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