Home | History | Annotate | Download | only in sdk
      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.sdk;
     18 
     19 import static com.android.ide.eclipse.adt.AdtConstants.DOT_XML;
     20 import static com.android.sdklib.SdkConstants.FD_RES;
     21 
     22 import com.android.annotations.NonNull;
     23 import com.android.ddmlib.IDevice;
     24 import com.android.ide.common.rendering.LayoutLibrary;
     25 import com.android.ide.common.sdk.LoadStatus;
     26 import com.android.ide.eclipse.adt.AdtConstants;
     27 import com.android.ide.eclipse.adt.AdtPlugin;
     28 import com.android.ide.eclipse.adt.internal.build.DexWrapper;
     29 import com.android.ide.eclipse.adt.internal.editors.common.CommonXmlEditor;
     30 import com.android.ide.eclipse.adt.internal.preferences.AdtPrefs;
     31 import com.android.ide.eclipse.adt.internal.project.BaseProjectHelper;
     32 import com.android.ide.eclipse.adt.internal.project.LibraryClasspathContainerInitializer;
     33 import com.android.ide.eclipse.adt.internal.project.ProjectHelper;
     34 import com.android.ide.eclipse.adt.internal.resources.manager.GlobalProjectMonitor;
     35 import com.android.ide.eclipse.adt.internal.resources.manager.GlobalProjectMonitor.IFileListener;
     36 import com.android.ide.eclipse.adt.internal.resources.manager.GlobalProjectMonitor.IProjectListener;
     37 import com.android.ide.eclipse.adt.internal.resources.manager.GlobalProjectMonitor.IResourceEventListener;
     38 import com.android.ide.eclipse.adt.internal.sdk.ProjectState.LibraryDifference;
     39 import com.android.ide.eclipse.adt.internal.sdk.ProjectState.LibraryState;
     40 import com.android.io.StreamException;
     41 import com.android.prefs.AndroidLocation.AndroidLocationException;
     42 import com.android.sdklib.AndroidVersion;
     43 import com.android.sdklib.IAndroidTarget;
     44 import com.android.sdklib.ISdkLog;
     45 import com.android.sdklib.SdkConstants;
     46 import com.android.sdklib.SdkManager;
     47 import com.android.sdklib.internal.avd.AvdManager;
     48 import com.android.sdklib.internal.project.ProjectProperties;
     49 import com.android.sdklib.internal.project.ProjectProperties.PropertyType;
     50 import com.android.sdklib.internal.project.ProjectPropertiesWorkingCopy;
     51 
     52 import org.eclipse.core.resources.IFile;
     53 import org.eclipse.core.resources.IFolder;
     54 import org.eclipse.core.resources.IMarkerDelta;
     55 import org.eclipse.core.resources.IProject;
     56 import org.eclipse.core.resources.IResource;
     57 import org.eclipse.core.resources.IResourceDelta;
     58 import org.eclipse.core.resources.IncrementalProjectBuilder;
     59 import org.eclipse.core.runtime.CoreException;
     60 import org.eclipse.core.runtime.IPath;
     61 import org.eclipse.core.runtime.IProgressMonitor;
     62 import org.eclipse.core.runtime.IStatus;
     63 import org.eclipse.core.runtime.QualifiedName;
     64 import org.eclipse.core.runtime.Status;
     65 import org.eclipse.core.runtime.jobs.Job;
     66 import org.eclipse.jdt.core.IJavaProject;
     67 import org.eclipse.jdt.core.JavaCore;
     68 import org.eclipse.jface.preference.IPreferenceStore;
     69 import org.eclipse.ui.IEditorDescriptor;
     70 import org.eclipse.ui.IEditorInput;
     71 import org.eclipse.ui.IEditorPart;
     72 import org.eclipse.ui.IEditorReference;
     73 import org.eclipse.ui.IFileEditorInput;
     74 import org.eclipse.ui.IWorkbenchPage;
     75 import org.eclipse.ui.IWorkbenchPartSite;
     76 import org.eclipse.ui.IWorkbenchWindow;
     77 import org.eclipse.ui.PartInitException;
     78 import org.eclipse.ui.PlatformUI;
     79 import org.eclipse.ui.ide.IDE;
     80 
     81 import java.io.File;
     82 import java.io.IOException;
     83 import java.net.MalformedURLException;
     84 import java.net.URL;
     85 import java.util.ArrayList;
     86 import java.util.Arrays;
     87 import java.util.Collection;
     88 import java.util.HashMap;
     89 import java.util.HashSet;
     90 import java.util.List;
     91 import java.util.Map;
     92 import java.util.Map.Entry;
     93 import java.util.Set;
     94 
     95 /**
     96  * Central point to load, manipulate and deal with the Android SDK. Only one SDK can be used
     97  * at the same time.
     98  *
     99  * To start using an SDK, call {@link #loadSdk(String)} which returns the instance of
    100  * the Sdk object.
    101  *
    102  * To get the list of platforms or add-ons present in the SDK, call {@link #getTargets()}.
    103  */
    104 public final class Sdk  {
    105     private final static boolean DEBUG = false;
    106 
    107     private final static Object LOCK = new Object();
    108 
    109     private static Sdk sCurrentSdk = null;
    110 
    111     /**
    112      * Map associating {@link IProject} and their state {@link ProjectState}.
    113      * <p/>This <b>MUST NOT</b> be accessed directly. Instead use {@link #getProjectState(IProject)}.
    114      */
    115     private final static HashMap<IProject, ProjectState> sProjectStateMap =
    116             new HashMap<IProject, ProjectState>();
    117 
    118     /**
    119      * Data bundled using during the load of Target data.
    120      * <p/>This contains the {@link LoadStatus} and a list of projects that attempted
    121      * to compile before the loading was finished. Those projects will be recompiled
    122      * at the end of the loading.
    123      */
    124     private final static class TargetLoadBundle {
    125         LoadStatus status;
    126         final HashSet<IJavaProject> projecsToReload = new HashSet<IJavaProject>();
    127     }
    128 
    129     private final SdkManager mManager;
    130     private final DexWrapper mDexWrapper;
    131     private final AvdManager mAvdManager;
    132 
    133     /** Map associating an {@link IAndroidTarget} to an {@link AndroidTargetData} */
    134     private final HashMap<IAndroidTarget, AndroidTargetData> mTargetDataMap =
    135         new HashMap<IAndroidTarget, AndroidTargetData>();
    136     /** Map associating an {@link IAndroidTarget} and its {@link TargetLoadBundle}. */
    137     private final HashMap<IAndroidTarget, TargetLoadBundle> mTargetDataStatusMap =
    138         new HashMap<IAndroidTarget, TargetLoadBundle>();
    139 
    140     /**
    141      * If true the target data will never load anymore. The only way to reload them is to
    142      * completely reload the SDK with {@link #loadSdk(String)}
    143      */
    144     private boolean mDontLoadTargetData = false;
    145 
    146     private final String mDocBaseUrl;
    147 
    148     private final LayoutDeviceManager mLayoutDeviceManager = new LayoutDeviceManager();
    149 
    150     /**
    151      * Classes implementing this interface will receive notification when targets are changed.
    152      */
    153     public interface ITargetChangeListener {
    154         /**
    155          * Sent when project has its target changed.
    156          */
    157         void onProjectTargetChange(IProject changedProject);
    158 
    159         /**
    160          * Called when the targets are loaded (either the SDK finished loading when Eclipse starts,
    161          * or the SDK is changed).
    162          */
    163         void onTargetLoaded(IAndroidTarget target);
    164 
    165         /**
    166          * Called when the base content of the SDK is parsed.
    167          */
    168         void onSdkLoaded();
    169     }
    170 
    171     /**
    172      * Basic abstract implementation of the ITargetChangeListener for the case where both
    173      * {@link #onProjectTargetChange(IProject)} and {@link #onTargetLoaded(IAndroidTarget)}
    174      * use the same code based on a simple test requiring to know the current IProject.
    175      */
    176     public static abstract class TargetChangeListener implements ITargetChangeListener {
    177         /**
    178          * Returns the {@link IProject} associated with the listener.
    179          */
    180         public abstract IProject getProject();
    181 
    182         /**
    183          * Called when the listener needs to take action on the event. This is only called
    184          * if {@link #getProject()} and the {@link IAndroidTarget} associated with the project
    185          * match the values received in {@link #onProjectTargetChange(IProject)} and
    186          * {@link #onTargetLoaded(IAndroidTarget)}.
    187          */
    188         public abstract void reload();
    189 
    190         @Override
    191         public void onProjectTargetChange(IProject changedProject) {
    192             if (changedProject != null && changedProject.equals(getProject())) {
    193                 reload();
    194             }
    195         }
    196 
    197         @Override
    198         public void onTargetLoaded(IAndroidTarget target) {
    199             IProject project = getProject();
    200             if (target != null && target.equals(Sdk.getCurrent().getTarget(project))) {
    201                 reload();
    202             }
    203         }
    204 
    205         @Override
    206         public void onSdkLoaded() {
    207             // do nothing;
    208         }
    209     }
    210 
    211     /**
    212      * Returns the lock object used to synchronize all operations dealing with SDK, targets and
    213      * projects.
    214      */
    215     public static final Object getLock() {
    216         return LOCK;
    217     }
    218 
    219     /**
    220      * Loads an SDK and returns an {@link Sdk} object if success.
    221      * <p/>If the SDK failed to load, it displays an error to the user.
    222      * @param sdkLocation the OS path to the SDK.
    223      */
    224     public static Sdk loadSdk(String sdkLocation) {
    225         synchronized (LOCK) {
    226             if (sCurrentSdk != null) {
    227                 sCurrentSdk.dispose();
    228                 sCurrentSdk = null;
    229             }
    230 
    231             final ArrayList<String> logMessages = new ArrayList<String>();
    232             ISdkLog log = new ISdkLog() {
    233                 @Override
    234                 public void error(Throwable throwable, String errorFormat, Object... arg) {
    235                     if (errorFormat != null) {
    236                         logMessages.add(String.format("Error: " + errorFormat, arg));
    237                     }
    238 
    239                     if (throwable != null) {
    240                         logMessages.add(throwable.getMessage());
    241                     }
    242                 }
    243 
    244                 @Override
    245                 public void warning(String warningFormat, Object... arg) {
    246                     logMessages.add(String.format("Warning: " + warningFormat, arg));
    247                 }
    248 
    249                 @Override
    250                 public void printf(String msgFormat, Object... arg) {
    251                     logMessages.add(String.format(msgFormat, arg));
    252                 }
    253             };
    254 
    255             // get an SdkManager object for the location
    256             SdkManager manager = SdkManager.createManager(sdkLocation, log);
    257             if (manager != null) {
    258                 // load DX.
    259                 DexWrapper dexWrapper = new DexWrapper();
    260                 String dexLocation =
    261                         sdkLocation + File.separator +
    262                         SdkConstants.OS_SDK_PLATFORM_TOOLS_LIB_FOLDER + SdkConstants.FN_DX_JAR;
    263                 IStatus res = dexWrapper.loadDex(dexLocation);
    264                 if (res != Status.OK_STATUS) {
    265                     log.error(null, res.getMessage());
    266                     dexWrapper = null;
    267                 }
    268 
    269                 // create the AVD Manager
    270                 AvdManager avdManager = null;
    271                 try {
    272                     avdManager = new AvdManager(manager, log);
    273                 } catch (AndroidLocationException e) {
    274                     log.error(e, "Error parsing the AVDs");
    275                 }
    276                 sCurrentSdk = new Sdk(manager, dexWrapper, avdManager);
    277                 return sCurrentSdk;
    278             } else {
    279                 StringBuilder sb = new StringBuilder("Error Loading the SDK:\n");
    280                 for (String msg : logMessages) {
    281                     sb.append('\n');
    282                     sb.append(msg);
    283                 }
    284                 AdtPlugin.displayError("Android SDK", sb.toString());
    285             }
    286             return null;
    287         }
    288     }
    289 
    290     /**
    291      * Returns the current {@link Sdk} object.
    292      */
    293     public static Sdk getCurrent() {
    294         synchronized (LOCK) {
    295             return sCurrentSdk;
    296         }
    297     }
    298 
    299     /**
    300      * Returns the location (OS path) of the current SDK.
    301      */
    302     public String getSdkLocation() {
    303         return mManager.getLocation();
    304     }
    305 
    306     /**
    307      * Returns a <em>new</em> {@link SdkManager} that can parse the SDK located
    308      * at the current {@link #getSdkLocation()}.
    309      * <p/>
    310      * Implementation detail: The {@link Sdk} has its own internal manager with
    311      * a custom logger which is not designed to be useful for outsiders. Callers
    312      * who need their own {@link SdkManager} for parsing will often want to control
    313      * the logger for their own need.
    314      * <p/>
    315      * This is just a convenient method equivalent to writing:
    316      * <pre>SdkManager.createManager(Sdk.getCurrent().getSdkLocation(), log);</pre>
    317      *
    318      * @param log The logger for the {@link SdkManager}.
    319      * @return A new {@link SdkManager} parsing the same location.
    320      */
    321     public @NonNull SdkManager getNewSdkManager(@NonNull ISdkLog log) {
    322         return SdkManager.createManager(getSdkLocation(), log);
    323     }
    324 
    325     /**
    326      * Returns the URL to the local documentation.
    327      * Can return null if no documentation is found in the current SDK.
    328      *
    329      * @return A file:// URL on the local documentation folder if it exists or null.
    330      */
    331     public String getDocumentationBaseUrl() {
    332         return mDocBaseUrl;
    333     }
    334 
    335     /**
    336      * Returns the list of targets that are available in the SDK.
    337      */
    338     public IAndroidTarget[] getTargets() {
    339         return mManager.getTargets();
    340     }
    341 
    342     /**
    343      * Returns a target from a hash that was generated by {@link IAndroidTarget#hashString()}.
    344      *
    345      * @param hash the {@link IAndroidTarget} hash string.
    346      * @return The matching {@link IAndroidTarget} or null.
    347      */
    348     public IAndroidTarget getTargetFromHashString(String hash) {
    349         return mManager.getTargetFromHashString(hash);
    350     }
    351 
    352     /**
    353      * Initializes a new project with a target. This creates the <code>project.properties</code>
    354      * file.
    355      * @param project the project to initialize
    356      * @param target the project's target.
    357      * @throws IOException if creating the file failed in any way.
    358      * @throws StreamException
    359      */
    360     public void initProject(IProject project, IAndroidTarget target)
    361             throws IOException, StreamException {
    362         if (project == null || target == null) {
    363             return;
    364         }
    365 
    366         synchronized (LOCK) {
    367             // check if there's already a state?
    368             ProjectState state = getProjectState(project);
    369 
    370             ProjectPropertiesWorkingCopy properties = null;
    371 
    372             if (state != null) {
    373                 properties = state.getProperties().makeWorkingCopy();
    374             }
    375 
    376             if (properties == null) {
    377                 IPath location = project.getLocation();
    378                 if (location == null) {  // can return null when the project is being deleted.
    379                     // do nothing and return null;
    380                     return;
    381                 }
    382 
    383                 properties = ProjectProperties.create(location.toOSString(), PropertyType.PROJECT);
    384             }
    385 
    386             // save the target hash string in the project persistent property
    387             properties.setProperty(ProjectProperties.PROPERTY_TARGET, target.hashString());
    388             properties.save();
    389         }
    390     }
    391 
    392     /**
    393      * Returns the {@link ProjectState} object associated with a given project.
    394      * <p/>
    395      * This method is the only way to properly get the project's {@link ProjectState}
    396      * If the project has not yet been loaded, then it is loaded.
    397      * <p/>Because this methods deals with projects, it's not linked to an actual {@link Sdk}
    398      * objects, and therefore is static.
    399      * <p/>The value returned by {@link ProjectState#getTarget()} will change as {@link Sdk} objects
    400      * are replaced.
    401      * @param project the request project
    402      * @return the ProjectState for the project.
    403      */
    404     @SuppressWarnings("deprecation")
    405     public static ProjectState getProjectState(IProject project) {
    406         if (project == null) {
    407             return null;
    408         }
    409 
    410         synchronized (LOCK) {
    411             ProjectState state = sProjectStateMap.get(project);
    412             if (state == null) {
    413                 // load the project.properties from the project folder.
    414                 IPath location = project.getLocation();
    415                 if (location == null) {  // can return null when the project is being deleted.
    416                     // do nothing and return null;
    417                     return null;
    418                 }
    419 
    420                 String projectLocation = location.toOSString();
    421 
    422                 ProjectProperties properties = ProjectProperties.load(projectLocation,
    423                         PropertyType.PROJECT);
    424                 if (properties == null) {
    425                     // legacy support: look for default.properties and rename it if needed.
    426                     properties = ProjectProperties.load(projectLocation,
    427                             PropertyType.LEGACY_DEFAULT);
    428 
    429                     if (properties == null) {
    430                         AdtPlugin.log(IStatus.ERROR,
    431                                 "Failed to load properties file for project '%s'",
    432                                 project.getName());
    433                         return null;
    434                     } else {
    435                         //legacy mode.
    436                         // get a working copy with the new type "project"
    437                         ProjectPropertiesWorkingCopy wc = properties.makeWorkingCopy(
    438                                 PropertyType.PROJECT);
    439                         // and save it
    440                         try {
    441                             wc.save();
    442 
    443                             // delete the old file.
    444                             ProjectProperties.delete(projectLocation, PropertyType.LEGACY_DEFAULT);
    445                         } catch (Exception e) {
    446                             AdtPlugin.log(IStatus.ERROR,
    447                                     "Failed to rename properties file to %1$s for project '%s2$'",
    448                                     PropertyType.PROJECT.getFilename(), project.getName());
    449                         }
    450                     }
    451                 }
    452 
    453                 state = new ProjectState(project, properties);
    454                 sProjectStateMap.put(project, state);
    455 
    456                 // try to resolve the target
    457                 if (AdtPlugin.getDefault().getSdkLoadStatus() == LoadStatus.LOADED) {
    458                     sCurrentSdk.loadTarget(state);
    459                 }
    460             }
    461 
    462             return state;
    463         }
    464     }
    465 
    466     /**
    467      * Returns the {@link IAndroidTarget} object associated with the given {@link IProject}.
    468      */
    469     public IAndroidTarget getTarget(IProject project) {
    470         if (project == null) {
    471             return null;
    472         }
    473 
    474         ProjectState state = getProjectState(project);
    475         if (state != null) {
    476             return state.getTarget();
    477         }
    478 
    479         return null;
    480     }
    481 
    482     /**
    483      * Loads the {@link IAndroidTarget} for a given project.
    484      * <p/>This method will get the target hash string from the project properties, and resolve
    485      * it to an {@link IAndroidTarget} object and store it inside the {@link ProjectState}.
    486      * @param state the state representing the project to load.
    487      * @return the target that was loaded.
    488      */
    489     public IAndroidTarget loadTarget(ProjectState state) {
    490         IAndroidTarget target = null;
    491         if (state != null) {
    492             String hash = state.getTargetHashString();
    493             if (hash != null) {
    494                 state.setTarget(target = getTargetFromHashString(hash));
    495             }
    496         }
    497 
    498         return target;
    499     }
    500 
    501     /**
    502      * Checks and loads (if needed) the data for a given target.
    503      * <p/> The data is loaded in a separate {@link Job}, and opened editors will be notified
    504      * through their implementation of {@link ITargetChangeListener#onTargetLoaded(IAndroidTarget)}.
    505      * <p/>An optional project as second parameter can be given to be recompiled once the target
    506      * data is finished loading.
    507      * <p/>The return value is non-null only if the target data has already been loaded (and in this
    508      * case is the status of the load operation)
    509      * @param target the target to load.
    510      * @param project an optional project to be recompiled when the target data is loaded.
    511      * If the target is already loaded, nothing happens.
    512      * @return The load status if the target data is already loaded.
    513      */
    514     public LoadStatus checkAndLoadTargetData(final IAndroidTarget target, IJavaProject project) {
    515         boolean loadData = false;
    516 
    517         synchronized (LOCK) {
    518             if (mDontLoadTargetData) {
    519                 return LoadStatus.FAILED;
    520             }
    521 
    522             TargetLoadBundle bundle = mTargetDataStatusMap.get(target);
    523             if (bundle == null) {
    524                 bundle = new TargetLoadBundle();
    525                 mTargetDataStatusMap.put(target,bundle);
    526 
    527                 // set status to loading
    528                 bundle.status = LoadStatus.LOADING;
    529 
    530                 // add project to bundle
    531                 if (project != null) {
    532                     bundle.projecsToReload.add(project);
    533                 }
    534 
    535                 // and set the flag to start the loading below
    536                 loadData = true;
    537             } else if (bundle.status == LoadStatus.LOADING) {
    538                 // add project to bundle
    539                 if (project != null) {
    540                     bundle.projecsToReload.add(project);
    541                 }
    542 
    543                 return bundle.status;
    544             } else if (bundle.status == LoadStatus.LOADED || bundle.status == LoadStatus.FAILED) {
    545                 return bundle.status;
    546             }
    547         }
    548 
    549         if (loadData) {
    550             Job job = new Job(String.format("Loading data for %1$s", target.getFullName())) {
    551                 @Override
    552                 protected IStatus run(IProgressMonitor monitor) {
    553                     AdtPlugin plugin = AdtPlugin.getDefault();
    554                     try {
    555                         IStatus status = new AndroidTargetParser(target).run(monitor);
    556 
    557                         IJavaProject[] javaProjectArray = null;
    558 
    559                         synchronized (LOCK) {
    560                             TargetLoadBundle bundle = mTargetDataStatusMap.get(target);
    561 
    562                             if (status.getCode() != IStatus.OK) {
    563                                 bundle.status = LoadStatus.FAILED;
    564                                 bundle.projecsToReload.clear();
    565                             } else {
    566                                 bundle.status = LoadStatus.LOADED;
    567 
    568                                 // Prepare the array of project to recompile.
    569                                 // The call is done outside of the synchronized block.
    570                                 javaProjectArray = bundle.projecsToReload.toArray(
    571                                         new IJavaProject[bundle.projecsToReload.size()]);
    572 
    573                                 // and update the UI of the editors that depend on the target data.
    574                                 plugin.updateTargetListeners(target);
    575                             }
    576                         }
    577 
    578                         if (javaProjectArray != null) {
    579                             ProjectHelper.updateProjects(javaProjectArray);
    580                         }
    581 
    582                         return status;
    583                     } catch (Throwable t) {
    584                         synchronized (LOCK) {
    585                             TargetLoadBundle bundle = mTargetDataStatusMap.get(target);
    586                             bundle.status = LoadStatus.FAILED;
    587                         }
    588 
    589                         AdtPlugin.log(t, "Exception in checkAndLoadTargetData.");    //$NON-NLS-1$
    590                         return new Status(IStatus.ERROR, AdtPlugin.PLUGIN_ID,
    591                                 String.format(
    592                                         "Parsing Data for %1$s failed", //$NON-NLS-1$
    593                                         target.hashString()),
    594                                 t);
    595                     }
    596                 }
    597             };
    598             job.setPriority(Job.BUILD); // build jobs are run after other interactive jobs
    599             job.schedule();
    600         }
    601 
    602         // The only way to go through here is when the loading starts through the Job.
    603         // Therefore the current status of the target is LOADING.
    604         return LoadStatus.LOADING;
    605     }
    606 
    607     /**
    608      * Return the {@link AndroidTargetData} for a given {@link IAndroidTarget}.
    609      */
    610     public AndroidTargetData getTargetData(IAndroidTarget target) {
    611         synchronized (LOCK) {
    612             return mTargetDataMap.get(target);
    613         }
    614     }
    615 
    616     /**
    617      * Return the {@link AndroidTargetData} for a given {@link IProject}.
    618      */
    619     public AndroidTargetData getTargetData(IProject project) {
    620         synchronized (LOCK) {
    621             IAndroidTarget target = getTarget(project);
    622             if (target != null) {
    623                 return getTargetData(target);
    624             }
    625         }
    626 
    627         return null;
    628     }
    629 
    630     /**
    631      * Returns a {@link DexWrapper} object to be used to execute dx commands. If dx.jar was not
    632      * loaded properly, then this will return <code>null</code>.
    633      */
    634     public DexWrapper getDexWrapper() {
    635         return mDexWrapper;
    636     }
    637 
    638     /**
    639      * Returns the {@link AvdManager}. If the AvdManager failed to parse the AVD folder, this could
    640      * be <code>null</code>.
    641      */
    642     public AvdManager getAvdManager() {
    643         return mAvdManager;
    644     }
    645 
    646     public static AndroidVersion getDeviceVersion(IDevice device) {
    647         try {
    648             Map<String, String> props = device.getProperties();
    649             String apiLevel = props.get(IDevice.PROP_BUILD_API_LEVEL);
    650             if (apiLevel == null) {
    651                 return null;
    652             }
    653 
    654             return new AndroidVersion(Integer.parseInt(apiLevel),
    655                     props.get((IDevice.PROP_BUILD_CODENAME)));
    656         } catch (NumberFormatException e) {
    657             return null;
    658         }
    659     }
    660 
    661     public LayoutDeviceManager getLayoutDeviceManager() {
    662         return mLayoutDeviceManager;
    663     }
    664 
    665     /**
    666      * Returns a list of {@link ProjectState} representing projects depending, directly or
    667      * indirectly on a given library project.
    668      * @param project the library project.
    669      * @return a possibly empty list of ProjectState.
    670      */
    671     public static Set<ProjectState> getMainProjectsFor(IProject project) {
    672         synchronized (LOCK) {
    673             // first get the project directly depending on this.
    674             HashSet<ProjectState> list = new HashSet<ProjectState>();
    675 
    676             // loop on all project and see if ProjectState.getLibrary returns a non null
    677             // project.
    678             for (Entry<IProject, ProjectState> entry : sProjectStateMap.entrySet()) {
    679                 if (project != entry.getKey()) {
    680                     LibraryState library = entry.getValue().getLibrary(project);
    681                     if (library != null) {
    682                         list.add(entry.getValue());
    683                     }
    684                 }
    685             }
    686 
    687             // now look for projects depending on the projects directly depending on the library.
    688             HashSet<ProjectState> result = new HashSet<ProjectState>(list);
    689             for (ProjectState p : list) {
    690                 if (p.isLibrary()) {
    691                     Set<ProjectState> set = getMainProjectsFor(p.getProject());
    692                     result.addAll(set);
    693                 }
    694             }
    695 
    696             return result;
    697         }
    698     }
    699 
    700     /**
    701      * Unload the SDK's target data.
    702      *
    703      * If <var>preventReload</var>, this effect is final until the SDK instance is changed
    704      * through {@link #loadSdk(String)}.
    705      *
    706      * The goal is to unload the targets to be able to replace existing targets with new ones,
    707      * before calling {@link #loadSdk(String)} to fully reload the SDK.
    708      *
    709      * @param preventReload prevent the data from being loaded again for the remaining live of
    710      *   this {@link Sdk} instance.
    711      */
    712     public void unloadTargetData(boolean preventReload) {
    713         synchronized (LOCK) {
    714             mDontLoadTargetData = preventReload;
    715 
    716             // dispose of the target data.
    717             for (AndroidTargetData data : mTargetDataMap.values()) {
    718                 data.dispose();
    719             }
    720 
    721             mTargetDataMap.clear();
    722         }
    723     }
    724 
    725     private Sdk(SdkManager manager, DexWrapper dexWrapper, AvdManager avdManager) {
    726         mManager = manager;
    727         mDexWrapper = dexWrapper;
    728         mAvdManager = avdManager;
    729 
    730         // listen to projects closing
    731         GlobalProjectMonitor monitor = GlobalProjectMonitor.getMonitor();
    732         // need to register the resource event listener first because the project listener
    733         // is called back during registration with project opened in the workspace.
    734         monitor.addResourceEventListener(mResourceEventListener);
    735         monitor.addProjectListener(mProjectListener);
    736         monitor.addFileListener(mFileListener,
    737                 IResourceDelta.CHANGED | IResourceDelta.ADDED | IResourceDelta.REMOVED);
    738 
    739         // pre-compute some paths
    740         mDocBaseUrl = getDocumentationBaseUrl(mManager.getLocation() +
    741                 SdkConstants.OS_SDK_DOCS_FOLDER);
    742 
    743         // load the built-in and user layout devices
    744         mLayoutDeviceManager.loadDefaultAndUserDevices(mManager.getLocation());
    745         // and the ones from the add-on
    746         loadLayoutDevices();
    747 
    748         // update whatever ProjectState is already present with new IAndroidTarget objects.
    749         synchronized (LOCK) {
    750             for (Entry<IProject, ProjectState> entry: sProjectStateMap.entrySet()) {
    751                 entry.getValue().setTarget(
    752                         getTargetFromHashString(entry.getValue().getTargetHashString()));
    753             }
    754         }
    755     }
    756 
    757     /**
    758      *  Cleans and unloads the SDK.
    759      */
    760     private void dispose() {
    761         GlobalProjectMonitor monitor = GlobalProjectMonitor.getMonitor();
    762         monitor.removeProjectListener(mProjectListener);
    763         monitor.removeFileListener(mFileListener);
    764         monitor.removeResourceEventListener(mResourceEventListener);
    765 
    766         // the IAndroidTarget objects are now obsolete so update the project states.
    767         synchronized (LOCK) {
    768             for (Entry<IProject, ProjectState> entry: sProjectStateMap.entrySet()) {
    769                 entry.getValue().setTarget(null);
    770             }
    771 
    772             // dispose of the target data.
    773             for (AndroidTargetData data : mTargetDataMap.values()) {
    774                 data.dispose();
    775             }
    776 
    777             mTargetDataMap.clear();
    778         }
    779     }
    780 
    781     void setTargetData(IAndroidTarget target, AndroidTargetData data) {
    782         synchronized (LOCK) {
    783             mTargetDataMap.put(target, data);
    784         }
    785     }
    786 
    787     /**
    788      * Returns the URL to the local documentation.
    789      * Can return null if no documentation is found in the current SDK.
    790      *
    791      * @param osDocsPath Path to the documentation folder in the current SDK.
    792      *  The folder may not actually exist.
    793      * @return A file:// URL on the local documentation folder if it exists or null.
    794      */
    795     private String getDocumentationBaseUrl(String osDocsPath) {
    796         File f = new File(osDocsPath);
    797 
    798         if (f.isDirectory()) {
    799             try {
    800                 // Note: to create a file:// URL, one would typically use something like
    801                 // f.toURI().toURL().toString(). However this generates a broken path on
    802                 // Windows, namely "C:\\foo" is converted to "file:/C:/foo" instead of
    803                 // "file:///C:/foo" (i.e. there should be 3 / after "file:"). So we'll
    804                 // do the correct thing manually.
    805 
    806                 String path = f.getAbsolutePath();
    807                 if (File.separatorChar != '/') {
    808                     path = path.replace(File.separatorChar, '/');
    809                 }
    810 
    811                 // For some reason the URL class doesn't add the mandatory "//" after
    812                 // the "file:" protocol name, so it has to be hacked into the path.
    813                 URL url = new URL("file", null, "//" + path);  //$NON-NLS-1$ //$NON-NLS-2$
    814                 String result = url.toString();
    815                 return result;
    816             } catch (MalformedURLException e) {
    817                 // ignore malformed URLs
    818             }
    819         }
    820 
    821         return null;
    822     }
    823 
    824     /**
    825      * Parses the SDK add-ons to look for files called {@link SdkConstants#FN_DEVICES_XML} to
    826      * load {@link LayoutDevice} from them.
    827      */
    828     private void loadLayoutDevices() {
    829         IAndroidTarget[] targets = mManager.getTargets();
    830         for (IAndroidTarget target : targets) {
    831             if (target.isPlatform() == false) {
    832                 File deviceXml = new File(target.getLocation(), SdkConstants.FN_DEVICES_XML);
    833                 if (deviceXml.isFile()) {
    834                     mLayoutDeviceManager.parseAddOnLayoutDevice(deviceXml);
    835                 }
    836             }
    837         }
    838 
    839         mLayoutDeviceManager.sealAddonLayoutDevices();
    840     }
    841 
    842     /**
    843      * Delegate listener for project changes.
    844      */
    845     private IProjectListener mProjectListener = new IProjectListener() {
    846         @Override
    847         public void projectClosed(IProject project) {
    848             onProjectRemoved(project, false /*deleted*/);
    849         }
    850 
    851         @Override
    852         public void projectDeleted(IProject project) {
    853             onProjectRemoved(project, true /*deleted*/);
    854         }
    855 
    856         private void onProjectRemoved(IProject removedProject, boolean deleted) {
    857             try {
    858                 if (removedProject.hasNature(AdtConstants.NATURE_DEFAULT) == false) {
    859                     return;
    860                 }
    861             } catch (CoreException e) {
    862                 // this can only happen if the project does not exist or is not open, neither
    863                 // of which can happen here since we're processing a Project removed/deleted event
    864                 // which is processed before the project is actually removed/closed.
    865             }
    866 
    867             if (DEBUG) {
    868                 System.out.println(">>> CLOSED: " + removedProject.getName());
    869             }
    870 
    871             // get the target project
    872             synchronized (LOCK) {
    873                 // Don't use getProject() as it could create the ProjectState if it's not
    874                 // there yet and this is not what we want. We want the current object.
    875                 // Therefore, direct access to the map.
    876                 ProjectState removedState = sProjectStateMap.get(removedProject);
    877                 if (removedState != null) {
    878                     // 1. clear the layout lib cache associated with this project
    879                     IAndroidTarget target = removedState.getTarget();
    880                     if (target != null) {
    881                         // get the bridge for the target, and clear the cache for this project.
    882                         AndroidTargetData data = mTargetDataMap.get(target);
    883                         if (data != null) {
    884                             LayoutLibrary layoutLib = data.getLayoutLibrary();
    885                             if (layoutLib != null && layoutLib.getStatus() == LoadStatus.LOADED) {
    886                                 layoutLib.clearCaches(removedProject);
    887                             }
    888                         }
    889                     }
    890 
    891                     // 2. if the project is a library, make sure to update the
    892                     // LibraryState for any project referencing it.
    893                     // Also, record the updated projects that are libraries, to update
    894                     // projects that depend on them.
    895                     for (ProjectState projectState : sProjectStateMap.values()) {
    896                         LibraryState libState = projectState.getLibrary(removedProject);
    897                         if (libState != null) {
    898                             // Close the library right away.
    899                             // This remove links between the LibraryState and the projectState.
    900                             // This is because in case of a rename of a project, projectClosed and
    901                             // projectOpened will be called before any other job is run, so we
    902                             // need to make sure projectOpened is closed with the main project
    903                             // state up to date.
    904                             libState.close();
    905 
    906                             // record that this project changed, and in case it's a library
    907                             // that its parents need to be updated as well.
    908                             markProject(projectState, projectState.isLibrary());
    909                         }
    910                     }
    911 
    912                     // now remove the project for the project map.
    913                     sProjectStateMap.remove(removedProject);
    914                 }
    915             }
    916 
    917             if (DEBUG) {
    918                 System.out.println("<<<");
    919             }
    920         }
    921 
    922         @Override
    923         public void projectOpened(IProject project) {
    924             onProjectOpened(project);
    925         }
    926 
    927         @Override
    928         public void projectOpenedWithWorkspace(IProject project) {
    929             // no need to force recompilation when projects are opened with the workspace.
    930             onProjectOpened(project);
    931         }
    932 
    933         @Override
    934         public void allProjectsOpenedWithWorkspace() {
    935             // Correct currently open editors
    936             fixOpenLegacyEditors();
    937         }
    938 
    939         private void onProjectOpened(final IProject openedProject) {
    940             try {
    941                 if (openedProject.hasNature(AdtConstants.NATURE_DEFAULT) == false) {
    942                     return;
    943                 }
    944             } catch (CoreException e) {
    945                 // this can only happen if the project does not exist or is not open, neither
    946                 // of which can happen here since we're processing a Project opened event.
    947             }
    948 
    949 
    950             ProjectState openedState = getProjectState(openedProject);
    951             if (openedState != null) {
    952                 if (DEBUG) {
    953                     System.out.println(">>> OPENED: " + openedProject.getName());
    954                 }
    955 
    956                 synchronized (LOCK) {
    957                     final boolean isLibrary = openedState.isLibrary();
    958                     final boolean hasLibraries = openedState.hasLibraries();
    959 
    960                     if (isLibrary || hasLibraries) {
    961                         boolean foundLibraries = false;
    962                         // loop on all the existing project and update them based on this new
    963                         // project
    964                         for (ProjectState projectState : sProjectStateMap.values()) {
    965                             if (projectState != openedState) {
    966                                 // If the project has libraries, check if this project
    967                                 // is a reference.
    968                                 if (hasLibraries) {
    969                                     // ProjectState#needs() both checks if this is a missing library
    970                                     // and updates LibraryState to contains the new values.
    971                                     // This must always be called.
    972                                     LibraryState libState = openedState.needs(projectState);
    973 
    974                                     if (libState != null) {
    975                                         // found a library! Add the main project to the list of
    976                                         // modified project
    977                                         foundLibraries = true;
    978                                     }
    979                                 }
    980 
    981                                 // if the project is a library check if the other project depend
    982                                 // on it.
    983                                 if (isLibrary) {
    984                                     // ProjectState#needs() both checks if this is a missing library
    985                                     // and updates LibraryState to contains the new values.
    986                                     // This must always be called.
    987                                     LibraryState libState = projectState.needs(openedState);
    988 
    989                                     if (libState != null) {
    990                                         // There's a dependency! Add the project to the list of
    991                                         // modified project, but also to a list of projects
    992                                         // that saw one of its dependencies resolved.
    993                                         markProject(projectState, projectState.isLibrary());
    994                                     }
    995                                 }
    996                             }
    997                         }
    998 
    999                         // if the project has a libraries and we found at least one, we add
   1000                         // the project to the list of modified project.
   1001                         // Since we already went through the parent, no need to update them.
   1002                         if (foundLibraries) {
   1003                             markProject(openedState, false /*updateParents*/);
   1004                         }
   1005                     }
   1006                 }
   1007 
   1008                 // Correct file editor associations.
   1009                 fixEditorAssociations(openedProject);
   1010 
   1011                 if (DEBUG) {
   1012                     System.out.println("<<<");
   1013                 }
   1014             }
   1015         }
   1016 
   1017         @Override
   1018         public void projectRenamed(IProject project, IPath from) {
   1019             // we don't actually care about this anymore.
   1020         }
   1021     };
   1022 
   1023     /**
   1024      * Delegate listener for file changes.
   1025      */
   1026     private IFileListener mFileListener = new IFileListener() {
   1027         @Override
   1028         public void fileChanged(final IFile file, IMarkerDelta[] markerDeltas, int kind) {
   1029             if (SdkConstants.FN_PROJECT_PROPERTIES.equals(file.getName()) &&
   1030                     file.getParent() == file.getProject()) {
   1031                 try {
   1032                     // reload the content of the project.properties file and update
   1033                     // the target.
   1034                     IProject iProject = file.getProject();
   1035 
   1036                     if (iProject.hasNature(AdtConstants.NATURE_DEFAULT) == false) {
   1037                         return;
   1038                     }
   1039 
   1040                     ProjectState state = Sdk.getProjectState(iProject);
   1041 
   1042                     // get the current target
   1043                     IAndroidTarget oldTarget = state.getTarget();
   1044 
   1045                     // get the current library flag
   1046                     boolean wasLibrary = state.isLibrary();
   1047 
   1048                     LibraryDifference diff = state.reloadProperties();
   1049 
   1050                     // load the (possibly new) target.
   1051                     IAndroidTarget newTarget = loadTarget(state);
   1052 
   1053                     // reload the libraries if needed
   1054                     if (diff.hasDiff()) {
   1055                         if (diff.added) {
   1056                             synchronized (LOCK) {
   1057                                 for (ProjectState projectState : sProjectStateMap.values()) {
   1058                                     if (projectState != state) {
   1059                                         // need to call needs to do the libraryState link,
   1060                                         // but no need to look at the result, as we'll compare
   1061                                         // the result of getFullLibraryProjects()
   1062                                         // this is easier to due to indirect dependencies.
   1063                                         state.needs(projectState);
   1064                                     }
   1065                                 }
   1066                             }
   1067                         }
   1068 
   1069                         markProject(state, wasLibrary || state.isLibrary());
   1070                     }
   1071 
   1072                     // apply the new target if needed.
   1073                     if (newTarget != oldTarget) {
   1074                         IJavaProject javaProject = BaseProjectHelper.getJavaProject(
   1075                                 file.getProject());
   1076                         if (javaProject != null) {
   1077                             ProjectHelper.updateProject(javaProject);
   1078                         }
   1079 
   1080                         // update the editors to reload with the new target
   1081                         AdtPlugin.getDefault().updateTargetListeners(iProject);
   1082                     }
   1083                 } catch (CoreException e) {
   1084                     // This can't happen as it's only for closed project (or non existing)
   1085                     // but in that case we can't get a fileChanged on this file.
   1086                 }
   1087             } else if (kind == IResourceDelta.ADDED || kind == IResourceDelta.REMOVED) {
   1088                 // check if it's an add/remove on a jar files inside libs
   1089                 if (file.getProjectRelativePath().segmentCount() == 2 &&
   1090                         file.getParent().getName().equals(SdkConstants.FD_NATIVE_LIBS)) {
   1091                     // need to update the project and whatever depend on it.
   1092 
   1093                     processJarFileChange(file);
   1094                 }
   1095             }
   1096         }
   1097 
   1098         private void processJarFileChange(final IFile file) {
   1099             try {
   1100                 IProject iProject = file.getProject();
   1101 
   1102                 if (iProject.hasNature(AdtConstants.NATURE_DEFAULT) == false) {
   1103                     return;
   1104                 }
   1105 
   1106                 List<IJavaProject> projectList = new ArrayList<IJavaProject>();
   1107                 IJavaProject javaProject = BaseProjectHelper.getJavaProject(iProject);
   1108                 if (javaProject != null) {
   1109                     projectList.add(javaProject);
   1110                 }
   1111 
   1112                 ProjectState state = Sdk.getProjectState(iProject);
   1113 
   1114                 Collection<ProjectState> parents = state.getFullParentProjects();
   1115                 for (ProjectState s : parents) {
   1116                     javaProject = BaseProjectHelper.getJavaProject(s.getProject());
   1117                     if (javaProject != null) {
   1118                         projectList.add(javaProject);
   1119                     }
   1120                 }
   1121 
   1122                 ProjectHelper.updateProjects(
   1123                         projectList.toArray(new IJavaProject[projectList.size()]));
   1124 
   1125             } catch (CoreException e) {
   1126                 // This can't happen as it's only for closed project (or non existing)
   1127                 // but in that case we can't get a fileChanged on this file.
   1128             }
   1129         }
   1130     };
   1131 
   1132     /** List of modified projects. This is filled in
   1133      * {@link IProjectListener#projectOpened(IProject)},
   1134      * {@link IProjectListener#projectOpenedWithWorkspace(IProject)},
   1135      * {@link IProjectListener#projectClosed(IProject)}, and
   1136      * {@link IProjectListener#projectDeleted(IProject)} and processed in
   1137      * {@link IResourceEventListener#resourceChangeEventEnd()}.
   1138      */
   1139     private final List<ProjectState> mModifiedProjects = new ArrayList<ProjectState>();
   1140     private final List<ProjectState> mModifiedChildProjects = new ArrayList<ProjectState>();
   1141 
   1142     private void markProject(ProjectState projectState, boolean updateParents) {
   1143         if (mModifiedProjects.contains(projectState) == false) {
   1144             if (DEBUG) {
   1145                 System.out.println("\tMARKED: " + projectState.getProject().getName());
   1146             }
   1147             mModifiedProjects.add(projectState);
   1148         }
   1149 
   1150         // if the project is resolved also add it to this list.
   1151         if (updateParents) {
   1152             if (mModifiedChildProjects.contains(projectState) == false) {
   1153                 if (DEBUG) {
   1154                     System.out.println("\tMARKED(child): " + projectState.getProject().getName());
   1155                 }
   1156                 mModifiedChildProjects.add(projectState);
   1157             }
   1158         }
   1159     }
   1160 
   1161     /**
   1162      * Delegate listener for resource changes. This is called before and after any calls to the
   1163      * project and file listeners (for a given resource change event).
   1164      */
   1165     private IResourceEventListener mResourceEventListener = new IResourceEventListener() {
   1166         @Override
   1167         public void resourceChangeEventStart() {
   1168             mModifiedProjects.clear();
   1169             mModifiedChildProjects.clear();
   1170         }
   1171 
   1172         @Override
   1173         public void resourceChangeEventEnd() {
   1174             if (mModifiedProjects.size() == 0) {
   1175                 return;
   1176             }
   1177 
   1178             // first make sure all the parents are updated
   1179             updateParentProjects();
   1180 
   1181             // for all modified projects, update their library list
   1182             // and gather their IProject
   1183             final List<IJavaProject> projectList = new ArrayList<IJavaProject>();
   1184             for (ProjectState state : mModifiedProjects) {
   1185                 state.updateFullLibraryList();
   1186                 projectList.add(JavaCore.create(state.getProject()));
   1187             }
   1188 
   1189             Job job = new Job("Android Library Update") { //$NON-NLS-1$
   1190                 @Override
   1191                 protected IStatus run(IProgressMonitor monitor) {
   1192                     LibraryClasspathContainerInitializer.updateProjects(
   1193                             projectList.toArray(new IJavaProject[projectList.size()]));
   1194 
   1195                     for (IJavaProject javaProject : projectList) {
   1196                         try {
   1197                             javaProject.getProject().build(IncrementalProjectBuilder.FULL_BUILD,
   1198                                     monitor);
   1199                         } catch (CoreException e) {
   1200                             // pass
   1201                         }
   1202                     }
   1203                     return Status.OK_STATUS;
   1204                 }
   1205             };
   1206             job.setPriority(Job.BUILD);
   1207             job.schedule();
   1208         }
   1209     };
   1210 
   1211     /**
   1212      * Updates all existing projects with a given list of new/updated libraries.
   1213      * This loops through all opened projects and check if they depend on any of the given
   1214      * library project, and if they do, they are linked together.
   1215      */
   1216     private void updateParentProjects() {
   1217         if (mModifiedChildProjects.size() == 0) {
   1218             return;
   1219         }
   1220 
   1221         ArrayList<ProjectState> childProjects = new ArrayList<ProjectState>(mModifiedChildProjects);
   1222         mModifiedChildProjects.clear();
   1223         synchronized (LOCK) {
   1224             // for each project for which we must update its parent, we loop on the parent
   1225             // projects and adds them to the list of modified projects. If they are themselves
   1226             // libraries, we add them too.
   1227             for (ProjectState state : childProjects) {
   1228                 if (DEBUG) {
   1229                     System.out.println(">>> Updating parents of " + state.getProject().getName());
   1230                 }
   1231                 List<ProjectState> parents = state.getParentProjects();
   1232                 for (ProjectState parent : parents) {
   1233                     markProject(parent, parent.isLibrary());
   1234                 }
   1235                 if (DEBUG) {
   1236                     System.out.println("<<<");
   1237                 }
   1238             }
   1239         }
   1240 
   1241         // done, but there may be parents that are also libraries. Need to update their parents.
   1242         updateParentProjects();
   1243     }
   1244 
   1245     /**
   1246      * Fix editor associations for the given project, if not already done.
   1247      * <p/>
   1248      * Eclipse has a per-file setting for which editor should be used for each file
   1249      * (see {@link IDE#setDefaultEditor(IFile, String)}).
   1250      * We're using this flag to pick between the various XML editors (layout, drawable, etc)
   1251      * since they all have the same file name extension.
   1252      * <p/>
   1253      * Unfortunately, the file setting can be "wrong" for two reasons:
   1254      * <ol>
   1255      *   <li> The editor type was added <b>after</b> a file had been seen by the IDE.
   1256      *        For example, we added new editors for animations and for drawables around
   1257      *        ADT 12, but any file seen by ADT in earlier versions will continue to use
   1258      *        the vanilla Eclipse XML editor instead.
   1259      *   <li> A bug in ADT 14 and ADT 15 (see issue 21124) meant that files created in new
   1260      *        folders would end up with wrong editor associations. Even though that bug
   1261      *        is fixed in ADT 16, the fix only affects new files, it cannot retroactively
   1262      *        fix editor associations that were set incorrectly by ADT 14 or 15.
   1263      * </ol>
   1264      * <p/>
   1265      * This method attempts to fix the editor bindings retroactively by scanning all the
   1266      * resource XML files and resetting the editor associations.
   1267      * Since this is a potentially slow operation, this is only done "once"; we use a
   1268      * persistent project property to avoid looking repeatedly. In the future if we add
   1269      * additional editors, we can rev the scanned version value.
   1270      */
   1271     private void fixEditorAssociations(final IProject project) {
   1272         QualifiedName KEY = new QualifiedName(AdtPlugin.PLUGIN_ID, "editorbinding"); //$NON-NLS-1$
   1273 
   1274         try {
   1275             String value = project.getPersistentProperty(KEY);
   1276             int currentVersion = 0;
   1277             if (value != null) {
   1278                 try {
   1279                     currentVersion = Integer.parseInt(value);
   1280                 } catch (Exception ingore) {
   1281                 }
   1282             }
   1283 
   1284             // The target version we're comparing to. This must be incremented each time
   1285             // we change the processing here so that a new version of the plugin would
   1286             // try to fix existing user projects.
   1287             final int targetVersion = 2;
   1288 
   1289             if (currentVersion >= targetVersion) {
   1290                 return;
   1291             }
   1292 
   1293             // Set to specific version such that we can rev the version in the future
   1294             // to trigger further scanning
   1295             project.setPersistentProperty(KEY, Integer.toString(targetVersion));
   1296 
   1297             // Now update the actual editor associations.
   1298             Job job = new Job("Update Android editor bindings") { //$NON-NLS-1$
   1299                 @Override
   1300                 protected IStatus run(IProgressMonitor monitor) {
   1301                     try {
   1302                         for (IResource folderResource : project.getFolder(FD_RES).members()) {
   1303                             if (folderResource instanceof IFolder) {
   1304                                 IFolder folder = (IFolder) folderResource;
   1305 
   1306                                 for (IResource resource : folder.members()) {
   1307                                     if (resource instanceof IFile &&
   1308                                             resource.getName().endsWith(DOT_XML)) {
   1309                                         fixXmlFile((IFile) resource);
   1310                                     }
   1311                                 }
   1312                             }
   1313                         }
   1314 
   1315                         // TODO change AndroidManifest.xml ID too
   1316 
   1317                     } catch (CoreException e) {
   1318                         AdtPlugin.log(e, null);
   1319                     }
   1320 
   1321                     return Status.OK_STATUS;
   1322                 }
   1323 
   1324                 /**
   1325                  * Attempt to fix the editor ID for the given /res XML file.
   1326                  */
   1327                 private void fixXmlFile(final IFile file) {
   1328                     // Fix the default editor ID for this resource.
   1329                     // This has no effect on currently open editors.
   1330                     IEditorDescriptor desc = IDE.getDefaultEditor(file);
   1331 
   1332                     if (desc == null || !CommonXmlEditor.ID.equals(desc.getId())) {
   1333                         IDE.setDefaultEditor(file, CommonXmlEditor.ID);
   1334                     }
   1335                 }
   1336             };
   1337             job.setPriority(Job.BUILD);
   1338             job.schedule();
   1339         } catch (CoreException e) {
   1340             AdtPlugin.log(e, null);
   1341         }
   1342     }
   1343 
   1344     /**
   1345      * Tries to fix all currently open Android legacy editors.
   1346      * <p/>
   1347      * If an editor is found to match one of the legacy ids, we'll try to close it.
   1348      * If that succeeds, we try to reopen it using the new common editor ID.
   1349      * <p/>
   1350      * This method must be run from the UI thread.
   1351      */
   1352     private void fixOpenLegacyEditors() {
   1353 
   1354         AdtPlugin adt = AdtPlugin.getDefault();
   1355         if (adt == null) {
   1356             return;
   1357         }
   1358 
   1359         final IPreferenceStore store = adt.getPreferenceStore();
   1360         int currentValue = store.getInt(AdtPrefs.PREFS_FIX_LEGACY_EDITORS);
   1361         // The target version we're comparing to. This must be incremented each time
   1362         // we change the processing here so that a new version of the plugin would
   1363         // try to fix existing editors.
   1364         final int targetValue = 1;
   1365 
   1366         if (currentValue >= targetValue) {
   1367             return;
   1368         }
   1369 
   1370         // To be able to close and open editors we need to make sure this is done
   1371         // in the UI thread, which this isn't invoked from.
   1372         PlatformUI.getWorkbench().getDisplay().asyncExec(new Runnable() {
   1373             @Override
   1374             public void run() {
   1375                 HashSet<String> legacyIds =
   1376                     new HashSet<String>(Arrays.asList(CommonXmlEditor.LEGACY_EDITOR_IDS));
   1377 
   1378                 for (IWorkbenchWindow win : PlatformUI.getWorkbench().getWorkbenchWindows()) {
   1379                     for (IWorkbenchPage page : win.getPages()) {
   1380                         for (IEditorReference ref : page.getEditorReferences()) {
   1381                             try {
   1382                                 IEditorInput input = ref.getEditorInput();
   1383                                 if (input instanceof IFileEditorInput) {
   1384                                     IFile file = ((IFileEditorInput)input).getFile();
   1385                                     IEditorPart part = ref.getEditor(true /*restore*/);
   1386                                     if (part != null) {
   1387                                         IWorkbenchPartSite site = part.getSite();
   1388                                         if (site != null) {
   1389                                             String id = site.getId();
   1390                                             if (legacyIds.contains(id)) {
   1391                                                 // This editor matches one of legacy editor IDs.
   1392                                                 fixEditor(page, part, input, file, id);
   1393                                             }
   1394                                         }
   1395                                     }
   1396                                 }
   1397                             } catch (Exception e) {
   1398                                 // ignore
   1399                             }
   1400                         }
   1401                     }
   1402                 }
   1403 
   1404                 // Remember that we managed to do fix all editors
   1405                 store.setValue(AdtPrefs.PREFS_FIX_LEGACY_EDITORS, targetValue);
   1406             }
   1407 
   1408             private void fixEditor(
   1409                     IWorkbenchPage page,
   1410                     IEditorPart part,
   1411                     IEditorInput input,
   1412                     IFile file,
   1413                     String id) {
   1414                 IDE.setDefaultEditor(file, CommonXmlEditor.ID);
   1415 
   1416                 boolean ok = page.closeEditor(part, true /*save*/);
   1417 
   1418                 AdtPlugin.log(IStatus.INFO,
   1419                     "Closed legacy editor ID %s for %s: %s", //$NON-NLS-1$
   1420                     id,
   1421                     file.getFullPath(),
   1422                     ok ? "Success" : "Failed");//$NON-NLS-1$ //$NON-NLS-2$
   1423 
   1424                 if (ok) {
   1425                     // Try to reopen it with the new ID
   1426                     try {
   1427                         page.openEditor(input, CommonXmlEditor.ID);
   1428                     } catch (PartInitException e) {
   1429                         AdtPlugin.log(e,
   1430                             "Failed to reopen %s",          //$NON-NLS-1$
   1431                             file.getFullPath());
   1432                     }
   1433                 }
   1434             }
   1435         });
   1436     }
   1437 }
   1438