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