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