Home | History | Annotate | Download | only in adt
      1 /*
      2  * Copyright (C) 2007 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;
     18 
     19 import com.android.ddmuilib.console.DdmConsole;
     20 import com.android.ddmuilib.console.IDdmConsole;
     21 import com.android.ide.common.log.ILogger;
     22 import com.android.ide.common.resources.ResourceFile;
     23 import com.android.ide.common.resources.ResourceFolder;
     24 import com.android.ide.common.sdk.LoadStatus;
     25 import com.android.ide.eclipse.adt.internal.VersionCheck;
     26 import com.android.ide.eclipse.adt.internal.editors.AndroidXmlEditor;
     27 import com.android.ide.eclipse.adt.internal.editors.IconFactory;
     28 import com.android.ide.eclipse.adt.internal.editors.animator.AnimationEditor;
     29 import com.android.ide.eclipse.adt.internal.editors.color.ColorEditor;
     30 import com.android.ide.eclipse.adt.internal.editors.drawable.DrawableEditor;
     31 import com.android.ide.eclipse.adt.internal.editors.layout.LayoutEditor;
     32 import com.android.ide.eclipse.adt.internal.editors.layout.gle2.IncludeFinder;
     33 import com.android.ide.eclipse.adt.internal.editors.menu.MenuEditor;
     34 import com.android.ide.eclipse.adt.internal.editors.resources.ResourcesEditor;
     35 import com.android.ide.eclipse.adt.internal.editors.xml.XmlEditor;
     36 import com.android.ide.eclipse.adt.internal.preferences.AdtPrefs;
     37 import com.android.ide.eclipse.adt.internal.preferences.AdtPrefs.BuildVerbosity;
     38 import com.android.ide.eclipse.adt.internal.project.AndroidClasspathContainerInitializer;
     39 import com.android.ide.eclipse.adt.internal.project.BaseProjectHelper;
     40 import com.android.ide.eclipse.adt.internal.project.ProjectHelper;
     41 import com.android.ide.eclipse.adt.internal.resources.manager.GlobalProjectMonitor;
     42 import com.android.ide.eclipse.adt.internal.resources.manager.GlobalProjectMonitor.IFileListener;
     43 import com.android.ide.eclipse.adt.internal.resources.manager.ProjectResources;
     44 import com.android.ide.eclipse.adt.internal.resources.manager.ResourceManager;
     45 import com.android.ide.eclipse.adt.internal.sdk.Sdk;
     46 import com.android.ide.eclipse.adt.internal.sdk.Sdk.ITargetChangeListener;
     47 import com.android.ide.eclipse.adt.internal.ui.EclipseUiHelper;
     48 import com.android.ide.eclipse.ddms.DdmsPlugin;
     49 import com.android.io.StreamException;
     50 import com.android.resources.ResourceFolderType;
     51 import com.android.sdklib.IAndroidTarget;
     52 import com.android.sdklib.SdkConstants;
     53 
     54 import org.eclipse.core.resources.IFile;
     55 import org.eclipse.core.resources.IFolder;
     56 import org.eclipse.core.resources.IMarkerDelta;
     57 import org.eclipse.core.resources.IProject;
     58 import org.eclipse.core.resources.IResourceDelta;
     59 import org.eclipse.core.resources.IWorkspace;
     60 import org.eclipse.core.resources.ResourcesPlugin;
     61 import org.eclipse.core.runtime.CoreException;
     62 import org.eclipse.core.runtime.IProgressMonitor;
     63 import org.eclipse.core.runtime.IStatus;
     64 import org.eclipse.core.runtime.QualifiedName;
     65 import org.eclipse.core.runtime.Status;
     66 import org.eclipse.core.runtime.SubMonitor;
     67 import org.eclipse.core.runtime.jobs.Job;
     68 import org.eclipse.jdt.core.IJavaElement;
     69 import org.eclipse.jdt.core.IJavaProject;
     70 import org.eclipse.jdt.core.JavaCore;
     71 import org.eclipse.jdt.ui.JavaUI;
     72 import org.eclipse.jface.dialogs.MessageDialog;
     73 import org.eclipse.jface.preference.IPreferenceStore;
     74 import org.eclipse.jface.resource.ImageDescriptor;
     75 import org.eclipse.jface.text.IRegion;
     76 import org.eclipse.jface.util.IPropertyChangeListener;
     77 import org.eclipse.jface.util.PropertyChangeEvent;
     78 import org.eclipse.swt.graphics.Color;
     79 import org.eclipse.swt.graphics.Image;
     80 import org.eclipse.swt.widgets.Display;
     81 import org.eclipse.swt.widgets.Shell;
     82 import org.eclipse.ui.IEditorDescriptor;
     83 import org.eclipse.ui.IEditorPart;
     84 import org.eclipse.ui.IWorkbench;
     85 import org.eclipse.ui.IWorkbenchPage;
     86 import org.eclipse.ui.IWorkbenchWindow;
     87 import org.eclipse.ui.PartInitException;
     88 import org.eclipse.ui.PlatformUI;
     89 import org.eclipse.ui.browser.IWebBrowser;
     90 import org.eclipse.ui.browser.IWorkbenchBrowserSupport;
     91 import org.eclipse.ui.console.ConsolePlugin;
     92 import org.eclipse.ui.console.IConsole;
     93 import org.eclipse.ui.console.IConsoleConstants;
     94 import org.eclipse.ui.console.MessageConsole;
     95 import org.eclipse.ui.console.MessageConsoleStream;
     96 import org.eclipse.ui.ide.IDE;
     97 import org.eclipse.ui.part.FileEditorInput;
     98 import org.eclipse.ui.plugin.AbstractUIPlugin;
     99 import org.osgi.framework.Bundle;
    100 import org.osgi.framework.BundleContext;
    101 
    102 import java.io.BufferedInputStream;
    103 import java.io.BufferedReader;
    104 import java.io.File;
    105 import java.io.FileNotFoundException;
    106 import java.io.FileReader;
    107 import java.io.FileWriter;
    108 import java.io.IOException;
    109 import java.io.InputStream;
    110 import java.io.InputStreamReader;
    111 import java.io.OutputStream;
    112 import java.io.Reader;
    113 import java.net.MalformedURLException;
    114 import java.net.URL;
    115 import java.util.ArrayList;
    116 import java.util.Arrays;
    117 import java.util.List;
    118 
    119 /**
    120  * The activator class controls the plug-in life cycle
    121  */
    122 public class AdtPlugin extends AbstractUIPlugin implements ILogger {
    123     /**
    124      * Temporary logging code to help track down
    125      * http://code.google.com/p/android/issues/detail?id=15003
    126      *
    127      * Deactivated right now.
    128      * TODO remove this and associated logging code once we're done with issue 15003.
    129      */
    130     public static final boolean DEBUG_XML_FILE_INIT = false;
    131 
    132     /** The plug-in ID */
    133     public static final String PLUGIN_ID = "com.android.ide.eclipse.adt"; //$NON-NLS-1$
    134 
    135     /** singleton instance */
    136     private static AdtPlugin sPlugin;
    137 
    138     private static Image sAndroidLogo;
    139     private static ImageDescriptor sAndroidLogoDesc;
    140 
    141     /** The global android console */
    142     private MessageConsole mAndroidConsole;
    143 
    144     /** Stream to write in the android console */
    145     private MessageConsoleStream mAndroidConsoleStream;
    146 
    147     /** Stream to write error messages to the android console */
    148     private MessageConsoleStream mAndroidConsoleErrorStream;
    149 
    150     /** Color used in the error console */
    151     private Color mRed;
    152 
    153     /** Load status of the SDK. Any access MUST be in a synchronized(mPostLoadProjects) block */
    154     private LoadStatus mSdkIsLoaded = LoadStatus.LOADING;
    155     /** Project to update once the SDK is loaded.
    156      * Any access MUST be in a synchronized(mPostLoadProjectsToResolve) block */
    157     private final ArrayList<IJavaProject> mPostLoadProjectsToResolve =
    158             new ArrayList<IJavaProject>();
    159     /** Project to check validity of cache vs actual once the SDK is loaded.
    160      * Any access MUST be in a synchronized(mPostLoadProjectsToResolve) block */
    161     private final ArrayList<IJavaProject> mPostLoadProjectsToCheck = new ArrayList<IJavaProject>();
    162 
    163     private GlobalProjectMonitor mResourceMonitor;
    164     private ArrayList<ITargetChangeListener> mTargetChangeListeners =
    165             new ArrayList<ITargetChangeListener>();
    166 
    167     protected boolean mSdkIsLoading;
    168 
    169     /**
    170      * An error handler for checkSdkLocationAndId() that will handle the generated error
    171      * or warning message. Each method must return a boolean that will in turn be returned by
    172      * checkSdkLocationAndId.
    173      */
    174     public static abstract class CheckSdkErrorHandler {
    175         /** Handle an error message during sdk location check. Returns whatever
    176          * checkSdkLocationAndId() should returns.
    177          */
    178         public abstract boolean handleError(String message);
    179 
    180         /** Handle a warning message during sdk location check. Returns whatever
    181          * checkSdkLocationAndId() should returns.
    182          */
    183         public abstract boolean handleWarning(String message);
    184     }
    185 
    186     /**
    187      * The constructor
    188      */
    189     public AdtPlugin() {
    190         sPlugin = this;
    191     }
    192 
    193     /*
    194      * (non-Javadoc)
    195      * @see org.eclipse.ui.plugin.AbstractUIPlugin#start(org.osgi.framework.BundleContext)
    196      */
    197     @Override
    198     public void start(BundleContext context) throws Exception {
    199         super.start(context);
    200 
    201         Display display = getDisplay();
    202 
    203         // set the default android console.
    204         mAndroidConsole = new MessageConsole("Android", null); //$NON-NLS-1$
    205         ConsolePlugin.getDefault().getConsoleManager().addConsoles(
    206                 new IConsole[] { mAndroidConsole });
    207 
    208         // get the stream to write in the android console.
    209         mAndroidConsoleStream = mAndroidConsole.newMessageStream();
    210         mAndroidConsoleErrorStream = mAndroidConsole.newMessageStream();
    211         mRed = new Color(display, 0xFF, 0x00, 0x00);
    212 
    213         // because this can be run, in some cases, by a non ui thread, and because
    214         // changing the console properties update the ui, we need to make this change
    215         // in the ui thread.
    216         display.asyncExec(new Runnable() {
    217             public void run() {
    218                 mAndroidConsoleErrorStream.setColor(mRed);
    219             }
    220         });
    221 
    222         // set up the ddms console to use this objects
    223         DdmConsole.setConsole(new IDdmConsole() {
    224             public void printErrorToConsole(String message) {
    225                 AdtPlugin.printErrorToConsole((String)null, message);
    226             }
    227             public void printErrorToConsole(String[] messages) {
    228                 AdtPlugin.printErrorToConsole((String)null, (Object[])messages);
    229             }
    230             public void printToConsole(String message) {
    231                 AdtPlugin.printToConsole((String)null, message);
    232             }
    233             public void printToConsole(String[] messages) {
    234                 AdtPlugin.printToConsole((String)null, (Object[])messages);
    235             }
    236         });
    237 
    238         // get the eclipse store
    239         IPreferenceStore eclipseStore = getPreferenceStore();
    240         AdtPrefs.init(eclipseStore);
    241 
    242         // set the listener for the preference change
    243         eclipseStore.addPropertyChangeListener(new IPropertyChangeListener() {
    244             public void propertyChange(PropertyChangeEvent event) {
    245                 // load the new preferences
    246                 AdtPrefs.getPrefs().loadValues(event);
    247 
    248                 // if the SDK changed, we have to do some extra work
    249                 if (AdtPrefs.PREFS_SDK_DIR.equals(event.getProperty())) {
    250 
    251                     // finally restart adb, in case it's a different version
    252                     DdmsPlugin.setToolsLocation(getOsAbsoluteAdb(), true /* startAdb */,
    253                             getOsAbsoluteHprofConv(), getOsAbsoluteTraceview());
    254 
    255                     // get the SDK location and build id.
    256                     if (checkSdkLocationAndId()) {
    257                         // if sdk if valid, reparse it
    258 
    259                         reparseSdk();
    260                     }
    261                 }
    262             }
    263         });
    264 
    265         // load preferences.
    266         AdtPrefs.getPrefs().loadValues(null /*event*/);
    267 
    268         // initialize editors
    269         startEditors();
    270 
    271         // Listen on resource file edits for updates to file inclusion
    272         IncludeFinder.start();
    273 
    274         // Parse the SDK content.
    275         // This is deferred in separate jobs to avoid blocking the bundle start.
    276         final boolean isSdkLocationValid = checkSdkLocationAndId();
    277         if (isSdkLocationValid) {
    278             // parse the SDK resources.
    279             // Wait 2 seconds before starting the job. This leaves some time to the
    280             // other bundles to initialize.
    281             parseSdkContent(2000 /*milliseconds*/);
    282         }
    283     }
    284 
    285     /*
    286      * (non-Javadoc)
    287      *
    288      * @see org.eclipse.ui.plugin.AbstractUIPlugin#stop(org.osgi.framework.BundleContext)
    289      */
    290     @Override
    291     public void stop(BundleContext context) throws Exception {
    292         super.stop(context);
    293 
    294         stopEditors();
    295         IncludeFinder.stop();
    296 
    297         mRed.dispose();
    298         synchronized (AdtPlugin.class) {
    299             sPlugin = null;
    300         }
    301     }
    302 
    303     /**
    304      * Returns the shared instance
    305      *
    306      * @return the shared instance
    307      */
    308     public static synchronized AdtPlugin getDefault() {
    309         return sPlugin;
    310     }
    311 
    312     public static Display getDisplay() {
    313         IWorkbench bench = null;
    314         synchronized (AdtPlugin.class) {
    315             bench = sPlugin.getWorkbench();
    316         }
    317 
    318         if (bench != null) {
    319             return bench.getDisplay();
    320         }
    321         return null;
    322     }
    323 
    324     /** Returns the adb path relative to the sdk folder */
    325     public static String getOsRelativeAdb() {
    326         return SdkConstants.OS_SDK_PLATFORM_TOOLS_FOLDER + SdkConstants.FN_ADB;
    327     }
    328 
    329     /** Returns the zipalign path relative to the sdk folder */
    330     public static String getOsRelativeZipAlign() {
    331         return SdkConstants.OS_SDK_TOOLS_FOLDER + SdkConstants.FN_ZIPALIGN;
    332     }
    333 
    334     /** Returns the emulator path relative to the sdk folder */
    335     public static String getOsRelativeEmulator() {
    336         return SdkConstants.OS_SDK_TOOLS_FOLDER + SdkConstants.FN_EMULATOR;
    337     }
    338 
    339     /** Returns the adb path relative to the sdk folder */
    340     public static String getOsRelativeProguard() {
    341         return SdkConstants.OS_SDK_TOOLS_PROGUARD_BIN_FOLDER + SdkConstants.FN_PROGUARD;
    342     }
    343 
    344     /** Returns the absolute adb path */
    345     public static String getOsAbsoluteAdb() {
    346         return getOsSdkFolder() + getOsRelativeAdb();
    347     }
    348 
    349     /** Returns the absolute zipalign path */
    350     public static String getOsAbsoluteZipAlign() {
    351         return getOsSdkFolder() + getOsRelativeZipAlign();
    352     }
    353 
    354     /** Returns the absolute traceview path */
    355     public static String getOsAbsoluteTraceview() {
    356         return getOsSdkFolder() + SdkConstants.OS_SDK_TOOLS_FOLDER +
    357                 AdtConstants.FN_TRACEVIEW;
    358     }
    359 
    360     /** Returns the absolute emulator path */
    361     public static String getOsAbsoluteEmulator() {
    362         return getOsSdkFolder() + getOsRelativeEmulator();
    363     }
    364 
    365     public static String getOsAbsoluteHprofConv() {
    366         return getOsSdkFolder() + SdkConstants.OS_SDK_TOOLS_FOLDER +
    367                 AdtConstants.FN_HPROF_CONV;
    368     }
    369 
    370     /** Returns the absolute proguard path */
    371     public static String getOsAbsoluteProguard() {
    372         return getOsSdkFolder() + getOsRelativeProguard();
    373     }
    374 
    375     /**
    376      * Returns a Url file path to the javaDoc folder.
    377      */
    378     public static String getUrlDoc() {
    379         return ProjectHelper.getJavaDocPath(
    380                 getOsSdkFolder() + AdtConstants.WS_JAVADOC_FOLDER_LEAF);
    381     }
    382 
    383     /**
    384      * Returns the SDK folder.
    385      * Guaranteed to be terminated by a platform-specific path separator.
    386      */
    387     public static synchronized String getOsSdkFolder() {
    388         if (sPlugin == null) {
    389             return null;
    390         }
    391 
    392         return AdtPrefs.getPrefs().getOsSdkFolder();
    393     }
    394 
    395     public static String getOsSdkToolsFolder() {
    396         return getOsSdkFolder() + SdkConstants.OS_SDK_TOOLS_FOLDER;
    397     }
    398 
    399     /**
    400      * Returns an image descriptor for the image file at the given
    401      * plug-in relative path
    402      *
    403      * @param path the path
    404      * @return the image descriptor
    405      */
    406     public static ImageDescriptor getImageDescriptor(String path) {
    407         return imageDescriptorFromPlugin(PLUGIN_ID, path);
    408     }
    409 
    410     /**
    411      * Reads the contents of an {@link IFile} and return it as a String
    412      *
    413      * @param file the file to be read
    414      * @return the String read from the file, or null if there was an error
    415      */
    416     public static String readFile(IFile file) {
    417         InputStream contents = null;
    418         try {
    419             contents = file.getContents();
    420             String charset = file.getCharset();
    421             return readFile(new InputStreamReader(contents, charset));
    422         } catch (CoreException e) {
    423             // pass -- ignore files we can't read
    424         } catch (IOException e) {
    425             // pass -- ignore files we can't read.
    426 
    427             // Note that IFile.getContents() indicates it throws a CoreException but
    428             // experience shows that if the file does not exists it really throws
    429             // IOException.
    430             // New InputStreamReader() throws UnsupportedEncodingException
    431             // which is handled by this IOException catch.
    432 
    433         } finally {
    434             try {
    435                 if (contents != null) {
    436                     contents.close();
    437                 }
    438             } catch (IOException e) {
    439                 // ignore
    440             }
    441         }
    442 
    443         return null;
    444     }
    445 
    446     /**
    447      * Reads the contents of an {@link File} and return it as a String
    448      *
    449      * @param file the file to be read
    450      * @return the String read from the file, or null if there was an error
    451      */
    452     public static String readFile(File file) {
    453         try {
    454             return readFile(new FileReader(file));
    455         } catch (FileNotFoundException e) {
    456             AdtPlugin.log(e, "Can't read file %1$s", file); //$NON-NLS-1$
    457         }
    458 
    459         return null;
    460     }
    461 
    462     /**
    463      * Writes the given content out to the given {@link File}. The file will be deleted if
    464      * it already exists.
    465      *
    466      * @param file the target file
    467      * @param content the content to be written into the file
    468      */
    469     public static void writeFile(File file, String content) {
    470         if (file.exists()) {
    471             file.delete();
    472         }
    473         FileWriter fw = null;
    474         try {
    475             fw = new FileWriter(file);
    476             fw.write(content);
    477         } catch (IOException e) {
    478             AdtPlugin.log(e, null);
    479         } finally {
    480             if (fw != null) {
    481                 try {
    482                     fw.close();
    483                 } catch (IOException e) {
    484                     AdtPlugin.log(e, null);
    485                 }
    486             }
    487         }
    488     }
    489 
    490     /**
    491      * Returns true iff the given file contains the given String.
    492      *
    493      * @param file the file to look for the string in
    494      * @param string the string to be searched for
    495      * @return true if the file is found and contains the given string anywhere within it
    496      */
    497     public static boolean fileContains(IFile file, String string) {
    498         InputStream contents = null;
    499         try {
    500             contents = file.getContents();
    501             String charset = file.getCharset();
    502             return streamContains(new InputStreamReader(contents, charset), string);
    503         } catch (Exception e) {
    504             AdtPlugin.log(e, "Can't read file %1$s", file); //$NON-NLS-1$
    505         }
    506 
    507         return false;
    508     }
    509 
    510     /**
    511      * Returns true iff the given file contains the given String.
    512      *
    513      * @param file the file to look for the string in
    514      * @param string the string to be searched for
    515      * @return true if the file is found and contains the given string anywhere within it
    516      */
    517     public static boolean fileContains(File file, String string) {
    518         try {
    519             return streamContains(new FileReader(file), string);
    520         } catch (Exception e) {
    521             AdtPlugin.log(e, "Can't read file %1$s", file); //$NON-NLS-1$
    522         }
    523 
    524         return false;
    525     }
    526 
    527     /**
    528      * Returns true iff the given input stream contains the given String.
    529      *
    530      * @param r the stream to look for the string in
    531      * @param string the string to be searched for
    532      * @return true if the file is found and contains the given string anywhere within it
    533      */
    534     public static boolean streamContains(Reader r, String string) {
    535         if (string.length() == 0) {
    536             return true;
    537         }
    538 
    539         PushbackReader reader = null;
    540         try {
    541             reader = new PushbackReader(r, string.length());
    542             char first = string.charAt(0);
    543             while (true) {
    544                 int c = reader.read();
    545                 if (c == -1) {
    546                     return false;
    547                 } else if (c == first) {
    548                     boolean matches = true;
    549                     for (int i = 1; i < string.length(); i++) {
    550                         c = reader.read();
    551                         if (c == -1) {
    552                             return false;
    553                         } else if (string.charAt(i) != (char)c) {
    554                             matches = false;
    555                             // Back up the characters that did not match
    556                             reader.backup(i-1);
    557                             break;
    558                         }
    559                     }
    560                     if (matches) {
    561                         return true;
    562                     }
    563                 }
    564             }
    565         } catch (Exception e) {
    566             AdtPlugin.log(e, "Can't read stream"); //$NON-NLS-1$
    567         } finally {
    568             try {
    569                 if (reader != null) {
    570                     reader.close();
    571                 }
    572             } catch (IOException e) {
    573                 AdtPlugin.log(e, "Can't read stream"); //$NON-NLS-1$
    574             }
    575         }
    576 
    577         return false;
    578 
    579     }
    580 
    581     /**
    582      * A special reader that allows backing up in the input (up to a predefined maximum
    583      * number of characters)
    584      * <p>
    585      * NOTE: This class ONLY works with the {@link #read()} method!!
    586      */
    587     private static class PushbackReader extends BufferedReader {
    588         /**
    589          * Rolling/circular buffer. Can be a char rather than int since we never store EOF
    590          * in it.
    591          */
    592         private char[] mStorage;
    593 
    594         /** Points to the head of the queue. When equal to the tail, the queue is empty. */
    595         private int mHead;
    596 
    597         /**
    598          * Points to the tail of the queue. This will move with each read of the actual
    599          * wrapped reader, and the characters previous to it in the circular buffer are
    600          * the most recently read characters.
    601          */
    602         private int mTail;
    603 
    604         /**
    605          * Creates a new reader with a given maximum number of backup characters
    606          *
    607          * @param reader the reader to wrap
    608          * @param max the maximum number of characters to allow rollback for
    609          */
    610         public PushbackReader(Reader reader, int max) {
    611             super(reader);
    612             mStorage = new char[max + 1];
    613         }
    614 
    615         @Override
    616         public int read() throws IOException {
    617             // Have we backed up? If so we should serve characters
    618             // from the storage
    619             if (mHead != mTail) {
    620                 char c = mStorage[mHead];
    621                 mHead = (mHead + 1) % mStorage.length;
    622                 return c;
    623             }
    624             assert mHead == mTail;
    625 
    626             // No backup -- read the next character, but stash it into storage
    627             // as well such that we can retrieve it if we must.
    628             int c = super.read();
    629             mStorage[mHead] = (char) c;
    630             mHead = mTail = (mHead + 1) % mStorage.length;
    631             return c;
    632         }
    633 
    634         /**
    635          * Backs up the reader a given number of characters. The next N reads will yield
    636          * the N most recently read characters prior to this backup.
    637          *
    638          * @param n the number of characters to be backed up
    639          */
    640         public void backup(int n) {
    641             if (n >= mStorage.length) {
    642                 throw new IllegalArgumentException("Exceeded backup limit");
    643             }
    644             assert n < mStorage.length;
    645             mHead -= n;
    646             if (mHead < 0) {
    647                 mHead += mStorage.length;
    648             }
    649         }
    650     }
    651 
    652     /**
    653      * Reads the contents of a {@link ResourceFile} and returns it as a String
    654      *
    655      * @param file the file to be read
    656      * @return the contents as a String, or null if reading failed
    657      */
    658     public static String readFile(ResourceFile file) {
    659         InputStream contents = null;
    660         try {
    661             contents = file.getFile().getContents();
    662             return readFile(new InputStreamReader(contents));
    663         } catch (StreamException e) {
    664             // pass -- ignore files we can't read
    665         } finally {
    666             try {
    667                 if (contents != null) {
    668                     contents.close();
    669                 }
    670             } catch (IOException e) {
    671                 AdtPlugin.log(e, "Can't read layout file"); //$NON-NLS-1$
    672             }
    673         }
    674 
    675         return null;
    676     }
    677 
    678     /**
    679      * Reads the contents of an {@link InputStreamReader} and return it as a String
    680      *
    681      * @param inputStream the input stream to be read from
    682      * @return the String read from the stream, or null if there was an error
    683      */
    684     public static String readFile(Reader inputStream) {
    685         BufferedReader reader = null;
    686         try {
    687             reader = new BufferedReader(inputStream);
    688             StringBuilder sb = new StringBuilder(2000);
    689             while (true) {
    690                 int c = reader.read();
    691                 if (c == -1) {
    692                     return sb.toString();
    693                 } else {
    694                     sb.append((char)c);
    695                 }
    696             }
    697         } catch (IOException e) {
    698             // pass -- ignore files we can't read
    699         } finally {
    700             try {
    701                 if (reader != null) {
    702                     reader.close();
    703                 }
    704             } catch (IOException e) {
    705                 AdtPlugin.log(e, "Can't read input stream"); //$NON-NLS-1$
    706             }
    707         }
    708 
    709         return null;
    710     }
    711 
    712     /**
    713      * Reads and returns the content of a text file embedded in the plugin jar
    714      * file.
    715      * @param filepath the file path to the text file
    716      * @return null if the file could not be read
    717      */
    718     public static String readEmbeddedTextFile(String filepath) {
    719         try {
    720             InputStream is = readEmbeddedFileAsStream(filepath);
    721             if (is != null) {
    722                 BufferedReader reader = new BufferedReader(new InputStreamReader(is));
    723 
    724                 String line;
    725                 StringBuilder total = new StringBuilder(reader.readLine());
    726                 while ((line = reader.readLine()) != null) {
    727                     total.append('\n');
    728                     total.append(line);
    729                 }
    730 
    731                 return total.toString();
    732             }
    733         } catch (IOException e) {
    734             // we'll just return null
    735             AdtPlugin.log(e, "Failed to read text file '%s'", filepath);  //$NON-NLS-1$
    736         }
    737 
    738         return null;
    739     }
    740 
    741     /**
    742      * Reads and returns the content of a binary file embedded in the plugin jar
    743      * file.
    744      * @param filepath the file path to the text file
    745      * @return null if the file could not be read
    746      */
    747     public static byte[] readEmbeddedFile(String filepath) {
    748         try {
    749             InputStream is = readEmbeddedFileAsStream(filepath);
    750             if (is != null) {
    751                 // create a buffered reader to facilitate reading.
    752                 BufferedInputStream stream = new BufferedInputStream(is);
    753 
    754                 // get the size to read.
    755                 int avail = stream.available();
    756 
    757                 // create the buffer and reads it.
    758                 byte[] buffer = new byte[avail];
    759                 stream.read(buffer);
    760 
    761                 // and return.
    762                 return buffer;
    763             }
    764         } catch (IOException e) {
    765             // we'll just return null;.
    766             AdtPlugin.log(e, "Failed to read binary file '%s'", filepath);  //$NON-NLS-1$
    767         }
    768 
    769         return null;
    770     }
    771 
    772     /**
    773      * Reads and returns the content of a binary file embedded in the plugin jar
    774      * file.
    775      * @param filepath the file path to the text file
    776      * @return null if the file could not be read
    777      */
    778     public static InputStream readEmbeddedFileAsStream(String filepath) {
    779         // attempt to read an embedded file
    780         try {
    781             URL url = getEmbeddedFileUrl(AdtConstants.WS_SEP + filepath);
    782             if (url != null) {
    783                 return url.openStream();
    784             }
    785         } catch (MalformedURLException e) {
    786             // we'll just return null.
    787             AdtPlugin.log(e, "Failed to read stream '%s'", filepath);  //$NON-NLS-1$
    788         } catch (IOException e) {
    789             // we'll just return null;.
    790             AdtPlugin.log(e, "Failed to read stream '%s'", filepath);  //$NON-NLS-1$
    791         }
    792 
    793         return null;
    794     }
    795 
    796     /**
    797      * Returns the URL of a binary file embedded in the plugin jar file.
    798      * @param filepath the file path to the text file
    799      * @return null if the file was not found.
    800      */
    801     public static URL getEmbeddedFileUrl(String filepath) {
    802         Bundle bundle = null;
    803         synchronized (AdtPlugin.class) {
    804             if (sPlugin != null) {
    805                 bundle = sPlugin.getBundle();
    806             } else {
    807                 AdtPlugin.log(IStatus.WARNING, "ADT Plugin is missing");    //$NON-NLS-1$
    808                 return null;
    809             }
    810         }
    811 
    812         // attempt to get a file to one of the template.
    813         String path = filepath;
    814         if (!path.startsWith(AdtConstants.WS_SEP)) {
    815             path = AdtConstants.WS_SEP + path;
    816         }
    817 
    818         URL url = bundle.getEntry(path);
    819 
    820         if (url == null) {
    821             AdtPlugin.log(IStatus.INFO, "Bundle file URL not found at path '%s'", path); //$NON-NLS-1$
    822         }
    823 
    824         return url;
    825     }
    826 
    827     /**
    828      * Displays an error dialog box. This dialog box is ran asynchronously in the ui thread,
    829      * therefore this method can be called from any thread.
    830      * @param title The title of the dialog box
    831      * @param message The error message
    832      */
    833     public final static void displayError(final String title, final String message) {
    834         // get the current Display
    835         final Display display = getDisplay();
    836 
    837         // dialog box only run in ui thread..
    838         display.asyncExec(new Runnable() {
    839             public void run() {
    840                 Shell shell = display.getActiveShell();
    841                 MessageDialog.openError(shell, title, message);
    842             }
    843         });
    844     }
    845 
    846     /**
    847      * Displays a warning dialog box. This dialog box is ran asynchronously in the ui thread,
    848      * therefore this method can be called from any thread.
    849      * @param title The title of the dialog box
    850      * @param message The warning message
    851      */
    852     public final static void displayWarning(final String title, final String message) {
    853         // get the current Display
    854         final Display display = getDisplay();
    855 
    856         // dialog box only run in ui thread..
    857         display.asyncExec(new Runnable() {
    858             public void run() {
    859                 Shell shell = display.getActiveShell();
    860                 MessageDialog.openWarning(shell, title, message);
    861             }
    862         });
    863     }
    864 
    865     /**
    866      * Display a yes/no question dialog box. This dialog is opened synchronously in the ui thread,
    867      * therefore this message can be called from any thread.
    868      * @param title The title of the dialog box
    869      * @param message The error message
    870      * @return true if OK was clicked.
    871      */
    872     public final static boolean displayPrompt(final String title, final String message) {
    873         // get the current Display and Shell
    874         final Display display = getDisplay();
    875 
    876         // we need to ask the user what he wants to do.
    877         final boolean[] result = new boolean[1];
    878         display.syncExec(new Runnable() {
    879             public void run() {
    880                 Shell shell = display.getActiveShell();
    881                 result[0] = MessageDialog.openQuestion(shell, title, message);
    882             }
    883         });
    884         return result[0];
    885     }
    886 
    887     /**
    888      * Logs a message to the default Eclipse log.
    889      *
    890      * @param severity The severity code. Valid values are: {@link IStatus#OK},
    891      * {@link IStatus#ERROR}, {@link IStatus#INFO}, {@link IStatus#WARNING} or
    892      * {@link IStatus#CANCEL}.
    893      * @param format The format string, like for {@link String#format(String, Object...)}.
    894      * @param args The arguments for the format string, like for
    895      * {@link String#format(String, Object...)}.
    896      */
    897     public static void log(int severity, String format, Object ... args) {
    898         if (format == null) {
    899             return;
    900         }
    901 
    902         String message = String.format(format, args);
    903         Status status = new Status(severity, PLUGIN_ID, message);
    904 
    905         if (getDefault() != null) {
    906             getDefault().getLog().log(status);
    907         } else {
    908             // During UnitTests, we generally don't have a plugin object. It's ok
    909             // to log to stdout or stderr in this case.
    910             (severity < IStatus.ERROR ? System.out : System.err).println(status.toString());
    911         }
    912     }
    913 
    914     /**
    915      * Logs an exception to the default Eclipse log.
    916      * <p/>
    917      * The status severity is always set to ERROR.
    918      *
    919      * @param exception the exception to log.
    920      * @param format The format string, like for {@link String#format(String, Object...)}.
    921      * @param args The arguments for the format string, like for
    922      * {@link String#format(String, Object...)}.
    923      */
    924     public static void log(Throwable exception, String format, Object ... args) {
    925         String message = null;
    926         if (format != null) {
    927             message = String.format(format, args);
    928         } else {
    929             message = "";
    930         }
    931         Status status = new Status(IStatus.ERROR, PLUGIN_ID, message, exception);
    932 
    933         if (getDefault() != null) {
    934             getDefault().getLog().log(status);
    935         } else {
    936             // During UnitTests, we generally don't have a plugin object. It's ok
    937             // to log to stderr in this case.
    938             System.err.println(status.toString());
    939         }
    940     }
    941 
    942     /**
    943      * This is a mix between log(Throwable) and printErrorToConsole.
    944      * <p/>
    945      * This logs the exception with an ERROR severity and the given printf-like format message.
    946      * The same message is then printed on the Android error console with the associated tag.
    947      *
    948      * @param exception the exception to log.
    949      * @param format The format string, like for {@link String#format(String, Object...)}.
    950      * @param args The arguments for the format string, like for
    951      * {@link String#format(String, Object...)}.
    952      */
    953     public static synchronized void logAndPrintError(Throwable exception, String tag,
    954             String format, Object ... args) {
    955         if (sPlugin != null) {
    956             String message = String.format(format, args);
    957             Status status = new Status(IStatus.ERROR, PLUGIN_ID, message, exception);
    958             getDefault().getLog().log(status);
    959             printToStream(sPlugin.mAndroidConsoleErrorStream, tag, message);
    960             showAndroidConsole();
    961         }
    962     }
    963 
    964     /**
    965      * Prints one or more error message to the android console.
    966      * @param tag A tag to be associated with the message. Can be null.
    967      * @param objects the objects to print through their <code>toString</code> method.
    968      */
    969     public static synchronized void printErrorToConsole(String tag, Object... objects) {
    970         if (sPlugin != null) {
    971             printToStream(sPlugin.mAndroidConsoleErrorStream, tag, objects);
    972 
    973             showAndroidConsole();
    974         }
    975     }
    976 
    977     /**
    978      * Prints one or more error message to the android console.
    979      * @param objects the objects to print through their <code>toString</code> method.
    980      */
    981     public static void printErrorToConsole(Object... objects) {
    982         printErrorToConsole((String)null, objects);
    983     }
    984 
    985     /**
    986      * Prints one or more error message to the android console.
    987      * @param project The project to which the message is associated. Can be null.
    988      * @param objects the objects to print through their <code>toString</code> method.
    989      */
    990     public static void printErrorToConsole(IProject project, Object... objects) {
    991         String tag = project != null ? project.getName() : null;
    992         printErrorToConsole(tag, objects);
    993     }
    994 
    995     /**
    996      * Prints one or more build messages to the android console, filtered by Build output verbosity.
    997      * @param level {@link BuildVerbosity} level of the message.
    998      * @param project The project to which the message is associated. Can be null.
    999      * @param objects the objects to print through their <code>toString</code> method.
   1000      * @see BuildVerbosity#ALWAYS
   1001      * @see BuildVerbosity#NORMAL
   1002      * @see BuildVerbosity#VERBOSE
   1003      */
   1004     public static synchronized void printBuildToConsole(BuildVerbosity level, IProject project,
   1005             Object... objects) {
   1006         if (sPlugin != null) {
   1007             if (level.getLevel() <= AdtPrefs.getPrefs().getBuildVerbosity().getLevel()) {
   1008                 String tag = project != null ? project.getName() : null;
   1009                 printToStream(sPlugin.mAndroidConsoleStream, tag, objects);
   1010             }
   1011         }
   1012     }
   1013 
   1014     /**
   1015      * Prints one or more message to the android console.
   1016      * @param tag The tag to be associated with the message. Can be null.
   1017      * @param objects the objects to print through their <code>toString</code> method.
   1018      */
   1019     public static synchronized void printToConsole(String tag, Object... objects) {
   1020         if (sPlugin != null) {
   1021             printToStream(sPlugin.mAndroidConsoleStream, tag, objects);
   1022         }
   1023     }
   1024 
   1025     /**
   1026      * Prints one or more message to the android console.
   1027      * @param project The project to which the message is associated. Can be null.
   1028      * @param objects the objects to print through their <code>toString</code> method.
   1029      */
   1030     public static void printToConsole(IProject project, Object... objects) {
   1031         String tag = project != null ? project.getName() : null;
   1032         printToConsole(tag, objects);
   1033     }
   1034 
   1035     /** Force the display of the android console */
   1036     public static void showAndroidConsole() {
   1037         // first make sure the console is in the workbench
   1038         EclipseUiHelper.showView(IConsoleConstants.ID_CONSOLE_VIEW, true);
   1039 
   1040         // now make sure it's not docked.
   1041         ConsolePlugin.getDefault().getConsoleManager().showConsoleView(
   1042                 AdtPlugin.getDefault().getAndroidConsole());
   1043     }
   1044 
   1045     /**
   1046      * Returns whether the {@link IAndroidTarget}s have been loaded from the SDK.
   1047      */
   1048     public final LoadStatus getSdkLoadStatus() {
   1049         synchronized (Sdk.getLock()) {
   1050             return mSdkIsLoaded;
   1051         }
   1052     }
   1053 
   1054     /**
   1055      * Sets the given {@link IJavaProject} to have its target resolved again once the SDK finishes
   1056      * to load.
   1057      */
   1058     public final void setProjectToResolve(IJavaProject javaProject) {
   1059         synchronized (Sdk.getLock()) {
   1060             mPostLoadProjectsToResolve.add(javaProject);
   1061         }
   1062     }
   1063 
   1064     /**
   1065      * Sets the given {@link IJavaProject} to have its target checked for consistency
   1066      * once the SDK finishes to load. This is used if the target is resolved using cached
   1067      * information while the SDK is loading.
   1068      */
   1069     public final void setProjectToCheck(IJavaProject javaProject) {
   1070         // only lock on
   1071         synchronized (Sdk.getLock()) {
   1072             mPostLoadProjectsToCheck.add(javaProject);
   1073         }
   1074     }
   1075 
   1076     /**
   1077      * Checks the location of the SDK is valid and if it is, grab the SDK API version
   1078      * from the SDK.
   1079      * @return false if the location is not correct.
   1080      */
   1081     private boolean checkSdkLocationAndId() {
   1082         String sdkLocation = AdtPrefs.getPrefs().getOsSdkFolder();
   1083         if (sdkLocation == null || sdkLocation.length() == 0) {
   1084             return false;
   1085         }
   1086 
   1087         return checkSdkLocationAndId(sdkLocation, new CheckSdkErrorHandler() {
   1088             @Override
   1089             public boolean handleError(String message) {
   1090                 AdtPlugin.displayError("Android SDK Verification", message);
   1091                 return false;
   1092             }
   1093 
   1094             @Override
   1095             public boolean handleWarning(String message) {
   1096                 AdtPlugin.displayWarning("Android SDK Verification", message);
   1097                 return true;
   1098             }
   1099         });
   1100     }
   1101 
   1102     /**
   1103      * Internal helper to perform the actual sdk location and id check.
   1104      *
   1105      * @param osSdkLocation The sdk directory, an OS path.
   1106      * @param errorHandler An checkSdkErrorHandler that can display a warning or an error.
   1107      * @return False if there was an error or the result from the errorHandler invocation.
   1108      */
   1109     public boolean checkSdkLocationAndId(String osSdkLocation, CheckSdkErrorHandler errorHandler) {
   1110         if (osSdkLocation.endsWith(File.separator) == false) {
   1111             osSdkLocation = osSdkLocation + File.separator;
   1112         }
   1113 
   1114         File osSdkFolder = new File(osSdkLocation);
   1115         if (osSdkFolder.isDirectory() == false) {
   1116             return errorHandler.handleError(
   1117                     String.format(Messages.Could_Not_Find_Folder, osSdkLocation));
   1118         }
   1119 
   1120         String osTools = osSdkLocation + SdkConstants.OS_SDK_TOOLS_FOLDER;
   1121         File toolsFolder = new File(osTools);
   1122         if (toolsFolder.isDirectory() == false) {
   1123             return errorHandler.handleError(
   1124                     String.format(Messages.Could_Not_Find_Folder_In_SDK,
   1125                             SdkConstants.FD_TOOLS, osSdkLocation));
   1126         }
   1127 
   1128         // first check the min plug-in requirement as its error message is easier to figure
   1129         // out for the user
   1130         if (VersionCheck.checkVersion(osSdkLocation, errorHandler) == false) {
   1131             return false;
   1132         }
   1133 
   1134         // check that we have both the tools component and the platform-tools component.
   1135         String platformTools = osSdkLocation + SdkConstants.OS_SDK_PLATFORM_TOOLS_FOLDER;
   1136         if (checkFolder(platformTools) == false) {
   1137             return errorHandler.handleWarning("SDK Platform Tools component is missing!\n" +
   1138                     "Please use the SDK Manager to install it.");
   1139         }
   1140 
   1141         String tools = osSdkLocation + SdkConstants.OS_SDK_TOOLS_FOLDER;
   1142         if (checkFolder(tools) == false) {
   1143             return errorHandler.handleError("SDK Tools component is missing!\n" +
   1144                     "Please use the SDK Manager to install it.");
   1145         }
   1146 
   1147         // check the path to various tools we use to make sure nothing is missing. This is
   1148         // not meant to be exhaustive.
   1149         String[] filesToCheck = new String[] {
   1150                 osSdkLocation + getOsRelativeAdb(),
   1151                 osSdkLocation + getOsRelativeEmulator()
   1152         };
   1153         for (String file : filesToCheck) {
   1154             if (checkFile(file) == false) {
   1155                 return errorHandler.handleError(String.format(Messages.Could_Not_Find, file));
   1156             }
   1157         }
   1158 
   1159         return true;
   1160     }
   1161 
   1162     /**
   1163      * Checks if a path reference a valid existing file.
   1164      * @param osPath the os path to check.
   1165      * @return true if the file exists and is, in fact, a file.
   1166      */
   1167     private boolean checkFile(String osPath) {
   1168         File file = new File(osPath);
   1169         if (file.isFile() == false) {
   1170             return false;
   1171         }
   1172 
   1173         return true;
   1174     }
   1175 
   1176     /**
   1177      * Checks if a path reference a valid existing folder.
   1178      * @param osPath the os path to check.
   1179      * @return true if the folder exists and is, in fact, a folder.
   1180      */
   1181     private boolean checkFolder(String osPath) {
   1182         File file = new File(osPath);
   1183         if (file.isDirectory() == false) {
   1184             return false;
   1185         }
   1186 
   1187         return true;
   1188     }
   1189 
   1190     /**
   1191      * Parses the SDK resources.
   1192      */
   1193     private void parseSdkContent(long delay) {
   1194         // Perform the update in a thread (here an Eclipse runtime job)
   1195         // since this should never block the caller (especially the start method)
   1196         Job job = new Job(Messages.AdtPlugin_Android_SDK_Content_Loader) {
   1197             @SuppressWarnings("unchecked")
   1198             @Override
   1199             protected IStatus run(IProgressMonitor monitor) {
   1200                 try {
   1201 
   1202                     if (mSdkIsLoading) {
   1203                         return new Status(IStatus.WARNING, PLUGIN_ID,
   1204                                 "An Android SDK is already being loaded. Please try again later.");
   1205                     }
   1206 
   1207                     mSdkIsLoading = true;
   1208 
   1209                     SubMonitor progress = SubMonitor.convert(monitor,
   1210                             "Initialize SDK Manager", 100);
   1211 
   1212                     Sdk sdk = Sdk.loadSdk(AdtPrefs.getPrefs().getOsSdkFolder());
   1213 
   1214                     if (sdk != null) {
   1215 
   1216                         ArrayList<IJavaProject> list = new ArrayList<IJavaProject>();
   1217                         synchronized (Sdk.getLock()) {
   1218                             mSdkIsLoaded = LoadStatus.LOADED;
   1219 
   1220                             progress.setTaskName("Check Projects");
   1221 
   1222                             for (IJavaProject javaProject : mPostLoadProjectsToResolve) {
   1223                                 IProject iProject = javaProject.getProject();
   1224                                 if (iProject.isOpen()) {
   1225                                     // project that have been resolved before the sdk was loaded
   1226                                     // will have a ProjectState where the IAndroidTarget is null
   1227                                     // so we load the target now that the SDK is loaded.
   1228                                     sdk.loadTarget(Sdk.getProjectState(iProject));
   1229                                     list.add(javaProject);
   1230                                 }
   1231                             }
   1232 
   1233                             // done with this list.
   1234                             mPostLoadProjectsToResolve.clear();
   1235                         }
   1236 
   1237                         // check the projects that need checking.
   1238                         // The method modifies the list (it removes the project that
   1239                         // do not need to be resolved again).
   1240                         AndroidClasspathContainerInitializer.checkProjectsCache(
   1241                                 mPostLoadProjectsToCheck);
   1242 
   1243                         list.addAll(mPostLoadProjectsToCheck);
   1244 
   1245                         // update the project that needs recompiling.
   1246                         if (list.size() > 0) {
   1247                             IJavaProject[] array = list.toArray(
   1248                                     new IJavaProject[list.size()]);
   1249                             AndroidClasspathContainerInitializer.updateProjects(array);
   1250                         }
   1251 
   1252                         progress.worked(10);
   1253                     } else {
   1254                         // SDK failed to Load!
   1255                         // Sdk#loadSdk() has already displayed an error.
   1256                         synchronized (Sdk.getLock()) {
   1257                             mSdkIsLoaded = LoadStatus.FAILED;
   1258                         }
   1259                     }
   1260 
   1261                     // Notify resource changed listeners
   1262                     progress.setTaskName("Refresh UI");
   1263                     progress.setWorkRemaining(mTargetChangeListeners.size());
   1264 
   1265                     // Clone the list before iterating, to avoid ConcurrentModification
   1266                     // exceptions
   1267                     final List<ITargetChangeListener> listeners =
   1268                         (List<ITargetChangeListener>)mTargetChangeListeners.clone();
   1269                     final SubMonitor progress2 = progress;
   1270                     AdtPlugin.getDisplay().asyncExec(new Runnable() {
   1271                         public void run() {
   1272                             for (ITargetChangeListener listener : listeners) {
   1273                                 try {
   1274                                     listener.onSdkLoaded();
   1275                                 } catch (Exception e) {
   1276                                     AdtPlugin.log(e, "Failed to update a TargetChangeListener."); //$NON-NLS-1$
   1277                                 } finally {
   1278                                     progress2.worked(1);
   1279                                 }
   1280                             }
   1281                         }
   1282                     });
   1283                 } catch (Throwable t) {
   1284                     log(t, "Unknown exception in parseSdkContent.");    //$NON-NLS-1$
   1285                     return new Status(IStatus.ERROR, PLUGIN_ID,
   1286                             "parseSdkContent failed", t);               //$NON-NLS-1$
   1287 
   1288                 } finally {
   1289                     mSdkIsLoading = false;
   1290                     if (monitor != null) {
   1291                         monitor.done();
   1292                     }
   1293                 }
   1294 
   1295                 return Status.OK_STATUS;
   1296             }
   1297         };
   1298         job.setPriority(Job.BUILD); // build jobs are run after other interactive jobs
   1299         if (delay > 0) {
   1300             job.schedule(delay);
   1301         } else {
   1302             job.schedule();
   1303         }
   1304     }
   1305 
   1306     /** Returns the global android console */
   1307     public MessageConsole getAndroidConsole() {
   1308         return mAndroidConsole;
   1309     }
   1310 
   1311     // ----- Methods for Editors -------
   1312 
   1313     public void startEditors() {
   1314         sAndroidLogoDesc = imageDescriptorFromPlugin(AdtPlugin.PLUGIN_ID,
   1315                 "/icons/android.png"); //$NON-NLS-1$
   1316         sAndroidLogo = sAndroidLogoDesc.createImage();
   1317 
   1318         // Add a resource listener to handle compiled resources.
   1319         IWorkspace ws = ResourcesPlugin.getWorkspace();
   1320         mResourceMonitor = GlobalProjectMonitor.startMonitoring(ws);
   1321 
   1322         if (mResourceMonitor != null) {
   1323             try {
   1324                 setupDefaultEditor(mResourceMonitor);
   1325                 ResourceManager.setup(mResourceMonitor);
   1326             } catch (Throwable t) {
   1327                 log(t, "ResourceManager.setup failed"); //$NON-NLS-1$
   1328             }
   1329         }
   1330     }
   1331 
   1332     /**
   1333      * The <code>AbstractUIPlugin</code> implementation of this <code>Plugin</code>
   1334      * method saves this plug-in's preference and dialog stores and shuts down
   1335      * its image registry (if they are in use). Subclasses may extend this
   1336      * method, but must send super <b>last</b>. A try-finally statement should
   1337      * be used where necessary to ensure that <code>super.shutdown()</code> is
   1338      * always done.
   1339      *
   1340      * @see org.eclipse.ui.plugin.AbstractUIPlugin#stop(org.osgi.framework.BundleContext)
   1341      */
   1342     public void stopEditors() {
   1343         sAndroidLogo.dispose();
   1344 
   1345         IconFactory.getInstance().dispose();
   1346 
   1347         // Remove the resource listener that handles compiled resources.
   1348         IWorkspace ws = ResourcesPlugin.getWorkspace();
   1349         GlobalProjectMonitor.stopMonitoring(ws);
   1350 
   1351         mRed.dispose();
   1352     }
   1353 
   1354     /**
   1355      * Returns an Image for the small Android logo.
   1356      *
   1357      * Callers should not dispose it.
   1358      */
   1359     public static Image getAndroidLogo() {
   1360         return sAndroidLogo;
   1361     }
   1362 
   1363     /**
   1364      * Returns an {@link ImageDescriptor} for the small Android logo.
   1365      *
   1366      * Callers should not dispose it.
   1367      */
   1368     public static ImageDescriptor getAndroidLogoDesc() {
   1369         return sAndroidLogoDesc;
   1370     }
   1371 
   1372     /**
   1373      * Returns the ResourceMonitor object.
   1374      */
   1375     public GlobalProjectMonitor getResourceMonitor() {
   1376         return mResourceMonitor;
   1377     }
   1378 
   1379     /**
   1380      * Sets up the editor to register default editors for resource files when needed.
   1381      *
   1382      * This is called by the {@link AdtPlugin} during initialization.
   1383      *
   1384      * @param monitor The main Resource Monitor object.
   1385      */
   1386     public void setupDefaultEditor(GlobalProjectMonitor monitor) {
   1387         monitor.addFileListener(new IFileListener() {
   1388 
   1389             private static final String UNKNOWN_EDITOR = "unknown-editor"; //$NON-NLS-1$
   1390 
   1391             /* (non-Javadoc)
   1392              * Sent when a file changed.
   1393              * @param file The file that changed.
   1394              * @param markerDeltas The marker deltas for the file.
   1395              * @param kind The change kind. This is equivalent to
   1396              * {@link IResourceDelta#accept(IResourceDeltaVisitor)}
   1397              *
   1398              * @see IFileListener#fileChanged
   1399              */
   1400             public void fileChanged(IFile file, IMarkerDelta[] markerDeltas, int kind) {
   1401                 if (AdtConstants.EXT_XML.equals(file.getFileExtension())) {
   1402                     // The resources files must have a file path similar to
   1403                     //    project/res/.../*.xml
   1404                     // There is no support for sub folders, so the segment count must be 4
   1405                     if (DEBUG_XML_FILE_INIT) {
   1406                         AdtPlugin.log(IStatus.INFO, "fileChanged %1$s",
   1407                             file.getFullPath().toOSString());
   1408                     }
   1409 
   1410                     if (file.getFullPath().segmentCount() == 4) {
   1411                         // check if we are inside the res folder.
   1412                         String segment = file.getFullPath().segment(1);
   1413                         if (segment.equalsIgnoreCase(SdkConstants.FD_RESOURCES)) {
   1414                             // we are inside a res/ folder, get the actual ResourceFolder
   1415                             ProjectResources resources = ResourceManager.getInstance().
   1416                                 getProjectResources(file.getProject());
   1417 
   1418                             // This happens when importing old Android projects in Eclipse
   1419                             // that lack the container (probably because resources fail to build
   1420                             // properly.)
   1421                             if (resources == null) {
   1422                                 log(IStatus.INFO,
   1423                                         "getProjectResources failed for path %1$s in project %2$s", //$NON-NLS-1$
   1424                                         file.getFullPath().toOSString(),
   1425                                         file.getProject().getName());
   1426                                 return;
   1427                             }
   1428 
   1429                             ResourceFolder resFolder = resources.getResourceFolder(
   1430                                 (IFolder)file.getParent());
   1431 
   1432                             if (resFolder != null) {
   1433                                 if (kind == IResourceDelta.ADDED) {
   1434                                     resourceAdded(file, resFolder.getType());
   1435                                 } else if (kind == IResourceDelta.CHANGED) {
   1436                                     resourceChanged(file, resFolder.getType());
   1437                                 }
   1438                             } else {
   1439                                 if (DEBUG_XML_FILE_INIT) {
   1440                                     AdtPlugin.log(IStatus.INFO, "  The resource folder was null");
   1441                                 }
   1442 
   1443                                 // if the res folder is null, this means the name is invalid,
   1444                                 // in this case we remove whatever android editors that was set
   1445                                 // as the default editor.
   1446                                 IEditorDescriptor desc = IDE.getDefaultEditor(file);
   1447                                 String editorId = desc.getId();
   1448                                 if (DEBUG_XML_FILE_INIT) {
   1449                                     AdtPlugin.log(IStatus.INFO, "Old editor id=%1$s", editorId);
   1450                                 }
   1451                                 if (editorId.startsWith(AdtConstants.EDITORS_NAMESPACE)) {
   1452                                     // reset the default editor.
   1453                                     IDE.setDefaultEditor(file, null);
   1454                                     if (DEBUG_XML_FILE_INIT) {
   1455                                         AdtPlugin.log(IStatus.INFO, "  Resetting editor id to default");
   1456                                     }
   1457                                 }
   1458                             }
   1459                         } else {
   1460                             if (DEBUG_XML_FILE_INIT) {
   1461                                 AdtPlugin.log(IStatus.INFO, "    Not in resources/, ignoring");
   1462                             }
   1463                         }
   1464                     }
   1465                 }
   1466             }
   1467 
   1468             private void resourceAdded(IFile file, ResourceFolderType type) {
   1469                 if (DEBUG_XML_FILE_INIT) {
   1470                     AdtPlugin.log(IStatus.INFO, "resourceAdded %1$s - type=%1$s",
   1471                         file.getFullPath().toOSString(), type);
   1472                 }
   1473                 // set the default editor based on the type.
   1474                 if (type == ResourceFolderType.LAYOUT) {
   1475                     if (DEBUG_XML_FILE_INIT) {
   1476                         AdtPlugin.log(IStatus.INFO, "   set default editor id to layout");
   1477                     }
   1478                     IDE.setDefaultEditor(file, LayoutEditor.ID);
   1479                 } else if (type == ResourceFolderType.VALUES) {
   1480                     IDE.setDefaultEditor(file, ResourcesEditor.ID);
   1481                 } else if (type == ResourceFolderType.MENU) {
   1482                     IDE.setDefaultEditor(file, MenuEditor.ID);
   1483                 } else if (type == ResourceFolderType.COLOR) {
   1484                     IDE.setDefaultEditor(file, ColorEditor.ID);
   1485                 } else if (type == ResourceFolderType.DRAWABLE) {
   1486                     IDE.setDefaultEditor(file, DrawableEditor.ID);
   1487                 } else if (type == ResourceFolderType.ANIMATOR
   1488                         || type == ResourceFolderType.ANIM) {
   1489                     IDE.setDefaultEditor(file, AnimationEditor.ID);
   1490                 } else if (type == ResourceFolderType.XML) {
   1491                     if (XmlEditor.canHandleFile(file)) {
   1492                         if (DEBUG_XML_FILE_INIT) {
   1493                             AdtPlugin.log(IStatus.INFO, "   set default editor id to XmlEditor.id");
   1494                         }
   1495                         IDE.setDefaultEditor(file, XmlEditor.ID);
   1496                     } else {
   1497                         if (DEBUG_XML_FILE_INIT) {
   1498                             AdtPlugin.log(IStatus.INFO, "   set default editor id unknown");
   1499                         }
   1500                         // set a property to determine later if the XML can be handled
   1501                         QualifiedName qname = new QualifiedName(
   1502                                 AdtPlugin.PLUGIN_ID,
   1503                                 UNKNOWN_EDITOR);
   1504                         setFileProperty(file, qname, "1"); //$NON-NLS-1$
   1505                     }
   1506                 }
   1507             }
   1508 
   1509             private void resourceChanged(IFile file, ResourceFolderType type) {
   1510                 if (DEBUG_XML_FILE_INIT) {
   1511                     AdtPlugin.log(IStatus.INFO, "resourceChanged %1$s - type=%1$s",
   1512                         file.getFullPath().toOSString(), type);
   1513                 }
   1514                 if (type == ResourceFolderType.XML) {
   1515                     IEditorDescriptor ed = IDE.getDefaultEditor(file);
   1516                     if (ed == null || !ed.getId().equals(XmlEditor.ID)) {
   1517                         QualifiedName qname = new QualifiedName(
   1518                                 AdtPlugin.PLUGIN_ID,
   1519                                 UNKNOWN_EDITOR);
   1520                         String prop = getFileProperty(file, qname);
   1521                         if (prop != null && XmlEditor.canHandleFile(file)) {
   1522                             try {
   1523                                 // remove the property & set editor
   1524                                 setFileProperty(file, qname, null);
   1525 
   1526                                 // the window can be null sometimes
   1527                                 IWorkbench wb = PlatformUI.getWorkbench();
   1528                                 IWorkbenchWindow win = wb == null ? null :
   1529                                                        wb.getActiveWorkbenchWindow();
   1530                                 IWorkbenchPage page = win == null ? null :
   1531                                                       win.getActivePage();
   1532 
   1533                                 IEditorPart oldEditor = page == null ? null :
   1534                                                         page.findEditor(new FileEditorInput(file));
   1535                                 if (page != null &&
   1536                                         oldEditor != null &&
   1537                                         AdtPlugin.displayPrompt("Android XML Editor",
   1538                                             String.format("The file you just saved as been recognized as a file that could be better handled using the Android XML Editor. Do you want to edit '%1$s' using the Android XML editor instead?",
   1539                                                     file.getFullPath()))) {
   1540                                     IDE.setDefaultEditor(file, XmlEditor.ID);
   1541                                     IEditorPart newEditor = page.openEditor(
   1542                                             new FileEditorInput(file),
   1543                                             XmlEditor.ID,
   1544                                             true, /* activate */
   1545                                             IWorkbenchPage.MATCH_NONE);
   1546 
   1547                                     if (newEditor != null) {
   1548                                         page.closeEditor(oldEditor, true /* save */);
   1549                                     }
   1550                                 }
   1551                             } catch (CoreException e) {
   1552                                 // page.openEditor may have failed
   1553                             }
   1554                         }
   1555                     }
   1556                 }
   1557             }
   1558 
   1559         }, IResourceDelta.ADDED | IResourceDelta.CHANGED);
   1560     }
   1561 
   1562     /**
   1563      * Adds a new {@link ITargetChangeListener} to be notified when a new SDK is loaded, or when
   1564      * a project has its target changed.
   1565      */
   1566     public void addTargetListener(ITargetChangeListener listener) {
   1567         mTargetChangeListeners.add(listener);
   1568     }
   1569 
   1570     /**
   1571      * Removes an existing {@link ITargetChangeListener}.
   1572      * @see #addTargetListener(ITargetChangeListener)
   1573      */
   1574     public void removeTargetListener(ITargetChangeListener listener) {
   1575         mTargetChangeListeners.remove(listener);
   1576     }
   1577 
   1578     /**
   1579      * Updates all the {@link ITargetChangeListener}s that a target has changed for a given project.
   1580      * <p/>Only editors related to that project should reload.
   1581      */
   1582     @SuppressWarnings("unchecked")
   1583     public void updateTargetListeners(final IProject project) {
   1584         final List<ITargetChangeListener> listeners =
   1585             (List<ITargetChangeListener>)mTargetChangeListeners.clone();
   1586 
   1587         AdtPlugin.getDisplay().asyncExec(new Runnable() {
   1588             public void run() {
   1589                 for (ITargetChangeListener listener : listeners) {
   1590                     try {
   1591                         listener.onProjectTargetChange(project);
   1592                     } catch (Exception e) {
   1593                         AdtPlugin.log(e, "Failed to update a TargetChangeListener.");  //$NON-NLS-1$
   1594                     }
   1595                 }
   1596             }
   1597         });
   1598     }
   1599 
   1600     /**
   1601      * Updates all the {@link ITargetChangeListener}s that a target data was loaded.
   1602      * <p/>Only editors related to a project using this target should reload.
   1603      */
   1604     @SuppressWarnings("unchecked")
   1605     public void updateTargetListeners(final IAndroidTarget target) {
   1606         final List<ITargetChangeListener> listeners =
   1607             (List<ITargetChangeListener>)mTargetChangeListeners.clone();
   1608 
   1609         AdtPlugin.getDisplay().asyncExec(new Runnable() {
   1610             public void run() {
   1611                 for (ITargetChangeListener listener : listeners) {
   1612                     try {
   1613                         listener.onTargetLoaded(target);
   1614                     } catch (Exception e) {
   1615                         AdtPlugin.log(e, "Failed to update a TargetChangeListener.");  //$NON-NLS-1$
   1616                     }
   1617                 }
   1618             }
   1619         });
   1620     }
   1621 
   1622     public static synchronized OutputStream getOutStream() {
   1623         return sPlugin.mAndroidConsoleStream;
   1624     }
   1625 
   1626     public static synchronized OutputStream getErrorStream() {
   1627         return sPlugin.mAndroidConsoleErrorStream;
   1628     }
   1629 
   1630     /**
   1631      * Sets the named persistent property for the given file to the given value
   1632      *
   1633      * @param file the file to associate the property with
   1634      * @param qname the name of the property
   1635      * @param value the new value, or null to clear the property
   1636      */
   1637     public static void setFileProperty(IFile file, QualifiedName qname, String value) {
   1638         try {
   1639             file.setPersistentProperty(qname, value);
   1640         } catch (CoreException e) {
   1641             log(e, "Cannot set property %1$s to %2$s", qname, value);
   1642         }
   1643     }
   1644 
   1645     /**
   1646      * Gets the named persistent file property from the given file
   1647      *
   1648      * @param file the file to look up properties for
   1649      * @param qname the name of the property to look up
   1650      * @return the property value, or null
   1651      */
   1652     public static String getFileProperty(IFile file, QualifiedName qname) {
   1653         try {
   1654             return file.getPersistentProperty(qname);
   1655         } catch (CoreException e) {
   1656             log(e, "Cannot get property %1$s", qname);
   1657         }
   1658 
   1659         return null;
   1660     }
   1661 
   1662     /**
   1663      * Reparses the content of the SDK and updates opened projects.
   1664      */
   1665     public void reparseSdk() {
   1666         // add all the opened Android projects to the list of projects to be updated
   1667         // after the SDK is reloaded
   1668         synchronized (Sdk.getLock()) {
   1669             // get the project to refresh.
   1670             IJavaProject[] androidProjects = BaseProjectHelper.getAndroidProjects(null /*filter*/);
   1671             mPostLoadProjectsToResolve.addAll(Arrays.asList(androidProjects));
   1672         }
   1673 
   1674         // parse the SDK resources at the new location
   1675         parseSdkContent(0 /*immediately*/);
   1676     }
   1677 
   1678     /**
   1679      * Prints messages, associated with a project to the specified stream
   1680      * @param stream The stream to write to
   1681      * @param tag The tag associated to the message. Can be null
   1682      * @param objects The objects to print through their toString() method (or directly for
   1683      * {@link String} objects.
   1684      */
   1685     public static synchronized void printToStream(MessageConsoleStream stream, String tag,
   1686             Object... objects) {
   1687         String dateTag = AndroidPrintStream.getMessageTag(tag);
   1688 
   1689         for (Object obj : objects) {
   1690             stream.print(dateTag);
   1691             stream.print(" "); //$NON-NLS-1$
   1692             if (obj instanceof String) {
   1693                 stream.println((String)obj);
   1694             } else if (obj == null) {
   1695                 stream.println("(null)");  //$NON-NLS-1$
   1696             } else {
   1697                 stream.println(obj.toString());
   1698             }
   1699         }
   1700     }
   1701 
   1702     // --------- ILogger methods -----------
   1703 
   1704     public void error(Throwable t, String format, Object... args) {
   1705         if (t != null) {
   1706             log(t, format, args);
   1707         } else {
   1708             log(IStatus.ERROR, format, args);
   1709         }
   1710     }
   1711 
   1712     public void printf(String format, Object... args) {
   1713         log(IStatus.INFO, format, args);
   1714     }
   1715 
   1716     public void warning(String format, Object... args) {
   1717         log(IStatus.WARNING, format, args);
   1718     }
   1719 
   1720     /**
   1721      * Opens the given URL in a browser tab
   1722      *
   1723      * @param url the URL to open in a browser
   1724      */
   1725     public static void openUrl(URL url) {
   1726         IWorkbenchBrowserSupport support = PlatformUI.getWorkbench().getBrowserSupport();
   1727         IWebBrowser browser;
   1728         try {
   1729             browser = support.createBrowser(PLUGIN_ID);
   1730             browser.openURL(url);
   1731         } catch (PartInitException e) {
   1732             log(e, null);
   1733         }
   1734     }
   1735 
   1736     /**
   1737      * Opens a Java class for the given fully qualified class name
   1738      *
   1739      * @param project the project containing the class
   1740      * @param fqcn the fully qualified class name of the class to be opened
   1741      * @return true if the class was opened, false otherwise
   1742      */
   1743     public static boolean openJavaClass(IProject project, String fqcn) {
   1744         if (fqcn == null) {
   1745             return false;
   1746         }
   1747 
   1748         // Handle inner classes
   1749         if (fqcn.indexOf('$') != -1) {
   1750             fqcn = fqcn.replaceAll("\\$", "."); //$NON-NLS-1$ //$NON-NLS-2$
   1751         }
   1752 
   1753         try {
   1754             if (project.hasNature(JavaCore.NATURE_ID)) {
   1755                 IJavaProject javaProject = JavaCore.create(project);
   1756                 IJavaElement result = javaProject.findType(fqcn);
   1757                 if (result != null) {
   1758                     return JavaUI.openInEditor(result) != null;
   1759                 }
   1760             }
   1761         } catch (Throwable e) {
   1762             log(e, "Can't open class %1$s", fqcn); //$NON-NLS-1$
   1763         }
   1764 
   1765         return false;
   1766     }
   1767 
   1768     /**
   1769      * Opens the given file and shows the given (optional) region in the editor (or
   1770      * if no region is specified, opens the editor tab.)
   1771      *
   1772      * @param file the file to be opened
   1773      * @param region an optional region which if set will be selected and shown to the
   1774      *            user
   1775      * @throws PartInitException if something goes wrong
   1776      */
   1777     public static void openFile(IFile file, IRegion region) throws PartInitException {
   1778         openFile(file, region, true);
   1779     }
   1780 
   1781     /**
   1782      * Opens the given file and shows the given (optional) region
   1783      *
   1784      * @param file the file to be opened
   1785      * @param region an optional region which if set will be selected and shown to the
   1786      *            user
   1787      * @param showEditorTab if true, front the editor tab after opening the file
   1788      * @return the editor that was opened, or null if no editor was opened
   1789      * @throws PartInitException if something goes wrong
   1790      */
   1791     public static IEditorPart openFile(IFile file, IRegion region, boolean showEditorTab)
   1792             throws PartInitException {
   1793         IWorkbench workbench = PlatformUI.getWorkbench();
   1794         if (workbench == null) {
   1795             return null;
   1796         }
   1797         IWorkbenchWindow activeWorkbenchWindow = workbench.getActiveWorkbenchWindow();
   1798         if (activeWorkbenchWindow == null) {
   1799             return null;
   1800         }
   1801         IWorkbenchPage page = activeWorkbenchWindow.getActivePage();
   1802         if (page == null) {
   1803             return null;
   1804         }
   1805         IEditorPart targetEditor = IDE.openEditor(page, file, true);
   1806         if (targetEditor instanceof AndroidXmlEditor) {
   1807             AndroidXmlEditor editor = (AndroidXmlEditor) targetEditor;
   1808             if (region != null) {
   1809                 editor.show(region.getOffset(), region.getLength(), showEditorTab);
   1810             } else if (showEditorTab) {
   1811                 editor.setActivePage(AndroidXmlEditor.TEXT_EDITOR_ID);
   1812             }
   1813         }
   1814 
   1815         return targetEditor;
   1816     }
   1817 }
   1818