Home | History | Annotate | Download | only in ddms
      1 /*
      2  * Copyright (C) 2007 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
      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.ddms;
     18 
     19 import com.android.ddmlib.AndroidDebugBridge;
     20 import com.android.ddmlib.Client;
     21 import com.android.ddmlib.DdmPreferences;
     22 import com.android.ddmlib.IDevice;
     23 import com.android.ddmlib.Log;
     24 import com.android.ddmlib.AndroidDebugBridge.IDeviceChangeListener;
     25 import com.android.ddmlib.Log.ILogOutput;
     26 import com.android.ddmlib.Log.LogLevel;
     27 import com.android.ddmuilib.DdmUiPreferences;
     28 import com.android.ddmuilib.StackTracePanel;
     29 import com.android.ddmuilib.DevicePanel.IUiSelectionListener;
     30 import com.android.ide.eclipse.ddms.i18n.Messages;
     31 import com.android.ide.eclipse.ddms.preferences.PreferenceInitializer;
     32 
     33 import org.eclipse.core.runtime.CoreException;
     34 import org.eclipse.core.runtime.IConfigurationElement;
     35 import org.eclipse.core.runtime.IExtensionPoint;
     36 import org.eclipse.core.runtime.IExtensionRegistry;
     37 import org.eclipse.core.runtime.IProgressMonitor;
     38 import org.eclipse.core.runtime.IStatus;
     39 import org.eclipse.core.runtime.Platform;
     40 import org.eclipse.core.runtime.Status;
     41 import org.eclipse.core.runtime.jobs.Job;
     42 import org.eclipse.jface.dialogs.MessageDialog;
     43 import org.eclipse.jface.preference.IPreferenceStore;
     44 import org.eclipse.jface.util.IPropertyChangeListener;
     45 import org.eclipse.jface.util.PropertyChangeEvent;
     46 import org.eclipse.swt.SWTException;
     47 import org.eclipse.swt.graphics.Color;
     48 import org.eclipse.swt.widgets.Display;
     49 import org.eclipse.swt.widgets.Shell;
     50 import org.eclipse.ui.IWorkbench;
     51 import org.eclipse.ui.console.ConsolePlugin;
     52 import org.eclipse.ui.console.IConsole;
     53 import org.eclipse.ui.console.MessageConsole;
     54 import org.eclipse.ui.console.MessageConsoleStream;
     55 import org.eclipse.ui.plugin.AbstractUIPlugin;
     56 import org.osgi.framework.BundleContext;
     57 
     58 import java.io.File;
     59 import java.util.ArrayList;
     60 import java.util.Calendar;
     61 
     62 /**
     63  * The activator class controls the plug-in life cycle
     64  */
     65 public final class DdmsPlugin extends AbstractUIPlugin implements IDeviceChangeListener,
     66         IUiSelectionListener, com.android.ddmuilib.StackTracePanel.ISourceRevealer {
     67 
     68 
     69     // The plug-in ID
     70     public static final String PLUGIN_ID = "com.android.ide.eclipse.ddms"; //$NON-NLS-1$
     71 
     72     /** The singleton instance */
     73     private static DdmsPlugin sPlugin;
     74 
     75     /** Location of the adb command line executable */
     76     private static String sAdbLocation;
     77     private static String sToolsFolder;
     78     private static String sHprofConverter;
     79 
     80     private boolean mHasDebuggerConnectors;
     81     /** debugger connectors for already running apps.
     82      * Initialized from an extension point.
     83      */
     84     private IDebuggerConnector[] mDebuggerConnectors;
     85     private ISourceRevealer[] mSourceRevealers;
     86     private ITraceviewLauncher[] mTraceviewLaunchers;
     87 
     88 
     89     /** Console for DDMS log message */
     90     private MessageConsole mDdmsConsole;
     91 
     92     private IDevice mCurrentDevice;
     93     private Client mCurrentClient;
     94     private boolean mListeningToUiSelection = false;
     95 
     96     private final ArrayList<ISelectionListener> mListeners = new ArrayList<ISelectionListener>();
     97 
     98     private Color mRed;
     99 
    100 
    101     /**
    102      * Classes which implement this interface provide methods that deals
    103      * with {@link IDevice} and {@link Client} selectionchanges.
    104      */
    105     public interface ISelectionListener {
    106 
    107         /**
    108          * Sent when a new {@link Client} is selected.
    109          * @param selectedClient The selected client. If null, no clients are selected.
    110          */
    111         public void selectionChanged(Client selectedClient);
    112 
    113         /**
    114          * Sent when a new {@link IDevice} is selected.
    115          * @param selectedDevice the selected device. If null, no devices are selected.
    116          */
    117         public void selectionChanged(IDevice selectedDevice);
    118     }
    119 
    120     /**
    121      * The constructor
    122      */
    123     public DdmsPlugin() {
    124         sPlugin = this;
    125     }
    126 
    127     /*
    128      * (non-Javadoc)
    129      *
    130      * @see org.eclipse.ui.plugin.AbstractUIPlugin#start(org.osgi.framework.BundleContext)
    131      */
    132     @Override
    133     public void start(BundleContext context) throws Exception {
    134         super.start(context);
    135 
    136         final Display display = getDisplay();
    137 
    138         // get the eclipse store
    139         final IPreferenceStore eclipseStore = getPreferenceStore();
    140 
    141         AndroidDebugBridge.addDeviceChangeListener(this);
    142 
    143         DdmUiPreferences.setStore(eclipseStore);
    144 
    145         //DdmUiPreferences.displayCharts();
    146 
    147         // set the consoles.
    148         mDdmsConsole = new MessageConsole("DDMS", null); //$NON-NLS-1$
    149         ConsolePlugin.getDefault().getConsoleManager().addConsoles(
    150                 new IConsole[] {
    151                     mDdmsConsole
    152                 });
    153 
    154         final MessageConsoleStream consoleStream = mDdmsConsole.newMessageStream();
    155         final MessageConsoleStream errorConsoleStream = mDdmsConsole.newMessageStream();
    156         mRed = new Color(display, 0xFF, 0x00, 0x00);
    157 
    158         // because this can be run, in some cases, by a non UI thread, and because
    159         // changing the console properties update the UI, we need to make this change
    160         // in the UI thread.
    161         display.asyncExec(new Runnable() {
    162             public void run() {
    163                 errorConsoleStream.setColor(mRed);
    164             }
    165         });
    166 
    167         // set up the ddms log to use the ddms console.
    168         Log.setLogOutput(new ILogOutput() {
    169             public void printLog(LogLevel logLevel, String tag, String message) {
    170                 if (logLevel.getPriority() >= LogLevel.ERROR.getPriority()) {
    171                     printToStream(errorConsoleStream, tag, message);
    172                     ConsolePlugin.getDefault().getConsoleManager().showConsoleView(mDdmsConsole);
    173                 } else {
    174                     printToStream(consoleStream, tag, message);
    175                 }
    176             }
    177 
    178             public void printAndPromptLog(final LogLevel logLevel, final String tag,
    179                     final String message) {
    180                 printLog(logLevel, tag, message);
    181                 // dialog box only run in UI thread..
    182                 display.asyncExec(new Runnable() {
    183                     public void run() {
    184                         Shell shell = display.getActiveShell();
    185                         if (logLevel == LogLevel.ERROR) {
    186                             MessageDialog.openError(shell, tag, message);
    187                         } else {
    188                             MessageDialog.openWarning(shell, tag, message);
    189                         }
    190                     }
    191                 });
    192             }
    193 
    194         });
    195 
    196         // set the listener for the preference change
    197         eclipseStore.addPropertyChangeListener(new IPropertyChangeListener() {
    198             public void propertyChange(PropertyChangeEvent event) {
    199                 // get the name of the property that changed.
    200                 String property = event.getProperty();
    201 
    202                 if (PreferenceInitializer.ATTR_DEBUG_PORT_BASE.equals(property)) {
    203                     DdmPreferences.setDebugPortBase(
    204                             eclipseStore.getInt(PreferenceInitializer.ATTR_DEBUG_PORT_BASE));
    205                 } else if (PreferenceInitializer.ATTR_SELECTED_DEBUG_PORT.equals(property)) {
    206                     DdmPreferences.setSelectedDebugPort(
    207                             eclipseStore.getInt(PreferenceInitializer.ATTR_SELECTED_DEBUG_PORT));
    208                 } else if (PreferenceInitializer.ATTR_THREAD_INTERVAL.equals(property)) {
    209                     DdmUiPreferences.setThreadRefreshInterval(
    210                             eclipseStore.getInt(PreferenceInitializer.ATTR_THREAD_INTERVAL));
    211                 } else if (PreferenceInitializer.ATTR_LOG_LEVEL.equals(property)) {
    212                     DdmPreferences.setLogLevel(
    213                             eclipseStore.getString(PreferenceInitializer.ATTR_LOG_LEVEL));
    214                 } else if (PreferenceInitializer.ATTR_TIME_OUT.equals(property)) {
    215                     DdmPreferences.setTimeOut(
    216                             eclipseStore.getInt(PreferenceInitializer.ATTR_TIME_OUT));
    217                 } else if (PreferenceInitializer.ATTR_USE_ADBHOST.equals(property)) {
    218                     DdmPreferences.setUseAdbHost(
    219                             eclipseStore.getBoolean(PreferenceInitializer.ATTR_USE_ADBHOST));
    220                 } else if (PreferenceInitializer.ATTR_ADBHOST_VALUE.equals(property)) {
    221                     DdmPreferences.setAdbHostValue(
    222                             eclipseStore.getString(PreferenceInitializer.ATTR_ADBHOST_VALUE));
    223                 }
    224             }
    225         });
    226 
    227         // do some last initializations
    228 
    229         // set the preferences.
    230         PreferenceInitializer.setupPreferences();
    231 
    232         // this class is set as the main source revealer and will look at all the implementations
    233         // of the extension point. see #reveal(String, String, int)
    234         StackTracePanel.setSourceRevealer(this);
    235 
    236         /*
    237          * Load the extension point implementations.
    238          * The first step is to load the IConfigurationElement representing the implementations.
    239          * The 2nd step is to use these objects to instantiate the implementation classes.
    240          *
    241          * Because the 2nd step will trigger loading the plug-ins providing the implementations,
    242          * and those plug-ins could access DDMS classes (like ADT), this 2nd step should be done
    243          * in a Job to ensure that DDMS is loaded, so that the other plug-ins can load.
    244          *
    245          * Both steps could be done in the 2nd step but some of DDMS UI rely on knowing if there
    246          * is an implementation or not (DeviceView), so we do the first steps in start() and, in
    247          * some case, record it.
    248          *
    249          */
    250 
    251         // get the IConfigurationElement for the debuggerConnector right away.
    252         final IConfigurationElement[] dcce = findConfigElements(
    253                 "com.android.ide.eclipse.ddms.debuggerConnector"); //$NON-NLS-1$
    254         mHasDebuggerConnectors = dcce.length > 0;
    255 
    256         // get the other configElements and instantiante them in a Job.
    257         new Job(Messages.DdmsPlugin_DDMS_Post_Create_Init) {
    258             @Override
    259             protected IStatus run(IProgressMonitor monitor) {
    260                 try {
    261                     // init the lib
    262                     AndroidDebugBridge.init(true /* debugger support */);
    263 
    264                     // get the available adb locators
    265                     IConfigurationElement[] elements = findConfigElements(
    266                             "com.android.ide.eclipse.ddms.toolsLocator"); //$NON-NLS-1$
    267 
    268                     IToolsLocator[] locators = instantiateToolsLocators(elements);
    269 
    270                     for (IToolsLocator locator : locators) {
    271                         try {
    272                             String adbLocation = locator.getAdbLocation();
    273                             String traceviewLocation = locator.getTraceViewLocation();
    274                             String hprofConvLocation = locator.getHprofConvLocation();
    275                             if (adbLocation != null && traceviewLocation != null &&
    276                                     hprofConvLocation != null) {
    277                                 // checks if the location is valid.
    278                                 if (setToolsLocation(adbLocation, hprofConvLocation,
    279                                         traceviewLocation)) {
    280 
    281                                     AndroidDebugBridge.createBridge(sAdbLocation,
    282                                             true /* forceNewBridge */);
    283 
    284                                     // no need to look at the other locators.
    285                                     break;
    286                                 }
    287                             }
    288                         } catch (Throwable t) {
    289                             // ignore, we'll just not use this implementation.
    290                         }
    291                     }
    292 
    293                     // get the available debugger connectors
    294                     mDebuggerConnectors = instantiateDebuggerConnectors(dcce);
    295 
    296                     // get the available source revealers
    297                     elements = findConfigElements("com.android.ide.eclipse.ddms.sourceRevealer"); //$NON-NLS-1$
    298                     mSourceRevealers = instantiateSourceRevealers(elements);
    299 
    300                     // get the available Traceview Launchers.
    301                     elements = findConfigElements("com.android.ide.eclipse.ddms.traceviewLauncher"); //$NON-NLS-1$
    302                     mTraceviewLaunchers = instantiateTraceviewLauncher(elements);
    303 
    304                     return Status.OK_STATUS;
    305                 } catch (CoreException e) {
    306                     return e.getStatus();
    307                 }
    308             }
    309         }.schedule();
    310     }
    311 
    312     private IConfigurationElement[] findConfigElements(String name) {
    313 
    314         // get the adb location from an implementation of the ADB Locator extension point.
    315         IExtensionRegistry extensionRegistry = Platform.getExtensionRegistry();
    316         IExtensionPoint extensionPoint = extensionRegistry.getExtensionPoint(name);
    317         if (extensionPoint != null) {
    318             return extensionPoint.getConfigurationElements();
    319         }
    320 
    321         // shouldn't happen or it means the plug-in is broken.
    322         return new IConfigurationElement[0];
    323     }
    324 
    325     /**
    326      * Finds if any other plug-in is extending the exposed Extension Point called adbLocator.
    327      *
    328      * @return an array of all locators found, or an empty array if none were found.
    329      */
    330     private IToolsLocator[] instantiateToolsLocators(IConfigurationElement[] configElements)
    331             throws CoreException {
    332         ArrayList<IToolsLocator> list = new ArrayList<IToolsLocator>();
    333 
    334         if (configElements.length > 0) {
    335             // only use the first one, ignore the others.
    336             IConfigurationElement configElement = configElements[0];
    337 
    338             // instantiate the class
    339             Object obj = configElement.createExecutableExtension("class"); //$NON-NLS-1$
    340             if (obj instanceof IToolsLocator) {
    341                 list.add((IToolsLocator) obj);
    342             }
    343         }
    344 
    345         return list.toArray(new IToolsLocator[list.size()]);
    346     }
    347 
    348     /**
    349      * Finds if any other plug-in is extending the exposed Extension Point called debuggerConnector.
    350      *
    351      * @return an array of all locators found, or an empty array if none were found.
    352      */
    353     private IDebuggerConnector[] instantiateDebuggerConnectors(
    354             IConfigurationElement[] configElements) throws CoreException {
    355         ArrayList<IDebuggerConnector> list = new ArrayList<IDebuggerConnector>();
    356 
    357         if (configElements.length > 0) {
    358             // only use the first one, ignore the others.
    359             IConfigurationElement configElement = configElements[0];
    360 
    361             // instantiate the class
    362             Object obj = configElement.createExecutableExtension("class"); //$NON-NLS-1$
    363             if (obj instanceof IDebuggerConnector) {
    364                 list.add((IDebuggerConnector) obj);
    365             }
    366         }
    367 
    368         return list.toArray(new IDebuggerConnector[list.size()]);
    369     }
    370 
    371     /**
    372      * Finds if any other plug-in is extending the exposed Extension Point called sourceRevealer.
    373      *
    374      * @return an array of all locators found, or an empty array if none were found.
    375      */
    376     private ISourceRevealer[] instantiateSourceRevealers(IConfigurationElement[] configElements)
    377             throws CoreException {
    378         ArrayList<ISourceRevealer> list = new ArrayList<ISourceRevealer>();
    379 
    380         if (configElements.length > 0) {
    381             // only use the first one, ignore the others.
    382             IConfigurationElement configElement = configElements[0];
    383 
    384             // instantiate the class
    385             Object obj = configElement.createExecutableExtension("class"); //$NON-NLS-1$
    386             if (obj instanceof ISourceRevealer) {
    387                 list.add((ISourceRevealer) obj);
    388             }
    389         }
    390 
    391         return list.toArray(new ISourceRevealer[list.size()]);
    392     }
    393 
    394     /**
    395      * Finds if any other plug-in is extending the exposed Extension Point called traceviewLauncher.
    396      *
    397      * @return an array of all locators found, or an empty array if none were found.
    398      */
    399     private ITraceviewLauncher[] instantiateTraceviewLauncher(
    400             IConfigurationElement[] configElements)
    401             throws CoreException {
    402         ArrayList<ITraceviewLauncher> list = new ArrayList<ITraceviewLauncher>();
    403 
    404         if (configElements.length > 0) {
    405             // only use the first one, ignore the others.
    406             IConfigurationElement configElement = configElements[0];
    407 
    408             // instantiate the class
    409             Object obj = configElement.createExecutableExtension("class"); //$NON-NLS-1$
    410             if (obj instanceof ITraceviewLauncher) {
    411                 list.add((ITraceviewLauncher) obj);
    412             }
    413         }
    414 
    415         return list.toArray(new ITraceviewLauncher[list.size()]);
    416     }
    417 
    418     public static Display getDisplay() {
    419         IWorkbench bench = sPlugin.getWorkbench();
    420         if (bench != null) {
    421             return bench.getDisplay();
    422         }
    423         return null;
    424     }
    425 
    426     /*
    427      * (non-Javadoc)
    428      *
    429      * @see org.eclipse.ui.plugin.AbstractUIPlugin#stop(org.osgi.framework.BundleContext)
    430      */
    431     @Override
    432     public void stop(BundleContext context) throws Exception {
    433         AndroidDebugBridge.removeDeviceChangeListener(this);
    434 
    435         AndroidDebugBridge.terminate();
    436 
    437         mRed.dispose();
    438 
    439         sPlugin = null;
    440         super.stop(context);
    441     }
    442 
    443     /**
    444      * Returns the shared instance
    445      *
    446      * @return the shared instance
    447      */
    448     public static DdmsPlugin getDefault() {
    449         return sPlugin;
    450     }
    451 
    452     public static String getAdb() {
    453         return sAdbLocation;
    454     }
    455 
    456     public static String getToolsFolder2() {
    457         return sToolsFolder;
    458     }
    459 
    460     public static String getHprofConverter() {
    461         return sHprofConverter;
    462     }
    463 
    464     /**
    465      * Stores the adb location. This returns true if the location is an existing file.
    466      */
    467     private static boolean setToolsLocation(String adbLocation, String hprofConvLocation,
    468             String traceViewLocation) {
    469 
    470         File adb = new File(adbLocation);
    471         File hprofConverter = new File(hprofConvLocation);
    472         File traceview = new File(traceViewLocation);
    473 
    474         String missing = "";
    475         if (adb.isFile() == false) {
    476             missing += adb.getAbsolutePath() + " ";
    477         }
    478         if (hprofConverter.isFile() == false) {
    479             missing += hprofConverter.getAbsolutePath() + " ";
    480         }
    481         if (traceview.isFile() == false) {
    482             missing += traceview.getAbsolutePath() + " ";
    483         }
    484 
    485         if (missing.length() > 0) {
    486             String msg = String.format("DDMS files not found: %1$s", missing);
    487             Log.e("DDMS", msg);
    488             Status status = new Status(IStatus.ERROR, PLUGIN_ID, msg, null /*exception*/);
    489             getDefault().getLog().log(status);
    490             return false;
    491         }
    492 
    493         sAdbLocation = adbLocation;
    494         sHprofConverter = hprofConverter.getAbsolutePath();
    495         DdmUiPreferences.setTraceviewLocation(traceview.getAbsolutePath());
    496 
    497         return true;
    498     }
    499 
    500     /**
    501      * Set the location of the adb executable and optionally starts adb
    502      * @param adb location of adb
    503      * @param startAdb flag to start adb
    504      */
    505     public static void setToolsLocation(String adbLocation, boolean startAdb,
    506             String hprofConvLocation, String traceViewLocation) {
    507 
    508         if (setToolsLocation(adbLocation, hprofConvLocation, traceViewLocation)) {
    509             // starts the server in a thread in case this is blocking.
    510             if (startAdb) {
    511                 new Thread() {
    512                     @Override
    513                     public void run() {
    514                         // create and start the bridge
    515                         try {
    516                             AndroidDebugBridge.createBridge(sAdbLocation,
    517                                     false /* forceNewBridge */);
    518                         } catch (Throwable t) {
    519                             Status status = new Status(IStatus.ERROR, PLUGIN_ID,
    520                                     "Failed to create AndroidDebugBridge", t);
    521                             getDefault().getLog().log(status);
    522                         }
    523                     }
    524                 }.start();
    525             }
    526         }
    527     }
    528 
    529     /**
    530      * Returns whether there are implementations of the debuggerConnectors extension point.
    531      * <p/>
    532      * This is guaranteed to return the correct value as soon as the plug-in is loaded.
    533      */
    534     public boolean hasDebuggerConnectors() {
    535         return mHasDebuggerConnectors;
    536     }
    537 
    538     /**
    539      * Returns the implementations of {@link IDebuggerConnector}.
    540      * <p/>
    541      * There may be a small amount of time right after the plug-in load where this can return
    542      * null even if there are implementation.
    543      * <p/>
    544      * Since the use of the implementation likely require user input, the UI can use
    545      * {@link #hasDebuggerConnectors()} to know if there are implementations before they are loaded.
    546      */
    547     public IDebuggerConnector[] getDebuggerConnectors() {
    548         return mDebuggerConnectors;
    549     }
    550 
    551     public synchronized void addSelectionListener(ISelectionListener listener) {
    552         mListeners.add(listener);
    553 
    554         // notify the new listener of the current selection
    555        listener.selectionChanged(mCurrentDevice);
    556        listener.selectionChanged(mCurrentClient);
    557     }
    558 
    559     public synchronized void removeSelectionListener(ISelectionListener listener) {
    560         mListeners.remove(listener);
    561     }
    562 
    563     public synchronized void setListeningState(boolean state) {
    564         mListeningToUiSelection = state;
    565     }
    566 
    567     /**
    568      * Sent when the a device is connected to the {@link AndroidDebugBridge}.
    569      * <p/>
    570      * This is sent from a non UI thread.
    571      * @param device the new device.
    572      *
    573      * @see IDeviceChangeListener#deviceConnected(IDevice)
    574      */
    575     public void deviceConnected(IDevice device) {
    576         // if we are listening to selection coming from the ui, then we do nothing, as
    577         // any change in the devices/clients, will be handled by the UI, and we'll receive
    578         // selection notification through our implementation of IUiSelectionListener.
    579         if (mListeningToUiSelection == false) {
    580             if (mCurrentDevice == null) {
    581                 handleDefaultSelection(device);
    582             }
    583         }
    584     }
    585 
    586     /**
    587      * Sent when the a device is disconnected to the {@link AndroidDebugBridge}.
    588      * <p/>
    589      * This is sent from a non UI thread.
    590      * @param device the new device.
    591      *
    592      * @see IDeviceChangeListener#deviceDisconnected(IDevice)
    593      */
    594     public void deviceDisconnected(IDevice device) {
    595         // if we are listening to selection coming from the ui, then we do nothing, as
    596         // any change in the devices/clients, will be handled by the UI, and we'll receive
    597         // selection notification through our implementation of IUiSelectionListener.
    598         if (mListeningToUiSelection == false) {
    599             // test if the disconnected device was the default selection.
    600             if (mCurrentDevice == device) {
    601                 // try to find a new device
    602                 AndroidDebugBridge bridge = AndroidDebugBridge.getBridge();
    603                 if (bridge != null) {
    604                     // get the device list
    605                     IDevice[] devices = bridge.getDevices();
    606 
    607                     // check if we still have devices
    608                     if (devices.length == 0) {
    609                         handleDefaultSelection((IDevice)null);
    610                     } else {
    611                         handleDefaultSelection(devices[0]);
    612                     }
    613                 } else {
    614                     handleDefaultSelection((IDevice)null);
    615                 }
    616             }
    617         }
    618     }
    619 
    620     /**
    621      * Sent when a device data changed, or when clients are started/terminated on the device.
    622      * <p/>
    623      * This is sent from a non UI thread.
    624      * @param device the device that was updated.
    625      * @param changeMask the mask indicating what changed.
    626      *
    627      * @see IDeviceChangeListener#deviceChanged(IDevice)
    628      */
    629     public void deviceChanged(IDevice device, int changeMask) {
    630         // if we are listening to selection coming from the ui, then we do nothing, as
    631         // any change in the devices/clients, will be handled by the UI, and we'll receive
    632         // selection notification through our implementation of IUiSelectionListener.
    633         if (mListeningToUiSelection == false) {
    634 
    635             // check if this is our device
    636             if (device == mCurrentDevice) {
    637                 if (mCurrentClient == null) {
    638                     handleDefaultSelection(device);
    639                 } else {
    640                     // get the clients and make sure ours is still in there.
    641                     Client[] clients = device.getClients();
    642                     boolean foundClient = false;
    643                     for (Client client : clients) {
    644                         if (client == mCurrentClient) {
    645                             foundClient = true;
    646                             break;
    647                         }
    648                     }
    649 
    650                     // if we haven't found our client, lets look for a new one
    651                     if (foundClient == false) {
    652                         mCurrentClient = null;
    653                         handleDefaultSelection(device);
    654                     }
    655                 }
    656             }
    657         }
    658     }
    659 
    660     /**
    661      * Sent when a new {@link IDevice} and {@link Client} are selected.
    662      * @param selectedDevice the selected device. If null, no devices are selected.
    663      * @param selectedClient The selected client. If null, no clients are selected.
    664      */
    665     public synchronized void selectionChanged(IDevice selectedDevice, Client selectedClient) {
    666         if (mCurrentDevice != selectedDevice) {
    667             mCurrentDevice = selectedDevice;
    668 
    669             // notify of the new default device
    670             for (ISelectionListener listener : mListeners) {
    671                 listener.selectionChanged(mCurrentDevice);
    672             }
    673         }
    674 
    675         if (mCurrentClient != selectedClient) {
    676             mCurrentClient = selectedClient;
    677 
    678             // notify of the new default client
    679             for (ISelectionListener listener : mListeners) {
    680                 listener.selectionChanged(mCurrentClient);
    681             }
    682         }
    683     }
    684 
    685     /**
    686      * Handles a default selection of a {@link IDevice} and {@link Client}.
    687      * @param device the selected device
    688      */
    689     private void handleDefaultSelection(final IDevice device) {
    690         // because the listener expect to receive this from the UI thread, and this is called
    691         // from the AndroidDebugBridge notifications, we need to run this in the UI thread.
    692         try {
    693             Display display = getDisplay();
    694 
    695             display.asyncExec(new Runnable() {
    696                 public void run() {
    697                     // set the new device if different.
    698                     boolean newDevice = false;
    699                     if (mCurrentDevice != device) {
    700                         mCurrentDevice = device;
    701                         newDevice = true;
    702 
    703                         // notify of the new default device
    704                         for (ISelectionListener listener : mListeners) {
    705                             listener.selectionChanged(mCurrentDevice);
    706                         }
    707                     }
    708 
    709                     if (device != null) {
    710                         // if this is a device switch or the same device but we didn't find a valid
    711                         // client the last time, we go look for a client to use again.
    712                         if (newDevice || mCurrentClient == null) {
    713                             // now get the new client
    714                             Client[] clients =  device.getClients();
    715                             if (clients.length > 0) {
    716                                 handleDefaultSelection(clients[0]);
    717                             } else {
    718                                 handleDefaultSelection((Client)null);
    719                             }
    720                         }
    721                     } else {
    722                         handleDefaultSelection((Client)null);
    723                     }
    724                 }
    725             });
    726         } catch (SWTException e) {
    727             // display is disposed. Do nothing since we're quitting anyway.
    728         }
    729     }
    730 
    731     private void handleDefaultSelection(Client client) {
    732         mCurrentClient = client;
    733 
    734         // notify of the new default client
    735         for (ISelectionListener listener : mListeners) {
    736             listener.selectionChanged(mCurrentClient);
    737         }
    738     }
    739 
    740     /**
    741      * Prints a message, associated with a project to the specified stream
    742      * @param stream The stream to write to
    743      * @param tag The tag associated to the message. Can be null
    744      * @param message The message to print.
    745      */
    746     private static synchronized void printToStream(MessageConsoleStream stream, String tag,
    747             String message) {
    748         String dateTag = getMessageTag(tag);
    749 
    750         stream.print(dateTag);
    751         if (!dateTag.endsWith(" ")) {
    752             stream.print(" ");          //$NON-NLS-1$
    753         }
    754         stream.println(message);
    755     }
    756 
    757     /**
    758      * Creates a string containing the current date/time, and the tag
    759      * @param tag The tag associated to the message. Can be null
    760      * @return The dateTag
    761      */
    762     private static String getMessageTag(String tag) {
    763         Calendar c = Calendar.getInstance();
    764 
    765         if (tag == null) {
    766             return String.format(Messages.DdmsPlugin_Message_Tag_Mask_1, c);
    767         }
    768 
    769         return String.format(Messages.DdmsPlugin_Message_Tag_Mask_2, c, tag);
    770     }
    771 
    772     /**
    773      * Implementation of com.android.ddmuilib.StackTracePanel.ISourceRevealer.
    774      */
    775     public void reveal(String applicationName, String className, int line) {
    776         // loop on all source revealer till one succeeds
    777         if (mSourceRevealers != null) {
    778             for (ISourceRevealer revealer : mSourceRevealers) {
    779                 try {
    780                     if (revealer.reveal(applicationName, className, line)) {
    781                         break;
    782                     }
    783                 } catch (Throwable t) {
    784                     // ignore, we'll just not use this implementation.
    785                 }
    786             }
    787         }
    788     }
    789 
    790     public boolean launchTraceview(String osPath) {
    791         if (mTraceviewLaunchers != null) {
    792             for (ITraceviewLauncher launcher : mTraceviewLaunchers) {
    793                 try {
    794                     if (launcher.openFile(osPath)) {
    795                         return true;
    796                     }
    797                 } catch (Throwable t) {
    798                     // ignore, we'll just not use this implementation.
    799                 }
    800             }
    801         }
    802 
    803         return false;
    804     }
    805 
    806     private LogCatMonitor mLogCatMonitor;
    807     public void startLogCatMonitor(IDevice device) {
    808         if (mLogCatMonitor == null) {
    809             mLogCatMonitor = new LogCatMonitor(getDebuggerConnectors(), getPreferenceStore());
    810         }
    811 
    812         mLogCatMonitor.monitorDevice(device);
    813     }
    814 }
    815