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