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 String getToolsFolder() {
    494         return sToolsFolder;
    495     }
    496 
    497     public static String getHprofConverter() {
    498         return sHprofConverter;
    499     }
    500 
    501     /**
    502      * Stores the adb location. This returns true if the location is an existing file.
    503      */
    504     private static boolean setToolsLocation(String adbLocation, String hprofConvLocation,
    505             String traceViewLocation) {
    506 
    507         File adb = new File(adbLocation);
    508         File hprofConverter = new File(hprofConvLocation);
    509         File traceview = new File(traceViewLocation);
    510 
    511         String missing = "";
    512         if (adb.isFile() == false) {
    513             missing += adb.getAbsolutePath() + " ";
    514         }
    515         if (hprofConverter.isFile() == false) {
    516             missing += hprofConverter.getAbsolutePath() + " ";
    517         }
    518         if (traceview.isFile() == false) {
    519             missing += traceview.getAbsolutePath() + " ";
    520         }
    521 
    522         if (missing.length() > 0) {
    523             String msg = String.format("DDMS files not found: %1$s", missing);
    524             Log.e("DDMS", msg);
    525             Status status = new Status(IStatus.ERROR, PLUGIN_ID, msg, null /*exception*/);
    526             getDefault().getLog().log(status);
    527             return false;
    528         }
    529 
    530         sAdbLocation = adbLocation;
    531         sHprofConverter = hprofConverter.getAbsolutePath();
    532         DdmUiPreferences.setTraceviewLocation(traceview.getAbsolutePath());
    533 
    534         sToolsFolder = traceview.getParent();
    535 
    536         return true;
    537     }
    538 
    539     /**
    540      * Set the location of the adb executable and optionally starts adb
    541      * @param adb location of adb
    542      * @param startAdb flag to start adb
    543      */
    544     public static void setToolsLocation(String adbLocation, boolean startAdb,
    545             String hprofConvLocation, String traceViewLocation) {
    546 
    547         if (setToolsLocation(adbLocation, hprofConvLocation, traceViewLocation)) {
    548             // starts the server in a thread in case this is blocking.
    549             if (startAdb) {
    550                 new Thread() {
    551                     @Override
    552                     public void run() {
    553                         // create and start the bridge
    554                         try {
    555                             AndroidDebugBridge.createBridge(sAdbLocation,
    556                                     false /* forceNewBridge */);
    557                         } catch (Throwable t) {
    558                             Status status = new Status(IStatus.ERROR, PLUGIN_ID,
    559                                     "Failed to create AndroidDebugBridge", t);
    560                             getDefault().getLog().log(status);
    561                         }
    562                     }
    563                 }.start();
    564             }
    565         }
    566     }
    567 
    568     /**
    569      * Returns whether there are implementations of the debuggerConnectors extension point.
    570      * <p/>
    571      * This is guaranteed to return the correct value as soon as the plug-in is loaded.
    572      */
    573     public boolean hasDebuggerConnectors() {
    574         return mHasDebuggerConnectors;
    575     }
    576 
    577     /**
    578      * Returns the implementations of {@link IDebuggerConnector}.
    579      * <p/>
    580      * There may be a small amount of time right after the plug-in load where this can return
    581      * null even if there are implementation.
    582      * <p/>
    583      * Since the use of the implementation likely require user input, the UI can use
    584      * {@link #hasDebuggerConnectors()} to know if there are implementations before they are loaded.
    585      */
    586     public IDebuggerConnector[] getDebuggerConnectors() {
    587         return mDebuggerConnectors;
    588     }
    589 
    590     public synchronized void addSelectionListener(ISelectionListener listener) {
    591         mListeners.add(listener);
    592 
    593         // notify the new listener of the current selection
    594        listener.selectionChanged(mCurrentDevice);
    595        listener.selectionChanged(mCurrentClient);
    596     }
    597 
    598     public synchronized void removeSelectionListener(ISelectionListener listener) {
    599         mListeners.remove(listener);
    600     }
    601 
    602     public synchronized void setListeningState(boolean state) {
    603         mListeningToUiSelection = state;
    604     }
    605 
    606     /**
    607      * Sent when the a device is connected to the {@link AndroidDebugBridge}.
    608      * <p/>
    609      * This is sent from a non UI thread.
    610      * @param device the new device.
    611      *
    612      * @see IDeviceChangeListener#deviceConnected(IDevice)
    613      */
    614     @Override
    615     public void deviceConnected(IDevice device) {
    616         // if we are listening to selection coming from the ui, then we do nothing, as
    617         // any change in the devices/clients, will be handled by the UI, and we'll receive
    618         // selection notification through our implementation of IUiSelectionListener.
    619         if (mListeningToUiSelection == false) {
    620             if (mCurrentDevice == null) {
    621                 handleDefaultSelection(device);
    622             }
    623         }
    624     }
    625 
    626     /**
    627      * Sent when the a device is disconnected to the {@link AndroidDebugBridge}.
    628      * <p/>
    629      * This is sent from a non UI thread.
    630      * @param device the new device.
    631      *
    632      * @see IDeviceChangeListener#deviceDisconnected(IDevice)
    633      */
    634     @Override
    635     public void deviceDisconnected(IDevice device) {
    636         // if we are listening to selection coming from the ui, then we do nothing, as
    637         // any change in the devices/clients, will be handled by the UI, and we'll receive
    638         // selection notification through our implementation of IUiSelectionListener.
    639         if (mListeningToUiSelection == false) {
    640             // test if the disconnected device was the default selection.
    641             if (mCurrentDevice == device) {
    642                 // try to find a new device
    643                 AndroidDebugBridge bridge = AndroidDebugBridge.getBridge();
    644                 if (bridge != null) {
    645                     // get the device list
    646                     IDevice[] devices = bridge.getDevices();
    647 
    648                     // check if we still have devices
    649                     if (devices.length == 0) {
    650                         handleDefaultSelection((IDevice)null);
    651                     } else {
    652                         handleDefaultSelection(devices[0]);
    653                     }
    654                 } else {
    655                     handleDefaultSelection((IDevice)null);
    656                 }
    657             }
    658         }
    659     }
    660 
    661     /**
    662      * Sent when a device data changed, or when clients are started/terminated on the device.
    663      * <p/>
    664      * This is sent from a non UI thread.
    665      * @param device the device that was updated.
    666      * @param changeMask the mask indicating what changed.
    667      *
    668      * @see IDeviceChangeListener#deviceChanged(IDevice)
    669      */
    670     @Override
    671     public void deviceChanged(IDevice device, int changeMask) {
    672         // if we are listening to selection coming from the ui, then we do nothing, as
    673         // any change in the devices/clients, will be handled by the UI, and we'll receive
    674         // selection notification through our implementation of IUiSelectionListener.
    675         if (mListeningToUiSelection == false) {
    676 
    677             // check if this is our device
    678             if (device == mCurrentDevice) {
    679                 if (mCurrentClient == null) {
    680                     handleDefaultSelection(device);
    681                 } else {
    682                     // get the clients and make sure ours is still in there.
    683                     Client[] clients = device.getClients();
    684                     boolean foundClient = false;
    685                     for (Client client : clients) {
    686                         if (client == mCurrentClient) {
    687                             foundClient = true;
    688                             break;
    689                         }
    690                     }
    691 
    692                     // if we haven't found our client, lets look for a new one
    693                     if (foundClient == false) {
    694                         mCurrentClient = null;
    695                         handleDefaultSelection(device);
    696                     }
    697                 }
    698             }
    699         }
    700     }
    701 
    702     /**
    703      * Sent when a new {@link IDevice} and {@link Client} are selected.
    704      * @param selectedDevice the selected device. If null, no devices are selected.
    705      * @param selectedClient The selected client. If null, no clients are selected.
    706      */
    707     @Override
    708     public synchronized void selectionChanged(IDevice selectedDevice, Client selectedClient) {
    709         if (mCurrentDevice != selectedDevice) {
    710             mCurrentDevice = selectedDevice;
    711 
    712             // notify of the new default device
    713             for (ISelectionListener listener : mListeners) {
    714                 listener.selectionChanged(mCurrentDevice);
    715             }
    716         }
    717 
    718         if (mCurrentClient != selectedClient) {
    719             mCurrentClient = selectedClient;
    720 
    721             // notify of the new default client
    722             for (ISelectionListener listener : mListeners) {
    723                 listener.selectionChanged(mCurrentClient);
    724             }
    725         }
    726     }
    727 
    728     /**
    729      * Handles a default selection of a {@link IDevice} and {@link Client}.
    730      * @param device the selected device
    731      */
    732     private void handleDefaultSelection(final IDevice device) {
    733         // because the listener expect to receive this from the UI thread, and this is called
    734         // from the AndroidDebugBridge notifications, we need to run this in the UI thread.
    735         try {
    736             Display display = getDisplay();
    737 
    738             display.asyncExec(new Runnable() {
    739                 @Override
    740                 public void run() {
    741                     // set the new device if different.
    742                     boolean newDevice = false;
    743                     if (mCurrentDevice != device) {
    744                         mCurrentDevice = device;
    745                         newDevice = true;
    746 
    747                         // notify of the new default device
    748                         for (ISelectionListener listener : mListeners) {
    749                             listener.selectionChanged(mCurrentDevice);
    750                         }
    751                     }
    752 
    753                     if (device != null) {
    754                         // if this is a device switch or the same device but we didn't find a valid
    755                         // client the last time, we go look for a client to use again.
    756                         if (newDevice || mCurrentClient == null) {
    757                             // now get the new client
    758                             Client[] clients =  device.getClients();
    759                             if (clients.length > 0) {
    760                                 handleDefaultSelection(clients[0]);
    761                             } else {
    762                                 handleDefaultSelection((Client)null);
    763                             }
    764                         }
    765                     } else {
    766                         handleDefaultSelection((Client)null);
    767                     }
    768                 }
    769             });
    770         } catch (SWTException e) {
    771             // display is disposed. Do nothing since we're quitting anyway.
    772         }
    773     }
    774 
    775     private void handleDefaultSelection(Client client) {
    776         mCurrentClient = client;
    777 
    778         // notify of the new default client
    779         for (ISelectionListener listener : mListeners) {
    780             listener.selectionChanged(mCurrentClient);
    781         }
    782     }
    783 
    784     /**
    785      * Prints a message, associated with a project to the specified stream
    786      * @param stream The stream to write to
    787      * @param tag The tag associated to the message. Can be null
    788      * @param message The message to print.
    789      */
    790     private static synchronized void printToStream(MessageConsoleStream stream, String tag,
    791             String message) {
    792         String dateTag = getMessageTag(tag);
    793 
    794         stream.print(dateTag);
    795         if (!dateTag.endsWith(" ")) {
    796             stream.print(" ");          //$NON-NLS-1$
    797         }
    798         stream.println(message);
    799     }
    800 
    801     /**
    802      * Creates a string containing the current date/time, and the tag
    803      * @param tag The tag associated to the message. Can be null
    804      * @return The dateTag
    805      */
    806     private static String getMessageTag(String tag) {
    807         Calendar c = Calendar.getInstance();
    808 
    809         if (tag == null) {
    810             return String.format(Messages.DdmsPlugin_Message_Tag_Mask_1, c);
    811         }
    812 
    813         return String.format(Messages.DdmsPlugin_Message_Tag_Mask_2, c, tag);
    814     }
    815 
    816     /**
    817      * Implementation of com.android.ddmuilib.StackTracePanel.ISourceRevealer.
    818      */
    819     @Override
    820     public void reveal(String applicationName, String className, int line) {
    821         JavaSourceRevealer.reveal(applicationName, className, line);
    822     }
    823 
    824     public boolean launchTraceview(String osPath) {
    825         if (mTraceviewLaunchers != null) {
    826             for (ITraceviewLauncher launcher : mTraceviewLaunchers) {
    827                 try {
    828                     if (launcher.openFile(osPath)) {
    829                         return true;
    830                     }
    831                 } catch (Throwable t) {
    832                     // ignore, we'll just not use this implementation.
    833                 }
    834             }
    835         }
    836 
    837         return false;
    838     }
    839 
    840     /**
    841      * Returns the list of clients that extend the clientAction extension point.
    842      */
    843     @NonNull
    844     public synchronized List<IClientAction> getClientSpecificActions() {
    845         if (mClientSpecificActions == null) {
    846             // get available client specific action extensions
    847             IConfigurationElement[] elements =
    848                     findConfigElements("com.android.ide.eclipse.ddms.clientAction"); //$NON-NLS-1$
    849             try {
    850                 mClientSpecificActions = instantiateClientSpecificActions(elements);
    851             } catch (CoreException e) {
    852                 mClientSpecificActions = Collections.emptyList();
    853             }
    854         }
    855 
    856         return mClientSpecificActions;
    857     }
    858 
    859     private LogCatMonitor mLogCatMonitor;
    860     public void startLogCatMonitor(IDevice device) {
    861         if (mLogCatMonitor == null) {
    862             mLogCatMonitor = new LogCatMonitor(getDebuggerConnectors(), getPreferenceStore());
    863         }
    864 
    865         mLogCatMonitor.monitorDevice(device);
    866     }
    867 
    868     /** Returns an image descriptor for the image file at the given plug-in relative path */
    869     public static ImageDescriptor getImageDescriptor(String path) {
    870         return imageDescriptorFromPlugin(PLUGIN_ID, path);
    871     }
    872 }
    873