1 /* 2 * Copyright (C) 2012 The Android Open Source Project 3 * 4 * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/org/documents/epl-v10.php 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.ide.eclipse.adt.internal.editors.layout.configuration; 18 19 import static com.android.SdkConstants.ANDROID_STYLE_RESOURCE_PREFIX; 20 import static com.android.SdkConstants.PREFIX_RESOURCE_REF; 21 import static com.android.SdkConstants.STYLE_RESOURCE_PREFIX; 22 23 import com.android.annotations.NonNull; 24 import com.android.annotations.Nullable; 25 import com.android.ide.common.rendering.LayoutLibrary; 26 import com.android.ide.common.rendering.api.Capability; 27 import com.android.ide.common.resources.ResourceFolder; 28 import com.android.ide.common.resources.ResourceRepository; 29 import com.android.ide.common.resources.configuration.DensityQualifier; 30 import com.android.ide.common.resources.configuration.DeviceConfigHelper; 31 import com.android.ide.common.resources.configuration.FolderConfiguration; 32 import com.android.ide.common.resources.configuration.LayoutDirectionQualifier; 33 import com.android.ide.common.resources.configuration.LocaleQualifier; 34 import com.android.ide.common.resources.configuration.NightModeQualifier; 35 import com.android.ide.common.resources.configuration.ScreenSizeQualifier; 36 import com.android.ide.common.resources.configuration.UiModeQualifier; 37 import com.android.ide.common.resources.configuration.VersionQualifier; 38 import com.android.ide.eclipse.adt.AdtPlugin; 39 import com.android.ide.eclipse.adt.internal.editors.layout.gle2.RenderService; 40 import com.android.ide.eclipse.adt.internal.editors.manifest.ManifestInfo; 41 import com.android.ide.eclipse.adt.internal.editors.manifest.ManifestInfo.ActivityAttributes; 42 import com.android.ide.eclipse.adt.internal.preferences.AdtPrefs; 43 import com.android.ide.eclipse.adt.internal.resources.ResourceHelper; 44 import com.android.ide.eclipse.adt.internal.resources.manager.ProjectResources; 45 import com.android.ide.eclipse.adt.internal.resources.manager.ResourceManager; 46 import com.android.ide.eclipse.adt.internal.sdk.AndroidTargetData; 47 import com.android.ide.eclipse.adt.internal.sdk.Sdk; 48 import com.android.resources.Density; 49 import com.android.resources.LayoutDirection; 50 import com.android.resources.NightMode; 51 import com.android.resources.ScreenSize; 52 import com.android.resources.UiMode; 53 import com.android.sdklib.AndroidVersion; 54 import com.android.sdklib.IAndroidTarget; 55 import com.android.sdklib.devices.Device; 56 import com.android.sdklib.devices.State; 57 import com.android.utils.Pair; 58 import com.google.common.base.Objects; 59 60 import org.eclipse.core.resources.IFile; 61 import org.eclipse.core.resources.IProject; 62 import org.eclipse.core.runtime.CoreException; 63 import org.eclipse.core.runtime.QualifiedName; 64 65 import java.util.List; 66 67 /** 68 * A {@linkplain Configuration} is a selection of device, orientation, theme, 69 * etc for use when rendering a layout. 70 */ 71 public class Configuration { 72 /** The {@link FolderConfiguration} in change flags or override flags */ 73 public static final int CFG_FOLDER = 1 << 0; 74 /** The {@link Device} in change flags or override flags */ 75 public static final int CFG_DEVICE = 1 << 1; 76 /** The {@link State} in change flags or override flags */ 77 public static final int CFG_DEVICE_STATE = 1 << 2; 78 /** The theme in change flags or override flags */ 79 public static final int CFG_THEME = 1 << 3; 80 /** The locale in change flags or override flags */ 81 public static final int CFG_LOCALE = 1 << 4; 82 /** The rendering {@link IAndroidTarget} in change flags or override flags */ 83 public static final int CFG_TARGET = 1 << 5; 84 /** The {@link NightMode} in change flags or override flags */ 85 public static final int CFG_NIGHT_MODE = 1 << 6; 86 /** The {@link UiMode} in change flags or override flags */ 87 public static final int CFG_UI_MODE = 1 << 7; 88 /** The {@link UiMode} in change flags or override flags */ 89 public static final int CFG_ACTIVITY = 1 << 8; 90 91 /** References all attributes */ 92 public static final int MASK_ALL = 0xFFFF; 93 94 /** Attributes which affect which best-layout-file selection */ 95 public static final int MASK_FILE_ATTRS = 96 CFG_DEVICE|CFG_DEVICE_STATE|CFG_LOCALE|CFG_TARGET|CFG_NIGHT_MODE|CFG_UI_MODE; 97 98 /** Attributes which affect rendering appearance */ 99 public static final int MASK_RENDERING = MASK_FILE_ATTRS|CFG_THEME; 100 101 /** 102 * Setting name for project-wide setting controlling rendering target and locale which 103 * is shared for all files 104 */ 105 public final static QualifiedName NAME_RENDER_STATE = 106 new QualifiedName(AdtPlugin.PLUGIN_ID, "render"); //$NON-NLS-1$ 107 108 private final static String MARKER_FRAMEWORK = "-"; //$NON-NLS-1$ 109 private final static String MARKER_PROJECT = "+"; //$NON-NLS-1$ 110 private final static String SEP = ":"; //$NON-NLS-1$ 111 private final static String SEP_LOCALE = "-"; //$NON-NLS-1$ 112 113 @NonNull 114 protected ConfigurationChooser mConfigChooser; 115 116 /** The {@link FolderConfiguration} representing the state of the UI controls */ 117 @NonNull 118 protected final FolderConfiguration mFullConfig = new FolderConfiguration(); 119 120 /** The {@link FolderConfiguration} being edited. */ 121 @Nullable 122 protected FolderConfiguration mEditedConfig; 123 124 /** The target of the project of the file being edited. */ 125 @Nullable 126 private IAndroidTarget mTarget; 127 128 /** The theme style to render with */ 129 @Nullable 130 private String mTheme; 131 132 /** The device to render with */ 133 @Nullable 134 private Device mDevice; 135 136 /** The device state */ 137 @Nullable 138 private State mState; 139 140 /** 141 * The activity associated with the layout. This is just a cached value of 142 * the true value stored on the layout. 143 */ 144 @Nullable 145 private String mActivity; 146 147 /** The locale to use for this configuration */ 148 @NonNull 149 private Locale mLocale = Locale.ANY; 150 151 /** UI mode */ 152 @NonNull 153 private UiMode mUiMode = UiMode.NORMAL; 154 155 /** Night mode */ 156 @NonNull 157 private NightMode mNightMode = NightMode.NOTNIGHT; 158 159 /** The display name */ 160 private String mDisplayName; 161 162 /** 163 * Creates a new {@linkplain Configuration} 164 * 165 * @param chooser the associated chooser 166 */ 167 protected Configuration(@NonNull ConfigurationChooser chooser) { 168 mConfigChooser = chooser; 169 } 170 171 /** 172 * Sets the associated configuration chooser 173 * 174 * @param chooser the chooser 175 */ 176 void setChooser(@NonNull ConfigurationChooser chooser) { 177 // TODO: We should get rid of the binding between configurations 178 // and configuration choosers. This is currently needed because 179 // the choosers contain vital data such as the set of available 180 // rendering targets, the set of available locales etc, which 181 // also doesn't belong inside the configuration but is needed by it. 182 mConfigChooser = chooser; 183 } 184 185 /** 186 * Gets the associated configuration chooser 187 * 188 * @return the chooser 189 */ 190 @NonNull 191 ConfigurationChooser getChooser() { 192 return mConfigChooser; 193 } 194 195 /** 196 * Creates a new {@linkplain Configuration} 197 * 198 * @param chooser the associated chooser 199 * @return a new configuration 200 */ 201 @NonNull 202 public static Configuration create(@NonNull ConfigurationChooser chooser) { 203 return new Configuration(chooser); 204 } 205 206 /** 207 * Creates a configuration suitable for the given file 208 * 209 * @param base the base configuration to base the file configuration off of 210 * @param file the file to look up a configuration for 211 * @return a suitable configuration 212 */ 213 @NonNull 214 public static Configuration create( 215 @NonNull Configuration base, 216 @NonNull IFile file) { 217 Configuration configuration = copy(base); 218 ConfigurationChooser chooser = base.getChooser(); 219 ProjectResources resources = chooser.getResources(); 220 ConfigurationMatcher matcher = new ConfigurationMatcher(chooser, configuration, file, 221 resources, false); 222 223 ResourceFolder resFolder = ResourceManager.getInstance().getResourceFolder(file); 224 configuration.mEditedConfig = new FolderConfiguration(); 225 configuration.mEditedConfig.set(resFolder.getConfiguration()); 226 227 matcher.adaptConfigSelection(true /*needBestMatch*/); 228 configuration.syncFolderConfig(); 229 230 return configuration; 231 } 232 233 /** 234 * Creates a new {@linkplain Configuration} that is a copy from a different configuration 235 * 236 * @param original the original to copy from 237 * @return a new configuration copied from the original 238 */ 239 @NonNull 240 public static Configuration copy(@NonNull Configuration original) { 241 Configuration copy = create(original.mConfigChooser); 242 copy.mFullConfig.set(original.mFullConfig); 243 if (original.mEditedConfig != null) { 244 copy.mEditedConfig = new FolderConfiguration(); 245 copy.mEditedConfig.set(original.mEditedConfig); 246 } 247 copy.mTarget = original.getTarget(); 248 copy.mTheme = original.getTheme(); 249 copy.mDevice = original.getDevice(); 250 copy.mState = original.getDeviceState(); 251 copy.mActivity = original.getActivity(); 252 copy.mLocale = original.getLocale(); 253 copy.mUiMode = original.getUiMode(); 254 copy.mNightMode = original.getNightMode(); 255 copy.mDisplayName = original.getDisplayName(); 256 257 return copy; 258 } 259 260 /** 261 * Returns the associated activity 262 * 263 * @return the activity 264 */ 265 @Nullable 266 public String getActivity() { 267 return mActivity; 268 } 269 270 /** 271 * Returns the chosen device. 272 * 273 * @return the chosen device 274 */ 275 @Nullable 276 public Device getDevice() { 277 return mDevice; 278 } 279 280 /** 281 * Returns the chosen device state 282 * 283 * @return the device state 284 */ 285 @Nullable 286 public State getDeviceState() { 287 return mState; 288 } 289 290 /** 291 * Returns the chosen locale 292 * 293 * @return the locale 294 */ 295 @NonNull 296 public Locale getLocale() { 297 return mLocale; 298 } 299 300 /** 301 * Returns the UI mode 302 * 303 * @return the UI mode 304 */ 305 @NonNull 306 public UiMode getUiMode() { 307 return mUiMode; 308 } 309 310 /** 311 * Returns the day/night mode 312 * 313 * @return the night mode 314 */ 315 @NonNull 316 public NightMode getNightMode() { 317 return mNightMode; 318 } 319 320 /** 321 * Returns the current theme style 322 * 323 * @return the theme style 324 */ 325 @Nullable 326 public String getTheme() { 327 return mTheme; 328 } 329 330 /** 331 * Returns the rendering target 332 * 333 * @return the target 334 */ 335 @Nullable 336 public IAndroidTarget getTarget() { 337 return mTarget; 338 } 339 340 /** 341 * Returns the display name to show for this configuration 342 * 343 * @return the display name, or null if none has been assigned 344 */ 345 @Nullable 346 public String getDisplayName() { 347 return mDisplayName; 348 } 349 350 /** 351 * Returns whether the configuration's theme is a project theme. 352 * <p/> 353 * The returned value is meaningless if {@link #getTheme()} returns 354 * <code>null</code>. 355 * 356 * @return true for project a theme, false for a framework theme 357 */ 358 public boolean isProjectTheme() { 359 String theme = getTheme(); 360 if (theme != null) { 361 assert theme.startsWith(STYLE_RESOURCE_PREFIX) 362 || theme.startsWith(ANDROID_STYLE_RESOURCE_PREFIX); 363 364 return ResourceHelper.isProjectStyle(theme); 365 } 366 367 return false; 368 } 369 370 /** 371 * Returns true if the current layout is locale-specific 372 * 373 * @return if this configuration represents a locale-specific layout 374 */ 375 public boolean isLocaleSpecificLayout() { 376 return mEditedConfig == null || mEditedConfig.getLocaleQualifier() != null; 377 } 378 379 /** 380 * Returns the full, complete {@link FolderConfiguration} 381 * 382 * @return the full configuration 383 */ 384 @NonNull 385 public FolderConfiguration getFullConfig() { 386 return mFullConfig; 387 } 388 389 /** 390 * Copies the full, complete {@link FolderConfiguration} into the given 391 * folder config instance. 392 * 393 * @param dest the {@link FolderConfiguration} instance to copy into 394 */ 395 public void copyFullConfig(FolderConfiguration dest) { 396 dest.set(mFullConfig); 397 } 398 399 /** 400 * Returns the edited {@link FolderConfiguration} (this is not a full 401 * configuration, so you can think of it as the "constraints" used by the 402 * {@link ConfigurationMatcher} to produce a full configuration. 403 * 404 * @return the constraints configuration 405 */ 406 @NonNull 407 public FolderConfiguration getEditedConfig() { 408 return mEditedConfig; 409 } 410 411 /** 412 * Sets the edited {@link FolderConfiguration} (this is not a full 413 * configuration, so you can think of it as the "constraints" used by the 414 * {@link ConfigurationMatcher} to produce a full configuration. 415 * 416 * @param editedConfig the constraints configuration 417 */ 418 public void setEditedConfig(@NonNull FolderConfiguration editedConfig) { 419 mEditedConfig = editedConfig; 420 } 421 422 /** 423 * Sets the associated activity 424 * 425 * @param activity the activity 426 */ 427 public void setActivity(String activity) { 428 mActivity = activity; 429 } 430 431 /** 432 * Sets the device 433 * 434 * @param device the device 435 * @param skipSync if true, don't sync folder configuration (typically because 436 * you are going to set other configuration parameters and you'll call 437 * {@link #syncFolderConfig()} once at the end) 438 */ 439 public void setDevice(Device device, boolean skipSync) { 440 mDevice = device; 441 442 if (!skipSync) { 443 syncFolderConfig(); 444 } 445 } 446 447 /** 448 * Sets the device state 449 * 450 * @param state the device state 451 * @param skipSync if true, don't sync folder configuration (typically because 452 * you are going to set other configuration parameters and you'll call 453 * {@link #syncFolderConfig()} once at the end) 454 */ 455 public void setDeviceState(State state, boolean skipSync) { 456 mState = state; 457 458 if (!skipSync) { 459 syncFolderConfig(); 460 } 461 } 462 463 /** 464 * Sets the locale 465 * 466 * @param locale the locale 467 * @param skipSync if true, don't sync folder configuration (typically because 468 * you are going to set other configuration parameters and you'll call 469 * {@link #syncFolderConfig()} once at the end) 470 */ 471 public void setLocale(@NonNull Locale locale, boolean skipSync) { 472 mLocale = locale; 473 474 if (!skipSync) { 475 syncFolderConfig(); 476 } 477 } 478 479 /** 480 * Sets the rendering target 481 * 482 * @param target rendering target 483 * @param skipSync if true, don't sync folder configuration (typically because 484 * you are going to set other configuration parameters and you'll call 485 * {@link #syncFolderConfig()} once at the end) 486 */ 487 public void setTarget(IAndroidTarget target, boolean skipSync) { 488 mTarget = target; 489 490 if (!skipSync) { 491 syncFolderConfig(); 492 } 493 } 494 495 /** 496 * Sets the display name to be shown for this configuration. 497 * 498 * @param displayName the new display name 499 */ 500 public void setDisplayName(@Nullable String displayName) { 501 mDisplayName = displayName; 502 } 503 504 /** 505 * Sets the night mode 506 * 507 * @param night the night mode 508 * @param skipSync if true, don't sync folder configuration (typically because 509 * you are going to set other configuration parameters and you'll call 510 * {@link #syncFolderConfig()} once at the end) 511 */ 512 public void setNightMode(@NonNull NightMode night, boolean skipSync) { 513 mNightMode = night; 514 515 if (!skipSync) { 516 syncFolderConfig(); 517 } 518 } 519 520 /** 521 * Sets the UI mode 522 * 523 * @param uiMode the UI mode 524 * @param skipSync if true, don't sync folder configuration (typically because 525 * you are going to set other configuration parameters and you'll call 526 * {@link #syncFolderConfig()} once at the end) 527 */ 528 public void setUiMode(@NonNull UiMode uiMode, boolean skipSync) { 529 mUiMode = uiMode; 530 531 if (!skipSync) { 532 syncFolderConfig(); 533 } 534 } 535 536 /** 537 * Sets the theme style 538 * 539 * @param theme the theme 540 */ 541 public void setTheme(String theme) { 542 mTheme = theme; 543 checkThemePrefix(); 544 } 545 546 /** 547 * Updates the folder configuration such that it reflects changes in 548 * configuration state such as the device orientation, the UI mode, the 549 * rendering target, etc. 550 */ 551 public void syncFolderConfig() { 552 Device device = getDevice(); 553 if (device == null) { 554 return; 555 } 556 557 // get the device config from the device/state combos. 558 FolderConfiguration config = DeviceConfigHelper.getFolderConfig(getDeviceState()); 559 560 // replace the config with the one from the device 561 mFullConfig.set(config); 562 563 // sync the selected locale 564 Locale locale = getLocale(); 565 mFullConfig.setLocaleQualifier(locale.qualifier); 566 if (!locale.hasLanguage()) { 567 // Avoid getting the layout library if the locale doesn't have any language. 568 mFullConfig.setLayoutDirectionQualifier( 569 new LayoutDirectionQualifier(LayoutDirection.LTR)); 570 } else { 571 Sdk currentSdk = Sdk.getCurrent(); 572 if (currentSdk != null) { 573 AndroidTargetData targetData = currentSdk.getTargetData(getTarget()); 574 if (targetData != null) { 575 LayoutLibrary layoutLib = targetData.getLayoutLibrary(); 576 if (layoutLib != null) { 577 if (layoutLib.isRtl(locale.toLocaleId())) { 578 mFullConfig.setLayoutDirectionQualifier( 579 new LayoutDirectionQualifier(LayoutDirection.RTL)); 580 } else { 581 mFullConfig.setLayoutDirectionQualifier( 582 new LayoutDirectionQualifier(LayoutDirection.LTR)); 583 } 584 } 585 } 586 } 587 } 588 589 // Replace the UiMode with the selected one, if one is selected 590 UiMode uiMode = getUiMode(); 591 if (uiMode != null) { 592 mFullConfig.setUiModeQualifier(new UiModeQualifier(uiMode)); 593 } 594 595 // Replace the NightMode with the selected one, if one is selected 596 NightMode nightMode = getNightMode(); 597 if (nightMode != null) { 598 mFullConfig.setNightModeQualifier(new NightModeQualifier(nightMode)); 599 } 600 601 // replace the API level by the selection of the combo 602 IAndroidTarget target = getTarget(); 603 if (target == null && mConfigChooser != null) { 604 target = mConfigChooser.getProjectTarget(); 605 } 606 if (target != null) { 607 int apiLevel = target.getVersion().getApiLevel(); 608 mFullConfig.setVersionQualifier(new VersionQualifier(apiLevel)); 609 } 610 } 611 612 /** 613 * Creates a string suitable for persistence, which can be initialized back 614 * to a configuration via {@link #initialize(String)} 615 * 616 * @return a persistent string 617 */ 618 @NonNull 619 public String toPersistentString() { 620 StringBuilder sb = new StringBuilder(32); 621 Device device = getDevice(); 622 if (device != null) { 623 sb.append(device.getName()); 624 sb.append(SEP); 625 State state = getDeviceState(); 626 if (state != null) { 627 sb.append(state.getName()); 628 } 629 sb.append(SEP); 630 Locale locale = getLocale(); 631 if (isLocaleSpecificLayout() && locale != null && locale.qualifier.hasLanguage()) { 632 // locale[0]/[1] can be null sometimes when starting Eclipse 633 sb.append(locale.qualifier.getLanguage()); 634 sb.append(SEP_LOCALE); 635 if (locale.qualifier.hasRegion()) { 636 sb.append(locale.qualifier.getRegion()); 637 } 638 } 639 sb.append(SEP); 640 // Need to escape the theme: if we write the full theme style, then 641 // we can end up with ":"'s in the string (as in @android:style/Theme) which 642 // can be mistaken for {@link #SEP}. Instead use {@link #MARKER_FRAMEWORK}. 643 String theme = getTheme(); 644 if (theme != null) { 645 String themeName = ResourceHelper.styleToTheme(theme); 646 if (theme.startsWith(STYLE_RESOURCE_PREFIX)) { 647 sb.append(MARKER_PROJECT); 648 } else if (theme.startsWith(ANDROID_STYLE_RESOURCE_PREFIX)) { 649 sb.append(MARKER_FRAMEWORK); 650 } 651 sb.append(themeName); 652 } 653 sb.append(SEP); 654 UiMode uiMode = getUiMode(); 655 if (uiMode != null) { 656 sb.append(uiMode.getResourceValue()); 657 } 658 sb.append(SEP); 659 NightMode nightMode = getNightMode(); 660 if (nightMode != null) { 661 sb.append(nightMode.getResourceValue()); 662 } 663 sb.append(SEP); 664 665 // We used to store the render target here in R9. Leave a marker 666 // to ensure that we don't reuse this slot; add new extra fields after it. 667 sb.append(SEP); 668 String activity = getActivity(); 669 if (activity != null) { 670 sb.append(activity); 671 } 672 } 673 674 return sb.toString(); 675 } 676 677 /** Returns the preferred theme, or null */ 678 @Nullable 679 String computePreferredTheme() { 680 IProject project = mConfigChooser.getProject(); 681 ManifestInfo manifest = ManifestInfo.get(project); 682 683 // Look up the screen size for the current state 684 ScreenSize screenSize = null; 685 Device device = getDevice(); 686 if (device != null) { 687 List<State> states = device.getAllStates(); 688 for (State state : states) { 689 FolderConfiguration folderConfig = DeviceConfigHelper.getFolderConfig(state); 690 if (folderConfig != null) { 691 ScreenSizeQualifier qualifier = folderConfig.getScreenSizeQualifier(); 692 screenSize = qualifier.getValue(); 693 break; 694 } 695 } 696 } 697 698 // Look up the default/fallback theme to use for this project (which 699 // depends on the screen size when no particular theme is specified 700 // in the manifest) 701 String defaultTheme = manifest.getDefaultTheme(getTarget(), screenSize); 702 703 String preferred = defaultTheme; 704 if (getTheme() == null) { 705 // If we are rendering a layout in included context, pick the theme 706 // from the outer layout instead 707 708 String activity = getActivity(); 709 if (activity != null) { 710 ActivityAttributes attributes = manifest.getActivityAttributes(activity); 711 if (attributes != null) { 712 preferred = attributes.getTheme(); 713 } 714 } 715 if (preferred == null) { 716 preferred = defaultTheme; 717 } 718 setTheme(preferred); 719 } 720 721 return preferred; 722 } 723 724 private void checkThemePrefix() { 725 if (mTheme != null && !mTheme.startsWith(PREFIX_RESOURCE_REF)) { 726 if (mTheme.isEmpty()) { 727 computePreferredTheme(); 728 return; 729 } 730 ResourceRepository frameworkRes = mConfigChooser.getClient().getFrameworkResources(); 731 if (frameworkRes != null 732 && frameworkRes.hasResourceItem(ANDROID_STYLE_RESOURCE_PREFIX + mTheme)) { 733 mTheme = ANDROID_STYLE_RESOURCE_PREFIX + mTheme; 734 } else { 735 mTheme = STYLE_RESOURCE_PREFIX + mTheme; 736 } 737 } 738 } 739 740 /** 741 * Initializes a string previously created with 742 * {@link #toPersistentString()} 743 * 744 * @param data the string to initialize back from 745 * @return true if the configuration was initialized 746 */ 747 boolean initialize(String data) { 748 String[] values = data.split(SEP); 749 if (values.length >= 6 && values.length <= 8) { 750 for (Device d : mConfigChooser.getDevices()) { 751 if (d.getName().equals(values[0])) { 752 mDevice = d; 753 String stateName = null; 754 FolderConfiguration config = null; 755 if (!values[1].isEmpty() && !values[1].equals("null")) { //$NON-NLS-1$ 756 stateName = values[1]; 757 config = DeviceConfigHelper.getFolderConfig(mDevice, stateName); 758 } else if (mDevice.getAllStates().size() > 0) { 759 State first = mDevice.getAllStates().get(0); 760 stateName = first.getName(); 761 config = DeviceConfigHelper.getFolderConfig(first); 762 } 763 mState = getState(mDevice, stateName); 764 if (config != null) { 765 // Load locale. Note that this can get overwritten by the 766 // project-wide settings read below. 767 LocaleQualifier locale = Locale.ANY_QUALIFIER; 768 String locales[] = values[2].split(SEP_LOCALE); 769 if (locales.length >= 2 && locales[0].length() > 0 770 && !LocaleQualifier.FAKE_VALUE.equals(locales[0])) { 771 String language = locales[0]; 772 String region = locales[1]; 773 if (region.length() > 0 && !LocaleQualifier.FAKE_VALUE.equals(region)) { 774 locale = LocaleQualifier.getQualifier(language + "-r" + region); 775 } else { 776 locale = new LocaleQualifier(language); 777 } 778 mLocale = Locale.create(locale); 779 } 780 781 // Decode the theme name: See {@link #getData} 782 mTheme = values[3]; 783 if (mTheme.startsWith(MARKER_FRAMEWORK)) { 784 mTheme = ANDROID_STYLE_RESOURCE_PREFIX 785 + mTheme.substring(MARKER_FRAMEWORK.length()); 786 } else if (mTheme.startsWith(MARKER_PROJECT)) { 787 mTheme = STYLE_RESOURCE_PREFIX 788 + mTheme.substring(MARKER_PROJECT.length()); 789 } else { 790 checkThemePrefix(); 791 } 792 793 mUiMode = UiMode.getEnum(values[4]); 794 if (mUiMode == null) { 795 mUiMode = UiMode.NORMAL; 796 } 797 mNightMode = NightMode.getEnum(values[5]); 798 if (mNightMode == null) { 799 mNightMode = NightMode.NOTNIGHT; 800 } 801 802 // element 7/values[6]: used to store render target in R9. 803 // No longer stored here. If adding more data, make 804 // sure you leave 7 alone. 805 806 Pair<Locale, IAndroidTarget> pair = loadRenderState(mConfigChooser); 807 if (pair != null) { 808 // We only use the "global" setting 809 if (!isLocaleSpecificLayout()) { 810 mLocale = pair.getFirst(); 811 } 812 mTarget = pair.getSecond(); 813 } 814 815 if (values.length == 8) { 816 mActivity = values[7]; 817 } 818 819 return true; 820 } 821 } 822 } 823 } 824 825 return false; 826 } 827 828 /** 829 * Loads the render state (the locale and the render target, which are shared among 830 * all the layouts meaning that changing it in one will change it in all) and returns 831 * the current project-wide locale and render target to be used. 832 * 833 * @param chooser the {@link ConfigurationChooser} providing information about 834 * loaded targets 835 * @return a pair of a locale and a render target 836 */ 837 @Nullable 838 static Pair<Locale, IAndroidTarget> loadRenderState(ConfigurationChooser chooser) { 839 IProject project = chooser.getProject(); 840 if (project == null || !project.isAccessible()) { 841 return null; 842 } 843 844 try { 845 String data = project.getPersistentProperty(NAME_RENDER_STATE); 846 if (data != null) { 847 Locale locale = Locale.ANY; 848 IAndroidTarget target = null; 849 850 String[] values = data.split(SEP); 851 if (values.length == 2) { 852 853 LocaleQualifier qualifier = Locale.ANY_QUALIFIER; 854 String locales[] = values[0].split(SEP_LOCALE); 855 if (locales.length >= 2 && locales[0].length() > 0 856 && !LocaleQualifier.FAKE_VALUE.equals(locales[0])) { 857 String language = locales[0]; 858 String region = locales[1]; 859 if (region.length() > 0 && !LocaleQualifier.FAKE_VALUE.equals(region)) { 860 locale = Locale.create(LocaleQualifier.getQualifier(language + "-r" + region)); 861 } else { 862 locale = Locale.create(new LocaleQualifier(language)); 863 } 864 } else { 865 locale = Locale.ANY; 866 } 867 if (AdtPrefs.getPrefs().isAutoPickRenderTarget()) { 868 target = ConfigurationMatcher.findDefaultRenderTarget(chooser); 869 } else { 870 String targetString = values[1]; 871 target = stringToTarget(chooser, targetString); 872 // See if we should "correct" the rendering target to a 873 // better version. If you're using a pre-release version 874 // of the render target, and a final release is 875 // available and installed, we should switch to that 876 // one instead. 877 if (target != null) { 878 AndroidVersion version = target.getVersion(); 879 List<IAndroidTarget> targetList = chooser.getTargetList(); 880 if (version.getCodename() != null && targetList != null) { 881 int targetApiLevel = version.getApiLevel() + 1; 882 for (IAndroidTarget t : targetList) { 883 if (t.getVersion().getApiLevel() == targetApiLevel 884 && t.isPlatform()) { 885 target = t; 886 break; 887 } 888 } 889 } 890 } else { 891 target = ConfigurationMatcher.findDefaultRenderTarget(chooser); 892 } 893 } 894 } 895 896 return Pair.of(locale, target); 897 } 898 899 return Pair.of(Locale.ANY, ConfigurationMatcher.findDefaultRenderTarget(chooser)); 900 } catch (CoreException e) { 901 AdtPlugin.log(e, null); 902 } 903 904 return null; 905 } 906 907 /** 908 * Saves the render state (the current locale and render target settings) into the 909 * project wide settings storage 910 */ 911 void saveRenderState() { 912 IProject project = mConfigChooser.getProject(); 913 if (project == null) { 914 return; 915 } 916 try { 917 // Generate a persistent string from locale+target 918 StringBuilder sb = new StringBuilder(32); 919 Locale locale = getLocale(); 920 if (locale != null) { 921 // locale[0]/[1] can be null sometimes when starting Eclipse 922 sb.append(locale.qualifier.getLanguage()); 923 sb.append(SEP_LOCALE); 924 if (locale.qualifier.hasRegion()) { 925 sb.append(locale.qualifier.getRegion()); 926 } 927 } 928 sb.append(SEP); 929 IAndroidTarget target = getTarget(); 930 if (target != null) { 931 sb.append(targetToString(target)); 932 sb.append(SEP); 933 } 934 935 project.setPersistentProperty(NAME_RENDER_STATE, sb.toString()); 936 } catch (CoreException e) { 937 AdtPlugin.log(e, null); 938 } 939 } 940 941 /** 942 * Returns a String id to represent an {@link IAndroidTarget} which can be translated 943 * back to an {@link IAndroidTarget} by the matching {@link #stringToTarget}. The id 944 * will never contain the {@link #SEP} character. 945 * 946 * @param target the target to return an id for 947 * @return an id for the given target; never null 948 */ 949 @NonNull 950 public static String targetToString(@NonNull IAndroidTarget target) { 951 return target.getFullName().replace(SEP, ""); //$NON-NLS-1$ 952 } 953 954 /** 955 * Returns an {@link IAndroidTarget} that corresponds to the given id that was 956 * originally returned by {@link #targetToString}. May be null, if the platform is no 957 * longer available, or if the platform list has not yet been initialized. 958 * 959 * @param chooser the {@link ConfigurationChooser} providing information about 960 * loaded targets 961 * @param id the id that corresponds to the desired platform 962 * @return an {@link IAndroidTarget} that matches the given id, or null 963 */ 964 @Nullable 965 public static IAndroidTarget stringToTarget( 966 @NonNull ConfigurationChooser chooser, 967 @NonNull String id) { 968 List<IAndroidTarget> targetList = chooser.getTargetList(); 969 if (targetList != null && targetList.size() > 0) { 970 for (IAndroidTarget target : targetList) { 971 if (id.equals(targetToString(target))) { 972 return target; 973 } 974 } 975 } 976 977 return null; 978 } 979 980 /** 981 * Returns an {@link IAndroidTarget} that corresponds to the given id that was 982 * originally returned by {@link #targetToString}. May be null, if the platform is no 983 * longer available, or if the platform list has not yet been initialized. 984 * 985 * @param id the id that corresponds to the desired platform 986 * @return an {@link IAndroidTarget} that matches the given id, or null 987 */ 988 @Nullable 989 public static IAndroidTarget stringToTarget( 990 @NonNull String id) { 991 Sdk currentSdk = Sdk.getCurrent(); 992 if (currentSdk != null) { 993 IAndroidTarget[] targets = currentSdk.getTargets(); 994 for (IAndroidTarget target : targets) { 995 if (id.equals(targetToString(target))) { 996 return target; 997 } 998 } 999 } 1000 1001 return null; 1002 } 1003 1004 /** 1005 * Returns the {@link State} by the given name for the given {@link Device} 1006 * 1007 * @param device the device 1008 * @param name the name of the state 1009 */ 1010 @Nullable 1011 static State getState(@Nullable Device device, @Nullable String name) { 1012 if (device == null) { 1013 return null; 1014 } else if (name != null) { 1015 State state = device.getState(name); 1016 if (state != null) { 1017 return state; 1018 } 1019 } 1020 1021 return device.getDefaultState(); 1022 } 1023 1024 /** 1025 * Returns the currently selected {@link Density}. This is guaranteed to be non null. 1026 * 1027 * @return the density 1028 */ 1029 @NonNull 1030 public Density getDensity() { 1031 if (mFullConfig != null) { 1032 DensityQualifier qual = mFullConfig.getDensityQualifier(); 1033 if (qual != null) { 1034 // just a sanity check 1035 Density d = qual.getValue(); 1036 if (d != Density.NODPI) { 1037 return d; 1038 } 1039 } 1040 } 1041 1042 // no config? return medium as the default density. 1043 return Density.MEDIUM; 1044 } 1045 1046 /** 1047 * Get the next cyclical state after the given state 1048 * 1049 * @param from the state to start with 1050 * @return the following state following 1051 */ 1052 @Nullable 1053 public State getNextDeviceState(@Nullable State from) { 1054 Device device = getDevice(); 1055 if (device == null) { 1056 return null; 1057 } 1058 List<State> states = device.getAllStates(); 1059 for (int i = 0; i < states.size(); i++) { 1060 if (states.get(i) == from) { 1061 return states.get((i + 1) % states.size()); 1062 } 1063 } 1064 1065 return null; 1066 } 1067 1068 /** 1069 * Returns true if this configuration supports the given rendering 1070 * capability 1071 * 1072 * @param capability the capability to check 1073 * @return true if the capability is supported 1074 */ 1075 public boolean supports(Capability capability) { 1076 IAndroidTarget target = getTarget(); 1077 if (target != null) { 1078 return RenderService.supports(target, capability); 1079 } 1080 1081 return false; 1082 } 1083 1084 @Override 1085 public String toString() { 1086 return Objects.toStringHelper(this.getClass()) 1087 .add("display", getDisplayName()) //$NON-NLS-1$ 1088 .add("persistent", toPersistentString()) //$NON-NLS-1$ 1089 .toString(); 1090 } 1091 } 1092