• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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          */
selectionChanged(Client selectedClient)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          */
selectionChanged(IDevice selectedDevice)118         public void selectionChanged(IDevice selectedDevice);
119     }
120 
121     /**
122      * The constructor
123      */
DdmsPlugin()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
start(BundleContext context)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 
showConsoleView(MessageConsole console)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. */
findConfigElements(String extensionPointId)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      */
instantiateToolsLocators(IConfigurationElement[] configElements)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      */
instantiateDebuggerConnectors( IConfigurationElement[] configElements)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      */
instantiateTraceviewLauncher( IConfigurationElement[] configElements)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 
getDisplay()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
stop(BundleContext context)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      */
getDefault()458     public static DdmsPlugin getDefault() {
459         return sPlugin;
460     }
461 
getAdb()462     public static String getAdb() {
463         return sAdbLocation;
464     }
465 
getToolsFolder2()466     public static String getToolsFolder2() {
467         return sToolsFolder;
468     }
469 
getHprofConverter()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      */
setToolsLocation(String adbLocation, String hprofConvLocation, String traceViewLocation)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      */
setToolsLocation(String adbLocation, boolean startAdb, String hprofConvLocation, String traceViewLocation)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      */
hasDebuggerConnectors()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      */
getDebuggerConnectors()557     public IDebuggerConnector[] getDebuggerConnectors() {
558         return mDebuggerConnectors;
559     }
560 
addSelectionListener(ISelectionListener listener)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 
removeSelectionListener(ISelectionListener listener)569     public synchronized void removeSelectionListener(ISelectionListener listener) {
570         mListeners.remove(listener);
571     }
572 
setListeningState(boolean state)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
deviceConnected(IDevice device)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
deviceDisconnected(IDevice device)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
deviceChanged(IDevice device, int changeMask)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
selectionChanged(IDevice selectedDevice, Client selectedClient)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      */
handleDefaultSelection(final IDevice device)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 
handleDefaultSelection(Client client)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      */
printToStream(MessageConsoleStream stream, String tag, String message)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      */
getMessageTag(String tag)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
reveal(String applicationName, String className, int line)791     public void reveal(String applicationName, String className, int line) {
792         JavaSourceRevealer.reveal(applicationName, className, line);
793     }
794 
launchTraceview(String osPath)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;
startLogCatMonitor(IDevice device)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