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