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