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