Snap for 11189729 from f9eab98e3bd7174b125d997f0aafbbb547efdeca to studio-iguana-release
Change-Id: Icbb602962c77deed36697bae633b0494e621096a
diff --git a/directaccess/src/com/google/gct/directaccess/provisioner/DirectAccessDeviceHandle.kt b/directaccess/src/com/google/gct/directaccess/provisioner/DirectAccessDeviceHandle.kt
index 939db18..dd138dc 100644
--- a/directaccess/src/com/google/gct/directaccess/provisioner/DirectAccessDeviceHandle.kt
+++ b/directaccess/src/com/google/gct/directaccess/provisioner/DirectAccessDeviceHandle.kt
@@ -16,6 +16,7 @@
package com.google.gct.directaccess.provisioner
import com.android.adblib.ConnectedDevice
+import com.android.adblib.deviceInfo
import com.android.adblib.deviceProperties
import com.android.adblib.serialNumber
import com.android.sdklib.deviceprovisioner.ActivationAction
@@ -120,6 +121,8 @@
private var hasUserForceCheckedInDevice = false
/** Tracks [Reservation] activated */
private var hasReservationActivated = false
+ /** Tracks reservation expired shown */
+ private var hasShownReservationExpiredNotification = false
/** [ContentManagerListener] that listens to panel changes in RDW */
private var rdwPanelChangeListener: ContentManagerListener? = null
@@ -138,7 +141,7 @@
return@invokeOnCompletion
}
if (hasReservationActivated && state.connectedDevice != null) {
- notificationManager.showReservationExpiredNotification()
+ showReservationExpiredNotification()
}
when (sessionState) {
@@ -202,6 +205,13 @@
}
}
+ private fun showReservationExpiredNotification() =
+ synchronized(this) {
+ if (hasShownReservationExpiredNotification) return@synchronized
+ notificationManager.showReservationExpiredNotification()
+ hasShownReservationExpiredNotification = true
+ }
+
override val activationAction =
object : ActivationAction {
/** Starts connection to the remote device. */
@@ -418,6 +428,9 @@
state.reservation?.endTime?.epochSecond
)
}
+ if (reservationFlow.value.sessionState.isClosed()) {
+ showReservationExpiredNotification()
+ }
}
trackDisconnectMetric(true, connectionStateReason.toFailureReason(throwable))
}
diff --git a/directaccess/src/com/google/gct/directaccess/provisioner/DirectAccessNotificationManager.kt b/directaccess/src/com/google/gct/directaccess/provisioner/DirectAccessNotificationManager.kt
index fc4b31c..0fa2b4a 100644
--- a/directaccess/src/com/google/gct/directaccess/provisioner/DirectAccessNotificationManager.kt
+++ b/directaccess/src/com/google/gct/directaccess/provisioner/DirectAccessNotificationManager.kt
@@ -166,7 +166,7 @@
if (reservationExpiringNotification.notificationVisible) reservationExpiringNotification.show()
}
- fun showReservationExpiredNotification() {
+ fun showReservationExpiredNotification() =
stickyNotificationGroup
.createNotification(
"$deviceName session ended",
@@ -183,7 +183,6 @@
}
)
.notify(project)
- }
private fun getDeviceDisconnectedNotificationPhrase(reservationExpireTime: Long): String? {
val timeRemaining =
diff --git a/directaccess/testSrc/com/google/gct/directaccess/analytics/DirectAccessUsageTrackerTest.kt b/directaccess/testSrc/com/google/gct/directaccess/analytics/DirectAccessUsageTrackerTest.kt
index beac760..8ff24cb 100644
--- a/directaccess/testSrc/com/google/gct/directaccess/analytics/DirectAccessUsageTrackerTest.kt
+++ b/directaccess/testSrc/com/google/gct/directaccess/analytics/DirectAccessUsageTrackerTest.kt
@@ -45,6 +45,7 @@
import com.google.gct.directaccess.provisioner.DirectAccessDeviceProvisionerPlugin
import com.google.gct.directaccess.provisioner.DirectAccessDeviceTemplate
import com.google.gct.directaccess.provisioner.PLUGIN_ID
+import com.google.gct.directaccess.rule.CleanUpNotificationRule
import com.google.gct.login.GoogleLogin
import com.google.gct.login.LoginState
import com.google.gct.login.LoginStateRule
@@ -98,9 +99,14 @@
private val projectRule = ProjectRule()
private val grpcConnectionRule = GrpcConnectionRule(listOf(service))
private val loginStateRule = LoginStateRule(LoginStatus.LoggedIn("test@gmail.com"))
+ private val cleanUpNotificationRule = CleanUpNotificationRule(projectRule)
@get:Rule
- val ruleChain = RuleChain.outerRule(projectRule).around(grpcConnectionRule).around(loginStateRule)
+ val ruleChain: RuleChain =
+ RuleChain.outerRule(projectRule)
+ .around(grpcConnectionRule)
+ .around(loginStateRule)
+ .around(cleanUpNotificationRule)
private val session = FakeAdbSession()
private lateinit var plugin: DirectAccessDeviceProvisionerPlugin
@@ -488,6 +494,11 @@
}
yieldUntil { reservationFlow.value.sessionState.isClosed() }
+ // Wait for the end reservation event.
+ // This also ensures that the sticky notification for reservation end is shown
+ // This notification is then cleaned by the rule
+ findUsageEvent(END_RESERVATION)
+
val studioEvent = findUsageEvent(DISCONNECT_DEVICE)
assertThat(studioEvent.kind).isEqualTo(AndroidStudioEvent.EventKind.DIRECT_ACCESS_USAGE_EVENT)
diff --git a/directaccess/testSrc/com/google/gct/directaccess/provisioner/DirectAccessDeviceProvisionerTest.kt b/directaccess/testSrc/com/google/gct/directaccess/provisioner/DirectAccessDeviceProvisionerTest.kt
index e421542..d4cbe16 100644
--- a/directaccess/testSrc/com/google/gct/directaccess/provisioner/DirectAccessDeviceProvisionerTest.kt
+++ b/directaccess/testSrc/com/google/gct/directaccess/provisioner/DirectAccessDeviceProvisionerTest.kt
@@ -19,6 +19,7 @@
import com.android.adblib.DevicePropertyNames
import com.android.adblib.DeviceSelector
import com.android.adblib.DeviceState
+import com.android.adblib.connectedDevicesTracker
import com.android.adblib.scope
import com.android.adblib.testing.FakeAdbSession
import com.android.adblib.testingutils.CoroutineTestUtils.runBlockingWithTimeout
@@ -53,6 +54,7 @@
import com.google.gct.directaccess.TestUtils.getNotifications
import com.google.gct.directaccess.TestUtils.refreshReservations
import com.google.gct.directaccess.TestUtils.reservation
+import com.google.gct.directaccess.rule.CleanUpNotificationRule
import com.google.gct.directaccess.rule.FakeToolWindowRule
import com.google.gct.directaccess.ui.SelectDeviceDialog
import com.google.gct.login.LoginStateRule
@@ -96,7 +98,6 @@
import kotlinx.coroutines.withContext
import org.junit.After
import org.junit.Before
-import org.junit.Ignore
import org.junit.Rule
import org.junit.Test
import org.junit.rules.RuleChain
@@ -112,6 +113,7 @@
private val grpcConnectionRule = GrpcConnectionRule(listOf(service))
private val loginStateRule = LoginStateRule(LoginStatus.LoggedIn("test@gmail.com"))
private val fakeToolWindowRule = FakeToolWindowRule(projectRule)
+ private val cleanUpNotificationRule = CleanUpNotificationRule(projectRule)
@get:Rule
val ruleChain: RuleChain =
@@ -119,6 +121,7 @@
.around(grpcConnectionRule)
.around(loginStateRule)
.around(fakeToolWindowRule)
+ .around(cleanUpNotificationRule)
private val session = FakeAdbSession()
private lateinit var plugin: DirectAccessDeviceProvisionerPlugin
@@ -483,7 +486,7 @@
// Device removed after logout.
loginStateRule.state.value = LoginStatus.LoggedOut
yieldUntil {
- provisioner.templates.value.all { !it.activationAction.presentation.value.enabled }
+ provisioner.templates.value.all { (it as DirectAccessDeviceTemplate).activeDevice == null }
}
yieldUntil { provisioner.devices.value.isEmpty() }
@@ -517,7 +520,6 @@
}
@Test
- @Ignore("b/309136739")
fun testActionsInNotificationOnDisconnectDevice() = runBlockingWithTimeout {
val template = plugin.templates.value[0]
@@ -560,7 +562,6 @@
}
@Test
- @Ignore("b/309136739")
fun testActionsInNotificationOnExpiringReservation() = runBlockingWithTimeout {
val deviceInfo = deviceInfoListProvider()[0]
val template = plugin.templates.value[0]
@@ -608,7 +609,6 @@
}
@Test
- @Ignore("b/309136739")
fun testBannerNotificationForReservationExpiringNotification() = runBlockingWithTimeout {
val bannerNotifications = mutableListOf<EditorNotificationPanel>()
val handle = setupReservationExpiringTest()
@@ -646,7 +646,6 @@
}
@Test
- @Ignore("b/309136739")
fun testBalloonNotificationForReservationExpiringNotification() = runBlockingWithTimeout {
val bannerNotifications = mutableListOf<EditorNotificationPanel>()
val handle = setupReservationExpiringTest()
@@ -670,7 +669,6 @@
}
@Test
- @Ignore("b/309136739")
fun testNotificationOnUnexpectedDeviceDisconnection() = runBlockingWithTimeout {
val template = plugin.templates.value[0]
template.activationAction.activate()
@@ -692,22 +690,18 @@
}
@Test
- @Ignore("b/309136739")
fun testNotificationExpiringOnDisconnectDevice() = runBlockingWithTimeout {
val template = plugin.templates.value[0]
- template.activationAction.activate()
+ val handle = template.activationAction.activate() as DirectAccessDeviceHandle
yieldUntil { provisioner.devices.value.isNotEmpty() }
- val handle = (template as DirectAccessDeviceTemplate).activeDevice
- handle?.reservation?.let {
- directAccessReservationManager.fetchReservationFlow(it.name).waitUntilActive()
- }
+ directAccessReservationManager.fetchReservationFlow(handle.reservation.name).waitUntilActive()
- handle?.deactivationAction?.deactivate()
- yieldUntil { handle?.connectionState is ConnectionState.Disconnected }
+ handle.deactivationAction.deactivate()
+ yieldUntil { handle.connectionState is ConnectionState.Disconnected }
val firstNotificationsList = getNotifications(projectRule.project)
assertThat(firstNotificationsList.size).isEqualTo(1)
- handle?.activationAction?.activate()
+ handle.activationAction.activate()
yieldUntil { firstNotificationsList[0].isExpired }
// Expiring a notification does not guarantee it is no longer visible. Wait for the notification
@@ -716,12 +710,12 @@
// Device will reconnect after previous action. Disconnect again to show notification for force
// check-in
- handle?.deactivationAction?.deactivate()
+ handle.deactivationAction.deactivate()
val secondNotificationsList = getNotifications(projectRule.project)
assertThat(secondNotificationsList.size).isEqualTo(1)
- handle?.reservationAction?.endReservation()
+ handle.reservationAction.endReservation()
yieldUntil { getNotifications(projectRule.project).isEmpty() }
}
@@ -784,7 +778,6 @@
}
@Test
- @Ignore("b/309136739")
fun testNoNotificationWhenReservationCancelledBeforeActive() = runBlockingWithTimeout {
val template = plugin.templates.value[0] as DirectAccessDeviceTemplate
@@ -815,12 +808,12 @@
}
@Test
- @Ignore("b/309136739")
fun testNoNotificationOnForceCheckIn() = runBlockingWithTimeout {
setupConnection { reservationName ->
object : FakeDirectAccessConnection(directAccessReservationManager, reservationName, scope) {
override suspend fun endReservation(withGracePeriod: Boolean) {
session.hostServices.disconnect(deviceAddress()!!)
+ yieldUntil { session.connectedDevicesTracker.connectedDevices.value.isEmpty() }
super.endReservation(withGracePeriod)
}
}
@@ -842,7 +835,6 @@
}
@Test
- @Ignore("b/309136739")
fun testNoNotificationOnForceCheckInWhenReservationEndDelayed() = runBlockingWithTimeout {
setupConnection { reservationName ->
object :
@@ -962,8 +954,20 @@
}
@Test
- @Ignore("b/309136739")
fun testStickyNotificationOnReservationExpiry() = runBlockingWithTimeout {
+ setupConnection { reservationName ->
+ object :
+ FakeDirectAccessConnection(
+ directAccessReservationManager,
+ reservationName,
+ scope.createChildScope(true)
+ ) {
+ override suspend fun closeConnection(stateReason: DirectAccessConnection.StateReason) {
+ session.hostServices.disconnect(deviceAddress()!!)
+ super.closeConnection(stateReason)
+ }
+ }
+ }
val deviceInfo = deviceInfoListProvider()[0]
val template = plugin.templates.value[0] as DirectAccessDeviceTemplate
@@ -986,12 +990,14 @@
session.hostServices.devices =
DeviceList(listOf(com.android.adblib.DeviceInfo(serialNumber, DeviceState.ONLINE)), listOf())
yieldUntil { handle.state is Connected }
+ yieldUntil { provisioner.devices.value.isNotEmpty() }
directAccessReservationManager.cancelReservation(flow.value.name)
yieldUntil { !flow.value.isActive() }
- assertThat(template.activeDevice).isNull()
+ yieldUntil { template.activeDevice == null }
+ yieldUntil { getNotifications(projectRule.project).isNotEmpty() }
val notificationsList = getNotifications(projectRule.project)
assertThat(notificationsList.size).isEqualTo(1)
diff --git a/directaccess/testSrc/com/google/gct/directaccess/rule/CleanUpNotificationRule.kt b/directaccess/testSrc/com/google/gct/directaccess/rule/CleanUpNotificationRule.kt
new file mode 100644
index 0000000..167be8d
--- /dev/null
+++ b/directaccess/testSrc/com/google/gct/directaccess/rule/CleanUpNotificationRule.kt
@@ -0,0 +1,26 @@
+/*
+ * 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.google.gct.directaccess.rule
+
+import com.google.gct.directaccess.TestUtils.getNotifications
+import com.intellij.testFramework.ProjectRule
+import org.junit.rules.ExternalResource
+
+internal class CleanUpNotificationRule(private val projectRule: ProjectRule) : ExternalResource() {
+ override fun after() {
+ getNotifications(projectRule.project).forEach { it.expire() }
+ }
+}
diff --git a/test-recorder/src/com/google/gct/testrecorder/ui/RecordingDialog.java b/test-recorder/src/com/google/gct/testrecorder/ui/RecordingDialog.java
index 0b3ec61..329adb9 100644
--- a/test-recorder/src/com/google/gct/testrecorder/ui/RecordingDialog.java
+++ b/test-recorder/src/com/google/gct/testrecorder/ui/RecordingDialog.java
@@ -1017,8 +1017,8 @@
helper.addDependency(ANDROID_TEST_IMPLEMENTATION,
compactNotation,
excludes,
- new ExactDependencyMatcher(compactNotation),
- gradleBuildModel);
+ gradleBuildModel,
+ new ExactDependencyMatcher(ANDROID_TEST_IMPLEMENTATION, compactNotation));
}
}.queue();
}
diff --git a/test-recorder/src/com/google/gct/testrecorder/ui/TestRecorderAction.java b/test-recorder/src/com/google/gct/testrecorder/ui/TestRecorderAction.java
index b55bf2e..c71875b 100644
--- a/test-recorder/src/com/google/gct/testrecorder/ui/TestRecorderAction.java
+++ b/test-recorder/src/com/google/gct/testrecorder/ui/TestRecorderAction.java
@@ -16,10 +16,8 @@
package com.google.gct.testrecorder.ui;
import com.android.annotations.VisibleForTesting;
-import com.android.ide.common.repository.GradleCoordinate;
import com.android.tools.analytics.UsageTracker;
import com.android.tools.analytics.UsageTrackerUtils;
-import com.android.tools.idea.projectsystem.AndroidModuleSystem;
import com.android.tools.idea.projectsystem.ProjectSystemUtil;
import com.android.tools.idea.run.deployment.DeviceAndSnapshotComboBoxTargetProvider;
import com.google.common.collect.Lists;
@@ -41,7 +39,6 @@
import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.actionSystem.Presentation;
-import com.intellij.openapi.module.Module;
import com.intellij.openapi.project.DumbService;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.Messages;
@@ -99,7 +96,12 @@
}
// Disable Espresso Test Recorder for Compose projects, since Espresso Testing Framework does not support Compose.
- TestRecorderRunConfigurationProxy testRecorderConfigurationProxy = TestRecorderRunConfigurationProxy.getInstance(getSuitableRunConfigurations(project).get(0));
+ List<RunConfiguration> runConfigurations = getSuitableRunConfigurations(project);
+ if (runConfigurations.isEmpty()) {
+ presentation.setEnabled(false);
+ return;
+ }
+ TestRecorderRunConfigurationProxy testRecorderConfigurationProxy = TestRecorderRunConfigurationProxy.getInstance(runConfigurations.get(0));
if (ProjectSystemUtil.getModuleSystem(testRecorderConfigurationProxy.getModule()).getUsesCompose()) {
presentation.setEnabled(false);
return;