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