/*
* Copyright (C) 2012 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.sdkuilib.internal.widgets;
import com.android.SdkConstants;
import com.android.annotations.Nullable;
import com.android.prefs.AndroidLocation.AndroidLocationException;
import com.android.resources.Density;
import com.android.resources.ScreenSize;
import com.android.sdklib.IAndroidTarget;
import com.android.sdklib.ISystemImage;
import com.android.sdklib.SdkManager;
import com.android.sdklib.devices.Camera;
import com.android.sdklib.devices.CameraLocation;
import com.android.sdklib.devices.Device;
import com.android.sdklib.devices.DeviceManager;
import com.android.sdklib.devices.Hardware;
import com.android.sdklib.devices.Screen;
import com.android.sdklib.devices.Software;
import com.android.sdklib.devices.Storage;
import com.android.sdklib.internal.avd.AvdInfo;
import com.android.sdklib.internal.avd.AvdManager;
import com.android.sdklib.internal.avd.AvdManager.AvdConflict;
import com.android.sdklib.internal.avd.HardwareProperties;
import com.android.sdkuilib.internal.repository.icons.ImageFactory;
import com.android.sdkuilib.ui.GridDialog;
import com.android.utils.ILogger;
import com.android.utils.Pair;
import org.eclipse.jface.dialogs.IDialogConstants;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.ModifyEvent;
import org.eclipse.swt.events.ModifyListener;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.VerifyEvent;
import org.eclipse.swt.events.VerifyListener;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Combo;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.FileDialog;
import org.eclipse.swt.widgets.Group;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Text;
import java.io.File;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class AvdCreationDialog extends GridDialog {
private AvdManager mAvdManager;
private ImageFactory mImageFactory;
private ILogger mSdkLog;
private AvdInfo mAvdInfo;
private boolean mHaveSystemImage;
private final TreeMap<String, IAndroidTarget> mCurrentTargets =
new TreeMap<String, IAndroidTarget>();
private Button mOkButton;
private Text mAvdName;
private Combo mDevice;
private Combo mTarget;
private Combo mAbi;
private Button mKeyboard;
private Button mSkin;
private Combo mFrontCamera;
private Combo mBackCamera;
private Button mSnapshot;
private Button mGpuEmulation;
private Text mRam;
private Text mVmHeap;
private Text mDataPartition;
private Combo mDataPartitionSize;
private Button mSdCardSizeRadio;
private Text mSdCardSize;
private Combo mSdCardSizeCombo;
private Button mSdCardFileRadio;
private Text mSdCardFile;
private Button mBrowseSdCard;
private Button mForceCreation;
private Composite mStatusComposite;
private Label mStatusIcon;
private Label mStatusLabel;
private Device mInitWithDevice;
private AvdInfo mCreatedAvd;
/**
* {@link VerifyListener} for {@link Text} widgets that should only contains
* numbers.
*/
private final VerifyListener mDigitVerifier = new VerifyListener() {
@Override
public void verifyText(VerifyEvent event) {
int count = event.text.length();
for (int i = 0; i < count; i++) {
char c = event.text.charAt(i);
if (c < '0' || c > '9') {
event.doit = false;
return;
}
}
}
};
public AvdCreationDialog(Shell shell,
AvdManager avdManager,
ImageFactory imageFactory,
ILogger log,
AvdInfo editAvdInfo) {
super(shell, 2, false);
mAvdManager = avdManager;
mImageFactory = imageFactory;
mSdkLog = log;
mAvdInfo = editAvdInfo;
}
/** Returns the AVD Created, if successful. */
public AvdInfo getCreatedAvd() {
return mCreatedAvd;
}
@Override
protected Control createContents(Composite parent) {
Control control = super.createContents(parent);
getShell().setText(mAvdInfo == null ? "Create new Android Virtual Device (AVD)"
: "Edit Android Virtual Device (AVD)");
mOkButton = getButton(IDialogConstants.OK_ID);
if (mAvdInfo != null) {
fillExistingAvdInfo(mAvdInfo);
} else if (mInitWithDevice != null) {
fillInitialDeviceInfo(mInitWithDevice);
}
validatePage();
return control;
}
@Override
public void createDialogContent(Composite parent) {
Label label;
String tooltip;
ValidateListener validateListener = new ValidateListener();
// --- avd name
label = new Label(parent, SWT.NONE);
label.setText("AVD Name:");
tooltip = "The name of the Android Virtual Device";
label.setToolTipText(tooltip);
mAvdName = new Text(parent, SWT.BORDER);
mAvdName.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
mAvdName.addModifyListener(new CreateNameModifyListener());
// --- device selection
label = new Label(parent, SWT.NONE);
label.setText("Device:");
tooltip = "The device this AVD will be based on";
mDevice = new Combo(parent, SWT.READ_ONLY | SWT.DROP_DOWN);
mDevice.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
initializeDevices();
mDevice.addSelectionListener(new DeviceSelectionListener());
// --- api target
label = new Label(parent, SWT.NONE);
label.setText("Target:");
tooltip = "The target API of the AVD";
label.setToolTipText(tooltip);
mTarget = new Combo(parent, SWT.READ_ONLY | SWT.DROP_DOWN);
mTarget.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
mTarget.setToolTipText(tooltip);
mTarget.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
reloadAbiTypeCombo();
validatePage();
}
});
reloadTargetCombo();
// --- avd ABIs
label = new Label(parent, SWT.NONE);
label.setText("CPU/ABI:");
tooltip = "The CPU/ABI of the virtual device";
label.setToolTipText(tooltip);
mAbi = new Combo(parent, SWT.READ_ONLY | SWT.DROP_DOWN);
mAbi.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
mAbi.setToolTipText(tooltip);
mAbi.addSelectionListener(validateListener);
label = new Label(parent, SWT.NONE);
label.setText("Keyboard:");
mKeyboard = new Button(parent, SWT.CHECK);
mKeyboard.setSelection(true); // default to having a keyboard irrespective of device
mKeyboard.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
mKeyboard.setText("Hardware keyboard present");
label = new Label(parent, SWT.NONE);
label.setText("Skin:");
mSkin = new Button(parent, SWT.CHECK);
mSkin.setSelection(true);
mSkin.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
mSkin.setText("Display a skin with hardware controls");
label = new Label(parent, SWT.NONE);
label.setText("Front Camera:");
tooltip = "";
label.setToolTipText(tooltip);
mFrontCamera = new Combo(parent, SWT.READ_ONLY | SWT.DROP_DOWN);
mFrontCamera.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
mFrontCamera.add("None");
mFrontCamera.add("Emulated");
mFrontCamera.add("Webcam0");
mFrontCamera.select(0);
label = new Label(parent, SWT.NONE);
label.setText("Back Camera:");
tooltip = "";
label.setToolTipText(tooltip);
mBackCamera = new Combo(parent, SWT.READ_ONLY | SWT.DROP_DOWN);
mBackCamera.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
mBackCamera.add("None");
mBackCamera.add("Emulated");
mBackCamera.add("Webcam0");
mBackCamera.select(0);
toggleCameras();
// --- memory options group
label = new Label(parent, SWT.NONE);
label.setText("Memory Options:");
Group memoryGroup = new Group(parent, SWT.BORDER);
memoryGroup.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
memoryGroup.setLayout(new GridLayout(4, false));
label = new Label(memoryGroup, SWT.NONE);
label.setText("RAM:");
tooltip = "The amount of RAM the emulated device should have in MiB";
label.setToolTipText(tooltip);
mRam = new Text(memoryGroup, SWT.BORDER);
mRam.addVerifyListener(mDigitVerifier);
mRam.addModifyListener(validateListener);
mRam.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
label = new Label(memoryGroup, SWT.NONE);
label.setText("VM Heap:");
tooltip = "The amount of memory, in MiB, available to typical Android applications";
label.setToolTipText(tooltip);
mVmHeap = new Text(memoryGroup, SWT.BORDER);
mVmHeap.addVerifyListener(mDigitVerifier);
mVmHeap.addModifyListener(validateListener);
mVmHeap.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
mVmHeap.setToolTipText(tooltip);
// --- Data partition group
label = new Label(parent, SWT.NONE);
label.setText("Internal Storage:");
tooltip = "The size of the data partition on the device.";
Group storageGroup = new Group(parent, SWT.NONE);
storageGroup.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
storageGroup.setLayout(new GridLayout(2, false));
mDataPartition = new Text(storageGroup, SWT.BORDER);
mDataPartition.setText("200");
mDataPartition.addVerifyListener(mDigitVerifier);
mDataPartition.addModifyListener(validateListener);
mDataPartition.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
mDataPartitionSize = new Combo(storageGroup, SWT.READ_ONLY | SWT.DROP_DOWN);
mDataPartitionSize.add("MiB");
mDataPartitionSize.add("GiB");
mDataPartitionSize.select(0);
mDataPartitionSize.addModifyListener(validateListener);
// --- sd card group
label = new Label(parent, SWT.NONE);
label.setText("SD Card:");
label.setLayoutData(new GridData(GridData.BEGINNING, GridData.BEGINNING,
false, false));
final Group sdCardGroup = new Group(parent, SWT.NONE);
sdCardGroup.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
sdCardGroup.setLayout(new GridLayout(3, false));
mSdCardSizeRadio = new Button(sdCardGroup, SWT.RADIO);
mSdCardSizeRadio.setText("Size:");
mSdCardSizeRadio.setToolTipText("Create a new SD Card file");
mSdCardSizeRadio.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent arg0) {
boolean sizeMode = mSdCardSizeRadio.getSelection();
enableSdCardWidgets(sizeMode);
validatePage();
}
});
mSdCardSize = new Text(sdCardGroup, SWT.BORDER);
mSdCardSize.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
mSdCardSize.addVerifyListener(mDigitVerifier);
mSdCardSize.addModifyListener(validateListener);
mSdCardSize.setToolTipText("Size of the new SD Card file (must be at least 9 MiB)");
mSdCardSizeCombo = new Combo(sdCardGroup, SWT.DROP_DOWN | SWT.READ_ONLY);
mSdCardSizeCombo.add("KiB");
mSdCardSizeCombo.add("MiB");
mSdCardSizeCombo.add("GiB");
mSdCardSizeCombo.select(1);
mSdCardSizeCombo.addSelectionListener(validateListener);
mSdCardFileRadio = new Button(sdCardGroup, SWT.RADIO);
mSdCardFileRadio.setText("File:");
mSdCardFileRadio.setToolTipText("Use an existing file for the SD Card");
mSdCardFile = new Text(sdCardGroup, SWT.BORDER);
mSdCardFile.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
mSdCardFile.addModifyListener(validateListener);
mSdCardFile.setToolTipText("File to use for the SD Card");
mBrowseSdCard = new Button(sdCardGroup, SWT.PUSH);
mBrowseSdCard.setText("Browse...");
mBrowseSdCard.setToolTipText("Select the file to use for the SD Card");
mBrowseSdCard.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent arg0) {
onBrowseSdCard();
validatePage();
}
});
mSdCardSizeRadio.setSelection(true);
enableSdCardWidgets(true);
// --- avd options group
label = new Label(parent, SWT.NONE);
label.setText("Emulation Options:");
Group optionsGroup = new Group(parent, SWT.NONE);
optionsGroup.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
optionsGroup.setLayout(new GridLayout(2, true));
mSnapshot = new Button(optionsGroup, SWT.CHECK);
mSnapshot.setText("Snapshot");
mSnapshot.setToolTipText("Emulator's state will be persisted between emulator executions");
mSnapshot.addSelectionListener(validateListener);
mGpuEmulation = new Button(optionsGroup, SWT.CHECK);
mGpuEmulation.setText("Use Host GPU");
mGpuEmulation.setToolTipText("Enable hardware OpenGLES emulation");
mGpuEmulation.addSelectionListener(validateListener);
// --- force creation group
mForceCreation = new Button(parent, SWT.CHECK);
mForceCreation.setText("Override the existing AVD with the same name");
mForceCreation
.setToolTipText("There's already an AVD with the same name. Check this to delete it and replace it by the new AVD.");
mForceCreation.setLayoutData(new GridData(GridData.BEGINNING, GridData.CENTER,
true, false, 2, 1));
mForceCreation.setEnabled(false);
mForceCreation.addSelectionListener(validateListener);
// add a separator to separate from the ok/cancel button
label = new Label(parent, SWT.SEPARATOR | SWT.HORIZONTAL);
label.setLayoutData(new GridData(GridData.FILL, GridData.CENTER, true, false, 3, 1));
// add stuff for the error display
mStatusComposite = new Composite(parent, SWT.NONE);
mStatusComposite.setLayoutData(new GridData(GridData.FILL, GridData.CENTER,
true, false, 3, 1));
GridLayout gl;
mStatusComposite.setLayout(gl = new GridLayout(2, false));
gl.marginHeight = gl.marginWidth = 0;
mStatusIcon = new Label(mStatusComposite, SWT.NONE);
mStatusIcon.setLayoutData(new GridData(GridData.BEGINNING, GridData.BEGINNING,
false, false));
mStatusLabel = new Label(mStatusComposite, SWT.NONE);
mStatusLabel.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
mStatusLabel.setText(""); //$NON-NLS-1$
}
@Nullable
private Device getSelectedDevice() {
Device[] devices = (Device[]) mDevice.getData();
if (devices != null) {
int index = mDevice.getSelectionIndex();
if (index != -1 && index < devices.length) {
return devices[index];
}
}
return null;
}
private void selectDevice(String manufacturer, String name) {
Device[] devices = (Device[]) mDevice.getData();
if (devices != null) {
for (int i = 0, n = devices.length; i < n; i++) {
Device device = devices[i];
if (device.getManufacturer().equals(manufacturer)
&& device.getName().equals(name)) {
mDevice.select(i);
break;
}
}
}
}
private void selectDevice(Device device) {
Device[] devices = (Device[]) mDevice.getData();
if (devices != null) {
for (int i = 0, n = devices.length; i < n; i++) {
if (devices[i].equals(device)) {
mDevice.select(i);
break;
}
}
}
}
private void initializeDevices() {
assert mDevice != null;
SdkManager sdkManager = mAvdManager.getSdkManager();
String location = sdkManager.getLocation();
if (sdkManager != null && location != null) {
DeviceManager deviceManager = DeviceManager.createInstance(location, mSdkLog);
List<Device> deviceList = deviceManager.getDevices(DeviceManager.ALL_DEVICES);
// Sort
List<Device> nexus = new ArrayList<Device>(deviceList.size());
List<Device> other = new ArrayList<Device>(deviceList.size());
for (Device device : deviceList) {
if (isNexus(device) && !isGeneric(device)) {
nexus.add(device);
} else {
other.add(device);
}
}
Collections.reverse(other);
Collections.sort(nexus, new Comparator<Device>() {
@Override
public int compare(Device device1, Device device2) {
// Descending order of age
return nexusRank(device2) - nexusRank(device1);
}
});
List<Device> all = nexus;
all.addAll(other);
Device[] devices = all.toArray(new Device[all.size()]);
String[] labels = new String[devices.length];
for (int i = 0, n = devices.length; i < n; i++) {
Device device = devices[i];
if (isNexus(device) && !isGeneric(device)) {
labels[i] = getNexusLabel(device);
} else {
labels[i] = getGenericLabel(device);
}
}
mDevice.setData(devices);
mDevice.setItems(labels);
}
}
/**
* Can be called after the constructor to set the default device for this AVD.
* Useful especially for new AVDs.
* @param device
*/
public void selectInitialDevice(Device device) {
mInitWithDevice = device;
}
/**
* {@link ModifyListener} used for live-validation of the fields content.
*/
private class ValidateListener extends SelectionAdapter implements ModifyListener {
@Override
public void modifyText(ModifyEvent e) {
validatePage();
}
@Override
public void widgetSelected(SelectionEvent e) {
super.widgetSelected(e);
validatePage();
}
}
/**
* Callback when the AVD name is changed. When creating a new AVD, enables
* the force checkbox if the name is a duplicate. When editing an existing
* AVD, it's OK for the name to match the existing AVD.
*/
private class CreateNameModifyListener implements ModifyListener {
@Override
public void modifyText(ModifyEvent e) {
String name = mAvdName.getText().trim();
if (mAvdInfo == null || !name.equals(mAvdInfo.getName())) {
// Case where we're creating a new AVD or editing an existing
// one
// and the AVD name has been changed... check for name
// uniqueness.
Pair<AvdConflict, String> conflict = mAvdManager.isAvdNameConflicting(name);
if (conflict.getFirst() != AvdManager.AvdConflict.NO_CONFLICT) {
// If we're changing the state from disabled to enabled,
// make sure
// to uncheck the button, to force the user to voluntarily
// re-enforce it.
// This happens when editing an existing AVD and changing
// the name from
// the existing AVD to another different existing AVD.
if (!mForceCreation.isEnabled()) {
mForceCreation.setEnabled(true);
mForceCreation.setSelection(false);
}
} else {
mForceCreation.setEnabled(false);
mForceCreation.setSelection(false);
}
} else {
// Case where we're editing an existing AVD with the name
// unchanged.
mForceCreation.setEnabled(false);
mForceCreation.setSelection(false);
}
validatePage();
}
}
private class DeviceSelectionListener extends SelectionAdapter {
@Override
public void widgetSelected(SelectionEvent arg0) {
Device currentDevice = getSelectedDevice();
if (currentDevice != null) {
fillDeviceProperties(currentDevice);
}
toggleCameras();
validatePage();
}
}
private void fillDeviceProperties(Device device) {
Hardware hw = device.getDefaultHardware();
Long ram = hw.getRam().getSizeAsUnit(Storage.Unit.MiB);
mRam.setText(Long.toString(ram));
// Set the default VM heap size. This is based on the Android CDD minimums for each
// screen size and density.
Screen s = hw.getScreen();
ScreenSize size = s.getSize();
Density density = s.getPixelDensity();
int vmHeapSize = 32;
if (size.equals(ScreenSize.XLARGE)) {
switch (density) {
case LOW:
case MEDIUM:
vmHeapSize = 32;
break;
case TV:
case HIGH:
vmHeapSize = 64;
break;
case XHIGH:
case XXHIGH:
vmHeapSize = 128;
break;
case NODPI:
break;
}
} else {
switch (density) {
case LOW:
case MEDIUM:
vmHeapSize = 16;
break;
case TV:
case HIGH:
vmHeapSize = 32;
break;
case XHIGH:
case XXHIGH:
vmHeapSize = 64;
break;
case NODPI:
break;
}
}
mVmHeap.setText(Integer.toString(vmHeapSize));
List<Software> allSoftware = device.getAllSoftware();
if (allSoftware != null && !allSoftware.isEmpty()) {
Software first = allSoftware.get(0);
int min = first.getMinSdkLevel();;
int max = first.getMaxSdkLevel();;
for (int i = 1; i < allSoftware.size(); i++) {
min = Math.min(min, first.getMinSdkLevel());
max = Math.max(max, first.getMaxSdkLevel());
}
if (mCurrentTargets != null) {
int bestApiLevel = Integer.MAX_VALUE;
IAndroidTarget bestTarget = null;
for (IAndroidTarget target : mCurrentTargets.values()) {
if (!target.isPlatform()) {
continue;
}
int apiLevel = target.getVersion().getApiLevel();
if (apiLevel >= min && apiLevel <= max) {
if (bestTarget == null || apiLevel < bestApiLevel) {
bestTarget = target;
bestApiLevel = apiLevel;
}
}
}
if (bestTarget != null) {
selectTarget(bestTarget);
reloadAbiTypeCombo();
}
}
}
}
private void toggleCameras() {
mFrontCamera.setEnabled(false);
mBackCamera.setEnabled(false);
Device d = getSelectedDevice();
if (d != null) {
for (Camera c : d.getDefaultHardware().getCameras()) {
if (CameraLocation.FRONT.equals(c.getLocation())) {
mFrontCamera.setEnabled(true);
}
if (CameraLocation.BACK.equals(c.getLocation())) {
mBackCamera.setEnabled(true);
}
}
}
}
private void reloadTargetCombo() {
String selected = null;
int index = mTarget.getSelectionIndex();
if (index >= 0) {
selected = mTarget.getItem(index);
}
mCurrentTargets.clear();
mTarget.removeAll();
boolean found = false;
index = -1;
List<IAndroidTarget> targetData = new ArrayList<IAndroidTarget>();
SdkManager sdkManager = mAvdManager.getSdkManager();
if (sdkManager != null) {
for (IAndroidTarget target : sdkManager.getTargets()) {
String name;
if (target.isPlatform()) {
name = String.format("%s - API Level %s",
target.getName(),
target.getVersion().getApiString());
} else {
name = String.format("%s (%s) - API Level %s",
target.getName(),
target.getVendor(),
target.getVersion().getApiString());
}
mCurrentTargets.put(name, target);
mTarget.add(name);
targetData.add(target);
if (!found) {
index++;
found = name.equals(selected);
}
}
}
mTarget.setEnabled(mCurrentTargets.size() > 0);
mTarget.setData(targetData.toArray(new IAndroidTarget[targetData.size()]));
if (found) {
mTarget.select(index);
}
}
private void selectTarget(IAndroidTarget target) {
IAndroidTarget[] targets = (IAndroidTarget[]) mTarget.getData();
if (targets != null) {
for (int i = 0; i < targets.length; i++) {
if (target == targets[i]) {
mTarget.select(i);
break;
}
}
}
}
@SuppressWarnings("unused")
@Deprecated // FIXME unused, cleanup later
private IAndroidTarget getSelectedTarget() {
IAndroidTarget[] targets = (IAndroidTarget[]) mTarget.getData();
int index = mTarget.getSelectionIndex();
if (targets != null && index != -1 && index < targets.length) {
return targets[index];
}
return null;
}
/**
* Reload all the abi types in the selection list
*/
private void reloadAbiTypeCombo() {
String selected = null;
boolean found = false;
int index = mTarget.getSelectionIndex();
if (index >= 0) {
String targetName = mTarget.getItem(index);
IAndroidTarget target = mCurrentTargets.get(targetName);
ISystemImage[] systemImages = getSystemImages(target);
mAbi.setEnabled(systemImages.length > 1);
// If user explicitly selected an ABI before, preserve that option
// If user did not explicitly select before (only one option before)
// force them to select
index = mAbi.getSelectionIndex();
if (index >= 0 && mAbi.getItemCount() > 1) {
selected = mAbi.getItem(index);
}
mAbi.removeAll();
int i;
for (i = 0; i < systemImages.length; i++) {
String prettyAbiType = AvdInfo.getPrettyAbiType(systemImages[i].getAbiType());
mAbi.add(prettyAbiType);
if (!found) {
found = prettyAbiType.equals(selected);
if (found) {
mAbi.select(i);
}
}
}
mHaveSystemImage = systemImages.length > 0;
if (!mHaveSystemImage) {
mAbi.add("No system images installed for this target.");
mAbi.select(0);
} else if (systemImages.length == 1) {
mAbi.select(0);
}
}
}
/**
* Enable or disable the sd card widgets.
*
* @param sizeMode if true the size-based widgets are to be enabled, and the
* file-based ones disabled.
*/
private void enableSdCardWidgets(boolean sizeMode) {
mSdCardSize.setEnabled(sizeMode);
mSdCardSizeCombo.setEnabled(sizeMode);
mSdCardFile.setEnabled(!sizeMode);
mBrowseSdCard.setEnabled(!sizeMode);
}
private void onBrowseSdCard() {
FileDialog dlg = new FileDialog(getContents().getShell(), SWT.OPEN);
dlg.setText("Choose SD Card image file.");
String fileName = dlg.open();
if (fileName != null) {
mSdCardFile.setText(fileName);
}
}
@Override
public void okPressed() {
if (createAvd()) {
super.okPressed();
}
}
private void validatePage() {
String error = null;
String warning = null;
boolean valid = true;
if (mAvdName.getText().isEmpty()) {
error = "AVD Name cannot be empty";
setPageValid(false, error, warning);
return;
}
String avdName = mAvdName.getText();
if (!AvdManager.RE_AVD_NAME.matcher(avdName).matches()) {
error = String.format(
"AVD name '%1$s' contains invalid characters.\nAllowed characters are: %2$s",
avdName, AvdManager.CHARS_AVD_NAME);
setPageValid(false, error, warning);
return;
}
if (mDevice.getSelectionIndex() < 0) {
setPageValid(false, error, warning);
return;
}
if (mTarget.getSelectionIndex() < 0 ||
!mHaveSystemImage || mAbi.getSelectionIndex() < 0) {
setPageValid(false, error, warning);
return;
}
if (mRam.getText().isEmpty()) {
setPageValid(false, error, warning);
return;
}
if (mVmHeap.getText().isEmpty()) {
setPageValid(false, error, warning);
return;
}
if (mDataPartition.getText().isEmpty() || mDataPartitionSize.getSelectionIndex() < 0) {
error = "Invalid Data partition size.";
setPageValid(false, error, warning);
return;
}
// validate sdcard size or file
if (mSdCardSizeRadio.getSelection()) {
if (!mSdCardSize.getText().isEmpty() && mSdCardSizeCombo.getSelectionIndex() >= 0) {
try {
long sdSize = Long.parseLong(mSdCardSize.getText());
int sizeIndex = mSdCardSizeCombo.getSelectionIndex();
if (sizeIndex >= 0) {
// index 0 shifts by 10 (1024=K), index 1 by 20, etc.
sdSize <<= 10 * (1 + sizeIndex);
}
if (sdSize < AvdManager.SDCARD_MIN_BYTE_SIZE ||
sdSize > AvdManager.SDCARD_MAX_BYTE_SIZE) {
valid = false;
error = "SD Card size is invalid. Range is 9 MiB..1023 GiB.";
}
} catch (NumberFormatException e) {
valid = false;
error = " SD Card size must be a valid integer between 9 MiB and 1023 GiB";
}
}
} else {
if (mSdCardFile.getText().isEmpty() || !new File(mSdCardFile.getText()).isFile()) {
valid = false;
error = "SD Card path isn't valid.";
}
}
if (!valid) {
setPageValid(valid, error, warning);
return;
}
if (mForceCreation.isEnabled() && !mForceCreation.getSelection()) {
valid = false;
error = String.format(
"The AVD name '%s' is already used.\n" +
"Check \"Override the existing AVD\" to delete the existing one.",
mAvdName.getText());
}
if (mAvdInfo != null && !mAvdInfo.getName().equals(mAvdName.getText())) {
warning = String.format("The AVD '%1$s' will be duplicated into '%2$s'.",
mAvdInfo.getName(),
mAvdName.getText());
}
if (mGpuEmulation.getSelection() && mSnapshot.getSelection()) {
valid = false;
error = "GPU Emulation and Snapshot cannot be used simultaneously";
}
setPageValid(valid, error, warning);
return;
}
private void setPageValid(boolean valid, String error, String warning) {
mOkButton.setEnabled(valid);
if (error != null) {
mStatusIcon.setImage(mImageFactory.getImageByName("reject_icon16.png")); //$NON-NLS-1$
mStatusLabel.setText(error);
} else if (warning != null) {
mStatusIcon.setImage(mImageFactory.getImageByName("warning_icon16.png")); //$NON-NLS-1$
mStatusLabel.setText(warning);
} else {
mStatusIcon.setImage(null);
mStatusLabel.setText(" \n "); //$NON-NLS-1$
}
mStatusComposite.pack(true);
}
private boolean createAvd() {
String avdName = mAvdName.getText();
if (avdName == null || avdName.isEmpty()) {
return false;
}
String targetName = mTarget.getItem(mTarget.getSelectionIndex());
IAndroidTarget target = mCurrentTargets.get(targetName);
if (target == null) {
return false;
}
// get the abi type
String abiType = SdkConstants.ABI_ARMEABI;
ISystemImage[] systemImages = getSystemImages(target);
if (systemImages.length > 0) {
int abiIndex = mAbi.getSelectionIndex();
if (abiIndex >= 0) {
String prettyname = mAbi.getItem(abiIndex);
// Extract the abi type
int firstIndex = prettyname.indexOf("(");
int lastIndex = prettyname.indexOf(")");
abiType = prettyname.substring(firstIndex + 1, lastIndex);
}
}
// get the SD card data from the UI.
String sdName = null;
if (mSdCardSizeRadio.getSelection()) {
// size mode
String value = mSdCardSize.getText().trim();
if (value.length() > 0) {
sdName = value;
// add the unit
switch (mSdCardSizeCombo.getSelectionIndex()) {
case 0:
sdName += "K"; //$NON-NLS-1$
break;
case 1:
sdName += "M"; //$NON-NLS-1$
break;
case 2:
sdName += "G"; //$NON-NLS-1$
break;
default:
// shouldn't be here
assert false;
}
}
} else {
// file mode.
sdName = mSdCardFile.getText().trim();
}
// Get the device
Device device = getSelectedDevice();
if (device == null) {
return false;
}
Screen s = device.getDefaultHardware().getScreen();
String skinName = s.getXDimension() + "x" + s.getYDimension();
ILogger log = mSdkLog;
if (log == null || log instanceof MessageBoxLog) {
// If the current logger is a message box, we use our own (to make sure
// to display errors right away and customize the title).
log = new MessageBoxLog(
String.format("Result of creating AVD '%s':", avdName),
getContents().getDisplay(),
false /* logErrorsOnly */);
}
Map<String, String> hwProps = DeviceManager.getHardwareProperties(device);
if (mGpuEmulation.getSelection()) {
hwProps.put(AvdManager.AVD_INI_GPU_EMULATION, HardwareProperties.BOOLEAN_YES);
}
File avdFolder = null;
try {
avdFolder = AvdInfo.getDefaultAvdFolder(mAvdManager, avdName);
} catch (AndroidLocationException e) {
return false;
}
// Although the device has this information, some devices have more RAM than we'd want to
// allocate to an emulator.
hwProps.put(AvdManager.AVD_INI_RAM_SIZE, mRam.getText());
hwProps.put(AvdManager.AVD_INI_VM_HEAP_SIZE, mVmHeap.getText());
String suffix;
switch (mDataPartitionSize.getSelectionIndex()) {
case 0:
suffix = "M";
break;
case 1:
suffix = "G";
break;
default:
suffix = "K";
}
hwProps.put(AvdManager.AVD_INI_DATA_PARTITION_SIZE, mDataPartition.getText()+suffix);
hwProps.put(HardwareProperties.HW_KEYBOARD,
mKeyboard.getSelection() ?
HardwareProperties.BOOLEAN_YES : HardwareProperties.BOOLEAN_NO);
hwProps.put(AvdManager.AVD_INI_SKIN_DYNAMIC,
mSkin.getSelection() ?
HardwareProperties.BOOLEAN_YES : HardwareProperties.BOOLEAN_NO);
if (mFrontCamera.isEnabled()) {
hwProps.put(AvdManager.AVD_INI_CAMERA_FRONT,
mFrontCamera.getText().toLowerCase());
}
if (mBackCamera.isEnabled()) {
hwProps.put(AvdManager.AVD_INI_CAMERA_BACK,
mBackCamera.getText().toLowerCase());
}
if (sdName != null) {
hwProps.put(HardwareProperties.HW_SDCARD, HardwareProperties.BOOLEAN_YES);
}
AvdInfo avdInfo = mAvdManager.createAvd(avdFolder,
avdName,
target,
abiType,
skinName,
sdName,
hwProps,
mSnapshot.getSelection(),
mForceCreation.getSelection(),
mAvdInfo != null, // edit existing
log);
mCreatedAvd = avdInfo;
boolean success = avdInfo != null;
if (log instanceof MessageBoxLog) {
((MessageBoxLog) log).displayResult(success);
}
return success;
}
private void fillExistingAvdInfo(AvdInfo avd) {
mAvdName.setText(avd.getName());
selectDevice(avd.getDeviceManufacturer(), avd.getDeviceName());
toggleCameras();
IAndroidTarget target = avd.getTarget();
if (target != null && !mCurrentTargets.isEmpty()) {
// Try to select the target in the target combo.
// This will fail if the AVD needs to be repaired.
//
// This is a linear search but the list is always
// small enough and we only do this once.
int n = mTarget.getItemCount();
for (int i = 0; i < n; i++) {
if (target.equals(mCurrentTargets.get(mTarget.getItem(i)))) {
mTarget.select(i);
reloadAbiTypeCombo();
break;
}
}
}
ISystemImage[] systemImages = getSystemImages(target);
if (target != null && systemImages.length > 0) {
mAbi.setEnabled(systemImages.length > 1);
String abiType = AvdInfo.getPrettyAbiType(avd.getAbiType());
int n = mAbi.getItemCount();
for (int i = 0; i < n; i++) {
if (abiType.equals(mAbi.getItem(i))) {
mAbi.select(i);
break;
}
}
}
Map<String, String> props = avd.getProperties();
if (props != null) {
String snapshots = props.get(AvdManager.AVD_INI_SNAPSHOT_PRESENT);
if (snapshots != null && snapshots.length() > 0) {
mSnapshot.setSelection(snapshots.equals("true"));
}
String gpuEmulation = props.get(AvdManager.AVD_INI_GPU_EMULATION);
mGpuEmulation.setSelection(gpuEmulation != null &&
gpuEmulation.equals(HardwareProperties.BOOLEAN_VALUES[0]));
String sdcard = props.get(AvdManager.AVD_INI_SDCARD_PATH);
if (sdcard != null && sdcard.length() > 0) {
enableSdCardWidgets(false);
mSdCardSizeRadio.setSelection(false);
mSdCardFileRadio.setSelection(true);
mSdCardFile.setText(sdcard);
}
String ramSize = props.get(AvdManager.AVD_INI_RAM_SIZE);
if (ramSize != null) {
mRam.setText(ramSize);
}
String vmHeapSize = props.get(AvdManager.AVD_INI_VM_HEAP_SIZE);
if (vmHeapSize != null) {
mVmHeap.setText(vmHeapSize);
}
String dataPartitionSize = props.get(AvdManager.AVD_INI_DATA_PARTITION_SIZE);
if (dataPartitionSize != null) {
mDataPartition.setText(
dataPartitionSize.substring(0, dataPartitionSize.length() - 1));
switch (dataPartitionSize.charAt(dataPartitionSize.length() - 1)) {
case 'M':
mDataPartitionSize.select(0);
break;
case 'G':
mDataPartitionSize.select(1);
break;
default:
mDataPartitionSize.select(-1);
}
}
mKeyboard.setSelection(
props.get(HardwareProperties.HW_KEYBOARD) == HardwareProperties.BOOLEAN_YES);
mSkin.setSelection(
props.get(AvdManager.AVD_INI_SKIN_DYNAMIC) == HardwareProperties.BOOLEAN_YES);
String cameraFront = props.get(AvdManager.AVD_INI_CAMERA_FRONT);
if (cameraFront != null) {
String[] items = mFrontCamera.getItems();
for (int i = 0; i < items.length; i++) {
if (items[i].toLowerCase().equals(cameraFront)) {
mFrontCamera.select(i);
break;
}
}
}
String cameraBack = props.get(AvdManager.AVD_INI_CAMERA_BACK);
if (cameraBack != null) {
String[] items = mBackCamera.getItems();
for (int i = 0; i < items.length; i++) {
if (items[i].toLowerCase().equals(cameraBack)) {
mBackCamera.select(i);
break;
}
}
}
sdcard = props.get(AvdManager.AVD_INI_SDCARD_SIZE);
if (sdcard != null && sdcard.length() > 0) {
String[] values = new String[2];
long sdcardSize = AvdManager.parseSdcardSize(sdcard, values);
if (sdcardSize != AvdManager.SDCARD_NOT_SIZE_PATTERN) {
enableSdCardWidgets(true);
mSdCardFileRadio.setSelection(false);
mSdCardSizeRadio.setSelection(true);
mSdCardSize.setText(values[0]);
String suffix = values[1];
int n = mSdCardSizeCombo.getItemCount();
for (int i = 0; i < n; i++) {
if (mSdCardSizeCombo.getItem(i).startsWith(suffix)) {
mSdCardSizeCombo.select(i);
}
}
}
}
}
}
private void fillInitialDeviceInfo(Device device) {
String name = device.getManufacturer();
if (!name.equals("Generic") && // TODO define & use constants
!name.equals("User") &&
device.getName().indexOf(name) == -1) {
name = " by " + name;
} else {
name = "";
}
name = "AVD for " + device.getName() + name;
// sanitize the name
name = name.replaceAll("[^0-9a-zA-Z_-]+", " ").trim().replaceAll("[ _]+", "_");
mAvdName.setText(name);
// Select the device
selectDevice(device);
toggleCameras();
// If there's only one target, select it by default.
// TODO: if there are more than 1 target, select the higher platform target as
// a likely default.
if (mTarget.getItemCount() == 1) {
mTarget.select(0);
reloadAbiTypeCombo();
}
fillDeviceProperties(device);
}
/**
* Returns the list of system images of a target.
* <p/>
* If target is null, returns an empty list. If target is an add-on with no
* system images, return the list from its parent platform.
*
* @param target An IAndroidTarget. Can be null.
* @return A non-null ISystemImage array. Can be empty.
*/
private ISystemImage[] getSystemImages(IAndroidTarget target) {
if (target != null) {
ISystemImage[] images = target.getSystemImages();
if ((images == null || images.length == 0) && !target.isPlatform()) {
// If an add-on does not provide any system images, use the ones
// from the parent.
images = target.getParent().getSystemImages();
}
if (images != null) {
return images;
}
}
return new ISystemImage[0];
}
// Code copied from DeviceMenuListener in ADT; unify post release
private static final String NEXUS = "Nexus"; //$NON-NLS-1$
private static final String GENERIC = "Generic"; //$NON-NLS-1$
private static Pattern PATTERN = Pattern.compile(
"(\\d+\\.?\\d*)in (.+?)( \\(.*Nexus.*\\))?"); //$NON-NLS-1$
private static int nexusRank(Device device) {
String name = device.getName();
if (name.endsWith(" One")) { //$NON-NLS-1$
return 1;
}
if (name.endsWith(" S")) { //$NON-NLS-1$
return 2;
}
if (name.startsWith("Galaxy")) { //$NON-NLS-1$
return 3;
}
if (name.endsWith(" 7")) { //$NON-NLS-1$
return 4;
}
if (name.endsWith(" 10")) { //$NON-NLS-1$
return 5;
}
if (name.endsWith(" 4")) { //$NON-NLS-1$
return 6;
}
return 7;
}
private static boolean isGeneric(Device device) {
return device.getManufacturer().equals(GENERIC);
}
private static boolean isNexus(Device device) {
return device.getName().contains(NEXUS);
}
private static String getGenericLabel(Device d) {
// * Replace "'in'" with '"' (e.g. 2.7" QVGA instead of 2.7in QVGA)
// * Use the same precision for all devices (all but one specify decimals)
// * Add some leading space such that the dot ends up roughly in the
// same space
// * Add in screen resolution and density
String name = d.getName();
if (name.equals("3.7 FWVGA slider")) { //$NON-NLS-1$
// Fix metadata: this one entry doesn't have "in" like the rest of them
name = "3.7in FWVGA slider"; //$NON-NLS-1$
}
Matcher matcher = PATTERN.matcher(name);
if (matcher.matches()) {
String size = matcher.group(1);
String n = matcher.group(2);
int dot = size.indexOf('.');
if (dot == -1) {
size = size + ".0";
dot = size.length() - 2;
}
for (int i = 0; i < 2 - dot; i++) {
size = ' ' + size;
}
name = size + "\" " + n;
}
return String.format(java.util.Locale.US, "%1$s (%2$s)", name,
getResolutionString(d));
}
private static String getNexusLabel(Device d) {
String name = d.getName();
Screen screen = d.getDefaultHardware().getScreen();
float length = (float) screen.getDiagonalLength();
return String.format(java.util.Locale.US, "%1$s (%3$s\", %2$s)",
name, getResolutionString(d), Float.toString(length));
}
@Nullable
private static String getResolutionString(Device device) {
Screen screen = device.getDefaultHardware().getScreen();
return String.format(java.util.Locale.US,
"%1$d \u00D7 %2$d: %3$s", // U+00D7: Unicode multiplication sign
screen.getXDimension(),
screen.getYDimension(),
screen.getPixelDensity().getResourceValue());
}
}