1/* 2 * Copyright (C) 2012 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 17package com.android.sdkuilib.internal.widgets; 18 19import com.android.annotations.Nullable; 20import com.android.resources.Density; 21import com.android.resources.Keyboard; 22import com.android.resources.KeyboardState; 23import com.android.resources.Navigation; 24import com.android.resources.NavigationState; 25import com.android.resources.ResourceEnum; 26import com.android.resources.ScreenOrientation; 27import com.android.resources.ScreenRatio; 28import com.android.resources.ScreenSize; 29import com.android.resources.TouchScreen; 30import com.android.sdklib.devices.Abi; 31import com.android.sdklib.devices.ButtonType; 32import com.android.sdklib.devices.Camera; 33import com.android.sdklib.devices.CameraLocation; 34import com.android.sdklib.devices.Device; 35import com.android.sdklib.devices.DeviceManager; 36import com.android.sdklib.devices.Hardware; 37import com.android.sdklib.devices.Multitouch; 38import com.android.sdklib.devices.Network; 39import com.android.sdklib.devices.PowerType; 40import com.android.sdklib.devices.Screen; 41import com.android.sdklib.devices.ScreenType; 42import com.android.sdklib.devices.Sensor; 43import com.android.sdklib.devices.Software; 44import com.android.sdklib.devices.State; 45import com.android.sdklib.devices.Storage; 46import com.android.sdkuilib.internal.repository.icons.ImageFactory; 47import com.android.sdkuilib.ui.GridDialog; 48 49import org.eclipse.jface.dialogs.IDialogConstants; 50import org.eclipse.swt.SWT; 51import org.eclipse.swt.events.ModifyEvent; 52import org.eclipse.swt.events.ModifyListener; 53import org.eclipse.swt.events.SelectionAdapter; 54import org.eclipse.swt.events.SelectionEvent; 55import org.eclipse.swt.events.SelectionListener; 56import org.eclipse.swt.layout.GridData; 57import org.eclipse.swt.layout.GridLayout; 58import org.eclipse.swt.widgets.Button; 59import org.eclipse.swt.widgets.Combo; 60import org.eclipse.swt.widgets.Composite; 61import org.eclipse.swt.widgets.Control; 62import org.eclipse.swt.widgets.Group; 63import org.eclipse.swt.widgets.Label; 64import org.eclipse.swt.widgets.Shell; 65import org.eclipse.swt.widgets.Text; 66 67import java.util.List; 68 69public class DeviceCreationDialog extends GridDialog { 70 71 private static final String MANUFACTURER = "User"; 72 73 private final ImageFactory mImageFactory; 74 private final DeviceManager mManager; 75 private List<Device> mUserDevices; 76 77 private Device mDevice; 78 79 private Text mDeviceName; 80 private Text mDiagonalLength; 81 private Text mXDimension; 82 private Text mYDimension; 83 private Button mKeyboard; 84 private Button mDpad; 85 private Button mTrackball; 86 private Button mNoNav; 87 private Text mRam; 88 private Combo mRamCombo; 89 private Combo mButtons; 90 private Combo mSize; 91 private Combo mDensity; 92 private Combo mRatio; 93 private Button mAccelerometer; // hw.accelerometer 94 private Button mGyro; // hw.sensors.orientation 95 private Button mGps; // hw.sensors.gps 96 private Button mProximitySensor; // hw.sensors.proximity 97 private Button mCameraFront; 98 private Button mCameraRear; 99 private Group mStateGroup; 100 private Button mPortrait; 101 private Label mPortraitLabel; 102 private Button mPortraitNav; 103 private Button mLandscape; 104 private Label mLandscapeLabel; 105 private Button mLandscapeNav; 106 private Button mPortraitKeys; 107 private Label mPortraitKeysLabel; 108 private Button mPortraitKeysNav; 109 private Button mLandscapeKeys; 110 private Label mLandscapeKeysLabel; 111 private Button mLandscapeKeysNav; 112 113 private Button mForceCreation; 114 private Label mStatusIcon; 115 private Label mStatusLabel; 116 117 private Button mOkButton; 118 119 // The hardware instance attached to each of the states of the created 120 // device 121 private Hardware mHardware; 122 // This contains the Software for the device. Since it has no effect on the 123 // emulator whatsoever, we just use a single instance with reasonable 124 // defaults. 125 private static final Software mSoftware; 126 127 static { 128 mSoftware = new Software(); 129 mSoftware.setLiveWallpaperSupport(true); 130 mSoftware.setGlVersion("2.0"); 131 } 132 133 public DeviceCreationDialog(Shell parentShell, 134 DeviceManager manager, 135 ImageFactory imageFactory, 136 @Nullable Device device) { 137 super(parentShell, 2, false); 138 mImageFactory = imageFactory; 139 mDevice = device; 140 mManager = manager; 141 mUserDevices = mManager.getUserDevices(); 142 } 143 144 @Override 145 protected Control createContents(Composite parent) { 146 Control control = super.createContents(parent); 147 148 mOkButton = getButton(IDialogConstants.OK_ID); 149 150 if (mDevice == null) { 151 getShell().setText("Create New Device"); 152 } else { 153 if (mUserDevices.contains(mDevice)) { 154 getShell().setText("Edit Device"); 155 } else { 156 getShell().setText("Clone Device"); 157 } 158 } 159 160 validatePage(); 161 162 return control; 163 } 164 165 @Override 166 public void createDialogContent(Composite parent) { 167 168 ValidationListener validator = new ValidationListener(); 169 SizeListener sizeListener = new SizeListener(); 170 NavStateListener navListener = new NavStateListener(); 171 172 String tooltip = "Name of the new device"; 173 generateLabel("Name:", tooltip, parent); 174 mDeviceName = generateText(parent, tooltip, new CreateNameModifyListener()); 175 176 tooltip = "Diagonal length of the screen in inches"; 177 generateLabel("Screen Size (in):", tooltip, parent); 178 mDiagonalLength = generateText(parent, tooltip, sizeListener); 179 180 tooltip = "The resolution of the device in pixels"; 181 generateLabel("Resolution:", tooltip, parent); 182 Group dimensionGroup = new Group(parent, SWT.NONE); 183 dimensionGroup.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); 184 dimensionGroup.setLayout(new GridLayout(3, false)); 185 mXDimension = generateText(dimensionGroup, tooltip, sizeListener); 186 new Label(dimensionGroup, SWT.NONE).setText("x"); 187 mYDimension = generateText(dimensionGroup, tooltip, sizeListener); 188 189 tooltip = "The screen size bucket that the device falls into"; 190 generateLabel("Size:", tooltip, parent); 191 mSize = generateCombo(parent, tooltip, ScreenSize.values(), 1, validator); 192 193 tooltip = "The aspect ratio bucket the screen falls into. A \"long\" screen is wider."; 194 generateLabel("Screen Ratio:", tooltip, parent); 195 mRatio = generateCombo(parent, tooltip, ScreenRatio.values(), 1, validator); 196 197 tooltip = "The pixel density bucket the device falls in"; 198 generateLabel("Density:", tooltip, parent); 199 mDensity = generateCombo(parent, tooltip, Density.values(), 3, validator); 200 201 generateLabel("Sensors:", "The sensors available on the device", parent); 202 Group sensorGroup = new Group(parent, SWT.NONE); 203 sensorGroup.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); 204 sensorGroup.setLayout(new GridLayout(2, false)); 205 mAccelerometer = generateButton(sensorGroup, "Accelerometer", 206 "Presence of an accelerometer", SWT.CHECK, true, validator); 207 mGyro = generateButton(sensorGroup, "Gyroscope", 208 "Presence of a gyroscope", SWT.CHECK, true, validator); 209 mGps = generateButton(sensorGroup, "GPS", "Presence of a GPS", SWT.CHECK, true, validator); 210 mProximitySensor = generateButton(sensorGroup, "Proximity Sensor", 211 "Presence of a proximity sensor", SWT.CHECK, true, validator); 212 213 generateLabel("Cameras", "The cameras available on the device", parent); 214 Group cameraGroup = new Group(parent, SWT.NONE); 215 cameraGroup.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); 216 cameraGroup.setLayout(new GridLayout(2, false)); 217 mCameraFront = generateButton(cameraGroup, "Front", "Presence of a front camera", 218 SWT.CHECK, false, validator); 219 mCameraRear = generateButton(cameraGroup, "Rear", "Presence of a rear camera", 220 SWT.CHECK, true, validator); 221 222 generateLabel("Input:", "The input hardware on the given device", parent); 223 Group inputGroup = new Group(parent, SWT.NONE); 224 inputGroup.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); 225 inputGroup.setLayout(new GridLayout(3, false)); 226 mKeyboard = generateButton(inputGroup, "Keyboard", "Presence of a hardware keyboard", 227 SWT.CHECK, false, 228 new KeyboardListener()); 229 GridData gridData = new GridData(GridData.FILL_HORIZONTAL); 230 gridData.horizontalSpan = 3; 231 mKeyboard.setLayoutData(gridData); 232 mNoNav = generateButton(inputGroup, "No Nav", "No hardware navigation", 233 SWT.RADIO, true, navListener); 234 mDpad = generateButton(inputGroup, "DPad", "The device has a DPad navigation element", 235 SWT.RADIO, false, navListener); 236 mTrackball = generateButton(inputGroup, "Trackball", 237 "The device has a trackball navigation element", SWT.RADIO, false, navListener); 238 239 tooltip = "The amount of RAM on the device"; 240 generateLabel("RAM:", tooltip, parent); 241 Group ramGroup = new Group(parent, SWT.NONE); 242 ramGroup.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); 243 ramGroup.setLayout(new GridLayout(2, false)); 244 mRam = generateText(ramGroup, tooltip, validator); 245 mRamCombo = new Combo(ramGroup, SWT.DROP_DOWN | SWT.READ_ONLY); 246 mRamCombo.setToolTipText(tooltip); 247 mRamCombo.add("MiB"); 248 mRamCombo.add("GiB"); 249 mRamCombo.select(0); 250 mRamCombo.addModifyListener(validator); 251 252 tooltip = "Type of buttons (Home, Menu, etc.) on the device. " 253 + "This can be software buttons like on the Galaxy Nexus, or hardware buttons like " 254 + "the capacitive buttons on the Nexus S."; 255 generateLabel("Buttons:", tooltip, parent); 256 mButtons = new Combo(parent, SWT.DROP_DOWN | SWT.READ_ONLY); 257 mButtons.setToolTipText(tooltip); 258 mButtons.add("Software"); 259 mButtons.add("Hardware"); 260 mButtons.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); 261 mButtons.select(0); 262 mButtons.addModifyListener(validator); 263 264 generateLabel("Device States:", "The available states for the given device", parent); 265 266 mStateGroup = new Group(parent, SWT.NONE); 267 mStateGroup.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); 268 mStateGroup.setLayout(new GridLayout(2, true)); 269 270 tooltip = "The device has a portait position with no keyboard available"; 271 mPortraitLabel = generateLabel("Portrait:", tooltip, mStateGroup); 272 gridData = new GridData(GridData.FILL_HORIZONTAL); 273 gridData.horizontalSpan = 2; 274 mPortraitLabel.setLayoutData(gridData); 275 mPortrait = generateButton(mStateGroup, "Enabled", tooltip, SWT.CHECK, true, 276 navListener); 277 mPortraitNav = generateButton(mStateGroup, "Navigation", 278 "Hardware navigation is available in this state", SWT.CHECK, true, validator); 279 mPortraitNav.setEnabled(false); 280 281 tooltip = "The device has a landscape position with no keyboard available"; 282 mLandscapeLabel = generateLabel("Landscape:", tooltip, mStateGroup); 283 gridData = new GridData(GridData.FILL_HORIZONTAL); 284 gridData.horizontalSpan = 2; 285 mLandscapeLabel.setLayoutData(gridData); 286 mLandscape = generateButton(mStateGroup, "Enabled", tooltip, SWT.CHECK, true, 287 navListener); 288 mLandscapeNav = generateButton(mStateGroup, "Navigation", 289 "Hardware navigation is available in this state", SWT.CHECK, true, validator); 290 mLandscapeNav.setEnabled(false); 291 292 tooltip = "The device has a portait position with a keyboard available"; 293 mPortraitKeysLabel = generateLabel("Portrait with keyboard:", tooltip, mStateGroup); 294 gridData = new GridData(GridData.FILL_HORIZONTAL); 295 gridData.horizontalSpan = 2; 296 mPortraitKeysLabel.setLayoutData(gridData); 297 mPortraitKeysLabel.setEnabled(false); 298 mPortraitKeys = generateButton(mStateGroup, "Enabled", tooltip, SWT.CHECK, true, 299 navListener); 300 mPortraitKeys.setEnabled(false); 301 mPortraitKeysNav = generateButton(mStateGroup, "Navigation", 302 "Hardware navigation is available in this state", SWT.CHECK, true, validator); 303 mPortraitKeysNav.setEnabled(false); 304 305 tooltip = "The device has a landscape position with the keyboard open"; 306 mLandscapeKeysLabel = generateLabel("Landscape with keyboard:", tooltip, mStateGroup); 307 gridData = new GridData(GridData.FILL_HORIZONTAL); 308 gridData.horizontalSpan = 2; 309 mLandscapeKeysLabel.setLayoutData(gridData); 310 mLandscapeKeysLabel.setEnabled(false); 311 mLandscapeKeys = generateButton(mStateGroup, "Enabled", tooltip, SWT.CHECK, true, 312 navListener); 313 mLandscapeKeys.setEnabled(false); 314 mLandscapeKeysNav = generateButton(mStateGroup, "Navigation", 315 "Hardware navigation is available in this state", SWT.CHECK, true, validator); 316 mLandscapeKeysNav.setEnabled(false); 317 318 mForceCreation = new Button(parent, SWT.CHECK); 319 mForceCreation.setText("Override the existing device with the same name"); 320 mForceCreation 321 .setToolTipText("There's already an AVD with the same name. Check this to delete it and replace it by the new AVD."); 322 mForceCreation.setLayoutData(new GridData(GridData.BEGINNING, GridData.CENTER, 323 true, false, 2, 1)); 324 mForceCreation.setEnabled(false); 325 mForceCreation.addSelectionListener(validator); 326 327 // add a separator to separate from the ok/cancel button 328 Label label = new Label(parent, SWT.SEPARATOR | SWT.HORIZONTAL); 329 label.setLayoutData(new GridData(GridData.FILL, GridData.CENTER, true, false, 3, 1)); 330 331 // add stuff for the error display 332 Composite statusComposite = new Composite(parent, SWT.NONE); 333 GridLayout gl; 334 statusComposite.setLayoutData(new GridData(GridData.FILL, GridData.CENTER, 335 true, false, 3, 1)); 336 statusComposite.setLayout(gl = new GridLayout(2, false)); 337 gl.marginHeight = gl.marginWidth = 0; 338 339 mStatusIcon = new Label(statusComposite, SWT.NONE); 340 mStatusIcon.setLayoutData(new GridData(GridData.BEGINNING, GridData.BEGINNING, 341 false, false)); 342 mStatusLabel = new Label(statusComposite, SWT.NONE); 343 mStatusLabel.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); 344 mStatusLabel.setText(""); //$NON-NLS-1$ 345 346 prefillWithDevice(mDevice); 347 348 validatePage(); 349 } 350 351 private Button generateButton(Composite parent, String text, String tooltip, int type, 352 boolean selected, SelectionListener listener) { 353 Button b = new Button(parent, type); 354 b.setText(text); 355 b.setToolTipText(tooltip); 356 b.setSelection(selected); 357 b.addSelectionListener(listener); 358 b.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); 359 return b; 360 } 361 362 /** 363 * Generates a combo widget attached to the given parent, then sets the 364 * tooltip, adds all of the {@link String}s returned by 365 * {@link ResourceEnum#getResourceValue()} for each {@link ResourceEnum}, 366 * sets the combo to the given index and adds the given 367 * {@link ModifyListener}. 368 */ 369 private Combo generateCombo(Composite parent, String tooltip, ResourceEnum[] values, 370 int selection, 371 ModifyListener validator) { 372 Combo c = new Combo(parent, SWT.DROP_DOWN | SWT.READ_ONLY); 373 c.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); 374 c.setToolTipText(tooltip); 375 for (ResourceEnum r : values) { 376 c.add(r.getResourceValue()); 377 } 378 c.select(selection); 379 c.addModifyListener(validator); 380 return c; 381 } 382 383 /** Generates a text widget with the given tooltip, parent and listener */ 384 private Text generateText(Composite parent, String tooltip, ModifyListener listener) { 385 Text t = new Text(parent, SWT.BORDER); 386 t.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); 387 t.setToolTipText(tooltip); 388 t.addModifyListener(listener); 389 return t; 390 } 391 392 /** Generates a label and attaches it to the given parent */ 393 private Label generateLabel(String text, String tooltip, Composite parent) { 394 Label label = new Label(parent, SWT.NONE); 395 label.setText(text); 396 label.setToolTipText(tooltip); 397 label.setLayoutData(new GridData(GridData.VERTICAL_ALIGN_CENTER)); 398 return label; 399 } 400 401 /** 402 * Callback when the device name is changed. Enforces that device names 403 * don't conflict with already existing devices unless we're editing that 404 * device. 405 */ 406 private class CreateNameModifyListener implements ModifyListener { 407 @Override 408 public void modifyText(ModifyEvent e) { 409 String name = mDeviceName.getText(); 410 boolean nameCollision = false; 411 for (Device d : mUserDevices) { 412 if (MANUFACTURER.equals(d.getManufacturer()) && name.equals(d.getName())) { 413 nameCollision = true; 414 break; 415 } 416 } 417 mForceCreation.setEnabled(nameCollision); 418 mForceCreation.setSelection(!nameCollision); 419 420 validatePage(); 421 } 422 } 423 424 /** 425 * Callback attached to the diagonal length and resolution text boxes. Sets 426 * the screen size and display density based on their values, then validates 427 * the page. 428 */ 429 private class SizeListener implements ModifyListener { 430 @Override 431 public void modifyText(ModifyEvent e) { 432 433 if (!mDiagonalLength.getText().isEmpty()) { 434 double diagonal = Double.parseDouble(mDiagonalLength.getText()); 435 double diagonalDp = 160.0 * diagonal; 436 437 // Set the Screen Size 438 if (diagonalDp >= 1200) { 439 mSize.select(ScreenSize.getIndex(ScreenSize.getEnum("xlarge"))); 440 } else if (diagonalDp >= 800) { 441 mSize.select(ScreenSize.getIndex(ScreenSize.getEnum("large"))); 442 } else if (diagonalDp >= 568) { 443 mSize.select(ScreenSize.getIndex(ScreenSize.getEnum("normal"))); 444 } else { 445 mSize.select(ScreenSize.getIndex(ScreenSize.getEnum("small"))); 446 } 447 if (!mXDimension.getText().isEmpty() && !mYDimension.getText().isEmpty()) { 448 449 // Set the density based on which bucket it's closest to 450 double x = Double.parseDouble(mXDimension.getText()); 451 double y = Double.parseDouble(mYDimension.getText()); 452 double dpi = Math.sqrt(x * x + y * y) / diagonal; 453 double difference = Double.MAX_VALUE; 454 Density bucket = Density.MEDIUM; 455 for (Density d : Density.values()) { 456 if (Math.abs(d.getDpiValue() - dpi) < difference) { 457 difference = Math.abs(d.getDpiValue() - dpi); 458 bucket = d; 459 } 460 } 461 mDensity.select(Density.getIndex(bucket)); 462 } 463 } 464 } 465 } 466 467 468 /** 469 * Callback attached to the keyboard checkbox.Enables / disables device 470 * states based on the keyboard presence and then validates the page. 471 */ 472 private class KeyboardListener extends SelectionAdapter { 473 @Override 474 public void widgetSelected(SelectionEvent event) { 475 super.widgetSelected(event); 476 if (mKeyboard.getSelection()) { 477 mPortraitKeys.setEnabled(true); 478 mPortraitKeysLabel.setEnabled(true); 479 mLandscapeKeys.setEnabled(true); 480 mLandscapeKeysLabel.setEnabled(true); 481 } else { 482 mPortraitKeys.setEnabled(false); 483 mPortraitKeysLabel.setEnabled(false); 484 mLandscapeKeys.setEnabled(false); 485 mLandscapeKeysLabel.setEnabled(false); 486 } 487 toggleNav(); 488 validatePage(); 489 } 490 491 } 492 493 /** 494 * Listens for changes on widgets that affect nav availability and toggles 495 * the nav checkboxes for device states based on them. 496 */ 497 private class NavStateListener extends SelectionAdapter { 498 @Override 499 public void widgetSelected(SelectionEvent event) { 500 super.widgetSelected(event); 501 toggleNav(); 502 validatePage(); 503 } 504 } 505 506 /** 507 * Method that inspects all of the relevant dialog state and enables or disables the nav 508 * elements accordingly. 509 */ 510 private void toggleNav() { 511 mPortraitNav.setEnabled(mPortrait.getSelection() && !mNoNav.getSelection()); 512 mLandscapeNav.setEnabled(mLandscape.getSelection() && !mNoNav.getSelection()); 513 mPortraitKeysNav.setEnabled(mPortraitKeys.getSelection() && mPortraitKeys.getEnabled() 514 && !mNoNav.getSelection()); 515 mLandscapeKeysNav.setEnabled(mLandscapeKeys.getSelection() 516 && mLandscapeKeys.getEnabled() && !mNoNav.getSelection()); 517 validatePage(); 518 } 519 520 /** 521 * Callback that validates the page on modification events or widget 522 * selections 523 */ 524 private class ValidationListener extends SelectionAdapter implements ModifyListener { 525 @Override 526 public void modifyText(ModifyEvent e) { 527 validatePage(); 528 } 529 530 @Override 531 public void widgetSelected(SelectionEvent e) { 532 super.widgetSelected(e); 533 validatePage(); 534 } 535 } 536 537 /** 538 * Validates all of the config options to ensure a valid device can be 539 * created from them. 540 * 541 * @return Whether the config options will result in a valid device. 542 */ 543 private boolean validatePage() { 544 boolean valid = true; 545 String error = null; 546 String warning = null; 547 setError(null); 548 549 String name = mDeviceName.getText(); 550 551 /* If we're editing / cloning a device, this will get called when the name gets pre-filled 552 * but the ok button won't be populated yet, so we need to skip the initial setting. 553 */ 554 if (mOkButton != null) { 555 if (mDevice == null) { 556 getShell().setText("Create New Device"); 557 mOkButton.setText("Create Device"); 558 } else { 559 if (mDevice.getName().equals(name)){ 560 if (mUserDevices.contains(mDevice)) { 561 getShell().setText("Edit Device"); 562 mOkButton.setText("Edit Device"); 563 } else { 564 warning = "Only user created devices are editable.\nA clone of it will be created under " + 565 "the \"User\" category."; 566 getShell().setText("Clone Device"); 567 mOkButton.setText("Clone Device"); 568 } 569 } else { 570 warning = "The device \"" + mDevice.getName() +"\" will be duplicated into\n" + 571 "\"" + name + "\" under the \"User\" category"; 572 getShell().setText("Clone Device"); 573 mOkButton.setText("Clone Device"); 574 } 575 } 576 } 577 578 if (name.isEmpty()) { 579 valid = false; 580 } 581 if (!validateFloat("Diagonal Length", mDiagonalLength.getText())) { 582 valid = false; 583 } 584 if (!validateInt("Resolution", mXDimension.getText())) { 585 valid = false; 586 } 587 if (!validateInt("Resolution", mYDimension.getText())) { 588 valid = false; 589 } 590 if (mSize.getSelectionIndex() < 0) { 591 error = "A size bucket must be selected."; 592 valid = false; 593 } 594 if (mDensity.getSelectionIndex() < 0) { 595 error = "A screen density bucket must be selected"; 596 valid = false; 597 } 598 if (mRatio.getSelectionIndex() < 0) { 599 error = "A screen ratio must be selected."; 600 valid = false; 601 } 602 if (!mNoNav.getSelection() && !mTrackball.getSelection() && !mDpad.getSelection()) { 603 error = "A mode of hardware navigation, or no navigation, has to be selected."; 604 valid = false; 605 } 606 if (!validateInt("RAM", mRam.getText())) { 607 valid = false; 608 } 609 if (mRamCombo.getSelectionIndex() < 0) { 610 error = "RAM must have a selected unit."; 611 valid = false; 612 } 613 if (mButtons.getSelectionIndex() < 0) { 614 error = "A button type must be selected."; 615 valid = false; 616 } 617 if (mKeyboard.getSelection()) { 618 if (!mPortraitKeys.getSelection() 619 && !mPortrait.getSelection() 620 && !mLandscapeKeys.getSelection() 621 && !mLandscape.getSelection()) { 622 error = "At least one device state must be enabled."; 623 valid = false; 624 } 625 } else { 626 if (!mPortrait.getSelection() && !mLandscape.getSelection()) { 627 error = "At least one device state must be enabled"; 628 valid = false; 629 } 630 } 631 if (mForceCreation.isEnabled() && !mForceCreation.getSelection()) { 632 error = "Name conflicts with an existing device."; 633 valid = false; 634 } 635 636 if (mOkButton != null) { 637 mOkButton.setEnabled(valid); 638 } 639 640 if (error != null) { 641 setError(error); 642 } else if (warning != null) { 643 setWarning(warning); 644 } 645 646 return valid; 647 } 648 649 /** 650 * Validates the string is a valid, positive float. If not, it sets the 651 * error at the bottom of the dialog and returns false. Note this does 652 * <b>not</b> unset the error message, it's up to the caller to unset it iff 653 * it knows there are no errors on the page. 654 */ 655 private boolean validateFloat(String box, String value) { 656 if (value == null || value.isEmpty()) { 657 return false; 658 } 659 boolean ret = true; 660 try { 661 double val = Double.parseDouble(value); 662 if (val <= 0) { 663 ret = false; 664 } 665 } catch (NumberFormatException e) { 666 ret = false; 667 } 668 if (!ret) { 669 setError(box + " must be a valid, positive number."); 670 } 671 return ret; 672 } 673 674 /** 675 * Validates the string is a valid, positive integer. If not, it sets the 676 * error at the bottom of the dialog and returns false. Note this does 677 * <b>not</b> unset the error message, it's up to the caller to unset it iff 678 * it knows there are no errors on the page. 679 */ 680 private boolean validateInt(String box, String value) { 681 if (value == null || value.isEmpty()) { 682 return false; 683 } 684 boolean ret = true; 685 try { 686 int val = Integer.parseInt(value); 687 if (val <= 0) { 688 ret = false; 689 } 690 } catch (NumberFormatException e) { 691 ret = false; 692 } 693 694 if (!ret) { 695 setError(box + " must be a valid, positive integer."); 696 } 697 698 return ret; 699 } 700 701 /** 702 * Sets the error to the given string. If null, removes the error message. 703 */ 704 private void setError(@Nullable String error) { 705 if (error == null) { 706 mStatusIcon.setImage(null); 707 mStatusLabel.setText(""); 708 } else { 709 mStatusIcon.setImage(mImageFactory.getImageByName("reject_icon16.png")); 710 mStatusLabel.setText(error); 711 } 712 } 713 714 /** 715 * Sets the warning message to the given string. If null, removes the 716 * warning message. 717 */ 718 private void setWarning(@Nullable String warning) { 719 if (warning == null) { 720 mStatusIcon.setImage(null); 721 mStatusLabel.setText(""); 722 } else { 723 mStatusIcon.setImage(mImageFactory.getImageByName("warning_icon16.png")); 724 mStatusLabel.setText(warning); 725 } 726 } 727 728 /** Sets the hardware for the new device */ 729 private void prefillWithDevice(@Nullable Device device) { 730 if (device == null) { 731 732 // Setup the default hardware instance with reasonable values for 733 // the things which are configurable via this dialog. 734 mHardware = new Hardware(); 735 736 Screen s = new Screen(); 737 s.setXdpi(316); 738 s.setYdpi(316); 739 s.setMultitouch(Multitouch.JAZZ_HANDS); 740 s.setMechanism(TouchScreen.FINGER); 741 s.setScreenType(ScreenType.CAPACITIVE); 742 mHardware.setScreen(s); 743 744 mHardware.addNetwork(Network.BLUETOOTH); 745 mHardware.addNetwork(Network.WIFI); 746 mHardware.addNetwork(Network.NFC); 747 748 mHardware.addSensor(Sensor.BAROMETER); 749 mHardware.addSensor(Sensor.COMPASS); 750 mHardware.addSensor(Sensor.LIGHT_SENSOR); 751 752 mHardware.setHasMic(true); 753 mHardware.addInternalStorage(new Storage(4, Storage.Unit.GiB)); 754 mHardware.setCpu("Generic CPU"); 755 mHardware.setGpu("Generic GPU"); 756 757 mHardware.addSupportedAbi(Abi.ARMEABI); 758 mHardware.addSupportedAbi(Abi.ARMEABI_V7A); 759 mHardware.addSupportedAbi(Abi.MIPS); 760 mHardware.addSupportedAbi(Abi.X86); 761 762 mHardware.setChargeType(PowerType.BATTERY); 763 return; 764 } 765 mHardware = device.getDefaultHardware().deepCopy(); 766 mDeviceName.setText(device.getName()); 767 mForceCreation.setSelection(true); 768 Screen s = mHardware.getScreen(); 769 mDiagonalLength.setText(Double.toString(s.getDiagonalLength())); 770 mXDimension.setText(Integer.toString(s.getXDimension())); 771 mYDimension.setText(Integer.toString(s.getYDimension())); 772 String size = s.getSize().getResourceValue(); 773 for (int i = 0; i < mSize.getItemCount(); i++) { 774 if (size.equals(mSize.getItem(i))) { 775 mSize.select(i); 776 break; 777 } 778 } 779 String ratio = s.getRatio().getResourceValue(); 780 for (int i = 0; i < mRatio.getItemCount(); i++) { 781 if (ratio.equals(mRatio.getItem(i))) { 782 mRatio.select(i); 783 break; 784 } 785 } 786 String density = s.getPixelDensity().getResourceValue(); 787 for (int i = 0; i < mDensity.getItemCount(); i++) { 788 if (density.equals(mDensity.getItem(i))) { 789 mDensity.select(i); 790 break; 791 } 792 } 793 mKeyboard.setSelection(!Keyboard.NOKEY.equals(mHardware.getKeyboard())); 794 mDpad.setSelection(Navigation.DPAD.equals(mHardware.getNav())); 795 mTrackball.setSelection(Navigation.TRACKBALL.equals(mHardware.getNav())); 796 mNoNav.setSelection(Navigation.NONAV.equals(mHardware.getNav())); 797 mAccelerometer.setSelection(mHardware.getSensors().contains(Sensor.ACCELEROMETER)); 798 mGyro.setSelection(mHardware.getSensors().contains(Sensor.GYROSCOPE)); 799 mGps.setSelection(mHardware.getSensors().contains(Sensor.GPS)); 800 mProximitySensor.setSelection(mHardware.getSensors().contains(Sensor.PROXIMITY_SENSOR)); 801 mCameraFront.setSelection(false); 802 mCameraRear.setSelection(false); 803 for (Camera c : mHardware.getCameras()) { 804 if (CameraLocation.FRONT.equals(c.getLocation())) { 805 mCameraFront.setSelection(true); 806 } else if (CameraLocation.BACK.equals(c.getLocation())) { 807 mCameraRear.setSelection(true); 808 } 809 } 810 mRam.setText(Long.toString(mHardware.getRam().getSizeAsUnit(Storage.Unit.MiB))); 811 mRamCombo.select(0); 812 for (int i = 0; i < mButtons.getItemCount(); i++) { 813 if (mButtons.getItem(i).equals(mHardware.getButtonType().toString())) { 814 mButtons.select(i); 815 break; 816 } 817 } 818 819 for (State state : device.getAllStates()) { 820 Button nav = null; 821 if (state.getOrientation().equals(ScreenOrientation.PORTRAIT)) { 822 if (state.getKeyState().equals(KeyboardState.EXPOSED)) { 823 mPortraitKeys.setSelection(true); 824 nav = mPortraitKeysNav; 825 } else { 826 mPortrait.setSelection(true); 827 nav = mPortraitNav; 828 } 829 } else { 830 if (state.getKeyState().equals(KeyboardState.EXPOSED)) { 831 mLandscapeKeys.setSelection(true); 832 nav = mLandscapeKeysNav; 833 } else { 834 mLandscape.setSelection(true); 835 nav = mLandscapeNav; 836 } 837 } 838 nav.setSelection(state.getNavState().equals(NavigationState.EXPOSED) 839 && !mHardware.getNav().equals(Navigation.NONAV)); 840 } 841 } 842 843 /** 844 * If given a valid page, generates the corresponding device. The device is 845 * then added to the user device list, replacing any previous device with 846 * its given name and manufacturer, and the list is saved out to disk. 847 */ 848 @Override 849 protected void okPressed() { 850 if (validatePage()) { 851 Device.Builder builder = new Device.Builder(); 852 builder.setManufacturer("User"); 853 builder.setName(mDeviceName.getText()); 854 builder.addSoftware(mSoftware); 855 Screen s = mHardware.getScreen(); 856 double diagonal = Double.parseDouble(mDiagonalLength.getText()); 857 int x = Integer.parseInt(mXDimension.getText()); 858 int y = Integer.parseInt(mYDimension.getText()); 859 s.setDiagonalLength(diagonal); 860 s.setXDimension(x); 861 s.setYDimension(y); 862 // The diagonal DPI will be somewhere in between the X and Y dpi if 863 // they differ 864 double dpi = Math.sqrt(x * x + y * y) / diagonal; 865 s.setXdpi(dpi); 866 s.setYdpi(dpi); 867 s.setPixelDensity(Density.getEnum(mDensity.getText())); 868 s.setSize(ScreenSize.getEnum(mSize.getText())); 869 s.setRatio(ScreenRatio.getEnum(mRatio.getText())); 870 if (mAccelerometer.getSelection()) { 871 mHardware.addSensor(Sensor.ACCELEROMETER); 872 } 873 if (mGyro.getSelection()) { 874 mHardware.addSensor(Sensor.GYROSCOPE); 875 } 876 if (mGps.getSelection()) { 877 mHardware.addSensor(Sensor.GPS); 878 } 879 if (mProximitySensor.getSelection()) { 880 mHardware.addSensor(Sensor.PROXIMITY_SENSOR); 881 } 882 if (mCameraFront.getSelection()) { 883 Camera c = new Camera(); 884 c.setAutofocus(true); 885 c.setFlash(true); 886 c.setLocation(CameraLocation.FRONT); 887 mHardware.addCamera(c); 888 } 889 if (mCameraRear.getSelection()) { 890 Camera c = new Camera(); 891 c.setAutofocus(true); 892 c.setFlash(true); 893 c.setLocation(CameraLocation.BACK); 894 mHardware.addCamera(c); 895 } 896 if (mKeyboard.getSelection()) { 897 mHardware.setKeyboard(Keyboard.QWERTY); 898 } else { 899 mHardware.setKeyboard(Keyboard.NOKEY); 900 } 901 if (mDpad.getSelection()) { 902 mHardware.setNav(Navigation.DPAD); 903 } else if (mTrackball.getSelection()) { 904 mHardware.setNav(Navigation.TRACKBALL); 905 } else { 906 mHardware.setNav(Navigation.NONAV); 907 } 908 long ram = Long.parseLong(mRam.getText()); 909 Storage.Unit unit = Storage.Unit.getEnum(mRamCombo.getText()); 910 mHardware.setRam(new Storage(ram, unit)); 911 if (mButtons.getText().equals("Hardware")) { 912 mHardware.setButtonType(ButtonType.HARD); 913 } else { 914 mHardware.setButtonType(ButtonType.SOFT); 915 } 916 917 // Set the first enabled state to the default state 918 boolean defaultSelected = false; 919 if (mPortrait.getSelection()) { 920 State state = new State(); 921 state.setName("Portrait"); 922 state.setDescription("The device in portrait orientation"); 923 state.setOrientation(ScreenOrientation.PORTRAIT); 924 if (mHardware.getNav().equals(Navigation.NONAV) || !mPortraitNav.getSelection()) { 925 state.setNavState(NavigationState.HIDDEN); 926 } else { 927 state.setNavState(NavigationState.EXPOSED); 928 } 929 if (mHardware.getKeyboard().equals(Keyboard.NOKEY)) { 930 state.setKeyState(KeyboardState.SOFT); 931 } else { 932 state.setKeyState(KeyboardState.HIDDEN); 933 } 934 state.setHardware(mHardware); 935 if (!defaultSelected) { 936 state.setDefaultState(true); 937 defaultSelected = true; 938 } 939 builder.addState(state); 940 } 941 if (mLandscape.getSelection()) { 942 State state = new State(); 943 state.setName("Landscape"); 944 state.setDescription("The device in landscape orientation"); 945 state.setOrientation(ScreenOrientation.LANDSCAPE); 946 if (mHardware.getNav().equals(Navigation.NONAV) || !mLandscapeNav.getSelection()) { 947 state.setNavState(NavigationState.HIDDEN); 948 } else { 949 state.setNavState(NavigationState.EXPOSED); 950 } 951 if (mHardware.getKeyboard().equals(Keyboard.NOKEY)) { 952 state.setKeyState(KeyboardState.SOFT); 953 } else { 954 state.setKeyState(KeyboardState.HIDDEN); 955 } 956 state.setHardware(mHardware); 957 if (!defaultSelected) { 958 state.setDefaultState(true); 959 defaultSelected = true; 960 } 961 builder.addState(state); 962 } 963 if (mKeyboard.getSelection()) { 964 if (mPortraitKeys.getSelection()) { 965 State state = new State(); 966 state.setName("Portrait with keyboard"); 967 state.setDescription("The device in portrait orientation with a keyboard open"); 968 state.setOrientation(ScreenOrientation.LANDSCAPE); 969 if (mHardware.getNav().equals(Navigation.NONAV) 970 || !mPortraitKeysNav.getSelection()) { 971 state.setNavState(NavigationState.HIDDEN); 972 } else { 973 state.setNavState(NavigationState.EXPOSED); 974 } 975 state.setKeyState(KeyboardState.EXPOSED); 976 state.setHardware(mHardware); 977 if (!defaultSelected) { 978 state.setDefaultState(true); 979 defaultSelected = true; 980 } 981 builder.addState(state); 982 } 983 if (mLandscapeKeys.getSelection()) { 984 State state = new State(); 985 state.setName("Landscape with keyboard"); 986 state.setDescription("The device in landscape orientation with a keyboard open"); 987 state.setOrientation(ScreenOrientation.LANDSCAPE); 988 if (mHardware.getNav().equals(Navigation.NONAV) 989 || !mLandscapeKeysNav.getSelection()) { 990 state.setNavState(NavigationState.HIDDEN); 991 } else { 992 state.setNavState(NavigationState.EXPOSED); 993 } 994 state.setKeyState(KeyboardState.EXPOSED); 995 state.setHardware(mHardware); 996 if (!defaultSelected) { 997 state.setDefaultState(true); 998 defaultSelected = true; 999 } 1000 builder.addState(state); 1001 } 1002 } 1003 Device d = builder.build(); 1004 if (mForceCreation.isEnabled() && mForceCreation.getSelection()) { 1005 mManager.replaceUserDevice(d); 1006 } else { 1007 mManager.addUserDevice(d); 1008 } 1009 mManager.saveUserDevices(); 1010 super.okPressed(); 1011 } 1012 } 1013 1014} 1015