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