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