Snap for 10412050 from d3a79b43853023f8a922cf05eb64fc43835eb3bf to studio-hedgehog-release
Change-Id: I3d5753e115459d51778b3f72443db4eacf9262ae
diff --git a/adt-ui/src/main/java/com/android/tools/adtui/categorytable/CategoryTable.kt b/adt-ui/src/main/java/com/android/tools/adtui/categorytable/CategoryTable.kt
index bdb8a36..5a643a6 100644
--- a/adt-ui/src/main/java/com/android/tools/adtui/categorytable/CategoryTable.kt
+++ b/adt-ui/src/main/java/com/android/tools/adtui/categorytable/CategoryTable.kt
@@ -171,8 +171,8 @@
addMouseListener(
object : MouseAdapter() {
- override fun mouseClicked(e: MouseEvent) {
- mouseClickedOnRow(e)
+ override fun mousePressed(e: MouseEvent) {
+ mousePressedOnRow(e)
}
}
)
@@ -229,7 +229,7 @@
internal fun indexOf(key: RowKey<T>): Int = rowComponents.indexOfFirst { it.rowKey == key }
- private fun mouseClickedOnRow(e: MouseEvent) {
+ private fun mousePressedOnRow(e: MouseEvent) {
if (SwingUtilities.isLeftMouseButton(e)) {
rowComponents
.firstOrNull { it.isVisible && e.y < it.y + it.height }
diff --git a/adt-ui/src/main/java/com/android/tools/adtui/categorytable/Column.kt b/adt-ui/src/main/java/com/android/tools/adtui/categorytable/Column.kt
index 8d523a9..7a4b301 100644
--- a/adt-ui/src/main/java/com/android/tools/adtui/categorytable/Column.kt
+++ b/adt-ui/src/main/java/com/android/tools/adtui/categorytable/Column.kt
@@ -15,6 +15,7 @@
*/
package com.android.tools.adtui.categorytable
+import com.android.tools.adtui.event.DelegateMouseEventHandler
import com.intellij.ui.components.JBLabel
import javax.swing.JComponent
@@ -44,6 +45,13 @@
fun createUi(rowValue: T): U
/**
+ * If the UI for this column has MouseListeners, this delegate should be installed as an
+ * additional MouseListener if the standard mouse behavior for the row is still desired (i.e.,
+ * selecting the row).
+ */
+ fun installMouseDelegate(component: U, mouseDelegate: DelegateMouseEventHandler) {}
+
+ /**
* Updates the UI based on the current value. Updating the UI only in response to this method
* helps to ensure that the overall state of the table remains in sync.
*/
@@ -78,6 +86,9 @@
override fun createUi(rowValue: T) = JBLabel()
+ // KT-39603
+ override fun installMouseDelegate(component: JBLabel, mouseDelegate: DelegateMouseEventHandler) {}
+
override fun updateValue(rowValue: T, component: JBLabel, value: String) {
component.text = value
}
diff --git a/adt-ui/src/main/java/com/android/tools/adtui/categorytable/RowComponent.kt b/adt-ui/src/main/java/com/android/tools/adtui/categorytable/RowComponent.kt
index 8beef9e..c0e0b34 100644
--- a/adt-ui/src/main/java/com/android/tools/adtui/categorytable/RowComponent.kt
+++ b/adt-ui/src/main/java/com/android/tools/adtui/categorytable/RowComponent.kt
@@ -15,6 +15,7 @@
*/
package com.android.tools.adtui.categorytable
+import com.android.tools.adtui.event.DelegateMouseEventHandler
import com.intellij.openapi.actionSystem.DataKey
import com.intellij.openapi.actionSystem.DataProvider
import com.intellij.ui.components.JBLabel
@@ -128,9 +129,11 @@
initialValue: T,
primaryKey: Any
) : RowComponent<T>(), DataProvider {
+ private val mouseDelegate = DelegateMouseEventHandler.delegateTo(this)
+
/** The components of this row, in model order. */
val componentList: List<ColumnComponent<T, *, *>> =
- columns.map { ColumnComponent(it, initialValue) }
+ columns.map { ColumnComponent(it, initialValue, mouseDelegate) }
init {
layout = ValueRowLayout(header)
@@ -192,9 +195,11 @@
*/
internal class ColumnComponent<T, C, U : JComponent>(
val column: Column<T, C, U>,
- initialValue: T
+ initialValue: T,
+ mouseDelegate: DelegateMouseEventHandler,
) {
- val component = column.createUi(initialValue)
+ val component =
+ column.createUi(initialValue).also { column.installMouseDelegate(it, mouseDelegate) }
fun updateValue(rowValue: T) {
column.updateValue(rowValue, component, column.attribute.value(rowValue))
}
diff --git a/adt-ui/src/main/java/com/android/tools/adtui/compose/IssueNotificationAction.kt b/adt-ui/src/main/java/com/android/tools/adtui/compose/IssueNotificationAction.kt
index e20c27d..a48fe5e 100644
--- a/adt-ui/src/main/java/com/android/tools/adtui/compose/IssueNotificationAction.kt
+++ b/adt-ui/src/main/java/com/android/tools/adtui/compose/IssueNotificationAction.kt
@@ -180,6 +180,10 @@
return JBUI.insets(1)
}
+ open fun shouldHide(status: ComposeStatus, dataContext: DataContext) : Boolean {
+ return status.icon == null && StringUtil.isEmpty(status.title)
+ }
+
override fun createCustomComponent(presentation: Presentation, place: String): JComponent = IssueNotificationActionButton(this, presentation, place)
override fun updateCustomComponent(component: JComponent, presentation: Presentation) {
@@ -193,7 +197,7 @@
val presentation = e.presentation
createStatusInfo(project, e.dataContext)?.let {
presentation.apply {
- if (it.icon == null && StringUtil.isEmpty(it.title)) {
+ if (shouldHide(it, e.dataContext)) {
isEnabledAndVisible = false
return@let
}
diff --git a/adt-ui/src/main/java/com/android/tools/adtui/workbench/WorkBench.java b/adt-ui/src/main/java/com/android/tools/adtui/workbench/WorkBench.java
index 5939513..c657850 100644
--- a/adt-ui/src/main/java/com/android/tools/adtui/workbench/WorkBench.java
+++ b/adt-ui/src/main/java/com/android/tools/adtui/workbench/WorkBench.java
@@ -160,8 +160,8 @@
addToolsToModel(minimizedWindows);
if (!isDisposed) {
myWorkBenchManager.register(this);
+ KeyboardFocusManager.getCurrentKeyboardFocusManager().addPropertyChangeListener("focusOwner", myMyPropertyChangeListener);
}
- KeyboardFocusManager.getCurrentKeyboardFocusManager().addPropertyChangeListener("focusOwner", myMyPropertyChangeListener);
}
@Override
diff --git a/android-common/src/com/android/tools/idea/flags/StudioFlags.java b/android-common/src/com/android/tools/idea/flags/StudioFlags.java
index e8854b3..eb49a90 100644
--- a/android-common/src/com/android/tools/idea/flags/StudioFlags.java
+++ b/android-common/src/com/android/tools/idea/flags/StudioFlags.java
@@ -1176,9 +1176,9 @@
//region Compose
private static final FlagGroup COMPOSE = new FlagGroup(FLAGS, "compose", "Compose");
- public static final Flag<Boolean> COMPOSE_PREVIEW_LITE_MODE = Flag.create(
- COMPOSE, "preview.compose.lite.mode", "Enable Compose Preview Lite Mode",
- "If enabled, Preview Lite Mode will be enabled.",
+ public static final Flag<Boolean> COMPOSE_PREVIEW_ESSENTIALS_MODE = Flag.create(
+ COMPOSE, "preview.essentials.mode", "Enable Compose Preview Essentials Mode",
+ "If enabled, Compose Preview Essentials Mode will be enabled.",
true);
public static final Flag<Boolean> COMPOSE_PREVIEW_DOUBLE_RENDER = Flag.create(
diff --git a/android/integration/testSrc/com/android/tools/idea/LanguageHighlightingTest.kt b/android/integration/testSrc/com/android/tools/idea/LanguageHighlightingTest.kt
index 0cfb420..8f3f18a 100644
--- a/android/integration/testSrc/com/android/tools/idea/LanguageHighlightingTest.kt
+++ b/android/integration/testSrc/com/android/tools/idea/LanguageHighlightingTest.kt
@@ -32,7 +32,7 @@
val system: AndroidSystem = AndroidSystem.standard()
@Test
- fun basicShowUsages() {
+ fun kotlinHighlighting() {
val project = AndroidProject("tools/adt/idea/android/integration/testData/languagehighlighting")
// Create a maven repo and set it up in the installation and environment
diff --git a/android/rendering/testSrc/com/android/tools/idea/rendering/RenderServiceTest.java b/android/rendering/testSrc/com/android/tools/idea/rendering/RenderServiceTest.java
index f5c4fd5..55da9b4 100644
--- a/android/rendering/testSrc/com/android/tools/idea/rendering/RenderServiceTest.java
+++ b/android/rendering/testSrc/com/android/tools/idea/rendering/RenderServiceTest.java
@@ -19,6 +19,7 @@
import com.android.tools.rendering.RenderAsyncActionExecutor;
import com.android.tools.rendering.RenderService;
import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.atomic.AtomicReference;
import junit.framework.TestCase;
import java.util.concurrent.CountDownLatch;
@@ -88,4 +89,30 @@
assertEquals(renderActionCounter + 1, renderActionExecutor.getExecutedRenderActionCount());
assertTrue(called.get());
}
+
+ public void testRenderThreadCheck() throws Throwable {
+ assertFalse(RenderService.isRenderThread());
+ AtomicReference<Throwable> exceptionReference = new AtomicReference<>(null);
+ RenderService.getRenderAsyncActionExecutor().runAsyncAction(() -> {
+ assertTrue(RenderService.isRenderThread());
+ Thread thread = new Thread(() -> {
+ assertTrue(RenderService.isRenderThread());
+ });
+
+ thread.setUncaughtExceptionHandler((t, e) -> {
+ exceptionReference.set(e);
+ });
+ thread.start();
+ try {
+ thread.join();
+ }
+ catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ }
+ }).get();
+
+ Throwable exception = exceptionReference.get();
+ if (exception != null) throw exception;
+ assertFalse(RenderService.isRenderThread());
+ }
}
\ No newline at end of file
diff --git a/android/src/com/android/tools/idea/avdmanager/Category.java b/android/src/com/android/tools/idea/avdmanager/Category.java
index e7f006e..441879c 100644
--- a/android/src/com/android/tools/idea/avdmanager/Category.java
+++ b/android/src/com/android/tools/idea/avdmanager/Category.java
@@ -22,19 +22,17 @@
import org.jetbrains.annotations.NotNull;
enum Category {
- PHONE("Phone", "Pixel Fold", definition ->
+ PHONE("Phone", "pixel_fold", definition ->
!definition.getIsDeprecated() && definition.getTagId() == null && !hasTabletScreen(definition)),
- TABLET("Tablet", "Pixel Tablet", definition ->
+ TABLET("Tablet", "pixel_tablet", definition ->
!definition.getIsDeprecated() && definition.getTagId() == null && hasTabletScreen(definition)),
- WEAR_OS("Wear OS", "Wear OS Square", definition -> !definition.getIsDeprecated() && HardwareConfigHelper.isWear(definition)),
- DESKTOP("Desktop", "Medium Desktop", definition -> !definition.getIsDeprecated() && HardwareConfigHelper.isDesktop(definition)),
+ WEAR_OS("Wear OS", "wearos_square", definition -> !definition.getIsDeprecated() && HardwareConfigHelper.isWear(definition)),
+ DESKTOP("Desktop", "desktop_medium", definition -> !definition.getIsDeprecated() && HardwareConfigHelper.isDesktop(definition)),
+ TV("TV", "tv_1080p", definition -> !definition.getIsDeprecated() && (HardwareConfigHelper.isTv(definition) || hasTvScreen(definition))),
- TV("TV", "Television (1080p)", definition ->
- !definition.getIsDeprecated() && (HardwareConfigHelper.isTv(definition) || hasTvScreen(definition))),
-
- AUTOMOTIVE("Automotive", "Automotive (1024p landscape)", definition ->
+ AUTOMOTIVE("Automotive", "automotive_1024p_landscape", definition ->
!definition.getIsDeprecated() && HardwareConfigHelper.isAutomotive(definition)),
LEGACY("Legacy", "Nexus S", Device::getIsDeprecated);
@@ -43,7 +41,7 @@
private final String myName;
@NotNull
- private final String myDefaultDefinitionName;
+ private final String myDefaultDefinitionId;
@NotNull
private final Predicate<Device> myPredicate;
@@ -57,15 +55,15 @@
return definition.getDefaultHardware().getScreen().getDiagonalLength() >= Device.MINIMUM_TV_SIZE;
}
- Category(@NotNull String name, @NotNull String defaultDefinitionName, @NotNull Predicate<Device> predicate) {
+ Category(@NotNull String name, @NotNull String defaultDefinitionId, @NotNull Predicate<Device> predicate) {
myName = name;
- myDefaultDefinitionName = defaultDefinitionName;
+ myDefaultDefinitionId = defaultDefinitionId;
myPredicate = predicate;
}
@NotNull
- final String getDefaultDefinitionName() {
- return myDefaultDefinitionName;
+ final String getDefaultDefinitionId() {
+ return myDefaultDefinitionId;
}
@NotNull
diff --git a/android/src/com/android/tools/idea/avdmanager/DeviceDefinitionList.java b/android/src/com/android/tools/idea/avdmanager/DeviceDefinitionList.java
index 0fb940b..a1c88d8 100644
--- a/android/src/com/android/tools/idea/avdmanager/DeviceDefinitionList.java
+++ b/android/src/com/android/tools/idea/avdmanager/DeviceDefinitionList.java
@@ -17,6 +17,7 @@
import com.android.sdklib.devices.Device;
import com.android.tools.adtui.common.ColoredIconGenerator;
+import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Multimap;
import com.google.common.collect.MultimapBuilder;
@@ -97,10 +98,21 @@
private final List<DeviceDefinitionSelectionListener> myListeners = new ArrayList<>();
private final List<DeviceCategorySelectionListener> myCategoryListeners = new ArrayList<>();
private Collection<Device> myDevices;
+
+ @NotNull
+ private final DeviceSupplier mySupplier;
+
private Device myDefaultDevice;
- public DeviceDefinitionList() {
+ @SuppressWarnings("unused")
+ DeviceDefinitionList() {
+ this(new DeviceSupplier());
+ }
+
+ @VisibleForTesting
+ DeviceDefinitionList(@NotNull DeviceSupplier supplier) {
super(new BorderLayout());
+ mySupplier = supplier;
refreshDeviceProfiles();
setDefaultDevices();
@@ -143,10 +155,10 @@
@NotNull
private Device getDefaultDefinition(@NotNull Category category) {
- var definition = category.getDefaultDefinitionName();
+ var id = category.getDefaultDefinitionId();
return myCategoryToDefinitionMultimap.get(category).stream()
- .filter(d -> d.getDisplayName().equals(definition))
+ .filter(definition -> definition.getId().equals(id))
.findFirst()
.orElseThrow();
}
@@ -270,7 +282,7 @@
}
private void refreshDeviceProfiles() {
- myDevices = new DeviceSupplier().get();
+ myDevices = mySupplier.get();
myCategoryToDefinitionMultimap = myDevices.stream()
.collect(Multimaps.toMultimap(Category::valueOfDefinition,
diff --git a/android/src/com/android/tools/idea/editors/liveedit/ui/LiveEditNotificationAction.kt b/android/src/com/android/tools/idea/editors/liveedit/ui/LiveEditNotificationAction.kt
index a32e36a..90d371b 100644
--- a/android/src/com/android/tools/idea/editors/liveedit/ui/LiveEditNotificationAction.kt
+++ b/android/src/com/android/tools/idea/editors/liveedit/ui/LiveEditNotificationAction.kt
@@ -16,6 +16,7 @@
package com.android.tools.idea.editors.liveedit.ui
import com.android.ddmlib.IDevice
+import com.android.tools.adtui.compose.ComposeStatus
import com.android.tools.adtui.compose.InformationPopup
import com.android.tools.adtui.compose.InformationPopupImpl
import com.android.tools.adtui.compose.IssueNotificationAction
@@ -37,6 +38,7 @@
import com.intellij.openapi.actionSystem.DataProvider
import com.intellij.openapi.actionSystem.DefaultActionGroup
import com.intellij.openapi.actionSystem.PlatformCoreDataKeys
+import com.intellij.openapi.actionSystem.PlatformDataKeys
import com.intellij.openapi.actionSystem.RightAlignedToolbarAction
import com.intellij.openapi.actionSystem.Separator
import com.intellij.openapi.actionSystem.ex.ActionUtil
@@ -83,7 +85,7 @@
dataContext: DataContext,
): InformationPopup? {
return getStatusInfo(project, dataContext).let { status ->
- if (status == LiveEditStatus.Disabled) {
+ if (shouldHideImpl(status, dataContext)) {
return@let null
}
@@ -181,11 +183,19 @@
return JBUI.insets(2)
}
+ override fun shouldHide(status: ComposeStatus, dataContext: DataContext): Boolean {
+ return shouldHideImpl(status, dataContext)
+ }
+
override fun getActionUpdateThread(): ActionUpdateThread {
return ActionUpdateThread.EDT
}
}
+private fun shouldHideImpl(status: ComposeStatus, dataContext: DataContext): Boolean {
+ return status == LiveEditStatus.Disabled && dataContext.getData(PlatformDataKeys.TOOL_WINDOW)?.id != RUNNING_DEVICES_TOOL_WINDOW_ID
+}
+
/**
* [DefaultActionGroup] that shows the notification chip and the [RedeployAction] button when applicable.
*/
diff --git a/android/src/com/android/tools/idea/rendering/StudioRenderSecurityManager.kt b/android/src/com/android/tools/idea/rendering/StudioRenderSecurityManager.kt
index a67d6b0..87f59cc 100644
--- a/android/src/com/android/tools/idea/rendering/StudioRenderSecurityManager.kt
+++ b/android/src/com/android/tools/idea/rendering/StudioRenderSecurityManager.kt
@@ -16,14 +16,21 @@
package com.android.tools.idea.rendering
import com.android.tools.adtui.webp.WebpNativeLibHelper
+import com.android.tools.rendering.RenderService
import com.android.tools.rendering.security.RenderSecurityException
import com.android.tools.rendering.security.RenderSecurityManager
+import com.android.tools.rendering.security.RenderSecurityManagerDefaults
import java.io.File
/** Studio-specific [RenderSecurityManager]. */
-class StudioRenderSecurityManager(
- sdkPath: String?, projectPath: String?, restrictReads: Boolean
-) : RenderSecurityManager(sdkPath, projectPath, restrictReads) {
+class StudioRenderSecurityManager(sdkPath: String?, projectPath: String?, restrictReads: Boolean) :
+ RenderSecurityManager(
+ sdkPath,
+ projectPath,
+ restrictReads,
+ RenderSecurityManagerDefaults.getDefaultAllowedPaths(),
+ RenderService::isRenderThread
+ ) {
override fun checkLink(lib: String) {
// Allow linking with relative paths
// Needed to for example load the "fontmanager" library from layout lib (from the
@@ -39,4 +46,4 @@
throw RenderSecurityException.create("Link", lib)
}
}
-}
\ No newline at end of file
+}
diff --git a/android/src/com/android/tools/idea/run/deployment/liveedit/LiveEditStatus.kt b/android/src/com/android/tools/idea/run/deployment/liveedit/LiveEditStatus.kt
index aa286bd..867bb4f 100644
--- a/android/src/com/android/tools/idea/run/deployment/liveedit/LiveEditStatus.kt
+++ b/android/src/com/android/tools/idea/run/deployment/liveedit/LiveEditStatus.kt
@@ -123,7 +123,7 @@
}
}
- object Disabled : LiveEditStatus(null, "", "", DISABLED)
+ object Disabled : LiveEditStatus(AllIcons.General.Warning, "Live Edit disabled", "Live Edit is disabled.", DISABLED)
object UnrecoverableError :
LiveEditStatus(
diff --git a/android/src/com/android/tools/idea/stats/AndroidStudioEventLogger.kt b/android/src/com/android/tools/idea/stats/AndroidStudioEventLogger.kt
index 13195fb..50509a3 100644
--- a/android/src/com/android/tools/idea/stats/AndroidStudioEventLogger.kt
+++ b/android/src/com/android/tools/idea/stats/AndroidStudioEventLogger.kt
@@ -18,7 +18,11 @@
import com.android.tools.analytics.UsageTracker
import com.android.tools.analytics.withProjectId
import com.google.wireless.android.sdk.stats.AndroidStudioEvent
+import com.google.wireless.android.sdk.stats.AndroidStudioEvent.EventKind.DEBUGGER_EVENT
import com.google.wireless.android.sdk.stats.DebuggerEvent
+import com.google.wireless.android.sdk.stats.DebuggerEvent.FramesViewUpdated
+import com.google.wireless.android.sdk.stats.DebuggerEvent.FramesViewUpdated.FileTypeInfo
+import com.google.wireless.android.sdk.stats.DebuggerEvent.Type.FRAMES_VIEW_UPDATED
import com.google.wireless.android.sdk.stats.FileType
import com.google.wireless.android.sdk.stats.FileUsage
import com.google.wireless.android.sdk.stats.KotlinGradlePerformance
@@ -31,8 +35,10 @@
import com.intellij.internal.statistic.eventLog.EventLogConfiguration
import com.intellij.internal.statistic.eventLog.EventLogGroup
import com.intellij.internal.statistic.eventLog.StatisticsEventLogger
+import com.intellij.internal.statistic.eventLog.events.EventFields
import com.intellij.openapi.project.ProjectManager
import com.intellij.openapi.project.getProjectCacheFileName
+import com.intellij.xdebugger.impl.XDebuggerActionsCollector
import org.jetbrains.kotlin.statistics.metrics.BooleanMetrics
import org.jetbrains.kotlin.statistics.metrics.StringMetrics
import java.util.Locale
@@ -52,6 +58,7 @@
"run.configuration.exec" -> logRunConfigurationExec(eventId, data)
"vfs" -> logVfsEvent(eventId, data)
"debugger.breakpoints.usage" -> logDebuggerBreakpointsUsage(eventId, data)
+ "xdebugger.actions" -> logDebuggerEvent(eventId, data)
}
return CompletableFuture.completedFuture(null)
}
@@ -243,6 +250,43 @@
}
+ private fun logDebuggerEvent(eventId: String, data: Map<String, Any>) {
+ val event = when (eventId) {
+ XDebuggerActionsCollector.EVENT_FRAMES_UPDATED -> {
+ @Suppress("UnstableApiUsage")
+ val durationMs = data[EventFields.DurationMs.name] as? Long ?: return
+ val totalFrames = data[XDebuggerActionsCollector.TOTAL_FRAMES] as? Int ?: return
+
+ @Suppress("UNCHECKED_CAST")
+ val fileTypes = data[XDebuggerActionsCollector.FILE_TYPES] as? List<String> ?: return
+
+ @Suppress("UNCHECKED_CAST")
+ val framesPerFileType = data[XDebuggerActionsCollector.FRAMES_PER_TYPE] as? List<Int> ?: return
+
+ val fileTypeInfos = fileTypes.zip(framesPerFileType).map { (type, frames) ->
+ FileTypeInfo.newBuilder()
+ .setFileType(type)
+ .setNumFrames(frames)
+ .build()
+ }
+ DebuggerEvent.newBuilder()
+ .setType(FRAMES_VIEW_UPDATED)
+ .setFramesViewUpdated(
+ FramesViewUpdated.newBuilder()
+ .setDurationMs(durationMs)
+ .setTotalFrames(totalFrames)
+ .addAllFileTypeInfos(fileTypeInfos)
+ )
+ }
+
+ else -> return
+ }
+ UsageTracker.log(
+ AndroidStudioEvent.newBuilder()
+ .setKind(DEBUGGER_EVENT)
+ .setDebuggerEvent(event))
+ }
+
/**
* Adds the associated project from the IntelliJ anonymization project id to the builder
*/
diff --git a/android/src/org/jetbrains/android/uipreview/AndroidEditorSettings.java b/android/src/org/jetbrains/android/uipreview/AndroidEditorSettings.java
index 502cc02..3f66f31d 100644
--- a/android/src/org/jetbrains/android/uipreview/AndroidEditorSettings.java
+++ b/android/src/org/jetbrains/android/uipreview/AndroidEditorSettings.java
@@ -106,7 +106,7 @@
private EditorMode myPreferredComposableEditorMode;
private EditorMode myPreferredKotlinEditorMode;
private double myMagnifySensitivity = DEFAULT_MAGNIFY_SENSITIVITY;
- private boolean myComposePreviewLiteModeEnabled = false;
+ private boolean myComposePreviewEssentialsModeEnabled = false;
public EditorMode getPreferredEditorMode() {
return myPreferredEditorMode;
@@ -148,12 +148,12 @@
myMagnifySensitivity = magnifySensitivity;
}
- public boolean isComposePreviewLiteModeEnabled() {
- return myComposePreviewLiteModeEnabled;
+ public boolean isComposePreviewEssentialsModeEnabled() {
+ return myComposePreviewEssentialsModeEnabled;
}
- public void setComposePreviewLiteModeEnabled(boolean composePreviewLiteModeEnabled) {
- myComposePreviewLiteModeEnabled = composePreviewLiteModeEnabled;
+ public void setComposePreviewEssentialsModeEnabled(boolean composePreviewEssentialsModeEnabled) {
+ myComposePreviewEssentialsModeEnabled = composePreviewEssentialsModeEnabled;
}
}
}
diff --git a/compose-designer/src/com/android/tools/idea/compose/preview/ComposePreviewBundle.properties b/compose-designer/src/com/android/tools/idea/compose/preview/ComposePreviewBundle.properties
index ad9b681..38b7aa2 100644
--- a/compose-designer/src/com/android/tools/idea/compose/preview/ComposePreviewBundle.properties
+++ b/compose-designer/src/com/android/tools/idea/compose/preview/ComposePreviewBundle.properties
@@ -8,31 +8,31 @@
<li><code>updateTransition</code></li>\
<li><code>rememberInfiniteTransition</code></li>\
</ul></html>
-action.animation.inspector.lite.mode.description=Animation Preview is not available in Compose Preview Essentials Mode
+action.animation.inspector.essentials.mode.description=Animation Preview is not available in Compose Preview Essentials Mode
action.stop.animation.inspector.title=Stop Animation Preview
action.stop.animation.inspector.description=Closes the Animation Preview
action.run.title=Run Preview
action.run.description=Run this preview in isolation on the emulator or running device
-action.run.lite.mode.description=Run Preview is not available in Compose Preview Essentials Mode
+action.run.essentials.mode.description=Run Preview is not available in Compose Preview Essentials Mode
action.run.description.test.files=Running the preview is not supported in test files
action.group.switch.title=Switch Preview Group
action.interactive.title=Start Interactive Mode
action.interactive.description=Live preview to test gestures
-action.interactive.lite.mode.description=Interactive Mode is not available in Compose Preview Essentials Mode
+action.interactive.essentials.mode.description=Interactive Mode is not available in Compose Preview Essentials Mode
action.jump.to.definition=Jump to Definition
action.stop.interactive.title=Stop Interactive Mode
action.stop.interactive.description=Stops interactive mode
action.scene.mode.colorblind.dropdown.title=Color Blind Modes
action.scene.view.control.title=View Control
action.scene.view.control.description=Control the views and layout of preview elements
-action.scene.view.control.lite.mode.description=Some actions are not available in Compose Preview Essentials Mode
+action.scene.view.control.essentials.mode.description=Some actions are not available in Compose Preview Essentials Mode
action.scene.view.control.show.inspection.tooltip=Show Inspection Tooltips
action.scene.view.control.start.filter.preview.mode=Filter Previews
action.scene.view.control.no.matched.result=no result
action.scene.view.control.one.matched.result=1 result
action.scene.view.control.multiple.matched.results={0} results
action.uicheck.description=Check this preview for common screen size and accessibility issues
-action.uicheck.lite.mode.description=UI Check is not available in Compose Preview Essentials Mode
+action.uicheck.essentials.mode.description=UI Check is not available in Compose Preview Essentials Mode
action.uicheck.title=Start UI Check Mode
action.stop.uicheck.description=Stops UI Check mode
action.stop.uicheck.title=Stop UI Check
@@ -43,7 +43,7 @@
action.preview.fast.refresh.enable.title=Enable Live Updates
action.preview.fast.refresh.disable.title=Disable Live Updates
action.preview.fast.refresh.toggle.description=Toggles the state of the Preview Live Edit refresh
-action.preview.fast.refresh.disabled.in.lite.mode.description=Preview Live Updates are disabled in Essentials Mode
+action.preview.fast.refresh.disabled.in.essentials.mode.description=Preview Live Updates are disabled in Essentials Mode
action.open.issues.panel.title=Open Issues Panel
action.view.problems=View Problems
action.zoom.to.selection=Zoom to Selection
diff --git a/compose-designer/src/com/android/tools/idea/compose/preview/ComposePreviewRepresentationProvider.kt b/compose-designer/src/com/android/tools/idea/compose/preview/ComposePreviewRepresentationProvider.kt
index 643a270..a00431a 100644
--- a/compose-designer/src/com/android/tools/idea/compose/preview/ComposePreviewRepresentationProvider.kt
+++ b/compose-designer/src/com/android/tools/idea/compose/preview/ComposePreviewRepresentationProvider.kt
@@ -30,7 +30,7 @@
import com.android.tools.idea.compose.preview.actions.StopInteractivePreviewAction
import com.android.tools.idea.compose.preview.actions.StopUiCheckPreviewAction
import com.android.tools.idea.compose.preview.actions.visibleOnlyInComposeStaticPreview
-import com.android.tools.idea.compose.preview.lite.ComposePreviewLiteModeManager
+import com.android.tools.idea.compose.preview.essentials.ComposePreviewEssentialsModeManager
import com.android.tools.idea.editors.sourcecode.isKotlinFileType
import com.android.tools.idea.flags.StudioFlags
import com.android.tools.idea.preview.representation.CommonRepresentationEditorFileType
@@ -74,7 +74,7 @@
layoutManagers = PREVIEW_LAYOUT_MANAGER_OPTIONS
) {
!isAnyPreviewRefreshing(it.dataContext) &&
- !ComposePreviewLiteModeManager.isLiteModeEnabled
+ !ComposePreviewEssentialsModeManager.isEssentialsModeEnabled
}
.visibleOnlyInComposeStaticPreview(),
StudioFlags.COMPOSE_DEBUG_BOUNDS.ifEnabled { ShowDebugBoundaries() },
diff --git a/compose-designer/src/com/android/tools/idea/compose/preview/ComposePreviewTroubleInfoCollector.kt b/compose-designer/src/com/android/tools/idea/compose/preview/ComposePreviewTroubleInfoCollector.kt
index ee69a59..ab8c56f 100644
--- a/compose-designer/src/com/android/tools/idea/compose/preview/ComposePreviewTroubleInfoCollector.kt
+++ b/compose-designer/src/com/android/tools/idea/compose/preview/ComposePreviewTroubleInfoCollector.kt
@@ -15,12 +15,12 @@
*/
package com.android.tools.idea.compose.preview
-import com.android.tools.idea.compose.preview.lite.ComposePreviewLiteModeManager
+import com.android.tools.idea.compose.preview.essentials.ComposePreviewEssentialsModeManager
import com.intellij.openapi.project.Project
import com.intellij.troubleshooting.TroubleInfoCollector
/** [TroubleInfoCollector] to collect information related to Compose Preview. */
class ComposePreviewTroubleInfoCollector : TroubleInfoCollector {
override fun collectInfo(project: Project) =
- "Compose Preview Essentials Mode enabled: ${ComposePreviewLiteModeManager.isLiteModeEnabled}"
+ "Compose Preview Essentials Mode enabled: ${ComposePreviewEssentialsModeManager.isEssentialsModeEnabled}"
}
diff --git a/compose-designer/src/com/android/tools/idea/compose/preview/ComposePreviewViewImpl.kt b/compose-designer/src/com/android/tools/idea/compose/preview/ComposePreviewViewImpl.kt
index 90a39ab..1af1003 100644
--- a/compose-designer/src/com/android/tools/idea/compose/preview/ComposePreviewViewImpl.kt
+++ b/compose-designer/src/com/android/tools/idea/compose/preview/ComposePreviewViewImpl.kt
@@ -28,9 +28,9 @@
import com.android.tools.idea.common.surface.DesignSurface
import com.android.tools.idea.common.surface.GuiInputHandler
import com.android.tools.idea.common.surface.handleLayoutlibNativeCrash
-import com.android.tools.idea.compose.preview.lite.ComposeEssentialsMode
-import com.android.tools.idea.compose.preview.lite.ComposePreviewLiteModeManager
-import com.android.tools.idea.compose.preview.lite.EssentialsModeWrapperPanel
+import com.android.tools.idea.compose.preview.essentials.ComposeEssentialsMode
+import com.android.tools.idea.compose.preview.essentials.ComposePreviewEssentialsModeManager
+import com.android.tools.idea.compose.preview.essentials.EssentialsModeWrapperPanel
import com.android.tools.idea.editors.build.ProjectBuildStatusManager
import com.android.tools.idea.editors.build.ProjectStatus
import com.android.tools.idea.editors.notifications.NotificationPanel
@@ -145,9 +145,9 @@
(PreviewDisplaySettings, LayoutlibSceneManager) -> LayoutlibSceneManager
): List<ComposePreviewElement> {
return mainSurface.updatePreviewsAndRefresh(
- // Don't reuse models when in lite mode to avoid briefly showing an unexpected/mixed
+ // Don't reuse models when in essentials mode to avoid briefly showing an unexpected/mixed
// state of the old and new preview.
- tryReusingModels = !ComposePreviewLiteModeManager.isLiteModeEnabled,
+ tryReusingModels = !ComposePreviewEssentialsModeManager.isEssentialsModeEnabled,
reinflate,
previewElements,
Logger.getInstance(ComposePreviewView::class.java),
@@ -365,7 +365,7 @@
set(value) {
// Avoid repeated values.
if (value == field) return
- // If lite mode is enabled,disabled or updated - components should be rearranged.
+ // If essentials mode is enabled,disabled or updated - components should be rearranged.
// Remove components from its existing places.
if (field == null) {
content.remove(mainSurface)
diff --git a/compose-designer/src/com/android/tools/idea/compose/preview/Filtering.kt b/compose-designer/src/com/android/tools/idea/compose/preview/Filtering.kt
index 3c00934..d89d515 100644
--- a/compose-designer/src/com/android/tools/idea/compose/preview/Filtering.kt
+++ b/compose-designer/src/com/android/tools/idea/compose/preview/Filtering.kt
@@ -60,7 +60,7 @@
) : PreviewElementProvider<ComposePreviewElementInstance> {
/**
* The Composable [ComposePreviewElementInstance] to filter. If no [ComposePreviewElementInstance]
- * is defined by that intsance, then this filter will return all the available previews.
+ * is defined by that instance, then this filter will return all the available previews.
*/
@Volatile var instance: ComposePreviewElementInstance? = null
diff --git a/compose-designer/src/com/android/tools/idea/compose/preview/Preview.kt b/compose-designer/src/com/android/tools/idea/compose/preview/Preview.kt
index 03f41db..6be7351 100644
--- a/compose-designer/src/com/android/tools/idea/compose/preview/Preview.kt
+++ b/compose-designer/src/com/android/tools/idea/compose/preview/Preview.kt
@@ -31,10 +31,10 @@
import com.android.tools.idea.compose.preview.PreviewGroup.Companion.ALL_PREVIEW_GROUP
import com.android.tools.idea.compose.preview.animation.ComposePreviewAnimationManager
import com.android.tools.idea.compose.preview.designinfo.hasDesignInfoProviders
+import com.android.tools.idea.compose.preview.essentials.ComposeEssentialsMode
+import com.android.tools.idea.compose.preview.essentials.ComposePreviewEssentialsModeManager
import com.android.tools.idea.compose.preview.fast.FastPreviewSurface
import com.android.tools.idea.compose.preview.fast.requestFastPreviewRefreshAndTrack
-import com.android.tools.idea.compose.preview.lite.ComposeEssentialsMode
-import com.android.tools.idea.compose.preview.lite.ComposePreviewLiteModeManager
import com.android.tools.idea.compose.preview.navigation.ComposePreviewNavigationHandler
import com.android.tools.idea.compose.preview.scene.ComposeSceneComponentProvider
import com.android.tools.idea.compose.preview.scene.ComposeScreenViewProvider
@@ -408,21 +408,21 @@
/**
* Updates the [composeWorkBench]'s [ComposeEssentialsMode] according to the state of Android
- * Studio Essentials Mode or Compose Preview Lite Mode.
+ * Studio (and/or Compose Preview) Essentials Mode.
*
* @param sourceEventType type of the event that triggered the update
*/
private fun updateEssentialsMode(
sourceEventType: ComposePreviewLiteModeEvent.ComposePreviewLiteModeEventType? = null
) {
- val liteModeIsEnabled = ComposePreviewLiteModeManager.isLiteModeEnabled
- val composeEssentialsModeIsSet = composeWorkBench.essentialsMode != null
+ val essentialsModeIsEnabled = ComposePreviewEssentialsModeManager.isEssentialsModeEnabled
+ val composePreviewViewEssentialsModeIsSet = composeWorkBench.essentialsMode != null
// Only update essentials mode if needed
- if (liteModeIsEnabled == composeEssentialsModeIsSet) return
+ if (essentialsModeIsEnabled == composePreviewViewEssentialsModeIsSet) return
- if (composeEssentialsModeIsSet) {
+ if (composePreviewViewEssentialsModeIsSet) {
composeWorkBench.essentialsMode = null
- singlePreviewElementInstance = null // Remove filter applied by lite mode.
+ singlePreviewElementInstance = null // Remove filter applied by essentials mode.
} else {
composeWorkBench.essentialsMode = ComposeEssentialsMode(composeWorkBench.mainSurface)
}
@@ -832,12 +832,12 @@
}
// If Fast Preview is enabled, prefetch the daemon for the current configuration.
- // This should not happen when lite mode is enabled.
+ // This should not happen when essentials mode is enabled.
if (
module != null &&
!module.isDisposed &&
FastPreviewManager.getInstance(project).isEnabled &&
- !ComposePreviewLiteModeManager.isLiteModeEnabled
+ !ComposePreviewEssentialsModeManager.isEssentialsModeEnabled
) {
FastPreviewManager.getInstance(project).preStartDaemon(module)
}
@@ -1016,7 +1016,7 @@
!EssentialsMode.isEnabled() &&
interactiveModeFlow.value.isStoppingOrDisabled() &&
!animationInspection.get() &&
- !ComposePreviewLiteModeManager.isLiteModeEnabled
+ !ComposePreviewEssentialsModeManager.isEssentialsModeEnabled
)
requestRefresh()
}
@@ -1026,12 +1026,12 @@
/**
* Whether fast preview is available. In addition to checking its normal availability from
- * [FastPreviewManager], we also verify that lite mode is not enabled, because fast preview should
- * not be available in this case.
+ * [FastPreviewManager], we also verify that essentials mode is not enabled, because fast preview
+ * should not be available in this case.
*/
private fun isFastPreviewAvailable() =
FastPreviewManager.getInstance(project).isAvailable &&
- !ComposePreviewLiteModeManager.isLiteModeEnabled
+ !ComposePreviewEssentialsModeManager.isEssentialsModeEnabled
override fun onActivate() {
lifecycleManager.activate()
@@ -1195,9 +1195,10 @@
)
}
// Some Composables (e.g. Popup) delay their content placement and wrap them into a coroutine
- // controlled by the Compose clock. For that reason, we need to call executeCallbacksAsync()
- // once, to make sure the queued behaviors are triggered and displayed in static preview.
- surface.sceneManagers.forEach { it.executeCallbacksAsync() }
+ // controlled by the Compose clock. For that reason, we need to call
+ // executeCallbacksAndRequestRender() once, to make sure the queued behaviors are triggered
+ // and displayed in static preview.
+ surface.sceneManagers.forEach { it.executeCallbacksAndRequestRender(null) }
}
/**
@@ -1215,7 +1216,9 @@
.setComposePreviewLiteModeEvent(
ComposePreviewLiteModeEvent.newBuilder()
.setType(eventType)
- .setIsComposePreviewLiteMode(ComposePreviewLiteModeManager.isLiteModeEnabled)
+ .setIsComposePreviewLiteMode(
+ ComposePreviewEssentialsModeManager.isEssentialsModeEnabled
+ )
)
)
}
diff --git a/compose-designer/src/com/android/tools/idea/compose/preview/actions/AnimationInspectorAction.kt b/compose-designer/src/com/android/tools/idea/compose/preview/actions/AnimationInspectorAction.kt
index cb1fe97..b79680d 100644
--- a/compose-designer/src/com/android/tools/idea/compose/preview/actions/AnimationInspectorAction.kt
+++ b/compose-designer/src/com/android/tools/idea/compose/preview/actions/AnimationInspectorAction.kt
@@ -17,7 +17,7 @@
import com.android.tools.idea.compose.preview.COMPOSE_PREVIEW_ELEMENT_INSTANCE
import com.android.tools.idea.compose.preview.COMPOSE_PREVIEW_MANAGER
-import com.android.tools.idea.compose.preview.lite.ComposePreviewLiteModeManager
+import com.android.tools.idea.compose.preview.essentials.ComposePreviewEssentialsModeManager
import com.android.tools.idea.compose.preview.message
import com.intellij.openapi.actionSystem.AnActionEvent
import com.intellij.openapi.actionSystem.DataContext
@@ -43,13 +43,14 @@
override fun updateButton(e: AnActionEvent) {
super.updateButton(e)
e.presentation.apply {
- val isLiteModeEnabled = ComposePreviewLiteModeManager.isLiteModeEnabled
- isEnabled = !isLiteModeEnabled
+ val isEssentialsModeEnabled = ComposePreviewEssentialsModeManager.isEssentialsModeEnabled
+ isEnabled = !isEssentialsModeEnabled
// Only display the animation inspector icon if there are animations to be inspected.
isVisible = getPreviewElement()?.hasAnimations == true
- text = if (isLiteModeEnabled) null else message("action.animation.inspector.title")
+ text = if (isEssentialsModeEnabled) null else message("action.animation.inspector.title")
description =
- if (isLiteModeEnabled) message("action.animation.inspector.lite.mode.description")
+ if (isEssentialsModeEnabled)
+ message("action.animation.inspector.essentials.mode.description")
else message("action.animation.inspector.description")
}
}
diff --git a/compose-designer/src/com/android/tools/idea/compose/preview/actions/ComposeViewControlAction.kt b/compose-designer/src/com/android/tools/idea/compose/preview/actions/ComposeViewControlAction.kt
index 23602e8..bf37cf7 100644
--- a/compose-designer/src/com/android/tools/idea/compose/preview/actions/ComposeViewControlAction.kt
+++ b/compose-designer/src/com/android/tools/idea/compose/preview/actions/ComposeViewControlAction.kt
@@ -21,9 +21,9 @@
import com.android.tools.adtui.actions.ZoomOutAction
import com.android.tools.idea.actions.DESIGN_SURFACE
import com.android.tools.idea.compose.preview.analytics.PreviewCanvasTracker
+import com.android.tools.idea.compose.preview.essentials.ComposePreviewEssentialsModeManager
import com.android.tools.idea.compose.preview.isAnyPreviewRefreshing
import com.android.tools.idea.compose.preview.isPreviewFilterEnabled
-import com.android.tools.idea.compose.preview.lite.ComposePreviewLiteModeManager
import com.android.tools.idea.compose.preview.message
import com.android.tools.idea.flags.StudioFlags
import com.android.tools.idea.uibuilder.surface.LayoutManagerSwitcher
@@ -57,15 +57,18 @@
e.presentation.isEnabled = !isAnyPreviewRefreshing(e.dataContext)
e.presentation.isVisible = !isPreviewFilterEnabled(e.dataContext)
e.presentation.description =
- if (ComposePreviewLiteModeManager.isLiteModeEnabled)
- message("action.scene.view.control.lite.mode.description")
+ if (ComposePreviewEssentialsModeManager.isEssentialsModeEnabled)
+ message("action.scene.view.control.essentials.mode.description")
else message("action.scene.view.control.description")
}
@VisibleForTesting
public override fun updateActions(context: DataContext): Boolean {
removeAll()
- if (StudioFlags.COMPOSE_VIEW_FILTER.get() && !ComposePreviewLiteModeManager.isLiteModeEnabled) {
+ if (
+ StudioFlags.COMPOSE_VIEW_FILTER.get() &&
+ !ComposePreviewEssentialsModeManager.isEssentialsModeEnabled
+ ) {
DESIGN_SURFACE.getData(context)?.let { surface ->
add(ComposeShowFilterAction(surface))
addSeparator()
diff --git a/compose-designer/src/com/android/tools/idea/compose/preview/actions/DeployToDeviceAction.kt b/compose-designer/src/com/android/tools/idea/compose/preview/actions/DeployToDeviceAction.kt
index 6228455..e0a9eb3 100644
--- a/compose-designer/src/com/android/tools/idea/compose/preview/actions/DeployToDeviceAction.kt
+++ b/compose-designer/src/com/android/tools/idea/compose/preview/actions/DeployToDeviceAction.kt
@@ -18,7 +18,7 @@
import com.android.tools.compose.COMPOSE_PREVIEW_ACTIVITY_FQN
import com.android.tools.idea.compose.preview.COMPOSE_PREVIEW_ELEMENT_INSTANCE
import com.android.tools.idea.compose.preview.ComposePreviewElement
-import com.android.tools.idea.compose.preview.lite.ComposePreviewLiteModeManager
+import com.android.tools.idea.compose.preview.essentials.ComposePreviewEssentialsModeManager
import com.android.tools.idea.compose.preview.message
import com.android.tools.idea.compose.preview.previewProviderClassAndIndex
import com.android.tools.idea.compose.preview.runconfiguration.ComposePreviewRunConfiguration
@@ -60,14 +60,14 @@
val isTestFile =
previewElement()?.previewBodyPsi?.let { isTestFile(it.project, it.virtualFile) } ?: false
e.presentation.apply {
- val isLiteModeEnabled = ComposePreviewLiteModeManager.isLiteModeEnabled
- isEnabled = !isTestFile && !isLiteModeEnabled
+ val isEssentialsModeEnabled = ComposePreviewEssentialsModeManager.isEssentialsModeEnabled
+ isEnabled = !isTestFile && !isEssentialsModeEnabled
isVisible = true
- text = if (isLiteModeEnabled) null else message("action.run.title")
+ text = if (isEssentialsModeEnabled) null else message("action.run.title")
description =
if (isTestFile) message("action.run.description.test.files")
else {
- if (isLiteModeEnabled) message("action.run.lite.mode.description")
+ if (isEssentialsModeEnabled) message("action.run.essentials.mode.description")
else message("action.run.description")
}
}
diff --git a/compose-designer/src/com/android/tools/idea/compose/preview/actions/EnableInteractiveAction.kt b/compose-designer/src/com/android/tools/idea/compose/preview/actions/EnableInteractiveAction.kt
index 7c29bfc..d9d5f23 100644
--- a/compose-designer/src/com/android/tools/idea/compose/preview/actions/EnableInteractiveAction.kt
+++ b/compose-designer/src/com/android/tools/idea/compose/preview/actions/EnableInteractiveAction.kt
@@ -17,7 +17,7 @@
import com.android.tools.idea.compose.preview.COMPOSE_PREVIEW_ELEMENT_INSTANCE
import com.android.tools.idea.compose.preview.COMPOSE_PREVIEW_MANAGER
-import com.android.tools.idea.compose.preview.lite.ComposePreviewLiteModeManager
+import com.android.tools.idea.compose.preview.essentials.ComposePreviewEssentialsModeManager
import com.android.tools.idea.compose.preview.message
import com.android.tools.idea.concurrency.AndroidCoroutineScope
import com.intellij.openapi.actionSystem.AnActionEvent
@@ -41,12 +41,12 @@
override fun updateButton(e: AnActionEvent) {
super.updateButton(e)
- val isLiteModeEnabled = ComposePreviewLiteModeManager.isLiteModeEnabled
+ val isEssentialsModeEnabled = ComposePreviewEssentialsModeManager.isEssentialsModeEnabled
e.presentation.isVisible = true
- e.presentation.isEnabled = !isLiteModeEnabled
- e.presentation.text = if (isLiteModeEnabled) null else message("action.interactive.title")
+ e.presentation.isEnabled = !isEssentialsModeEnabled
+ e.presentation.text = if (isEssentialsModeEnabled) null else message("action.interactive.title")
e.presentation.description =
- if (isLiteModeEnabled) message("action.interactive.lite.mode.description")
+ if (isEssentialsModeEnabled) message("action.interactive.essentials.mode.description")
else message("action.interactive.description")
}
diff --git a/compose-designer/src/com/android/tools/idea/compose/preview/actions/EnableUiCheckAction.kt b/compose-designer/src/com/android/tools/idea/compose/preview/actions/EnableUiCheckAction.kt
index 6a80d6f..d04677a 100644
--- a/compose-designer/src/com/android/tools/idea/compose/preview/actions/EnableUiCheckAction.kt
+++ b/compose-designer/src/com/android/tools/idea/compose/preview/actions/EnableUiCheckAction.kt
@@ -18,7 +18,7 @@
import com.android.tools.idea.common.error.IssuePanelService
import com.android.tools.idea.compose.preview.COMPOSE_PREVIEW_ELEMENT_INSTANCE
import com.android.tools.idea.compose.preview.COMPOSE_PREVIEW_MANAGER
-import com.android.tools.idea.compose.preview.lite.ComposePreviewLiteModeManager
+import com.android.tools.idea.compose.preview.essentials.ComposePreviewEssentialsModeManager
import com.android.tools.idea.compose.preview.message
import com.android.tools.idea.flags.StudioFlags
import com.intellij.openapi.actionSystem.ActionUpdateThread
@@ -37,12 +37,12 @@
override fun updateButton(e: AnActionEvent) {
super.updateButton(e)
val isUiCheckModeEnabled = StudioFlags.NELE_COMPOSE_UI_CHECK_MODE.get()
- val isLiteModeEnabled = ComposePreviewLiteModeManager.isLiteModeEnabled
+ val isEssentialsModeEnabled = ComposePreviewEssentialsModeManager.isEssentialsModeEnabled
e.presentation.isVisible = isUiCheckModeEnabled
- e.presentation.isEnabled = isUiCheckModeEnabled && !isLiteModeEnabled
- e.presentation.text = if (isLiteModeEnabled) null else message("action.uicheck.title")
+ e.presentation.isEnabled = isUiCheckModeEnabled && !isEssentialsModeEnabled
+ e.presentation.text = if (isEssentialsModeEnabled) null else message("action.uicheck.title")
e.presentation.description =
- if (isLiteModeEnabled) message("action.uicheck.lite.mode.description")
+ if (isEssentialsModeEnabled) message("action.uicheck.essentials.mode.description")
else message("action.uicheck.description")
}
diff --git a/compose-designer/src/com/android/tools/idea/compose/preview/actions/SwitchSurfaceLayoutManagerAction.kt b/compose-designer/src/com/android/tools/idea/compose/preview/actions/SwitchSurfaceLayoutManagerAction.kt
index 0d1dfcb..83c2f77 100644
--- a/compose-designer/src/com/android/tools/idea/compose/preview/actions/SwitchSurfaceLayoutManagerAction.kt
+++ b/compose-designer/src/com/android/tools/idea/compose/preview/actions/SwitchSurfaceLayoutManagerAction.kt
@@ -18,7 +18,7 @@
import com.android.tools.adtui.actions.DropDownAction
import com.android.tools.idea.common.actions.ActionButtonWithToolTipDescription
import com.android.tools.idea.common.surface.DesignSurface.SceneViewAlignment
-import com.android.tools.idea.compose.preview.lite.ComposePreviewLiteModeManager
+import com.android.tools.idea.compose.preview.essentials.ComposePreviewEssentialsModeManager
import com.android.tools.idea.compose.preview.message
import com.android.tools.idea.uibuilder.surface.LayoutManagerSwitcher
import com.android.tools.idea.uibuilder.surface.layout.SurfaceLayoutManager
@@ -72,8 +72,8 @@
}
override fun isSelected(e: AnActionEvent): Boolean {
- if (ComposePreviewLiteModeManager.isLiteModeEnabled) {
- // In Lite Mode, the standard layout manager actions should be disabled, as Gallery
+ if (ComposePreviewEssentialsModeManager.isEssentialsModeEnabled) {
+ // In Essentials Mode, the standard layout manager actions should be disabled, as Gallery
// will be selected by default and a special action will be created for it.
return false
}
@@ -92,15 +92,16 @@
// We will only add the actions and be visible if there are more than one option
if (layoutManagers.size > 1) {
layoutManagers.forEach { add(SetSurfaceLayoutManagerAction(it)) }
- if (ComposePreviewLiteModeManager.isLiteModeEnabled) {
- // When lite mode is enabled, we add another action to the list, so users have a visual
- // indication that they're not in neither Grid nor List view. When/if Gallery becomes a
- // mode in the future that's independent of Lite Mode, we need to refactor this code.
+ if (ComposePreviewEssentialsModeManager.isEssentialsModeEnabled) {
+ // When essentials mode is enabled, we add another action to the list, so users have a
+ // visual indication that they're not in neither Grid nor List view. When/if Gallery
+ // becomes a mode in the future that's independent of Essentials Mode, we need to refactor
+ // this code.
val galleryAction =
object : ToggleAction(message("gallery.mode.title")) {
override fun getActionUpdateThread() = ActionUpdateThread.EDT
override fun isSelected(e: AnActionEvent) =
- ComposePreviewLiteModeManager.isLiteModeEnabled
+ ComposePreviewEssentialsModeManager.isEssentialsModeEnabled
override fun setSelected(e: AnActionEvent, state: Boolean) {}
override fun update(e: AnActionEvent) {
super.update(e)
diff --git a/compose-designer/src/com/android/tools/idea/compose/preview/actions/ToggleFastPreviewAction.kt b/compose-designer/src/com/android/tools/idea/compose/preview/actions/ToggleFastPreviewAction.kt
index bd144c8..6410268 100644
--- a/compose-designer/src/com/android/tools/idea/compose/preview/actions/ToggleFastPreviewAction.kt
+++ b/compose-designer/src/com/android/tools/idea/compose/preview/actions/ToggleFastPreviewAction.kt
@@ -15,9 +15,9 @@
*/
package com.android.tools.idea.compose.preview.actions
+import com.android.tools.idea.compose.preview.essentials.ComposePreviewEssentialsModeManager
import com.android.tools.idea.compose.preview.fast.FastPreviewSurface
import com.android.tools.idea.compose.preview.findComposePreviewManagersForContext
-import com.android.tools.idea.compose.preview.lite.ComposePreviewLiteModeManager
import com.android.tools.idea.compose.preview.message
import com.android.tools.idea.editors.fast.ManualDisabledReason
import com.android.tools.idea.editors.fast.fastPreviewManager
@@ -52,9 +52,9 @@
presentation.isEnabledAndVisible = false
return
}
- if (ComposePreviewLiteModeManager.isLiteModeEnabled) {
+ if (ComposePreviewEssentialsModeManager.isEssentialsModeEnabled) {
presentation.description =
- message("action.preview.fast.refresh.disabled.in.lite.mode.description")
+ message("action.preview.fast.refresh.disabled.in.essentials.mode.description")
presentation.isEnabled = false
} else {
presentation.description = message("action.preview.fast.refresh.toggle.description")
diff --git a/compose-designer/src/com/android/tools/idea/compose/preview/analytics/MultiPreviewUsageTracker.kt b/compose-designer/src/com/android/tools/idea/compose/preview/analytics/MultiPreviewUsageTracker.kt
index 5d3f3bc..24e6987 100644
--- a/compose-designer/src/com/android/tools/idea/compose/preview/analytics/MultiPreviewUsageTracker.kt
+++ b/compose-designer/src/com/android/tools/idea/compose/preview/analytics/MultiPreviewUsageTracker.kt
@@ -17,7 +17,7 @@
import com.android.tools.idea.common.analytics.DesignerUsageTrackerManager
import com.android.tools.idea.common.analytics.setApplicationId
-import com.android.tools.idea.compose.preview.lite.ComposePreviewLiteModeManager
+import com.android.tools.idea.compose.preview.essentials.ComposePreviewEssentialsModeManager
import com.android.tools.idea.preview.PreviewNode
import com.google.common.cache.Cache
import com.google.common.cache.CacheBuilder
@@ -91,7 +91,7 @@
.filter { !it.nodeInfo.isUseless() && !it.nodeInfo.isPreviewType() }
.map { it.nodeInfo.build() }
)
- .setIsComposePreviewLiteMode(ComposePreviewLiteModeManager.isLiteModeEnabled)
+ .setIsComposePreviewLiteMode(ComposePreviewEssentialsModeManager.isEssentialsModeEnabled)
fun build(): ComposeMultiPreviewEvent {
return eventBuilder.build()
diff --git a/compose-designer/src/com/android/tools/idea/compose/preview/lite/ComposeEssentialsMode.kt b/compose-designer/src/com/android/tools/idea/compose/preview/essentials/ComposeEssentialsMode.kt
similarity index 97%
rename from compose-designer/src/com/android/tools/idea/compose/preview/lite/ComposeEssentialsMode.kt
rename to compose-designer/src/com/android/tools/idea/compose/preview/essentials/ComposeEssentialsMode.kt
index a698578..5ffeb33 100644
--- a/compose-designer/src/com/android/tools/idea/compose/preview/lite/ComposeEssentialsMode.kt
+++ b/compose-designer/src/com/android/tools/idea/compose/preview/essentials/ComposeEssentialsMode.kt
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.tools.idea.compose.preview.lite
+package com.android.tools.idea.compose.preview.essentials
import com.android.tools.idea.compose.preview.findComposePreviewManagersForContext
import com.intellij.openapi.actionSystem.DataContext
diff --git a/compose-designer/src/com/android/tools/idea/compose/preview/lite/ComposePreviewLiteModeManager.kt b/compose-designer/src/com/android/tools/idea/compose/preview/essentials/ComposePreviewEssentialsModeManager.kt
similarity index 71%
rename from compose-designer/src/com/android/tools/idea/compose/preview/lite/ComposePreviewLiteModeManager.kt
rename to compose-designer/src/com/android/tools/idea/compose/preview/essentials/ComposePreviewEssentialsModeManager.kt
index 643a795..171cb53 100644
--- a/compose-designer/src/com/android/tools/idea/compose/preview/lite/ComposePreviewLiteModeManager.kt
+++ b/compose-designer/src/com/android/tools/idea/compose/preview/essentials/ComposePreviewEssentialsModeManager.kt
@@ -13,21 +13,21 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.tools.idea.compose.preview.lite
+package com.android.tools.idea.compose.preview.essentials
import com.android.tools.idea.flags.StudioFlags
import com.android.tools.idea.modes.essentials.EssentialsMode
import org.jetbrains.android.uipreview.AndroidEditorSettings
/**
- * Service to handle and query the state of Compose Preview Lite Mode. The state can be set via
- * settings.
+ * Service to handle and query the state of Compose Preview Essentials Mode. The state can be
+ * changed via settings panel.
*/
-object ComposePreviewLiteModeManager {
+object ComposePreviewEssentialsModeManager {
- val isLiteModeEnabled: Boolean
+ val isEssentialsModeEnabled: Boolean
get() =
- StudioFlags.COMPOSE_PREVIEW_LITE_MODE.get() &&
- (AndroidEditorSettings.getInstance().globalState.isComposePreviewLiteModeEnabled ||
+ StudioFlags.COMPOSE_PREVIEW_ESSENTIALS_MODE.get() &&
+ (AndroidEditorSettings.getInstance().globalState.isComposePreviewEssentialsModeEnabled ||
EssentialsMode.isEnabled())
}
diff --git a/compose-designer/src/com/android/tools/idea/compose/preview/lite/GalleryTabs.kt b/compose-designer/src/com/android/tools/idea/compose/preview/essentials/GalleryTabs.kt
similarity index 99%
rename from compose-designer/src/com/android/tools/idea/compose/preview/lite/GalleryTabs.kt
rename to compose-designer/src/com/android/tools/idea/compose/preview/essentials/GalleryTabs.kt
index a1bb90a..603ae5b 100644
--- a/compose-designer/src/com/android/tools/idea/compose/preview/lite/GalleryTabs.kt
+++ b/compose-designer/src/com/android/tools/idea/compose/preview/essentials/GalleryTabs.kt
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.tools.idea.compose.preview.lite
+package com.android.tools.idea.compose.preview.essentials
import com.android.tools.adtui.util.ActionToolbarUtil
import com.android.tools.idea.compose.preview.Colors
diff --git a/compose-designer/src/com/android/tools/idea/compose/preview/lite/PreviewElementKey.kt b/compose-designer/src/com/android/tools/idea/compose/preview/essentials/PreviewElementKey.kt
similarity index 93%
rename from compose-designer/src/com/android/tools/idea/compose/preview/lite/PreviewElementKey.kt
rename to compose-designer/src/com/android/tools/idea/compose/preview/essentials/PreviewElementKey.kt
index 9b792c7..af15cce 100644
--- a/compose-designer/src/com/android/tools/idea/compose/preview/lite/PreviewElementKey.kt
+++ b/compose-designer/src/com/android/tools/idea/compose/preview/essentials/PreviewElementKey.kt
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.tools.idea.compose.preview.lite
+package com.android.tools.idea.compose.preview.essentials
import com.android.tools.idea.compose.preview.ComposePreviewElementInstance
diff --git a/compose-designer/src/com/android/tools/idea/compose/preview/runconfiguration/ComposePreviewRunConfigurationProducer.kt b/compose-designer/src/com/android/tools/idea/compose/preview/runconfiguration/ComposePreviewRunConfigurationProducer.kt
index 1e08458..474bc32 100644
--- a/compose-designer/src/com/android/tools/idea/compose/preview/runconfiguration/ComposePreviewRunConfigurationProducer.kt
+++ b/compose-designer/src/com/android/tools/idea/compose/preview/runconfiguration/ComposePreviewRunConfigurationProducer.kt
@@ -17,8 +17,8 @@
import com.android.tools.compose.COMPOSE_PREVIEW_ACTIVITY_FQN
import com.android.tools.compose.COMPOSE_PREVIEW_PARAMETER_ANNOTATION_FQN
+import com.android.tools.idea.compose.preview.essentials.ComposePreviewEssentialsModeManager
import com.android.tools.idea.compose.preview.isValidComposePreview
-import com.android.tools.idea.compose.preview.lite.ComposePreviewLiteModeManager
import com.android.tools.idea.kotlin.fqNameMatches
import com.android.tools.idea.kotlin.getClassName
import com.android.tools.idea.projectsystem.getHolderModule
@@ -65,7 +65,7 @@
context: ConfigurationContext,
sourceElement: Ref<PsiElement>
): Boolean {
- if (ComposePreviewLiteModeManager.isLiteModeEnabled) return false
+ if (ComposePreviewEssentialsModeManager.isEssentialsModeEnabled) return false
val module = context.module ?: context.location?.module ?: return false
configuration.setLaunchActivity(COMPOSE_PREVIEW_ACTIVITY_FQN, true)
context.containingComposePreviewFunction()?.let { ktNamedFunction ->
@@ -104,7 +104,7 @@
configuration: ComposePreviewRunConfiguration,
context: ConfigurationContext
): Boolean {
- if (ComposePreviewLiteModeManager.isLiteModeEnabled) return false
+ if (ComposePreviewEssentialsModeManager.isEssentialsModeEnabled) return false
context.containingComposePreviewFunction()?.let {
val createdFromContext = configuration.composableMethodFqn == it.composePreviewFunctionFqn()
if (createdFromContext) {
diff --git a/compose-designer/src/com/android/tools/idea/compose/preview/runconfiguration/ComposePreviewRunLineMarkerContributor.kt b/compose-designer/src/com/android/tools/idea/compose/preview/runconfiguration/ComposePreviewRunLineMarkerContributor.kt
index afc19a8..1ef2c1b 100644
--- a/compose-designer/src/com/android/tools/idea/compose/preview/runconfiguration/ComposePreviewRunLineMarkerContributor.kt
+++ b/compose-designer/src/com/android/tools/idea/compose/preview/runconfiguration/ComposePreviewRunLineMarkerContributor.kt
@@ -15,8 +15,8 @@
*/
package com.android.tools.idea.compose.preview.runconfiguration
+import com.android.tools.idea.compose.preview.essentials.ComposePreviewEssentialsModeManager
import com.android.tools.idea.compose.preview.isValidComposePreview
-import com.android.tools.idea.compose.preview.lite.ComposePreviewLiteModeManager
import com.android.tools.idea.compose.preview.message
import com.intellij.execution.lineMarker.ExecutorAction
import com.intellij.execution.lineMarker.RunLineMarkerContributor
@@ -38,7 +38,7 @@
class ComposePreviewRunLineMarkerContributor : RunLineMarkerContributor() {
override fun getInfo(element: PsiElement): Info? {
- if (ComposePreviewLiteModeManager.isLiteModeEnabled) return null
+ if (ComposePreviewEssentialsModeManager.isEssentialsModeEnabled) return null
// Marker should be in a single LeafPsiElement. We choose the identifier and return null for
// other elements within the function.
if (element !is LeafPsiElement) return null
diff --git a/compose-designer/testSrc/com/android/tools/idea/compose/gradle/preview/RenderErrorTest.kt b/compose-designer/testSrc/com/android/tools/idea/compose/gradle/preview/RenderErrorTest.kt
index 00b49dd..71588d7 100644
--- a/compose-designer/testSrc/com/android/tools/idea/compose/gradle/preview/RenderErrorTest.kt
+++ b/compose-designer/testSrc/com/android/tools/idea/compose/gradle/preview/RenderErrorTest.kt
@@ -205,6 +205,7 @@
assertThat(offsets.sorted(), `is`(listOf(1667, 1817)))
}
+ @Ignore("b/289364358")
@Test
fun testVisualLintErrors() {
val issueModel = VisualLintService.getInstance(project).issueModel
diff --git a/compose-designer/testSrc/com/android/tools/idea/compose/gradle/preview/TestComposePreview.kt b/compose-designer/testSrc/com/android/tools/idea/compose/gradle/preview/TestComposePreview.kt
index fdde159..c7e62ca 100644
--- a/compose-designer/testSrc/com/android/tools/idea/compose/gradle/preview/TestComposePreview.kt
+++ b/compose-designer/testSrc/com/android/tools/idea/compose/gradle/preview/TestComposePreview.kt
@@ -21,7 +21,7 @@
import com.android.tools.idea.compose.preview.ComposePreviewView
import com.android.tools.idea.compose.preview.NopComposePreviewManager
import com.android.tools.idea.compose.preview.createMainDesignSurfaceBuilder
-import com.android.tools.idea.compose.preview.lite.ComposeEssentialsMode
+import com.android.tools.idea.compose.preview.essentials.ComposeEssentialsMode
import com.android.tools.idea.compose.preview.navigation.ComposePreviewNavigationHandler
import com.android.tools.idea.compose.preview.scene.ComposeSceneComponentProvider
import com.android.tools.idea.compose.preview.scene.ComposeScreenViewProvider
diff --git a/compose-designer/testSrc/com/android/tools/idea/compose/preview/ComposePreviewRepresentationTest.kt b/compose-designer/testSrc/com/android/tools/idea/compose/preview/ComposePreviewRepresentationTest.kt
index adf71c9..59af698 100644
--- a/compose-designer/testSrc/com/android/tools/idea/compose/preview/ComposePreviewRepresentationTest.kt
+++ b/compose-designer/testSrc/com/android/tools/idea/compose/preview/ComposePreviewRepresentationTest.kt
@@ -21,7 +21,7 @@
import com.android.tools.idea.common.surface.DesignSurfaceListener
import com.android.tools.idea.compose.ComposeProjectRule
import com.android.tools.idea.compose.pickers.preview.property.referenceDeviceIds
-import com.android.tools.idea.compose.preview.lite.ComposeEssentialsMode
+import com.android.tools.idea.compose.preview.essentials.ComposeEssentialsMode
import com.android.tools.idea.compose.preview.navigation.ComposePreviewNavigationHandler
import com.android.tools.idea.concurrency.AndroidDispatchers.workerThread
import com.android.tools.idea.editors.build.ProjectStatus
diff --git a/compose-designer/testSrc/com/android/tools/idea/compose/preview/analytics/MultiPreviewUsageTrackerTest.kt b/compose-designer/testSrc/com/android/tools/idea/compose/preview/analytics/MultiPreviewUsageTrackerTest.kt
index 5fe1529..0805390 100644
--- a/compose-designer/testSrc/com/android/tools/idea/compose/preview/analytics/MultiPreviewUsageTrackerTest.kt
+++ b/compose-designer/testSrc/com/android/tools/idea/compose/preview/analytics/MultiPreviewUsageTrackerTest.kt
@@ -327,8 +327,8 @@
}
@Test
- fun testLogEvent_LiteMode() {
- StudioFlags.COMPOSE_PREVIEW_LITE_MODE.override(true)
+ fun testLogEvent_EssentialsMode() {
+ StudioFlags.COMPOSE_PREVIEW_ESSENTIALS_MODE.override(true)
fun logAndGetMultiPreviewEvent() =
MultiPreviewUsageTracker.getInstance(null)
.logEvent(MultiPreviewEvent(listOf(), ""))
@@ -336,13 +336,13 @@
try {
val settings = AndroidEditorSettings.getInstance().globalState
- settings.isComposePreviewLiteModeEnabled = false
+ settings.isComposePreviewEssentialsModeEnabled = false
assertFalse(logAndGetMultiPreviewEvent().isComposePreviewLiteMode)
- settings.isComposePreviewLiteModeEnabled = true
+ settings.isComposePreviewEssentialsModeEnabled = true
assertTrue(logAndGetMultiPreviewEvent().isComposePreviewLiteMode)
} finally {
- StudioFlags.COMPOSE_PREVIEW_LITE_MODE.clearOverride()
+ StudioFlags.COMPOSE_PREVIEW_ESSENTIALS_MODE.clearOverride()
}
}
diff --git a/compose-designer/testSrc/com/android/tools/idea/compose/preview/lite/ComposeEssentialsModeTest.kt b/compose-designer/testSrc/com/android/tools/idea/compose/preview/essentials/ComposeEssentialsModeTest.kt
similarity index 97%
rename from compose-designer/testSrc/com/android/tools/idea/compose/preview/lite/ComposeEssentialsModeTest.kt
rename to compose-designer/testSrc/com/android/tools/idea/compose/preview/essentials/ComposeEssentialsModeTest.kt
index 17fd087..9c7e604 100644
--- a/compose-designer/testSrc/com/android/tools/idea/compose/preview/lite/ComposeEssentialsModeTest.kt
+++ b/compose-designer/testSrc/com/android/tools/idea/compose/preview/essentials/ComposeEssentialsModeTest.kt
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.tools.idea.compose.preview.lite
+package com.android.tools.idea.compose.preview.essentials
import com.android.tools.adtui.TreeWalker
import com.android.tools.idea.compose.preview.COMPOSE_PREVIEW_MANAGER
diff --git a/compose-designer/testSrc/com/android/tools/idea/compose/preview/essentials/ComposePreviewEssentialsModeManagerTest.kt b/compose-designer/testSrc/com/android/tools/idea/compose/preview/essentials/ComposePreviewEssentialsModeManagerTest.kt
new file mode 100644
index 0000000..0850a0c
--- /dev/null
+++ b/compose-designer/testSrc/com/android/tools/idea/compose/preview/essentials/ComposePreviewEssentialsModeManagerTest.kt
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.tools.idea.compose.preview.essentials
+
+import com.android.flags.junit.FlagRule
+import com.android.tools.idea.flags.StudioFlags
+import com.android.tools.idea.modes.essentials.EssentialsMode
+import com.android.tools.idea.testing.AndroidProjectRule
+import org.jetbrains.android.uipreview.AndroidEditorSettings
+import org.jetbrains.android.uipreview.AndroidEditorSettings.GlobalState
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertTrue
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+
+class ComposePreviewEssentialsModeManagerTest {
+
+ @get:Rule val essentialsModeFlagRule = FlagRule(StudioFlags.COMPOSE_PREVIEW_ESSENTIALS_MODE)
+ @get:Rule val projectRule = AndroidProjectRule.inMemory()
+
+ private lateinit var settings: GlobalState
+
+ @Before
+ fun setUp() {
+ settings = AndroidEditorSettings.getInstance().globalState
+ }
+
+ @Test
+ fun essentialsModeIsOnlyEnabledIfFlagIsEnabled() {
+ StudioFlags.COMPOSE_PREVIEW_ESSENTIALS_MODE.override(false)
+ assertFalse(ComposePreviewEssentialsModeManager.isEssentialsModeEnabled)
+
+ settings.isComposePreviewEssentialsModeEnabled = true
+ // Even with the essentials mode enabled in the settings panel, we shouldn't be in
+ // essentials mode if the flag is disabled.
+ assertFalse(ComposePreviewEssentialsModeManager.isEssentialsModeEnabled)
+ }
+
+ @Test
+ fun essentialsModeIsControlledViaSettingsIfFlagIsEnabled() {
+ StudioFlags.COMPOSE_PREVIEW_ESSENTIALS_MODE.override(true)
+ settings.isComposePreviewEssentialsModeEnabled = false
+ assertFalse(ComposePreviewEssentialsModeManager.isEssentialsModeEnabled)
+
+ settings.isComposePreviewEssentialsModeEnabled = true
+ assertTrue(ComposePreviewEssentialsModeManager.isEssentialsModeEnabled)
+ }
+
+ @Test
+ fun previewEssentialsModeIsEnabledIfStudioEssentialsModeIsEnabled() {
+ StudioFlags.COMPOSE_PREVIEW_ESSENTIALS_MODE.override(true)
+ try {
+ settings.isComposePreviewEssentialsModeEnabled = false
+ assertFalse(ComposePreviewEssentialsModeManager.isEssentialsModeEnabled)
+
+ // Enable Android Studio essentials mode. Note that preview essentials mode is still disabled
+ // in settings.
+ EssentialsMode.setEnabled(true, projectRule.project)
+ assertTrue(ComposePreviewEssentialsModeManager.isEssentialsModeEnabled)
+ } finally {
+ EssentialsMode.setEnabled(false, projectRule.project)
+ }
+ }
+}
diff --git a/compose-designer/testSrc/com/android/tools/idea/compose/preview/lite/GalleryTabsTest.kt b/compose-designer/testSrc/com/android/tools/idea/compose/preview/essentials/GalleryTabsTest.kt
similarity index 99%
rename from compose-designer/testSrc/com/android/tools/idea/compose/preview/lite/GalleryTabsTest.kt
rename to compose-designer/testSrc/com/android/tools/idea/compose/preview/essentials/GalleryTabsTest.kt
index baad209..9963452 100644
--- a/compose-designer/testSrc/com/android/tools/idea/compose/preview/lite/GalleryTabsTest.kt
+++ b/compose-designer/testSrc/com/android/tools/idea/compose/preview/essentials/GalleryTabsTest.kt
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.tools.idea.compose.preview.lite
+package com.android.tools.idea.compose.preview.essentials
import com.android.tools.adtui.TreeWalker
import com.android.tools.adtui.swing.FakeUi
diff --git a/compose-designer/testSrc/com/android/tools/idea/compose/preview/lite/ComposePreviewLiteModeManagerTest.kt b/compose-designer/testSrc/com/android/tools/idea/compose/preview/lite/ComposePreviewLiteModeManagerTest.kt
deleted file mode 100644
index a130f4b..0000000
--- a/compose-designer/testSrc/com/android/tools/idea/compose/preview/lite/ComposePreviewLiteModeManagerTest.kt
+++ /dev/null
@@ -1,78 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.tools.idea.compose.preview.lite
-
-import com.android.flags.junit.FlagRule
-import com.android.tools.idea.flags.StudioFlags
-import com.android.tools.idea.modes.essentials.EssentialsMode
-import com.android.tools.idea.testing.AndroidProjectRule
-import org.jetbrains.android.uipreview.AndroidEditorSettings
-import org.jetbrains.android.uipreview.AndroidEditorSettings.GlobalState
-import org.junit.Assert.assertFalse
-import org.junit.Assert.assertTrue
-import org.junit.Before
-import org.junit.Rule
-import org.junit.Test
-
-class ComposePreviewLiteModeManagerTest {
-
- @get:Rule val liteModeFlagRule = FlagRule(StudioFlags.COMPOSE_PREVIEW_LITE_MODE)
- @get:Rule val projectRule = AndroidProjectRule.inMemory()
-
- private lateinit var settings: GlobalState
-
- @Before
- fun setUp() {
- settings = AndroidEditorSettings.getInstance().globalState
- }
-
- @Test
- fun liteModeIsOnlyEnabledIfFlagIsEnabled() {
- StudioFlags.COMPOSE_PREVIEW_LITE_MODE.override(false)
- assertFalse(ComposePreviewLiteModeManager.isLiteModeEnabled)
-
- settings.isComposePreviewLiteModeEnabled = true
- // Even with the lite mode enabled in the settings panel, we shouldn't be in
- // lite mode if the flag is disabled.
- assertFalse(ComposePreviewLiteModeManager.isLiteModeEnabled)
- }
-
- @Test
- fun liteModeIsControlledViaSettingsIfFlagIsEnabled() {
- StudioFlags.COMPOSE_PREVIEW_LITE_MODE.override(true)
- settings.isComposePreviewLiteModeEnabled = false
- assertFalse(ComposePreviewLiteModeManager.isLiteModeEnabled)
-
- settings.isComposePreviewLiteModeEnabled = true
- assertTrue(ComposePreviewLiteModeManager.isLiteModeEnabled)
- }
-
- @Test
- fun liteModeIsEnabledIfEssentialsModeIsEnabled() {
- StudioFlags.COMPOSE_PREVIEW_LITE_MODE.override(true)
- try {
- settings.isComposePreviewLiteModeEnabled = false
- assertFalse(ComposePreviewLiteModeManager.isLiteModeEnabled)
-
- // Enable Android Studio essentials mode. Note that preview lite mode is still disabled in
- // settings.
- EssentialsMode.setEnabled(true, projectRule.project)
- assertTrue(ComposePreviewLiteModeManager.isLiteModeEnabled)
- } finally {
- EssentialsMode.setEnabled(false, projectRule.project)
- }
- }
-}
diff --git a/compose-designer/testSrc/com/android/tools/idea/compose/preview/runconfiguration/ComposePreviewRunConfigurationProducerTest.kt b/compose-designer/testSrc/com/android/tools/idea/compose/preview/runconfiguration/ComposePreviewRunConfigurationProducerTest.kt
index de90c828..ac0f302 100644
--- a/compose-designer/testSrc/com/android/tools/idea/compose/preview/runconfiguration/ComposePreviewRunConfigurationProducerTest.kt
+++ b/compose-designer/testSrc/com/android/tools/idea/compose/preview/runconfiguration/ComposePreviewRunConfigurationProducerTest.kt
@@ -60,13 +60,13 @@
.trimIndent()
)
composableFunction = PsiTreeUtil.findChildrenOfType(file, KtNamedFunction::class.java).first()
- StudioFlags.COMPOSE_PREVIEW_LITE_MODE.override(true)
+ StudioFlags.COMPOSE_PREVIEW_ESSENTIALS_MODE.override(true)
}
override fun tearDown() {
super.tearDown()
- StudioFlags.COMPOSE_PREVIEW_LITE_MODE.clearOverride()
- AndroidEditorSettings.getInstance().globalState.isComposePreviewLiteModeEnabled = false
+ StudioFlags.COMPOSE_PREVIEW_ESSENTIALS_MODE.clearOverride()
+ AndroidEditorSettings.getInstance().globalState.isComposePreviewEssentialsModeEnabled = false
}
override fun configureAdditionalModules(
@@ -103,13 +103,13 @@
}
}
- fun testSetupConfigurationFromContextWhenLiteModeIsEnabled() {
- AndroidEditorSettings.getInstance().globalState.isComposePreviewLiteModeEnabled = true
+ fun testSetupConfigurationFromContextWhenEssentialsModeIsEnabled() {
+ AndroidEditorSettings.getInstance().globalState.isComposePreviewEssentialsModeEnabled = true
val context = ConfigurationContext(composableFunction)
val runConfiguration = newComposePreviewRunConfiguration()
val producer = ComposePreviewRunConfigurationProducer()
- // We shouldn't be able to create a configuration from context when lite mode is enabled.
+ // We shouldn't be able to create a configuration from context when essentials mode is enabled.
assertFalse(
producer.setupConfigurationFromContext(runConfiguration, context, Ref(context.psiLocation))
)
@@ -296,15 +296,16 @@
assertTrue(producer.isConfigurationFromContext(runConfiguration, context))
}
- fun testIsConfigurationFromContextWhenLiteModeIsEnabled() {
- AndroidEditorSettings.getInstance().globalState.isComposePreviewLiteModeEnabled = true
+ fun testIsConfigurationFromContextWhenEssentialsModeIsEnabled() {
+ AndroidEditorSettings.getInstance().globalState.isComposePreviewEssentialsModeEnabled = true
val producer = ComposePreviewRunConfigurationProducer()
val context = ConfigurationContext(composableFunction)
val runConfiguration = newComposePreviewRunConfiguration()
runConfiguration.name = "Preview1"
runConfiguration.composableMethodFqn = "TestKt.Preview1"
- // Configuration shouldn't match when Lite Mode is enabled, even if both name and FQN match.
+ // Configuration shouldn't match when Compose Preview Essentials Mode is enabled,
+ // even if both name and FQN match.
assertFalse(producer.isConfigurationFromContext(runConfiguration, context))
}
diff --git a/compose-designer/testSrc/com/android/tools/idea/compose/preview/runconfiguration/ComposePreviewRunLineMarkerContributorTest.kt b/compose-designer/testSrc/com/android/tools/idea/compose/preview/runconfiguration/ComposePreviewRunLineMarkerContributorTest.kt
index be1d401..6d724e9 100644
--- a/compose-designer/testSrc/com/android/tools/idea/compose/preview/runconfiguration/ComposePreviewRunLineMarkerContributorTest.kt
+++ b/compose-designer/testSrc/com/android/tools/idea/compose/preview/runconfiguration/ComposePreviewRunLineMarkerContributorTest.kt
@@ -48,13 +48,13 @@
super.setUp()
myFixture.stubComposableAnnotation()
myFixture.stubPreviewAnnotation()
- StudioFlags.COMPOSE_PREVIEW_LITE_MODE.override(true)
+ StudioFlags.COMPOSE_PREVIEW_ESSENTIALS_MODE.override(true)
}
override fun tearDown() {
super.tearDown()
- StudioFlags.COMPOSE_PREVIEW_LITE_MODE.clearOverride()
- AndroidEditorSettings.getInstance().globalState.isComposePreviewLiteModeEnabled = false
+ StudioFlags.COMPOSE_PREVIEW_ESSENTIALS_MODE.clearOverride()
+ AndroidEditorSettings.getInstance().globalState.isComposePreviewEssentialsModeEnabled = false
}
override fun configureAdditionalModules(
@@ -92,8 +92,8 @@
assertNotNull(contributor.getInfo(functionIdentifier))
}
- fun testGetInfoWhenLiteModeIsEnabled() {
- AndroidEditorSettings.getInstance().globalState.isComposePreviewLiteModeEnabled = true
+ fun testGetInfoWhenEssentialsModeIsEnabled() {
+ AndroidEditorSettings.getInstance().globalState.isComposePreviewEssentialsModeEnabled = true
val file =
myFixture.addFileToProjectAndInvalidate(
"src/Test.kt",
@@ -112,7 +112,7 @@
val functionIdentifier = file.findFunctionIdentifier("Preview1")
// Although the function is a valid preview, a run line marker should not be created, because
- // Lite Mode is enabled
+ // Essentials Mode is enabled
assertNull(contributor.getInfo(functionIdentifier))
}
diff --git a/designer/src/com/android/tools/idea/common/error/IssuePanelService.kt b/designer/src/com/android/tools/idea/common/error/IssuePanelService.kt
index c34322d..aac3202 100644
--- a/designer/src/com/android/tools/idea/common/error/IssuePanelService.kt
+++ b/designer/src/com/android/tools/idea/common/error/IssuePanelService.kt
@@ -183,7 +183,7 @@
setSharedIssuePanelVisibility(false)
return
}
- if (isComposeFile(newFile) || isSupportedDesignerFileType(newFile)) {
+ if (isSupportedDesignerFileType(newFile)) {
addIssuePanel()
return
}
@@ -359,9 +359,6 @@
fileToTabName[file]?.let { return it }
- if (isComposeFile(file)) {
- return "Compose"
- }
val name = getTabNameOfSupportedDesignerFile(file)
if (name != null) {
return name
@@ -378,7 +375,7 @@
*/
private fun isDesignEditor(editor: FileEditor): Boolean {
val virtualFile = editor.file ?: return false
- return isComposeFile(virtualFile) || isSupportedDesignerFileType(virtualFile) || editor.getDesignSurface() != null
+ return isSupportedDesignerFileType(virtualFile) || editor.getDesignSurface() != null
}
private fun isComposeFile(file: VirtualFile): Boolean {
@@ -400,6 +397,7 @@
private fun getTabNameOfSupportedDesignerFile(file: VirtualFile): String? {
val psiFile = file.toPsiFile(project) ?: return null
return when {
+ isComposeFile(file) -> "Compose"
LayoutFileType.isResourceTypeOf(psiFile) -> "Layout and Qualifiers"
PreferenceScreenFileType.isResourceTypeOf(psiFile) -> "Preference"
MenuFileType.isResourceTypeOf(psiFile) -> "Menu"
diff --git a/designer/src/com/android/tools/idea/uibuilder/handlers/BuiltinViewHandlerProvider.kt b/designer/src/com/android/tools/idea/uibuilder/handlers/BuiltinViewHandlerProvider.kt
index 82e3400..0e07d88 100644
--- a/designer/src/com/android/tools/idea/uibuilder/handlers/BuiltinViewHandlerProvider.kt
+++ b/designer/src/com/android/tools/idea/uibuilder/handlers/BuiltinViewHandlerProvider.kt
@@ -227,6 +227,7 @@
PreferenceTags.PREFERENCE_SCREEN -> PreferenceScreenHandler()
PreferenceTags.RINGTONE_PREFERENCE -> RingtonePreferenceHandler()
PreferenceTags.SWITCH_PREFERENCE -> SwitchPreferenceHandler()
+ PreferenceTags.SWITCH_PREFERENCE_COMPAT -> SwitchPreferenceHandler()
else -> null
}
}
diff --git a/designer/src/com/android/tools/idea/uibuilder/handlers/motion/MotionLayoutComponentHelper.java b/designer/src/com/android/tools/idea/uibuilder/handlers/motion/MotionLayoutComponentHelper.java
index a622f499..1b6a0cf 100644
--- a/designer/src/com/android/tools/idea/uibuilder/handlers/motion/MotionLayoutComponentHelper.java
+++ b/designer/src/com/android/tools/idea/uibuilder/handlers/motion/MotionLayoutComponentHelper.java
@@ -30,6 +30,7 @@
import com.android.tools.res.ids.ResourceIdManager;
import com.android.utils.Pair;
import com.intellij.util.ArrayUtil;
+import com.intellij.util.concurrency.EdtExecutorService;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Arrays;
@@ -158,6 +159,23 @@
return -1;
}
+ private void whenDesignToolAvailable(@NotNull Runnable runnable) {
+ if (myDesignTool != null) {
+ runnable.run();
+ return;
+ }
+
+ if (myFuture == null) {
+ return;
+ }
+
+ myFuture.thenRunAsync(() -> {
+ if (myDesignTool == null) {
+ runnable.run();
+ }
+ }, EdtExecutorService.getInstance());
+ }
+
private boolean isMyDesignToolAvailable() {
if (myDesignTool == null) {
if (myFuture != null) {
@@ -397,9 +415,6 @@
}
private boolean setTransitionPosition(float position) {
- if (!isMyDesignToolAvailable()) {
- return false;
- }
if (myCallSetTransitionPosition == null) {
try {
myCallSetTransitionPosition = myDesignTool.getClass().getMethod("setToolPosition", float.class);
@@ -438,18 +453,17 @@
return true;
}
- public boolean setProgress(float value) {
- if (!isMyDesignToolAvailable()) {
- return false;
- }
- NlModel model = myMotionLayoutComponent.getModel();
- //model.notifyModified(NlModel.ChangeType.EDIT);
- if (!setTransitionPosition(value)) {
- return false;
- }
- model.notifyLiveUpdate(false);
- refresh(myMotionLayoutComponent);
- return true;
+ public void setProgress(float value) {
+ whenDesignToolAvailable(() -> {
+ NlModel model = myMotionLayoutComponent.getModel();
+
+ if (!setTransitionPosition(value)) {
+ return;
+ }
+
+ model.notifyLiveUpdate(false);
+ refresh(myMotionLayoutComponent);
+ });
}
public void setTransition(String start, String end) {
@@ -497,46 +511,45 @@
}
public void setState(String state) {
- if (!isMyDesignToolAvailable()) {
- return;
- }
- if (myCallSetState == null) {
- try {
- myCallSetState = myDesignTool.getClass().getMethod("setState", String.class);
- }
- catch (NoSuchMethodException e) {
- if (DEBUG) {
- e.printStackTrace();
+ whenDesignToolAvailable(() -> {
+ if (myCallSetState == null) {
+ try {
+ myCallSetState = myDesignTool.getClass().getMethod("setState", String.class);
}
- myCallSetState = null;
+ catch (NoSuchMethodException e) {
+ if (DEBUG) {
+ e.printStackTrace();
+ }
+ myCallSetState = null;
+ return;
+ }
+ }
+ if (myCallSetState != null) {
+ try {
+ RenderService.getRenderAsyncActionExecutor().runAsyncAction(() -> {
+ try {
+ myCallSetState.invoke(myDesignTool, state);
+ }
+ catch (ClassCastException | IllegalAccessException | InvocationTargetException e) {
+ myCallSetState = null;
+ if (DEBUG) {
+ e.printStackTrace();
+ }
+ }
+ });
+ }
+ catch (Exception e) {
+ if (DEBUG) {
+ e.printStackTrace();
+ }
+ }
+ }
+ if (myCallSetState == null) {
return;
}
- }
- if (myCallSetState != null) {
- try {
- RenderService.getRenderAsyncActionExecutor().runAsyncAction(() -> {
- try {
- myCallSetState.invoke(myDesignTool, state);
- }
- catch (ClassCastException | IllegalAccessException | InvocationTargetException e) {
- myCallSetState = null;
- if (DEBUG) {
- e.printStackTrace();
- }
- }
- });
- }
- catch (Exception e) {
- if (DEBUG) {
- e.printStackTrace();
- }
- }
- }
- if (myCallSetState == null) {
- return;
- }
- NlModel model = myMotionLayoutComponent.getModel();
- model.notifyLiveUpdate(false);
+ NlModel model = myMotionLayoutComponent.getModel();
+ model.notifyLiveUpdate(false);
+ });
}
public void disableAutoTransition(boolean disable) {
diff --git a/designer/src/com/android/tools/idea/uibuilder/handlers/preference/PreferenceCategoryHandler.java b/designer/src/com/android/tools/idea/uibuilder/handlers/preference/PreferenceCategoryHandler.java
index 37801e8..f0bc2e4 100644
--- a/designer/src/com/android/tools/idea/uibuilder/handlers/preference/PreferenceCategoryHandler.java
+++ b/designer/src/com/android/tools/idea/uibuilder/handlers/preference/PreferenceCategoryHandler.java
@@ -22,6 +22,7 @@
import com.android.tools.idea.uibuilder.api.*;
import com.android.tools.idea.common.model.NlComponent;
import com.android.tools.idea.common.scene.SceneComponent;
+import com.google.common.base.Strings;
import org.intellij.lang.annotations.Language;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -72,4 +73,11 @@
return true;
}
+
+ @NotNull
+ @Override
+ public String getTitle(@NotNull NlComponent component) {
+ String title = component.getAttribute(AUTO_URI, ATTR_TITLE);
+ return title != null ? title : Strings.nullToEmpty(component.getAndroidAttribute(ATTR_TITLE));
+ }
}
diff --git a/designer/src/com/android/tools/idea/uibuilder/handlers/preference/PreferenceHandler.java b/designer/src/com/android/tools/idea/uibuilder/handlers/preference/PreferenceHandler.java
index 4c21b13..61113f6 100644
--- a/designer/src/com/android/tools/idea/uibuilder/handlers/preference/PreferenceHandler.java
+++ b/designer/src/com/android/tools/idea/uibuilder/handlers/preference/PreferenceHandler.java
@@ -80,6 +80,7 @@
@NotNull
@Override
public String getTitle(@NotNull NlComponent component) {
- return Strings.nullToEmpty(component.getAndroidAttribute(ATTR_TITLE));
+ String title = component.getAttribute(AUTO_URI, ATTR_TITLE);
+ return title != null ? title : Strings.nullToEmpty(component.getAndroidAttribute(ATTR_TITLE));
}
}
diff --git a/designer/src/com/android/tools/idea/uibuilder/handlers/preference/SwitchPreferenceHandler.java b/designer/src/com/android/tools/idea/uibuilder/handlers/preference/SwitchPreferenceHandler.java
index d3411ef..505eba0 100644
--- a/designer/src/com/android/tools/idea/uibuilder/handlers/preference/SwitchPreferenceHandler.java
+++ b/designer/src/com/android/tools/idea/uibuilder/handlers/preference/SwitchPreferenceHandler.java
@@ -29,7 +29,6 @@
import static com.android.SdkConstants.ATTR_TITLE;
import static com.android.SdkConstants.PreferenceAttributes.*;
-import static com.android.SdkConstants.PreferenceTags.SWITCH_PREFERENCE;
public final class SwitchPreferenceHandler extends PreferenceHandler {
@Language("XML")
@@ -80,7 +79,7 @@
if (type == InsertType.CREATE) {
NlWriteCommandActionUtil.run(newChild, "Set SwitchPreference", () -> {
- newChild.setAndroidAttribute(ATTR_KEY, generateKey(newChild, SWITCH_PREFERENCE, "switch_preference_"));
+ newChild.setAndroidAttribute(ATTR_KEY, generateKey(newChild, newChild.getTagName(), "switch_preference_"));
});
}
return true;
diff --git a/designer/src/com/android/tools/idea/uibuilder/options/NlOptionsConfigurable.kt b/designer/src/com/android/tools/idea/uibuilder/options/NlOptionsConfigurable.kt
index dc43c14..c7a3fcb 100644
--- a/designer/src/com/android/tools/idea/uibuilder/options/NlOptionsConfigurable.kt
+++ b/designer/src/com/android/tools/idea/uibuilder/options/NlOptionsConfigurable.kt
@@ -146,7 +146,7 @@
}
group("Compose Preview") {
- if (StudioFlags.COMPOSE_PREVIEW_LITE_MODE.get()) {
+ if (StudioFlags.COMPOSE_PREVIEW_ESSENTIALS_MODE.get()) {
buttonsGroup("Resource Usage:") {
if (EssentialsMode.isEnabled()) {
row {
@@ -161,8 +161,8 @@
row {
defaultModeRadioButton =
radioButton("Default")
- .bindSelected({ !state.isComposePreviewLiteModeEnabled }) {
- state.isComposePreviewLiteModeEnabled = !it
+ .bindSelected({ !state.isComposePreviewEssentialsModeEnabled }) {
+ state.isComposePreviewEssentialsModeEnabled = !it
}
}.enabled(!EssentialsMode.isEnabled())
indent {
@@ -175,13 +175,14 @@
}
}.enabled(!EssentialsMode.isEnabled())
row {
- val liteModeHint = "Preview will preserve resources by inflating previews on demand, and disabling live updates and " +
- "preview modes. <a href=\"https://developer.android.com/jetpack/compose/tooling/previews\">Learn more</a>"
+ val essentialsModeHint =
+ "Preview will preserve resources by inflating previews on demand, and disabling live updates and preview modes. " +
+ "<a href=\"https://developer.android.com/jetpack/compose/tooling/previews\">Learn more</a>"
radioButton("Essentials")
- .comment(liteModeHint)
- .bindSelected(state::isComposePreviewLiteModeEnabled) {
- state.isComposePreviewLiteModeEnabled = it
+ .comment(essentialsModeHint)
+ .bindSelected(state::isComposePreviewEssentialsModeEnabled) {
+ state.isComposePreviewEssentialsModeEnabled = it
}
}.enabled(!EssentialsMode.isEnabled())
}
diff --git a/designer/testSrc/com/android/tools/idea/uibuilder/structure/NlTreeCellRendererTest.kt b/designer/testSrc/com/android/tools/idea/uibuilder/structure/NlTreeCellRendererTest.kt
index 1000216..dc19aef 100644
--- a/designer/testSrc/com/android/tools/idea/uibuilder/structure/NlTreeCellRendererTest.kt
+++ b/designer/testSrc/com/android/tools/idea/uibuilder/structure/NlTreeCellRendererTest.kt
@@ -125,4 +125,54 @@
assertThat(labels[0].text).isEqualTo("SwitchPreferenceCompat")
assertThat(labels[0].icon).isSameAs(StudioIcons.LayoutEditor.Palette.VIEW)
}
+
+ @Test
+ fun displayPreferenceAndroidTitle() {
+ val badgeHandler = mock(NlTreeBadgeHandler::class.java)
+ val panel = mock(NlVisibilityGutterPanel::class.java)
+ val tree = NlComponentTree(projectRule.project, null, panel)
+ UIUtil.putClientProperty(tree, ExpandableItemsHandler.EXPANDED_RENDERER, true)
+ val button = MockNlComponent.create(
+ ApplicationManager.getApplication().runReadAction<XmlTag> {
+ XmlTagUtil.createTag(projectRule.project, "<SwitchPreferenceCompat android:title=\"Title\"></SwitchPreferenceCompat>")
+ .apply { putUserData(ModuleUtilCore.KEY_MODULE, projectRule.module) }
+ }
+ )
+ tree.setUI(object : BasicTreeUI() {
+ override fun getLeftChildIndent() = 0
+ override fun getRightChildIndent() = 0
+ override fun getPathForRow(tree: JTree?, row: Int) = TreePath(arrayOf(button))
+ })
+ val renderer = NlTreeCellRenderer(badgeHandler)
+ val component = renderer.getTreeCellRendererComponent(tree, button, selected = false, expanded = false, leaf = true, row = 3,
+ hasFocus = false) as JComponent
+ val labels = UIUtil.findComponentsOfType(component, JLabel::class.java)
+ assertThat(labels[0].text).isEqualTo("Title")
+ assertThat(labels[0].icon).isSameAs(StudioIcons.LayoutEditor.Palette.VIEW)
+ }
+
+ @Test
+ fun displayPreferenceAppTitle() {
+ val badgeHandler = mock(NlTreeBadgeHandler::class.java)
+ val panel = mock(NlVisibilityGutterPanel::class.java)
+ val tree = NlComponentTree(projectRule.project, null, panel)
+ UIUtil.putClientProperty(tree, ExpandableItemsHandler.EXPANDED_RENDERER, true)
+ val button = MockNlComponent.create(
+ ApplicationManager.getApplication().runReadAction<XmlTag> {
+ XmlTagUtil.createTag(projectRule.project, "<SwitchPreferenceCompat app:title=\"Title\"></SwitchPreferenceCompat>")
+ .apply { putUserData(ModuleUtilCore.KEY_MODULE, projectRule.module) }
+ }
+ )
+ tree.setUI(object : BasicTreeUI() {
+ override fun getLeftChildIndent() = 0
+ override fun getRightChildIndent() = 0
+ override fun getPathForRow(tree: JTree?, row: Int) = TreePath(arrayOf(button))
+ })
+ val renderer = NlTreeCellRenderer(badgeHandler)
+ val component = renderer.getTreeCellRendererComponent(tree, button, selected = false, expanded = false, leaf = true, row = 3,
+ hasFocus = false) as JComponent
+ val labels = UIUtil.findComponentsOfType(component, JLabel::class.java)
+ assertThat(labels[0].text).isEqualTo("Title")
+ assertThat(labels[0].icon).isSameAs(StudioIcons.LayoutEditor.Palette.VIEW)
+ }
}
\ No newline at end of file
diff --git a/device-manager-v2/src/com/android/tools/idea/devicemanagerv2/DeviceManagerPanel.kt b/device-manager-v2/src/com/android/tools/idea/devicemanagerv2/DeviceManagerPanel.kt
index e185587..ed6c383 100644
--- a/device-manager-v2/src/com/android/tools/idea/devicemanagerv2/DeviceManagerPanel.kt
+++ b/device-manager-v2/src/com/android/tools/idea/devicemanagerv2/DeviceManagerPanel.kt
@@ -28,7 +28,7 @@
import com.android.tools.adtui.actions.DropDownAction
import com.android.tools.adtui.categorytable.CategoryTable
import com.android.tools.adtui.categorytable.IconButton
-import com.android.tools.adtui.categorytable.RowKey
+import com.android.tools.adtui.categorytable.RowKey.ValueRowKey
import com.android.tools.adtui.util.ActionToolbarUtil
import com.android.tools.idea.concurrency.AndroidCoroutineScope
import com.android.tools.idea.concurrency.AndroidDispatchers.uiThread
@@ -178,6 +178,17 @@
panelScope.launch(uiDispatcher) { trackDevices() }
panelScope.launch(uiDispatcher) { trackDeviceTemplates() }
+
+ // Keep the device details synced with the selected row.
+ panelScope.launch(uiDispatcher) {
+ deviceTable.selection.asFlow().collect { selectedRows ->
+ if (deviceDetailsPanelRow != null) {
+ (selectedRows.singleOrNull() as? ValueRowKey)?.let { selectedRow ->
+ deviceTable.values.find { it.key() == selectedRow.key }?.let { showDeviceDetails(it) }
+ }
+ }
+ }
+ }
}
private suspend fun trackDevices() {
@@ -228,8 +239,8 @@
if (deviceTable.addOrUpdateRow(it, beforeKey = handle.sourceTemplate)) {
handle.sourceTemplate?.let {
if (templateInstantiationCount.add(it, 1) == 0) {
- if (deviceTable.selection.selectedKeys().contains(RowKey.ValueRowKey(it))) {
- deviceTable.selection.selectRow(RowKey.ValueRowKey(handle))
+ if (deviceTable.selection.selectedKeys().contains(ValueRowKey(it))) {
+ deviceTable.selection.selectRow(ValueRowKey(handle))
}
deviceTable.setRowVisibleByKey(it, false)
}
diff --git a/device-manager-v2/src/com/android/tools/idea/devicemanagerv2/DeviceTableColumns.kt b/device-manager-v2/src/com/android/tools/idea/devicemanagerv2/DeviceTableColumns.kt
index 5911c9f..58602e6 100644
--- a/device-manager-v2/src/com/android/tools/idea/devicemanagerv2/DeviceTableColumns.kt
+++ b/device-manager-v2/src/com/android/tools/idea/devicemanagerv2/DeviceTableColumns.kt
@@ -22,6 +22,7 @@
import com.android.tools.adtui.categorytable.Column
import com.android.tools.adtui.categorytable.IconLabel
import com.android.tools.adtui.categorytable.LabelColumn
+import com.android.tools.adtui.event.DelegateMouseEventHandler
import com.google.common.collect.Ordering
import com.intellij.openapi.project.Project
import com.intellij.util.ui.JBDimension
@@ -136,6 +137,14 @@
}
}
+ override fun installMouseDelegate(
+ component: ActionButtonsPanel,
+ mouseDelegate: DelegateMouseEventHandler
+ ) {
+ // Install the mouse handler on each child of the panel
+ component.components.forEach { mouseDelegate.installListenerOn(it) }
+ }
+
// TODO: Precomputing this is a hack... can we base it on the panel after it has been
// constructed?
override val widthConstraint =
diff --git a/device-manager-v2/testSrc/com/android/tools/idea/devicemanagerv2/DeviceManagerPanelTest.kt b/device-manager-v2/testSrc/com/android/tools/idea/devicemanagerv2/DeviceManagerPanelTest.kt
index e56f107..3589b61 100644
--- a/device-manager-v2/testSrc/com/android/tools/idea/devicemanagerv2/DeviceManagerPanelTest.kt
+++ b/device-manager-v2/testSrc/com/android/tools/idea/devicemanagerv2/DeviceManagerPanelTest.kt
@@ -21,16 +21,16 @@
import com.android.sdklib.deviceprovisioner.DeviceState
import com.android.sdklib.deviceprovisioner.DeviceTemplate
import com.android.tools.adtui.categorytable.CategoryTable
-import com.android.tools.adtui.categorytable.RowKey
+import com.android.tools.adtui.categorytable.IconButton
+import com.android.tools.adtui.categorytable.RowKey.ValueRowKey
+import com.android.tools.adtui.swing.FakeUi
import com.google.common.truth.Truth.assertThat
import com.intellij.openapi.project.Project
import com.intellij.testFramework.ProjectRule
import icons.StudioIcons
-import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.cancel
import kotlinx.coroutines.channels.Channel
-import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.consumeAsFlow
import kotlinx.coroutines.flow.stateIn
@@ -60,7 +60,7 @@
deviceHandles.send(listOf(pixel5Emulator, pixel6))
deviceTemplates.send(listOf(pixel5Template))
- deviceTable.selection.selectRow(RowKey.ValueRowKey(pixel5Template))
+ deviceTable.selection.selectRow(ValueRowKey(pixel5Template))
assertThat(deviceTable.values).hasSize(3)
val originalValues = deviceTable.values.map { it.key() }
@@ -74,7 +74,7 @@
.inOrder()
assertThat(deviceTable.selection.selectedKeys())
- .containsExactly(RowKey.ValueRowKey<DeviceRowData>(pixel5Handle))
+ .containsExactly(ValueRowKey<DeviceRowData>(pixel5Handle))
}
@Test
@@ -108,6 +108,54 @@
assertThat(deviceTable.visibleKeys()).containsExactly(pixel4, pixel5Template, pixel6)
}
+ @Test
+ fun detailsTracksSelection() = runTestWithFixture {
+ val pixel4 = createHandle("Pixel 4")
+ val pixel5 = createHandle("Pixel 5")
+
+ deviceHandles.send(listOf(pixel4, pixel5))
+
+ val pixel4Row = DeviceRowData.create(pixel4, emptyList())
+ val pixel5Row = DeviceRowData.create(pixel5, emptyList())
+
+ ViewDetailsAction().actionPerformed(actionEvent(dataContext(panel, deviceRowData = pixel4Row)))
+
+ assertThat(panel.deviceDetailsPanelRow).isEqualTo(pixel4Row)
+
+ deviceTable.selection.selectRow(ValueRowKey(pixel5))
+
+ assertThat(panel.deviceDetailsPanelRow).isEqualTo(pixel5Row)
+
+ deviceTable.selection.clear()
+
+ // Shouldn't change anything
+ assertThat(panel.deviceDetailsPanelRow).isEqualTo(pixel5Row)
+ }
+
+ @Test
+ fun clickRow() = runTestWithFixture {
+ val pixel4 = createHandle("Pixel 4")
+ val pixel5 = createHandle("Pixel 5")
+
+ deviceHandles.send(listOf(pixel4, pixel5))
+
+ panel.setBounds(0, 0, 800, 400)
+
+ val fakeUi = FakeUi(panel, createFakeWindow = true)
+ fakeUi.layout()
+
+ assertThat(deviceTable.selection.selectedKeys()).isEmpty()
+
+ val runButton =
+ checkNotNull(fakeUi.findComponent<IconButton> { it.baseIcon == StudioIcons.Avd.RUN })
+ assertThat(runButton.isEnabled).isTrue()
+
+ fakeUi.clickOn(runButton)
+
+ assertThat(deviceTable.selection.selectedKeys())
+ .containsExactly(ValueRowKey<DeviceRowData>(pixel4))
+ }
+
fun <T : Any> CategoryTable<T>.visibleKeys() =
values.mapNotNull { primaryKey(it).takeIf { isRowVisibleByKey(it) } }
@@ -141,23 +189,14 @@
val deviceTable = panel.deviceTable
fun createHandle(name: String, sourceTemplate: DeviceTemplate? = null) =
- FakeDeviceHandle(scope.createChildScope(isSupervisor = true), name, sourceTemplate)
- fun createTemplate(name: String) = FakeDeviceTemplate(name)
- }
-
- private class FakeDeviceHandle(
- override val scope: CoroutineScope,
- val name: String,
- override val sourceTemplate: DeviceTemplate?,
- ) : DeviceHandle {
- override val stateFlow =
- MutableStateFlow<DeviceState>(
- DeviceState.Disconnected(
- DeviceProperties.build {
- model = name
- icon = StudioIcons.DeviceExplorer.PHYSICAL_DEVICE_PHONE
- }
- )
+ FakeDeviceHandle(
+ scope.createChildScope(isSupervisor = true),
+ sourceTemplate,
+ DeviceProperties.build {
+ model = name
+ icon = StudioIcons.DeviceExplorer.PHYSICAL_DEVICE_PHONE
+ }
)
+ fun createTemplate(name: String) = FakeDeviceTemplate(name)
}
}
diff --git a/device-manager-v2/testSrc/com/android/tools/idea/devicemanagerv2/FakeDeviceHandle.kt b/device-manager-v2/testSrc/com/android/tools/idea/devicemanagerv2/FakeDeviceHandle.kt
index 75323e1..71ae0a4 100644
--- a/device-manager-v2/testSrc/com/android/tools/idea/devicemanagerv2/FakeDeviceHandle.kt
+++ b/device-manager-v2/testSrc/com/android/tools/idea/devicemanagerv2/FakeDeviceHandle.kt
@@ -20,6 +20,7 @@
import com.android.sdklib.deviceprovisioner.DeviceHandle
import com.android.sdklib.deviceprovisioner.DeviceProperties
import com.android.sdklib.deviceprovisioner.DeviceState
+import com.android.sdklib.deviceprovisioner.DeviceTemplate
import com.android.sdklib.deviceprovisioner.RepairDeviceAction
import com.android.sdklib.deviceprovisioner.TestDefaultDeviceActionPresentation
import icons.StudioIcons
@@ -29,13 +30,12 @@
internal class FakeDeviceHandle(
override val scope: CoroutineScope,
+ override val sourceTemplate: DeviceTemplate? = null,
+ initialProperties: DeviceProperties =
+ DeviceProperties.build { icon = StudioIcons.DeviceExplorer.PHYSICAL_DEVICE_PHONE },
) : DeviceHandle {
override val stateFlow =
- MutableStateFlow<DeviceState>(
- DeviceState.Disconnected(
- DeviceProperties.build { icon = StudioIcons.DeviceExplorer.PHYSICAL_DEVICE_PHONE }
- )
- )
+ MutableStateFlow<DeviceState>(DeviceState.Disconnected(initialProperties))
override val activationAction = FakeActivationAction()
override val deactivationAction = FakeDeactivationAction()
override val repairDeviceAction = FakeRepairDeviceAction()
diff --git a/intellij.android.avdmanager.tests/src/com/android/tools/idea/avdmanager/Definitions.java b/intellij.android.avdmanager.tests/src/com/android/tools/idea/avdmanager/Definitions.java
index 1484104..b104633 100644
--- a/intellij.android.avdmanager.tests/src/com/android/tools/idea/avdmanager/Definitions.java
+++ b/intellij.android.avdmanager.tests/src/com/android/tools/idea/avdmanager/Definitions.java
@@ -28,54 +28,56 @@
@NotNull
static Device mockPhone() {
- return mockDefinition(null, mockHardware(0));
+ return mockDefinition(null, mockHardware(0), "Pixel Fold", "pixel_fold");
}
@NotNull
static Device mockTablet() {
- return mockDefinition(null, mockHardware(10.055));
+ return mockDefinition(null, mockHardware(10.055), "Pixel Tablet", "pixel_tablet");
}
@NotNull
static Device mockWearOsDefinition() {
- return mockDefinition("android-wear", mockHardware(0));
+ return mockDefinition("android-wear", mockHardware(0), "Wear OS Square", "wearos_square");
}
@NotNull
static Device mockDesktop() {
- return mockDefinition("android-desktop", null);
+ return mockDefinition("android-desktop", null, "Medium Desktop", "desktop_medium");
}
@NotNull
static Device mockTv() {
- return mockDefinition("android-tv", mockHardware(0));
+ return mockDefinition("android-tv", mockHardware(0), "Television (1080p)", "tv_1080p");
}
@NotNull
static Device mockAutomotiveDefinition() {
- return mockDefinition("android-automotive", mockHardware(0));
+ return mockDefinition("android-automotive", mockHardware(0), "Automotive (1024p landscape)", "automotive_1024p_landscape");
}
@NotNull
static Device mockLegacyDefinition() {
- var definition = mockDefinition(null, null);
+ var definition = mockDefinition(null, null, "Nexus S", "Nexus S");
Mockito.when(definition.getIsDeprecated()).thenReturn(true);
return definition;
}
@NotNull
- private static Device mockDefinition(@Nullable String id, @Nullable Hardware hardware) {
+ static Device mockDefinition(@Nullable String tagId, @Nullable Hardware hardware, @NotNull String name, @NotNull String id) {
var definition = Mockito.mock(Device.class);
- Mockito.when(definition.getTagId()).thenReturn(id);
+ Mockito.when(definition.getTagId()).thenReturn(tagId);
Mockito.when(definition.getDefaultHardware()).thenReturn(hardware);
+ Mockito.when(definition.getDisplayName()).thenReturn(name);
+ Mockito.when(definition.getId()).thenReturn(id);
return definition;
}
@NotNull
- private static Hardware mockHardware(double length) {
+ static Hardware mockHardware(double length) {
var screen = Mockito.mock(Screen.class);
Mockito.when(screen.getDiagonalLength()).thenReturn(length);
diff --git a/intellij.android.avdmanager.tests/src/com/android/tools/idea/avdmanager/DeviceDefinitionListTest.java b/intellij.android.avdmanager.tests/src/com/android/tools/idea/avdmanager/DeviceDefinitionListTest.java
new file mode 100644
index 0000000..d7f35dd
--- /dev/null
+++ b/intellij.android.avdmanager.tests/src/com/android/tools/idea/avdmanager/DeviceDefinitionListTest.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.tools.idea.avdmanager;
+
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.Mockito;
+
+@RunWith(JUnit4.class)
+public final class DeviceDefinitionListTest {
+ @Test
+ public void deviceDefinitionList() {
+ // Arrange
+ var definitions = List.of(Definitions.mockPhone(),
+ Definitions.mockTablet(),
+ Definitions.mockWearOsDefinition(),
+ Definitions.mockDesktop(),
+ Definitions.mockDefinition("android-tv", Definitions.mockHardware(0), "Android TV (1080p)", "tv_1080p"),
+ Definitions.mockAutomotiveDefinition(),
+ Definitions.mockLegacyDefinition());
+
+ var supplier = Mockito.mock(DeviceSupplier.class);
+ Mockito.when(supplier.get()).thenReturn(definitions);
+
+ // Act
+ new DeviceDefinitionList(supplier);
+ }
+}
diff --git a/profilers-ui/src/com/android/tools/profilers/cpu/analysis/FullTraceSummaryDetailsView.kt b/profilers-ui/src/com/android/tools/profilers/cpu/analysis/FullTraceSummaryDetailsView.kt
index 593d4e5..7cc2786 100644
--- a/profilers-ui/src/com/android/tools/profilers/cpu/analysis/FullTraceSummaryDetailsView.kt
+++ b/profilers-ui/src/com/android/tools/profilers/cpu/analysis/FullTraceSummaryDetailsView.kt
@@ -21,7 +21,8 @@
import com.android.tools.profilers.StudioProfilersView
import com.android.tools.profilers.StringFormattingUtils.formatLongValueWithCommas
import com.android.tools.profilers.cpu.analysis.PowerRailTableUtils.POWER_RAIL_TOTAL_VALUE_IN_RANGE_TOOLTIP_MSG
-import com.android.tools.profilers.cpu.analysis.PowerRailTableUtils.computeCumulativeEnergyUsageInRange
+import com.android.tools.profilers.cpu.analysis.PowerRailTableUtils.computeCumulativeEnergyInRange
+import com.android.tools.profilers.cpu.analysis.PowerRailTableUtils.computePowerUsageRange
import com.android.tools.profilers.cpu.systemtrace.PowerRailTrackModel.Companion.POWER_RAIL_UNIT
import com.google.common.annotations.VisibleForTesting
import com.intellij.util.ui.JBUI
@@ -39,6 +40,9 @@
@get: VisibleForTesting
val energyUsedLabel = JLabel()
+ @get: VisibleForTesting
+ var powerRailTable: PowerRailTable? = null
+
init {
addRowToCommonSection("Time Range", timeRangeLabel)
addRowToCommonSection("Duration", durationLabel)
@@ -47,8 +51,9 @@
cpuCapture.systemTraceData?.powerRailCounters?.let { powerRailCounters ->
if (powerRailCounters.isNotEmpty()) {
addRowToCommonSectionWithInfoIcon("Total Energy Used in Range", energyUsedLabel, POWER_RAIL_TOTAL_VALUE_IN_RANGE_TOOLTIP_MSG)
- addSection(
- PowerRailTable(profilersView.studioProfilers, powerRailCounters, tabModel.selectionRange, tabModel.captureRange).component)
+ powerRailTable = PowerRailTable(profilersView.studioProfilers, powerRailCounters, tabModel.selectionRange,
+ tabModel.captureRange)
+ addSection(powerRailTable!!.component)
}
}
// Add a collapsible Help Text section containing Navigation and Analysis instructions (initially collapsed)
@@ -70,7 +75,8 @@
val cpuCapture = tabModel.dataSeries[0]
val powerRailCounters = cpuCapture.systemTraceData?.powerRailCounters
powerRailCounters?.forEach {
- totalEnergyUws += computeCumulativeEnergyUsageInRange(it.value.cumulativeData, selectionRange)
+ val powerUsageRange = computePowerUsageRange(it.value.cumulativeData, selectionRange)
+ totalEnergyUws += computeCumulativeEnergyInRange(powerUsageRange)
}
energyUsedLabel.text = "${formatLongValueWithCommas(totalEnergyUws)} $POWER_RAIL_UNIT"
diff --git a/profilers-ui/src/com/android/tools/profilers/cpu/analysis/PowerRailTable.kt b/profilers-ui/src/com/android/tools/profilers/cpu/analysis/PowerRailTable.kt
index 462a1f4..e1d208f 100644
--- a/profilers-ui/src/com/android/tools/profilers/cpu/analysis/PowerRailTable.kt
+++ b/profilers-ui/src/com/android/tools/profilers/cpu/analysis/PowerRailTable.kt
@@ -24,7 +24,9 @@
import com.android.tools.profilers.StudioProfilers
import com.android.tools.profilers.StringFormattingUtils.formatLongValueWithCommas
import com.android.tools.profilers.cpu.analysis.PowerRailTableUtils.POWER_RAIL_TOTAL_VALUE_IN_RANGE_TOOLTIP_MSG
-import com.android.tools.profilers.cpu.analysis.PowerRailTableUtils.computeCumulativeEnergyUsageInRange
+import com.android.tools.profilers.cpu.analysis.PowerRailTableUtils.computeAveragePowerInRange
+import com.android.tools.profilers.cpu.analysis.PowerRailTableUtils.computeCumulativeEnergyInRange
+import com.android.tools.profilers.cpu.analysis.PowerRailTableUtils.computePowerUsageRange
import com.android.tools.profilers.cpu.systemtrace.PowerCounterData
import com.android.tools.profilers.cpu.systemtrace.PowerRailTrackModel.Companion.POWER_RAIL_UNIT
import com.google.common.annotations.VisibleForTesting
@@ -33,22 +35,22 @@
import icons.StudioIcons
import java.awt.Component
import java.awt.FlowLayout
+import java.text.DecimalFormat
import javax.swing.JComponent
import javax.swing.JLabel
import javax.swing.JPanel
import javax.swing.JTable
-import javax.swing.SwingConstants
import javax.swing.table.AbstractTableModel
import javax.swing.table.TableCellRenderer
import javax.swing.table.TableRowSorter
/**
- * UI component for a table presenting the power consumption per power rail in the user-selected range.
+ * UI component for a table presenting the average power and cumulative energy consumption per power rail in the user-selected range.
* Takes a map of power rail names to their respective [PowerCounterData] and generates [PowerRailRow]s.
* The table will look like this:
- * | Power Rail | Cumulative consumption (µWs) |
- * | Foo | 123,456,789 |
- * | Bar | 987,654,321 |
+ * | Power Rail | Average power (mW) | Cumulative consumption (µWs) |
+ * | Foo | 20.0 | 123,456,789 |
+ * | Bar | 50.0 | 987,654,321 |
*
* @param powerRailCounters map of [String] to [PowerCounterData], where [PowerCounterData] includes the cumulative power series data
* @param range a range to query power data on
@@ -69,14 +71,24 @@
autoCreateRowSorter = true
showVerticalLines = true
showHorizontalLines = false
- columnModel.getColumn(Column.RAIL_NAME.ordinal).cellRenderer = CustomBorderTableCellRenderer().apply {
- horizontalAlignment = SwingConstants.LEFT
+
+ columnModel.getColumn(Column.RAIL_NAME.ordinal).cellRenderer = CustomBorderTableCellRenderer()
+ val avgPowerColumnRenderer = object : CustomBorderTableCellRenderer() {
+ override fun getTableCellRendererComponent(table: JTable?,
+ value: Any?,
+ isSelected: Boolean,
+ hasFocus: Boolean,
+ row: Int,
+ column: Int): Component {
+ val decimalFormat = DecimalFormat("#.##")
+ val newValue = decimalFormat.format(value)
+ return super.getTableCellRendererComponent(table, newValue, isSelected, hasFocus, row, column)
+ }
}
+ columnModel.getColumn(Column.AVERAGE.ordinal).cellRenderer = avgPowerColumnRenderer
// The following is an override of the non-header cells of the Cumulative column's rendering behavior.
- // This override will fulfill the following desired properties for the cumulative values:
- // (1) Formatting with commas to be more readable
- // (2) Right justification
+ // This override formats the cumulative values with commas to be more readable.
val cumulativeColumnRenderer = object : CustomBorderTableCellRenderer() {
override fun getTableCellRendererComponent(table: JTable?,
value: Any?,
@@ -84,37 +96,37 @@
hasFocus: Boolean,
row: Int,
column: Int): Component {
- val newValue = if (value is Long) formatLongValueWithCommas(value) else value
+ val newValue = if (value is Long && column == Column.CONSUMPTION.ordinal) formatLongValueWithCommas(value) else value
return super.getTableCellRendererComponent(table, newValue, isSelected, hasFocus, row, column)
}
-
- override fun getHorizontalAlignment(): Int {
- return SwingConstants.RIGHT
- }
}
columnModel.getColumn(Column.CONSUMPTION.ordinal).cellRenderer = cumulativeColumnRenderer
}
// The following is an override of the default table header rendering behavior.
- // This override will enable the Cumulative column header to have the following desires properties:
- // (1) Right justification
- // (2) Containing an info icon
- // (3) Containing a tooltip warning user about the data's accuracy
+ // This override will create a custom JLabel component to host the Cumulative column header with the following properties:
+ // (1) Contains an info icon
+ // (2) Contains a tooltip warning user about the data's accuracy
table.tableHeader.columnModel.getColumn(
Column.CONSUMPTION.ordinal).headerRenderer = TableCellRenderer { table, value, isSelected, hasFocus, row, column ->
val headerComponent: Component = table.tableHeader.defaultRenderer.getTableCellRendererComponent(table, value, isSelected, hasFocus,
row, column)
- JPanel().apply {
- // By default, table header for a JBTable is left justified. By wrapping the new header rendering into a panel
- // with a FlowLayout with RIGHT positioning, we can right justify the header content.
- layout = FlowLayout(FlowLayout.RIGHT)
+ JLabel().apply {
+ // All other table headers are left justified by default, so we left justify this custom table header as well.
+ layout = FlowLayout(FlowLayout.LEFT)
+ border = JBUI.Borders.empty()
+ if (headerComponent is JComponent) {
+ // This customer header component does not have the default margin that other table header's get, so we add it.
+ headerComponent.border = JBUI.Borders.emptyLeft(2)
+ }
// Add the header label/text.
add(headerComponent)
// Add the info icon.
val iconComp = JLabel()
iconComp.icon = StudioIcons.Common.INFO_INLINE
+ iconComp.border = JBUI.Borders.empty()
add(iconComp)
// Add the info tooltip regarding the data's accuracy.
@@ -187,7 +199,14 @@
private fun computePowerSummary() {
dataRows = powerRailCounters.map {
- PowerRailRow(it.key, computeCumulativeEnergyUsageInRange(it.value.cumulativeData, selectionRange))
+ val powerUsageRange = computePowerUsageRange(it.value.cumulativeData, selectionRange)
+ // When the range selection only contains one or zero data points, it is possible for the
+ // upper bound's timestamp (x) to be less than or equal to the lower bound's timestamp (x).
+ // Thus, in this case, the cumulative value should be 0 as there is no positive difference
+ // in start and end power values.
+ val consumption = computeCumulativeEnergyInRange(powerUsageRange)
+ val average = computeAveragePowerInRange(powerUsageRange)
+ PowerRailRow(it.key, consumption, average)
}
fireTableDataChanged()
}
@@ -209,6 +228,11 @@
return data.name
}
},
+ AVERAGE("Average power (mW)", java.lang.Double::class.java) {
+ override fun getValueFrom(data: PowerRailRow): Double {
+ return data.average
+ }
+ },
CONSUMPTION("Cumulative consumption ($POWER_RAIL_UNIT)", java.lang.Long::class.java) {
override fun getValueFrom(data: PowerRailRow): Long {
return data.consumption
@@ -224,5 +248,6 @@
*/
private data class PowerRailRow(
val name: String,
- val consumption: Long
+ val consumption: Long,
+ val average: Double
)
\ No newline at end of file
diff --git a/profilers-ui/src/com/android/tools/profilers/cpu/analysis/PowerRailTableUtils.kt b/profilers-ui/src/com/android/tools/profilers/cpu/analysis/PowerRailTableUtils.kt
index d2ec792..ba712d1 100644
--- a/profilers-ui/src/com/android/tools/profilers/cpu/analysis/PowerRailTableUtils.kt
+++ b/profilers-ui/src/com/android/tools/profilers/cpu/analysis/PowerRailTableUtils.kt
@@ -18,19 +18,56 @@
import com.android.tools.adtui.model.Range
import com.android.tools.adtui.model.SeriesData
import com.google.common.annotations.VisibleForTesting
+import java.util.concurrent.TimeUnit
object PowerRailTableUtils {
+ private fun isPowerRangeInvalid(lowerBoundTs: Long, upperBoundTs: Long) = lowerBoundTs >= upperBoundTs
+
+ fun computeCumulativeEnergyInRange(powerUsageRange: PowerUsageRange) : Long {
+ val lowerBound = powerUsageRange.lowerBound
+ val upperBound = powerUsageRange.upperBound
+ val lowerBoundTs = lowerBound.x
+ val upperBoundTs = upperBound.x
+ return if (isPowerRangeInvalid(lowerBoundTs, upperBoundTs)) {
+ 0
+ }
+ else {
+ upperBound.value - lowerBound.value
+ }
+ }
+
+ fun computeAveragePowerInRange(powerUsageRange: PowerUsageRange) : Double {
+ val lowerBound = powerUsageRange.lowerBound
+ val upperBound = powerUsageRange.upperBound
+ val lowerBoundTs = lowerBound.x
+ val upperBoundTs = upperBound.x
+ val durationMs = TimeUnit.MICROSECONDS.toMillis(upperBoundTs - lowerBoundTs)
+ return if (isPowerRangeInvalid(lowerBoundTs, upperBoundTs) || durationMs == 0L) {
+ 0.0
+ }
+ else {
+ val cumulativeEnergy = computeCumulativeEnergyInRange(powerUsageRange)
+ // The time is in micro-seconds, so this converts it to milliseconds.
+ cumulativeEnergy / durationMs.toDouble()
+ }
+ }
+
+ data class PowerUsageRange(
+ val lowerBound: SeriesData<Long>,
+ val upperBound: SeriesData<Long>
+ )
+
/**
- * Computes and returns the cumulative energy used in the passed-in range.
+ * Computes and returns the cumulative power used in the passed-in range.
*/
- fun computeCumulativeEnergyUsageInRange(cumulativeData: List<SeriesData<Long>>, selectionRange: Range): Long {
+ fun computePowerUsageRange(cumulativeData: List<SeriesData<Long>>, selectionRange: Range): PowerUsageRange {
val lowerBound = getLowerBoundDataInRange(cumulativeData, selectionRange.min)
val upperBound = getUpperBoundDataInRange(cumulativeData, selectionRange.max)
// When the range selection only contains one or zero data points, it is possible for the
// upper bound's timestamp (x) to be less than or equal to the lower bound's timestamp (x).
// Thus, in this case, the cumulative value should be 0 as there is no positive difference
- // in start and end energy values.
- return if (upperBound.x > lowerBound.x) (upperBound.value - lowerBound.value) else 0
+ // in start and end power values.
+ return PowerUsageRange(lowerBound, upperBound)
}
/**
diff --git a/profilers-ui/testSrc/com/android/tools/profilers/cpu/analysis/FullTraceSummaryDetailsViewTest.kt b/profilers-ui/testSrc/com/android/tools/profilers/cpu/analysis/FullTraceSummaryDetailsViewTest.kt
index fbe5a46..f2376bf 100644
--- a/profilers-ui/testSrc/com/android/tools/profilers/cpu/analysis/FullTraceSummaryDetailsViewTest.kt
+++ b/profilers-ui/testSrc/com/android/tools/profilers/cpu/analysis/FullTraceSummaryDetailsViewTest.kt
@@ -17,6 +17,7 @@
import com.android.tools.adtui.model.FakeTimer
import com.android.tools.adtui.model.Range
+import com.android.tools.adtui.ui.HideablePanel
import com.android.tools.idea.transport.faketransport.FakeGrpcServer
import com.android.tools.idea.transport.faketransport.FakeTransportService
import com.android.tools.profilers.FakeIdeProfilerComponents
@@ -28,7 +29,6 @@
import com.android.tools.profilers.cpu.CpuCapture
import com.android.tools.profilers.cpu.systemtrace.CounterModel
import com.android.tools.profilers.cpu.systemtrace.ProcessModel
-import com.android.tools.profilers.cpu.systemtrace.SystemTraceCpuCapture
import com.android.tools.profilers.cpu.systemtrace.SystemTraceCpuCaptureBuilder
import com.android.tools.profilers.cpu.systemtrace.SystemTraceCpuCaptureBuilderTest
import com.android.tools.profilers.cpu.systemtrace.ThreadModel
@@ -153,5 +153,17 @@
assertThat(view.commonSection.componentCount).isEqualTo(6)
// Three components expected: the common section, the power table section, and the help text section.
assertThat(view.components.size).isEqualTo(3)
+
+ // Assert that the power rail table section is a {@link HideablePanel}
+ assertThat(view.components[1]).isInstanceOf(HideablePanel::class.java)
+
+ // Assert that the power rail table has the correct dimensions and column headers.
+ assertThat(view.powerRailTable).isNotNull()
+ assertThat(view.powerRailTable!!.table.columnCount).isEqualTo(3)
+ // Note: header is not included in the row count.
+ assertThat(view.powerRailTable!!.table.rowCount).isEqualTo(1)
+ assertThat(view.powerRailTable!!.table.tableHeader.columnModel.getColumn(0).headerValue).isEqualTo("Rail name")
+ assertThat(view.powerRailTable!!.table.tableHeader.columnModel.getColumn(1).headerValue).isEqualTo("Average power (mW)")
+ assertThat(view.powerRailTable!!.table.tableHeader.columnModel.getColumn(2).headerValue).isEqualTo("Cumulative consumption (µWs)")
}
}
\ No newline at end of file
diff --git a/profilers-ui/testSrc/com/android/tools/profilers/cpu/analysis/PowerRangeUtilsTest.kt b/profilers-ui/testSrc/com/android/tools/profilers/cpu/analysis/PowerRangeUtilsTest.kt
index 821b87f..a271924 100644
--- a/profilers-ui/testSrc/com/android/tools/profilers/cpu/analysis/PowerRangeUtilsTest.kt
+++ b/profilers-ui/testSrc/com/android/tools/profilers/cpu/analysis/PowerRangeUtilsTest.kt
@@ -17,7 +17,9 @@
import com.android.tools.adtui.model.Range
import com.android.tools.adtui.model.SeriesData
-import com.android.tools.profilers.cpu.analysis.PowerRailTableUtils.computeCumulativeEnergyUsageInRange
+import com.android.tools.profilers.cpu.analysis.PowerRailTableUtils.computeAveragePowerInRange
+import com.android.tools.profilers.cpu.analysis.PowerRailTableUtils.computeCumulativeEnergyInRange
+import com.android.tools.profilers.cpu.analysis.PowerRailTableUtils.computePowerUsageRange
import com.android.tools.profilers.cpu.analysis.PowerRailTableUtils.getLowerBoundDataInRange
import com.android.tools.profilers.cpu.analysis.PowerRailTableUtils.getUpperBoundDataInRange
import org.junit.Assert
@@ -26,39 +28,51 @@
class PowerRangeUtilsTest {
@Test
- fun testComputeCumulativePowerUsageInRangeEntireRange() {
- // Test case: Range encapsulates all data, expect first elements value subtracted from last elements value
- val dataList = listOf(SeriesData(10L, 10L), SeriesData(12L, 20L), SeriesData(15L, 30L))
+ fun testComputedPowerUsageInRangeEntireRange() {
+ // Test case: Range encapsulates all data, expect first element and last element used to compute energy and avg power
+ val dataList = listOf(SeriesData(1000L, 10000L), SeriesData(12000L, 20000L), SeriesData(26000L, 35000L))
- val powerUsedInRange = computeCumulativeEnergyUsageInRange(dataList, Range(9.0, 16.0))
- Assert.assertEquals(powerUsedInRange, 20L)
+ val powerUsageRange = computePowerUsageRange(dataList, Range(0.0, 27000.0))
+ val energyUsedInRange = computeCumulativeEnergyInRange(powerUsageRange)
+ val avgPowerInRange = computeAveragePowerInRange(powerUsageRange)
+ Assert.assertEquals(energyUsedInRange, 25000L)
+ Assert.assertEquals(1000.0, avgPowerInRange, 0.000000000001)
}
@Test
- fun testComputeCumulativePowerUsageInRangeSubsetRange() {
- // Test case: Range encapsulates last two data points, expect second to last's elements value subtracted from last elements value
- val dataList = listOf(SeriesData(10L, 10L), SeriesData(12L, 20L), SeriesData(15L, 30L))
+ fun testComputedPowerUsageInRangeSubsetRange() {
+ // Test case: Range encapsulates last two data points, expect second element and last element used to compute energy and avg power
+ val dataList = listOf(SeriesData(10000L, 10000L), SeriesData(12000L, 20000L), SeriesData(26000L, 35000L))
- val powerUsedInRange = computeCumulativeEnergyUsageInRange(dataList, Range(11.0, 16.0))
- Assert.assertEquals(powerUsedInRange, 10L)
+ val powerUsageRange = computePowerUsageRange(dataList, Range(11000.0, 27000.0))
+ val energyUsedInRange = computeCumulativeEnergyInRange(powerUsageRange)
+ val avgPowerInRange = computeAveragePowerInRange(powerUsageRange)
+ Assert.assertEquals(energyUsedInRange, 15000L)
+ Assert.assertEquals(15000 / (14000.0 / 1000.0), avgPowerInRange, 0.000000000001)
}
@Test
- fun testComputeCumulativePowerUsageInRangeUpperBoundLessThanLower() {
- // Test case: Range's upper bound timestamp is less than lower bound's timestamp, expect power used to be 0
- val dataList = listOf(SeriesData(10L, 10L), SeriesData(12L, 20L), SeriesData(15L, 30L))
+ fun testComputedPowerUsageInRangeUpperBoundLessThanLower() {
+ // Test case: Range's upper bound timestamp is less than lower bound's timestamp, expect total energy and avg power used to be 0
+ val dataList = listOf(SeriesData(10000L, 10000L), SeriesData(12000L, 20000L), SeriesData(26000L, 35000L))
- val powerUsedInRange = computeCumulativeEnergyUsageInRange(dataList, Range(12.0, 11.0))
- Assert.assertEquals(powerUsedInRange, 0L)
+ val powerUsageRange = computePowerUsageRange(dataList, Range(12000.0, 11000.0))
+ val energyUsedInRange = computeCumulativeEnergyInRange(powerUsageRange)
+ val avgPowerInRange = computeAveragePowerInRange(powerUsageRange)
+ Assert.assertEquals(energyUsedInRange, 0)
+ Assert.assertEquals(avgPowerInRange, 0.0, 0.000000000001)
}
@Test
- fun testComputeCumulativePowerUsageInRangeUpperBoundEqualToLower() {
- // Test case: Range's upper bound timestamp is equal to lower bound's timestamp, expect power used to be 0
- val dataList = listOf(SeriesData(10L, 10L), SeriesData(12L, 20L), SeriesData(15L, 30L))
+ fun testComputedPowerUsageInRangeUpperBoundEqualToLower() {
+ // Test case: Range's upper bound timestamp is equal to lower bound's timestamp, expect total energy and avg power used to be 0
+ val dataList = listOf(SeriesData(10000L, 10000L), SeriesData(12000L, 20000L), SeriesData(26000L, 35000L))
- val powerUsedInRange = computeCumulativeEnergyUsageInRange(dataList, Range(11.0, 11.0))
- Assert.assertEquals(powerUsedInRange, 0L)
+ val powerUsageRange = computePowerUsageRange(dataList, Range(11000.0, 11000.0))
+ val energyUsedInRange = computeCumulativeEnergyInRange(powerUsageRange)
+ val avgPowerInRange = computeAveragePowerInRange(powerUsageRange)
+ Assert.assertEquals(energyUsedInRange, 0)
+ Assert.assertEquals(avgPowerInRange, 0.0, 0.000000000001)
}
@Test
diff --git a/project-system-gradle/src/com/android/tools/idea/projectsystem/gradle/GradleProjectPath.kt b/project-system-gradle/src/com/android/tools/idea/projectsystem/gradle/GradleProjectPath.kt
index f82e944..f6689a5 100644
--- a/project-system-gradle/src/com/android/tools/idea/projectsystem/gradle/GradleProjectPath.kt
+++ b/project-system-gradle/src/com/android/tools/idea/projectsystem/gradle/GradleProjectPath.kt
@@ -19,7 +19,6 @@
import com.android.tools.idea.gradle.model.impl.IdeModuleSourceSetImpl
import com.android.tools.idea.projectsystem.ProjectSyncModificationTracker
import com.android.utils.FileUtils
-import com.intellij.openapi.externalSystem.model.project.ModuleData
import com.intellij.openapi.externalSystem.util.ExternalSystemApiUtil
import com.intellij.openapi.externalSystem.util.ExternalSystemApiUtil.getExternalModuleType
import com.intellij.openapi.module.Module
@@ -33,9 +32,7 @@
import org.jetbrains.annotations.VisibleForTesting
import org.jetbrains.plugins.gradle.execution.build.CachedModuleDataFinder
import org.jetbrains.plugins.gradle.util.GradleConstants
-import org.jetbrains.plugins.gradle.util.gradleIdentityPath
import org.jetbrains.plugins.gradle.util.gradleIdentityPathOrNull
-import org.jetbrains.plugins.gradle.util.gradlePath
import org.jetbrains.plugins.gradle.util.gradlePathOrNull
import org.jetbrains.plugins.gradle.util.isIncludedBuild
import java.io.File
@@ -152,7 +149,7 @@
fun Module.getGradleIdentityPath(): String? {
@Suppress("UnstableApiUsage")
- return CachedModuleDataFinder.getGradleModuleData(this)?.gradleIdentityPath
+ return CachedModuleDataFinder.getGradleModuleData(this)?.moduleData?.gradleIdentityPathOrNull
}
fun Module.getGradleProjectPath(): GradleProjectPath? {
diff --git a/rendering/src/com/android/tools/rendering/RenderExecutor.kt b/rendering/src/com/android/tools/rendering/RenderExecutor.kt
index c0c187e..645ac33 100644
--- a/rendering/src/com/android/tools/rendering/RenderExecutor.kt
+++ b/rendering/src/com/android/tools/rendering/RenderExecutor.kt
@@ -275,6 +275,11 @@
/** Returns true if the render thread is busy running some code, false otherwise. */
fun isBusy() = renderingExecutorService.isBusy
+ /**
+ * Returns true if called from the render thread.
+ */
+ fun isRenderThread(): Boolean = renderingExecutorService.hasSpawnedCurrentThread()
+
companion object {
@JvmStatic
fun create(): RenderExecutor {
diff --git a/rendering/src/com/android/tools/rendering/RenderService.java b/rendering/src/com/android/tools/rendering/RenderService.java
index aec0bf3..0f90463 100644
--- a/rendering/src/com/android/tools/rendering/RenderService.java
+++ b/rendering/src/com/android/tools/rendering/RenderService.java
@@ -60,21 +60,36 @@
* The {@link RenderService} provides rendering and layout information for Android layouts. This is a wrapper around the layout library.
*/
final public class RenderService implements Disposable {
- private static RenderExecutor ourExecutor;
+ private static final Object ourExecutorLock = new Object();
+ private static RenderExecutor ourExecutor = getOrCreateExecutor();
- static {
- ourExecutor = RenderExecutor.create();
+ @NotNull
+ private static RenderExecutor getOrCreateExecutor() {
+ synchronized (ourExecutorLock) {
+ if (ourExecutor == null) ourExecutor = RenderExecutor.create();
+ return ourExecutor;
+ }
+ }
+
+ @Nullable
+ private static RenderExecutor getExistingExecutor() {
+ synchronized (ourExecutorLock) {
+ return ourExecutor;
+ }
}
@TestOnly
public static void initializeRenderExecutor() {
assert ApplicationManager.getApplication().isUnitTestMode(); // Only to be called from unit testszs
- ourExecutor = RenderExecutor.create();
+ synchronized (ourExecutorLock) {
+ ourExecutor = RenderExecutor.create();
+ }
}
public static void shutdownRenderExecutor() {
- ourExecutor.shutdown();
+ RenderExecutor currentExecutor = getExistingExecutor();
+ if (currentExecutor != null) currentExecutor.shutdown();
}
/**
@@ -85,7 +100,9 @@
public static void shutdownRenderExecutor(@SuppressWarnings("SameParameterValue") long timeoutSeconds) {
assert ApplicationManager.getApplication().isUnitTestMode(); // Only to be called from unit tests
- ourExecutor.shutdown(timeoutSeconds);
+ // We avoid using getExecutor here since we do not want to create a new one if it doesn't exist
+ RenderExecutor currentExecutor = getExistingExecutor();
+ if (currentExecutor != null) currentExecutor.shutdown(timeoutSeconds);
}
private static final String JDK_INSTALL_URL = "https://developer.android.com/preview/setup-sdk.html#java8";
@@ -98,7 +115,7 @@
@NotNull
public static RenderAsyncActionExecutor getRenderAsyncActionExecutor() {
- return ourExecutor;
+ return getOrCreateExecutor();
}
/**
@@ -106,7 +123,7 @@
*/
@TestOnly
public static StackTraceElement[] getCurrentExecutionStackTrace() {
- return ourExecutor.currentStackTrace();
+ return getOrCreateExecutor().currentStackTrace();
}
public RenderService(@NotNull Consumer<RenderTaskBuilder> configureBuilder) {
@@ -148,7 +165,16 @@
* @return true if the underlying {@link RenderExecutor} is busy, false otherwise.
*/
public static boolean isBusy() {
- return ourExecutor.isBusy();
+ RenderExecutor currentExecutor = getExistingExecutor();
+ return currentExecutor != null && currentExecutor.isBusy();
+ }
+
+ /**
+ * @return true if called from the render thread.
+ */
+ public static boolean isRenderThread() {
+ RenderExecutor currentExecutor = getExistingExecutor();
+ return currentExecutor != null && currentExecutor.isRenderThread();
}
/**
diff --git a/rendering/src/com/android/tools/rendering/SingleThreadExecutorService.kt b/rendering/src/com/android/tools/rendering/SingleThreadExecutorService.kt
index 4646b1e..4c2c791 100644
--- a/rendering/src/com/android/tools/rendering/SingleThreadExecutorService.kt
+++ b/rendering/src/com/android/tools/rendering/SingleThreadExecutorService.kt
@@ -32,9 +32,7 @@
Integer.getInteger("layoutlib.thread.profile.timeoutms", 4000)
private val PROFILE_INTERVAL_MS = Integer.getInteger("layoutlib.thread.profile.intervalms", 2500)
-/**
- * Settings for profiling a thread.
- */
+/** Settings for profiling a thread. */
data class ThreadProfileSettings(
/** Ms to wait before considering a running task as slow and start profiling. */
val profileSlowTasksTimeoutMs: Long = PROFILE_SLOW_TASKS_TIMEOUT_MS.toLong(),
@@ -48,42 +46,45 @@
val onSlowThread: (Throwable) -> Unit
) {
companion object {
- val disabled = ThreadProfileSettings(
- maxSamples = 0,
- scheduledExecutorService = ScheduledThreadPoolExecutor(0),
- onSlowThread = {}
- )
+ val disabled =
+ ThreadProfileSettings(
+ maxSamples = 0,
+ scheduledExecutorService = ScheduledThreadPoolExecutor(0),
+ onSlowThread = {}
+ )
}
}
/**
- * Interface for a single threaded executor service with additional functionality to allow
- * for interrupting the thread and checking whether is active or not.
+ * Interface for a single threaded executor service with additional functionality to allow for
+ * interrupting the thread and checking whether is active or not.
*/
-interface SingleThreadExecutorService: ExecutorService {
+interface SingleThreadExecutorService : ExecutorService {
/** True if busy and a new command will not execute immediately. */
val isBusy: Boolean
+ /** Returns true if called from the thread that is handled by this [ExecutorService]. */
+ fun hasSpawnedCurrentThread(): Boolean
+
/**
- * Returns the current stack thread for the thread executing commands.
- * The stack trace might be empty if the thread is not currently running.
+ * Returns the current stack thread for the thread executing commands. The stack trace might be
+ * empty if the thread is not currently running.
*/
fun stackTrace(): Array<StackTraceElement>
- /**
- * Sends an interrupt to the thread and returns immediately.
- */
+ /** Sends an interrupt to the thread and returns immediately. */
fun interrupt()
companion object {
/**
- * Creates a new single thread [ExecutorService] with optional profiling. The [threadName] will name
- * the thread used by this executor.
+ * Creates a new single thread [ExecutorService] with optional profiling. The [threadName] will
+ * name the thread used by this executor.
*/
fun create(
threadName: String,
threadProfileSettings: ThreadProfileSettings
- ): SingleThreadExecutorService = ProfilingSingleThreadExecutorImpl(threadName, threadProfileSettings)
+ ): SingleThreadExecutorService =
+ ProfilingSingleThreadExecutorImpl(threadName, threadProfileSettings)
}
}
@@ -97,30 +98,28 @@
override val isBusy: Boolean
get() = _isBusy.get()
+ private val renderThreadGroup = ThreadGroup("Render Thread group")
+
/**
* The thread factory allows us controlling when the new thread is created to we can keep track of
* it. This allows us to capture the stack trace later.
*/
private val threadFactory = ThreadFactory {
- val newThread = Thread(null, it, threadName).apply { isDaemon = true }
+ val newThread = Thread(renderThreadGroup, it, threadName).apply { isDaemon = true }
theThread.set(newThread)
newThread
}
- private fun profileThread(
- remainingSamples: Int,
- profileThreadFuture: CompletableFuture<Unit>
- ) {
+ private fun profileThread(remainingSamples: Int, profileThreadFuture: CompletableFuture<Unit>) {
if (remainingSamples <= 0) return
if (!isBusy) return
threadProfileSettings.onSlowThread(
TimeoutException(
- "Slow render action ${threadProfileSettings.maxSamples-remainingSamples}/${threadProfileSettings.maxSamples}"
- ).also { slowException ->
- theThread.get()?.also {
- slowException.stackTrace = it.stackTrace
+ "Slow render action ${threadProfileSettings.maxSamples-remainingSamples}/${threadProfileSettings.maxSamples}"
+ )
+ .also { slowException ->
+ theThread.get()?.also { slowException.stackTrace = it.stackTrace }
}
- }
)
threadProfileSettings.scheduledExecutorService
.schedule(
@@ -133,34 +132,37 @@
}
}
private var profileThreadFuture: CompletableFuture<Unit>? = null
- private val threadPoolExecutor = object:
- ThreadPoolExecutor(1, 1, 0, TimeUnit.MILLISECONDS, PriorityBlockingQueue(), threadFactory) {
- override fun beforeExecute(t: Thread?, r: Runnable?) {
- _isBusy.set(true)
- profileThreadFuture = CompletableFuture()
- threadProfileSettings.scheduledExecutorService
- .schedule(
- { profileThread(threadProfileSettings.maxSamples, profileThreadFuture!!) },
- threadProfileSettings.profileSlowTasksTimeoutMs,
- TimeUnit.MILLISECONDS
- )
- .also { scheduledFuture ->
- profileThreadFuture!!.whenComplete { _, _ -> scheduledFuture.cancel(true) }
- }
- super.beforeExecute(t, r)
+ private val threadPoolExecutor =
+ object :
+ ThreadPoolExecutor(1, 1, 0, TimeUnit.MILLISECONDS, PriorityBlockingQueue(), threadFactory) {
+ override fun beforeExecute(t: Thread?, r: Runnable?) {
+ _isBusy.set(true)
+ profileThreadFuture = CompletableFuture()
+ threadProfileSettings.scheduledExecutorService
+ .schedule(
+ { profileThread(threadProfileSettings.maxSamples, profileThreadFuture!!) },
+ threadProfileSettings.profileSlowTasksTimeoutMs,
+ TimeUnit.MILLISECONDS
+ )
+ .also { scheduledFuture ->
+ profileThreadFuture!!.whenComplete { _, _ -> scheduledFuture.cancel(true) }
+ }
+ super.beforeExecute(t, r)
+ }
+
+ override fun afterExecute(r: Runnable?, t: Throwable?) {
+ val pendingProfiling = profileThreadFuture
+ profileThreadFuture = null
+ pendingProfiling?.cancel(true)
+ _isBusy.set(false)
+ super.afterExecute(r, t)
+ }
}
- override fun afterExecute(r: Runnable?, t: Throwable?) {
- val pendingProfiling = profileThreadFuture
- profileThreadFuture = null
- pendingProfiling?.cancel(true)
- _isBusy.set(false)
- super.afterExecute(r, t)
- }
- }
+ override fun hasSpawnedCurrentThread(): Boolean =
+ renderThreadGroup.parentOf(Thread.currentThread().threadGroup)
- override fun stackTrace(): Array<StackTraceElement> =
- theThread.get()?.stackTrace ?: emptyArray()
+ override fun stackTrace(): Array<StackTraceElement> = theThread.get()?.stackTrace ?: emptyArray()
override fun interrupt() {
theThread.get()?.interrupt()
diff --git a/rendering/src/com/android/tools/rendering/security/RenderSecurityManager.java b/rendering/src/com/android/tools/rendering/security/RenderSecurityManager.java
index 5465dc5..1a57dd0 100644
--- a/rendering/src/com/android/tools/rendering/security/RenderSecurityManager.java
+++ b/rendering/src/com/android/tools/rendering/security/RenderSecurityManager.java
@@ -16,6 +16,7 @@
package com.android.tools.rendering.security;
import com.android.annotations.Nullable;
+import com.android.tools.rendering.RenderService;
import com.android.utils.ILogger;
import com.intellij.openapi.application.PathManager;
@@ -31,6 +32,7 @@
import java.util.Arrays;
import java.util.PropertyPermission;
import java.util.concurrent.Callable;
+import java.util.function.Supplier;
import org.jetbrains.annotations.NotNull;
import static com.android.SdkConstants.*;
@@ -59,25 +61,6 @@
public static boolean sEnabled = !VALUE_FALSE.equals(System.getProperty(ENABLED_PROPERTY));
/**
- * Thread local data which indicates whether the current thread is relevant for
- * this security manager. This is an inheritable thread local such that any threads
- * spawned from this thread will also apply the security manager; otherwise code
- * could just create new threads and execute code separate from the security manager
- * there.
- */
- private static ThreadLocal<Boolean> sIsRenderThread = new InheritableThreadLocal<Boolean>() {
- @Override
- protected synchronized Boolean initialValue() {
- return Boolean.FALSE;
- }
-
- @Override
- protected synchronized Boolean childValue(Boolean parentValue) {
- return parentValue;
- }
- };
-
- /**
* Secret which must be provided by callers wishing to deactivate the security manager
*/
private static Object sCredential;
@@ -100,15 +83,7 @@
private boolean isRestrictReads;
- @NotNull
- private static String normalizeDirectoryPath(@NotNull Path originalPath) {
- return originalPath.normalize() + originalPath.getFileSystem().getSeparator();
- }
-
- @NotNull
- private static String normalizeDirectoryPath(@NotNull String stringPath) {
- return normalizeDirectoryPath(Paths.get(stringPath));
- }
+ private final Supplier<Boolean> isRenderThread;
/**
* Returns the current render security manager, if any. This will only return
@@ -117,12 +92,10 @@
*/
@Nullable
public static RenderSecurityManager getCurrent() {
- if (sIsRenderThread.get()) {
- SecurityManager securityManager = System.getSecurityManager();
- if (securityManager instanceof RenderSecurityManager) {
- RenderSecurityManager manager = (RenderSecurityManager)securityManager;
- return manager.isRelevant() ? manager : null;
- }
+ SecurityManager securityManager = System.getSecurityManager();
+ if (securityManager instanceof RenderSecurityManager) {
+ RenderSecurityManager manager = (RenderSecurityManager)securityManager;
+ return manager.isRelevant() ? manager : null;
}
return null;
@@ -147,11 +120,12 @@
* @param restrictReads when true, reads will be restricted to only a set of directories including temp directory and the given sdkPath
* and projectPath directories.
*/
- public RenderSecurityManager(
+ protected RenderSecurityManager(
@Nullable String sdkPath,
@Nullable String projectPath,
boolean restrictReads,
- @NotNull String[] allowedPaths) {
+ @NotNull String[] allowedPaths,
+ @NotNull Supplier<Boolean> isRenderThread) {
mSdkPath = sdkPath;
mProjectPath = projectPath;
mTempDir = System.getProperty("java.io.tmpdir");
@@ -160,23 +134,24 @@
//noinspection AssignmentToStaticFieldFromInstanceMethod
sLastFailedPath = null;
isRestrictReads = restrictReads;
+ this.isRenderThread = isRenderThread;
}
- public RenderSecurityManager(
+ @NotNull
+ static RenderSecurityManager createForTests(
+ @Nullable String sdkPath,
+ @Nullable String projectPath,
+ boolean restrictReads,
+ @NotNull Supplier<Boolean> isRenderThread) {
+ return new RenderSecurityManager(sdkPath, projectPath, restrictReads, RenderSecurityManagerDefaults.getDefaultAllowedPaths(), isRenderThread);
+ }
+
+ @NotNull
+ public static RenderSecurityManager create(
@Nullable String sdkPath,
@Nullable String projectPath,
boolean restrictReads) {
- this(sdkPath, projectPath, restrictReads, new String[]{
- // When loading classes, IntelliJ might sometimes drop a corruption marker
- normalizeDirectoryPath(PathManager.getIndexRoot()),
- /*
- Root of the path where IntelliJ stores the logs. When loading classes,
- IntelliJ might try to update cache hashes for the loaded files
- */
- normalizeDirectoryPath(PathManager.getLogPath()),
- // When loading classes, IntelliJ might try to update cache hashes for the loaded files
- normalizeDirectoryPath(Paths.get(PathManager.getSystemPath(), "caches"))
- });
+ return new RenderSecurityManager(sdkPath, projectPath, restrictReads, RenderSecurityManagerDefaults.getDefaultAllowedPaths(), RenderService::isRenderThread);
}
/**
@@ -216,7 +191,6 @@
// Enable
assert !(current instanceof RenderSecurityManager);
myPreviousSecurityManager = current;
- sIsRenderThread.set(true);
mDisabled = false;
System.setSecurityManager(this);
//noinspection AssignmentToStaticFieldFromInstanceMethod
@@ -246,20 +220,18 @@
System.setSecurityManager(myPreviousSecurityManager);
}
else if (mLogger != null) {
- sIsRenderThread.set(false);
mLogger.warning("Security manager was changed behind the scenes: ", current);
}
}
finally {
mDisabled = true;
mAllowSetSecurityManager = false;
- sIsRenderThread.set(false);
}
}
}
protected boolean isRelevant() {
- return sEnabled && !mDisabled && sIsRenderThread.get();
+ return sEnabled && !mDisabled && isRenderThread.get();
}
/**
diff --git a/rendering/src/com/android/tools/rendering/security/RenderSecurityManagerDefaults.kt b/rendering/src/com/android/tools/rendering/security/RenderSecurityManagerDefaults.kt
new file mode 100644
index 0000000..29f907f
--- /dev/null
+++ b/rendering/src/com/android/tools/rendering/security/RenderSecurityManagerDefaults.kt
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.tools.rendering.security
+
+import com.intellij.openapi.application.PathManager
+import java.nio.file.Path
+import java.nio.file.Paths
+
+private fun normalizeDirectoryPath(originalPath: Path): String =
+ originalPath.normalize().toString() + originalPath.fileSystem.separator
+
+private fun normalizeDirectoryPath(stringPath: String): String =
+ normalizeDirectoryPath(Paths.get(stringPath))
+
+object RenderSecurityManagerDefaults {
+ @JvmStatic
+ fun getDefaultAllowedPaths(): Array<String> =
+ arrayOf(
+ // When loading classes, IntelliJ might sometimes drop a corruption marker
+ normalizeDirectoryPath(PathManager.getIndexRoot()),
+ /*
+ Root of the path where IntelliJ stores the logs. When loading classes,
+ IntelliJ might try to update cache hashes for the loaded files
+ */
+ normalizeDirectoryPath(PathManager.getLogPath()),
+ // When loading classes, IntelliJ might try to update cache hashes for the loaded files
+ normalizeDirectoryPath(Paths.get(PathManager.getSystemPath(), "caches"))
+ )
+}
\ No newline at end of file
diff --git a/rendering/testSrc/com/android/tools/rendering/RenderExecutorTest.kt b/rendering/testSrc/com/android/tools/rendering/RenderExecutorTest.kt
index b1b9d66..f96e68f 100644
--- a/rendering/testSrc/com/android/tools/rendering/RenderExecutorTest.kt
+++ b/rendering/testSrc/com/android/tools/rendering/RenderExecutorTest.kt
@@ -48,6 +48,7 @@
private class TestSingleThreadExecutorService(private val delegate: ExecutorService): AbstractExecutorService(), SingleThreadExecutorService {
override val isBusy: Boolean = false
+ override fun hasSpawnedCurrentThread(): Boolean = false
override fun stackTrace(): Array<StackTraceElement> = emptyArray()
override fun interrupt() {}
override fun execute(command: Runnable) = delegate.execute(command)
diff --git a/rendering/testSrc/com/android/tools/rendering/SingleThreadExecutorServiceTest.kt b/rendering/testSrc/com/android/tools/rendering/SingleThreadExecutorServiceTest.kt
index 475f7f3..e69a320 100644
--- a/rendering/testSrc/com/android/tools/rendering/SingleThreadExecutorServiceTest.kt
+++ b/rendering/testSrc/com/android/tools/rendering/SingleThreadExecutorServiceTest.kt
@@ -17,10 +17,12 @@
import com.android.testutils.VirtualTimeScheduler
import java.util.concurrent.CompletableFuture
+import java.util.concurrent.CountDownLatch
import java.util.concurrent.TimeUnit
import org.junit.Assert
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertTrue
import org.junit.Test
-import java.util.concurrent.CountDownLatch
class SingleThreadExecutorServiceTest {
@Test
@@ -66,4 +68,29 @@
executor.shutdown()
}
+
+ @Test
+ fun testHasSpawnedCurrentThread() {
+ val executor =
+ SingleThreadExecutorService.create(
+ "Test thread",
+ threadProfileSettings = ThreadProfileSettings.disabled
+ )
+
+ var exception: Throwable? = null
+ executor.submit {
+ assertTrue(executor.hasSpawnedCurrentThread())
+
+ Thread {
+ // Child threads must also return hasSpawnedCurrentThread
+ assertTrue(executor.hasSpawnedCurrentThread())
+ }.also {
+ it.setUncaughtExceptionHandler { t, e -> exception = e }
+ it.start()
+ it.join()
+ }
+ exception?.let { throw it }
+ }.get()
+ assertFalse(executor.hasSpawnedCurrentThread())
+ }
}
diff --git a/rendering/testSrc/com/android/tools/rendering/security/RenderSecurityManagerTest.java b/rendering/testSrc/com/android/tools/rendering/security/RenderSecurityManagerTest.java
index a7d1285..21c14b3 100644
--- a/rendering/testSrc/com/android/tools/rendering/security/RenderSecurityManagerTest.java
+++ b/rendering/testSrc/com/android/tools/rendering/security/RenderSecurityManagerTest.java
@@ -18,6 +18,7 @@
import com.android.ide.common.resources.RecordingLogger;
import com.android.testutils.TestUtils;
import com.android.utils.SdkUtils;
+import com.google.common.base.Predicates;
import com.google.common.io.Files;
import com.intellij.mock.MockApplication;
import com.intellij.openapi.Disposable;
@@ -30,6 +31,7 @@
import java.nio.charset.Charset;
import java.nio.file.Path;
import java.util.UUID;
+import java.util.function.Supplier;
import org.junit.After;
import org.junit.Before;
import org.junit.Ignore;
@@ -80,7 +82,7 @@
@Test
public void testExec() throws IOException {
assertNull(RenderSecurityManager.getCurrent());
- RenderSecurityManager manager = new RenderSecurityManager(null, null, false);
+ RenderSecurityManager manager = RenderSecurityManager.createForTests(null, null, false, () -> true);
RecordingLogger logger = new RecordingLogger();
manager.setLogger(logger);
try {
@@ -109,7 +111,7 @@
@Test
public void testSetSecurityManager() {
- RenderSecurityManager manager = new RenderSecurityManager(null, null, false);
+ RenderSecurityManager manager = RenderSecurityManager.createForTests(null, null, false, () -> true);
try {
manager.setActive(true, myCredential);
System.setSecurityManager(null);
@@ -126,10 +128,11 @@
@Test
public void testRead() {
- RenderSecurityManager manager = new RenderSecurityManager(
+ RenderSecurityManager manager = RenderSecurityManager.createForTests(
"/Users/userHome/Sdk",
"/Users/userHome/Projects/project1",
- true);
+ true,
+ () -> true);
// Not allowed paths
String[] notAllowedPaths = new String[] {
@@ -179,10 +182,11 @@
@Test
public void testWrite() {
- RenderSecurityManager manager = new RenderSecurityManager(
+ RenderSecurityManager manager = RenderSecurityManager.createForTests(
"/Users/userHome/Sdk",
"/Users/userHome/Projects/project1",
- false);
+ false,
+ () -> true);
String cachePath = PathManager.getSystemPath() + "/caches/";
String indexPath = PathManager.getIndexRoot() + "/";
@@ -230,7 +234,7 @@
@Test
public void testExecute() {
- RenderSecurityManager manager = new RenderSecurityManager(null, null, false);
+ RenderSecurityManager manager = RenderSecurityManager.createForTests(null, null, false, () -> true);
try {
manager.setActive(true, myCredential);
manager.checkPermission(new FilePermission("/foo", "execute"));
@@ -247,7 +251,7 @@
@Test
public void testDelete() {
- RenderSecurityManager manager = new RenderSecurityManager(null, null, false);
+ RenderSecurityManager manager = RenderSecurityManager.createForTests(null, null, false, () -> true);
try {
manager.setActive(true, myCredential);
manager.checkPermission(new FilePermission("/foo", "delete"));
@@ -264,7 +268,7 @@
@Test
public void testLoadLibrary() {
- RenderSecurityManager manager = new RenderSecurityManager(null, null, false);
+ RenderSecurityManager manager = RenderSecurityManager.createForTests(null, null, false, () -> true);
try {
manager.setActive(true, myCredential);
@@ -285,7 +289,7 @@
@Test
public void testAllowedLoadLibrary() {
- RenderSecurityManager manager = new RenderSecurityManager(null, null, false);
+ RenderSecurityManager manager = RenderSecurityManager.createForTests(null, null, false, () -> true);
try {
manager.setActive(true, myCredential);
@@ -302,7 +306,7 @@
@SuppressWarnings("CheckReturnValue")
@Test
public void testInvalidRead() {
- RenderSecurityManager manager = new RenderSecurityManager(null, null, false);
+ RenderSecurityManager manager = RenderSecurityManager.createForTests(null, null, false, () -> true);
try {
manager.setActive(true, myCredential);
@@ -322,7 +326,7 @@
@Test
public void testInvalidPropertyWrite() {
- RenderSecurityManager manager = new RenderSecurityManager(null, null, false);
+ RenderSecurityManager manager = RenderSecurityManager.createForTests(null, null, false, () -> true);
try {
manager.setActive(true, myCredential);
@@ -344,7 +348,7 @@
@SuppressWarnings("CheckReturnValue")
@Test
public void testReadOk() throws IOException {
- RenderSecurityManager manager = new RenderSecurityManager(null, null, false);
+ RenderSecurityManager manager = RenderSecurityManager.createForTests(null, null, false, () -> true);
try {
manager.setActive(true, myCredential);
@@ -367,7 +371,7 @@
@Test
public void testProperties() {
- RenderSecurityManager manager = new RenderSecurityManager(null, null, false);
+ RenderSecurityManager manager = RenderSecurityManager.createForTests(null, null, false, () -> true);
try {
manager.setActive(true, myCredential);
@@ -387,7 +391,7 @@
@Test
public void testExit() {
- RenderSecurityManager manager = new RenderSecurityManager(null, null, false);
+ RenderSecurityManager manager = RenderSecurityManager.createForTests(null, null, false, () -> true);
try {
manager.setActive(true, myCredential);
@@ -420,7 +424,7 @@
}
}
};
- RenderSecurityManager manager = new RenderSecurityManager(null, null, false);
+ RenderSecurityManager manager = RenderSecurityManager.createForTests(null, null, false, () -> true);
try {
manager.setActive(true, myCredential);
@@ -452,7 +456,7 @@
@Test
public void testActive() {
- RenderSecurityManager manager = new RenderSecurityManager(null, null, false);
+ RenderSecurityManager manager = RenderSecurityManager.createForTests(null, null, false, () -> true);
try {
manager.setActive(true, myCredential);
@@ -493,6 +497,8 @@
@Test
public void testThread2() throws InterruptedException {
+ ThreadGroup renderThreadGroup = new ThreadGroup("Render thread group");
+ Supplier<Boolean> isRenderThread = () -> renderThreadGroup.parentOf(Thread.currentThread().getThreadGroup());
final List<Thread> threads = Collections.synchronizedList(new ArrayList<>());
// Check that when a new thread is created simultaneously from an unrelated
// thread during rendering, that new thread does not pick up the security manager.
@@ -514,14 +520,14 @@
// uninstalled, and at barrier5 all threads will check that there are no more
// restrictions. At barrier6 all threads are done.
- final Thread thread1 = new Thread("render") {
+ final Thread thread1 = new Thread(renderThreadGroup, "render") {
@Override
public void run() {
try {
barrier1.await();
assertNull(RenderSecurityManager.getCurrent());
- RenderSecurityManager manager = new RenderSecurityManager(null, null, false);
+ RenderSecurityManager manager = RenderSecurityManager.createForTests(null, null, false, isRenderThread);
manager.setActive(true, myCredential);
barrier2.await();
@@ -649,7 +655,7 @@
public void testDisabled() {
assertNull(RenderSecurityManager.getCurrent());
- RenderSecurityManager manager = new RenderSecurityManager(null, null, false);
+ RenderSecurityManager manager = RenderSecurityManager.createForTests(null, null, false, () -> true);
RenderSecurityManager.sEnabled = false;
try {
assertNull(RenderSecurityManager.getCurrent());
@@ -708,7 +714,8 @@
});
thread.start();
- RenderSecurityManager manager = new RenderSecurityManager(null, null, false);
+ Thread thisThread = Thread.currentThread();
+ RenderSecurityManager manager = RenderSecurityManager.createForTests(null, null, false, () -> thisThread == Thread.currentThread());
RecordingLogger logger = new RecordingLogger();
manager.setLogger(logger);
try {
@@ -740,7 +747,7 @@
@Test
public void testEnterExitSafeRegion() {
- RenderSecurityManager manager = new RenderSecurityManager(null, null, false);
+ RenderSecurityManager manager = RenderSecurityManager.createForTests(null, null, false, () -> true);
Object credential = new Object();
try {
manager.setActive(true, credential);
@@ -815,7 +822,7 @@
@Test
public void testRunSafeRegion() throws Exception {
- RenderSecurityManager manager = new RenderSecurityManager(null, null, false);
+ RenderSecurityManager manager = RenderSecurityManager.createForTests(null, null, false, () -> true);
Object credential = new Object();
try {
manager.setActive(true, credential);
@@ -863,7 +870,7 @@
public void testImageIo() throws IOException, InterruptedException {
// Warm up ImageIO static state that calls write actions forbidden by RenderSecurityManager
ImageIO.getCacheDirectory();
- RenderSecurityManager manager = new RenderSecurityManager(null, null, false);
+ RenderSecurityManager manager = RenderSecurityManager.createForTests(null, null, false, () -> true);
try {
manager.setActive(true, myCredential);
@@ -907,7 +914,7 @@
@Test
public void testTempDir() throws IOException {
- RenderSecurityManager manager = new RenderSecurityManager(null, null, false);
+ RenderSecurityManager manager = RenderSecurityManager.createForTests(null, null, false, () -> true);
try {
manager.setActive(true, myCredential);
@@ -928,7 +935,7 @@
@Test
public void testAppTempDir() {
- RenderSecurityManager manager = new RenderSecurityManager(null, null, false);
+ RenderSecurityManager manager = RenderSecurityManager.createForTests(null, null, false, () -> true);
try {
manager.setAppTempDir("/random/path/");
manager.setActive(true, myCredential);
@@ -943,7 +950,7 @@
public void testSetTimeZone() {
// Warm up TimeZone.defaultTimeZone initialization that accesses properties forbidden by RenderSecurityManager
TimeZone.getDefault();
- RenderSecurityManager manager = new RenderSecurityManager(null, null, false);
+ RenderSecurityManager manager = RenderSecurityManager.createForTests(null, null, false, () -> true);
try {
manager.setActive(true, myCredential);
@@ -974,7 +981,7 @@
*/
@Test
public void testLogDir() {
- RenderSecurityManager manager = new RenderSecurityManager(null, null, false);
+ RenderSecurityManager manager = RenderSecurityManager.createForTests(null, null, false, () -> true);
try {
manager.setActive(true, myCredential);
@@ -990,7 +997,7 @@
}
@Test
public void testLogException() {
- RenderSecurityManager manager = new RenderSecurityManager(null, null, false);
+ RenderSecurityManager manager = RenderSecurityManager.createForTests(null, null, false, () -> true);
try {
manager.setActive(true, myCredential);
@@ -1017,7 +1024,7 @@
@Test
public void testNoLinkCreationAllowed() throws IOException {
- RenderSecurityManager manager = new RenderSecurityManager(null, null, false);
+ RenderSecurityManager manager = RenderSecurityManager.createForTests(null, null, false, () -> true);
Path testTemp = java.nio.file.Files.createTempDirectory("linkTest");
Path attackLink = testTemp.resolve("attack");
File victimFile = new File(PathManager.getConfigPath() + "/victim-" + UUID.randomUUID().toString());
@@ -1037,7 +1044,7 @@
@Test
public void testCheckWriteToNonExistingLink() throws IOException {
- RenderSecurityManager manager = new RenderSecurityManager(null, null, false);
+ RenderSecurityManager manager = RenderSecurityManager.createForTests(null, null, false, () -> true);
Path testTemp = java.nio.file.Files.createTempDirectory("linkTest");
Path attackLink = testTemp.resolve("attack");
@@ -1060,7 +1067,7 @@
@Test
public void testCheckWriteToExistingLink() throws IOException {
- RenderSecurityManager manager = new RenderSecurityManager(null, null, false);
+ RenderSecurityManager manager = RenderSecurityManager.createForTests(null, null, false, () -> true);
Path testTemp = java.nio.file.Files.createTempDirectory("linkTest");
Path attackLink = testTemp.resolve("attack");
@@ -1086,7 +1093,7 @@
*/
@Test
public void testPathTraversal() throws IOException {
- RenderSecurityManager manager = new RenderSecurityManager(null, null, false);
+ RenderSecurityManager manager = RenderSecurityManager.createForTests(null, null, false, () -> true);
try {
manager.setActive(true, myCredential);
manager.checkPermission(new FilePermission("/tmp/../dev/null", "read,write"));
@@ -1102,7 +1109,7 @@
@Test
public void testSystemPropertiesAccess() {
- RenderSecurityManager manager = new RenderSecurityManager(null, null, false);
+ RenderSecurityManager manager = RenderSecurityManager.createForTests(null, null, false, () -> true);
try {
manager.setActive(true, myCredential);
diff --git a/streaming/screen-sharing-agent/.idea/gradle.xml b/streaming/screen-sharing-agent/.idea/gradle.xml
index ae388c2..0897082 100644
--- a/streaming/screen-sharing-agent/.idea/gradle.xml
+++ b/streaming/screen-sharing-agent/.idea/gradle.xml
@@ -4,16 +4,15 @@
<component name="GradleSettings">
<option name="linkedExternalProjectsSettings">
<GradleProjectSettings>
- <option name="testRunner" value="GRADLE" />
- <option name="distributionType" value="DEFAULT_WRAPPED" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
- <option name="gradleJvm" value="jbr-17" />
+ <option name="gradleJvm" value="#GRADLE_LOCAL_JAVA_HOME" />
<option name="modules">
<set>
<option value="$PROJECT_DIR$" />
<option value="$PROJECT_DIR$/app" />
</set>
</option>
+ <option name="resolveExternalAnnotations" value="false" />
</GradleProjectSettings>
</option>
</component>
diff --git a/streaming/screen-sharing-agent/.idea/migrations.xml b/streaming/screen-sharing-agent/.idea/migrations.xml
new file mode 100644
index 0000000..f8051a6
--- /dev/null
+++ b/streaming/screen-sharing-agent/.idea/migrations.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+ <component name="ProjectMigrations">
+ <option name="MigrateToGradleLocalJavaHome">
+ <set>
+ <option value="$PROJECT_DIR$" />
+ </set>
+ </option>
+ </component>
+</project>
\ No newline at end of file
diff --git a/streaming/screen-sharing-agent/.idea/misc.xml b/streaming/screen-sharing-agent/.idea/misc.xml
index 0f86676..8978d23 100644
--- a/streaming/screen-sharing-agent/.idea/misc.xml
+++ b/streaming/screen-sharing-agent/.idea/misc.xml
@@ -1,4 +1,5 @@
<project version="4">
+ <component name="ExternalStorageConfigurationManager" enabled="true" />
<component name="ProjectRootManager" version="2" languageLevel="JDK_17" default="true" project-jdk-name="jbr-17" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/build/classes" />
</component>
diff --git a/streaming/screen-sharing-agent/app/build.gradle b/streaming/screen-sharing-agent/app/build.gradle
index 2ead813..5fa9314 100644
--- a/streaming/screen-sharing-agent/app/build.gradle
+++ b/streaming/screen-sharing-agent/app/build.gradle
@@ -39,4 +39,8 @@
lintOptions {
checkReleaseBuilds false
}
+ buildFeatures {
+ aidl true
+ }
+ namespace 'com.android.tools.screensharing'
}
diff --git a/streaming/screen-sharing-agent/app/src/main/cpp/accessors/surface_control.cc b/streaming/screen-sharing-agent/app/src/main/cpp/accessors/surface_control.cc
index 25ecec8..62d8c90 100644
--- a/streaming/screen-sharing-agent/app/src/main/cpp/accessors/surface_control.cc
+++ b/streaming/screen-sharing-agent/app/src/main/cpp/accessors/surface_control.cc
@@ -114,7 +114,7 @@
}
void SurfaceControl::ConfigureProjection(
- Jni jni, jobject display_token, ANativeWindow* surface, const DisplayInfo& display_info, Size projected_size) {
+ Jni jni, jobject display_token, ANativeWindow* surface, const DisplayInfo& display_info, ARect projection_rect) {
struct Transaction {
explicit Transaction(Jni jni)
: jni_(jni) {
@@ -129,7 +129,7 @@
InitializeStatics(jni);
Transaction transaction(jni);
SetDisplaySurface(jni, display_token, surface);
- SetDisplayProjection(jni, display_token, 0, display_info.logical_size.toRect(), projected_size.toRect());
+ SetDisplayProjection(jni, display_token, 0, display_info.logical_size.toRect(), projection_rect);
SetDisplayLayerStack(jni, display_token, display_info.layer_stack);
}
diff --git a/streaming/screen-sharing-agent/app/src/main/cpp/accessors/surface_control.h b/streaming/screen-sharing-agent/app/src/main/cpp/accessors/surface_control.h
index 6d17ce7..e862159 100644
--- a/streaming/screen-sharing-agent/app/src/main/cpp/accessors/surface_control.h
+++ b/streaming/screen-sharing-agent/app/src/main/cpp/accessors/surface_control.h
@@ -44,7 +44,8 @@
static void CloseTransaction(Jni jni);
static void SetDisplaySurface(Jni jni, jobject display_token, ANativeWindow* surface);
static void SetDisplayLayerStack(Jni jni, jobject display_token, int32_t layer_stack);
- static void SetDisplayProjection(Jni jni, jobject display_token, int32_t orientation, const ARect& layer_stack_rect, const ARect& display_rect);
+ static void SetDisplayProjection(
+ Jni jni, jobject display_token, int32_t orientation, const ARect& layer_stack_rect, const ARect& display_rect);
static JObject ToJava(Jni jni, const ARect& rect);
@@ -54,9 +55,9 @@
static JObject CreateDisplay(Jni jni, const char* name, bool secure);
static void DestroyDisplay(Jni jni, jobject display_token);
- // The display area defined by display_info.logical_size is mapped to projected size.
+ // The display area defined by display_info.logical_size is mapped to projection rectangle.
static void ConfigureProjection(
- Jni jni, jobject display_token, ANativeWindow* surface, const DisplayInfo& display_info, Size projected_size);
+ Jni jni, jobject display_token, ANativeWindow* surface, const DisplayInfo& display_info, ARect projection_rect);
private:
// SurfaceControl class.
diff --git a/streaming/screen-sharing-agent/app/src/main/cpp/display_streamer.cc b/streaming/screen-sharing-agent/app/src/main/cpp/display_streamer.cc
index 898ee9b..1cea876 100644
--- a/streaming/screen-sharing-agent/app/src/main/cpp/display_streamer.cc
+++ b/streaming/screen-sharing-agent/app/src/main/cpp/display_streamer.cc
@@ -53,7 +53,7 @@
namespace {
constexpr int MAX_SUBSEQUENT_ERRORS = 10;
-constexpr double MIN_VIDEO_RESOLUTION = 128;
+constexpr int MIN_VIDEO_RESOLUTION = 128;
constexpr int COLOR_FormatSurface = 0x7F000789; // See android.media.MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface.
constexpr int BIT_RATE = 8000000;
constexpr int BIT_RATE_REDUCED = 1000000;
@@ -185,12 +185,19 @@
}
Size ComputeVideoSize(Size rotated_display_size, Size max_resolution, Size size_alignment) {
- auto width = rotated_display_size.width;
- auto height = rotated_display_size.height;
- double scale = max(min(1.0, min(static_cast<double>(max_resolution.width) / width, static_cast<double>(max_resolution.height) / height)),
- max(MIN_VIDEO_RESOLUTION / width, MIN_VIDEO_RESOLUTION / height));
- return Size { RoundUpToMultipleOf(lround(width * scale), max(size_alignment.width, 8)),
- RoundUpToMultipleOf(lround(height * scale), max(size_alignment.height, 8)) };
+ double display_width = rotated_display_size.width;
+ double display_height = rotated_display_size.height;
+ if (size_alignment.width < 8) {
+ size_alignment.width = 8; // Increase horizontal size alignment to accommodate FFmpeg video decoder.
+ }
+ double scale = max(min(1.0, min(max_resolution.width / display_width, max_resolution.height / display_height)),
+ max(MIN_VIDEO_RESOLUTION / display_width, MIN_VIDEO_RESOLUTION / display_height));
+ // We are computing width of the frame first and the height based on the width to make sure that,
+ // if the video frame has a sightly different aspect ration than the display, it is taller rather
+ // than wider.
+ int32_t width = RoundUpToMultipleOf(lround(display_width * scale), size_alignment.width);
+ int32_t height = RoundUpToMultipleOf(lround(width * display_height / display_width), size_alignment.height);
+ return Size { width, height };
}
Size ConfigureCodec(AMediaCodec* codec, const CodecInfo& codec_info, Size max_video_resolution, AMediaFormat* media_format,
@@ -329,7 +336,9 @@
virtual_display.Resize(video_size.width, video_size.height, display_info_.logical_density_dpi);
virtual_display.SetSurface(surface);
} else {
- SurfaceControl::ConfigureProjection(jni, display_token, surface, display_info, video_size);
+ int32_t height = lround(static_cast<double>(video_size.width) * display_info.logical_size.height / display_info.logical_size.width);
+ int32_t y = (video_size.height - height) / 2;
+ SurfaceControl::ConfigureProjection(jni, display_token, surface, display_info, { 0, y, video_size.width, height });
}
AMediaCodec_start(codec);
running_codec_ = codec;
diff --git a/streaming/screen-sharing-agent/app/src/main/java/com/android/tools/screensharing/ClipboardAdapter.java b/streaming/screen-sharing-agent/app/src/main/java/com/android/tools/screensharing/ClipboardAdapter.java
index 86f022d..b294a82 100644
--- a/streaming/screen-sharing-agent/app/src/main/java/com/android/tools/screensharing/ClipboardAdapter.java
+++ b/streaming/screen-sharing-agent/app/src/main/java/com/android/tools/screensharing/ClipboardAdapter.java
@@ -30,7 +30,7 @@
@SuppressWarnings("unused") // Called through JNI.
public class ClipboardAdapter {
private static final String PACKAGE_NAME = "com.android.shell";
- private static final String ATTRIBUTION_TAG = "ScreenSharing";
+ private static final String ATTRIBUTION_TAG = "studio.screen.sharing";
private static final int USER_ID = 0;
private static final int DEVICE_ID_DEFAULT = 0; // From android.companion.virtual.VirtualDeviceManager
@@ -40,7 +40,6 @@
private static Method addPrimaryClipChangedListenerMethod;
private static Method removePrimaryClipChangedListenerMethod;
private static ClipboardListener clipboardListener;
- private static int numberOfExtraParameters;
private static PersistableBundle overlaySuppressor;
static {
@@ -54,8 +53,10 @@
setPrimaryClipMethod = findMethodAndMakeAccessible(methods, "setPrimaryClip");
addPrimaryClipChangedListenerMethod = findMethodAndMakeAccessible(methods, "addPrimaryClipChangedListener");
removePrimaryClipChangedListenerMethod = findMethodAndMakeAccessible(methods, "removePrimaryClipChangedListener");
- numberOfExtraParameters = getPrimaryClipMethod.getParameterCount() - 1;
- if (numberOfExtraParameters <= 3) {
+ if (checkNumberOfParameters(getPrimaryClipMethod, 1, 4) &&
+ checkNumberOfParameters(setPrimaryClipMethod, 2, 5) &&
+ checkNumberOfParameters(addPrimaryClipChangedListenerMethod, 2, 5) &&
+ checkNumberOfParameters(removePrimaryClipChangedListenerMethod, 2, 5)) {
clipboardListener = new ClipboardListener();
if (SDK_INT >= 33) {
overlaySuppressor = new PersistableBundle(1);
@@ -63,12 +64,12 @@
}
}
else {
- Log.e("ScreenSharing", "Unexpected number of getPrimaryClip parameters: " + (numberOfExtraParameters + 1));
+ clipboard = null;
}
}
}
catch (NoSuchMethodException e) {
- Log.e("ScreenSharing", e.getMessage());
+ Log.e(ATTRIBUTION_TAG, e.getMessage());
clipboard = null;
}
}
@@ -78,13 +79,14 @@
return "";
}
- ClipData clipData = numberOfExtraParameters == 0 ?
+ int numberOfParameters = getPrimaryClipMethod.getParameterCount();
+ ClipData clipData = numberOfParameters == 1 ?
(ClipData)getPrimaryClipMethod.invoke(clipboard, PACKAGE_NAME) :
- numberOfExtraParameters == 1 ?
+ numberOfParameters == 2 ?
(ClipData)getPrimaryClipMethod.invoke(clipboard, PACKAGE_NAME, USER_ID) :
- numberOfExtraParameters == 2 ?
+ numberOfParameters == 3 ?
(ClipData)getPrimaryClipMethod.invoke(clipboard, PACKAGE_NAME, ATTRIBUTION_TAG, USER_ID) :
- numberOfExtraParameters == 3 ?
+ numberOfParameters == 4 ?
(ClipData)getPrimaryClipMethod.invoke(clipboard, PACKAGE_NAME, ATTRIBUTION_TAG, USER_ID, DEVICE_ID_DEFAULT) :
null;
if (clipData == null || clipData.getItemCount() == 0) {
@@ -104,16 +106,17 @@
clipData.getDescription().setExtras(overlaySuppressor);
}
- if (numberOfExtraParameters == 0) {
+ int numberOfParameters = setPrimaryClipMethod.getParameterCount();
+ if (numberOfParameters == 2) {
setPrimaryClipMethod.invoke(clipboard, clipData, PACKAGE_NAME);
}
- else if (numberOfExtraParameters == 1) {
+ else if (numberOfParameters == 3) {
setPrimaryClipMethod.invoke(clipboard, clipData, PACKAGE_NAME, USER_ID);
}
- else if (numberOfExtraParameters == 2) {
+ else if (numberOfParameters == 4) {
setPrimaryClipMethod.invoke(clipboard, clipData, PACKAGE_NAME, ATTRIBUTION_TAG, USER_ID);
}
- else if (numberOfExtraParameters == 3) {
+ else if (numberOfParameters == 5) {
setPrimaryClipMethod.invoke(clipboard, clipData, PACKAGE_NAME, ATTRIBUTION_TAG, USER_ID, DEVICE_ID_DEFAULT);
}
}
@@ -123,16 +126,17 @@
return;
}
- if (numberOfExtraParameters == 0) {
+ int numberOfParameters = addPrimaryClipChangedListenerMethod.getParameterCount();
+ if (numberOfParameters == 2) {
addPrimaryClipChangedListenerMethod.invoke(clipboard, clipboardListener, PACKAGE_NAME);
}
- else if (numberOfExtraParameters == 1) {
+ else if (numberOfParameters == 3) {
addPrimaryClipChangedListenerMethod.invoke(clipboard, clipboardListener, PACKAGE_NAME, USER_ID);
}
- else if (numberOfExtraParameters == 2) {
+ else if (numberOfParameters == 4) {
addPrimaryClipChangedListenerMethod.invoke(clipboard, clipboardListener, PACKAGE_NAME, ATTRIBUTION_TAG, USER_ID);
}
- else if (numberOfExtraParameters == 3) {
+ else if (numberOfParameters == 5) {
addPrimaryClipChangedListenerMethod.invoke(clipboard, clipboardListener, PACKAGE_NAME, ATTRIBUTION_TAG, USER_ID, DEVICE_ID_DEFAULT);
}
}
@@ -142,16 +146,17 @@
return;
}
- if (numberOfExtraParameters == 0) {
+ int numberOfParameters = removePrimaryClipChangedListenerMethod.getParameterCount();
+ if (numberOfParameters == 2) {
removePrimaryClipChangedListenerMethod.invoke(clipboard, clipboardListener, PACKAGE_NAME);
}
- else if (numberOfExtraParameters == 1) {
+ else if (numberOfParameters == 3) {
removePrimaryClipChangedListenerMethod.invoke(clipboard, clipboardListener, PACKAGE_NAME, USER_ID);
}
- else if (numberOfExtraParameters == 2) {
+ else if (numberOfParameters == 4) {
removePrimaryClipChangedListenerMethod.invoke(clipboard, clipboardListener, PACKAGE_NAME, ATTRIBUTION_TAG, USER_ID);
}
- else if (numberOfExtraParameters == 3) {
+ else if (numberOfParameters == 5) {
removePrimaryClipChangedListenerMethod.invoke(clipboard, clipboardListener, PACKAGE_NAME, ATTRIBUTION_TAG, USER_ID,
DEVICE_ID_DEFAULT);
}
@@ -166,4 +171,13 @@
}
throw new NoSuchMethodException(name);
}
+
+ private static boolean checkNumberOfParameters(Method method, int minParam, int maxParam) {
+ int parameterCount = method.getParameterCount();
+ if (minParam <= parameterCount && parameterCount <= maxParam) {
+ return true;
+ }
+ Log.e(ATTRIBUTION_TAG, "Unexpected number of " + method.getName() + " parameters: " + parameterCount);
+ return false;
+ }
}
diff --git a/streaming/screen-sharing-agent/build.gradle b/streaming/screen-sharing-agent/build.gradle
index 04bc67d..1656642 100644
--- a/streaming/screen-sharing-agent/build.gradle
+++ b/streaming/screen-sharing-agent/build.gradle
@@ -5,7 +5,7 @@
mavenCentral()
}
dependencies {
- classpath 'com.android.tools.build:gradle:7.4.1'
+ classpath 'com.android.tools.build:gradle:8.0.2'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
diff --git a/streaming/screen-sharing-agent/gradle.properties b/streaming/screen-sharing-agent/gradle.properties
index 8890a43..56f70c0 100644
--- a/streaming/screen-sharing-agent/gradle.properties
+++ b/streaming/screen-sharing-agent/gradle.properties
@@ -16,3 +16,6 @@
# https://developer.android.com/topic/libraries/support-library/androidx-rn
android.useAndroidX=true
org.gradle.unsafe.configuration-cache=true
+android.defaults.buildfeatures.buildconfig=true
+android.nonTransitiveRClass=true
+android.nonFinalResIds=true
diff --git a/streaming/screen-sharing-agent/gradle/wrapper/gradle-wrapper.properties b/streaming/screen-sharing-agent/gradle/wrapper/gradle-wrapper.properties
index 9367b98..4f122b4 100644
--- a/streaming/screen-sharing-agent/gradle/wrapper/gradle-wrapper.properties
+++ b/streaming/screen-sharing-agent/gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,6 @@
#Tue May 10 11:42:43 PDT 2022
distributionBase=GRADLE_USER_HOME
-distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-bin.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.1-bin.zip
distributionPath=wrapper/dists
zipStorePath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
diff --git a/streaming/src/com/android/tools/idea/streaming/core/AbstractDisplayView.kt b/streaming/src/com/android/tools/idea/streaming/core/AbstractDisplayView.kt
index bbd7403..199f9f4 100644
--- a/streaming/src/com/android/tools/idea/streaming/core/AbstractDisplayView.kt
+++ b/streaming/src/com/android/tools/idea/streaming/core/AbstractDisplayView.kt
@@ -57,6 +57,7 @@
* Common base class for [com.android.tools.idea.streaming.emulator.EmulatorView] and
* [com.android.tools.idea.streaming.device.DeviceView].
*/
+@Suppress("UseJBColor")
abstract class AbstractDisplayView(val displayId: Int) : ZoomablePanel(), Disposable {
/** Serial number of the device sown in the view. */
@@ -289,7 +290,7 @@
}
/** Attempts to restore a lost device connection. */
- protected inner class Reconnector(val reconnectLabel: String, val progressMessage: String, val reconnect: suspend () -> Unit) {
+ protected inner class Reconnector(val reconnectLabel: String, private val progressMessage: String, val reconnect: suspend () -> Unit) {
/** Starts the reconnection attempt. */
internal fun start() {
diff --git a/streaming/src/com/android/tools/idea/streaming/core/StreamingToolWindowManager.kt b/streaming/src/com/android/tools/idea/streaming/core/StreamingToolWindowManager.kt
index 2d9edc5..93bd7cb 100644
--- a/streaming/src/com/android/tools/idea/streaming/core/StreamingToolWindowManager.kt
+++ b/streaming/src/com/android/tools/idea/streaming/core/StreamingToolWindowManager.kt
@@ -1028,7 +1028,7 @@
private inner class StartDeviceMirroringAction(
private val device: DeviceDescription,
- ) : DumbAwareAction(device.deviceName, null, device.config.deviceProperties.deviceType?.icon) {
+ ) : DumbAwareAction(device.deviceName, null, device.config.deviceProperties.icon) {
override fun actionPerformed(event: AnActionEvent) {
activateMirroring(device)
@@ -1039,7 +1039,7 @@
private inner class StartRemoteDeviceAction(
private val device: DeviceHandle,
- ) : DumbAwareAction(device.sourceTemplate?.properties?.composeDeviceName(), null, device.sourceTemplate?.properties?.deviceType?.icon) {
+ ) : DumbAwareAction(device.sourceTemplate?.properties?.composeDeviceName(), null, device.sourceTemplate?.properties?.icon) {
override fun actionPerformed(event: AnActionEvent) {
device.scope.launch { device.activationAction?.activate() }
diff --git a/streaming/src/com/android/tools/idea/streaming/core/StreamingUtils.kt b/streaming/src/com/android/tools/idea/streaming/core/StreamingUtils.kt
index ae717c1..156d90f 100644
--- a/streaming/src/com/android/tools/idea/streaming/core/StreamingUtils.kt
+++ b/streaming/src/com/android/tools/idea/streaming/core/StreamingUtils.kt
@@ -118,7 +118,7 @@
private fun Component.isComponentForAction(action: AnAction): Boolean =
this is AnActionHolder && this.action === action
-
+// TODO(b/289230363): use DeviceHandle.state.properties.icon, since it is the source of truth for device icons.
internal val AvdInfo.icon: Icon
get() {
return when (tag) {
@@ -129,16 +129,6 @@
}
}
-internal val DeviceType.icon: Icon
- get() {
- return when (this) {
- DeviceType.TV -> StudioIcons.DeviceExplorer.PHYSICAL_DEVICE_TV
- DeviceType.AUTOMOTIVE -> StudioIcons.DeviceExplorer.PHYSICAL_DEVICE_CAR
- DeviceType.WEAR -> StudioIcons.DeviceExplorer.PHYSICAL_DEVICE_WEAR
- DeviceType.HANDHELD -> StudioIcons.DeviceExplorer.PHYSICAL_DEVICE_PHONE
- }
- }
-
/**
* If the device is connected by WiFi, extracts the device's own serial number from the serial
* number returned by ADB, otherwise returns the original serial number. For example, converts
diff --git a/streaming/src/com/android/tools/idea/streaming/device/DeviceToolWindowPanel.kt b/streaming/src/com/android/tools/idea/streaming/device/DeviceToolWindowPanel.kt
index c370e2b..7e9ac29 100644
--- a/streaming/src/com/android/tools/idea/streaming/device/DeviceToolWindowPanel.kt
+++ b/streaming/src/com/android/tools/idea/streaming/device/DeviceToolWindowPanel.kt
@@ -24,7 +24,6 @@
import com.android.tools.idea.streaming.core.DeviceId
import com.android.tools.idea.streaming.core.RunningDevicePanel
import com.android.tools.idea.streaming.core.STREAMING_SECONDARY_TOOLBAR_ID
-import com.android.tools.idea.streaming.core.icon
import com.android.tools.idea.streaming.core.installFileDropHandler
import com.android.tools.idea.streaming.device.DeviceView.ConnectionState
import com.android.tools.idea.streaming.device.DeviceView.ConnectionStateListener
@@ -37,7 +36,6 @@
import com.intellij.openapi.Disposable
import com.intellij.openapi.project.Project
import com.intellij.openapi.util.Disposer
-import icons.StudioIcons
import java.awt.EventQueue
import javax.swing.Icon
import javax.swing.JComponent
@@ -57,10 +55,7 @@
get() = deviceClient.deviceName
override val icon: Icon
- get() {
- val icon = deviceConfig.deviceProperties.deviceType?.icon ?: StudioIcons.DeviceExplorer.PHYSICAL_DEVICE_PHONE
- return ExecutionUtil.getLiveIndicator(icon)
- }
+ get() = ExecutionUtil.getLiveIndicator(deviceClient.deviceHandle.state.properties.icon)
override val isClosable: Boolean = StudioFlags.DEVICE_MIRRORING_ADVANCED_TAB_CONTROL.get()
diff --git a/streaming/src/com/android/tools/idea/streaming/device/DeviceView.kt b/streaming/src/com/android/tools/idea/streaming/device/DeviceView.kt
index 77d22ce..3c01587 100644
--- a/streaming/src/com/android/tools/idea/streaming/device/DeviceView.kt
+++ b/streaming/src/com/android/tools/idea/streaming/device/DeviceView.kt
@@ -264,6 +264,7 @@
}
private fun connected() {
+ hideLongRunningOperationIndicatorInstantly()
if (connectionState == ConnectionState.CONNECTING) {
hideDisconnectedStateMessage()
connectionState = ConnectionState.CONNECTED
@@ -276,6 +277,7 @@
if (disposed) {
return@invokeLaterIfNeeded
}
+ hideLongRunningOperationIndicatorInstantly()
stopClipboardSynchronization()
val message: String
val reconnector: Reconnector
diff --git a/streaming/src/com/android/tools/idea/streaming/device/VideoDecoder.kt b/streaming/src/com/android/tools/idea/streaming/device/VideoDecoder.kt
index dc52643..e15612c 100644
--- a/streaming/src/com/android/tools/idea/streaming/device/VideoDecoder.kt
+++ b/streaming/src/com/android/tools/idea/streaming/device/VideoDecoder.kt
@@ -19,7 +19,8 @@
import com.android.annotations.concurrency.GuardedBy
import com.android.tools.adtui.ImageUtils
import com.android.tools.adtui.ImageUtils.ellipticalClip
-import com.android.tools.idea.streaming.core.coerceAtMost
+import com.android.tools.idea.streaming.core.rotatedByQuadrants
+import com.android.tools.idea.streaming.core.scaled
import com.intellij.openapi.diagnostic.debug
import com.intellij.openapi.diagnostic.thisLogger
import com.intellij.util.containers.ContainerUtil
@@ -173,16 +174,6 @@
private val pendingPacket: AVPacket = av_packet_alloc()
private var hasPendingPacket = false
- private val renderingSize: Dimension
- get() {
- val videoSize = Dimension(decodingFrame.width(), decodingFrame.height())
- val maximumSize = maxOutputSize
- if (maximumSize.width == 0 || maximumSize.height == 0) {
- return videoSize
- }
- return videoSize.coerceAtMost(maximumSize)
- }
-
init {
thisLogger().debug { "Receiving $codecName video stream" }
val ffmpegCodecName = when (codecName) {
@@ -320,11 +311,12 @@
throw VideoDecoderException("Could not receive video frame")
}
- val size = renderingSize
+ val frameWidth = decodingFrame.width()
+ val frameHeight = decodingFrame.height()
var renderingFrame = renderingFrame
- if (renderingFrame == null || renderingFrame.width() != size.width || renderingFrame.height() != size.height) {
+ if (renderingFrame == null || renderingFrame.width() != frameWidth || renderingFrame.height() != frameHeight) {
renderingFrame?.let { av_frame_free(it) }
- renderingFrame = createRenderingFrame(size).also { this.renderingFrame = it }
+ renderingFrame = createRenderingFrame(frameWidth, frameHeight).also { this.renderingFrame = it }
if (av_frame_get_buffer(renderingFrame, 4) < 0) {
throw RuntimeException("av_frame_get_buffer failed")
}
@@ -333,25 +325,30 @@
throw RuntimeException("av_frame_make_writable failed")
}
- sws_scale(getSwsContext(renderingFrame), decodingFrame.data(), decodingFrame.linesize(), 0, decodingFrame.height(),
+ sws_scale(getSwsContext(renderingFrame), decodingFrame.data(), decodingFrame.linesize(), 0, frameHeight,
renderingFrame.data(), renderingFrame.linesize())
- val numBytes = av_image_get_buffer_size(renderingFrame.format(), renderingFrame.width(), renderingFrame.height(), 1)
+ val numBytes = av_image_get_buffer_size(renderingFrame.format(), frameWidth, frameHeight, 1)
val framePixels = renderingFrame.data().get().asByteBufferOfSize(numBytes).asIntBuffer()
- val imageSize = Dimension(renderingFrame.width(), renderingFrame.height())
+ // Due to video size alignment requirements, the video frame may contain black strips at the top and at the bottom.
+ // These black strips have to be excluded from the rendered image.
+ val rotatedDisplaySize = header.displaySize.rotatedByQuadrants(header.displayOrientation - header.displayOrientationCorrection)
+ val imageHeight = frameWidth.scaled(rotatedDisplaySize.height.toDouble() / rotatedDisplaySize.width).coerceAtMost(frameHeight)
+ val startY = (frameHeight - imageHeight) / 2
+ framePixels.position(startY * frameWidth) // Skip the potential black strip at the top of the frame.
synchronized(imageLock) {
var image = displayFrame?.image
- if (image?.width == imageSize.width && image.height == imageSize.height &&
+ if (image?.width == frameWidth && image.height == imageHeight &&
displayFrame?.orientationCorrection == 0 && header.displayOrientationCorrection == 0 && !header.displayRound) {
val imagePixels = (image.raster.dataBuffer as DataBufferInt).data
- framePixels.get(imagePixels)
+ framePixels.get(imagePixels, 0, imageHeight * frameWidth)
}
else {
- val imagePixels = IntArray(imageSize.width * imageSize.height)
- framePixels.get(imagePixels)
+ val imagePixels = IntArray(frameWidth * imageHeight)
+ framePixels.get(imagePixels, 0, imageHeight * frameWidth)
val buffer = DataBufferInt(imagePixels, imagePixels.size)
- val sampleModel = SinglePixelPackedSampleModel(DataBuffer.TYPE_INT, imageSize.width, imageSize.height, SAMPLE_MODEL_BIT_MASKS)
+ val sampleModel = SinglePixelPackedSampleModel(DataBuffer.TYPE_INT, frameWidth, imageHeight, SAMPLE_MODEL_BIT_MASKS)
val raster = Raster.createWritableRaster(sampleModel, buffer, ZERO_POINT)
image = ImageUtils.rotateByQuadrants(BufferedImage(COLOR_MODEL, raster, false, null), header.displayOrientationCorrection)
if (header.displayRound) {
@@ -370,15 +367,15 @@
val context = sws_getCachedContext(swsContext, decodingFrame.width(), decodingFrame.height(), decodingFrame.format(),
renderingFrame.width(), renderingFrame.height(), renderingFrame.format(),
SWS_BILINEAR, null, null, null as DoublePointer?) ?:
- throw VideoDecoderException("Could not allocate SwsContext")
+ throw VideoDecoderException("Could not allocate SwsContext")
swsContext = context
return context
}
- private fun createRenderingFrame(size: Dimension): AVFrame {
+ private fun createRenderingFrame(width: Int, height: Int): AVFrame {
return av_frame_alloc().apply {
- width(size.width)
- height(size.height)
+ width(width)
+ height(height)
format(AV_PIX_FMT_BGRA)
}
}
diff --git a/streaming/testData/DeviceToolWindowPanelTest/golden/AppearanceAndToolbarActions1.png b/streaming/testData/DeviceToolWindowPanelTest/golden/AppearanceAndToolbarActions1.png
index 0254bf7..9e01200 100644
--- a/streaming/testData/DeviceToolWindowPanelTest/golden/AppearanceAndToolbarActions1.png
+++ b/streaming/testData/DeviceToolWindowPanelTest/golden/AppearanceAndToolbarActions1.png
Binary files differ
diff --git a/streaming/testData/DeviceViewTest/golden/MultiTouch1.png b/streaming/testData/DeviceViewTest/golden/MultiTouch1.png
index 2df0903..61ec020 100644
--- a/streaming/testData/DeviceViewTest/golden/MultiTouch1.png
+++ b/streaming/testData/DeviceViewTest/golden/MultiTouch1.png
Binary files differ
diff --git a/streaming/testData/DeviceViewTest/golden/MultiTouch2.png b/streaming/testData/DeviceViewTest/golden/MultiTouch2.png
index 9509cfc..bbe7e06 100644
--- a/streaming/testData/DeviceViewTest/golden/MultiTouch2.png
+++ b/streaming/testData/DeviceViewTest/golden/MultiTouch2.png
Binary files differ
diff --git a/streaming/testData/DeviceViewTest/golden/MultiTouch3.png b/streaming/testData/DeviceViewTest/golden/MultiTouch3.png
index 1b1fc68..7250e44 100644
--- a/streaming/testData/DeviceViewTest/golden/MultiTouch3.png
+++ b/streaming/testData/DeviceViewTest/golden/MultiTouch3.png
Binary files differ
diff --git a/streaming/testData/DeviceViewTest/golden/MultiTouch4.png b/streaming/testData/DeviceViewTest/golden/MultiTouch4.png
index 10e5da1..5c808fc 100644
--- a/streaming/testData/DeviceViewTest/golden/MultiTouch4.png
+++ b/streaming/testData/DeviceViewTest/golden/MultiTouch4.png
Binary files differ
diff --git a/streaming/testData/DeviceViewTest/golden/Rotation0.png b/streaming/testData/DeviceViewTest/golden/Rotation0.png
index 5afe285..7c1f743 100644
--- a/streaming/testData/DeviceViewTest/golden/Rotation0.png
+++ b/streaming/testData/DeviceViewTest/golden/Rotation0.png
Binary files differ
diff --git a/streaming/testData/DeviceViewTest/golden/Rotation180.png b/streaming/testData/DeviceViewTest/golden/Rotation180.png
index 1160d2f..6398d25 100644
--- a/streaming/testData/DeviceViewTest/golden/Rotation180.png
+++ b/streaming/testData/DeviceViewTest/golden/Rotation180.png
Binary files differ
diff --git a/streaming/testData/DeviceViewTest/golden/Rotation270.png b/streaming/testData/DeviceViewTest/golden/Rotation270.png
index 7862be9..1e613de 100644
--- a/streaming/testData/DeviceViewTest/golden/Rotation270.png
+++ b/streaming/testData/DeviceViewTest/golden/Rotation270.png
Binary files differ
diff --git a/streaming/testData/DeviceViewTest/golden/Rotation90.png b/streaming/testData/DeviceViewTest/golden/Rotation90.png
index 082e208..c067939 100644
--- a/streaming/testData/DeviceViewTest/golden/Rotation90.png
+++ b/streaming/testData/DeviceViewTest/golden/Rotation90.png
Binary files differ
diff --git a/streaming/testData/DeviceViewTest/golden/Zoom1.png b/streaming/testData/DeviceViewTest/golden/Zoom1.png
index ed7390f..5350f18 100644
--- a/streaming/testData/DeviceViewTest/golden/Zoom1.png
+++ b/streaming/testData/DeviceViewTest/golden/Zoom1.png
Binary files differ
diff --git a/streaming/testSrc/com/android/tools/idea/streaming/core/StreamingToolWindowManagerTest.kt b/streaming/testSrc/com/android/tools/idea/streaming/core/StreamingToolWindowManagerTest.kt
index 0fc58e6..d28c13d 100644
--- a/streaming/testSrc/com/android/tools/idea/streaming/core/StreamingToolWindowManagerTest.kt
+++ b/streaming/testSrc/com/android/tools/idea/streaming/core/StreamingToolWindowManagerTest.kt
@@ -19,6 +19,13 @@
import com.android.emulator.control.KeyboardEvent
import com.android.emulator.control.PaneEntry
import com.android.emulator.control.PaneEntry.PaneIndex
+import com.android.sdklib.deviceprovisioner.DeviceProperties
+import com.android.sdklib.deviceprovisioner.DeviceState
+import com.android.sdklib.deviceprovisioner.DeviceTemplate
+import com.android.sdklib.deviceprovisioner.Reservation
+import com.android.sdklib.deviceprovisioner.ReservationState
+import com.android.sdklib.deviceprovisioner.TemplateActivationAction
+import com.android.sdklib.deviceprovisioner.testing.DeviceProvisionerRule
import com.android.sdklib.internal.avd.AvdInfo
import com.android.testutils.MockitoKt.mock
import com.android.testutils.MockitoKt.whenever
@@ -71,10 +78,8 @@
import com.intellij.openapi.actionSystem.PlatformDataKeys
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.components.service
-import com.intellij.openapi.project.Project
import com.intellij.openapi.util.Disposer
import com.intellij.openapi.wm.ToolWindow
-import com.intellij.openapi.wm.ToolWindowFactory
import com.intellij.openapi.wm.ToolWindowManager
import com.intellij.openapi.wm.ToolWindowType
import com.intellij.openapi.wm.ex.ToolWindowManagerListener
@@ -113,9 +118,10 @@
private val emulatorRule = FakeEmulatorRule()
private val androidExecutorsRule = AndroidExecutorsRule(workerThreadExecutor = Executors.newCachedThreadPool())
private val popupRule = JBPopupRule()
+ private val provisionerRule = DeviceProvisionerRule()
@get:Rule
- val ruleChain = RuleChain(agentRule, emulatorRule, ClipboardSynchronizationDisablementRule(), androidExecutorsRule, EdtRule(),
- PortableUiFontRule(), HeadlessDialogRule(), popupRule)
+ val ruleChain = RuleChain(agentRule, provisionerRule, emulatorRule, ClipboardSynchronizationDisablementRule(), androidExecutorsRule,
+ EdtRule(), PortableUiFontRule(), HeadlessDialogRule(), popupRule)
private val windowFactory: StreamingToolWindowFactory by lazy { StreamingToolWindowFactory() }
private var nullableToolWindow: TestToolWindow? = null
@@ -375,6 +381,38 @@
}
@Test
+ fun testRemoteDevice() {
+ val properties = DeviceProperties.build {
+ icon = StudioIcons.DeviceExplorer.FIREBASE_DEVICE_CAR
+ model = "Pixel 9000"
+ }
+ val device = provisionerRule.deviceProvisionerPlugin.newDevice(properties = properties)
+ device.sourceTemplate = object: DeviceTemplate {
+ override val properties = properties
+ override val activationAction: TemplateActivationAction = mock()
+ override val editAction = null
+ }
+ device.stateFlow.value = DeviceState.Disconnected(
+ properties, false, "offline", Reservation(ReservationState.ACTIVE, "active", null, null))
+ provisionerRule.deviceProvisionerPlugin.addDevice(device)
+
+ val provisionerService: DeviceProvisionerService = mock()
+ whenever(provisionerService.deviceProvisioner).thenReturn(provisionerRule.deviceProvisioner)
+ project.replaceService(DeviceProvisionerService::class.java, provisionerService, agentRule.disposable)
+
+ toolWindow.show()
+ waitForCondition(2, TimeUnit.SECONDS) { toolWindow.tabActions.isNotEmpty() }
+ val newTabAction = toolWindow.tabActions[0]
+ newTabAction.actionPerformed(createTestEvent(toolWindow.component, project))
+ val popup: FakeListPopup<Any> = popupRule.fakePopupFactory.getNextPopup(2, TimeUnit.SECONDS)
+
+ assertThat(popup.actions.toString()).isEqualTo(
+ "[Separator (Remote Devices), Pixel 9000 (null), Separator (null), " +
+ "Pair Devices Using Wi-Fi (Open the Device Pairing dialog which allows connecting devices over Wi-Fi)]")
+ assertThat(popup.actions[1].templatePresentation.icon).isEqualTo(StudioIcons.DeviceExplorer.FIREBASE_DEVICE_CAR)
+ }
+
+ @Test
fun testPhysicalDeviceActivateOnConnection() {
if (!isFFmpegAvailableToTest()) {
return
diff --git a/streaming/testUtil/com/android/tools/idea/streaming/device/FakeScreenSharingAgent.kt b/streaming/testUtil/com/android/tools/idea/streaming/device/FakeScreenSharingAgent.kt
index dde0e38..ab6f069 100644
--- a/streaming/testUtil/com/android/tools/idea/streaming/device/FakeScreenSharingAgent.kt
+++ b/streaming/testUtil/com/android/tools/idea/streaming/device/FakeScreenSharingAgent.kt
@@ -482,7 +482,8 @@
return
}
- val size = getScaledAndRotatedDisplaySize()
+ val size = computeDisplayImageSize()
+ val videoSize = Dimension(size.width, size.height.roundUpToMultipleOf8())
val encoderContext = avcodec_alloc_context3(encoder)?.apply {
bit_rate(8000000L)
time_base(av_make_q(1, 1000))
@@ -490,8 +491,8 @@
gop_size(2)
max_b_frames(1)
pix_fmt(encoder.pix_fmts().get())
- width(size.width)
- height(size.height)
+ width(videoSize.width)
+ height(videoSize.height)
} ?: throw RuntimeException("Could not allocate encoder context")
if (avcodec_open2(encoderContext, encoder, null as AVDictionary?) < 0) {
@@ -499,8 +500,8 @@
}
val encodingFrame = av_frame_alloc().apply {
format(encoderContext.pix_fmt())
- width(size.width)
- height(size.height)
+ width(videoSize.width)
+ height(videoSize.height)
}
if (av_frame_get_buffer(encodingFrame, 0) < 0) {
throw RuntimeException("av_frame_get_buffer failed")
@@ -510,12 +511,12 @@
}
val image = drawDisplayImage(size.rotatedByQuadrants(-displayOrientation), imageFlavor, displayId)
- .rotatedByQuadrants(displayOrientation)
+ .rotatedByQuadrants(displayOrientation)
val rgbFrame = av_frame_alloc().apply {
format(AV_PIX_FMT_BGR24)
- width(size.width)
- height(size.height)
+ width(videoSize.width)
+ height(videoSize.height)
}
if (av_frame_get_buffer(rgbFrame, 1) < 0) {
throw RuntimeException("Could not allocate the video frame data")
@@ -524,7 +525,13 @@
// Copy the image to the frame with conversion to the destination format.
val dataBufferByte = image.raster.dataBuffer as DataBufferByte
val numBytes = av_image_get_buffer_size(rgbFrame.format(), rgbFrame.width(), rgbFrame.height(), 1)
- rgbFrame.data(0).asByteBufferOfSize(numBytes).put(dataBufferByte.data)
+ val byteBuffer = rgbFrame.data(0).asByteBufferOfSize(numBytes)
+ val y = (videoSize.height - size.height) / 2
+ // Fill the extra strip at the top with black three bytes per pixel.
+ byteBuffer.fill(0.toByte(), y * rgbFrame.width() * 3)
+ byteBuffer.put(dataBufferByte.data)
+ // Fill the extra strip at the bottom with black three bytes per pixel.
+ byteBuffer.fill(0.toByte(), (videoSize.height - y - size.height) * rgbFrame.width() * 3)
val swsContext = sws_getContext(rgbFrame.width(), rgbFrame.height(), rgbFrame.format(),
encodingFrame.width(), encodingFrame.height(), encodingFrame.format(),
SWS_BICUBIC, null, null, null as DoublePointer?)!!
@@ -611,15 +618,18 @@
}
}
- private fun getScaledAndRotatedDisplaySize(): Dimension {
+ private fun computeDisplayImageSize(): Dimension {
+ // The same logic as in ComputeVideoSize in display_streamer.cc except for rounding of height.
val rotatedDisplaySize = getFoldedDisplaySize().rotatedByQuadrants(displayOrientation)
- val width = rotatedDisplaySize.width
- val height = rotatedDisplaySize.height
+ val displayWidth = rotatedDisplaySize.width.toDouble()
+ val displayHeight = rotatedDisplaySize.height.toDouble()
val maxResolutionWidth = maxVideoResolution.width.coerceAtMost(maxVideoEncoderResolution)
val maxResolutionHeight = maxVideoResolution.height.coerceAtMost(maxVideoEncoderResolution)
- val scale = max(min(1.0, min(maxResolutionWidth.toDouble() / width, maxResolutionHeight.toDouble() / height)),
- max(MIN_VIDEO_RESOLUTION / width, MIN_VIDEO_RESOLUTION / height))
- return Dimension((width * scale).roundToInt().roundUpToMultipleOf8(), (height * scale).roundToInt().roundUpToMultipleOf8())
+ val scale = max(min(1.0, min(maxResolutionWidth / displayWidth, maxResolutionHeight / displayHeight)),
+ max(MIN_VIDEO_RESOLUTION / displayWidth, MIN_VIDEO_RESOLUTION / displayHeight))
+ val width = (displayWidth * scale).roundToInt().roundUpToMultipleOf8()
+ val height = (width * displayHeight / displayWidth).roundToInt()
+ return Dimension(width, height)
}
private fun getFoldedDisplaySize(): Dimension {
@@ -802,6 +812,12 @@
return false
}
+private fun ByteBuffer.fill(b: Byte, count: Int) {
+ for (i in 0 until count) {
+ put(b)
+ }
+}
+
private class ColorScheme(val start1: Color, val end1: Color, val start2: Color, val end2: Color)
private val COLOR_SCHEMES = listOf(ColorScheme(Color(236, 112, 99), Color(250, 219, 216), Color(212, 230, 241), Color(84, 153, 199)),
diff --git a/studio/version.bzl b/studio/version.bzl
index e46945e..831f1cb 100644
--- a/studio/version.bzl
+++ b/studio/version.bzl
@@ -3,5 +3,5 @@
STUDIO_CODENAME = "Hedgehog"
STUDIO_VERSION = "Canary"
-STUDIO_MICRO_PATCH = "1.10"
-STUDIO_RELEASE_NUMBER = 10
+STUDIO_MICRO_PATCH = "1.11"
+STUDIO_RELEASE_NUMBER = 11