OSDN Git Service

am ca4603db: SDK: fix NPE in AVD Manager > Start when skin.path is missing.
[android-x86/development.git] / tools / sdkmanager / libs / sdkuilib / src / com / android / sdkuilib / internal / widgets / AvdStartDialog.java
1 /*
2  * Copyright (C) 2009 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 package com.android.sdkuilib.internal.widgets;
18
19 import com.android.sdklib.internal.avd.AvdManager;
20 import com.android.sdklib.internal.avd.AvdManager.AvdInfo;
21 import com.android.sdkuilib.internal.repository.SettingsController;
22 import com.android.sdkuilib.ui.GridDialog;
23
24 import org.eclipse.jface.dialogs.IDialogConstants;
25 import org.eclipse.jface.window.Window;
26 import org.eclipse.swt.SWT;
27 import org.eclipse.swt.events.ModifyEvent;
28 import org.eclipse.swt.events.ModifyListener;
29 import org.eclipse.swt.events.SelectionAdapter;
30 import org.eclipse.swt.events.SelectionEvent;
31 import org.eclipse.swt.events.VerifyEvent;
32 import org.eclipse.swt.events.VerifyListener;
33 import org.eclipse.swt.layout.GridData;
34 import org.eclipse.swt.layout.GridLayout;
35 import org.eclipse.swt.widgets.Button;
36 import org.eclipse.swt.widgets.Composite;
37 import org.eclipse.swt.widgets.Control;
38 import org.eclipse.swt.widgets.Group;
39 import org.eclipse.swt.widgets.Label;
40 import org.eclipse.swt.widgets.Shell;
41 import org.eclipse.swt.widgets.Text;
42
43 import java.awt.Toolkit;
44 import java.io.BufferedReader;
45 import java.io.File;
46 import java.io.FileReader;
47 import java.io.IOException;
48 import java.util.HashMap;
49 import java.util.Map;
50 import java.util.regex.Matcher;
51 import java.util.regex.Pattern;
52
53 /**
54  * Dialog dealing with emulator launch options. The following options are supported:
55  * <ul>
56  * <li>-wipe-data</li>
57  * <li>-scale</li>
58  * </ul>
59  *
60  * Values are stored (in the class as static field) to be reused while the app is still running.
61  * The Monitor dpi is stored in the settings if availabe.
62  */
63 final class AvdStartDialog extends GridDialog {
64     // static field to reuse values during the same session.
65     private static boolean sWipeData = false;
66     private static int sMonitorDpi = 72; // used if there's no setting controller.
67     private static final Map<String, String> sSkinScaling = new HashMap<String, String>();
68
69     private static final Pattern sScreenSizePattern = Pattern.compile("\\d*(\\.\\d?)?");
70
71     private final AvdInfo mAvd;
72     private final String mSdkLocation;
73     private final SettingsController mSettingsController;
74
75     private Text mScreenSize;
76     private Text mMonitorDpi;
77     private Button mScaleButton;
78
79     private float mScale = 0.f;
80     private boolean mWipeData = false;
81     private int mDensity = 160; // medium density
82     private int mSize1 = -1;
83     private int mSize2 = -1;
84     private String mSkinDisplay;
85     private boolean mEnableScaling = true;
86     private Label mScaleField;
87
88     AvdStartDialog(Shell parentShell, AvdInfo avd, String sdkLocation,
89             SettingsController settingsController) {
90         super(parentShell, 2, false);
91         mAvd = avd;
92         mSdkLocation = sdkLocation;
93         mSettingsController = settingsController;
94         if (mAvd == null) {
95             throw new IllegalArgumentException("avd cannot be null");
96         }
97         if (mSdkLocation == null) {
98             throw new IllegalArgumentException("sdkLocation cannot be null");
99         }
100
101         computeSkinData();
102     }
103
104     public boolean getWipeData() {
105         return mWipeData;
106     }
107
108     /**
109      * Returns the scaling factor, or 0.f if none are set.
110      */
111     public float getScale() {
112         return mScale;
113     }
114
115     @Override
116     public void createDialogContent(final Composite parent) {
117         GridData gd;
118
119         Label l = new Label(parent, SWT.NONE);
120         l.setText("Skin:");
121
122         l = new Label(parent, SWT.NONE);
123         l.setText(mSkinDisplay == null ? "None" : mSkinDisplay);
124         l.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
125
126         l = new Label(parent, SWT.NONE);
127         l.setText("Density:");
128
129         l = new Label(parent, SWT.NONE);
130         l.setText(getDensityText());
131         l.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
132
133         mScaleButton = new Button(parent, SWT.CHECK);
134         mScaleButton.setText("Scale display to real size");
135         mScaleButton.setEnabled(mEnableScaling);
136         boolean defaultState = mEnableScaling && sSkinScaling.get(mAvd.getName()) != null;
137         mScaleButton.setSelection(defaultState);
138         mScaleButton.setLayoutData(gd = new GridData(GridData.FILL_HORIZONTAL));
139         gd.horizontalSpan = 2;
140         final Group scaleGroup = new Group(parent, SWT.NONE);
141         scaleGroup.setLayoutData(gd = new GridData(GridData.FILL_HORIZONTAL));
142         gd.horizontalIndent = 30;
143         gd.horizontalSpan = 2;
144         scaleGroup.setLayout(new GridLayout(3, false));
145
146         l = new Label(scaleGroup, SWT.NONE);
147         l.setText("Screen Size (in):");
148         mScreenSize = new Text(scaleGroup, SWT.BORDER);
149         mScreenSize.setText(getScreenSize());
150         mScreenSize.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
151         mScreenSize.addVerifyListener(new VerifyListener() {
152             public void verifyText(VerifyEvent event) {
153                 // combine the current content and the new text
154                 String text = mScreenSize.getText();
155                 text = text.substring(0, event.start) + event.text + text.substring(event.end);
156
157                 // now make sure it's a match for the regex
158                 event.doit = sScreenSizePattern.matcher(text).matches();
159             }
160         });
161         mScreenSize.addModifyListener(new ModifyListener() {
162             public void modifyText(ModifyEvent event) {
163                 onScaleChange();
164             }
165         });
166
167         // empty composite, only 2 widgets on this line.
168         new Composite(scaleGroup, SWT.NONE).setLayoutData(gd = new GridData());
169         gd.widthHint = gd.heightHint = 0;
170
171         l = new Label(scaleGroup, SWT.NONE);
172         l.setText("Monitor dpi:");
173         mMonitorDpi = new Text(scaleGroup, SWT.BORDER);
174         mMonitorDpi.setText(Integer.toString(getMonitorDpi()));
175         mMonitorDpi.setLayoutData(gd = new GridData(GridData.FILL_HORIZONTAL));
176         gd.widthHint = 50;
177         mMonitorDpi.addVerifyListener(new VerifyListener() {
178             public void verifyText(VerifyEvent event) {
179                 // check for digit only.
180                 for (int i = 0 ; i < event.text.length(); i++) {
181                     char letter = event.text.charAt(i);
182                     if (letter < '0' || letter > '9') {
183                         event.doit = false;
184                         return;
185                     }
186                 }
187             }
188         });
189         mMonitorDpi.addModifyListener(new ModifyListener() {
190             public void modifyText(ModifyEvent event) {
191                 onScaleChange();
192             }
193         });
194
195         Button button = new Button(scaleGroup, SWT.PUSH | SWT.FLAT);
196         button.setText("?");
197         button.setToolTipText("Click to figure out your monitor's pixel density");
198         button.addSelectionListener(new SelectionAdapter() {
199             @Override
200             public void widgetSelected(SelectionEvent arg0) {
201                 ResolutionChooserDialog dialog = new ResolutionChooserDialog(parent.getShell());
202                 if (dialog.open() == Window.OK) {
203                     mMonitorDpi.setText(Integer.toString(dialog.getDensity()));
204                 }
205             }
206         });
207
208         l = new Label(scaleGroup, SWT.NONE);
209         l.setText("Scale:");
210         mScaleField = new Label(scaleGroup, SWT.NONE);
211         mScaleField.setLayoutData(new GridData(GridData.FILL, GridData.CENTER,
212                 true /*grabExcessHorizontalSpace*/,
213                 true /*grabExcessVerticalSpace*/,
214                 2 /*horizontalSpan*/,
215                 1 /*verticalSpan*/));
216         setScale(mScale); // set initial text value
217
218         enableGroup(scaleGroup, defaultState);
219
220         mScaleButton.addSelectionListener(new SelectionAdapter() {
221             @Override
222             public void widgetSelected(SelectionEvent event) {
223                 boolean enabled = mScaleButton.getSelection();
224                 enableGroup(scaleGroup, enabled);
225                 if (enabled) {
226                     onScaleChange();
227                 } else {
228                     setScale(0);
229                 }
230             }
231         });
232
233         final Button wipeButton = new Button(parent, SWT.CHECK);
234         wipeButton.setLayoutData(gd = new GridData(GridData.FILL_HORIZONTAL));
235         gd.horizontalSpan = 2;
236         wipeButton.setText("Wipe user data");
237         wipeButton.setSelection(mWipeData = sWipeData);
238         wipeButton.addSelectionListener(new SelectionAdapter() {
239             @Override
240             public void widgetSelected(SelectionEvent arg0) {
241                 mWipeData = wipeButton.getSelection();
242             }
243         });
244
245         l = new Label(parent, SWT.SEPARATOR | SWT.HORIZONTAL);
246         l.setLayoutData(gd = new GridData(GridData.FILL_HORIZONTAL));
247         gd.horizontalSpan = 2;
248
249         // if the scaling is enabled by default, we must initialize the value of mScale
250         if (defaultState) {
251             onScaleChange();
252         }
253     }
254
255     /** On Windows we need to manually enable/disable the children of a group */
256     private void enableGroup(final Group group, boolean enabled) {
257         group.setEnabled(enabled);
258         for (Control c : group.getChildren()) {
259             c.setEnabled(enabled);
260         }
261     }
262
263     @Override
264     protected void configureShell(Shell newShell) {
265         super.configureShell(newShell);
266         newShell.setText("Launch Options");
267     }
268
269     @Override
270     protected Button createButton(Composite parent, int id, String label, boolean defaultButton) {
271         if (id == IDialogConstants.OK_ID) {
272             label = "Launch";
273         }
274
275         return super.createButton(parent, id, label, defaultButton);
276     }
277
278     @Override
279     protected void okPressed() {
280         // override ok to store some info
281         // first the monitor dpi
282         String dpi = mMonitorDpi.getText();
283         if (dpi.length() > 0) {
284             sMonitorDpi = Integer.parseInt(dpi);
285
286             // if there is a setting controller, save it
287             if (mSettingsController != null) {
288                 mSettingsController.setMonitorDensity(sMonitorDpi);
289                 mSettingsController.saveSettings();
290             }
291         }
292
293         // now the scale factor
294         String key = mAvd.getName();
295         sSkinScaling.remove(key);
296         if (mScaleButton.getSelection()) {
297             String size = mScreenSize.getText();
298             if (size.length() > 0) {
299                 sSkinScaling.put(key, size);
300             }
301         }
302
303         // and then the wipe-data checkbox
304         sWipeData = mWipeData;
305
306         // finally continue with the ok action
307         super.okPressed();
308     }
309
310     private void computeSkinData() {
311         Map<String, String> prop = mAvd.getProperties();
312         String dpi = prop.get("hw.lcd.density");
313         if (dpi != null && dpi.length() > 0) {
314             mDensity  = Integer.parseInt(dpi);
315         }
316
317         findSkinResolution();
318     }
319
320     private void onScaleChange() {
321         String sizeStr = mScreenSize.getText();
322         if (sizeStr.length() == 0) {
323             setScale(0);
324             return;
325         }
326
327         String dpiStr = mMonitorDpi.getText();
328         if (dpiStr.length() == 0) {
329             setScale(0);
330             return;
331         }
332
333         int dpi = Integer.parseInt(dpiStr);
334         float size = Float.parseFloat(sizeStr);
335         /*
336          * We are trying to emulate the following device:
337          * resolution: 'mSize1'x'mSize2'
338          * density: 'mDensity'
339          * screen diagonal: 'size'
340          * ontop a monitor running at 'dpi'
341          */
342         // We start by computing the screen diagonal in pixels, if the density was really mDensity
343         float diagonalPx = (float)Math.sqrt(mSize1*mSize1+mSize2*mSize2);
344         // Now we would convert this in actual inches:
345         //    diagonalIn = diagonal / mDensity
346         // the scale factor is a mix of adapting to the new density and to the new size.
347         //    (size/diagonalIn) * (dpi/mDensity)
348         // this can be simplified to:
349         setScale((size * dpi) / diagonalPx);
350     }
351
352     private void setScale(float scale) {
353         mScale = scale;
354
355         // Do the rounding exactly like AvdSelector will do.
356         scale = Math.round(scale * 100);
357         scale /=  100.f;
358
359         if (scale == 0.f) {
360             mScaleField.setText("default");  //$NON-NLS-1$
361         } else {
362             mScaleField.setText(String.format("%.2f", scale));  //$NON-NLS-1$
363         }
364     }
365
366     /**
367      * Returns the monitor dpi to start with.
368      * This can be coming from the settings, the session-based storage, or the from whatever Java
369      * can tell us.
370      */
371     private int getMonitorDpi() {
372         if (mSettingsController != null) {
373             sMonitorDpi = mSettingsController.getMonitorDensity();
374         }
375
376         if (sMonitorDpi == -1) { // first time? try to get a value
377             sMonitorDpi = Toolkit.getDefaultToolkit().getScreenResolution();
378         }
379
380         return sMonitorDpi;
381     }
382
383     /**
384      * Returns the screen size to start with.
385      * <p/>If an emulator with the same skin was already launched, scaled, the size used is reused.
386      * <p/>Otherwise the default is returned (3)
387      */
388     private String getScreenSize() {
389         String size = sSkinScaling.get(mAvd.getName());
390         if (size != null) {
391             return size;
392         }
393
394         return "3";
395     }
396
397     /**
398      * Returns a display string for the density.
399      */
400     private String getDensityText() {
401         switch (mDensity) {
402             case 120:
403                 return "Low (120)";
404             case 160:
405                 return "Medium (160)";
406             case 240:
407                 return "High (240)";
408         }
409
410         return Integer.toString(mDensity);
411     }
412
413     /**
414      * Finds the skin resolution and sets it in {@link #mSize1} and {@link #mSize2}.
415      */
416     private void findSkinResolution() {
417         Map<String, String> prop = mAvd.getProperties();
418         String skinName = prop.get(AvdManager.AVD_INI_SKIN_NAME);
419
420         if (skinName != null) {
421             Matcher m = AvdManager.NUMERIC_SKIN_SIZE.matcher(skinName);
422             if (m != null && m.matches()) {
423                 mSize1 = Integer.parseInt(m.group(1));
424                 mSize2 = Integer.parseInt(m.group(2));
425                 mSkinDisplay = skinName;
426                 mEnableScaling = true;
427                 return;
428             }
429         }
430
431         // The resolution is inside the layout file of the skin.
432         mEnableScaling = false; // default to false for now.
433
434         // path to the skin layout file.
435         String skinPath = prop.get(AvdManager.AVD_INI_SKIN_PATH);
436         if (skinPath != null) {
437             File skinFolder = new File(mSdkLocation, skinPath);
438             if (skinFolder.isDirectory()) {
439                 File layoutFile = new File(skinFolder, "layout");
440                 if (layoutFile.isFile()) {
441                     if (parseLayoutFile(layoutFile)) {
442                         mSkinDisplay = String.format("%1$s (%2$dx%3$d)", skinName, mSize1, mSize2);
443                         mEnableScaling = true;
444                     } else {
445                         mSkinDisplay = skinName;
446                     }
447                 }
448             }
449         }
450     }
451
452     /**
453      * Parses a layout file.
454      * <p/>
455      * the format is relatively easy. It's a collection of items defined as
456      * &lg;name&gt; {
457      *     &lg;content&gt;
458      * }
459      *
460      * content is either 1+ items or 1+ properties
461      * properties are defined as
462      * &lg;name&gt;&lg;whitespace&gt;&lg;value&gt;
463      *
464      * We're going to look for an item called display, with 2 properties height and width.
465      * This is very basic parser.
466      *
467      * @param layoutFile the file to parse
468      * @return true if both sizes where found.
469      */
470     private boolean parseLayoutFile(File layoutFile) {
471         try {
472             BufferedReader input = new BufferedReader(new FileReader(layoutFile));
473             String line;
474
475             while ((line = input.readLine()) != null) {
476                 // trim to remove whitespace
477                 line = line.trim();
478                 int len = line.length();
479                 if (len == 0) continue;
480
481                 // check if this is a new item
482                 if (line.charAt(len-1) == '{') {
483                     // this is the start of a node
484                     String[] tokens = line.split(" ");
485                     if ("display".equals(tokens[0])) {
486                         // this is the one we're looking for!
487                         while ((mSize1 == -1 || mSize2 == -1) &&
488                                 (line = input.readLine()) != null) {
489                             // trim to remove whitespace
490                             line = line.trim();
491                             len = line.length();
492                             if (len == 0) continue;
493
494                             if ("}".equals(line)) { // looks like we're done with the item.
495                                 break;
496                             }
497
498                             tokens = line.split(" ");
499                             if (tokens.length >= 2) {
500                                 // there can be multiple space between the name and value
501                                 // in which case we'll get an extra empty token in the middle.
502                                 if ("width".equals(tokens[0])) {
503                                     mSize1 = Integer.parseInt(tokens[tokens.length-1]);
504                                 } else if ("height".equals(tokens[0])) {
505                                     mSize2 = Integer.parseInt(tokens[tokens.length-1]);
506                                 }
507                             }
508                         }
509
510                         return mSize1 != -1 && mSize2 != -1;
511                     }
512                 }
513
514             }
515             // if it reaches here, display was not found.
516             // false is returned below.
517         } catch (IOException e) {
518             // ignore.
519         }
520
521         return false;
522     }
523 }