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