/*
* Copyright (C) 2007 The Android Open Source Project
*
* Licensed under the Eclipse Public License, Version 1.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.eclipse.org/org/documents/epl-v10.php
*
* 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.ide.eclipse.adt.launch;
import com.android.ddmlib.AndroidDebugBridge;
import com.android.ddmlib.Client;
import com.android.ddmlib.Device;
import com.android.ddmlib.IDevice;
import com.android.ddmlib.AndroidDebugBridge.IDeviceChangeListener;
import com.android.ddmlib.Device.DeviceState;
import com.android.ddmuilib.IImageLoader;
import com.android.ddmuilib.ImageHelper;
import com.android.ddmuilib.TableHelper;
import com.android.ide.eclipse.adt.AdtPlugin;
import com.android.ide.eclipse.adt.sdk.Sdk;
import com.android.ide.eclipse.ddms.DdmsPlugin;
import com.android.sdklib.IAndroidTarget;
import com.android.sdklib.avd.AvdManager;
import com.android.sdklib.avd.AvdManager.AvdInfo;
import com.android.sdkuilib.AvdSelector;
import org.eclipse.jface.dialogs.Dialog;
import org.eclipse.jface.dialogs.IDialogConstants;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.jface.viewers.ILabelProviderListener;
import org.eclipse.jface.viewers.IStructuredContentProvider;
import org.eclipse.jface.viewers.ITableLabelProvider;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.jface.viewers.TableViewer;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.swt.SWT;
import org.eclipse.swt.SWTException;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Table;
import java.util.ArrayList;
/**
* A dialog that lets the user choose a device to deploy an application.
* The user can either choose an exiting running device (including running emulators)
* or start a new emulator using an Android Virtual Device configuration that matches
* the current project.
*/
public class DeviceChooserDialog extends Dialog implements IDeviceChangeListener {
private final static int ICON_WIDTH = 16;
private final static String PREFS_COL_SERIAL = "deviceChooser.serial"; //$NON-NLS-1$
private final static String PREFS_COL_STATE = "deviceChooser.state"; //$NON-NLS-1$
private final static String PREFS_COL_AVD = "deviceChooser.avd"; //$NON-NLS-1$
private final static String PREFS_COL_TARGET = "deviceChooser.target"; //$NON-NLS-1$
private final static String PREFS_COL_DEBUG = "deviceChooser.debug"; //$NON-NLS-1$
private Table mDeviceTable;
private TableViewer mViewer;
private AvdSelector mPreferredAvdSelector;
private Image mDeviceImage;
private Image mEmulatorImage;
private Image mMatchImage;
private Image mNoMatchImage;
private Image mWarningImage;
private final DeviceChooserResponse mResponse;
private final String mPackageName;
private final IAndroidTarget mProjectTarget;
private final Sdk mSdk;
private final AvdInfo[] mFullAvdList;
private Button mDeviceRadioButton;
private boolean mDisableAvdSelectionChange = false;
/**
* Basic Content Provider for a table full of {@link Device} objects. The input is
* a {@link AndroidDebugBridge}.
*/
private class ContentProvider implements IStructuredContentProvider {
public Object[] getElements(Object inputElement) {
if (inputElement instanceof AndroidDebugBridge) {
return ((AndroidDebugBridge)inputElement).getDevices();
}
return new Object[0];
}
public void dispose() {
// pass
}
public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
// pass
}
}
/**
* A Label Provider for the {@link TableViewer} in {@link DeviceChooserDialog}.
* It provides labels and images for {@link Device} objects.
*/
private class LabelProvider implements ITableLabelProvider {
public Image getColumnImage(Object element, int columnIndex) {
if (element instanceof Device) {
Device device = (Device)element;
switch (columnIndex) {
case 0:
return device.isEmulator() ? mEmulatorImage : mDeviceImage;
case 2:
// check for compatibility.
if (device.isEmulator() == false) { // physical device
// get the api level of the device
try {
String apiValue = device.getProperty(
IDevice.PROP_BUILD_VERSION_NUMBER);
if (apiValue != null) {
int api = Integer.parseInt(apiValue);
if (api >= mProjectTarget.getApiVersionNumber()) {
// if the project is compiling against an add-on, the optional
// API may be missing from the device.
return mProjectTarget.isPlatform() ?
mMatchImage : mWarningImage;
} else {
return mNoMatchImage;
}
} else {
return mWarningImage;
}
} catch (NumberFormatException e) {
// lets consider the device non compatible
return mNoMatchImage;
}
} else {
// get the AvdInfo
AvdInfo info = mSdk.getAvdManager().getAvd(device.getAvdName(),
true /*validAvdOnly*/);
if (info == null) {
return mWarningImage;
}
return mProjectTarget.isCompatibleBaseFor(info.getTarget()) ?
mMatchImage : mNoMatchImage;
}
}
}
return null;
}
public String getColumnText(Object element, int columnIndex) {
if (element instanceof Device) {
Device device = (Device)element;
switch (columnIndex) {
case 0:
return device.getSerialNumber();
case 1:
if (device.isEmulator()) {
return device.getAvdName();
} else {
return "N/A"; // devices don't have AVD names.
}
case 2:
if (device.isEmulator()) {
AvdInfo info = mSdk.getAvdManager().getAvd(device.getAvdName(),
true /*validAvdOnly*/);
if (info == null) {
return "?";
}
return info.getTarget().getFullName();
} else {
String deviceBuild = device.getProperty(IDevice.PROP_BUILD_VERSION);
if (deviceBuild == null) {
return "unknown";
}
return deviceBuild;
}
case 3:
String debuggable = device.getProperty(IDevice.PROP_DEBUGGABLE);
if (debuggable != null && debuggable.equals("1")) { //$NON-NLS-1$
return "Yes";
} else {
return "";
}
case 4:
return getStateString(device);
}
}
return null;
}
public void addListener(ILabelProviderListener listener) {
// pass
}
public void dispose() {
// pass
}
public boolean isLabelProperty(Object element, String property) {
// pass
return false;
}
public void removeListener(ILabelProviderListener listener) {
// pass
}
}
public static class DeviceChooserResponse {
private AvdInfo mAvdToLaunch;
private IDevice mDeviceToUse;
public void setDeviceToUse(IDevice d) {
mDeviceToUse = d;
mAvdToLaunch = null;
}
public void setAvdToLaunch(AvdInfo avd) {
mAvdToLaunch = avd;
mDeviceToUse = null;
}
public IDevice getDeviceToUse() {
return mDeviceToUse;
}
public AvdInfo getAvdToLaunch() {
return mAvdToLaunch;
}
}
public DeviceChooserDialog(Shell parent, DeviceChooserResponse response, String packageName,
IAndroidTarget projectTarget) {
super(parent);
mResponse = response;
mPackageName = packageName;
mProjectTarget = projectTarget;
mSdk = Sdk.getCurrent();
// get the full list of Android Virtual Devices
AvdManager avdManager = mSdk.getAvdManager();
if (avdManager != null) {
mFullAvdList = avdManager.getValidAvds();
} else {
mFullAvdList = null;
}
loadImages();
}
private void cleanup() {
// done listening.
AndroidDebugBridge.removeDeviceChangeListener(this);
mEmulatorImage.dispose();
mDeviceImage.dispose();
mMatchImage.dispose();
mNoMatchImage.dispose();
mWarningImage.dispose();
}
@Override
protected void okPressed() {
cleanup();
super.okPressed();
}
@Override
protected void cancelPressed() {
cleanup();
super.cancelPressed();
}
@Override
protected Control createContents(Composite parent) {
Control content = super.createContents(parent);
// this must be called after createContents() has happened so that the
// ok button has been created (it's created after the call to createDialogArea)
updateDefaultSelection();
return content;
}
@Override
protected Control createDialogArea(Composite parent) {
Composite top = new Composite(parent, SWT.NONE);
top.setLayout(new GridLayout(1, true));
mDeviceRadioButton = new Button(top, SWT.RADIO);
mDeviceRadioButton.setText("Choose a running Android device");
mDeviceRadioButton.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
boolean deviceMode = mDeviceRadioButton.getSelection();
mDeviceTable.setEnabled(deviceMode);
mPreferredAvdSelector.setEnabled(!deviceMode);
if (deviceMode) {
handleDeviceSelection();
} else {
mResponse.setAvdToLaunch(mPreferredAvdSelector.getFirstSelected());
}
enableOkButton();
}
});
mDeviceRadioButton.setSelection(true);
// offset the selector from the radio button
Composite offsetComp = new Composite(top, SWT.NONE);
offsetComp.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
GridLayout layout = new GridLayout(1, false);
layout.marginRight = layout.marginHeight = 0;
layout.marginLeft = 30;
offsetComp.setLayout(layout);
IPreferenceStore store = AdtPlugin.getDefault().getPreferenceStore();
mDeviceTable = new Table(offsetComp, SWT.SINGLE | SWT.FULL_SELECTION);
GridData gd;
mDeviceTable.setLayoutData(gd = new GridData(GridData.FILL_BOTH));
gd.heightHint = 100;
mDeviceTable.setHeaderVisible(true);
mDeviceTable.setLinesVisible(true);
TableHelper.createTableColumn(mDeviceTable, "Serial Number",
SWT.LEFT, "AAA+AAAAAAAAAAAAAAAAAAA", //$NON-NLS-1$
PREFS_COL_SERIAL, store);
TableHelper.createTableColumn(mDeviceTable, "AVD Name",
SWT.LEFT, "engineering", //$NON-NLS-1$
PREFS_COL_AVD, store);
TableHelper.createTableColumn(mDeviceTable, "Target",
SWT.LEFT, "AAA+Android 9.9.9", //$NON-NLS-1$
PREFS_COL_TARGET, store);
TableHelper.createTableColumn(mDeviceTable, "Debug",
SWT.LEFT, "Debug", //$NON-NLS-1$
PREFS_COL_DEBUG, store);
TableHelper.createTableColumn(mDeviceTable, "State",
SWT.LEFT, "bootloader", //$NON-NLS-1$
PREFS_COL_STATE, store);
// create the viewer for it
mViewer = new TableViewer(mDeviceTable);
mViewer.setContentProvider(new ContentProvider());
mViewer.setLabelProvider(new LabelProvider());
mViewer.setInput(AndroidDebugBridge.getBridge());
mDeviceTable.addSelectionListener(new SelectionAdapter() {
/**
* Handles single-click selection on the device selector.
* {@inheritDoc}
*/
@Override
public void widgetSelected(SelectionEvent e) {
handleDeviceSelection();
}
/**
* Handles double-click selection on the device selector.
* Note that the single-click handler will probably already have been called.
* {@inheritDoc}
*/
@Override
public void widgetDefaultSelected(SelectionEvent e) {
handleDeviceSelection();
if (isOkButtonEnabled()) {
okPressed();
}
}
});
Button radio2 = new Button(top, SWT.RADIO);
radio2.setText("Launch a new Android Virtual Device");
// offset the selector from the radio button
offsetComp = new Composite(top, SWT.NONE);
offsetComp.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
layout = new GridLayout(1, false);
layout.marginRight = layout.marginHeight = 0;
layout.marginLeft = 30;
offsetComp.setLayout(layout);
mPreferredAvdSelector = new AvdSelector(offsetComp, getNonRunningAvds(), mProjectTarget);
mPreferredAvdSelector.setTableHeightHint(100);
mPreferredAvdSelector.setEnabled(false);
mPreferredAvdSelector.setSelectionListener(new SelectionAdapter() {
/**
* Handles single-click selection on the AVD selector.
* {@inheritDoc}
*/
@Override
public void widgetSelected(SelectionEvent e) {
if (mDisableAvdSelectionChange == false) {
mResponse.setAvdToLaunch(mPreferredAvdSelector.getFirstSelected());
enableOkButton();
}
}
/**
* Handles double-click selection on the AVD selector.
*
* Note that the single-click handler will probably already have been called
* but the selected item can have changed in between.
*
* {@inheritDoc}
*/
@Override
public void widgetDefaultSelected(SelectionEvent e) {
widgetSelected(e);
if (isOkButtonEnabled()) {
okPressed();
}
}
});
AndroidDebugBridge.addDeviceChangeListener(this);
return top;
}
private void loadImages() {
IImageLoader ddmsLoader = DdmsPlugin.getImageLoader();
Display display = DdmsPlugin.getDisplay();
IImageLoader adtLoader = AdtPlugin.getImageLoader();
if (mDeviceImage == null) {
mDeviceImage = ImageHelper.loadImage(ddmsLoader, display,
"device.png", //$NON-NLS-1$
ICON_WIDTH, ICON_WIDTH,
display.getSystemColor(SWT.COLOR_RED));
}
if (mEmulatorImage == null) {
mEmulatorImage = ImageHelper.loadImage(ddmsLoader, display,
"emulator.png", ICON_WIDTH, ICON_WIDTH, //$NON-NLS-1$
display.getSystemColor(SWT.COLOR_BLUE));
}
if (mMatchImage == null) {
mMatchImage = ImageHelper.loadImage(adtLoader, display,
"match.png", //$NON-NLS-1$
ICON_WIDTH, ICON_WIDTH,
display.getSystemColor(SWT.COLOR_GREEN));
}
if (mNoMatchImage == null) {
mNoMatchImage = ImageHelper.loadImage(adtLoader, display,
"error.png", //$NON-NLS-1$
ICON_WIDTH, ICON_WIDTH,
display.getSystemColor(SWT.COLOR_RED));
}
if (mWarningImage == null) {
mWarningImage = ImageHelper.loadImage(adtLoader, display,
"warning.png", //$NON-NLS-1$
ICON_WIDTH, ICON_WIDTH,
display.getSystemColor(SWT.COLOR_YELLOW));
}
}
/**
* Returns a display string representing the state of the device.
* @param d the device
*/
private static String getStateString(Device d) {
DeviceState deviceState = d.getState();
if (deviceState == DeviceState.ONLINE) {
return "Online";
} else if (deviceState == DeviceState.OFFLINE) {
return "Offline";
} else if (deviceState == DeviceState.BOOTLOADER) {
return "Bootloader";
}
return "??";
}
/**
* Sent when the a device is connected to the {@link AndroidDebugBridge}.
* <p/>
* This is sent from a non UI thread.
* @param device the new device.
*
* @see IDeviceChangeListener#deviceConnected(Device)
*/
public void deviceConnected(Device device) {
final DeviceChooserDialog dialog = this;
exec(new Runnable() {
public void run() {
if (mDeviceTable.isDisposed() == false) {
// refresh all
mViewer.refresh();
// update the selection
updateDefaultSelection();
// update the display of AvdInfo (since it's filtered to only display
// non running AVD.)
refillAvdList();
} else {
// table is disposed, we need to do something.
// lets remove ourselves from the listener.
AndroidDebugBridge.removeDeviceChangeListener(dialog);
}
}
});
}
/**
* Sent when the a device is connected to the {@link AndroidDebugBridge}.
* <p/>
* This is sent from a non UI thread.
* @param device the new device.
*
* @see IDeviceChangeListener#deviceDisconnected(Device)
*/
public void deviceDisconnected(Device device) {
deviceConnected(device);
}
/**
* Sent when a device data changed, or when clients are started/terminated on the device.
* <p/>
* This is sent from a non UI thread.
* @param device the device that was updated.
* @param changeMask the mask indicating what changed.
*
* @see IDeviceChangeListener#deviceChanged(Device, int)
*/
public void deviceChanged(final Device device, int changeMask) {
if ((changeMask & (Device.CHANGE_STATE | Device.CHANGE_BUILD_INFO)) != 0) {
final DeviceChooserDialog dialog = this;
exec(new Runnable() {
public void run() {
if (mDeviceTable.isDisposed() == false) {
// refresh the device
mViewer.refresh(device);
// update the defaultSelection.
updateDefaultSelection();
// update the display of AvdInfo (since it's filtered to only display
// non running AVD). This is done on deviceChanged because the avd name
// of a (emulator) device may be updated as the emulator boots.
refillAvdList();
// if the changed device is the current selection,
// we update the OK button based on its state.
if (device == mResponse.getDeviceToUse()) {
enableOkButton();
}
} else {
// table is disposed, we need to do something.
// lets remove ourselves from the listener.
AndroidDebugBridge.removeDeviceChangeListener(dialog);
}
}
});
}
}
/**
* Returns whether the dialog is in "device" mode (true), or in "avd" mode (false).
*/
private boolean isDeviceMode() {
return mDeviceRadioButton.getSelection();
}
/**
* Enables or disables the OK button of the dialog based on various selections in the dialog.
*/
private void enableOkButton() {
Button okButton = getButton(IDialogConstants.OK_ID);
if (isDeviceMode()) {
okButton.setEnabled(mResponse.getDeviceToUse() != null &&
mResponse.getDeviceToUse().isOnline());
} else {
okButton.setEnabled(mResponse.getAvdToLaunch() != null);
}
}
/**
* Returns true if the ok button is enabled.
*/
private boolean isOkButtonEnabled() {
Button okButton = getButton(IDialogConstants.OK_ID);
return okButton.isEnabled();
}
/**
* Executes the {@link Runnable} in the UI thread.
* @param runnable the runnable to execute.
*/
private void exec(Runnable runnable) {
try {
Display display = mDeviceTable.getDisplay();
display.asyncExec(runnable);
} catch (SWTException e) {
// tree is disposed, we need to do something. lets remove ourselves from the listener.
AndroidDebugBridge.removeDeviceChangeListener(this);
}
}
private void handleDeviceSelection() {
int count = mDeviceTable.getSelectionCount();
if (count != 1) {
handleSelection(null);
} else {
int index = mDeviceTable.getSelectionIndex();
Object data = mViewer.getElementAt(index);
if (data instanceof Device) {
handleSelection((Device)data);
} else {
handleSelection(null);
}
}
}
private void handleSelection(Device device) {
mResponse.setDeviceToUse(device);
enableOkButton();
}
/**
* Look for a default device to select. This is done by looking for the running
* clients on each device and finding one similar to the one being launched.
* <p/>
* This is done every time the device list changed unless there is a already selection.
*/
private void updateDefaultSelection() {
if (mDeviceTable.getSelectionCount() == 0) {
AndroidDebugBridge bridge = AndroidDebugBridge.getBridge();
Device[] devices = bridge.getDevices();
for (Device device : devices) {
Client[] clients = device.getClients();
for (Client client : clients) {
if (mPackageName.equals(client.getClientData().getClientDescription())) {
// found a match! Select it.
mViewer.setSelection(new StructuredSelection(device));
handleSelection(device);
// and we're done.
return;
}
}
}
}
handleDeviceSelection();
}
/**
* Returns the list of {@link AvdInfo} that are not already running in an emulator.
*/
private AvdInfo[] getNonRunningAvds() {
ArrayList<AvdInfo> list = new ArrayList<AvdInfo>();
Device[] devices = AndroidDebugBridge.getBridge().getDevices();
// loop through all the Avd and put the one that are not running in the list.
avdLoop: for (AvdInfo info : mFullAvdList) {
for (Device d : devices) {
if (info.getName().equals(d.getAvdName())) {
continue avdLoop;
}
}
list.add(info);
}
return list.toArray(new AvdInfo[list.size()]);
}
/**
* Refills the AVD list keeping the current selection.
*/
private void refillAvdList() {
AvdInfo[] array = getNonRunningAvds();
// save the current selection
AvdInfo selected = mPreferredAvdSelector.getFirstSelected();
// disable selection change.
mDisableAvdSelectionChange = true;
// set the new list in the selector
mPreferredAvdSelector.setAvds(array, mProjectTarget);
// attempt to reselect the proper avd if needed
if (selected != null) {
if (mPreferredAvdSelector.setSelection(selected) == false) {
// looks like the selection is lost. this can happen if an emulator
// running the AVD that was selected was launched from outside of Eclipse).
mResponse.setAvdToLaunch(null);
enableOkButton();
}
}
// enable the selection change
mDisableAvdSelectionChange = false;
}
}
|