Home | History | Annotate | Download | only in adt
      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          */
    174         public AndroidPrintStream(IProject project, String prefix, OutputStream stream) {
    175             super(stream);
    176             mProject = project;
    177         }
    178 
    179         @Override
    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          */
    204         public abstract boolean handleError(String message);
    205 
    206         /** Handle a warning message during sdk location check. Returns whatever
    207          * checkSdkLocationAndId() should returns.
    208          */
    209         public abstract boolean handleWarning(String message);
    210     }
    211 
    212     /**
    213      * The constructor
    214      */
    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
    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
    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      */
    407     public static synchronized AdtPlugin getDefault() {
    408         return sPlugin;
    409     }
    410 
    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 */
    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 */
    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 */
    434     public static String getOsRelativeEmulator() {
    435         return SdkConstants.OS_SDK_TOOLS_FOLDER + SdkConstants.FN_EMULATOR;
    436     }
    437 
    438     /** Returns the absolute adb path */
    439     public static String getOsAbsoluteAdb() {
    440         return getOsSdkFolder() + getOsRelativeAdb();
    441     }
    442 
    443     /** Returns the absolute zipalign path */
    444     public static String getOsAbsoluteZipAlign() {
    445         return getOsSdkFolder() + getOsRelativeZipAlign();
    446     }
    447 
    448     /** Returns the absolute traceview path */
    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 */
    455     public static String getOsAbsoluteEmulator() {
    456         return getOsSdkFolder() + getOsRelativeEmulator();
    457     }
    458 
    459     /**
    460      * Returns a Url file path to the javaDoc folder.
    461      */
    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      */
    471     public static synchronized String getOsSdkFolder() {
    472         if (sPlugin == null) {
    473             return null;
    474         }
    475 
    476         return AdtPrefs.getPrefs().getOsSdkFolder();
    477     }
    478 
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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 */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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