• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2007 The Android Open Source Project
3  *
4  * Licensed under the Eclipse Public License, Version 1.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.eclipse.org/org/documents/epl-v10.php
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.ide.eclipse.adt;
18 
19 import com.android.ddmuilib.StackTracePanel;
20 import com.android.ddmuilib.StackTracePanel.ISourceRevealer;
21 import com.android.ddmuilib.console.DdmConsole;
22 import com.android.ddmuilib.console.IDdmConsole;
23 import com.android.ide.eclipse.adt.internal.VersionCheck;
24 import com.android.ide.eclipse.adt.internal.editors.IconFactory;
25 import com.android.ide.eclipse.adt.internal.editors.layout.LayoutEditor;
26 import com.android.ide.eclipse.adt.internal.editors.menu.MenuEditor;
27 import com.android.ide.eclipse.adt.internal.editors.resources.ResourcesEditor;
28 import com.android.ide.eclipse.adt.internal.editors.xml.XmlEditor;
29 import com.android.ide.eclipse.adt.internal.launch.AndroidLaunchController;
30 import com.android.ide.eclipse.adt.internal.preferences.BuildPreferencePage;
31 import com.android.ide.eclipse.adt.internal.project.AndroidClasspathContainerInitializer;
32 import com.android.ide.eclipse.adt.internal.project.BaseProjectHelper;
33 import com.android.ide.eclipse.adt.internal.project.ExportHelper;
34 import com.android.ide.eclipse.adt.internal.project.ProjectHelper;
35 import com.android.ide.eclipse.adt.internal.project.ExportHelper.IExportCallback;
36 import com.android.ide.eclipse.adt.internal.resources.manager.ProjectResources;
37 import com.android.ide.eclipse.adt.internal.resources.manager.ResourceFolder;
38 import com.android.ide.eclipse.adt.internal.resources.manager.ResourceFolderType;
39 import com.android.ide.eclipse.adt.internal.resources.manager.ResourceManager;
40 import com.android.ide.eclipse.adt.internal.resources.manager.ResourceMonitor;
41 import com.android.ide.eclipse.adt.internal.resources.manager.ResourceMonitor.IFileListener;
42 import com.android.ide.eclipse.adt.internal.sdk.AndroidTargetParser;
43 import com.android.ide.eclipse.adt.internal.sdk.LoadStatus;
44 import com.android.ide.eclipse.adt.internal.sdk.Sdk;
45 import com.android.ide.eclipse.adt.internal.sdk.Sdk.ITargetChangeListener;
46 import com.android.ide.eclipse.adt.internal.ui.EclipseUiHelper;
47 import com.android.ide.eclipse.adt.internal.wizards.export.ExportWizard;
48 import com.android.ide.eclipse.ddms.DdmsPlugin;
49 import com.android.ide.eclipse.ddms.ImageLoader;
50 import com.android.sdklib.IAndroidTarget;
51 import com.android.sdklib.SdkConstants;
52 import com.android.sdkstats.SdkStatsService;
53 
54 import org.eclipse.core.resources.IFile;
55 import org.eclipse.core.resources.IFolder;
56 import org.eclipse.core.resources.IMarkerDelta;
57 import org.eclipse.core.resources.IProject;
58 import org.eclipse.core.resources.IResourceDelta;
59 import org.eclipse.core.resources.IWorkspace;
60 import org.eclipse.core.resources.ResourcesPlugin;
61 import org.eclipse.core.runtime.CoreException;
62 import org.eclipse.core.runtime.IProgressMonitor;
63 import org.eclipse.core.runtime.IStatus;
64 import org.eclipse.core.runtime.Preferences;
65 import org.eclipse.core.runtime.QualifiedName;
66 import org.eclipse.core.runtime.Status;
67 import org.eclipse.core.runtime.SubMonitor;
68 import org.eclipse.core.runtime.Preferences.IPropertyChangeListener;
69 import org.eclipse.core.runtime.Preferences.PropertyChangeEvent;
70 import org.eclipse.core.runtime.jobs.IJobChangeEvent;
71 import org.eclipse.core.runtime.jobs.Job;
72 import org.eclipse.core.runtime.jobs.JobChangeAdapter;
73 import org.eclipse.jdt.core.IJavaProject;
74 import org.eclipse.jface.dialogs.MessageDialog;
75 import org.eclipse.jface.preference.IPreferenceStore;
76 import org.eclipse.jface.resource.ImageDescriptor;
77 import org.eclipse.jface.viewers.StructuredSelection;
78 import org.eclipse.jface.wizard.WizardDialog;
79 import org.eclipse.swt.graphics.Color;
80 import org.eclipse.swt.graphics.Image;
81 import org.eclipse.swt.widgets.Display;
82 import org.eclipse.swt.widgets.Shell;
83 import org.eclipse.ui.IEditorDescriptor;
84 import org.eclipse.ui.IEditorPart;
85 import org.eclipse.ui.IWorkbench;
86 import org.eclipse.ui.IWorkbenchPage;
87 import org.eclipse.ui.PlatformUI;
88 import org.eclipse.ui.console.ConsolePlugin;
89 import org.eclipse.ui.console.IConsole;
90 import org.eclipse.ui.console.IConsoleConstants;
91 import org.eclipse.ui.console.MessageConsole;
92 import org.eclipse.ui.console.MessageConsoleStream;
93 import org.eclipse.ui.ide.IDE;
94 import org.eclipse.ui.part.FileEditorInput;
95 import org.eclipse.ui.plugin.AbstractUIPlugin;
96 import org.osgi.framework.Bundle;
97 import org.osgi.framework.BundleContext;
98 import org.osgi.framework.Constants;
99 import org.osgi.framework.Version;
100 
101 import java.io.BufferedInputStream;
102 import java.io.BufferedReader;
103 import java.io.File;
104 import java.io.IOException;
105 import java.io.InputStreamReader;
106 import java.io.OutputStream;
107 import java.io.PrintStream;
108 import java.net.MalformedURLException;
109 import java.net.URL;
110 import java.util.ArrayList;
111 import java.util.Arrays;
112 import java.util.Calendar;
113 import java.util.List;
114 
115 /**
116  * The activator class controls the plug-in life cycle
117  */
118 public class AdtPlugin extends AbstractUIPlugin {
119     /** The plug-in ID */
120     public static final String PLUGIN_ID = "com.android.ide.eclipse.adt"; //$NON-NLS-1$
121 
122     public final static String PREFS_SDK_DIR = PLUGIN_ID + ".sdk"; //$NON-NLS-1$
123 
124     public final static String PREFS_RES_AUTO_REFRESH = PLUGIN_ID + ".resAutoRefresh"; //$NON-NLS-1$
125 
126     public final static String PREFS_BUILD_VERBOSITY = PLUGIN_ID + ".buildVerbosity"; //$NON-NLS-1$
127 
128     public final static String PREFS_DEFAULT_DEBUG_KEYSTORE = PLUGIN_ID + ".defaultDebugKeyStore"; //$NON-NLS-1$
129 
130     public final static String PREFS_CUSTOM_DEBUG_KEYSTORE = PLUGIN_ID + ".customDebugKeyStore"; //$NON-NLS-1$
131 
132     public final static String PREFS_HOME_PACKAGE = PLUGIN_ID + ".homePackage"; //$NON-NLS-1$
133 
134     public final static String PREFS_EMU_OPTIONS = PLUGIN_ID + ".emuOptions"; //$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     /** default store, provided by eclipse */
143     private IPreferenceStore mStore;
144 
145     /** cached location for the sdk folder */
146     private String mOsSdkLocation;
147 
148     /** The global android console */
149     private MessageConsole mAndroidConsole;
150 
151     /** Stream to write in the android console */
152     private MessageConsoleStream mAndroidConsoleStream;
153 
154     /** Stream to write error messages to the android console */
155     private MessageConsoleStream mAndroidConsoleErrorStream;
156 
157     /** Image loader object */
158     private ImageLoader mLoader;
159 
160     /** Verbosity of the build */
161     private int mBuildVerbosity = AdtConstants.BUILD_NORMAL;
162 
163     /** Color used in the error console */
164     private Color mRed;
165 
166     /** Load status of the SDK. Any access MUST be in a synchronized(mPostLoadProjects) block */
167     private LoadStatus mSdkIsLoaded = LoadStatus.LOADING;
168     /** Project to update once the SDK is loaded.
169      * Any access MUST be in a synchronized(mPostLoadProjectsToResolve) block */
170     private final ArrayList<IJavaProject> mPostLoadProjectsToResolve =
171             new ArrayList<IJavaProject>();
172     /** Project to check validity of cache vs actual once the SDK is loaded.
173      * Any access MUST be in a synchronized(mPostLoadProjectsToResolve) block */
174     private final ArrayList<IJavaProject> mPostLoadProjectsToCheck = new ArrayList<IJavaProject>();
175 
176     private ResourceMonitor mResourceMonitor;
177     private ArrayList<ITargetChangeListener> mTargetChangeListeners =
178             new ArrayList<ITargetChangeListener>();
179 
180     protected boolean mSdkIsLoading;
181 
182     /**
183      * Custom PrintStream for Dx output. This class overrides the method
184      * <code>println()</code> and adds the standard output tag with the
185      * date and the project name in front of every messages.
186      */
187     private static final class AndroidPrintStream extends PrintStream {
188         private IProject mProject;
189         private String mPrefix;
190 
191         /**
192          * Default constructor with project and output stream.
193          * The project is used to get the project name for the output tag.
194          *
195          * @param project The Project
196          * @param prefix A prefix to be printed before the actual message. Can be null
197          * @param stream The Stream
198          */
AndroidPrintStream(IProject project, String prefix, OutputStream stream)199         public AndroidPrintStream(IProject project, String prefix, OutputStream stream) {
200             super(stream);
201             mProject = project;
202         }
203 
204         @Override
println(String message)205         public void println(String message) {
206             // write the date/project tag first.
207             String tag = getMessageTag(mProject != null ? mProject.getName() : null);
208 
209             print(tag);
210             if (mPrefix != null) {
211                 print(mPrefix);
212             }
213 
214             // then write the regular message
215             super.println(message);
216         }
217     }
218 
219     /**
220      * An error handler for checkSdkLocationAndId() that will handle the generated error
221      * or warning message. Each method must return a boolean that will in turn be returned by
222      * checkSdkLocationAndId.
223      */
224     public static abstract class CheckSdkErrorHandler {
225         /** Handle an error message during sdk location check. Returns whatever
226          * checkSdkLocationAndId() should returns.
227          */
handleError(String message)228         public abstract boolean handleError(String message);
229 
230         /** Handle a warning message during sdk location check. Returns whatever
231          * checkSdkLocationAndId() should returns.
232          */
handleWarning(String message)233         public abstract boolean handleWarning(String message);
234     }
235 
236     /**
237      * The constructor
238      */
AdtPlugin()239     public AdtPlugin() {
240         sPlugin = this;
241     }
242 
243     /*
244      * (non-Javadoc)
245      * @see org.eclipse.ui.plugin.AbstractUIPlugin#start(org.osgi.framework.BundleContext)
246      */
247     @SuppressWarnings("deprecation")
248     @Override
start(BundleContext context)249     public void start(BundleContext context) throws Exception {
250         super.start(context);
251 
252         Display display = getDisplay();
253 
254         // set the default android console.
255         mAndroidConsole = new MessageConsole("Android", null); //$NON-NLS-1$
256         ConsolePlugin.getDefault().getConsoleManager().addConsoles(
257                 new IConsole[] { mAndroidConsole });
258 
259         // get the stream to write in the android console.
260         mAndroidConsoleStream = mAndroidConsole.newMessageStream();
261         mAndroidConsoleErrorStream = mAndroidConsole.newMessageStream();
262         mRed = new Color(display, 0xFF, 0x00, 0x00);
263 
264         // because this can be run, in some cases, by a non ui thread, and beccause
265         // changing the console properties update the ui, we need to make this change
266         // in the ui thread.
267         display.asyncExec(new Runnable() {
268             public void run() {
269                 mAndroidConsoleErrorStream.setColor(mRed);
270             }
271         });
272 
273         // set up the ddms console to use this objects
274         DdmConsole.setConsole(new IDdmConsole() {
275             public void printErrorToConsole(String message) {
276                 AdtPlugin.printErrorToConsole((String)null, message);
277             }
278             public void printErrorToConsole(String[] messages) {
279                 AdtPlugin.printErrorToConsole((String)null, (Object[])messages);
280             }
281             public void printToConsole(String message) {
282                 AdtPlugin.printToConsole((String)null, message);
283             }
284             public void printToConsole(String[] messages) {
285                 AdtPlugin.printToConsole((String)null, (Object[])messages);
286             }
287         });
288 
289         // get the eclipse store
290         mStore = getPreferenceStore();
291 
292         // set the listener for the preference change
293         Preferences prefs = getPluginPreferences();
294         prefs.addPropertyChangeListener(new IPropertyChangeListener() {
295             public void propertyChange(PropertyChangeEvent event) {
296                 // get the name of the property that changed.
297                 String property = event.getProperty();
298 
299                 // if the SDK changed, we update the cached version
300                 if (PREFS_SDK_DIR.equals(property)) {
301 
302                     // get the new one from the preferences
303                     mOsSdkLocation = (String)event.getNewValue();
304 
305                     // make sure it ends with a separator
306                     if (mOsSdkLocation.endsWith(File.separator) == false) {
307                         mOsSdkLocation = mOsSdkLocation + File.separator;
308                     }
309 
310                     // finally restart adb, in case it's a different version
311                     DdmsPlugin.setAdb(getOsAbsoluteAdb(), true /* startAdb */);
312 
313                     // get the SDK location and build id.
314                     if (checkSdkLocationAndId()) {
315                         // if sdk if valid, reparse it
316 
317                         reparseSdk();
318                     }
319                 } else if (PREFS_BUILD_VERBOSITY.equals(property)) {
320                     mBuildVerbosity = BuildPreferencePage.getBuildLevel(
321                             mStore.getString(PREFS_BUILD_VERBOSITY));
322                 }
323             }
324         });
325 
326         mOsSdkLocation = mStore.getString(PREFS_SDK_DIR);
327 
328         // make sure it ends with a separator. Normally this is done when the preference
329         // is set. But to make sure older version still work, we fix it here as well.
330         if (mOsSdkLocation.length() > 0 && mOsSdkLocation.endsWith(File.separator) == false) {
331             mOsSdkLocation = mOsSdkLocation + File.separator;
332         }
333 
334         // check the location of SDK
335         final boolean isSdkLocationValid = checkSdkLocationAndId();
336 
337         mBuildVerbosity = BuildPreferencePage.getBuildLevel(
338                 mStore.getString(PREFS_BUILD_VERBOSITY));
339 
340         // create the loader that's able to load the images
341         mLoader = new ImageLoader(this);
342 
343         // start the DdmsPlugin by setting the adb location, only if it is set already.
344         if (mOsSdkLocation.length() > 0) {
345             DdmsPlugin.setAdb(getOsAbsoluteAdb(), true);
346         }
347 
348         // and give it the debug launcher for android projects
349         DdmsPlugin.setRunningAppDebugLauncher(new DdmsPlugin.IDebugLauncher() {
350             public boolean debug(String appName, int port) {
351                 // search for an android project matching the process name
352                 IProject project = ProjectHelper.findAndroidProjectByAppName(appName);
353                 if (project != null) {
354                     AndroidLaunchController.debugRunningApp(project, port);
355                     return true;
356                 } else {
357                     return false;
358                 }
359             }
360         });
361 
362         StackTracePanel.setSourceRevealer(new ISourceRevealer() {
363             public void reveal(String applicationName, String className, int line) {
364                 IProject project = ProjectHelper.findAndroidProjectByAppName(applicationName);
365                 if (project != null) {
366                     BaseProjectHelper.revealSource(project, className, line);
367                 }
368             }
369         });
370 
371         // setup export callback for editors
372         ExportHelper.setCallback(new IExportCallback() {
373             public void startExportWizard(IProject project) {
374                 StructuredSelection selection = new StructuredSelection(project);
375 
376                 ExportWizard wizard = new ExportWizard();
377                 wizard.init(PlatformUI.getWorkbench(), selection);
378                 WizardDialog dialog = new WizardDialog(getDisplay().getActiveShell(),
379                         wizard);
380                 dialog.open();
381             }
382         });
383 
384         // initialize editors
385         startEditors();
386 
387         // Ping the usage server and parse the SDK content.
388         // This is deferred in separate jobs to avoid blocking the bundle start.
389         // We also serialize them to avoid too many parallel jobs when Eclipse starts.
390         Job pingJob = createPingUsageServerJob();
391         pingJob.addJobChangeListener(new JobChangeAdapter() {
392            @Override
393             public void done(IJobChangeEvent event) {
394                 super.done(event);
395 
396                 // Once the ping job is finished, start the SDK parser
397                 if (isSdkLocationValid) {
398                     // parse the SDK resources.
399                     parseSdkContent();
400                 }
401             }
402         });
403         // build jobs are run after other interactive jobs
404         pingJob.setPriority(Job.BUILD);
405         // Wait 2 seconds before starting the ping job. This leaves some time to the
406         // other bundles to initialize.
407         pingJob.schedule(2000 /*milliseconds*/);
408     }
409 
410     /*
411      * (non-Javadoc)
412      *
413      * @see org.eclipse.ui.plugin.AbstractUIPlugin#stop(org.osgi.framework.BundleContext)
414      */
415     @Override
stop(BundleContext context)416     public void stop(BundleContext context) throws Exception {
417         super.stop(context);
418 
419         stopEditors();
420 
421         mRed.dispose();
422         synchronized (AdtPlugin.class) {
423             sPlugin = null;
424         }
425     }
426 
427     /** Return the image loader for the plugin */
getImageLoader()428     public static synchronized ImageLoader getImageLoader() {
429         if (sPlugin != null) {
430             return sPlugin.mLoader;
431         }
432         return null;
433     }
434 
435     /**
436      * Returns the shared instance
437      *
438      * @return the shared instance
439      */
getDefault()440     public static synchronized AdtPlugin getDefault() {
441         return sPlugin;
442     }
443 
getDisplay()444     public static Display getDisplay() {
445         IWorkbench bench = null;
446         synchronized (AdtPlugin.class) {
447             bench = sPlugin.getWorkbench();
448         }
449 
450         if (bench != null) {
451             return bench.getDisplay();
452         }
453         return null;
454     }
455 
456     /** Returns the adb path relative to the sdk folder */
getOsRelativeAdb()457     public static String getOsRelativeAdb() {
458         return SdkConstants.OS_SDK_TOOLS_FOLDER + SdkConstants.FN_ADB;
459     }
460 
461     /** Returns the zipalign path relative to the sdk folder */
getOsRelativeZipAlign()462     public static String getOsRelativeZipAlign() {
463         return SdkConstants.OS_SDK_TOOLS_FOLDER + SdkConstants.FN_ZIPALIGN;
464     }
465 
466     /** Returns the emulator path relative to the sdk folder */
getOsRelativeEmulator()467     public static String getOsRelativeEmulator() {
468         return SdkConstants.OS_SDK_TOOLS_FOLDER + SdkConstants.FN_EMULATOR;
469     }
470 
471     /** Returns the absolute adb path */
getOsAbsoluteAdb()472     public static String getOsAbsoluteAdb() {
473         return getOsSdkFolder() + getOsRelativeAdb();
474     }
475 
476     /** Returns the absolute zipalign path */
getOsAbsoluteZipAlign()477     public static String getOsAbsoluteZipAlign() {
478         return getOsSdkFolder() + getOsRelativeZipAlign();
479     }
480 
481     /** Returns the absolute traceview path */
getOsAbsoluteTraceview()482     public static String getOsAbsoluteTraceview() {
483         return getOsSdkFolder() + SdkConstants.OS_SDK_TOOLS_FOLDER +
484                 AndroidConstants.FN_TRACEVIEW;
485     }
486 
487     /** Returns the absolute emulator path */
getOsAbsoluteEmulator()488     public static String getOsAbsoluteEmulator() {
489         return getOsSdkFolder() + getOsRelativeEmulator();
490     }
491 
492     /**
493      * Returns a Url file path to the javaDoc folder.
494      */
getUrlDoc()495     public static String getUrlDoc() {
496         return ProjectHelper.getJavaDocPath(
497                 getOsSdkFolder() + AndroidConstants.WS_JAVADOC_FOLDER_LEAF);
498     }
499 
500     /**
501      * Returns the SDK folder.
502      * Guaranteed to be terminated by a platform-specific path separator.
503      */
getOsSdkFolder()504     public static synchronized String getOsSdkFolder() {
505         if (sPlugin == null) {
506             return null;
507         }
508 
509         if (sPlugin.mOsSdkLocation == null) {
510             sPlugin.mOsSdkLocation = sPlugin.mStore.getString(PREFS_SDK_DIR);
511         }
512         return sPlugin.mOsSdkLocation;
513     }
514 
getOsSdkToolsFolder()515     public static String getOsSdkToolsFolder() {
516         return getOsSdkFolder() + SdkConstants.OS_SDK_TOOLS_FOLDER;
517     }
518 
getAutoResRefresh()519     public static synchronized boolean getAutoResRefresh() {
520         if (sPlugin == null) {
521             return false;
522         }
523         return sPlugin.mStore.getBoolean(PREFS_RES_AUTO_REFRESH);
524     }
525 
getBuildVerbosity()526     public static synchronized int getBuildVerbosity() {
527         if (sPlugin != null) {
528             return sPlugin.mBuildVerbosity;
529         }
530 
531         return 0;
532     }
533 
534     /**
535      * Returns an image descriptor for the image file at the given
536      * plug-in relative path
537      *
538      * @param path the path
539      * @return the image descriptor
540      */
getImageDescriptor(String path)541     public static ImageDescriptor getImageDescriptor(String path) {
542         return imageDescriptorFromPlugin(PLUGIN_ID, path);
543     }
544 
545     /**
546      * Reads and returns the content of a text file embedded in the plugin jar
547      * file.
548      * @param filepath the file path to the text file
549      * @return null if the file could not be read
550      */
readEmbeddedTextFile(String filepath)551     public static String readEmbeddedTextFile(String filepath) {
552         Bundle bundle = null;
553         synchronized (AdtPlugin.class) {
554             if (sPlugin != null) {
555                 bundle = sPlugin.getBundle();
556             } else {
557                 return null;
558             }
559         }
560 
561         // attempt to get a file to one of the template.
562         try {
563             URL url = bundle.getEntry(AndroidConstants.WS_SEP + filepath);
564             if (url != null) {
565                 BufferedReader reader = new BufferedReader(
566                         new InputStreamReader(url.openStream()));
567 
568                 String line;
569                 StringBuilder total = new StringBuilder(reader.readLine());
570                 while ((line = reader.readLine()) != null) {
571                     total.append('\n');
572                     total.append(line);
573                 }
574 
575                 return total.toString();
576             }
577         } catch (MalformedURLException e) {
578             // we'll just return null.
579         } catch (IOException e) {
580             // we'll just return null.
581         }
582 
583         return null;
584     }
585 
586     /**
587      * Reads and returns the content of a binary file embedded in the plugin jar
588      * file.
589      * @param filepath the file path to the text file
590      * @return null if the file could not be read
591      */
readEmbeddedFile(String filepath)592     public static byte[] readEmbeddedFile(String filepath) {
593         Bundle bundle = null;
594         synchronized (AdtPlugin.class) {
595             if (sPlugin != null) {
596                 bundle = sPlugin.getBundle();
597             } else {
598                 return null;
599             }
600         }
601 
602         // attempt to get a file to one of the template.
603         try {
604             URL url = bundle.getEntry(AndroidConstants.WS_SEP + filepath);
605             if (url != null) {
606                 // create a buffered reader to facilitate reading.
607                 BufferedInputStream stream = new BufferedInputStream(
608                         url.openStream());
609 
610                 // get the size to read.
611                 int avail = stream.available();
612 
613                 // create the buffer and reads it.
614                 byte[] buffer = new byte[avail];
615                 stream.read(buffer);
616 
617                 // and return.
618                 return buffer;
619             }
620         } catch (MalformedURLException e) {
621             // we'll just return null.
622         } catch (IOException e) {
623             // we'll just return null;.
624         }
625 
626         return null;
627     }
628 
629     /**
630      * Displays an error dialog box. This dialog box is ran asynchronously in the ui thread,
631      * therefore this method can be called from any thread.
632      * @param title The title of the dialog box
633      * @param message The error message
634      */
displayError(final String title, final String message)635     public final static void displayError(final String title, final String message) {
636         // get the current Display
637         final Display display = getDisplay();
638 
639         // dialog box only run in ui thread..
640         display.asyncExec(new Runnable() {
641             public void run() {
642                 Shell shell = display.getActiveShell();
643                 MessageDialog.openError(shell, title, message);
644             }
645         });
646     }
647 
648     /**
649      * Displays a warning dialog box. This dialog box is ran asynchronously in the ui thread,
650      * therefore this method can be called from any thread.
651      * @param title The title of the dialog box
652      * @param message The warning message
653      */
displayWarning(final String title, final String message)654     public final static void displayWarning(final String title, final String message) {
655         // get the current Display
656         final Display display = getDisplay();
657 
658         // dialog box only run in ui thread..
659         display.asyncExec(new Runnable() {
660             public void run() {
661                 Shell shell = display.getActiveShell();
662                 MessageDialog.openWarning(shell, title, message);
663             }
664         });
665     }
666 
667     /**
668      * Display a yes/no question dialog box. This dialog is opened synchronously in the ui thread,
669      * therefore this message can be called from any thread.
670      * @param title The title of the dialog box
671      * @param message The error message
672      * @return true if OK was clicked.
673      */
displayPrompt(final String title, final String message)674     public final static boolean displayPrompt(final String title, final String message) {
675         // get the current Display and Shell
676         final Display display = getDisplay();
677 
678         // we need to ask the user what he wants to do.
679         final boolean[] result = new boolean[1];
680         display.syncExec(new Runnable() {
681             public void run() {
682                 Shell shell = display.getActiveShell();
683                 result[0] = MessageDialog.openQuestion(shell, title, message);
684             }
685         });
686         return result[0];
687     }
688 
689     /**
690      * Logs a message to the default Eclipse log.
691      *
692      * @param severity The severity code. Valid values are: {@link IStatus#OK},
693      * {@link IStatus#ERROR}, {@link IStatus#INFO}, {@link IStatus#WARNING} or
694      * {@link IStatus#CANCEL}.
695      * @param format The format string, like for {@link String#format(String, Object...)}.
696      * @param args The arguments for the format string, like for
697      * {@link String#format(String, Object...)}.
698      */
log(int severity, String format, Object ... args)699     public static void log(int severity, String format, Object ... args) {
700         String message = String.format(format, args);
701         Status status = new Status(severity, PLUGIN_ID, message);
702         getDefault().getLog().log(status);
703     }
704 
705     /**
706      * Logs an exception to the default Eclipse log.
707      * <p/>
708      * The status severity is always set to ERROR.
709      *
710      * @param exception the exception to log.
711      * @param format The format string, like for {@link String#format(String, Object...)}.
712      * @param args The arguments for the format string, like for
713      * {@link String#format(String, Object...)}.
714      */
log(Throwable exception, String format, Object ... args)715     public static void log(Throwable exception, String format, Object ... args) {
716         String message = String.format(format, args);
717         Status status = new Status(IStatus.ERROR, PLUGIN_ID, message, exception);
718         getDefault().getLog().log(status);
719     }
720 
721     /**
722      * This is a mix between log(Throwable) and printErrorToConsole.
723      * <p/>
724      * This logs the exception with an ERROR severity and the given printf-like format message.
725      * The same message is then printed on the Android error console with the associated tag.
726      *
727      * @param exception the exception to log.
728      * @param format The format string, like for {@link String#format(String, Object...)}.
729      * @param args The arguments for the format string, like for
730      * {@link String#format(String, Object...)}.
731      */
logAndPrintError(Throwable exception, String tag, String format, Object ... args)732     public static synchronized void logAndPrintError(Throwable exception, String tag,
733             String format, Object ... args) {
734         if (sPlugin != null) {
735             String message = String.format(format, args);
736             Status status = new Status(IStatus.ERROR, PLUGIN_ID, message, exception);
737             getDefault().getLog().log(status);
738             printToStream(sPlugin.mAndroidConsoleErrorStream, tag, message);
739             showAndroidConsole();
740         }
741     }
742 
743     /**
744      * Prints one or more error message to the android console.
745      * @param tag A tag to be associated with the message. Can be null.
746      * @param objects the objects to print through their <code>toString</code> method.
747      */
printErrorToConsole(String tag, Object... objects)748     public static synchronized void printErrorToConsole(String tag, Object... objects) {
749         if (sPlugin != null) {
750             printToStream(sPlugin.mAndroidConsoleErrorStream, tag, objects);
751 
752             showAndroidConsole();
753         }
754     }
755 
756     /**
757      * Prints one or more error message to the android console.
758      * @param objects the objects to print through their <code>toString</code> method.
759      */
printErrorToConsole(Object... objects)760     public static void printErrorToConsole(Object... objects) {
761         printErrorToConsole((String)null, objects);
762     }
763 
764     /**
765      * Prints one or more error message to the android console.
766      * @param project The project to which the message is associated. Can be null.
767      * @param objects the objects to print through their <code>toString</code> method.
768      */
printErrorToConsole(IProject project, Object... objects)769     public static void printErrorToConsole(IProject project, Object... objects) {
770         String tag = project != null ? project.getName() : null;
771         printErrorToConsole(tag, objects);
772     }
773 
774     /**
775      * Prints one or more build messages to the android console, filtered by Build output verbosity.
776      * @param level Verbosity level of the message.
777      * @param project The project to which the message is associated. Can be null.
778      * @param objects the objects to print through their <code>toString</code> method.
779      * @see AdtConstants#BUILD_ALWAYS
780      * @see AdtConstants#BUILD_NORMAL
781      * @see AdtConstants#BUILD_VERBOSE
782      */
printBuildToConsole(int level, IProject project, Object... objects)783     public static synchronized void printBuildToConsole(int level, IProject project,
784             Object... objects) {
785         if (sPlugin != null) {
786             if (level <= sPlugin.mBuildVerbosity) {
787                 String tag = project != null ? project.getName() : null;
788                 printToStream(sPlugin.mAndroidConsoleStream, tag, objects);
789             }
790         }
791     }
792 
793     /**
794      * Prints one or more message to the android console.
795      * @param tag The tag to be associated with the message. Can be null.
796      * @param objects the objects to print through their <code>toString</code> method.
797      */
printToConsole(String tag, Object... objects)798     public static synchronized void printToConsole(String tag, Object... objects) {
799         if (sPlugin != null) {
800             printToStream(sPlugin.mAndroidConsoleStream, tag, objects);
801         }
802     }
803 
804     /**
805      * Prints one or more message to the android console.
806      * @param project The project to which the message is associated. Can be null.
807      * @param objects the objects to print through their <code>toString</code> method.
808      */
printToConsole(IProject project, Object... objects)809     public static void printToConsole(IProject project, Object... objects) {
810         String tag = project != null ? project.getName() : null;
811         printToConsole(tag, objects);
812     }
813 
814     /** Force the display of the android console */
showAndroidConsole()815     public static void showAndroidConsole() {
816         // first make sure the console is in the workbench
817         EclipseUiHelper.showView(IConsoleConstants.ID_CONSOLE_VIEW, true);
818 
819         // now make sure it's not docked.
820         ConsolePlugin.getDefault().getConsoleManager().showConsoleView(
821                 AdtPlugin.getDefault().getAndroidConsole());
822     }
823 
824     /**
825      * Returns an standard PrintStream object for a specific project.<br>
826      * This PrintStream will add a date/project at the beginning of every
827      * <code>println()</code> output.
828      *
829      * @param project The project object
830      * @param prefix The prefix to be added to the message. Can be null.
831      * @return a new PrintStream
832      */
getOutPrintStream(IProject project, String prefix)833     public static synchronized PrintStream getOutPrintStream(IProject project, String prefix) {
834         if (sPlugin != null) {
835             return new AndroidPrintStream(project, prefix, sPlugin.mAndroidConsoleStream);
836         }
837 
838         return null;
839     }
840 
841     /**
842      * Returns an error PrintStream object for a specific project.<br>
843      * This PrintStream will add a date/project at the beginning of every
844      * <code>println()</code> output.
845      *
846      * @param project The project object
847      * @param prefix The prefix to be added to the message. Can be null.
848      * @return a new PrintStream
849      */
getErrPrintStream(IProject project, String prefix)850     public static synchronized PrintStream getErrPrintStream(IProject project, String prefix) {
851         if (sPlugin != null) {
852             return new AndroidPrintStream(project, prefix, sPlugin.mAndroidConsoleErrorStream);
853         }
854 
855         return null;
856     }
857 
858     /**
859      * Returns whether the Sdk has been loaded.
860      */
getSdkLoadStatus()861     public final LoadStatus getSdkLoadStatus() {
862         synchronized (getSdkLockObject()) {
863             return mSdkIsLoaded;
864         }
865     }
866 
867     /**
868      * Returns the lock object for SDK loading. If you wish to do things while the SDK is loading,
869      * you must synchronize on this object.
870      */
getSdkLockObject()871     public final Object getSdkLockObject() {
872         return mPostLoadProjectsToResolve;
873     }
874 
875     /**
876      * Sets the given {@link IJavaProject} to have its target resolved again once the SDK finishes
877      * to load.
878      */
setProjectToResolve(IJavaProject javaProject)879     public final void setProjectToResolve(IJavaProject javaProject) {
880         synchronized (getSdkLockObject()) {
881             mPostLoadProjectsToResolve.add(javaProject);
882         }
883     }
884 
885     /**
886      * Sets the given {@link IJavaProject} to have its target checked for consistency
887      * once the SDK finishes to load. This is used if the target is resolved using cached
888      * information while the SDK is loading.
889      */
setProjectToCheck(IJavaProject javaProject)890     public final void setProjectToCheck(IJavaProject javaProject) {
891         // only lock on
892         synchronized (getSdkLockObject()) {
893             mPostLoadProjectsToCheck.add(javaProject);
894         }
895     }
896 
897     /**
898      * Checks the location of the SDK is valid and if it is, grab the SDK API version
899      * from the SDK.
900      * @return false if the location is not correct.
901      */
checkSdkLocationAndId()902     private boolean checkSdkLocationAndId() {
903         if (mOsSdkLocation == null || mOsSdkLocation.length() == 0) {
904             displayError(Messages.Dialog_Title_SDK_Location, Messages.SDK_Not_Setup);
905             return false;
906         }
907 
908         return checkSdkLocationAndId(mOsSdkLocation, new CheckSdkErrorHandler() {
909             @Override
910             public boolean handleError(String message) {
911                 AdtPlugin.displayError(Messages.Dialog_Title_SDK_Location,
912                         String.format(Messages.Error_Check_Prefs, message));
913                 return false;
914             }
915 
916             @Override
917             public boolean handleWarning(String message) {
918                 AdtPlugin.displayWarning(Messages.Dialog_Title_SDK_Location, message);
919                 return true;
920             }
921         });
922     }
923 
924     /**
925      * Internal helper to perform the actual sdk location and id check.
926      *
927      * @param osSdkLocation The sdk directory, an OS path.
928      * @param errorHandler An checkSdkErrorHandler that can display a warning or an error.
929      * @return False if there was an error or the result from the errorHandler invocation.
930      */
931     public boolean checkSdkLocationAndId(String osSdkLocation, CheckSdkErrorHandler errorHandler) {
932         if (osSdkLocation.endsWith(File.separator) == false) {
933             osSdkLocation = osSdkLocation + File.separator;
934         }
935 
936         File osSdkFolder = new File(osSdkLocation);
937         if (osSdkFolder.isDirectory() == false) {
938             return errorHandler.handleError(
939                     String.format(Messages.Could_Not_Find_Folder, osSdkLocation));
940         }
941 
942         String osTools = osSdkLocation + SdkConstants.OS_SDK_TOOLS_FOLDER;
943         File toolsFolder = new File(osTools);
944         if (toolsFolder.isDirectory() == false) {
945             return errorHandler.handleError(
946                     String.format(Messages.Could_Not_Find_Folder_In_SDK,
947                             SdkConstants.FD_TOOLS, osSdkLocation));
948         }
949 
950         // check the path to various tools we use
951         String[] filesToCheck = new String[] {
952                 osSdkLocation + getOsRelativeAdb(),
953                 osSdkLocation + getOsRelativeEmulator()
954         };
955         for (String file : filesToCheck) {
956             if (checkFile(file) == false) {
957                 return errorHandler.handleError(String.format(Messages.Could_Not_Find, file));
958             }
959         }
960 
961         // check the SDK build id/version and the plugin version.
962         return VersionCheck.checkVersion(osSdkLocation, errorHandler);
963     }
964 
965     /**
966      * Checks if a path reference a valid existing file.
967      * @param osPath the os path to check.
968      * @return true if the file exists and is, in fact, a file.
969      */
970     private boolean checkFile(String osPath) {
971         File file = new File(osPath);
972         if (file.isFile() == false) {
973             return false;
974         }
975 
976         return true;
977     }
978 
979     /**
980      * Creates a job than can ping the usage server.
981      */
982     private Job createPingUsageServerJob() {
983         // In order to not block the plugin loading, so we spawn another thread.
984         Job job = new Job("Android SDK Ping") {  // Job name, visible in progress view
985             @Override
986             protected IStatus run(IProgressMonitor monitor) {
987                 try {
988                     pingUsageServer(); //$NON-NLS-1$
989 
990                     return Status.OK_STATUS;
991                 } catch (Throwable t) {
992                     log(t, "pingUsageServer failed");       //$NON-NLS-1$
993                     return new Status(IStatus.ERROR, PLUGIN_ID,
994                             "pingUsageServer failed", t);    //$NON-NLS-1$
995                 }
996             }
997         };
998         return job;
999     }
1000 
1001     /**
1002      * Parses the SDK resources.
1003      */
1004     private void parseSdkContent() {
1005         // Perform the update in a thread (here an Eclipse runtime job)
1006         // since this should never block the caller (especially the start method)
1007         Job job = new Job(Messages.AdtPlugin_Android_SDK_Content_Loader) {
1008             @SuppressWarnings("unchecked")
1009             @Override
1010             protected IStatus run(IProgressMonitor monitor) {
1011                 try {
1012 
1013                     if (mSdkIsLoading) {
1014                         return new Status(IStatus.WARNING, PLUGIN_ID,
1015                                 "An Android SDK is already being loaded. Please try again later.");
1016                     }
1017 
1018                     mSdkIsLoading = true;
1019 
1020                     SubMonitor progress = SubMonitor.convert(monitor,
1021                             "Initialize SDK Manager", 100);
1022 
1023                     Sdk sdk = Sdk.loadSdk(mOsSdkLocation);
1024 
1025                     if (sdk != null) {
1026 
1027                         progress.setTaskName(Messages.AdtPlugin_Parsing_Resources);
1028 
1029                         final IAndroidTarget[] targets = sdk.getTargets();
1030                         final int n = targets.length;
1031                         if (n > 0) {
1032                             // load the rest of the targets.
1033                             // TODO: make this on-demand.
1034                             int w = 60 / n;
1035                             for (IAndroidTarget target : targets) {
1036                                 SubMonitor p2 = progress.newChild(w);
1037                                 IStatus status = new AndroidTargetParser(target).run(p2);
1038                                 if (status.getCode() != IStatus.OK) {
1039                                     synchronized (getSdkLockObject()) {
1040                                         mSdkIsLoaded = LoadStatus.FAILED;
1041                                         mPostLoadProjectsToResolve.clear();
1042                                     }
1043                                     return status;
1044                                 }
1045                             }
1046                         }
1047 
1048                         ArrayList<IJavaProject> list = new ArrayList<IJavaProject>();
1049                         synchronized (getSdkLockObject()) {
1050                             mSdkIsLoaded = LoadStatus.LOADED;
1051 
1052                             progress.setTaskName("Check Projects");
1053 
1054                             for (IJavaProject javaProject : mPostLoadProjectsToResolve) {
1055                                 if (javaProject.getProject().isOpen()) {
1056                                     list.add(javaProject);
1057                                 }
1058                             }
1059 
1060                             // done with this list.
1061                             mPostLoadProjectsToResolve.clear();
1062                         }
1063 
1064                         // check the projects that need checking.
1065                         // The method modifies the list (it removes the project that
1066                         // do not need to be resolved again).
1067                         AndroidClasspathContainerInitializer.checkProjectsCache(
1068                                 mPostLoadProjectsToCheck);
1069 
1070                         list.addAll(mPostLoadProjectsToCheck);
1071 
1072                         // update the project that needs recompiling.
1073                         if (list.size() > 0) {
1074                             IJavaProject[] array = list.toArray(
1075                                     new IJavaProject[list.size()]);
1076                             AndroidClasspathContainerInitializer.updateProjects(array);
1077                         }
1078 
1079                         progress.worked(10);
1080                     }
1081 
1082                     // Notify resource changed listeners
1083                     progress.setTaskName("Refresh UI");
1084                     progress.setWorkRemaining(mTargetChangeListeners.size());
1085 
1086                     // Clone the list before iterating, to avoid Concurrent Modification
1087                     // exceptions
1088                     final List<ITargetChangeListener> listeners =
1089                             (List<ITargetChangeListener>)mTargetChangeListeners.clone();
1090                     final SubMonitor progress2 = progress;
1091                     AdtPlugin.getDisplay().syncExec(new Runnable() {
1092                         public void run() {
1093                             for (ITargetChangeListener listener : listeners) {
1094                                 try {
1095                                     listener.onTargetsLoaded();
1096                                 } catch (Exception e) {
1097                                     AdtPlugin.log(e, "Failed to update a TargetChangeListener.");  //$NON-NLS-1$
1098                                 } finally {
1099                                     progress2.worked(1);
1100                                 }
1101                             }
1102                         }
1103                     });
1104                 } catch (Throwable t) {
1105                     log(t, "Unknown exception in parseSdkContent.");    //$NON-NLS-1$
1106                     return new Status(IStatus.ERROR, PLUGIN_ID,
1107                             "parseSdkContent failed", t);               //$NON-NLS-1$
1108 
1109                 } finally {
1110                     mSdkIsLoading = false;
1111                     if (monitor != null) {
1112                         monitor.done();
1113                     }
1114                 }
1115 
1116                 return Status.OK_STATUS;
1117             }
1118         };
1119         job.setPriority(Job.BUILD); // build jobs are run after other interactive jobs
1120         job.schedule();
1121     }
1122 
1123     /** Returns the global android console */
1124     public MessageConsole getAndroidConsole() {
1125         return mAndroidConsole;
1126     }
1127 
1128     // ----- Methods for Editors -------
1129 
1130     public void startEditors() {
1131         sAndroidLogoDesc = imageDescriptorFromPlugin(AdtPlugin.PLUGIN_ID,
1132                 "/icons/android.png"); //$NON-NLS-1$
1133         sAndroidLogo = sAndroidLogoDesc.createImage();
1134 
1135         // Add a resource listener to handle compiled resources.
1136         IWorkspace ws = ResourcesPlugin.getWorkspace();
1137         mResourceMonitor = ResourceMonitor.startMonitoring(ws);
1138 
1139         if (mResourceMonitor != null) {
1140             try {
1141                 setupDefaultEditor(mResourceMonitor);
1142                 ResourceManager.setup(mResourceMonitor);
1143             } catch (Throwable t) {
1144                 log(t, "ResourceManager.setup failed"); //$NON-NLS-1$
1145             }
1146         }
1147     }
1148 
1149     /**
1150      * The <code>AbstractUIPlugin</code> implementation of this <code>Plugin</code>
1151      * method saves this plug-in's preference and dialog stores and shuts down
1152      * its image registry (if they are in use). Subclasses may extend this
1153      * method, but must send super <b>last</b>. A try-finally statement should
1154      * be used where necessary to ensure that <code>super.shutdown()</code> is
1155      * always done.
1156      *
1157      * @see org.eclipse.ui.plugin.AbstractUIPlugin#stop(org.osgi.framework.BundleContext)
1158      */
1159     public void stopEditors() {
1160         sAndroidLogo.dispose();
1161 
1162         IconFactory.getInstance().Dispose();
1163 
1164         // Remove the resource listener that handles compiled resources.
1165         IWorkspace ws = ResourcesPlugin.getWorkspace();
1166         ResourceMonitor.stopMonitoring(ws);
1167 
1168         mRed.dispose();
1169     }
1170 
1171     /**
1172      * Returns an Image for the small Android logo.
1173      *
1174      * Callers should not dispose it.
1175      */
1176     public static Image getAndroidLogo() {
1177         return sAndroidLogo;
1178     }
1179 
1180     /**
1181      * Returns an {@link ImageDescriptor} for the small Android logo.
1182      *
1183      * Callers should not dispose it.
1184      */
1185     public static ImageDescriptor getAndroidLogoDesc() {
1186         return sAndroidLogoDesc;
1187     }
1188 
1189     /**
1190      * Returns the ResourceMonitor object.
1191      */
1192     public ResourceMonitor getResourceMonitor() {
1193         return mResourceMonitor;
1194     }
1195 
1196     /**
1197      * Sets up the editor to register default editors for resource files when needed.
1198      *
1199      * This is called by the {@link AdtPlugin} during initialization.
1200      *
1201      * @param monitor The main Resource Monitor object.
1202      */
1203     public void setupDefaultEditor(ResourceMonitor monitor) {
1204         monitor.addFileListener(new IFileListener() {
1205 
1206             private static final String UNKNOWN_EDITOR = "unknown-editor"; //$NON-NLS-1$
1207 
1208             /* (non-Javadoc)
1209              * Sent when a file changed.
1210              * @param file The file that changed.
1211              * @param markerDeltas The marker deltas for the file.
1212              * @param kind The change kind. This is equivalent to
1213              * {@link IResourceDelta#accept(IResourceDeltaVisitor)}
1214              *
1215              * @see IFileListener#fileChanged
1216              */
1217             public void fileChanged(IFile file, IMarkerDelta[] markerDeltas, int kind) {
1218                 if (AndroidConstants.EXT_XML.equals(file.getFileExtension())) {
1219                     // The resources files must have a file path similar to
1220                     //    project/res/.../*.xml
1221                     // There is no support for sub folders, so the segment count must be 4
1222                     if (file.getFullPath().segmentCount() == 4) {
1223                         // check if we are inside the res folder.
1224                         String segment = file.getFullPath().segment(1);
1225                         if (segment.equalsIgnoreCase(SdkConstants.FD_RESOURCES)) {
1226                             // we are inside a res/ folder, get the actual ResourceFolder
1227                             ProjectResources resources = ResourceManager.getInstance().
1228                                 getProjectResources(file.getProject());
1229 
1230                             // This happens when importing old Android projects in Eclipse
1231                             // that lack the container (probably because resources fail to build
1232                             // properly.)
1233                             if (resources == null) {
1234                                 log(IStatus.INFO,
1235                                         "getProjectResources failed for path %1$s in project %2$s", //$NON-NLS-1$
1236                                         file.getFullPath().toOSString(),
1237                                         file.getProject().getName());
1238                                 return;
1239                             }
1240 
1241                             ResourceFolder resFolder = resources.getResourceFolder(
1242                                 (IFolder)file.getParent());
1243 
1244                             if (resFolder != null) {
1245                                 if (kind == IResourceDelta.ADDED) {
1246                                     resourceAdded(file, resFolder.getType());
1247                                 } else if (kind == IResourceDelta.CHANGED) {
1248                                     resourceChanged(file, resFolder.getType());
1249                                 }
1250                             } else {
1251                                 // if the res folder is null, this means the name is invalid,
1252                                 // in this case we remove whatever android editors that was set
1253                                 // as the default editor.
1254                                 IEditorDescriptor desc = IDE.getDefaultEditor(file);
1255                                 String editorId = desc.getId();
1256                                 if (editorId.startsWith(AndroidConstants.EDITORS_NAMESPACE)) {
1257                                     // reset the default editor.
1258                                     IDE.setDefaultEditor(file, null);
1259                                 }
1260                             }
1261                         }
1262                     }
1263                 }
1264             }
1265 
1266             private void resourceAdded(IFile file, ResourceFolderType type) {
1267                 // set the default editor based on the type.
1268                 if (type == ResourceFolderType.LAYOUT) {
1269                     IDE.setDefaultEditor(file, LayoutEditor.ID);
1270                 } else if (type == ResourceFolderType.DRAWABLE
1271                         || type == ResourceFolderType.VALUES) {
1272                     IDE.setDefaultEditor(file, ResourcesEditor.ID);
1273                 } else if (type == ResourceFolderType.MENU) {
1274                     IDE.setDefaultEditor(file, MenuEditor.ID);
1275                 } else if (type == ResourceFolderType.XML) {
1276                     if (XmlEditor.canHandleFile(file)) {
1277                         IDE.setDefaultEditor(file, XmlEditor.ID);
1278                     } else {
1279                         // set a property to determine later if the XML can be handled
1280                         QualifiedName qname = new QualifiedName(
1281                                 AdtPlugin.PLUGIN_ID,
1282                                 UNKNOWN_EDITOR);
1283                         try {
1284                             file.setPersistentProperty(qname, "1"); //$NON-NLS-1$
1285                         } catch (CoreException e) {
1286                             // pass
1287                         }
1288                     }
1289                 }
1290             }
1291 
1292             private void resourceChanged(IFile file, ResourceFolderType type) {
1293                 if (type == ResourceFolderType.XML) {
1294                     IEditorDescriptor ed = IDE.getDefaultEditor(file);
1295                     if (ed == null || ed.getId() != XmlEditor.ID) {
1296                         QualifiedName qname = new QualifiedName(
1297                                 AdtPlugin.PLUGIN_ID,
1298                                 UNKNOWN_EDITOR);
1299                         String prop = null;
1300                         try {
1301                             prop = file.getPersistentProperty(qname);
1302                         } catch (CoreException e) {
1303                             // pass
1304                         }
1305                         if (prop != null && XmlEditor.canHandleFile(file)) {
1306                             try {
1307                                 // remove the property & set editor
1308                                 file.setPersistentProperty(qname, null);
1309                                 IWorkbenchPage page = PlatformUI.getWorkbench().
1310                                                         getActiveWorkbenchWindow().getActivePage();
1311 
1312                                 IEditorPart oldEditor = page.findEditor(new FileEditorInput(file));
1313                                 if (oldEditor != null &&
1314                                         AdtPlugin.displayPrompt("Android XML Editor",
1315                                             String.format("The file you just saved as been recognized as a file that could be better handled using the Android XML Editor. Do you want to edit '%1$s' using the Android XML editor instead?",
1316                                                     file.getFullPath()))) {
1317                                     IDE.setDefaultEditor(file, XmlEditor.ID);
1318                                     IEditorPart newEditor = page.openEditor(
1319                                             new FileEditorInput(file),
1320                                             XmlEditor.ID,
1321                                             true, /* activate */
1322                                             IWorkbenchPage.MATCH_NONE);
1323 
1324                                     if (newEditor != null) {
1325                                         page.closeEditor(oldEditor, true /* save */);
1326                                     }
1327                                 }
1328                             } catch (CoreException e) {
1329                                 // setPersistentProperty or page.openEditor may have failed
1330                             }
1331                         }
1332                     }
1333                 }
1334             }
1335 
1336         }, IResourceDelta.ADDED | IResourceDelta.CHANGED);
1337     }
1338 
1339     /**
1340      * Adds a new {@link ITargetChangeListener} to be notified when a new SDK is loaded, or when
1341      * a project has its target changed.
1342      */
1343     public void addTargetListener(ITargetChangeListener listener) {
1344         mTargetChangeListeners.add(listener);
1345     }
1346 
1347     /**
1348      * Removes an existing {@link ITargetChangeListener}.
1349      * @see #addTargetListener(ITargetChangeListener)
1350      */
1351     public void removeTargetListener(ITargetChangeListener listener) {
1352         mTargetChangeListeners.remove(listener);
1353     }
1354 
1355     /**
1356      * Updates all the {@link ITargetChangeListener} that a target has changed for a given project.
1357      * <p/>Only editors related to that project should reload.
1358      */
1359     @SuppressWarnings("unchecked")
1360     public void updateTargetListener(final IProject project) {
1361         final List<ITargetChangeListener> listeners =
1362             (List<ITargetChangeListener>)mTargetChangeListeners.clone();
1363 
1364         AdtPlugin.getDisplay().asyncExec(new Runnable() {
1365             public void run() {
1366                 for (ITargetChangeListener listener : listeners) {
1367                     try {
1368                         listener.onProjectTargetChange(project);
1369                     } catch (Exception e) {
1370                         AdtPlugin.log(e, "Failed to update a TargetChangeListener.");  //$NON-NLS-1$
1371                     }
1372                 }
1373             }
1374         });
1375     }
1376 
1377     public static synchronized OutputStream getErrorStream() {
1378         return sPlugin.mAndroidConsoleErrorStream;
1379     }
1380 
1381     /**
1382      * Pings the usage start server.
1383      */
1384     private void pingUsageServer() {
1385         // get the version of the plugin
1386         String versionString = (String) getBundle().getHeaders().get(
1387                 Constants.BUNDLE_VERSION);
1388         Version version = new Version(versionString);
1389 
1390         versionString = String.format("%1$d.%2$d.%3$d", version.getMajor(), //$NON-NLS-1$
1391                 version.getMinor(), version.getMicro());
1392 
1393         SdkStatsService.ping("adt", versionString, getDisplay()); //$NON-NLS-1$
1394     }
1395 
1396     /**
1397      * Reparses the content of the SDK and updates opened projects.
1398      */
1399     public void reparseSdk() {
1400         // add all the opened Android projects to the list of projects to be updated
1401         // after the SDK is reloaded
1402         synchronized (getSdkLockObject()) {
1403             // get the project to refresh.
1404             IJavaProject[] androidProjects = BaseProjectHelper.getAndroidProjects();
1405             mPostLoadProjectsToResolve.addAll(Arrays.asList(androidProjects));
1406         }
1407 
1408         // parse the SDK resources at the new location
1409         parseSdkContent();
1410     }
1411 
1412     /**
1413      * Prints messages, associated with a project to the specified stream
1414      * @param stream The stream to write to
1415      * @param tag The tag associated to the message. Can be null
1416      * @param objects The objects to print through their toString() method (or directly for
1417      * {@link String} objects.
1418      */
1419     public static synchronized void printToStream(MessageConsoleStream stream, String tag,
1420             Object... objects) {
1421         String dateTag = getMessageTag(tag);
1422 
1423         for (Object obj : objects) {
1424             stream.print(dateTag);
1425             if (obj instanceof String) {
1426                 stream.println((String)obj);
1427             } else {
1428                 stream.println(obj.toString());
1429             }
1430         }
1431     }
1432 
1433     /**
1434      * Creates a string containing the current date/time, and the tag
1435      * @param tag The tag associated to the message. Can be null
1436      * @return The dateTag
1437      */
1438     public static String getMessageTag(String tag) {
1439         Calendar c = Calendar.getInstance();
1440 
1441         if (tag == null) {
1442             return String.format(Messages.Console_Date_Tag, c);
1443         }
1444 
1445         return String.format(Messages.Console_Data_Project_Tag, c, tag);
1446     }
1447 
1448 }
1449