Merge change I8e4697e1 into eclair

* changes:
  New layout optimization tool. Run layoutopt on the command line.
diff --git a/tools/eclipse/changes.txt b/tools/eclipse/changes.txt
index 5805681..0c653c3 100644
--- a/tools/eclipse/changes.txt
+++ b/tools/eclipse/changes.txt
@@ -1,3 +1,6 @@
+0.9.4:
+- New "Create project from sample" choice in the New Project Wizard.
+
 0.9.3:
 - New wizard to create Android JUnit Test Projects.
 - New AVD wizard.
diff --git a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/configurations/FolderConfiguration.java b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/configurations/FolderConfiguration.java
index 8a12e19..ae86f48 100644
--- a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/configurations/FolderConfiguration.java
+++ b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/configurations/FolderConfiguration.java
@@ -464,11 +464,11 @@
 
     /**
      * Returns whether the configuration is a match for the given reference config.
-     * <p/>A match means that:
+     * <p/>A match means that, for each qualifier of this config
      * <ul>
-     * <li>This config does not use any qualifier not used by the reference config</li>
-     * <li>The qualifier used by this config have the same values as the qualifiers of
-     * the reference config.</li>
+     * <li>The reference config has no value set
+     * <li>or, the qualifier of the reference config is a match. Depending on the qualifier type
+     * this does not mean the same exact value.</li>
      * </ul>
      * @param referenceConfig The reference configuration to test against.
      * @return true if the configuration matches.
@@ -478,14 +478,10 @@
             ResourceQualifier testQualifier = mQualifiers[i];
             ResourceQualifier referenceQualifier = referenceConfig.mQualifiers[i];
 
-            // we only care if testQualifier is non null.
-            if (testQualifier != null) {
-                if (referenceQualifier == null) { // reference config doesn't specify anything
-                                                  // for this qualifier so we refuse it.
-                    return false;
-                } else if (testQualifier.isMatchFor(referenceQualifier) == false) {
-                    return false;
-                }
+            // it's only a non match if both qualifiers are non-null, and they don't match.
+            if (testQualifier != null && referenceQualifier != null &&
+                        testQualifier.isMatchFor(referenceQualifier) == false) {
+                return false;
             }
         }
         return true;
diff --git a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/manager/ProjectResources.java b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/manager/ProjectResources.java
index 9d715d0..9052672 100644
--- a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/manager/ProjectResources.java
+++ b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/manager/ProjectResources.java
@@ -542,66 +542,69 @@
         for (int q = 0 ; q < count ; q++) {
             // look to see if one resource has this qualifier.
             // At the same time also record the best match value for the qualifier (if applicable).
+
+            // The reference value, to find the best match.
+            // Note that this qualifier could be null. In which case any qualifier found in the
+            // possible match, will all be considered best match.
             ResourceQualifier referenceQualifier = referenceConfig.getQualifier(q);
 
-            if (referenceQualifier != null) { // no need to check if it's null, since the loop
-                                              // above will have removed the resources anyway.
-                boolean found = false;
-                ResourceQualifier bestMatch = null;
-                for (Resource res : matchingResources) {
-                    ResourceQualifier qualifier = res.getConfiguration().getQualifier(q);
-                    if (qualifier != null) {
-                        // set the flag.
-                        found = true;
+            boolean found = false;
+            ResourceQualifier bestMatch = null; // this is to store the best match.
+            for (Resource res : matchingResources) {
+                ResourceQualifier qualifier = res.getConfiguration().getQualifier(q);
+                if (qualifier != null) {
+                    // set the flag.
+                    found = true;
 
-                        // now check for a best match.
+                    // Now check for a best match. If the reference qualifier is null ,
+                    // any qualifier is a "best" match (we don't need to record all of them.
+                    // Instead the non compatible ones are removed below)
+                    if (referenceQualifier != null) {
                         if (qualifier.isBetterMatchThan(bestMatch, referenceQualifier)) {
                             bestMatch = qualifier;
                         }
                     }
                 }
+            }
 
-                // if a resources as a qualifier at the current index, remove all the resources that
-                // do not have one.
-                // If there is one, and we have a bestComparable, also check that it's equal to the
-                // best comparable.
-                if (found) {
-                    for (int i = 0 ; i < matchingResources.size(); ) {
-                        Resource res = matchingResources.get(i);
-                        ResourceQualifier qualifier = res.getConfiguration().getQualifier(q);
+            // 4. If a resources has a qualifier at the current index, remove all the resources that
+            // do not have one, or whose qualifier value does not equal the best match found above
+            // unless there's no reference qualifier, in which case they are all considered
+            // "best" match.
+            if (found) {
+                for (int i = 0 ; i < matchingResources.size(); ) {
+                    Resource res = matchingResources.get(i);
+                    ResourceQualifier qualifier = res.getConfiguration().getQualifier(q);
 
-                        if (qualifier == null) { // no qualifier? remove the resources
-                            matchingResources.remove(res);
-                        } else if (bestMatch != null && bestMatch.equals(qualifier) == false) {
-                            // if there is a best match, only accept the resource if the qualifier
-                            // has the same best value.
-                            matchingResources.remove(res);
-                        } else {
-                            i++;
-                        }
+                    if (qualifier == null) {
+                        // this resources has no qualifier of this type: rejected.
+                        matchingResources.remove(res);
+                    } else if (referenceQualifier != null && bestMatch != null &&
+                            bestMatch.equals(qualifier) == false) {
+                        // there's a reference qualifier and there is a better match for it than
+                        // this resource, so we reject it.
+                        matchingResources.remove(res);
+                    } else {
+                        // looks like we keep this resource, move on to the next one.
+                        i++;
                     }
+                }
 
-                    // at this point we may have run out of matching resources before going
-                    // through all the qualifiers.
-                    if (matchingResources.size() == 1) {
-                        return matchingResources.get(0);
-                    } else if (matchingResources.size() == 0) {
-                        return null;
-                    }
+                // at this point we may have run out of matching resources before going
+                // through all the qualifiers.
+                if (matchingResources.size() < 2) {
+                    break;
                 }
             }
         }
 
-        // went through all the qualifiers. We should not have more than one
-        switch (matchingResources.size()) {
-            case 0:
-                return null;
-            case 1:
-                return matchingResources.get(1);
-            case 2:
-                assert false;
+        // Because we accept resources whose configuration have qualifiers where the reference 
+        // configuration doesn't, we can end up with more than one match. In this case, we just
+        // take the first one.
+        if (matchingResources.size() == 0) {
+            return null;
         }
-        return null;
+        return matchingResources.get(1);
     }
 
     /**
diff --git a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newproject/NewProjectCreationPage.java b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newproject/NewProjectCreationPage.java
index 5067111..8d5cf27 100644
--- a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newproject/NewProjectCreationPage.java
+++ b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newproject/NewProjectCreationPage.java
@@ -54,6 +54,7 @@
 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.DirectoryDialog;
 import org.eclipse.swt.widgets.Event;
@@ -65,6 +66,7 @@
 import java.io.File;
 import java.io.FileFilter;
 import java.net.URI;
+import java.util.ArrayList;
 import java.util.regex.Pattern;
 
 /**
@@ -90,9 +92,12 @@
     /** Initial value for all name fields (project, activity, application, package). Used
      * whenever a value is requested before controls are created. */
     private static final String INITIAL_NAME = "";  //$NON-NLS-1$
-    /** Initial value for the Create New Project radio; False means Create From Existing would be
-     * the default.*/
+    /** Initial value for the Create New Project radio. */
     private static final boolean INITIAL_CREATE_NEW_PROJECT = true;
+    /** Initial value for the Create Project From Sample. */
+    private static final boolean INITIAL_CREATE_FROM_SAMPLE = false;
+    /** Initial value for the Create Project From Existing Source. */
+    private static final boolean INITIAL_CREATE_FROM_SOURCE = false;
     /** Initial value for the Use Default Location check box. */
     private static final boolean INITIAL_USE_DEFAULT_LOCATION = true;
     /** Initial value for the Create Activity check box. */
@@ -127,6 +132,7 @@
     private Text mActivityNameField;
     private Text mApplicationNameField;
     private Button mCreateNewProjectRadio;
+    private Button mCreateFromSampleRadio;
     private Button mUseDefaultLocation;
     private Label mLocationLabel;
     private Text mLocationPathField;
@@ -145,6 +151,10 @@
     private boolean mApplicationNameModifiedByUser;
     private boolean mInternalMinSdkVersionUpdate;
 
+    private final ArrayList<String> mSamplesPaths = new ArrayList<String>();
+    private Combo mSamplesCombo;
+
+
 
     /**
      * Creates a new project creation wizard page.
@@ -249,6 +259,12 @@
                                                   : mCreateNewProjectRadio.getSelection();
         }
 
+        /** Returns the value of the "Create from Existing Sample" radio. */
+        public boolean isCreateFromSample() {
+            return mCreateFromSampleRadio == null ? INITIAL_CREATE_FROM_SAMPLE
+                                                  : mCreateFromSampleRadio.getSelection();
+        }
+
         /** Returns the value of the "Create Activity" checkbox. */
         public boolean isCreateActivity() {
             return mCreateActivityCheck == null ? INITIAL_CREATE_ACTIVITY
@@ -330,6 +346,7 @@
 
         // Update state the first time
         enableLocationWidgets();
+        loadSamplesForTarget(null /*target*/);
 
         // Show description the first time
         setErrorMessage(null);
@@ -407,9 +424,10 @@
         mCreateNewProjectRadio = new Button(group, SWT.RADIO);
         mCreateNewProjectRadio.setText("Create new project in workspace");
         mCreateNewProjectRadio.setSelection(INITIAL_CREATE_NEW_PROJECT);
+
         Button existing_project_radio = new Button(group, SWT.RADIO);
         existing_project_radio.setText("Create project from existing source");
-        existing_project_radio.setSelection(!INITIAL_CREATE_NEW_PROJECT);
+        existing_project_radio.setSelection(INITIAL_CREATE_FROM_SOURCE);
 
         mUseDefaultLocation = new Button(group, SWT.CHECK);
         mUseDefaultLocation.setText("Use default location");
@@ -462,6 +480,33 @@
                 onOpenDirectoryBrowser();
             }
         });
+
+        mCreateFromSampleRadio = new Button(group, SWT.RADIO);
+        mCreateFromSampleRadio.setText("Create project from existing sample");
+        mCreateFromSampleRadio.setSelection(INITIAL_CREATE_FROM_SAMPLE);
+        mCreateFromSampleRadio.addSelectionListener(location_listener);
+
+        Composite samples_group = new Composite(group, SWT.NONE);
+        samples_group.setLayout(new GridLayout(2, /* num columns */
+                false /* columns of not equal size */));
+        samples_group.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
+        samples_group.setFont(parent.getFont());
+
+        new Label(samples_group, SWT.NONE).setText("Samples:");
+
+        mSamplesCombo = new Combo(samples_group, SWT.DROP_DOWN | SWT.READ_ONLY);
+        mSamplesCombo.setEnabled(false);
+        mSamplesCombo.select(0);
+        mSamplesCombo.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
+        mSamplesCombo.setToolTipText("Select a sample");
+
+        mSamplesCombo.addSelectionListener(new SelectionAdapter() {
+            @Override
+            public void widgetSelected(SelectionEvent e) {
+                onSampleSelected();
+            }
+        });
+
     }
 
     /**
@@ -625,9 +670,22 @@
         return mLocationPathField == null ? "" : mLocationPathField.getText().trim();  //$NON-NLS-1$
     }
 
-    /** Returns the current project location, depending on the Use Default Location check box. */
+    /** Returns the current selected sample path,
+     * or an empty string if there's no valid selection. */
+    private String getSelectedSamplePath() {
+        int selIndex = mSamplesCombo.getSelectionIndex();
+        if (selIndex >= 0 && selIndex < mSamplesPaths.size()) {
+            return mSamplesPaths.get(selIndex);
+        }
+        return "";
+    }
+
+    /** Returns the current project location, depending on the Use Default Location check box
+     * or the Create From Sample check box. */
     private String getProjectLocation() {
-        if (mInfo.isNewProject() && mInfo.useDefaultLocation()) {
+        if (mInfo.isCreateFromSample()) {
+            return getSelectedSamplePath();
+        } else if (mInfo.isNewProject() && mInfo.useDefaultLocation()) {
             return Platform.getLocation().toString();
         } else {
             return getLocationPathFieldValue();
@@ -681,14 +739,25 @@
     }
 
     /**
+     * A sample was selected. Update the location field, manifest and validate.
+     */
+    private void onSampleSelected() {
+        // Note that getProjectLocation() is automatically updated to use the currently
+        // selected sample. We just need to refresh the manifest data & validate.
+        extractNamesFromAndroidManifest();
+        validatePageComplete();
+    }
+
+    /**
      * Enables or disable the location widgets depending on the user selection:
      * the location path is enabled when using the "existing source" mode (i.e. not new project)
      * or in new project mode with the "use default location" turned off.
      */
     private void enableLocationWidgets() {
         boolean is_new_project = mInfo.isNewProject();
-        boolean use_default = mInfo.useDefaultLocation();
-        boolean location_enabled = !is_new_project || !use_default;
+        boolean is_create_from_sample = mInfo.isCreateFromSample();
+        boolean use_default = mInfo.useDefaultLocation() && !is_create_from_sample;
+        boolean location_enabled = (!is_new_project || !use_default) && !is_create_from_sample;
         boolean create_activity = mInfo.isCreateActivity();
 
         mUseDefaultLocation.setEnabled(is_new_project);
@@ -697,6 +766,8 @@
         mLocationPathField.setEnabled(location_enabled);
         mBrowseButton.setEnabled(location_enabled);
 
+        mSamplesCombo.setEnabled(is_create_from_sample && mSamplesPaths.size() > 0);
+
         mPackageNameField.setEnabled(is_new_project);
         mCreateActivityCheck.setEnabled(is_new_project);
         mActivityNameField.setEnabled(is_new_project & create_activity);
@@ -718,6 +789,12 @@
      * @param abs_dir A new absolute directory path or null to use the default.
      */
     private void updateLocationPathField(String abs_dir) {
+
+        // We don't touch the location path if using the "Create From Sample" mode
+        if (mInfo.isCreateFromSample()) {
+            return;
+        }
+
         boolean is_new_project = mInfo.isNewProject();
         boolean use_default = mInfo.useDefaultLocation();
         boolean custom_location = !is_new_project || !use_default;
@@ -872,6 +949,10 @@
             mMinSdkVersionField.setText(target.getVersion().getApiString());
             mInternalMinSdkVersionUpdate = false;
         }
+
+        loadSamplesForTarget(target);
+        enableLocationWidgets();
+        onSampleSelected();
     }
 
     /**
@@ -969,15 +1050,15 @@
                 String[] ids = activityName.split(AndroidConstants.RE_DOT);
                 activityName = ids[ids.length - 1];
             }
-            if (mProjectNameField.getText().length() == 0 ||
-                    !mProjectNameModifiedByUser) {
+            if (mProjectNameField.getText().length() == 0 || !mProjectNameModifiedByUser) {
                 mInternalProjectNameUpdate = true;
+                mProjectNameModifiedByUser = false;
                 mProjectNameField.setText(activityName);
                 mInternalProjectNameUpdate = false;
             }
-            if (mApplicationNameField.getText().length() == 0 ||
-                    !mApplicationNameModifiedByUser) {
+            if (mApplicationNameField.getText().length() == 0 || !mApplicationNameModifiedByUser) {
                 mInternalApplicationNameUpdate = true;
+                mApplicationNameModifiedByUser = false;
                 mApplicationNameField.setText(activityName);
                 mInternalApplicationNameUpdate = false;
             }
@@ -1004,8 +1085,7 @@
 
                 // For the project name, remove any dots
                 packageName = packageName.replace('.', '_');
-                if (mProjectNameField.getText().length() == 0 ||
-                        !mProjectNameModifiedByUser) {
+                if (mProjectNameField.getText().length() == 0 || !mProjectNameModifiedByUser) {
                     mInternalProjectNameUpdate = true;
                     mProjectNameField.setText(packageName);
                     mInternalProjectNameUpdate = false;
@@ -1015,7 +1095,8 @@
         }
 
         // Select the target matching the manifest's sdk or build properties, if any
-        boolean foundTarget = false;
+        IAndroidTarget foundTarget = null;
+        IAndroidTarget currentTarget = mInfo.getSdkTarget();
 
         ProjectProperties p = ProjectProperties.create(projectLocation, null);
         if (p != null) {
@@ -1023,33 +1104,43 @@
             p.merge(PropertyType.BUILD).merge(PropertyType.DEFAULT);
             String v = p.getProperty(ProjectProperties.PROPERTY_TARGET);
             IAndroidTarget target = Sdk.getCurrent().getTargetFromHashString(v);
-            if (target != null) {
-                mSdkTargetSelector.setSelection(target);
-                foundTarget = true;
+            // We can change the current target if:
+            // - we found a new target
+            // - there is no current target
+            // - there is a current target but the new target is not <= to the current one.
+            if (target != null &&
+                    (currentTarget == null || !target.isCompatibleBaseFor(currentTarget))) {
+                foundTarget = target;
             }
         }
 
-        if (!foundTarget && minSdkVersion != null) {
+        if (foundTarget == null && minSdkVersion != null) {
+            // Otherwise try to match the requested sdk version
             for (IAndroidTarget target : mSdkTargetSelector.getTargets()) {
-                if (target.getVersion().equals(minSdkVersion)) {
-                    mSdkTargetSelector.setSelection(target);
-                    foundTarget = true;
+                if (target != null &&
+                        target.getVersion().equals(minSdkVersion) &&
+                        (currentTarget == null || !target.isCompatibleBaseFor(currentTarget))) {
+                    foundTarget = target;
                     break;
                 }
             }
         }
 
-        if (!foundTarget) {
+        if (foundTarget == null) {
+            // Or last attemp, try to match a sample project location
             for (IAndroidTarget target : mSdkTargetSelector.getTargets()) {
-                if (projectLocation.startsWith(target.getLocation())) {
-                    mSdkTargetSelector.setSelection(target);
-                    foundTarget = true;
+                if (target != null &&
+                        projectLocation.startsWith(target.getLocation()) &&
+                        (currentTarget == null || !target.isCompatibleBaseFor(currentTarget))) {
+                    foundTarget = target;
                     break;
                 }
             }
         }
 
-        if (!foundTarget) {
+        if (foundTarget != null) {
+            mSdkTargetSelector.setSelection(foundTarget);
+        } else {
             mInternalMinSdkVersionUpdate = true;
             if (minSdkVersion != null) {
                 mMinSdkVersionField.setText(minSdkVersion);
@@ -1059,6 +1150,104 @@
     }
 
     /**
+     * Updates the list of all samples for the given target SDK.
+     * The list is stored in mSamplesPaths as absolute directory paths.
+     * The combo is recreated to match this.
+     */
+    private void loadSamplesForTarget(IAndroidTarget target) {
+
+        // Keep the name of the old selection (if there were any samples)
+        String oldChoice = null;
+        if (mSamplesPaths.size() > 0) {
+            int selIndex = mSamplesCombo.getSelectionIndex();
+            if (selIndex > -1) {
+                oldChoice = mSamplesCombo.getItem(selIndex);
+            }
+        }
+
+        // Clear all current content
+        mSamplesCombo.removeAll();
+        mSamplesPaths.clear();
+
+        if (target != null) {
+            // Get the sample root path and recompute the list of samples
+            String samplesRootPath = target.getPath(IAndroidTarget.SAMPLES);
+
+            File samplesDir = new File(samplesRootPath);
+            findSamplesManifests(samplesDir, mSamplesPaths);
+
+            if (mSamplesPaths.size() == 0) {
+                // Odd, this target has no samples. Could happen with an addon.
+                mSamplesCombo.add("This target has no samples. Please select another target.");
+                mSamplesCombo.select(0);
+                return;
+            }
+
+            // Recompute the description of each sample (the relative path
+            // to the sample root). Also try to find the old selection.
+            int selIndex = 0;
+            int i = 0;
+            int n = samplesRootPath.length();
+            for (String path : mSamplesPaths) {
+                if (path.length() > n) {
+                    path = path.substring(n);
+                    if (path.charAt(0) == File.separatorChar) {
+                        path = path.substring(1);
+                    }
+                    if (path.endsWith(File.separator)) {
+                        path = path.substring(0, path.length() - 1);
+                    }
+                    path = path.replaceAll(Pattern.quote(File.separator), " > ");
+                }
+
+                if (oldChoice != null && oldChoice.equals(path)) {
+                    selIndex = i;
+                }
+
+                mSamplesCombo.add(path);
+                i++;
+            }
+
+            mSamplesCombo.select(selIndex);
+
+        } else {
+            mSamplesCombo.add("Please select a target.");
+            mSamplesCombo.select(0);
+        }
+    }
+
+    /**
+     * Recursively find potential sample directories under the given directory.
+     * Actually lists any directory that contains an android manifest.
+     * Paths found are added the samplesPaths list.
+     */
+    private void findSamplesManifests(File samplesDir, ArrayList<String> samplesPaths) {
+        if (!samplesDir.isDirectory()) {
+            return;
+        }
+
+        for (File f : samplesDir.listFiles()) {
+            if (f.isDirectory()) {
+                // Assume this is a sample if it contains an android manifest.
+                File manifestFile = new File(f, SdkConstants.FN_ANDROID_MANIFEST_XML);
+                if (manifestFile.isFile()) {
+                    samplesPaths.add(f.getPath());
+                }
+
+                // Recurse in the project, to find embedded tests sub-projects
+                // We can however skip this recursion for known android sub-dirs that
+                // can't have projects, namely for sources, assets and resources.
+                String leaf = f.getName();
+                if (!SdkConstants.FD_SOURCES.equals(leaf) &&
+                        !SdkConstants.FD_ASSETS.equals(leaf) &&
+                        !SdkConstants.FD_RES.equals(leaf)) {
+                    findSamplesManifests(f, samplesPaths);
+                }
+            }
+        }
+    }
+
+    /**
      * Returns whether this page's controls currently all contain valid values.
      *
      * @return <code>true</code> if all controls are valid, and
@@ -1069,10 +1258,10 @@
 
         int status = validateProjectField(workspace);
         if ((status & MSG_ERROR) == 0) {
-            status |= validateLocationPath(workspace);
+            status |= validateSdkTarget();
         }
         if ((status & MSG_ERROR) == 0) {
-            status |= validateSdkTarget();
+            status |= validateLocationPath(workspace);
         }
         if ((status & MSG_ERROR) == 0) {
             status |= validatePackageField();
diff --git a/tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/widgets/AvdStartDialog.java b/tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/widgets/AvdStartDialog.java
index 227f23e..d96534b 100644
--- a/tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/widgets/AvdStartDialog.java
+++ b/tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/widgets/AvdStartDialog.java
@@ -22,6 +22,7 @@
 
 import org.eclipse.jface.dialogs.Dialog;
 import org.eclipse.jface.dialogs.IDialogConstants;
+import org.eclipse.jface.window.Window;
 import org.eclipse.swt.SWT;
 import org.eclipse.swt.events.ModifyEvent;
 import org.eclipse.swt.events.ModifyListener;
@@ -83,7 +84,7 @@
     private String mSkinDisplay;
     private boolean mEnableScaling = true;
 
-    protected AvdStartDialog(Shell parentShell, AvdInfo avd, String sdkLocation,
+    AvdStartDialog(Shell parentShell, AvdInfo avd, String sdkLocation,
             SettingsController settingsController) {
         super(parentShell);
         mAvd = avd;
@@ -111,7 +112,7 @@
     }
 
     @Override
-    protected Control createDialogArea(Composite parent) {
+    protected Control createDialogArea(final Composite parent) {
         GridData gd;
 
         // create a composite with standard margins and spacing
@@ -150,7 +151,7 @@
         scaleGroup.setLayoutData(gd = new GridData(GridData.FILL_HORIZONTAL));
         gd.horizontalIndent = 30;
         gd.horizontalSpan = 2;
-        scaleGroup.setLayout(new GridLayout(2, false));
+        scaleGroup.setLayout(new GridLayout(3, false));
 
         l = new Label(scaleGroup, SWT.NONE);
         l.setText("Screen Size (in):");
@@ -172,12 +173,16 @@
                 onScaleChange();
             }
         });
+        // empty composite, only 2 widgets on this line.
+        new Composite(scaleGroup, SWT.NONE).setLayoutData(gd = new GridData());
+        gd.widthHint = gd.heightHint = 0;
 
         l = new Label(scaleGroup, SWT.NONE);
         l.setText("Monitor dpi:");
         mMonitorDpi = new Text(scaleGroup, SWT.BORDER);
         mMonitorDpi.setText(Integer.toString(getMonitorDpi()));
-        mMonitorDpi.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
+        mMonitorDpi.setLayoutData(gd = new GridData(GridData.FILL_HORIZONTAL));
+        gd.widthHint = 50;
         mMonitorDpi.addVerifyListener(new VerifyListener() {
             public void verifyText(VerifyEvent event) {
                 // check for digit only.
@@ -196,6 +201,19 @@
             }
         });
 
+        Button button = new Button(scaleGroup, SWT.PUSH | SWT.FLAT);
+        button.setText("?");
+        button.setToolTipText("Click to figure out your monitor's pixel density");
+        button.addSelectionListener(new SelectionAdapter() {
+            @Override
+            public void widgetSelected(SelectionEvent arg0) {
+                ResolutionChooserDialog dialog = new ResolutionChooserDialog(parent.getShell());
+                if (dialog.open() == Window.OK) {
+                    mMonitorDpi.setText(Integer.toString(dialog.getDensity()));
+                }
+            }
+        });
+
         scaleGroup.setEnabled(defaultState);
 
         mScaleButton.addSelectionListener(new SelectionAdapter() {
diff --git a/tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/widgets/ResolutionChooserDialog.java b/tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/widgets/ResolutionChooserDialog.java
new file mode 100644
index 0000000..94ed3b9
--- /dev/null
+++ b/tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/widgets/ResolutionChooserDialog.java
@@ -0,0 +1,138 @@
+/*
+ * Copyright (C) 2009 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 org.eclipse.jface.dialogs.Dialog;
+import org.eclipse.jface.dialogs.IDialogConstants;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.SelectionAdapter;
+import org.eclipse.swt.events.SelectionEvent;
+import org.eclipse.swt.graphics.Rectangle;
+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.Display;
+import org.eclipse.swt.widgets.Label;
+import org.eclipse.swt.widgets.Monitor;
+import org.eclipse.swt.widgets.Shell;
+
+/**
+ * Small dialog to let a user choose a screen size (from a fixed list) and a resolution
+ * (as returned by {@link Display#getMonitors()}).
+
+ * After the dialog as returned, one can query {@link #getDensity()} to get the chosen monitor
+ * pixel density.
+ */
+class ResolutionChooserDialog extends Dialog {
+    public final static float[] MONITOR_SIZES = new float[] {
+            13.3f, 14, 15.4f, 15.6f, 17, 19, 20, 21, 24, 30,
+    };
+
+    private Button mButton;
+    private Combo mScreenSizeCombo;
+    private Combo mMonitorCombo;
+
+    private Monitor[] mMonitors;
+    private int mScreenSizeIndex = -1;
+    private int mMonitorIndex = 0;
+
+    ResolutionChooserDialog(Shell parentShell) {
+        super(parentShell);
+    }
+
+    /**
+     * Returns the pixel density of the user-chosen monitor.
+     */
+    int getDensity() {
+        float size = MONITOR_SIZES[mScreenSizeIndex];
+        Rectangle rect = mMonitors[mMonitorIndex].getBounds();
+
+        // compute the density
+        double d = Math.sqrt(rect.width * rect.width + rect.height * rect.height) / size;
+        return (int)Math.round(d);
+    }
+
+    @Override
+    protected void configureShell(Shell newShell) {
+        newShell.setText("Monitor Density");
+        super.configureShell(newShell);
+    }
+
+    @Override
+    protected Control createContents(Composite parent) {
+        Control control = super.createContents(parent);
+        mButton = getButton(IDialogConstants.OK_ID);
+        mButton.setEnabled(false);
+        return control;
+    }
+
+    @Override
+    protected Control createDialogArea(Composite parent) {
+        // create a composite with standard margins and spacing
+        Composite composite = new Composite(parent, SWT.NONE);
+        GridLayout layout = new GridLayout(2, false);
+        layout.marginHeight = convertVerticalDLUsToPixels(IDialogConstants.VERTICAL_MARGIN);
+        layout.marginWidth = convertHorizontalDLUsToPixels(IDialogConstants.HORIZONTAL_MARGIN);
+        layout.verticalSpacing = convertVerticalDLUsToPixels(IDialogConstants.VERTICAL_SPACING);
+        layout.horizontalSpacing = convertHorizontalDLUsToPixels(
+                IDialogConstants.HORIZONTAL_SPACING);
+        composite.setLayout(layout);
+        composite.setLayoutData(new GridData(GridData.FILL_BOTH));
+
+        Label l = new Label(composite, SWT.NONE);
+        l.setText("Screen Size:");
+
+        mScreenSizeCombo = new Combo(composite, SWT.DROP_DOWN | SWT.READ_ONLY);
+        for (float size : MONITOR_SIZES) {
+            if (Math.round(size) == size) {
+                mScreenSizeCombo.add(String.format("%.0f\"", size));
+            } else {
+                mScreenSizeCombo.add(String.format("%.1f\"", size));
+            }
+        }
+        mScreenSizeCombo.addSelectionListener(new SelectionAdapter() {
+            @Override
+            public void widgetSelected(SelectionEvent arg0) {
+                mScreenSizeIndex = mScreenSizeCombo.getSelectionIndex();
+                mButton.setEnabled(mScreenSizeIndex != -1);
+            }
+        });
+
+        l = new Label(composite, SWT.NONE);
+        l.setText("Resolution:");
+
+        mMonitorCombo = new Combo(composite, SWT.DROP_DOWN | SWT.READ_ONLY);
+        mMonitors = parent.getDisplay().getMonitors();
+        for (Monitor m : mMonitors) {
+            Rectangle r = m.getBounds();
+            mMonitorCombo.add(String.format("%d x %d", r.width, r.height));
+        }
+        mMonitorCombo.select(mMonitorIndex);
+        mMonitorCombo.addSelectionListener(new SelectionAdapter() {
+            @Override
+            public void widgetDefaultSelected(SelectionEvent arg0) {
+                mMonitorIndex = mMonitorCombo.getSelectionIndex();
+            }
+        });
+
+        applyDialogFont(composite);
+        return composite;
+    }
+}