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