/* * Copyright 2000-2012 JetBrains s.r.o. * * 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 org.jetbrains.android.run; import com.android.prefs.AndroidLocation; import com.android.sdklib.IAndroidTarget; import com.android.sdklib.ISystemImage; import com.android.sdklib.internal.avd.AvdInfo; import com.android.sdklib.internal.avd.AvdManager; import com.intellij.ide.BrowserUtil; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.fileChooser.FileChooserDescriptorFactory; import com.intellij.openapi.project.Project; import com.intellij.openapi.ui.DialogWrapper; import com.intellij.openapi.ui.Messages; import com.intellij.openapi.ui.TextFieldWithBrowseButton; import com.intellij.ui.ClickListener; import com.intellij.ui.CollectionComboBoxModel; import com.intellij.ui.ListCellRendererWrapper; import org.jetbrains.android.facet.AndroidFacet; import org.jetbrains.android.sdk.AndroidSdkData; import org.jetbrains.android.sdk.AndroidSdkUtils; import org.jetbrains.android.sdk.MessageBuildingSdkLog; import org.jetbrains.android.util.AndroidBundle; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import javax.swing.*; import java.awt.*; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.MouseEvent; import java.io.File; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import static com.android.prefs.AndroidLocation.FOLDER_AVD; /** * @author Eugene.Kudelevsky * Date: May 9, 2009 */ public class CreateAvdDialog extends DialogWrapper { private static final Logger LOG = Logger.getInstance("#org.jetbrains.android.run.CreateAvdDialog"); private JTextField myNameField; private JComboBox myTargetBox; private JComboBox mySkinField; private JPanel myPanel; private JLabel myAvdInfoLink; private JLabel myAvdInfoLabel; private JRadioButton mySdCardSizeRadioButton; private JRadioButton mySdCardFileRadioButton; private JSpinner mySdCardSizeSpinner; private JComboBox mySdCardSizeUnitCombo; private TextFieldWithBrowseButton mySdCardFileTextField; private JComboBox myTagAbiCombo; private final AvdManager myAvdManager; private AvdInfo myCreatedAvd; private final Project myProject; private static final String MB_UNIT = "MiB"; private static final String MB_SUFFIX = "M"; private static final String KB_UNIT = "KiB"; private static final String KB_SUFFIX = "K"; private static class Size { final int myWidth; final int myHeight; private Size(int width, int height) { myWidth = width; myHeight = height; } } private static final String DEFAULT_SKIN = "HVGA"; private static Map<String, Size> displaySizes; private static void initializeDisplaySizes() { // portrait is default displaySizes = new HashMap<String, Size>(); displaySizes.put("HVGA", new Size(320, 480)); displaySizes.put("QVGA", new Size(240, 320)); displaySizes.put("WQVGA", new Size(240, 400)); displaySizes.put("WQVGA400", new Size(240, 400)); displaySizes.put("WQVGA432", new Size(240, 432)); displaySizes.put("FWQVGA", new Size(240, 432)); displaySizes.put("WVGA800", new Size(480, 800)); displaySizes.put("WVGA854", new Size(480, 854)); } @NotNull private String generateAvdName() { String prefix = "MyAvd"; for (int i = 0; ; i++) { String candidate = prefix + i; if (myAvdManager.getAvd(candidate, false) == null) { return candidate; } } } @Nullable private static String getSkinInfo(@NotNull String skinName) { int index = skinName.indexOf('-'); String nameWithoutSuffix = index >= 0 ? skinName.substring(0, index) : skinName; if (displaySizes == null) { initializeDisplaySizes(); } Size size = displaySizes.get(nameWithoutSuffix); if (size != null) { if (skinName.endsWith("-L")) { return size.myHeight + "x" + size.myWidth + ", landscape"; } if (skinName.endsWith("-P")) { return size.myWidth + "x" + size.myHeight + ", portrait"; } return size.myWidth + "x" + size.myHeight; } return null; } private static boolean containsDefaultSkinWithSuffix(@NotNull File[] skins) { for (File skin : skins) { if (skin != null) { String skinName = skin.getName(); if (!skinName.equals(DEFAULT_SKIN) && skinName.startsWith(DEFAULT_SKIN)) { return true; } } } return false; } private static String getSkinPresentation(@NotNull String skinName) { String info = getSkinInfo(skinName); return info != null ? skinName + " (" + info + ')' : skinName; } public CreateAvdDialog(@NotNull Project project, @NotNull AndroidFacet facet, @NotNull AvdManager manager, boolean onlyCompatibleTargets, boolean showAvdInfo) { super(project, true); myProject = project; setTitle(AndroidBundle.message("create.avd.dialog.title")); init(); myAvdManager = manager; final AndroidSdkData sdkData = facet.getConfiguration().getAndroidSdk(); assert sdkData != null; IAndroidTarget[] targets = sdkData.getTargets(); myTargetBox.setModel(new DefaultComboBoxModel(targets)); myTargetBox.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { Object selected = myTargetBox.getSelectedItem(); mySkinField.setEnabled(selected != null); if (selected != null) { IAndroidTarget target = (IAndroidTarget)selected; List<String> skinsToAdd = new ArrayList<String>(); File[] skins = target.getSkins(); for (File skin : skins) { // skip default HVGA skin without suffix String skinName = skin.getName(); if (DEFAULT_SKIN.equals(skinName) && containsDefaultSkinWithSuffix(skins)) { continue; } skinsToAdd.add(skinName); } mySkinField.setModel(new CollectionComboBoxModel(skinsToAdd, null)); reloadAbiCombo(); } } }); mySkinField.setRenderer(new ListCellRendererWrapper() { @Override public void customize(JList list, Object value, int index, boolean selected, boolean hasFocus) { String presentation = value != null ? getSkinPresentation((String)value) : null; setText(presentation); } }); myTargetBox.setRenderer(new ListCellRendererWrapper<IAndroidTarget>() { @Override public void customize(JList list, IAndroidTarget value, int index, boolean selected, boolean hasFocus) { setText(AndroidSdkUtils.getPresentableTargetName(value)); } }); myTagAbiCombo.setRenderer(new ListCellRendererWrapper<ISystemImage>() { @Override public void customize(JList list, ISystemImage value, int index, boolean selected, boolean hasFocus) { if (value != null) { setText(AvdInfo.getPrettyAbiType(value)); } } }); IAndroidTarget target = facet.getConfiguration().getAndroidTarget(); if (target != null) { myTargetBox.setSelectedItem(target); if (onlyCompatibleTargets) { myTargetBox.setEnabled(false); } } else if (targets.length > 0) { myTargetBox.setSelectedItem(targets[0]); } myNameField.setText(generateAvdName()); final String url = "http://developer.android.com/guide/developing/tools/avd.html"; myAvdInfoLink.setText("<html>\n" + " <body>\n" + " <p style=\"margin-top: 0;\">\n" + "<a href=\"" + url + "\">More information about AVDs</a>\n" + " </p>\n" + " </body>\n" + " </html>"); myAvdInfoLink.setCursor(new Cursor(Cursor.HAND_CURSOR)); myAvdInfoLink.setVisible(showAvdInfo); myAvdInfoLabel.setVisible(showAvdInfo); new ClickListener() { @Override public boolean onClick(@NotNull MouseEvent e, int clickCount) { try { BrowserUtil.browse(url); } catch (IllegalThreadStateException ex) { /* not a problem */ } return true; } }.installOn(myAvdInfoLabel); ActionListener listener = new ActionListener() { @Override public void actionPerformed(ActionEvent e) { updateSdCardElements(); } }; mySdCardFileRadioButton.addActionListener(listener); mySdCardSizeRadioButton.addActionListener(listener); mySdCardFileRadioButton.setSelected(true); updateSdCardElements(); mySdCardSizeUnitCombo.setModel(new DefaultComboBoxModel(new Object[]{MB_UNIT, KB_UNIT})); mySdCardSizeUnitCombo.setSelectedItem(MB_UNIT); mySdCardSizeUnitCombo.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { updateSpinner(); } }); updateSpinner(); mySdCardFileTextField .addBrowseFolderListener(AndroidBundle.message("android.create.avd.dialog.sdcard.file.browser.title"), null, project, FileChooserDescriptorFactory.createSingleFileNoJarsDescriptor()); reloadAbiCombo(); } private void reloadAbiCombo() { final IAndroidTarget selectedTarget = (IAndroidTarget)myTargetBox.getSelectedItem(); if (selectedTarget != null) { final ISystemImage[] systemImages = getSystemImages(selectedTarget); myTagAbiCombo.setModel(new DefaultComboBoxModel(systemImages)); if (systemImages.length == 0) { LOG.warn("Tag/ABIs not found for target " + selectedTarget.hashString()); } } } @NotNull private static ISystemImage[] getSystemImages(@NotNull IAndroidTarget target) { ISystemImage[] images = target.getSystemImages(); if ((images == null || images.length == 0) && !target.isPlatform()) { images = target.getParent().getSystemImages(); } return images != null ? images : new ISystemImage[0]; } private void updateSpinner() { boolean mb = MB_UNIT.equals(mySdCardSizeUnitCombo.getSelectedItem()); long max = mb ? 999999999 : 999999999999L; long min = mb ? 9 : 9 * 1024; Object o = mySdCardSizeSpinner.getValue(); long value = min; if (o instanceof Number) { value = ((Number)o).longValue(); if (value < min) { value = min; } else if (value > max) { value = max; } } mySdCardSizeSpinner.setModel(new SpinnerNumberModel(new Long(value), new Long(min), new Long(max), new Long(1))); } private void updateSdCardElements() { boolean file = mySdCardFileRadioButton.isSelected(); mySdCardFileTextField.setEnabled(file); mySdCardSizeSpinner.setEnabled(!file); mySdCardSizeUnitCombo.setEnabled(!file); } @Override protected JComponent createCenterPanel() { return myPanel; } @Override public JComponent getPreferredFocusedComponent() { return myNameField; } @Override protected void doOKAction() { if (myNameField.getText().length() == 0) { Messages.showErrorDialog(myPanel, AndroidBundle.message("specify.avd.name.error")); return; } else if (myTargetBox.getSelectedItem() == null) { Messages.showErrorDialog(myPanel, AndroidBundle.message("select.target.dialog.text")); return; } String avdName = myNameField.getText(); AvdInfo info = myAvdManager.getAvd(avdName, false); if (info != null) { boolean replace = Messages .showYesNoDialog(myPanel, AndroidBundle.message("replace.avd.question", avdName), AndroidBundle.message("create.avd.dialog.title"), Messages.getQuestionIcon()) == Messages.YES; if (!replace) return; } File avdFolder; try { avdFolder = new File(AndroidLocation.getFolder() + FOLDER_AVD, avdName + AvdManager.AVD_FOLDER_EXTENSION); } catch (AndroidLocation.AndroidLocationException e) { Messages.showErrorDialog(myPanel, e.getMessage(), "Error"); return; } super.doOKAction(); IAndroidTarget selectedTarget = (IAndroidTarget)myTargetBox.getSelectedItem(); String skinName = (String) mySkinField.getSelectedItem(); ISystemImage sysImg = (ISystemImage)myTagAbiCombo.getSelectedItem(); String sdCard = getSdCardParameter(); MessageBuildingSdkLog log = new MessageBuildingSdkLog(); myCreatedAvd = myAvdManager.createAvd(avdFolder, avdName, selectedTarget, sysImg.getTag(), sysImg.getAbiType(), null /*skinFolder*/, skinName, sdCard, null, null, true, false, false, log); if (log.getErrorMessage().length() > 0) { Messages.showErrorDialog(myProject, log.getErrorMessage(), AndroidBundle.message("android.avd.error.title")); } } @Nullable private String getSdCardParameter() { if (mySdCardFileRadioButton.isSelected()) { String text = mySdCardFileTextField.getText(); return text.length() > 0 ? text : null; } String value = mySdCardSizeSpinner.getValue().toString(); if (value.length() == 0 || "0".equals(value)) { return null; } String suffix = MB_UNIT.equals(mySdCardSizeUnitCombo.getSelectedItem()) ? MB_SUFFIX : KB_SUFFIX; return value + suffix; } @Nullable public AvdInfo getCreatedAvd() { return myCreatedAvd; } @Override protected String getHelpId() { return "reference.android.createAVD"; } }