• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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          */
handleError(Solution solution, String message)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          */
handleWarning(Solution solution, String message)195         public abstract boolean handleWarning(Solution solution, String message);
196     }
197 
198     /**
199      * The constructor
200      */
AdtPlugin()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
start(BundleContext context)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
stop(BundleContext context)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      */
getDefault()316     public static synchronized AdtPlugin getDefault() {
317         return sPlugin;
318     }
319 
getDisplay()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 */
getOsRelativeAdb()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 */
getOsRelativeZipAlign()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 */
getOsRelativeEmulator()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 */
getOsRelativeProguard()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 */
getOsAbsoluteAdb()353     public static String getOsAbsoluteAdb() {
354         return getOsSdkFolder() + getOsRelativeAdb();
355     }
356 
357     /** Returns the absolute zipalign path */
getOsAbsoluteZipAlign()358     public static String getOsAbsoluteZipAlign() {
359         return getOsSdkFolder() + getOsRelativeZipAlign();
360     }
361 
362     /** Returns the absolute traceview path */
getOsAbsoluteTraceview()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 */
getOsAbsoluteEmulator()369     public static String getOsAbsoluteEmulator() {
370         return getOsSdkFolder() + getOsRelativeEmulator();
371     }
372 
getOsAbsoluteHprofConv()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 */
getOsAbsoluteProguard()379     public static String getOsAbsoluteProguard() {
380         return getOsSdkFolder() + getOsRelativeProguard();
381     }
382 
383     /**
384      * Returns a Url file path to the javaDoc folder.
385      */
getUrlDoc()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      */
getOsSdkFolder()395     public static synchronized String getOsSdkFolder() {
396         if (sPlugin == null) {
397             return null;
398         }
399 
400         return AdtPrefs.getPrefs().getOsSdkFolder();
401     }
402 
getOsSdkToolsFolder()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      */
getImageDescriptor(String path)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      */
readFile(IFile file)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      */
readFile(File file)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      */
writeFile(File file, String content)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      */
fileContains(IFile file, String string)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      */
fileContains(File file, String string)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      */
streamContains(Reader r, String string)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          */
PushbackReader(Reader reader, int max)618         public PushbackReader(Reader reader, int max) {
619             super(reader);
620             mStorage = new char[max + 1];
621         }
622 
623         @Override
read()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          */
backup(int n)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