Snap for 11390309 from f7494d85793577e897fa052d3dae17a704af9c29 to studio-jellyfish-release
Change-Id: Ife7aa40b3058b29e2b4f2ed284822aa1995c0a36
diff --git a/directaccess/src/com/google/gct/directaccess/provisioner/DeviceInfo.kt b/directaccess/src/com/google/gct/directaccess/provisioner/DeviceInfo.kt
index b0c1852..a047a61 100644
--- a/directaccess/src/com/google/gct/directaccess/provisioner/DeviceInfo.kt
+++ b/directaccess/src/com/google/gct/directaccess/provisioner/DeviceInfo.kt
@@ -34,7 +34,10 @@
val screenY: Int,
val screenDensity: Int,
val deviceAvailabilityEstimateSeconds: Long?,
-)
+) {
+ /** A string key to distinguish itself from other [DeviceInfo]s. */
+ val key = "$id/$api"
+}
data class DeviceSelection(var isSelected: Boolean, val deviceInfo: DeviceInfo)
diff --git a/directaccess/src/com/google/gct/directaccess/provisioner/DirectAccessDeviceHandle.kt b/directaccess/src/com/google/gct/directaccess/provisioner/DirectAccessDeviceHandle.kt
index 451fbc6..237db6c 100644
--- a/directaccess/src/com/google/gct/directaccess/provisioner/DirectAccessDeviceHandle.kt
+++ b/directaccess/src/com/google/gct/directaccess/provisioner/DirectAccessDeviceHandle.kt
@@ -146,8 +146,16 @@
when (sessionState) {
SessionState.EXPIRED -> trackEndReservation(true, EndReservationType.EXPIRE)
SessionState.FINISHED -> trackEndReservation(true, EndReservationType.FORCE_CHECK_IN)
- else ->
+ SessionState.ERROR ->
trackEndReservation(false, EndReservationType.ERROR, FailureReason.UNKNOWN_FAILURE)
+ SessionState.UNAVAILABLE ->
+ trackEndReservation(
+ false,
+ EndReservationType.ERROR,
+ FailureReason.FAILED_TO_ALLOCATE_DEVICE,
+ )
+ else ->
+ trackEndReservation(false, EndReservationType.UNKNOWN, FailureReason.UNKNOWN_FAILURE)
}
}
}
diff --git a/directaccess/src/com/google/gct/directaccess/provisioner/DirectAccessDeviceProvisionerPlugin.kt b/directaccess/src/com/google/gct/directaccess/provisioner/DirectAccessDeviceProvisionerPlugin.kt
index 30bc1df..016785e 100644
--- a/directaccess/src/com/google/gct/directaccess/provisioner/DirectAccessDeviceProvisionerPlugin.kt
+++ b/directaccess/src/com/google/gct/directaccess/provisioner/DirectAccessDeviceProvisionerPlugin.kt
@@ -88,15 +88,15 @@
cloudProjectManager.accessibleDeviceInfoListFlow.stateFlow.collect {
newAccessibleDeviceInfoList ->
accessibleDeviceInfoMapFlow.value =
- newAccessibleDeviceInfoList.groupBy { it.id }.mapValues { it.value.first() }
+ newAccessibleDeviceInfoList.groupBy { it.key }.mapValues { it.value.first() }
project.service<DirectAccessService>().deviceSelectionListFlow.update {
oldDeviceSelectionList ->
val accessibleDeviceIdSet = newAccessibleDeviceInfoList.map { it.id }.toSet()
- val selectedDeviceIdSet =
- oldDeviceSelectionList.filter { it.isSelected }.map { it.deviceInfo.id }.toSet()
+ val deselectedDeviceIdSet =
+ oldDeviceSelectionList.filter { !it.isSelected }.map { it.deviceInfo.id }.toSet()
val accessibleDeviceSelectionList =
newAccessibleDeviceInfoList.map {
- DeviceSelection(it.id in selectedDeviceIdSet, it)
+ DeviceSelection(it.id !in deselectedDeviceIdSet, it)
}
val inaccessibleDeviceSelectionList =
oldDeviceSelectionList.filter { it.deviceInfo.id !in accessibleDeviceIdSet }
@@ -129,11 +129,11 @@
.map { selection -> selection.deviceInfo }
.map { deviceInfo ->
existingDeviceInfoMap[deviceInfo]?.firstOrNull()
- ?: cachedTemplatesMap.computeIfAbsent(deviceInfo.id) {
+ ?: cachedTemplatesMap.computeIfAbsent(deviceInfo.key) {
val templateScope = scope.createChildScope(isSupervisor = true)
val deviceInfoFlow =
accessibleDeviceInfoMapFlow
- .mapNotNull { deviceMap -> deviceMap[deviceInfo.id] }
+ .mapNotNull { deviceMap -> deviceMap[deviceInfo.key] }
.stateIn(templateScope, SharingStarted.Eagerly, deviceInfo)
DirectAccessDeviceTemplate(
project,
@@ -142,8 +142,8 @@
templateScope,
reservationsFlow.combine(accessibleDeviceInfoMapFlow) {
reservations,
- deviceInfoSet ->
- reservations != null && deviceInfoSet[deviceInfo.id] != null
+ deviceInfoMap ->
+ reservations != null && deviceInfoMap[deviceInfo.key] != null
},
)
}
diff --git a/directaccess/src/com/google/gct/directaccess/provisioner/DirectAccessDeviceTemplate.kt b/directaccess/src/com/google/gct/directaccess/provisioner/DirectAccessDeviceTemplate.kt
index a421807..8df85aa 100644
--- a/directaccess/src/com/google/gct/directaccess/provisioner/DirectAccessDeviceTemplate.kt
+++ b/directaccess/src/com/google/gct/directaccess/provisioner/DirectAccessDeviceTemplate.kt
@@ -66,7 +66,7 @@
val deviceInfo: DeviceInfo
get() = deviceInfoFlow.value
- override val id = DeviceId(PLUGIN_ID, true, "model_id=${deviceInfo.id}")
+ override val id = DeviceId(PLUGIN_ID, true, "model_id=${deviceInfo.key}")
override val properties = deviceInfo.toDeviceProperties()
diff --git a/directaccess/testSrc/com/google/gct/directaccess/TestUtils.kt b/directaccess/testSrc/com/google/gct/directaccess/TestUtils.kt
index c32ad21..eac97a5 100644
--- a/directaccess/testSrc/com/google/gct/directaccess/TestUtils.kt
+++ b/directaccess/testSrc/com/google/gct/directaccess/TestUtils.kt
@@ -87,6 +87,19 @@
150,
30,
),
+ DeviceInfo(
+ "id4",
+ "Google",
+ "Pixel Watch",
+ "Google",
+ "watch",
+ 34,
+ DeviceType.WEAR_OS,
+ 50,
+ 100,
+ 150,
+ 30,
+ ),
)
}
diff --git a/directaccess/testSrc/com/google/gct/directaccess/analytics/DirectAccessUsageTrackerTest.kt b/directaccess/testSrc/com/google/gct/directaccess/analytics/DirectAccessUsageTrackerTest.kt
index 66a9ba1..f276bc2 100644
--- a/directaccess/testSrc/com/google/gct/directaccess/analytics/DirectAccessUsageTrackerTest.kt
+++ b/directaccess/testSrc/com/google/gct/directaccess/analytics/DirectAccessUsageTrackerTest.kt
@@ -73,6 +73,7 @@
import com.google.wireless.android.sdk.stats.DirectAccessUsageEvent.ExtendReservationDetails.ExtendReservationDuration.SIXTY_MINUTES
import com.google.wireless.android.sdk.stats.DirectAccessUsageEvent.ExtendReservationDetails.ExtendReservationDuration.THIRTY_MINUTES
import com.google.wireless.android.sdk.stats.DirectAccessUsageEvent.FailureReason
+import com.google.wireless.android.sdk.stats.DirectAccessUsageEvent.FailureReason.FAILED_TO_ALLOCATE_DEVICE
import com.google.wireless.android.sdk.stats.DirectAccessUsageEvent.FailureReason.UNKNOWN_FAILURE
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.components.service
@@ -763,6 +764,37 @@
}
@Test
+ fun testEndReservationFailMetricWhenDeviceNotAllocated() = runBlockingWithTimeout {
+ val template = plugin.templates.value[0] as DirectAccessDeviceTemplate
+
+ // Activate device
+ val handle = template.activationAction.activate() as DirectAccessDeviceHandle
+ yieldUntil {
+ template.activeDevice?.connection?.state?.value?.connection is
+ DirectAccessConnection.ConnectionState.Connected
+ }
+ val reservationFlow =
+ directAccessReservationManager.fetchReservationFlow(handle.reservation.name)
+ reservationFlow.waitUntilActive()
+ (reservationFlow as MutableStateFlow).update {
+ it.toBuilder().apply { sessionState = Reservation.SessionState.UNAVAILABLE }.build()
+ }
+ yieldUntil { reservationFlow.value.sessionState == Reservation.SessionState.UNAVAILABLE }
+
+ val studioEvent = findUsageEvent(END_RESERVATION)
+ assertThat(studioEvent.kind).isEqualTo(AndroidStudioEvent.EventKind.DIRECT_ACCESS_USAGE_EVENT)
+
+ val directAccessEvent = studioEvent.directAccessUsageEvent
+ assertThat(directAccessEvent.type).isEqualTo(END_RESERVATION)
+ assertThat(directAccessEvent.hasDeviceSessionId()).isTrue()
+ assertThat(directAccessEvent.failureReason).isEqualTo(FAILED_TO_ALLOCATE_DEVICE)
+
+ val endReservationDetails = directAccessEvent.endReservationDetails
+ assertThat(endReservationDetails.success).isFalse()
+ assertThat(endReservationDetails.endReservationType).isEqualTo(ERROR)
+ }
+
+ @Test
fun trackEndReservationFailMetricWhenErrorEndingReservation() = runBlockingWithTimeout {
// Override default connection setup
setupConnection { reservationName ->
diff --git a/directaccess/testSrc/com/google/gct/directaccess/provisioner/DirectAccessDeviceProvisionerTest.kt b/directaccess/testSrc/com/google/gct/directaccess/provisioner/DirectAccessDeviceProvisionerTest.kt
index 5aba87e..70ea646 100644
--- a/directaccess/testSrc/com/google/gct/directaccess/provisioner/DirectAccessDeviceProvisionerTest.kt
+++ b/directaccess/testSrc/com/google/gct/directaccess/provisioner/DirectAccessDeviceProvisionerTest.kt
@@ -161,9 +161,9 @@
plugin = DirectAccessDeviceProvisionerPlugin(session.scope, projectRule.project)
provisioner = DeviceProvisioner.create(session, listOf(plugin), testDeviceIcons)
projectRule.project.showAllTemplates { selectionList ->
- // Templates are not added to the provisioner automatically after switching to a cloud project
+ // Templates are all added to the provisioner automatically after switching to a cloud project
// with access to more devices.
- selectionList.forEach { assertThat(it.isSelected).isFalse() }
+ selectionList.forEach { assertThat(it.isSelected).isTrue() }
}
yieldUntil { provisioner.templates.value.isNotEmpty() }
}
@@ -252,7 +252,7 @@
provisioner.templates.value[0].id.apply {
assertThat(isTemplate).isTrue()
assertThat(pluginId).isEqualTo(PLUGIN_ID)
- assertThat(identifier).isEqualTo("model_id=id1")
+ assertThat(identifier).isEqualTo("model_id=id1/31")
}
assertThat(provisioner.templates.value[0].properties.title).isEqualTo("Google Pixel 5")
assertThat(provisioner.templates.value[0].properties.resolution).isEqualTo(Resolution(100, 200))
@@ -271,6 +271,10 @@
assertThat(provisioner.templates.value[3].properties.resolution).isEqualTo(Resolution(50, 100))
assertThat(provisioner.templates.value[3].properties.density).isEqualTo(150)
assertThat(provisioner.templates.value[3].properties.isRemote).isTrue()
+ assertThat(provisioner.templates.value[4].properties.title).isEqualTo("Google Pixel Watch")
+ assertThat(provisioner.templates.value[4].properties.resolution).isEqualTo(Resolution(50, 100))
+ assertThat(provisioner.templates.value[4].properties.density).isEqualTo(150)
+ assertThat(provisioner.templates.value[4].properties.isRemote).isTrue()
// Log out
loginStateRule.state.value = LoginStatus.LoggedOut
@@ -478,7 +482,7 @@
FakeDirectAccessConnection(
directAccessReservationManager,
reservationName,
- scope.createChildScope(true)
+ scope.createChildScope(true),
) {
private val connectionScope = scope.createChildScope(isSupervisor = true)
@@ -964,7 +968,7 @@
@RunsInEdt
@Test
fun selectTemplates() = runBlockingWithTimeout {
- assertThat(plugin.templates.value.size).isEqualTo(4)
+ assertThat(plugin.templates.value.size).isEqualTo(5)
val deviceInfoList =
plugin.templates.value.map { (it as DirectAccessDeviceTemplate).deviceInfo }
@@ -973,7 +977,7 @@
scope.launch { plugin.createDeviceTemplateAction.create() }
}) {
val dialog = it as SelectDeviceDialog
- assertThat(dialog.deviceTable.componentCount).isEqualTo(4)
+ assertThat(dialog.deviceTable.componentCount).isEqualTo(5)
val icons = dialog.deviceTable.findAllDescendants<JLabel>().mapNotNull { it.icon }.toList()
assertThat(icons)
.containsExactly(
@@ -981,6 +985,7 @@
FIREBASE_DEVICE_PHONE,
FIREBASE_DEVICE_PHONE,
FIREBASE_DEVICE_WEAR,
+ FIREBASE_DEVICE_WEAR,
)
val checkboxList = dialog.deviceTable.findAllDescendants<JBCheckBox>().toList()
checkboxList.forEach { assertThat(it.isSelected).isTrue() }
@@ -990,7 +995,7 @@
dialog.clickDefaultButton()
}
}
- yieldUntil { plugin.templates.value.size == 2 }
+ yieldUntil { plugin.templates.value.size == 3 }
var templates = plugin.templates.value
assertThat((templates[0] as DirectAccessDeviceTemplate).deviceInfo).isEqualTo(deviceInfoList[0])
assertThat((templates[1] as DirectAccessDeviceTemplate).deviceInfo).isEqualTo(deviceInfoList[3])
@@ -1001,13 +1006,13 @@
scope.launch { plugin.createDeviceTemplateAction.create() }
}) {
val dialog = it as SelectDeviceDialog
- assertThat(dialog.deviceTable.componentCount).isEqualTo(4)
+ assertThat(dialog.deviceTable.componentCount).isEqualTo(5)
val checkboxList = dialog.deviceTable.findAllDescendants<JBCheckBox>().toList()
checkboxList[1].isSelected = true
dialog.clickDefaultButton()
}
}
- yieldUntil { plugin.templates.value.size == 3 }
+ yieldUntil { plugin.templates.value.size == 4 }
templates = plugin.templates.value
assertThat((templates[0] as DirectAccessDeviceTemplate).deviceInfo).isEqualTo(deviceInfoList[0])
assertThat((templates[1] as DirectAccessDeviceTemplate).deviceInfo).isEqualTo(deviceInfoList[1])
@@ -1017,30 +1022,30 @@
@RunsInEdt
@Test
fun selectTemplatesAfterLogout() = runBlockingWithTimeout {
- assertThat(plugin.templates.value.size).isEqualTo(4)
+ assertThat(plugin.templates.value.size).isEqualTo(5)
// Same templates after logout.
loginStateRule.state.value = LoginStatus.LoggedOut
yieldUntil {
provisioner.templates.value.all { !it.activationAction.presentation.value.enabled }
}
- assertThat(plugin.templates.value.size).isEqualTo(4)
+ assertThat(plugin.templates.value.size).isEqualTo(5)
// De-select a template.
withContext(AndroidDispatchers.uiThread) {
val dialog = SelectDeviceDialog(projectRule.project)
createModalDialogAndInteractWithIt({ dialog.show() }) {
- assertThat(dialog.deviceTable.componentCount).isEqualTo(4)
+ assertThat(dialog.deviceTable.componentCount).isEqualTo(5)
val checkboxList = dialog.deviceTable.findAllDescendants<JBCheckBox>().toList()
checkboxList[1].isSelected = false
dialog.clickDefaultButton()
}
}
- yieldUntil { plugin.templates.value.size == 3 }
+ yieldUntil { plugin.templates.value.size == 4 }
// The de-selected device info gets removed from the table.
withContext(AndroidDispatchers.uiThread) {
val dialog = SelectDeviceDialog(projectRule.project)
createModalDialogAndInteractWithIt({ dialog.show() }) {
- assertThat(dialog.deviceTable.componentCount).isEqualTo(3)
+ assertThat(dialog.deviceTable.componentCount).isEqualTo(4)
dialog.clickDefaultButton()
}
}
@@ -1049,16 +1054,16 @@
@RunsInEdt
@Test
fun testSelectDeviceDialogSearchTest() = runBlockingWithTimeout {
- assertThat(plugin.templates.value.size).isEqualTo(4)
+ assertThat(plugin.templates.value.size).isEqualTo(5)
withContext(AndroidDispatchers.uiThread) {
val dialog = SelectDeviceDialog(projectRule.project)
createModalDialogAndInteractWithIt({ dialog.show() }) {
- assertThat(dialog.deviceTable.componentCount).isEqualTo(4)
+ assertThat(dialog.deviceTable.componentCount).isEqualTo(5)
val searchTextField = dialog.contentPanel.findAllDescendants<SearchTextField>().first()
// Case-insensitive search
searchTextField.text = "GoOgLe WaTcH"
- assertThat(dialog.deviceTable.componentCount).isEqualTo(1)
+ assertThat(dialog.deviceTable.componentCount).isEqualTo(2)
assertThat(dialog.deviceTable.values[0].deviceInfo.name).isEqualTo("Pixel Watch")
// Search for devices with 6 in their name