summaryrefslogtreecommitdiff
path: root/device-manager
diff options
context:
space:
mode:
authorJuan C Nuno <juancnuno@google.com>2022-07-12 14:51:33 -0700
committerJuan Nuno <juancnuno@google.com>2022-07-14 18:19:55 +0000
commit48d7630a33065584b5ec408d21865bf7b04fb8cb (patch)
tree1231ac086156b7e071686cc692aefeac9fd6ae13 /device-manager
parentfda264027d9d103e36aa5e0784d082bbf682a4cb (diff)
downloadidea-48d7630a33065584b5ec408d21865bf7b04fb8cb.tar.gz
Implement stopping in the model
Bug: 237442318 Test: VirtualDeviceTableModelTest Change-Id: I617c393c98b361c165a7456876e2bc9b8b1a58c7
Diffstat (limited to 'device-manager')
-rw-r--r--device-manager/src/com/android/tools/idea/devicemanager/virtualtab/VirtualDeviceTableModel.java90
-rw-r--r--device-manager/testSrc/com/android/tools/idea/devicemanager/virtualtab/VirtualDeviceTableModelTest.java156
2 files changed, 226 insertions, 20 deletions
diff --git a/device-manager/src/com/android/tools/idea/devicemanager/virtualtab/VirtualDeviceTableModel.java b/device-manager/src/com/android/tools/idea/devicemanager/virtualtab/VirtualDeviceTableModel.java
index 70f23011b01..ae35e45f5d1 100644
--- a/device-manager/src/com/android/tools/idea/devicemanager/virtualtab/VirtualDeviceTableModel.java
+++ b/device-manager/src/com/android/tools/idea/devicemanager/virtualtab/VirtualDeviceTableModel.java
@@ -16,10 +16,14 @@
package com.android.tools.idea.devicemanager.virtualtab;
import com.android.annotations.concurrency.UiThread;
+import com.android.annotations.concurrency.WorkerThread;
+import com.android.ddmlib.EmulatorConsole;
+import com.android.ddmlib.IDevice;
import com.android.sdklib.AndroidVersion;
import com.android.tools.idea.avdmanager.AvdManagerConnection;
import com.android.tools.idea.devicemanager.ActivateDeviceFileExplorerWindowValue;
import com.android.tools.idea.devicemanager.Device;
+import com.android.tools.idea.devicemanager.DeviceManagerAndroidDebugBridge;
import com.android.tools.idea.devicemanager.DeviceManagerFutureCallback;
import com.android.tools.idea.devicemanager.DeviceManagerUsageTracker;
import com.android.tools.idea.devicemanager.Devices;
@@ -31,6 +35,7 @@ import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.wireless.android.sdk.stats.DeviceManagerEvent;
+import com.google.wireless.android.sdk.stats.DeviceManagerEvent.EventKind;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.project.Project;
import com.intellij.util.concurrency.AppExecutorUtil;
@@ -40,10 +45,13 @@ import java.util.Collection;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.Executor;
+import java.util.function.BiConsumer;
+import java.util.function.Function;
import javax.swing.table.AbstractTableModel;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
+// TODO Annotate the methods with the threading annotations
@UiThread
final class VirtualDeviceTableModel extends AbstractTableModel {
static final int DEVICE_MODEL_COLUMN_INDEX = 0;
@@ -56,8 +64,12 @@ final class VirtualDeviceTableModel extends AbstractTableModel {
private final @Nullable Project myProject;
private @NotNull List<@NotNull VirtualDevice> myDevices;
- private final @NotNull Callable<@NotNull AvdManagerConnection> myGetDefaultAvdManagerConnection;
private final @NotNull NewSetOnline myNewSetOnline;
+ private final @NotNull Callable<@NotNull AvdManagerConnection> myGetDefaultAvdManagerConnection;
+ private final @NotNull Function<@NotNull VirtualDeviceTableModel, @NotNull FutureCallback<@Nullable Object>> myNewSetAllOnline;
+ private final @NotNull DeviceManagerAndroidDebugBridge myBridge;
+ private final @NotNull Function<@NotNull IDevice, @NotNull EmulatorConsole> myGetConsole;
+ private final @NotNull BiConsumer<@NotNull Throwable, @Nullable Project> myShowErrorDialog;
@VisibleForTesting
interface NewSetOnline {
@@ -77,23 +89,33 @@ final class VirtualDeviceTableModel extends AbstractTableModel {
}
VirtualDeviceTableModel(@Nullable Project project) {
- this(project, List.of());
- }
-
- @VisibleForTesting
- VirtualDeviceTableModel(@Nullable Project project, @NotNull Collection<@NotNull VirtualDevice> devices) {
- this(project, devices, AvdManagerConnection::getDefaultAvdManagerConnection, VirtualDeviceTableModel::newSetOnline);
+ this(project,
+ List.of(),
+ VirtualDeviceTableModel::newSetOnline,
+ AvdManagerConnection::getDefaultAvdManagerConnection,
+ SetAllOnline::new,
+ new DeviceManagerAndroidDebugBridge(),
+ EmulatorConsole::getConsole,
+ VirtualTabMessages::showErrorDialog);
}
@VisibleForTesting
VirtualDeviceTableModel(@Nullable Project project,
@NotNull Collection<@NotNull VirtualDevice> devices,
+ @NotNull NewSetOnline newSetOnline,
@NotNull Callable<@NotNull AvdManagerConnection> getDefaultAvdManagerConnection,
- @NotNull NewSetOnline newSetOnline) {
+ @NotNull Function<@NotNull VirtualDeviceTableModel, @NotNull FutureCallback<@Nullable Object>> newSetAllOnline,
+ @NotNull DeviceManagerAndroidDebugBridge bridge,
+ @NotNull Function<@NotNull IDevice, @NotNull EmulatorConsole> getConsole,
+ @NotNull BiConsumer<@NotNull Throwable, @Nullable Project> showErrorDialog) {
myProject = project;
myDevices = new ArrayList<>(devices);
- myGetDefaultAvdManagerConnection = getDefaultAvdManagerConnection;
myNewSetOnline = newSetOnline;
+ myGetDefaultAvdManagerConnection = getDefaultAvdManagerConnection;
+ myNewSetAllOnline = newSetAllOnline;
+ myBridge = bridge;
+ myGetConsole = getConsole;
+ myShowErrorDialog = showErrorDialog;
}
@VisibleForTesting
@@ -307,7 +329,7 @@ final class VirtualDeviceTableModel extends AbstractTableModel {
assert false;
break;
case STOPPING:
- // TODO stop
+ stop(modelRowIndex);
break;
default:
assert false : state;
@@ -317,7 +339,7 @@ final class VirtualDeviceTableModel extends AbstractTableModel {
private void launch(int modelRowIndex) {
DeviceManagerEvent event = DeviceManagerEvent.newBuilder()
- .setKind(DeviceManagerEvent.EventKind.VIRTUAL_LAUNCH_ACTION)
+ .setKind(EventKind.VIRTUAL_LAUNCH_ACTION)
.build();
DeviceManagerUsageTracker.log(event);
@@ -332,18 +354,54 @@ final class VirtualDeviceTableModel extends AbstractTableModel {
// noinspection UnstableApiUsage
FluentFuture.from(getDefaultAvdManagerConnection())
.transformAsync(connection -> connection.startAvd(myProject, device.getAvdInfo()), executor)
- .addCallback(new SetAllOnline(this), executor);
+ .addCallback(myNewSetAllOnline.apply(this), executor);
}
- private static final class SetAllOnline implements FutureCallback<Object> {
+ private void stop(int modelRowIndex) {
+ DeviceManagerEvent event = DeviceManagerEvent.newBuilder()
+ .setKind(EventKind.VIRTUAL_STOP_ACTION)
+ .build();
+
+ DeviceManagerUsageTracker.log(event);
+
+ VirtualDevice device = myDevices.get(modelRowIndex).withState(VirtualDevice.State.STOPPING);
+
+ myDevices.set(modelRowIndex, device);
+ fireTableCellUpdated(modelRowIndex, LAUNCH_OR_STOP_MODEL_COLUMN_INDEX);
+
+ // noinspection UnstableApiUsage
+ FluentFuture.from(myBridge.findDevice(myProject, device.getKey()))
+ .transform(d -> stop(d, device), AppExecutorUtil.getAppExecutorService())
+ .addCallback(myNewSetAllOnline.apply(this), EdtExecutorService.getInstance());
+ }
+
+ /**
+ * Called by an application pool thread
+ */
+ @WorkerThread
+ @SuppressWarnings("SameReturnValue")
+ private @Nullable Void stop(@Nullable IDevice d, @NotNull Object device) {
+ if (d == null) {
+ throw new ErrorDialogException("Unable to stop " + device,
+ "An error occurred stopping " + device + ". To stop the device, try manually closing the " + device +
+ " emulator window.");
+ }
+
+ myGetConsole.apply(d).kill();
+ return null;
+ }
+
+ @VisibleForTesting
+ static final class SetAllOnline implements FutureCallback<Object> {
private final @NotNull VirtualDeviceTableModel myModel;
- private SetAllOnline(@NotNull VirtualDeviceTableModel model) {
+ @VisibleForTesting
+ SetAllOnline(@NotNull VirtualDeviceTableModel model) {
myModel = model;
}
@Override
- public void onSuccess(@NotNull Object object) {
+ public void onSuccess(@Nullable Object object) {
// The launch succeeded. Rely on the VirtualDeviceChangeListener, which calls VirtualDeviceTableModel::setAllOnline, to transition the
// device state from VirtualDevice.State.LAUNCHING to VirtualDevice.State.LAUNCHED. Likewise for STOPPING and STOPPED.
}
@@ -353,6 +411,8 @@ final class VirtualDeviceTableModel extends AbstractTableModel {
// The launch failed. Manually transition the state from LAUNCHING to whatever the AvdManagerConnection reports (STOPPED, presumably).
// Likewise for STOPPING.
myModel.setAllOnline();
+
+ myModel.myShowErrorDialog.accept(throwable, myModel.myProject);
}
}
}
diff --git a/device-manager/testSrc/com/android/tools/idea/devicemanager/virtualtab/VirtualDeviceTableModelTest.java b/device-manager/testSrc/com/android/tools/idea/devicemanager/virtualtab/VirtualDeviceTableModelTest.java
index 46476f2063f..3e72844fb0a 100644
--- a/device-manager/testSrc/com/android/tools/idea/devicemanager/virtualtab/VirtualDeviceTableModelTest.java
+++ b/device-manager/testSrc/com/android/tools/idea/devicemanager/virtualtab/VirtualDeviceTableModelTest.java
@@ -17,26 +17,33 @@ package com.android.tools.idea.devicemanager.virtualtab;
import static org.junit.Assert.assertEquals;
+import com.android.ddmlib.EmulatorConsole;
import com.android.ddmlib.IDevice;
import com.android.sdklib.AndroidVersion;
import com.android.sdklib.internal.avd.AvdInfo;
import com.android.tools.idea.avdmanager.AvdManagerConnection;
import com.android.tools.idea.devicemanager.CountDownLatchAssert;
import com.android.tools.idea.devicemanager.CountDownLatchFutureCallback;
+import com.android.tools.idea.devicemanager.DeviceManagerAndroidDebugBridge;
import com.android.tools.idea.devicemanager.Key;
+import com.android.tools.idea.devicemanager.virtualtab.VirtualDeviceTableModel.SetAllOnline;
import com.android.tools.idea.testing.swing.TableModelEventArgumentMatcher;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
+import com.intellij.openapi.project.Project;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.CountDownLatch;
+import java.util.function.BiConsumer;
import javax.swing.event.TableModelEvent;
import javax.swing.event.TableModelListener;
import javax.swing.table.TableModel;
import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
+import org.mockito.ArgumentMatcher;
import org.mockito.ArgumentMatchers;
import org.mockito.Mockito;
@@ -50,13 +57,17 @@ public final class VirtualDeviceTableModelTest {
@Test
public void setAllOnline() throws InterruptedException {
// Arrange
- Mockito.when(myConnection.isAvdRunning(myAvd)).thenReturn(true);
CountDownLatch latch = new CountDownLatch(1);
+ Mockito.when(myConnection.isAvdRunning(myAvd)).thenReturn(true);
VirtualDeviceTableModel model = new VirtualDeviceTableModel(null,
myDevices,
+ (m, key) -> newSetOnline(m, key, latch),
() -> myConnection,
- (m, key) -> newSetOnline(m, key, latch));
+ SetAllOnline::new,
+ new DeviceManagerAndroidDebugBridge(),
+ EmulatorConsole::getConsole,
+ VirtualTabMessages::showErrorDialog);
model.addTableModelListener(myListener);
@@ -81,7 +92,15 @@ public final class VirtualDeviceTableModelTest {
// Arrange
Mockito.when(myConnection.startAvd(null, myAvd)).thenReturn(Futures.immediateFuture(Mockito.mock(IDevice.class)));
- TableModel model = new VirtualDeviceTableModel(null, myDevices, () -> myConnection, VirtualDeviceTableModel::newSetOnline);
+ TableModel model = new VirtualDeviceTableModel(null,
+ myDevices,
+ VirtualDeviceTableModel::newSetOnline,
+ () -> myConnection,
+ SetAllOnline::new,
+ new DeviceManagerAndroidDebugBridge(),
+ EmulatorConsole::getConsole,
+ VirtualTabMessages::showErrorDialog);
+
model.addTableModelListener(myListener);
// Act
@@ -107,10 +126,22 @@ public final class VirtualDeviceTableModelTest {
@Test
public void setValueAtStartAvdFailed() throws Exception {
// Arrange
- Mockito.when(myConnection.startAvd(null, myAvd)).thenReturn(Futures.immediateFailedFuture(new RuntimeException()));
CountDownLatch latch = new CountDownLatch(1);
+ Throwable throwable = new RuntimeException();
+ Mockito.when(myConnection.startAvd(null, myAvd)).thenReturn(Futures.immediateFailedFuture(throwable));
+
+ @SuppressWarnings("unchecked")
+ BiConsumer<Throwable, Project> showErrorDialog = Mockito.mock(BiConsumer.class);
+
+ TableModel model = new VirtualDeviceTableModel(null,
+ myDevices,
+ (m, key) -> newSetOnline(m, key, latch),
+ () -> myConnection,
+ SetAllOnline::new,
+ new DeviceManagerAndroidDebugBridge(),
+ EmulatorConsole::getConsole,
+ showErrorDialog);
- TableModel model = new VirtualDeviceTableModel(null, myDevices, () -> myConnection, (m, key) -> newSetOnline(m, key, latch));
model.addTableModelListener(myListener);
// Act
@@ -125,5 +156,120 @@ public final class VirtualDeviceTableModelTest {
Mockito.verify(myListener).tableChanged(ArgumentMatchers.argThat(new TableModelEventArgumentMatcher(event)));
Mockito.verify(myListener).tableChanged(ArgumentMatchers.argThat(new TableModelEventArgumentMatcher(new TableModelEvent(model, 0))));
+
+ Mockito.verify(showErrorDialog).accept(throwable, null);
+ }
+
+ @Test
+ public void setValueAtStopFailed() throws Exception {
+ // Arrange
+ CountDownLatch latch = new CountDownLatch(1);
+ Mockito.when(myConnection.isAvdRunning(myAvd)).thenReturn(true);
+
+ DeviceManagerAndroidDebugBridge bridge = Mockito.mock(DeviceManagerAndroidDebugBridge.class);
+ Mockito.when(bridge.findDevice(null, TestVirtualDevices.newKey("Pixel_5_API_31"))).thenReturn(Futures.immediateFuture(null));
+
+ @SuppressWarnings("unchecked")
+ BiConsumer<Throwable, Project> showErrorDialog = Mockito.mock(BiConsumer.class);
+
+ TableModel model = new VirtualDeviceTableModel(null,
+ List.of(TestVirtualDevices.onlinePixel5Api31(myAvd)),
+ (m, key) -> newSetOnline(m, key, latch),
+ () -> myConnection,
+ SetAllOnline::new,
+ bridge,
+ EmulatorConsole::getConsole,
+ showErrorDialog);
+
+ model.addTableModelListener(myListener);
+
+ // Act
+ model.setValueAt(VirtualDevice.State.STOPPING, 0, VirtualDeviceTableModel.LAUNCH_OR_STOP_MODEL_COLUMN_INDEX);
+
+ // Assert
+ CountDownLatchAssert.await(latch);
+
+ assertEquals(TestVirtualDevices.onlinePixel5Api31(myAvd), model.getValueAt(0, VirtualDeviceTableModel.DEVICE_MODEL_COLUMN_INDEX));
+
+ TableModelEvent event = new TableModelEvent(model, 0, 0, VirtualDeviceTableModel.LAUNCH_OR_STOP_MODEL_COLUMN_INDEX);
+ Mockito.verify(myListener).tableChanged(ArgumentMatchers.argThat(new TableModelEventArgumentMatcher(event)));
+
+ Mockito.verify(myListener).tableChanged(ArgumentMatchers.argThat(new TableModelEventArgumentMatcher(new TableModelEvent(model, 0))));
+
+ ErrorDialogException exception = new ErrorDialogException(
+ "Unable to stop Pixel 5 API 31",
+ "An error occurred stopping Pixel 5 API 31. To stop the device, try manually closing the Pixel 5 API 31 emulator window.");
+
+ ArgumentMatcher<Throwable> matcher = new ErrorDialogExceptionArgumentMatcher(exception);
+ Mockito.verify(showErrorDialog).accept(ArgumentMatchers.argThat(matcher), ArgumentMatchers.isNull());
+ }
+
+ private static final class ErrorDialogExceptionArgumentMatcher implements ArgumentMatcher<Throwable> {
+ private final @NotNull ErrorDialogException myExpected;
+
+ private ErrorDialogExceptionArgumentMatcher(@NotNull ErrorDialogException expected) {
+ myExpected = expected;
+ }
+
+ @Override
+ public boolean matches(@NotNull Throwable actual) {
+ if (!(actual instanceof ErrorDialogException)) {
+ return false;
+ }
+
+ ErrorDialogException exception = (ErrorDialogException)actual;
+ return myExpected.getTitle().equals(exception.getTitle()) && myExpected.getMessage().equals(exception.getMessage());
+ }
+ }
+
+ @Test
+ public void setValueAtStopSucceeded() throws Exception {
+ // Arrange
+ CountDownLatch latch = new CountDownLatch(1);
+ Key key = TestVirtualDevices.newKey("Pixel_5_API_31");
+
+ DeviceManagerAndroidDebugBridge bridge = Mockito.mock(DeviceManagerAndroidDebugBridge.class);
+ Mockito.when(bridge.findDevice(null, key)).thenReturn(Futures.immediateFuture(Mockito.mock(IDevice.class)));
+
+ EmulatorConsole console = Mockito.mock(EmulatorConsole.class);
+
+ TableModel model = new VirtualDeviceTableModel(null,
+ List.of(TestVirtualDevices.onlinePixel5Api31(myAvd)),
+ VirtualDeviceTableModel::newSetOnline,
+ AvdManagerConnection::getDefaultAvdManagerConnection,
+ m -> newSetAllOnline(m, latch),
+ bridge,
+ device -> console,
+ VirtualTabMessages::showErrorDialog);
+
+ model.addTableModelListener(myListener);
+
+ // Act
+ model.setValueAt(VirtualDevice.State.STOPPING, 0, VirtualDeviceTableModel.LAUNCH_OR_STOP_MODEL_COLUMN_INDEX);
+
+ // Assert
+ CountDownLatchAssert.await(latch);
+
+ Object device = new VirtualDevice.Builder()
+ .setKey(key)
+ .setName("Pixel 5 API 31")
+ .setTarget("Android 12.0 Google APIs")
+ .setCpuArchitecture("x86_64")
+ .setAndroidVersion(new AndroidVersion(31))
+ .setState(VirtualDevice.State.STOPPING)
+ .setAvdInfo(myAvd)
+ .build();
+
+ assertEquals(device, model.getValueAt(0, VirtualDeviceTableModel.DEVICE_MODEL_COLUMN_INDEX));
+
+ TableModelEvent event = new TableModelEvent(model, 0, 0, VirtualDeviceTableModel.LAUNCH_OR_STOP_MODEL_COLUMN_INDEX);
+ Mockito.verify(myListener).tableChanged(ArgumentMatchers.argThat(new TableModelEventArgumentMatcher(event)));
+
+ Mockito.verify(console).kill();
+ }
+
+ private static @NotNull FutureCallback<@Nullable Object> newSetAllOnline(@NotNull VirtualDeviceTableModel model,
+ @NotNull CountDownLatch latch) {
+ return new CountDownLatchFutureCallback<>(new SetAllOnline(model), latch);
}
}