Home | History | Annotate | Download | only in configuration
      1 /*
      2  * Copyright (C) 2008 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.ide.common.layout.LayoutConstants.ANDROID_NS_NAME_PREFIX;
     20 import static com.android.ide.common.resources.ResourceResolver.PREFIX_ANDROID_STYLE;
     21 
     22 import com.android.ide.common.api.Rect;
     23 import com.android.ide.common.rendering.api.ResourceValue;
     24 import com.android.ide.common.rendering.api.StyleResourceValue;
     25 import com.android.ide.common.resources.ResourceFile;
     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.FolderConfiguration;
     30 import com.android.ide.common.resources.configuration.LanguageQualifier;
     31 import com.android.ide.common.resources.configuration.NightModeQualifier;
     32 import com.android.ide.common.resources.configuration.RegionQualifier;
     33 import com.android.ide.common.resources.configuration.ResourceQualifier;
     34 import com.android.ide.common.resources.configuration.ScreenDimensionQualifier;
     35 import com.android.ide.common.resources.configuration.ScreenOrientationQualifier;
     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.common.sdk.LoadStatus;
     40 import com.android.ide.eclipse.adt.AdtPlugin;
     41 import com.android.ide.eclipse.adt.internal.editors.layout.LayoutEditor;
     42 import com.android.ide.eclipse.adt.internal.editors.manifest.ManifestInfo;
     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.LayoutDevice;
     48 import com.android.ide.eclipse.adt.internal.sdk.LayoutDevice.DeviceConfig;
     49 import com.android.ide.eclipse.adt.internal.sdk.LayoutDeviceManager;
     50 import com.android.ide.eclipse.adt.internal.sdk.Sdk;
     51 import com.android.resources.Density;
     52 import com.android.resources.NightMode;
     53 import com.android.resources.ResourceFolderType;
     54 import com.android.resources.ResourceType;
     55 import com.android.resources.ScreenOrientation;
     56 import com.android.resources.ScreenSize;
     57 import com.android.resources.UiMode;
     58 import com.android.sdklib.AndroidVersion;
     59 import com.android.sdklib.IAndroidTarget;
     60 import com.android.sdklib.repository.PkgProps;
     61 import com.android.sdklib.util.SparseIntArray;
     62 import com.android.util.Pair;
     63 
     64 import org.eclipse.core.resources.IFile;
     65 import org.eclipse.core.resources.IFolder;
     66 import org.eclipse.core.resources.IProject;
     67 import org.eclipse.core.runtime.CoreException;
     68 import org.eclipse.core.runtime.IStatus;
     69 import org.eclipse.core.runtime.QualifiedName;
     70 import org.eclipse.swt.SWT;
     71 import org.eclipse.swt.events.SelectionAdapter;
     72 import org.eclipse.swt.events.SelectionEvent;
     73 import org.eclipse.swt.layout.GridData;
     74 import org.eclipse.swt.layout.GridLayout;
     75 import org.eclipse.swt.widgets.Button;
     76 import org.eclipse.swt.widgets.Combo;
     77 import org.eclipse.swt.widgets.Composite;
     78 import org.eclipse.swt.widgets.Label;
     79 import org.eclipse.ui.IEditorPart;
     80 import org.eclipse.ui.IWorkbench;
     81 import org.eclipse.ui.IWorkbenchPage;
     82 import org.eclipse.ui.IWorkbenchWindow;
     83 import org.eclipse.ui.PlatformUI;
     84 
     85 import java.util.ArrayList;
     86 import java.util.Collections;
     87 import java.util.Comparator;
     88 import java.util.HashSet;
     89 import java.util.List;
     90 import java.util.Locale;
     91 import java.util.Map;
     92 import java.util.Set;
     93 import java.util.SortedSet;
     94 
     95 /**
     96  * A composite that displays the current configuration displayed in a Graphical Layout Editor.
     97  * <p/>
     98  * The composite has several entry points:<br>
     99  * - {@link #setFile(IFile)}<br>
    100  *   Called after the constructor to set the file being edited. Nothing else is performed.<br>
    101  *<br>
    102  * - {@link #onXmlModelLoaded()}<br>
    103  *   Called when the XML model is loaded, either the first time or when the Target/SDK changes.
    104  *   This initializes the UI, either with the first compatible configuration found, or attempts
    105  *   to restore a configuration if one is found to have been saved in the file persistent storage.
    106  *   (see {@link #storeState()})<br>
    107  *<br>
    108  * - {@link #replaceFile(IFile)}<br>
    109  *   Called when a file, representing the same resource but with a different config is opened<br>
    110  *   by the user.<br>
    111  *<br>
    112  * - {@link #changeFileOnNewConfig(IFile)}<br>
    113  *   Called when config change triggers the editing of a file with a different config.
    114  *<p/>
    115  * Additionally, the composite can handle the following events.<br>
    116  * - SDK reload. This is when the main SDK is finished loading.<br>
    117  * - Target reload. This is when the target used by the project is the edited file has finished<br>
    118  *   loading.<br>
    119  */
    120 public class ConfigurationComposite extends Composite {
    121     private final static String SEP = ":"; //$NON-NLS-1$
    122     private final static String SEP_LOCALE = "-"; //$NON-NLS-1$
    123 
    124     /**
    125      * Setting name for project-wide setting controlling rendering target and locale which
    126      * is shared for all files
    127      */
    128     public final static QualifiedName NAME_RENDER_STATE =
    129         new QualifiedName(AdtPlugin.PLUGIN_ID, "render");//$NON-NLS-1$
    130 
    131     /**
    132      * Settings name for file-specific configuration preferences, such as which theme or
    133      * device to render the current layout with
    134      */
    135     public final static QualifiedName NAME_CONFIG_STATE =
    136         new QualifiedName(AdtPlugin.PLUGIN_ID, "state");//$NON-NLS-1$
    137 
    138     private final static String THEME_SEPARATOR = "----------"; //$NON-NLS-1$
    139 
    140     private final static int LOCALE_LANG = 0;
    141     private final static int LOCALE_REGION = 1;
    142 
    143     private Label mCurrentLayoutLabel;
    144     private Button mCreateButton;
    145 
    146     private Combo mDeviceCombo;
    147     private Combo mDeviceConfigCombo;
    148     private Combo mLocaleCombo;
    149     private Combo mUiModeCombo;
    150     private Combo mNightCombo;
    151     private Combo mThemeCombo;
    152     private Combo mTargetCombo;
    153 
    154     /**
    155      * List of booleans, matching item for item the theme names in the mThemeCombo
    156      * combobox, where each boolean represents whether the corresponding theme is a
    157      * project theme
    158      */
    159     private List<Boolean> mIsProjectTheme = new ArrayList<Boolean>(40);
    160 
    161     /** updates are disabled if > 0 */
    162     private int mDisableUpdates = 0;
    163 
    164     private List<LayoutDevice> mDeviceList;
    165     private final List<IAndroidTarget> mTargetList = new ArrayList<IAndroidTarget>();
    166 
    167     private final ArrayList<ResourceQualifier[] > mLocaleList =
    168         new ArrayList<ResourceQualifier[]>();
    169 
    170     private final ConfigState mState = new ConfigState();
    171 
    172     private boolean mSdkChanged = false;
    173     private boolean mFirstXmlModelChange = true;
    174 
    175     /** The config listener given to the constructor. Never null. */
    176     private final IConfigListener mListener;
    177 
    178     /** The {@link FolderConfiguration} representing the state of the UI controls */
    179     private final FolderConfiguration mCurrentConfig = new FolderConfiguration();
    180 
    181     /** The file being edited */
    182     private IFile mEditedFile;
    183     /** The {@link ProjectResources} for the edited file's project */
    184     private ProjectResources mResources;
    185     /** The target of the project of the file being edited. */
    186     private IAndroidTarget mProjectTarget;
    187     /** The target of the project of the file being edited. */
    188     private IAndroidTarget mRenderingTarget;
    189     /** The {@link FolderConfiguration} being edited. */
    190     private FolderConfiguration mEditedConfig;
    191     /** Serialized state to use when initializing the configuration after the SDK is loaded */
    192     private String mInitialState;
    193 
    194     /**
    195      * Interface implemented by the part which owns a {@link ConfigurationComposite}.
    196      * This notifies the owners when the configuration change.
    197      * The owner must also provide methods to provide the configuration that will
    198      * be displayed.
    199      */
    200     public interface IConfigListener {
    201         /**
    202          * Called when the {@link FolderConfiguration} change. The new config can be queried
    203          * with {@link ConfigurationComposite#getCurrentConfig()}.
    204          */
    205         void onConfigurationChange();
    206 
    207         /**
    208          * Called after a device has changed (in addition to {@link #onConfigurationChange}
    209          * getting called)
    210          */
    211         void onDevicePostChange();
    212 
    213         /**
    214          * Called when the current theme changes. The theme can be queried with
    215          * {@link ConfigurationComposite#getTheme()}.
    216          */
    217         void onThemeChange();
    218 
    219         /**
    220          * Called when the "Create" button is clicked.
    221          */
    222         void onCreate();
    223 
    224         /**
    225          * Called before the rendering target changes.
    226          * @param oldTarget the old rendering target
    227          */
    228         void onRenderingTargetPreChange(IAndroidTarget oldTarget);
    229 
    230         /**
    231          * Called after the rendering target changes.
    232          *
    233          * @param target the new rendering target
    234          */
    235         void onRenderingTargetPostChange(IAndroidTarget target);
    236 
    237         ResourceRepository getProjectResources();
    238         ResourceRepository getFrameworkResources();
    239         ResourceRepository getFrameworkResources(IAndroidTarget target);
    240         Map<ResourceType, Map<String, ResourceValue>> getConfiguredProjectResources();
    241         Map<ResourceType, Map<String, ResourceValue>> getConfiguredFrameworkResources();
    242         String getIncludedWithin();
    243     }
    244 
    245     /**
    246      * State of the current config. This is used during UI reset to attempt to return the
    247      * rendering to its original configuration.
    248      */
    249     private class ConfigState {
    250         LayoutDevice device;
    251         String configName;
    252         ResourceQualifier[] locale;
    253         String theme;
    254         /** UI mode. Guaranteed to be non null */
    255         UiMode uiMode = UiMode.NORMAL;
    256         /** night mode. Guaranteed to be non null */
    257         NightMode night = NightMode.NOTNIGHT;
    258         /** the version being targeted for rendering */
    259         IAndroidTarget target;
    260 
    261         String getData() {
    262             StringBuilder sb = new StringBuilder();
    263             if (device != null) {
    264                 sb.append(device.getName());
    265                 sb.append(SEP);
    266                 sb.append(configName);
    267                 sb.append(SEP);
    268                 if (isLocaleSpecificLayout() && locale != null) {
    269                     if (locale[0] != null && locale[1] != null) {
    270                         // locale[0]/[1] can be null sometimes when starting Eclipse
    271                         sb.append(((LanguageQualifier) locale[0]).getValue());
    272                         sb.append(SEP_LOCALE);
    273                         sb.append(((RegionQualifier) locale[1]).getValue());
    274                     }
    275                 }
    276                 sb.append(SEP);
    277                 sb.append(theme);
    278                 sb.append(SEP);
    279                 sb.append(uiMode.getResourceValue());
    280                 sb.append(SEP);
    281                 sb.append(night.getResourceValue());
    282                 sb.append(SEP);
    283 
    284                 // We used to store the render target here in R9. Leave a marker
    285                 // to ensure that we don't reuse this slot; add new extra fields after it.
    286                 sb.append(SEP);
    287             }
    288 
    289             return sb.toString();
    290         }
    291 
    292         boolean setData(String data) {
    293             String[] values = data.split(SEP);
    294             if (values.length == 6 || values.length == 7) {
    295                 for (LayoutDevice d : mDeviceList) {
    296                     if (d.getName().equals(values[0])) {
    297                         device = d;
    298                         FolderConfiguration config = device.getFolderConfigByName(values[1]);
    299                         if (config != null) {
    300                             configName = values[1];
    301 
    302                             // Load locale. Note that this can get overwritten by the
    303                             // project-wide settings read below.
    304                             locale = new ResourceQualifier[2];
    305                             String locales[] = values[2].split(SEP_LOCALE);
    306                             if (locales.length >= 2) {
    307                                 if (locales[0].length() > 0) {
    308                                     locale[0] = new LanguageQualifier(locales[0]);
    309                                 }
    310                                 if (locales[1].length() > 0) {
    311                                     locale[1] = new RegionQualifier(locales[1]);
    312                                 }
    313                             }
    314 
    315                             theme = values[3];
    316                             uiMode = UiMode.getEnum(values[4]);
    317                             if (uiMode == null) {
    318                                 uiMode = UiMode.NORMAL;
    319                             }
    320                             night = NightMode.getEnum(values[5]);
    321                             if (night == null) {
    322                                 night = NightMode.NOTNIGHT;
    323                             }
    324 
    325                             // element 7/values[6]: used to store render target in R9.
    326                             // No longer stored here. If adding more data, make
    327                             // sure you leave 7 alone.
    328 
    329                             Pair<ResourceQualifier[], IAndroidTarget> pair = loadRenderState();
    330 
    331                             // We only use the "global" setting
    332                             if (!isLocaleSpecificLayout()) {
    333                                 locale = pair.getFirst();
    334                             }
    335                             target = pair.getSecond();
    336 
    337                             return true;
    338                         }
    339                     }
    340                 }
    341             }
    342 
    343             return false;
    344         }
    345 
    346         @Override
    347         public String toString() {
    348             return getData();
    349         }
    350     }
    351 
    352     /**
    353      * Returns a String id to represent an {@link IAndroidTarget} which can be translated
    354      * back to an {@link IAndroidTarget} by the matching {@link #stringToTarget}. The id
    355      * will never contain the {@link #SEP} character.
    356      *
    357      * @param target the target to return an id for
    358      * @return an id for the given target; never null
    359      */
    360     private String targetToString(IAndroidTarget target) {
    361         return target.getFullName().replace(SEP, "");  //$NON-NLS-1$
    362     }
    363 
    364     /**
    365      * Returns an {@link IAndroidTarget} that corresponds to the given id that was
    366      * originally returned by {@link #targetToString}. May be null, if the platform is no
    367      * longer available, or if the platform list has not yet been initialized.
    368      *
    369      * @param id the id that corresponds to the desired platform
    370      * @return an {@link IAndroidTarget} that matches the given id, or null
    371      */
    372     private IAndroidTarget stringToTarget(String id) {
    373         if (mTargetList != null && mTargetList.size() > 0) {
    374             for (IAndroidTarget target : mTargetList) {
    375                 if (id.equals(targetToString(target))) {
    376                     return target;
    377                 }
    378             }
    379         }
    380 
    381         return null;
    382     }
    383 
    384     /**
    385      * Creates a new {@link ConfigurationComposite} and adds it to the parent.
    386      *
    387      * The method also receives custom buttons to set into the configuration composite. The list
    388      * is organized as an array of arrays. Each array represents a group of buttons thematically
    389      * grouped together.
    390      *
    391      * @param listener An {@link IConfigListener} that gets and sets configuration properties.
    392      *          Mandatory, cannot be null.
    393      * @param parent The parent composite.
    394      * @param style The style of this composite.
    395      * @param initialState The initial state (serialized form) to use for the configuration
    396      */
    397     public ConfigurationComposite(IConfigListener listener,
    398             Composite parent, int style, String initialState) {
    399         super(parent, style);
    400         mListener = listener;
    401         mInitialState = initialState;
    402 
    403         GridLayout gl;
    404         GridData gd;
    405         int cols = 7;  // device+config+dock+day+separator*2+theme
    406 
    407         // ---- First line: editing config display, locale, theme, create-button
    408         Composite labelParent = new Composite(this, SWT.NONE);
    409         labelParent.setLayout(gl = new GridLayout(5, false));
    410         gl.marginWidth = gl.marginHeight = 0;
    411         gl.marginTop = 3;
    412         labelParent.setLayoutData(gd = new GridData(GridData.FILL_HORIZONTAL));
    413         gd.horizontalSpan = cols;
    414 
    415         new Label(labelParent, SWT.NONE).setText("Editing config:");
    416         mCurrentLayoutLabel = new Label(labelParent, SWT.NONE);
    417         mCurrentLayoutLabel.setLayoutData(gd = new GridData(GridData.FILL_HORIZONTAL));
    418         gd.widthHint = 50;
    419 
    420         mLocaleCombo = new Combo(labelParent, SWT.DROP_DOWN | SWT.READ_ONLY);
    421         mLocaleCombo.addSelectionListener(new SelectionAdapter() {
    422             @Override
    423             public void widgetSelected(SelectionEvent e) {
    424                 onLocaleChange();
    425             }
    426         });
    427 
    428         // Layout bug workaround. Without this, in -some- scenarios the Locale combo box was
    429         // coming up tiny. Setting a minimumWidth hint does not work either. We need to have
    430         // 2 or more items in the locale combo box when the layout is first run. These items
    431         // are removed as part of the locale initialization when the SDK is loaded.
    432         mLocaleCombo.add("Locale"); //$NON-NLS-1$  // Dummy place holders
    433         mLocaleCombo.add("Locale"); //$NON-NLS-1$
    434 
    435         mTargetCombo = new Combo(labelParent, SWT.DROP_DOWN | SWT.READ_ONLY);
    436         mTargetCombo.add("Android AOSP"); //$NON-NLS-1$  // Dummy place holders
    437         mTargetCombo.add("Android AOSP"); //$NON-NLS-1$
    438         mTargetCombo.addSelectionListener(new SelectionAdapter() {
    439             @Override
    440             public void widgetSelected(SelectionEvent e) {
    441                 onRenderingTargetChange();
    442             }
    443         });
    444 
    445         mCreateButton = new Button(labelParent, SWT.PUSH | SWT.FLAT);
    446         mCreateButton.setText("Create...");
    447         mCreateButton.setEnabled(false);
    448         mCreateButton.addSelectionListener(new SelectionAdapter() {
    449             @Override
    450             public void widgetSelected(SelectionEvent e) {
    451                 if (mListener != null) {
    452                     mListener.onCreate();
    453                 }
    454             }
    455         });
    456 
    457         // ---- 2nd line: device/config/locale/theme Combos, create button.
    458 
    459         setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
    460         setLayout(gl = new GridLayout(cols, false));
    461         gl.marginHeight = 0;
    462         gl.horizontalSpacing = 0;
    463 
    464         mDeviceCombo = new Combo(this, SWT.DROP_DOWN | SWT.READ_ONLY);
    465         mDeviceCombo.setLayoutData(new GridData(
    466                 GridData.HORIZONTAL_ALIGN_FILL | GridData.GRAB_HORIZONTAL));
    467         mDeviceCombo.addSelectionListener(new SelectionAdapter() {
    468             @Override
    469             public void widgetSelected(SelectionEvent e) {
    470                 onDeviceChange(true /* recomputeLayout*/);
    471             }
    472         });
    473 
    474         mDeviceConfigCombo = new Combo(this, SWT.DROP_DOWN | SWT.READ_ONLY);
    475         mDeviceConfigCombo.setLayoutData(new GridData(
    476                 GridData.HORIZONTAL_ALIGN_FILL | GridData.GRAB_HORIZONTAL));
    477         mDeviceConfigCombo.addSelectionListener(new SelectionAdapter() {
    478             @Override
    479              public void widgetSelected(SelectionEvent e) {
    480                 onDeviceConfigChange();
    481             }
    482         });
    483 
    484         // first separator
    485         Label separator = new Label(this, SWT.SEPARATOR | SWT.VERTICAL);
    486         separator.setLayoutData(gd = new GridData(
    487                 GridData.VERTICAL_ALIGN_FILL | GridData.GRAB_VERTICAL));
    488         gd.heightHint = 0;
    489 
    490         mUiModeCombo = new Combo(this, SWT.DROP_DOWN | SWT.READ_ONLY);
    491         mUiModeCombo.setLayoutData(new GridData(GridData.HORIZONTAL_ALIGN_FILL
    492                 | GridData.GRAB_HORIZONTAL));
    493         for (UiMode mode : UiMode.values()) {
    494             mUiModeCombo.add(mode.getLongDisplayValue());
    495         }
    496         mUiModeCombo.addSelectionListener(new SelectionAdapter() {
    497             @Override
    498             public void widgetSelected(SelectionEvent e) {
    499                 onDockChange();
    500             }
    501         });
    502 
    503         mNightCombo = new Combo(this, SWT.DROP_DOWN | SWT.READ_ONLY);
    504         mNightCombo.setLayoutData(new GridData(GridData.HORIZONTAL_ALIGN_FILL
    505                 | GridData.GRAB_HORIZONTAL));
    506         for (NightMode mode : NightMode.values()) {
    507             mNightCombo.add(mode.getLongDisplayValue());
    508         }
    509         mNightCombo.addSelectionListener(new SelectionAdapter() {
    510             @Override
    511             public void widgetSelected(SelectionEvent e) {
    512                 onDayChange();
    513             }
    514         });
    515 
    516         mThemeCombo = new Combo(this, SWT.READ_ONLY | SWT.DROP_DOWN);
    517         mThemeCombo.setLayoutData(new GridData(
    518                 GridData.HORIZONTAL_ALIGN_FILL | GridData.GRAB_HORIZONTAL));
    519         mThemeCombo.setEnabled(false);
    520 
    521         mThemeCombo.addSelectionListener(new SelectionAdapter() {
    522             @Override
    523             public void widgetSelected(SelectionEvent e) {
    524                 onThemeChange();
    525             }
    526         });
    527     }
    528 
    529     // ---- Init and reset/reload methods ----
    530 
    531     /**
    532      * Sets the reference to the file being edited.
    533      * <p/>The UI is initialized in {@link #onXmlModelLoaded()} which is called as the XML model is
    534      * loaded (or reloaded as the SDK/target changes).
    535      *
    536      * @param file the file being opened
    537      *
    538      * @see #onXmlModelLoaded()
    539      * @see #replaceFile(IFile)
    540      * @see #changeFileOnNewConfig(IFile)
    541      */
    542     public void setFile(IFile file) {
    543         mEditedFile = file;
    544     }
    545 
    546     /**
    547      * Replaces the UI with a given file configuration. This is meant to answer the user
    548      * explicitly opening a different version of the same layout from the Package Explorer.
    549      * <p/>This attempts to keep the current config, but may change it if it's not compatible or
    550      * not the best match
    551      * <p/>This will NOT trigger a redraw event (will not call
    552      * {@link IConfigListener#onConfigurationChange()}.)
    553      * @param file the file being opened.
    554      */
    555     public void replaceFile(IFile file) {
    556         // if there is no previous selection, revert to default mode.
    557         if (mState.device == null) {
    558             setFile(file); // onTargetChanged will be called later.
    559             return;
    560         }
    561 
    562         mEditedFile = file;
    563         IProject iProject = mEditedFile.getProject();
    564         mResources = ResourceManager.getInstance().getProjectResources(iProject);
    565 
    566         ResourceFolder resFolder = ResourceManager.getInstance().getResourceFolder(file);
    567         mEditedConfig = resFolder.getConfiguration();
    568 
    569         mDisableUpdates++; // we do not want to trigger onXXXChange when setting
    570                            // new values in the widgets.
    571 
    572         try {
    573             // only attempt to do anything if the SDK and targets are loaded.
    574             LoadStatus sdkStatus = AdtPlugin.getDefault().getSdkLoadStatus();
    575             if (sdkStatus == LoadStatus.LOADED) {
    576                 LoadStatus targetStatus = Sdk.getCurrent().checkAndLoadTargetData(mProjectTarget,
    577                         null /*project*/);
    578 
    579                 if (targetStatus == LoadStatus.LOADED) {
    580 
    581                     // update the current config selection to make sure it's
    582                     // compatible with the new file
    583                     adaptConfigSelection(true /*needBestMatch*/);
    584 
    585                     // compute the final current config
    586                     computeCurrentConfig();
    587 
    588                     // update the string showing the config value
    589                     updateConfigDisplay(mEditedConfig);
    590                 }
    591             }
    592         } finally {
    593             mDisableUpdates--;
    594         }
    595     }
    596 
    597     /**
    598      * Updates the UI with a new file that was opened in response to a config change.
    599      * @param file the file being opened.
    600      *
    601      * @see #replaceFile(IFile)
    602      */
    603     public void changeFileOnNewConfig(IFile file) {
    604         mEditedFile = file;
    605         IProject iProject = mEditedFile.getProject();
    606         mResources = ResourceManager.getInstance().getProjectResources(iProject);
    607 
    608         ResourceFolder resFolder = ResourceManager.getInstance().getResourceFolder(file);
    609         mEditedConfig = resFolder.getConfiguration();
    610 
    611         // All that's needed is to update the string showing the config value
    612         // (since the config combo were chosen by the user).
    613         updateConfigDisplay(mEditedConfig);
    614     }
    615 
    616     /**
    617      * Responds to the event that the basic SDK information finished loading.
    618      * @param target the possibly new target object associated with the file being edited (in case
    619      * the SDK path was changed).
    620      */
    621     public void onSdkLoaded(IAndroidTarget target) {
    622         // a change to the SDK means that we need to check for new/removed devices.
    623         mSdkChanged = true;
    624 
    625         // store the new target.
    626         mProjectTarget = target;
    627 
    628         mDisableUpdates++; // we do not want to trigger onXXXChange when setting
    629                            // new values in the widgets.
    630         try {
    631             // this is going to be followed by a call to onTargetLoaded.
    632             // So we can only care about the layout devices in this case.
    633             initDevices();
    634             initTargets();
    635         } finally {
    636             mDisableUpdates--;
    637         }
    638     }
    639 
    640     /**
    641      * Answers to the XML model being loaded, either the first time or when the Target/SDK changes.
    642      * <p>This initializes the UI, either with the first compatible configuration found,
    643      * or attempts to restore a configuration if one is found to have been saved in the file
    644      * persistent storage.
    645      * <p>If the SDK or target are not loaded, nothing will happened (but the method must be called
    646      * back when those are loaded).
    647      * <p>The method automatically handles being called the first time after editor creation, or
    648      * being called after during SDK/Target changes (as long as {@link #onSdkLoaded(IAndroidTarget)}
    649      * is properly called).
    650      *
    651      * @see #storeState()
    652      * @see #onSdkLoaded(IAndroidTarget)
    653      */
    654     public AndroidTargetData onXmlModelLoaded() {
    655         AndroidTargetData targetData = null;
    656 
    657         // only attempt to do anything if the SDK and targets are loaded.
    658         LoadStatus sdkStatus = AdtPlugin.getDefault().getSdkLoadStatus();
    659         if (sdkStatus == LoadStatus.LOADED) {
    660             mDisableUpdates++; // we do not want to trigger onXXXChange when setting
    661 
    662             try {
    663                 // init the devices if needed (new SDK or first time going through here)
    664                 if (mSdkChanged || mFirstXmlModelChange) {
    665                     initDevices();
    666                     initTargets();
    667                 }
    668 
    669                 IProject iProject = mEditedFile.getProject();
    670 
    671                 Sdk currentSdk = Sdk.getCurrent();
    672                 if (currentSdk != null) {
    673                     mProjectTarget = currentSdk.getTarget(iProject);
    674                 }
    675 
    676                 LoadStatus targetStatus = LoadStatus.FAILED;
    677                 if (mProjectTarget != null) {
    678                     targetStatus = Sdk.getCurrent().checkAndLoadTargetData(mProjectTarget, null);
    679                     initTargets();
    680                 }
    681 
    682                 if (targetStatus == LoadStatus.LOADED) {
    683                     if (mResources == null) {
    684                         mResources = ResourceManager.getInstance().getProjectResources(iProject);
    685                     }
    686                     if (mEditedConfig == null) {
    687                         ResourceFolder resFolder = mResources.getResourceFolder(
    688                                 (IFolder) mEditedFile.getParent());
    689                         mEditedConfig = resFolder.getConfiguration();
    690                     }
    691 
    692                     targetData = Sdk.getCurrent().getTargetData(mProjectTarget);
    693 
    694                     // get the file stored state
    695                     boolean loadedConfigData = false;
    696                     String data = AdtPlugin.getFileProperty(mEditedFile, NAME_CONFIG_STATE);
    697                     if (mInitialState != null) {
    698                         data = mInitialState;
    699                         mInitialState = null;
    700                     }
    701                     if (data != null) {
    702                         loadedConfigData = mState.setData(data);
    703                     }
    704 
    705                     updateLocales();
    706 
    707                     // If the current state was loaded from the persistent storage, we update the
    708                     // UI with it and then try to adapt it (which will handle incompatible
    709                     // configuration).
    710                     // Otherwise, just look for the first compatible configuration.
    711                     if (loadedConfigData) {
    712                         // first make sure we have the config to adapt
    713                         selectDevice(mState.device);
    714                         fillConfigCombo(mState.configName);
    715 
    716                         adaptConfigSelection(false /*needBestMatch*/);
    717 
    718                         mUiModeCombo.select(UiMode.getIndex(mState.uiMode));
    719                         mNightCombo.select(NightMode.getIndex(mState.night));
    720                         mTargetCombo.select(mTargetList.indexOf(mState.target));
    721 
    722                         targetData = Sdk.getCurrent().getTargetData(mState.target);
    723                     } else {
    724                         findAndSetCompatibleConfig(false /*favorCurrentConfig*/);
    725 
    726                         // Default to modern layout lib
    727                         IAndroidTarget target = findDefaultRenderTarget();
    728                         if (target != null) {
    729                             targetData = Sdk.getCurrent().getTargetData(target);
    730                             mTargetCombo.select(mTargetList.indexOf(target));
    731                         }
    732                     }
    733 
    734                     // Update themes. This is done after updating the devices above,
    735                     // since we want to look at the chosen device size to decide
    736                     // what the default theme (for example, with Honeycomb we choose
    737                     // Holo as the default theme but only if the screen size is XLARGE
    738                     // (and of course only if the manifest does not specify another
    739                     // default theme).
    740                     updateThemes();
    741 
    742                     // update the string showing the config value
    743                     updateConfigDisplay(mEditedConfig);
    744 
    745                     // compute the final current config
    746                     computeCurrentConfig();
    747                 }
    748             } finally {
    749                 mDisableUpdates--;
    750                 mFirstXmlModelChange = false;
    751             }
    752         }
    753 
    754         return targetData;
    755     }
    756 
    757     /** Return the default render target to use, or null if no strong preference */
    758     private IAndroidTarget findDefaultRenderTarget() {
    759         // Default to layoutlib version 5
    760         Sdk current = Sdk.getCurrent();
    761         if (current != null) {
    762             IAndroidTarget projectTarget = current.getTarget(mEditedFile.getProject());
    763             int minProjectApi = Integer.MAX_VALUE;
    764             if (projectTarget != null) {
    765                 if (!projectTarget.isPlatform() && projectTarget.hasRenderingLibrary()) {
    766                     // Renderable non-platform targets are all going to be adequate (they
    767                     // will have at least version 5 of layoutlib) so use the project
    768                     // target as the render target.
    769                     return projectTarget;
    770                 }
    771 
    772                 if (projectTarget.getVersion().isPreview()
    773                         && projectTarget.hasRenderingLibrary()) {
    774                     // If the project target is a preview version, then just use it
    775                     return projectTarget;
    776                 }
    777 
    778                 minProjectApi = projectTarget.getVersion().getApiLevel();
    779             }
    780 
    781             // We want to pick a render target that contains at least version 5 (and
    782             // preferably version 6) of the layout library. To do this, we go through the
    783             // targets and pick the -smallest- API level that is both simultaneously at
    784             // least as big as the project API level, and supports layoutlib level 5+.
    785             IAndroidTarget best = null;
    786             int bestApiLevel = Integer.MAX_VALUE;
    787 
    788             for (IAndroidTarget target : current.getTargets()) {
    789                 // Non-platform targets are not chosen as the default render target
    790                 if (!target.isPlatform()) {
    791                     continue;
    792                 }
    793 
    794                 int apiLevel = target.getVersion().getApiLevel();
    795 
    796                 // Ignore targets that have a lower API level than the minimum project
    797                 // API level:
    798                 if (apiLevel < minProjectApi) {
    799                     continue;
    800                 }
    801 
    802                 // Look up the layout lib API level. This property is new so it will only
    803                 // be defined for version 6 or higher, which means non-null is adequate
    804                 // to see if this target is eligible:
    805                 String property = target.getProperty(PkgProps.LAYOUTLIB_API);
    806                 // In addition, Android 3.0 with API level 11 had version 5.0 which is adequate:
    807                 if (property != null || apiLevel >= 11) {
    808                     if (apiLevel < bestApiLevel) {
    809                         bestApiLevel = apiLevel;
    810                         best = target;
    811                     }
    812                 }
    813             }
    814 
    815             return best;
    816         }
    817 
    818         return null;
    819     }
    820 
    821     private static class ConfigBundle {
    822         FolderConfiguration config;
    823         int localeIndex;
    824         int dockModeIndex;
    825         int nightModeIndex;
    826 
    827         ConfigBundle() {
    828             config = new FolderConfiguration();
    829             localeIndex = 0;
    830             dockModeIndex = 0;
    831             nightModeIndex = 0;
    832         }
    833 
    834         ConfigBundle(ConfigBundle bundle) {
    835             config = new FolderConfiguration();
    836             config.set(bundle.config);
    837             localeIndex = bundle.localeIndex;
    838             dockModeIndex = bundle.dockModeIndex;
    839             nightModeIndex = bundle.nightModeIndex;
    840         }
    841     }
    842 
    843     private static class ConfigMatch {
    844         final FolderConfiguration testConfig;
    845         final LayoutDevice device;
    846         final String name;
    847         final ConfigBundle bundle;
    848 
    849         public ConfigMatch(FolderConfiguration testConfig,
    850                 LayoutDevice device, String name, ConfigBundle bundle) {
    851             this.testConfig = testConfig;
    852             this.device = device;
    853             this.name = name;
    854             this.bundle = bundle;
    855         }
    856 
    857         @Override
    858         public String toString() {
    859             return device.getName() + " - " + name;
    860         }
    861     }
    862 
    863     /**
    864      * Finds a device/config that can display {@link #mEditedConfig}.
    865      * <p/>Once found the device and config combos are set to the config.
    866      * <p/>If there is no compatible configuration, a custom one is created.
    867      * @param favorCurrentConfig if true, and no best match is found, don't change
    868      * the current config. This must only be true if the current config is compatible.
    869      */
    870     private void findAndSetCompatibleConfig(boolean favorCurrentConfig) {
    871         // list of compatible device/config/locale
    872         List<ConfigMatch> anyMatches = new ArrayList<ConfigMatch>();
    873 
    874         // list of actual best match (ie the file is a best match for the device/config)
    875         List<ConfigMatch> bestMatches = new ArrayList<ConfigMatch>();
    876 
    877         // get a locale that match the host locale roughly (may not be exact match on the region.)
    878         int localeHostMatch = getLocaleMatch();
    879 
    880         // build a list of combinations of non standard qualifiers to add to each device's
    881         // qualifier set when testing for a match.
    882         // These qualifiers are: locale, night-mode, car dock.
    883         List<ConfigBundle> configBundles = new ArrayList<ConfigBundle>(200);
    884 
    885         // If the edited file has locales, then we have to select a matching locale from
    886         // the list.
    887         // However, if it doesn't, we don't randomly take the first locale, we take one
    888         // matching the current host locale (making sure it actually exist in the project)
    889         int start, max;
    890         if (mEditedConfig.getLanguageQualifier() != null || localeHostMatch == -1) {
    891             // add all the locales
    892             start = 0;
    893             max = mLocaleList.size();
    894         } else {
    895             // only add the locale host match
    896             start = localeHostMatch;
    897             max = localeHostMatch + 1; // test is <
    898         }
    899 
    900         for (int i = start ; i < max ; i++) {
    901             ResourceQualifier[] l = mLocaleList.get(i);
    902 
    903             ConfigBundle bundle = new ConfigBundle();
    904             bundle.config.setLanguageQualifier((LanguageQualifier) l[LOCALE_LANG]);
    905             bundle.config.setRegionQualifier((RegionQualifier) l[LOCALE_REGION]);
    906 
    907             bundle.localeIndex = i;
    908             configBundles.add(bundle);
    909         }
    910 
    911         // add the dock mode to the bundle combinations.
    912         addDockModeToBundles(configBundles);
    913 
    914         // add the night mode to the bundle combinations.
    915         addNightModeToBundles(configBundles);
    916 
    917         addRenderTargetToBundles(configBundles);
    918 
    919         for (LayoutDevice device : mDeviceList) {
    920             for (DeviceConfig config : device.getConfigs()) {
    921 
    922                 // loop on the list of config bundles to create full configurations.
    923                 for (ConfigBundle bundle : configBundles) {
    924                     // create a new config with device config
    925                     FolderConfiguration testConfig = new FolderConfiguration();
    926                     testConfig.set(config.getConfig());
    927 
    928                     // add on top of it, the extra qualifiers from the bundle
    929                     testConfig.add(bundle.config);
    930 
    931                     if (mEditedConfig.isMatchFor(testConfig)) {
    932                         // this is a basic match. record it in case we don't find a match
    933                         // where the edited file is a best config.
    934                         anyMatches.add(new ConfigMatch(testConfig, device, config.getName(),
    935                                 bundle));
    936 
    937                         if (isCurrentFileBestMatchFor(testConfig)) {
    938                             // this is what we want.
    939                             bestMatches.add(new ConfigMatch(testConfig, device, config.getName(),
    940                                     bundle));
    941                         }
    942                     }
    943                 }
    944             }
    945         }
    946 
    947         if (bestMatches.size() == 0) {
    948             if (favorCurrentConfig) {
    949                 // quick check
    950                 if (mEditedConfig.isMatchFor(mCurrentConfig) == false) {
    951                     AdtPlugin.log(IStatus.ERROR,
    952                             "favorCurrentConfig can only be true if the current config is compatible");
    953                 }
    954 
    955                 // just display the warning
    956                 AdtPlugin.printErrorToConsole(mEditedFile.getProject(),
    957                         String.format(
    958                                 "'%1$s' is not a best match for any device/locale combination.",
    959                                 mEditedConfig.toDisplayString()),
    960                         String.format(
    961                                 "Displaying it with '%1$s'",
    962                                 mCurrentConfig.toDisplayString()));
    963             } else if (anyMatches.size() > 0) {
    964                 // select the best device anyway.
    965                 ConfigMatch match = selectConfigMatch(anyMatches);
    966                 selectDevice(mState.device = match.device);
    967                 fillConfigCombo(match.name);
    968                 mLocaleCombo.select(match.bundle.localeIndex);
    969                 mUiModeCombo.select(match.bundle.dockModeIndex);
    970                 mNightCombo.select(match.bundle.nightModeIndex);
    971 
    972                 // TODO: display a better warning!
    973                 computeCurrentConfig();
    974                 AdtPlugin.printErrorToConsole(mEditedFile.getProject(),
    975                         String.format(
    976                                 "'%1$s' is not a best match for any device/locale combination.",
    977                                 mEditedConfig.toDisplayString()),
    978                         String.format(
    979                                 "Displaying it with '%1$s' which is compatible, but will actually be displayed with another more specific version of the layout.",
    980                                 mCurrentConfig.toDisplayString()));
    981 
    982             } else {
    983                 // TODO: there is no device/config able to display the layout, create one.
    984                 // For the base config values, we'll take the first device and config,
    985                 // and replace whatever qualifier required by the layout file.
    986             }
    987         } else {
    988             ConfigMatch match = selectConfigMatch(bestMatches);
    989             selectDevice(mState.device = match.device);
    990             fillConfigCombo(match.name);
    991             mLocaleCombo.select(match.bundle.localeIndex);
    992             mUiModeCombo.select(match.bundle.dockModeIndex);
    993             mNightCombo.select(match.bundle.nightModeIndex);
    994         }
    995     }
    996 
    997     /**
    998      * Note: this comparator imposes orderings that are inconsistent with equals.
    999      */
   1000     private static class TabletConfigComparator implements Comparator<ConfigMatch> {
   1001         public int compare(ConfigMatch o1, ConfigMatch o2) {
   1002             ScreenSize ss1 = o1.testConfig.getScreenSizeQualifier().getValue();
   1003             ScreenSize ss2 = o2.testConfig.getScreenSizeQualifier().getValue();
   1004 
   1005             // X-LARGE is better than all others (which are considered identical)
   1006             // if both X-LARGE, then LANDSCAPE is better than all others (which are identical)
   1007 
   1008             if (ss1 == ScreenSize.XLARGE) {
   1009                 if (ss2 == ScreenSize.XLARGE) {
   1010                     ScreenOrientation so1 =
   1011                         o1.testConfig.getScreenOrientationQualifier().getValue();
   1012                     ScreenOrientation so2 =
   1013                         o2.testConfig.getScreenOrientationQualifier().getValue();
   1014 
   1015                     if (so1 == ScreenOrientation.LANDSCAPE) {
   1016                         if (so2 == ScreenOrientation.LANDSCAPE) {
   1017                             return 0;
   1018                         } else {
   1019                             return -1;
   1020                         }
   1021                     } else if (so2 == ScreenOrientation.LANDSCAPE) {
   1022                         return 1;
   1023                     } else {
   1024                         return 0;
   1025                     }
   1026                 } else {
   1027                     return -1;
   1028                 }
   1029             } else if (ss2 == ScreenSize.XLARGE) {
   1030                 return 1;
   1031             } else {
   1032                 return 0;
   1033             }
   1034         }
   1035     }
   1036 
   1037     /**
   1038      * Note: this comparator imposes orderings that are inconsistent with equals.
   1039      */
   1040     private static class PhoneConfigComparator implements Comparator<ConfigMatch> {
   1041 
   1042         private SparseIntArray mDensitySort = new SparseIntArray(4);
   1043 
   1044         public PhoneConfigComparator() {
   1045             // put the sort order for the density.
   1046             mDensitySort.put(Density.HIGH.getDpiValue(),   1);
   1047             mDensitySort.put(Density.MEDIUM.getDpiValue(), 2);
   1048             mDensitySort.put(Density.XHIGH.getDpiValue(),  3);
   1049             mDensitySort.put(Density.LOW.getDpiValue(),    4);
   1050         }
   1051 
   1052         public int compare(ConfigMatch o1, ConfigMatch o2) {
   1053             int dpi1 = Density.DEFAULT_DENSITY;
   1054             if (o1.testConfig.getDensityQualifier() != null) {
   1055                 dpi1 = o1.testConfig.getDensityQualifier().getValue().getDpiValue();
   1056                 dpi1 = mDensitySort.get(dpi1, 100 /* valueIfKeyNotFound*/);
   1057             }
   1058 
   1059             int dpi2 = Density.DEFAULT_DENSITY;
   1060             if (o2.testConfig.getDensityQualifier() != null) {
   1061                 dpi2 = o2.testConfig.getDensityQualifier().getValue().getDpiValue();
   1062                 dpi2 = mDensitySort.get(dpi2, 100 /* valueIfKeyNotFound*/);
   1063             }
   1064 
   1065             if (dpi1 == dpi2) {
   1066                 // portrait is better
   1067                 ScreenOrientation so1 =
   1068                     o1.testConfig.getScreenOrientationQualifier().getValue();
   1069                 ScreenOrientation so2 =
   1070                     o2.testConfig.getScreenOrientationQualifier().getValue();
   1071 
   1072                 if (so1 == ScreenOrientation.PORTRAIT) {
   1073                     if (so2 == ScreenOrientation.PORTRAIT) {
   1074                         return 0;
   1075                     } else {
   1076                         return -1;
   1077                     }
   1078                 } else if (so2 == ScreenOrientation.PORTRAIT) {
   1079                     return 1;
   1080                 } else {
   1081                     return 0;
   1082                 }
   1083             }
   1084 
   1085             return dpi1 - dpi2;
   1086         }
   1087     }
   1088 
   1089     private ConfigMatch selectConfigMatch(List<ConfigMatch> matches) {
   1090         // API 11-13: look for a x-large device
   1091         int apiLevel = mProjectTarget.getVersion().getApiLevel();
   1092         if (apiLevel >= 11 && apiLevel < 14) {
   1093             // TODO: Maybe check the compatible-screen tag in the manifest to figure out
   1094             // what kind of device should be used for display.
   1095             Collections.sort(matches, new TabletConfigComparator());
   1096         } else {
   1097             // lets look for a high density device
   1098             Collections.sort(matches, new PhoneConfigComparator());
   1099         }
   1100 
   1101         // Look at the currently active editor to see if it's a layout editor, and if so,
   1102         // look up its configuration and if the configuration is in our match list,
   1103         // use it. This means we "preserve" the current configuration when you open
   1104         // new layouts.
   1105         IWorkbench workbench = PlatformUI.getWorkbench();
   1106         IWorkbenchWindow activeWorkbenchWindow = workbench.getActiveWorkbenchWindow();
   1107         IWorkbenchPage page = activeWorkbenchWindow.getActivePage();
   1108         IEditorPart activeEditor = page.getActiveEditor();
   1109         if (activeEditor instanceof LayoutEditor
   1110                 && mEditedFile != null
   1111                 // (Only do this when the two files are in the same project)
   1112                 && ((LayoutEditor) activeEditor).getProject() == mEditedFile.getProject()) {
   1113             LayoutEditor editor = (LayoutEditor) activeEditor;
   1114             FolderConfiguration configuration = editor.getGraphicalEditor().getConfiguration();
   1115             if (configuration != null) {
   1116                 for (ConfigMatch match : matches) {
   1117                     if (configuration.equals(match.testConfig)) {
   1118                         return match;
   1119                     }
   1120                 }
   1121             }
   1122         }
   1123 
   1124         // the list has been sorted so that the first item is the best config
   1125         return matches.get(0);
   1126     }
   1127 
   1128     private void addRenderTargetToBundles(List<ConfigBundle> configBundles) {
   1129         Pair<ResourceQualifier[], IAndroidTarget> state = loadRenderState();
   1130         if (state != null) {
   1131             IAndroidTarget target = state.getSecond();
   1132             if (target != null) {
   1133                 int apiLevel = target.getVersion().getApiLevel();
   1134                 for (ConfigBundle bundle : configBundles) {
   1135                     bundle.config.setVersionQualifier(
   1136                             new VersionQualifier(apiLevel));
   1137                 }
   1138             }
   1139         }
   1140     }
   1141 
   1142     private void addDockModeToBundles(List<ConfigBundle> addConfig) {
   1143         ArrayList<ConfigBundle> list = new ArrayList<ConfigBundle>();
   1144 
   1145         // loop on each item and for each, add all variations of the dock modes
   1146         for (ConfigBundle bundle : addConfig) {
   1147             int index = 0;
   1148             for (UiMode mode : UiMode.values()) {
   1149                 ConfigBundle b = new ConfigBundle(bundle);
   1150                 b.config.setUiModeQualifier(new UiModeQualifier(mode));
   1151                 b.dockModeIndex = index++;
   1152                 list.add(b);
   1153             }
   1154         }
   1155 
   1156         addConfig.clear();
   1157         addConfig.addAll(list);
   1158     }
   1159 
   1160     private void addNightModeToBundles(List<ConfigBundle> addConfig) {
   1161         ArrayList<ConfigBundle> list = new ArrayList<ConfigBundle>();
   1162 
   1163         // loop on each item and for each, add all variations of the night modes
   1164         for (ConfigBundle bundle : addConfig) {
   1165             int index = 0;
   1166             for (NightMode mode : NightMode.values()) {
   1167                 ConfigBundle b = new ConfigBundle(bundle);
   1168                 b.config.setNightModeQualifier(new NightModeQualifier(mode));
   1169                 b.nightModeIndex = index++;
   1170                 list.add(b);
   1171             }
   1172         }
   1173 
   1174         addConfig.clear();
   1175         addConfig.addAll(list);
   1176     }
   1177 
   1178     /**
   1179      * Adapts the current device/config selection so that it's compatible with
   1180      * {@link #mEditedConfig}.
   1181      * <p/>If the current selection is compatible, nothing is changed.
   1182      * <p/>If it's not compatible, configs from the current devices are tested.
   1183      * <p/>If none are compatible, it reverts to
   1184      * {@link #findAndSetCompatibleConfig(FolderConfiguration)}
   1185      */
   1186     private void adaptConfigSelection(boolean needBestMatch) {
   1187         // check the device config (ie sans locale)
   1188         boolean needConfigChange = true; // if still true, we need to find another config.
   1189         boolean currentConfigIsCompatible = false;
   1190         int configIndex = mDeviceConfigCombo.getSelectionIndex();
   1191         if (configIndex != -1) {
   1192             String configName = mDeviceConfigCombo.getItem(configIndex);
   1193             FolderConfiguration currentConfig = mState.device.getFolderConfigByName(configName);
   1194             if (currentConfig != null && mEditedConfig.isMatchFor(currentConfig)) {
   1195                 currentConfigIsCompatible = true; // current config is compatible
   1196                 if (needBestMatch == false || isCurrentFileBestMatchFor(currentConfig)) {
   1197                     needConfigChange = false;
   1198                 }
   1199             }
   1200         }
   1201 
   1202         if (needConfigChange) {
   1203             // if the current config/locale isn't a correct match, then
   1204             // look for another config/locale in the same device.
   1205             FolderConfiguration testConfig = new FolderConfiguration();
   1206 
   1207             // first look in the current device.
   1208             String matchName = null;
   1209             int localeIndex = -1;
   1210             mainloop: for (DeviceConfig config : mState.device.getConfigs()) {
   1211                 testConfig.set(config.getConfig());
   1212 
   1213                 // loop on the locales.
   1214                 for (int i = 0 ; i < mLocaleList.size() ; i++) {
   1215                     ResourceQualifier[] locale = mLocaleList.get(i);
   1216 
   1217                     // update the test config with the locale qualifiers
   1218                     testConfig.setLanguageQualifier((LanguageQualifier)locale[LOCALE_LANG]);
   1219                     testConfig.setRegionQualifier((RegionQualifier)locale[LOCALE_REGION]);
   1220 
   1221                     if (mEditedConfig.isMatchFor(testConfig) &&
   1222                             isCurrentFileBestMatchFor(testConfig)) {
   1223                         matchName = config.getName();
   1224                         localeIndex = i;
   1225                         break mainloop;
   1226                     }
   1227                 }
   1228             }
   1229 
   1230             if (matchName != null) {
   1231                 selectConfig(matchName);
   1232                 mLocaleCombo.select(localeIndex);
   1233             } else {
   1234                 // no match in current device with any config/locale
   1235                 // attempt to find another device that can display this particular config.
   1236                 findAndSetCompatibleConfig(currentConfigIsCompatible);
   1237             }
   1238         }
   1239     }
   1240 
   1241     /**
   1242      * Finds a locale matching the config from a file.
   1243      * @param language the language qualifier or null if none is set.
   1244      * @param region the region qualifier or null if none is set.
   1245      * @return true if there was a change in the combobox as a result of applying the locale
   1246      */
   1247     private boolean setLocaleCombo(ResourceQualifier language, ResourceQualifier region) {
   1248         boolean changed = false;
   1249 
   1250         // find the locale match. Since the locale list is based on the content of the
   1251         // project resources there must be an exact match.
   1252         // The only trick is that the region could be null in the fileConfig but in our
   1253         // list of locales, this is represented as a RegionQualifier with value of
   1254         // FAKE_LOCALE_VALUE.
   1255         final int count = mLocaleList.size();
   1256         for (int i = 0 ; i < count ; i++) {
   1257             ResourceQualifier[] locale = mLocaleList.get(i);
   1258 
   1259             // the language qualifier in the locale list is never null.
   1260             if (locale[LOCALE_LANG].equals(language)) {
   1261                 // region comparison is more complex, as the region could be null.
   1262                 if (region == null) {
   1263                     if (RegionQualifier.FAKE_REGION_VALUE.equals(
   1264                             ((RegionQualifier)locale[LOCALE_REGION]).getValue())) {
   1265                         // match!
   1266                         if (mLocaleCombo.getSelectionIndex() != i) {
   1267                             mLocaleCombo.select(i);
   1268                             changed = true;
   1269                         }
   1270                         break;
   1271                     }
   1272                 } else if (region.equals(locale[LOCALE_REGION])) {
   1273                     // match!
   1274                     if (mLocaleCombo.getSelectionIndex() != i) {
   1275                         mLocaleCombo.select(i);
   1276                         changed = true;
   1277                     }
   1278                     break;
   1279                 }
   1280             }
   1281         }
   1282 
   1283         return changed;
   1284     }
   1285 
   1286     private void updateConfigDisplay(FolderConfiguration fileConfig) {
   1287         String current = fileConfig.toDisplayString();
   1288         String layoutLabel = current != null ? current : "(Default)";
   1289         mCurrentLayoutLabel.setText(layoutLabel);
   1290         mCurrentLayoutLabel.setToolTipText(layoutLabel);
   1291     }
   1292 
   1293     private void saveState() {
   1294         if (mDisableUpdates == 0) {
   1295             int index = mDeviceConfigCombo.getSelectionIndex();
   1296             if (index != -1) {
   1297                 mState.configName = mDeviceConfigCombo.getItem(index);
   1298             } else {
   1299                 mState.configName = null;
   1300             }
   1301 
   1302             // since the locales are relative to the project, only keeping the index is enough
   1303             index = mLocaleCombo.getSelectionIndex();
   1304             if (index != -1) {
   1305                 mState.locale = mLocaleList.get(index);
   1306             } else {
   1307                 mState.locale = null;
   1308             }
   1309 
   1310             index = mThemeCombo.getSelectionIndex();
   1311             if (index != -1) {
   1312                 mState.theme = mThemeCombo.getItem(index);
   1313             }
   1314 
   1315             index = mUiModeCombo.getSelectionIndex();
   1316             if (index != -1) {
   1317                 mState.uiMode = UiMode.getByIndex(index);
   1318             }
   1319 
   1320             index = mNightCombo.getSelectionIndex();
   1321             if (index != -1) {
   1322                 mState.night = NightMode.getByIndex(index);
   1323             }
   1324 
   1325             index = mTargetCombo.getSelectionIndex();
   1326             if (index != -1) {
   1327                 mState.target = mTargetList.get(index);
   1328             }
   1329         }
   1330     }
   1331 
   1332     /**
   1333      * Stores the current config selection into the edited file.
   1334      */
   1335     public void storeState() {
   1336         AdtPlugin.setFileProperty(mEditedFile, NAME_CONFIG_STATE, mState.getData());
   1337     }
   1338 
   1339     /**
   1340      * Updates the locale combo.
   1341      * This must be called from the UI thread.
   1342      */
   1343     public void updateLocales() {
   1344         if (mListener == null) {
   1345             return; // can't do anything w/o it.
   1346         }
   1347 
   1348         mDisableUpdates++;
   1349 
   1350         try {
   1351             // Reset the combo
   1352             mLocaleCombo.removeAll();
   1353             mLocaleList.clear();
   1354 
   1355             SortedSet<String> languages = null;
   1356             boolean hasLocale = false;
   1357 
   1358             // get the languages from the project.
   1359             ResourceRepository projectRes = mListener.getProjectResources();
   1360 
   1361             // in cases where the opened file is not linked to a project, this could be null.
   1362             if (projectRes != null) {
   1363                 // now get the languages from the project.
   1364                 languages = projectRes.getLanguages();
   1365 
   1366                 for (String language : languages) {
   1367                     hasLocale = true;
   1368 
   1369                     LanguageQualifier langQual = new LanguageQualifier(language);
   1370 
   1371                     // find the matching regions and add them
   1372                     SortedSet<String> regions = projectRes.getRegions(language);
   1373                     for (String region : regions) {
   1374                         mLocaleCombo.add(
   1375                                 String.format("%1$s / %2$s", language, region));
   1376                         RegionQualifier regionQual = new RegionQualifier(region);
   1377                         mLocaleList.add(new ResourceQualifier[] { langQual, regionQual });
   1378                     }
   1379 
   1380                     // now the entry for the other regions the language alone
   1381                     if (regions.size() > 0) {
   1382                         mLocaleCombo.add(String.format("%1$s / Other", language));
   1383                     } else {
   1384                         mLocaleCombo.add(String.format("%1$s / Any", language));
   1385                     }
   1386                     // create a region qualifier that will never be matched by qualified resources.
   1387                     mLocaleList.add(new ResourceQualifier[] {
   1388                             langQual,
   1389                             new RegionQualifier(RegionQualifier.FAKE_REGION_VALUE)
   1390                     });
   1391                 }
   1392             }
   1393 
   1394             // add a locale not present in the project resources. This will let the dev
   1395             // tests his/her default values.
   1396             if (hasLocale) {
   1397                 mLocaleCombo.add("Other");
   1398             } else {
   1399                 mLocaleCombo.add("Any locale");
   1400             }
   1401 
   1402             // create language/region qualifier that will never be matched by qualified resources.
   1403             mLocaleList.add(new ResourceQualifier[] {
   1404                     new LanguageQualifier(LanguageQualifier.FAKE_LANG_VALUE),
   1405                     new RegionQualifier(RegionQualifier.FAKE_REGION_VALUE)
   1406             });
   1407 
   1408             if (mState.locale != null) {
   1409                 // FIXME: this may fails if the layout was deleted (and was the last one to have
   1410                 // that local. (we have other problem in this case though)
   1411                 setLocaleCombo(mState.locale[LOCALE_LANG],
   1412                         mState.locale[LOCALE_REGION]);
   1413             } else {
   1414                 mLocaleCombo.select(0);
   1415             }
   1416 
   1417             mThemeCombo.getParent().layout();
   1418         } finally {
   1419             mDisableUpdates--;
   1420         }
   1421     }
   1422 
   1423     private int getLocaleMatch() {
   1424         Locale locale = Locale.getDefault();
   1425         if (locale != null) {
   1426             String currentLanguage = locale.getLanguage();
   1427             String currentRegion = locale.getCountry();
   1428 
   1429             final int count = mLocaleList.size();
   1430             for (int l = 0 ; l < count ; l++) {
   1431                 ResourceQualifier[] localeArray = mLocaleList.get(l);
   1432                 LanguageQualifier langQ = (LanguageQualifier)localeArray[LOCALE_LANG];
   1433                 RegionQualifier regionQ = (RegionQualifier)localeArray[LOCALE_REGION];
   1434 
   1435                 // there's always a ##/Other or ##/Any (which is the same, the region
   1436                 // contains FAKE_REGION_VALUE). If we don't find a perfect region match
   1437                 // we take the fake region. Since it's last in the list, this makes the
   1438                 // test easy.
   1439                 if (langQ.getValue().equals(currentLanguage) &&
   1440                         (regionQ.getValue().equals(currentRegion) ||
   1441                          regionQ.getValue().equals(RegionQualifier.FAKE_REGION_VALUE))) {
   1442                     return l;
   1443                 }
   1444             }
   1445 
   1446             // if no locale match the current local locale, it's likely that it is
   1447             // the default one which is the last one.
   1448             return count - 1;
   1449         }
   1450 
   1451         return -1;
   1452     }
   1453 
   1454     /**
   1455      * Updates the theme combo.
   1456      * This must be called from the UI thread.
   1457      */
   1458     private void updateThemes() {
   1459         if (mListener == null) {
   1460             return; // can't do anything w/o it.
   1461         }
   1462 
   1463         ResourceRepository frameworkRes = mListener.getFrameworkResources(getRenderingTarget());
   1464 
   1465         mDisableUpdates++;
   1466 
   1467         try {
   1468             // Reset the combo
   1469             mThemeCombo.removeAll();
   1470             mIsProjectTheme.clear();
   1471 
   1472             ArrayList<String> themes = new ArrayList<String>();
   1473             String includedIn = mListener.getIncludedWithin();
   1474 
   1475             // First list any themes that are declared by the manifest
   1476             if (mEditedFile != null) {
   1477                 IProject project = mEditedFile.getProject();
   1478                 ManifestInfo manifest = ManifestInfo.get(project);
   1479 
   1480                 // Look up the screen size for the current configuration
   1481                 ScreenSize screenSize = null;
   1482                 if (mState.device != null) {
   1483                     List<DeviceConfig> configs = mState.device.getConfigs();
   1484                     for (DeviceConfig config : configs) {
   1485                         ScreenSizeQualifier qualifier =
   1486                             config.getConfig().getScreenSizeQualifier();
   1487                         screenSize = qualifier.getValue();
   1488                         break;
   1489                     }
   1490                 }
   1491                 // Look up the default/fallback theme to use for this project (which
   1492                 // depends on the screen size when no particular theme is specified
   1493                 // in the manifest)
   1494                 String defaultTheme = manifest.getDefaultTheme(mState.target, screenSize);
   1495 
   1496                 Map<String, String> activityThemes = manifest.getActivityThemes();
   1497                 String pkg = manifest.getPackage();
   1498                 String preferred = null;
   1499                 boolean isIncluded = includedIn != null;
   1500                 if (mState.theme == null || isIncluded) {
   1501                     String layoutName = ResourceHelper.getLayoutName(mEditedFile);
   1502 
   1503                     // If we are rendering a layout in included context, pick the theme
   1504                     // from the outer layout instead
   1505                     if (includedIn != null) {
   1506                         layoutName = includedIn;
   1507                     }
   1508 
   1509                     String activity = ManifestInfo.guessActivity(project, layoutName, pkg);
   1510                     if (activity != null) {
   1511                         preferred = activityThemes.get(activity);
   1512                     }
   1513                     if (preferred == null) {
   1514                         preferred = defaultTheme;
   1515                     }
   1516                     String preferredTheme = ResourceHelper.styleToTheme(preferred);
   1517                     if (includedIn == null) {
   1518                         mState.theme = preferredTheme;
   1519                     }
   1520                     boolean isProjectTheme = !preferred.startsWith(PREFIX_ANDROID_STYLE);
   1521                     mThemeCombo.add(preferredTheme);
   1522                     mIsProjectTheme.add(Boolean.valueOf(isProjectTheme));
   1523 
   1524                     mThemeCombo.add(THEME_SEPARATOR);
   1525                     mIsProjectTheme.add(Boolean.FALSE);
   1526                 }
   1527 
   1528                 // Create a sorted list of unique themes referenced in the manifest
   1529                 // (sort alphabetically, but place the preferred theme at the
   1530                 // top of the list)
   1531                 Set<String> themeSet = new HashSet<String>(activityThemes.values());
   1532                 themeSet.add(defaultTheme);
   1533                 List<String> themeList = new ArrayList<String>(themeSet);
   1534                 final String first = preferred;
   1535                 Collections.sort(themeList, new Comparator<String>() {
   1536                     public int compare(String s1, String s2) {
   1537                         if (s1 == first) {
   1538                             return -1;
   1539                         } else if (s1 == first) {
   1540                             return 1;
   1541                         } else {
   1542                             return s1.compareTo(s2);
   1543                         }
   1544                     }
   1545                 });
   1546 
   1547                 if (themeList.size() > 1 ||
   1548                         (themeList.size() == 1 && (preferred == null ||
   1549                                 !preferred.equals(themeList.get(0))))) {
   1550                     for (String style : themeList) {
   1551                         String theme = ResourceHelper.styleToTheme(style);
   1552 
   1553                         // Initialize the chosen theme to the first item
   1554                         // in the used theme list (that's what would be chosen
   1555                         // anyway) such that we stop attempting to look up
   1556                         // the associated activity (during initialization,
   1557                         // this method can be called repeatedly.)
   1558                         if (mState.theme == null) {
   1559                             mState.theme = theme;
   1560                         }
   1561 
   1562                         boolean isProjectTheme = !style.startsWith(PREFIX_ANDROID_STYLE);
   1563                         mThemeCombo.add(theme);
   1564                         mIsProjectTheme.add(Boolean.valueOf(isProjectTheme));
   1565                     }
   1566                     mThemeCombo.add(THEME_SEPARATOR);
   1567                     mIsProjectTheme.add(Boolean.FALSE);
   1568                 }
   1569             }
   1570 
   1571             // now get the themes and languages from the project.
   1572             int projectThemeCount = 0;
   1573             ResourceRepository projectRes = mListener.getProjectResources();
   1574             // in cases where the opened file is not linked to a project, this could be null.
   1575             if (projectRes != null) {
   1576                 // get the configured resources for the project
   1577                 Map<ResourceType, Map<String, ResourceValue>> configuredProjectRes =
   1578                     mListener.getConfiguredProjectResources();
   1579 
   1580                 if (configuredProjectRes != null) {
   1581                     // get the styles.
   1582                     Map<String, ResourceValue> styleMap = configuredProjectRes.get(
   1583                             ResourceType.STYLE);
   1584 
   1585                     if (styleMap != null) {
   1586                         // collect the themes out of all the styles, ie styles that extend,
   1587                         // directly or indirectly a platform theme.
   1588                         for (ResourceValue value : styleMap.values()) {
   1589                             if (isTheme(value, styleMap)) {
   1590                                 themes.add(value.getName());
   1591                             }
   1592                         }
   1593 
   1594                         Collections.sort(themes);
   1595 
   1596                         for (String theme : themes) {
   1597                             mThemeCombo.add(theme);
   1598                             mIsProjectTheme.add(Boolean.TRUE);
   1599                         }
   1600                     }
   1601                 }
   1602                 projectThemeCount = themes.size();
   1603                 themes.clear();
   1604             }
   1605 
   1606             // get the themes, and languages from the Framework.
   1607             if (frameworkRes != null) {
   1608                 // get the configured resources for the framework
   1609                 Map<ResourceType, Map<String, ResourceValue>> frameworResources =
   1610                     frameworkRes.getConfiguredResources(getCurrentConfig());
   1611 
   1612                 if (frameworResources != null) {
   1613                     // get the styles.
   1614                     Map<String, ResourceValue> styles = frameworResources.get(ResourceType.STYLE);
   1615 
   1616 
   1617                     // collect the themes out of all the styles.
   1618                     for (ResourceValue value : styles.values()) {
   1619                         String name = value.getName();
   1620                         if (name.startsWith("Theme.") || name.equals("Theme")) {
   1621                             themes.add(value.getName());
   1622                         }
   1623                     }
   1624 
   1625                     // sort them and add them to the combo
   1626                     Collections.sort(themes);
   1627 
   1628                     if (projectThemeCount > 0 && themes.size() > 0) {
   1629                         mThemeCombo.add(THEME_SEPARATOR);
   1630                         mIsProjectTheme.add(Boolean.FALSE);
   1631                     }
   1632 
   1633                     for (String theme : themes) {
   1634                         mThemeCombo.add(theme);
   1635                         mIsProjectTheme.add(Boolean.FALSE);
   1636                     }
   1637 
   1638                     themes.clear();
   1639                 }
   1640             }
   1641 
   1642             // try to reselect the previous theme.
   1643             boolean needDefaultSelection = true;
   1644 
   1645             if (mState.theme != null && includedIn == null) {
   1646                 final int count = mThemeCombo.getItemCount();
   1647                 for (int i = 0 ; i < count ; i++) {
   1648                     if (mState.theme.equals(mThemeCombo.getItem(i))) {
   1649                         mThemeCombo.select(i);
   1650                         needDefaultSelection = false;
   1651                         mThemeCombo.setEnabled(true);
   1652                         break;
   1653                     }
   1654                 }
   1655             }
   1656 
   1657             if (needDefaultSelection) {
   1658                 if (mThemeCombo.getItemCount() > 0) {
   1659                     mThemeCombo.select(0);
   1660                     mThemeCombo.setEnabled(true);
   1661                 } else {
   1662                     mThemeCombo.setEnabled(false);
   1663                 }
   1664             }
   1665 
   1666             mThemeCombo.getParent().layout();
   1667         } finally {
   1668             mDisableUpdates--;
   1669         }
   1670 
   1671         assert mIsProjectTheme.size() == mThemeCombo.getItemCount();
   1672     }
   1673 
   1674     // ---- getters for the config selection values ----
   1675 
   1676     public FolderConfiguration getEditedConfig() {
   1677         return mEditedConfig;
   1678     }
   1679 
   1680     public FolderConfiguration getCurrentConfig() {
   1681         return mCurrentConfig;
   1682     }
   1683 
   1684     public void getCurrentConfig(FolderConfiguration config) {
   1685         config.set(mCurrentConfig);
   1686     }
   1687 
   1688     /**
   1689      * Returns the currently selected {@link Density}. This is guaranteed to be non null.
   1690      */
   1691     public Density getDensity() {
   1692         if (mCurrentConfig != null) {
   1693             DensityQualifier qual = mCurrentConfig.getDensityQualifier();
   1694             if (qual != null) {
   1695                 // just a sanity check
   1696                 Density d = qual.getValue();
   1697                 if (d != Density.NODPI) {
   1698                     return d;
   1699                 }
   1700             }
   1701         }
   1702 
   1703         // no config? return medium as the default density.
   1704         return Density.MEDIUM;
   1705     }
   1706 
   1707     /**
   1708      * Returns the current device xdpi.
   1709      */
   1710     public float getXDpi() {
   1711         if (mState.device != null) {
   1712             float dpi = mState.device.getXDpi();
   1713             if (Float.isNaN(dpi) == false) {
   1714                 return dpi;
   1715             }
   1716         }
   1717 
   1718         // get the pixel density as the density.
   1719         return getDensity().getDpiValue();
   1720     }
   1721 
   1722     /**
   1723      * Returns the current device ydpi.
   1724      */
   1725     public float getYDpi() {
   1726         if (mState.device != null) {
   1727             float dpi = mState.device.getYDpi();
   1728             if (Float.isNaN(dpi) == false) {
   1729                 return dpi;
   1730             }
   1731         }
   1732 
   1733         // get the pixel density as the density.
   1734         return getDensity().getDpiValue();
   1735     }
   1736 
   1737     public Rect getScreenBounds() {
   1738         // get the orientation from the current device config
   1739         ScreenOrientationQualifier qual = mCurrentConfig.getScreenOrientationQualifier();
   1740         ScreenOrientation orientation = ScreenOrientation.PORTRAIT;
   1741         if (qual != null) {
   1742             orientation = qual.getValue();
   1743         }
   1744 
   1745         // get the device screen dimension
   1746         ScreenDimensionQualifier qual2 = mCurrentConfig.getScreenDimensionQualifier();
   1747         int s1, s2;
   1748         if (qual2 != null) {
   1749             s1 = qual2.getValue1();
   1750             s2 = qual2.getValue2();
   1751         } else {
   1752             s1 = 480;
   1753             s2 = 320;
   1754         }
   1755 
   1756         switch (orientation) {
   1757             default:
   1758             case PORTRAIT:
   1759                 return new Rect(0, 0, s2, s1);
   1760             case LANDSCAPE:
   1761                 return new Rect(0, 0, s1, s2);
   1762             case SQUARE:
   1763                 return new Rect(0, 0, s1, s1);
   1764         }
   1765     }
   1766 
   1767     /**
   1768      * Returns the current theme, or null if the combo has no selection.
   1769      *
   1770      * @return the theme name, or null
   1771      */
   1772     public String getTheme() {
   1773         int themeIndex = mThemeCombo.getSelectionIndex();
   1774         if (themeIndex != -1) {
   1775             return mThemeCombo.getItem(themeIndex);
   1776         }
   1777 
   1778         return null;
   1779     }
   1780 
   1781     /**
   1782      * Returns the current device string, or null if the combo has no selection.
   1783      *
   1784      * @return the device name, or null
   1785      */
   1786     public String getDevice() {
   1787         int deviceIndex = mDeviceCombo.getSelectionIndex();
   1788         if (deviceIndex != -1) {
   1789             return mDeviceCombo.getItem(deviceIndex);
   1790         }
   1791 
   1792         return null;
   1793     }
   1794 
   1795     /**
   1796      * Returns whether the current theme selection is a project theme.
   1797      * <p/>The returned value is meaningless if {@link #getTheme()} returns <code>null</code>.
   1798      * @return true for project theme, false for framework theme
   1799      */
   1800     public boolean isProjectTheme() {
   1801         return mIsProjectTheme.get(mThemeCombo.getSelectionIndex()).booleanValue();
   1802     }
   1803 
   1804     public IAndroidTarget getRenderingTarget() {
   1805         int index = mTargetCombo.getSelectionIndex();
   1806         if (index >= 0) {
   1807             return mTargetList.get(index);
   1808         }
   1809 
   1810         return null;
   1811     }
   1812 
   1813     /**
   1814      * Loads the list of {@link IAndroidTarget} and inits the UI with it.
   1815      */
   1816     private void initTargets() {
   1817         mTargetCombo.removeAll();
   1818         mTargetList.clear();
   1819 
   1820         Sdk currentSdk = Sdk.getCurrent();
   1821         if (currentSdk != null) {
   1822             IAndroidTarget[] targets = currentSdk.getTargets();
   1823             int match = -1;
   1824             for (int i = 0 ; i < targets.length; i++) {
   1825                 // FIXME: add check based on project minSdkVersion
   1826                 if (targets[i].hasRenderingLibrary()) {
   1827                     mTargetCombo.add(targets[i].getShortClasspathName());
   1828                     mTargetList.add(targets[i]);
   1829 
   1830                     if (mRenderingTarget != null) {
   1831                         // use equals because the rendering could be from a previous SDK, so
   1832                         // it may not be the same instance.
   1833                         if (mRenderingTarget.equals(targets[i])) {
   1834                             match = mTargetList.indexOf(targets[i]);
   1835                         }
   1836                     } else if (mProjectTarget == targets[i]) {
   1837                         match = mTargetList.indexOf(targets[i]);
   1838                     }
   1839                 }
   1840             }
   1841 
   1842             mTargetCombo.setEnabled(mTargetList.size() > 1);
   1843             if (match == -1) {
   1844                 mTargetCombo.deselectAll();
   1845 
   1846                 // the rendering target is the same as the project.
   1847                 mRenderingTarget = mProjectTarget;
   1848             } else {
   1849                 mTargetCombo.select(match);
   1850 
   1851                 // set the rendering target to the new object.
   1852                 mRenderingTarget = mTargetList.get(match);
   1853             }
   1854         }
   1855     }
   1856 
   1857     /**
   1858      * Loads the list of {@link LayoutDevice} and inits the UI with it.
   1859      */
   1860     private void initDevices() {
   1861         mDeviceList = null;
   1862 
   1863         Sdk sdk = Sdk.getCurrent();
   1864         if (sdk != null) {
   1865             LayoutDeviceManager manager = sdk.getLayoutDeviceManager();
   1866             mDeviceList = manager.getCombinedList();
   1867         }
   1868 
   1869 
   1870         // remove older devices if applicable
   1871         mDeviceCombo.removeAll();
   1872         mDeviceConfigCombo.removeAll();
   1873 
   1874         // fill with the devices
   1875         if (mDeviceList != null) {
   1876             for (LayoutDevice device : mDeviceList) {
   1877                 mDeviceCombo.add(device.getName());
   1878             }
   1879             mDeviceCombo.select(0);
   1880 
   1881             if (mDeviceList.size() > 0) {
   1882                 List<DeviceConfig> configs = mDeviceList.get(0).getConfigs();
   1883                 for (DeviceConfig config : configs) {
   1884                     mDeviceConfigCombo.add(config.getName());
   1885                 }
   1886                 mDeviceConfigCombo.select(0);
   1887                 if (configs.size() == 1) {
   1888                     mDeviceConfigCombo.setEnabled(false);
   1889                 }
   1890             }
   1891         }
   1892 
   1893         // add the custom item
   1894         mDeviceCombo.add("Custom...");
   1895     }
   1896 
   1897     /**
   1898      * Selects a given {@link LayoutDevice} in the device combo, if it is found.
   1899      * @param device the device to select
   1900      * @return true if the device was found.
   1901      */
   1902     private boolean selectDevice(LayoutDevice device) {
   1903         final int count = mDeviceList.size();
   1904         for (int i = 0 ; i < count ; i++) {
   1905             // since device comes from mDeviceList, we can use the == operator.
   1906             if (device == mDeviceList.get(i)) {
   1907                 mDeviceCombo.select(i);
   1908                 return true;
   1909             }
   1910         }
   1911 
   1912         return false;
   1913     }
   1914 
   1915     /**
   1916      * Selects a config by name.
   1917      * @param name the name of the config to select.
   1918      */
   1919     private void selectConfig(String name) {
   1920         final int count = mDeviceConfigCombo.getItemCount();
   1921         for (int i = 0 ; i < count ; i++) {
   1922             String item = mDeviceConfigCombo.getItem(i);
   1923             if (name.equals(item)) {
   1924                 mDeviceConfigCombo.select(i);
   1925                 return;
   1926             }
   1927         }
   1928     }
   1929 
   1930     /**
   1931      * Called when the selection of the device combo changes.
   1932      * @param recomputeLayout
   1933      */
   1934     private void onDeviceChange(boolean recomputeLayout) {
   1935         // because changing the content of a combo triggers a change event, respect the
   1936         // mDisableUpdates flag
   1937         if (mDisableUpdates > 0) {
   1938             return;
   1939         }
   1940 
   1941         String newConfigName = null;
   1942 
   1943         int deviceIndex = mDeviceCombo.getSelectionIndex();
   1944         if (deviceIndex != -1) {
   1945             // check if the user is asking for the custom item
   1946             if (deviceIndex == mDeviceCombo.getItemCount() - 1) {
   1947                 onCustomDeviceConfig();
   1948                 return;
   1949             }
   1950 
   1951             // get the previous config, so that we can look for a close match
   1952             if (mState.device != null) {
   1953                 int index = mDeviceConfigCombo.getSelectionIndex();
   1954                 if (index != -1) {
   1955                     FolderConfiguration oldConfig = mState.device.getFolderConfigByName(
   1956                             mDeviceConfigCombo.getItem(index));
   1957 
   1958                     LayoutDevice newDevice = mDeviceList.get(deviceIndex);
   1959 
   1960                     newConfigName = getClosestMatch(oldConfig, newDevice.getConfigs());
   1961                 }
   1962             }
   1963 
   1964             mState.device = mDeviceList.get(deviceIndex);
   1965         } else {
   1966             mState.device = null;
   1967         }
   1968 
   1969         fillConfigCombo(newConfigName);
   1970 
   1971         computeCurrentConfig();
   1972 
   1973         if (recomputeLayout) {
   1974             onDeviceConfigChange();
   1975         }
   1976     }
   1977 
   1978     /**
   1979      * Handles a user request for the {@link ConfigManagerDialog}.
   1980      */
   1981     private void onCustomDeviceConfig() {
   1982         ConfigManagerDialog dialog = new ConfigManagerDialog(getShell());
   1983         dialog.open();
   1984 
   1985         // save the user devices
   1986         Sdk.getCurrent().getLayoutDeviceManager().save();
   1987 
   1988         // Update the UI with no triggered event
   1989         mDisableUpdates++;
   1990 
   1991         try {
   1992             LayoutDevice oldCurrent = mState.device;
   1993 
   1994             // but first, update the device combo
   1995             initDevices();
   1996 
   1997             // attempts to reselect the current device.
   1998             if (selectDevice(oldCurrent)) {
   1999                 // current device still exists.
   2000                 // reselect the config
   2001                 selectConfig(mState.configName);
   2002 
   2003                 // reset the UI as if it was just a replacement file, since we can keep
   2004                 // the current device (and possibly config).
   2005                 adaptConfigSelection(false /*needBestMatch*/);
   2006 
   2007             } else {
   2008                 // find a new device/config to match the current file.
   2009                 findAndSetCompatibleConfig(false /*favorCurrentConfig*/);
   2010             }
   2011         } finally {
   2012             mDisableUpdates--;
   2013         }
   2014 
   2015         // recompute the current config
   2016         computeCurrentConfig();
   2017 
   2018         // force a redraw
   2019         onDeviceChange(true /*recomputeLayout*/);
   2020     }
   2021 
   2022     /**
   2023      * Attempts to find a close config among a list
   2024      * @param oldConfig the reference config.
   2025      * @param configs the list of config to search through
   2026      * @return the name of the closest config match, or possibly null if no configs are compatible
   2027      * (this can only happen if the configs don't have a single qualifier that is the same).
   2028      */
   2029     private String getClosestMatch(FolderConfiguration oldConfig, List<DeviceConfig> configs) {
   2030 
   2031         // create 2 lists as we're going to go through one and put the candidates in the other.
   2032         ArrayList<DeviceConfig> list1 = new ArrayList<DeviceConfig>();
   2033         ArrayList<DeviceConfig> list2 = new ArrayList<DeviceConfig>();
   2034 
   2035         list1.addAll(configs);
   2036 
   2037         final int count = FolderConfiguration.getQualifierCount();
   2038         for (int i = 0 ; i < count ; i++) {
   2039             // compute the new candidate list by only taking configs that have
   2040             // the same i-th qualifier as the old config
   2041             for (DeviceConfig c : list1) {
   2042                 ResourceQualifier oldQualifier = oldConfig.getQualifier(i);
   2043 
   2044                 FolderConfiguration folderConfig = c.getConfig();
   2045                 ResourceQualifier newQualifier = folderConfig.getQualifier(i);
   2046 
   2047                 if (oldQualifier == null) {
   2048                     if (newQualifier == null) {
   2049                         list2.add(c);
   2050                     }
   2051                 } else if (oldQualifier.equals(newQualifier)) {
   2052                     list2.add(c);
   2053                 }
   2054             }
   2055 
   2056             // at any moment if the new candidate list contains only one match, its name
   2057             // is returned.
   2058             if (list2.size() == 1) {
   2059                 return list2.get(0).getName();
   2060             }
   2061 
   2062             // if the list is empty, then all the new configs failed. It is considered ok, and
   2063             // we move to the next qualifier anyway. This way, if a qualifier is different for
   2064             // all new configs it is simply ignored.
   2065             if (list2.size() != 0) {
   2066                 // move the candidates back into list1.
   2067                 list1.clear();
   2068                 list1.addAll(list2);
   2069                 list2.clear();
   2070             }
   2071         }
   2072 
   2073         // the only way to reach this point is if there's an exact match.
   2074         // (if there are more than one, then there's a duplicate config and it doesn't matter,
   2075         // we take the first one).
   2076         if (list1.size() > 0) {
   2077             return list1.get(0).getName();
   2078         }
   2079 
   2080         return null;
   2081     }
   2082 
   2083     /**
   2084      * fills the config combo with new values based on {@link #mState}.device.
   2085      * @param refName an optional name. if set the selection will match this name (if found)
   2086      */
   2087     private void fillConfigCombo(String refName) {
   2088         mDeviceConfigCombo.removeAll();
   2089 
   2090         if (mState.device != null) {
   2091             int selectionIndex = 0;
   2092             int i = 0;
   2093 
   2094             for (DeviceConfig config : mState.device.getConfigs()) {
   2095                 mDeviceConfigCombo.add(config.getName());
   2096 
   2097                 if (config.getName().equals(refName)) {
   2098                     selectionIndex = i;
   2099                 }
   2100                 i++;
   2101             }
   2102 
   2103             mDeviceConfigCombo.select(selectionIndex);
   2104             mDeviceConfigCombo.setEnabled(mState.device.getConfigs().size() > 1);
   2105         }
   2106     }
   2107 
   2108     /**
   2109      * Called when the device config selection changes.
   2110      */
   2111     private void onDeviceConfigChange() {
   2112         // because changing the content of a combo triggers a change event, respect the
   2113         // mDisableUpdates flag
   2114         if (mDisableUpdates > 0) {
   2115             return;
   2116         }
   2117 
   2118         if (computeCurrentConfig() && mListener != null) {
   2119             mListener.onConfigurationChange();
   2120             mListener.onDevicePostChange();
   2121         }
   2122     }
   2123 
   2124     /**
   2125      * Call back for language combo selection
   2126      */
   2127     private void onLocaleChange() {
   2128         // because mLocaleList triggers onLocaleChange at each modification, the filling
   2129         // of the combo with data will trigger notifications, and we don't want that.
   2130         if (mDisableUpdates > 0) {
   2131             return;
   2132         }
   2133 
   2134         if (computeCurrentConfig() &&  mListener != null) {
   2135             mListener.onConfigurationChange();
   2136         }
   2137 
   2138         // Store locale project-wide setting
   2139         saveRenderState();
   2140     }
   2141 
   2142     private void onDockChange() {
   2143         if (computeCurrentConfig() &&  mListener != null) {
   2144             mListener.onConfigurationChange();
   2145         }
   2146     }
   2147 
   2148     private void onDayChange() {
   2149         if (computeCurrentConfig() &&  mListener != null) {
   2150             mListener.onConfigurationChange();
   2151         }
   2152     }
   2153 
   2154     /**
   2155      * Call back for api level combo selection
   2156      */
   2157     private void onRenderingTargetChange() {
   2158         // because mApiCombo triggers onApiLevelChange at each modification, the filling
   2159         // of the combo with data will trigger notifications, and we don't want that.
   2160         if (mDisableUpdates > 0) {
   2161             return;
   2162         }
   2163 
   2164         // tell the listener a new rendering target is being set. Need to do this before updating
   2165         // mRenderingTarget.
   2166         if (mListener != null && mRenderingTarget != null) {
   2167             mListener.onRenderingTargetPreChange(mRenderingTarget);
   2168         }
   2169 
   2170         int index = mTargetCombo.getSelectionIndex();
   2171         mRenderingTarget = mTargetList.get(index);
   2172 
   2173         boolean computeOk = computeCurrentConfig();
   2174 
   2175         // force a theme update to reflect the new rendering target.
   2176         // This must be done after computeCurrentConfig since it'll depend on the currentConfig
   2177         // to figure out the theme list.
   2178         updateThemes();
   2179 
   2180         // since the state is saved in computeCurrentConfig, we need to resave it since theme
   2181         // change could have impacted it.
   2182         saveState();
   2183 
   2184         if (mListener != null && mRenderingTarget != null) {
   2185             mListener.onRenderingTargetPostChange(mRenderingTarget);
   2186         }
   2187 
   2188         // Store project-wide render-target setting
   2189         saveRenderState();
   2190 
   2191         if (computeOk &&  mListener != null) {
   2192             mListener.onConfigurationChange();
   2193         }
   2194     }
   2195 
   2196     /**
   2197      * Saves the current state and the current configuration
   2198      *
   2199      * @see #saveState()
   2200      */
   2201     private boolean computeCurrentConfig() {
   2202         saveState();
   2203 
   2204         if (mState.device != null) {
   2205             // get the device config from the device/config combos.
   2206             int configIndex = mDeviceConfigCombo.getSelectionIndex();
   2207             String name = mDeviceConfigCombo.getItem(configIndex);
   2208             FolderConfiguration config = mState.device.getFolderConfigByName(name);
   2209 
   2210             // replace the config with the one from the device
   2211             mCurrentConfig.set(config);
   2212 
   2213             // replace the locale qualifiers with the one coming from the locale combo
   2214             int index = mLocaleCombo.getSelectionIndex();
   2215             if (index != -1) {
   2216                 ResourceQualifier[] localeQualifiers = mLocaleList.get(index);
   2217 
   2218                 mCurrentConfig.setLanguageQualifier(
   2219                         (LanguageQualifier)localeQualifiers[LOCALE_LANG]);
   2220                 mCurrentConfig.setRegionQualifier(
   2221                         (RegionQualifier)localeQualifiers[LOCALE_REGION]);
   2222             }
   2223 
   2224             index = mUiModeCombo.getSelectionIndex();
   2225             if (index == -1) {
   2226                 index = 0; // no selection = 0
   2227             }
   2228             mCurrentConfig.setUiModeQualifier(new UiModeQualifier(UiMode.getByIndex(index)));
   2229 
   2230             index = mNightCombo.getSelectionIndex();
   2231             if (index == -1) {
   2232                 index = 0; // no selection = 0
   2233             }
   2234             mCurrentConfig.setNightModeQualifier(
   2235                     new NightModeQualifier(NightMode.getByIndex(index)));
   2236 
   2237             // replace the API level by the selection of the combo
   2238             index = mTargetCombo.getSelectionIndex();
   2239             if (index == -1) {
   2240                 index = mTargetList.indexOf(mProjectTarget);
   2241             }
   2242             if (index != -1) {
   2243                 IAndroidTarget target = mTargetList.get(index);
   2244 
   2245                 if (target != null) {
   2246                     mCurrentConfig.setVersionQualifier(
   2247                             new VersionQualifier(target.getVersion().getApiLevel()));
   2248                 }
   2249             }
   2250 
   2251             // update the create button.
   2252             checkCreateEnable();
   2253 
   2254             return true;
   2255         }
   2256 
   2257         return false;
   2258     }
   2259 
   2260     private void onThemeChange() {
   2261         saveState();
   2262 
   2263         int themeIndex = mThemeCombo.getSelectionIndex();
   2264         if (themeIndex != -1) {
   2265             String theme = mThemeCombo.getItem(themeIndex);
   2266 
   2267             if (theme.equals(THEME_SEPARATOR)) {
   2268                 mThemeCombo.select(0);
   2269             }
   2270 
   2271             if (mListener != null) {
   2272                 mListener.onThemeChange();
   2273             }
   2274         }
   2275     }
   2276 
   2277     /**
   2278      * Returns whether the given <var>style</var> is a theme.
   2279      * This is done by making sure the parent is a theme.
   2280      * @param value the style to check
   2281      * @param styleMap the map of styles for the current project. Key is the style name.
   2282      * @return True if the given <var>style</var> is a theme.
   2283      */
   2284     private boolean isTheme(ResourceValue value, Map<String, ResourceValue> styleMap) {
   2285         if (value instanceof StyleResourceValue) {
   2286             StyleResourceValue style = (StyleResourceValue)value;
   2287 
   2288             boolean frameworkStyle = false;
   2289             String parentStyle = style.getParentStyle();
   2290             if (parentStyle == null) {
   2291                 // if there is no specified parent style we look an implied one.
   2292                 // For instance 'Theme.light' is implied child style of 'Theme',
   2293                 // and 'Theme.light.fullscreen' is implied child style of 'Theme.light'
   2294                 String name = style.getName();
   2295                 int index = name.lastIndexOf('.');
   2296                 if (index != -1) {
   2297                     parentStyle = name.substring(0, index);
   2298                 }
   2299             } else {
   2300                 // remove the useless @ if it's there
   2301                 if (parentStyle.startsWith("@")) {
   2302                     parentStyle = parentStyle.substring(1);
   2303                 }
   2304 
   2305                 // check for framework identifier.
   2306                 if (parentStyle.startsWith(ANDROID_NS_NAME_PREFIX)) {
   2307                     frameworkStyle = true;
   2308                     parentStyle = parentStyle.substring(ANDROID_NS_NAME_PREFIX.length());
   2309                 }
   2310 
   2311                 // at this point we could have the format style/<name>. we want only the name
   2312                 if (parentStyle.startsWith("style/")) {
   2313                     parentStyle = parentStyle.substring("style/".length());
   2314                 }
   2315             }
   2316 
   2317             if (parentStyle != null) {
   2318                 if (frameworkStyle) {
   2319                     // if the parent is a framework style, it has to be 'Theme' or 'Theme.*'
   2320                     return parentStyle.equals("Theme") || parentStyle.startsWith("Theme.");
   2321                 } else {
   2322                     // if it's a project style, we check this is a theme.
   2323                     ResourceValue parentValue = styleMap.get(parentStyle);
   2324 
   2325                     // also prevent stackoverflow in case the dev mistakenly declared
   2326                     // the parent of the style as the style itfself.
   2327                     if (parentValue != null && parentValue.equals(value) == false) {
   2328                         return isTheme(parentValue, styleMap);
   2329                     }
   2330                 }
   2331             }
   2332         }
   2333 
   2334         return false;
   2335     }
   2336 
   2337     private void checkCreateEnable() {
   2338         mCreateButton.setEnabled(mEditedConfig.equals(mCurrentConfig) == false);
   2339     }
   2340 
   2341     /**
   2342      * Checks whether the current edited file is the best match for a given config.
   2343      * <p/>
   2344      * This tests against other versions of the same layout in the project.
   2345      * <p/>
   2346      * The given config must be compatible with the current edited file.
   2347      * @param config the config to test.
   2348      * @return true if the current edited file is the best match in the project for the
   2349      * given config.
   2350      */
   2351     private boolean isCurrentFileBestMatchFor(FolderConfiguration config) {
   2352         ResourceFile match = mResources.getMatchingFile(mEditedFile.getName(),
   2353                 ResourceFolderType.LAYOUT, config);
   2354 
   2355         if (match != null) {
   2356             return match.getFile().equals(mEditedFile);
   2357         } else {
   2358             // if we stop here that means the current file is not even a match!
   2359             AdtPlugin.log(IStatus.ERROR, "Current file is not a match for the given config.");
   2360         }
   2361 
   2362         return false;
   2363     }
   2364 
   2365     /**
   2366      * Resets the configuration chooser to reflect the given file configuration. This is
   2367      * intended to be used by the "Show Included In" functionality where the user has
   2368      * picked a non-default configuration (such as a particular landscape layout) and the
   2369      * configuration chooser must be switched to a landscape layout. This method will
   2370      * trigger a model change.
   2371      * <p>
   2372      * This will NOT trigger a redraw event!
   2373      * <p>
   2374      * FIXME: We are currently setting the configuration file to be the configuration for
   2375      * the "outer" (the including) file, rather than the inner file, which is the file the
   2376      * user is actually editing. We need to refine this, possibly with a way for the user
   2377      * to choose which configuration they are editing. And in particular, we should be
   2378      * filtering the configuration chooser to only show options in the outer configuration
   2379      * that are compatible with the inner included file.
   2380      *
   2381      * @param file the file to be configured
   2382      */
   2383     public void resetConfigFor(IFile file) {
   2384         setFile(file);
   2385         mEditedConfig = null;
   2386         onXmlModelLoaded();
   2387     }
   2388 
   2389     /**
   2390      * Syncs this configuration to the project wide locale and render target settings. The
   2391      * locale may ignore the project-wide setting if it is a locale-specific
   2392      * configuration.
   2393      *
   2394      * @return true if one or both of the toggles were changed, false if there were no
   2395      *         changes
   2396      */
   2397     public boolean syncRenderState() {
   2398         if (mEditedConfig == null) {
   2399             // Startup; ignore
   2400             return false;
   2401         }
   2402 
   2403         boolean localeChanged = false;
   2404         boolean renderTargetChanged = false;
   2405 
   2406         // When a page is re-activated, force the toggles to reflect the current project
   2407         // state
   2408 
   2409         Pair<ResourceQualifier[], IAndroidTarget> pair = loadRenderState();
   2410 
   2411         // Only sync the locale if this layout is not already a locale-specific layout!
   2412         if (!isLocaleSpecificLayout()) {
   2413             ResourceQualifier[] locale = pair.getFirst();
   2414             if (locale != null) {
   2415                 localeChanged = setLocaleCombo(locale[0], locale[1]);
   2416             }
   2417         }
   2418 
   2419         // Sync render target
   2420         IAndroidTarget target = pair.getSecond();
   2421         if (target != null) {
   2422             int targetIndex = mTargetList.indexOf(target);
   2423             if (targetIndex != mTargetCombo.getSelectionIndex()) {
   2424                 mTargetCombo.select(targetIndex);
   2425                 renderTargetChanged = true;
   2426             }
   2427         }
   2428 
   2429         if (!renderTargetChanged && !localeChanged) {
   2430             return false;
   2431         }
   2432 
   2433         // Update the locale and/or the render target. This code contains a logical
   2434         // merge of the onRenderingTargetChange() and onLocaleChange() methods, combined
   2435         // such that we don't duplicate work.
   2436 
   2437         if (renderTargetChanged) {
   2438             if (mListener != null && mRenderingTarget != null) {
   2439                 mListener.onRenderingTargetPreChange(mRenderingTarget);
   2440             }
   2441             int targetIndex = mTargetCombo.getSelectionIndex();
   2442             mRenderingTarget = mTargetList.get(targetIndex);
   2443         }
   2444 
   2445         // Compute the new configuration; we want to do this both for locale changes
   2446         // and for render targets.
   2447         boolean computeOk = computeCurrentConfig();
   2448 
   2449         if (renderTargetChanged) {
   2450             // force a theme update to reflect the new rendering target.
   2451             // This must be done after computeCurrentConfig since it'll depend on the currentConfig
   2452             // to figure out the theme list.
   2453             updateThemes();
   2454 
   2455             if (mListener != null && mRenderingTarget != null) {
   2456                 mListener.onRenderingTargetPostChange(mRenderingTarget);
   2457             }
   2458         }
   2459 
   2460         // For both locale and render target changes
   2461         if (computeOk &&  mListener != null) {
   2462             mListener.onConfigurationChange();
   2463         }
   2464 
   2465         return true;
   2466     }
   2467 
   2468     /**
   2469      * Loads the render state (the locale and the render target, which are shared among
   2470      * all the layouts meaning that changing it in one will change it in all) and returns
   2471      * the current project-wide locale and render target to be used.
   2472      *
   2473      * @return a pair of locale resource qualifiers and render target
   2474      */
   2475     private Pair<ResourceQualifier[], IAndroidTarget> loadRenderState() {
   2476         IProject project = mEditedFile.getProject();
   2477         try {
   2478             String data = project.getPersistentProperty(NAME_RENDER_STATE);
   2479             if (data != null) {
   2480                 ResourceQualifier[] locale = null;
   2481                 IAndroidTarget target = null;
   2482 
   2483                 String[] values = data.split(SEP);
   2484                 if (values.length == 2) {
   2485                     locale = new ResourceQualifier[2];
   2486                     String locales[] = values[0].split(SEP_LOCALE);
   2487                     if (locales.length >= 2) {
   2488                         if (locales[0].length() > 0) {
   2489                             locale[0] = new LanguageQualifier(locales[0]);
   2490                         }
   2491                         if (locales[1].length() > 0) {
   2492                             locale[1] = new RegionQualifier(locales[1]);
   2493                         }
   2494                     }
   2495                     target = stringToTarget(values[1]);
   2496 
   2497                     // See if we should "correct" the rendering target to a better version.
   2498                     // If you're using a pre-release version of the render target, and a
   2499                     // final release is available and installed, we should switch to that
   2500                     // one instead.
   2501                     if (target != null) {
   2502                         AndroidVersion version = target.getVersion();
   2503                         if (version.getCodename() != null && mTargetList != null) {
   2504                             int targetApiLevel = version.getApiLevel() + 1;
   2505                             for (IAndroidTarget t : mTargetList) {
   2506                                 if (t.getVersion().getApiLevel() == targetApiLevel
   2507                                         && t.isPlatform()) {
   2508                                     target = t;
   2509                                     break;
   2510                                 }
   2511                             }
   2512                         }
   2513                     }
   2514                 }
   2515 
   2516                 return Pair.of(locale, target);
   2517             }
   2518 
   2519             ResourceQualifier[] any = new ResourceQualifier[] {
   2520                     new LanguageQualifier(LanguageQualifier.FAKE_LANG_VALUE),
   2521                     new RegionQualifier(RegionQualifier.FAKE_REGION_VALUE)
   2522             };
   2523 
   2524             return Pair.of(any, findDefaultRenderTarget());
   2525         } catch (CoreException e) {
   2526             AdtPlugin.log(e, null);
   2527         }
   2528 
   2529         return null;
   2530     }
   2531 
   2532     /** Returns true if the current layout is locale-specific */
   2533     private boolean isLocaleSpecificLayout() {
   2534         return mEditedConfig == null || mEditedConfig.getLanguageQualifier() != null;
   2535     }
   2536 
   2537     /**
   2538      * Saves the render state (the current locale and render target settings) into the
   2539      * project wide settings storage
   2540      */
   2541     private void saveRenderState() {
   2542         IProject project = mEditedFile.getProject();
   2543         try {
   2544             int index = mLocaleCombo.getSelectionIndex();
   2545             ResourceQualifier[] locale = mLocaleList.get(index);
   2546             index = mTargetCombo.getSelectionIndex();
   2547             IAndroidTarget target = mTargetList.get(index);
   2548 
   2549             // Generate a persistent string from locale+target
   2550             StringBuilder sb = new StringBuilder();
   2551             if (locale != null) {
   2552                 if (locale[0] != null && locale[1] != null) {
   2553                     // locale[0]/[1] can be null sometimes when starting Eclipse
   2554                     sb.append(((LanguageQualifier) locale[0]).getValue());
   2555                     sb.append(SEP_LOCALE);
   2556                     sb.append(((RegionQualifier) locale[1]).getValue());
   2557                 }
   2558             }
   2559             sb.append(SEP);
   2560             if (target != null) {
   2561                 sb.append(targetToString(target));
   2562                 sb.append(SEP);
   2563             }
   2564 
   2565             String data = sb.toString();
   2566             project.setPersistentProperty(NAME_RENDER_STATE, data);
   2567         } catch (CoreException e) {
   2568             AdtPlugin.log(e, null);
   2569         }
   2570     }
   2571 }
   2572