• 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.Client;
21 import com.android.ddmlib.DdmConstants;
22 import com.android.ddmlib.DdmPreferences;
23 import com.android.ddmlib.IDevice;
24 import com.android.ddmlib.Log;
25 import com.android.ddmlib.AndroidDebugBridge.IDeviceChangeListener;
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.ide.eclipse.ddms.preferences.PreferenceInitializer;
31 import com.android.ide.eclipse.ddms.views.DeviceView;
32 
33 import org.eclipse.core.runtime.Preferences;
34 import org.eclipse.core.runtime.Preferences.IPropertyChangeListener;
35 import org.eclipse.core.runtime.Preferences.PropertyChangeEvent;
36 import org.eclipse.jface.dialogs.MessageDialog;
37 import org.eclipse.jface.preference.IPreferenceStore;
38 import org.eclipse.swt.SWTException;
39 import org.eclipse.swt.graphics.Color;
40 import org.eclipse.swt.widgets.Display;
41 import org.eclipse.swt.widgets.Shell;
42 import org.eclipse.ui.IWorkbench;
43 import org.eclipse.ui.console.ConsolePlugin;
44 import org.eclipse.ui.console.IConsole;
45 import org.eclipse.ui.console.MessageConsole;
46 import org.eclipse.ui.console.MessageConsoleStream;
47 import org.eclipse.ui.plugin.AbstractUIPlugin;
48 import org.osgi.framework.BundleContext;
49 
50 import java.io.File;
51 import java.util.ArrayList;
52 import java.util.Calendar;
53 
54 /**
55  * The activator class controls the plug-in life cycle
56  */
57 public final class DdmsPlugin extends AbstractUIPlugin implements IDeviceChangeListener,
58         IUiSelectionListener {
59 
60 
61     // The plug-in ID
62     public static final String PLUGIN_ID = "com.android.ide.eclipse.ddms"; // $NON-NLS-1$
63 
64     private static final String ADB_LOCATION = PLUGIN_ID + ".adb"; // $NON-NLS-1$
65 
66     /** The singleton instance */
67     private static DdmsPlugin sPlugin;
68 
69     /** Location of the adb command line executable */
70     private static String sAdbLocation;
71     private static String sToolsFolder;
72     private static String sHprofConverter;
73 
74     /**
75      * Debug Launcher for already running apps
76      */
77     private static IDebugLauncher sRunningAppDebugLauncher;
78 
79 
80     /** Console for DDMS log message */
81     private MessageConsole mDdmsConsole;
82 
83     private IDevice mCurrentDevice;
84     private Client mCurrentClient;
85     private boolean mListeningToUiSelection = false;
86 
87     private final ArrayList<ISelectionListener> mListeners = new ArrayList<ISelectionListener>();
88 
89     private Color mRed;
90 
91     private boolean mDdmlibInitialized;
92 
93     /**
94      * Interface to provide debugger launcher for running apps.
95      */
96     public interface IDebugLauncher {
debug(String packageName, int port)97         public boolean debug(String packageName, int port);
98     }
99 
100     /**
101      * Classes which implement this interface provide methods that deals
102      * with {@link IDevice} and {@link Client} selectionchanges.
103      */
104     public interface ISelectionListener {
105 
106         /**
107          * Sent when a new {@link Client} is selected.
108          * @param selectedClient The selected client. If null, no clients are selected.
109          */
selectionChanged(Client selectedClient)110         public void selectionChanged(Client selectedClient);
111 
112         /**
113          * Sent when a new {@link IDevice} is selected.
114          * @param selectedDevice the selected device. If null, no devices are selected.
115          */
selectionChanged(IDevice selectedDevice)116         public void selectionChanged(IDevice selectedDevice);
117     }
118 
119     /**
120      * The constructor
121      */
DdmsPlugin()122     public DdmsPlugin() {
123         sPlugin = this;
124     }
125 
126     /*
127      * (non-Javadoc)
128      *
129      * @see org.eclipse.ui.plugin.AbstractUIPlugin#start(org.osgi.framework.BundleContext)
130      */
131     @Override
start(BundleContext context)132     public void start(BundleContext context) throws Exception {
133         super.start(context);
134 
135         final Display display = getDisplay();
136 
137         // get the eclipse store
138         final IPreferenceStore eclipseStore = getPreferenceStore();
139 
140         AndroidDebugBridge.addDeviceChangeListener(this);
141 
142         DdmUiPreferences.setStore(eclipseStore);
143 
144         //DdmUiPreferences.displayCharts();
145 
146         // set the consoles.
147         mDdmsConsole = new MessageConsole("DDMS", null); // $NON-NLS-1$
148         ConsolePlugin.getDefault().getConsoleManager().addConsoles(
149                 new IConsole[] {
150                     mDdmsConsole
151                 });
152 
153         final MessageConsoleStream consoleStream = mDdmsConsole.newMessageStream();
154         final MessageConsoleStream errorConsoleStream = mDdmsConsole.newMessageStream();
155         mRed = new Color(display, 0xFF, 0x00, 0x00);
156 
157         // because this can be run, in some cases, by a non UI thread, and because
158         // changing the console properties update the UI, we need to make this change
159         // in the UI thread.
160         display.asyncExec(new Runnable() {
161             public void run() {
162                 errorConsoleStream.setColor(mRed);
163             }
164         });
165 
166         // set up the ddms log to use the ddms console.
167         Log.setLogOutput(new ILogOutput() {
168             public void printLog(LogLevel logLevel, String tag, String message) {
169                 if (logLevel.getPriority() >= LogLevel.ERROR.getPriority()) {
170                     printToStream(errorConsoleStream, tag, message);
171                     ConsolePlugin.getDefault().getConsoleManager().showConsoleView(mDdmsConsole);
172                 } else {
173                     printToStream(consoleStream, tag, message);
174                 }
175             }
176 
177             public void printAndPromptLog(final LogLevel logLevel, final String tag,
178                     final String message) {
179                 printLog(logLevel, tag, message);
180                 // dialog box only run in UI thread..
181                 display.asyncExec(new Runnable() {
182                     public void run() {
183                         Shell shell = display.getActiveShell();
184                         if (logLevel == LogLevel.ERROR) {
185                             MessageDialog.openError(shell, tag, message);
186                         } else {
187                             MessageDialog.openWarning(shell, tag, message);
188                         }
189                     }
190                 });
191             }
192 
193         });
194 
195         // set the listener for the preference change
196         Preferences prefs = getPluginPreferences();
197         prefs.addPropertyChangeListener(new IPropertyChangeListener() {
198             public void propertyChange(PropertyChangeEvent event) {
199                 // get the name of the property that changed.
200                 String property = event.getProperty();
201 
202                 if (PreferenceInitializer.ATTR_DEBUG_PORT_BASE.equals(property)) {
203                     DdmPreferences.setDebugPortBase(
204                             eclipseStore.getInt(PreferenceInitializer.ATTR_DEBUG_PORT_BASE));
205                 } else if (PreferenceInitializer.ATTR_SELECTED_DEBUG_PORT.equals(property)) {
206                     DdmPreferences.setSelectedDebugPort(
207                             eclipseStore.getInt(PreferenceInitializer.ATTR_SELECTED_DEBUG_PORT));
208                 } else if (PreferenceInitializer.ATTR_THREAD_INTERVAL.equals(property)) {
209                     DdmUiPreferences.setThreadRefreshInterval(
210                             eclipseStore.getInt(PreferenceInitializer.ATTR_THREAD_INTERVAL));
211                 } else if (PreferenceInitializer.ATTR_LOG_LEVEL.equals(property)) {
212                     DdmPreferences.setLogLevel(
213                             eclipseStore.getString(PreferenceInitializer.ATTR_LOG_LEVEL));
214                 } else if (PreferenceInitializer.ATTR_TIME_OUT.equals(property)) {
215                     DdmPreferences.setTimeOut(
216                             eclipseStore.getInt(PreferenceInitializer.ATTR_TIME_OUT));
217                 }
218             }
219         });
220 
221         // read the adb location from the prefs to attempt to start it properly without
222         // having to wait for ADT to start
223         final boolean adbValid = setAdbLocation(eclipseStore.getString(ADB_LOCATION));
224 
225         // start it in a thread to return from start() asap.
226         new Thread() {
227             @Override
228             public void run() {
229                 // init ddmlib if needed
230                 getDefault().initDdmlib();
231 
232                 // create and start the first bridge
233                 if (adbValid) {
234                     AndroidDebugBridge.createBridge(sAdbLocation, true /* forceNewBridge */);
235                 }
236             }
237         }.start();
238     }
239 
getDisplay()240     public static Display getDisplay() {
241         IWorkbench bench = sPlugin.getWorkbench();
242         if (bench != null) {
243             return bench.getDisplay();
244         }
245         return null;
246     }
247 
248     /*
249      * (non-Javadoc)
250      *
251      * @see org.eclipse.ui.plugin.AbstractUIPlugin#stop(org.osgi.framework.BundleContext)
252      */
253     @Override
stop(BundleContext context)254     public void stop(BundleContext context) throws Exception {
255         AndroidDebugBridge.removeDeviceChangeListener(this);
256 
257         AndroidDebugBridge.terminate();
258 
259         mRed.dispose();
260 
261         sPlugin = null;
262         super.stop(context);
263     }
264 
265     /**
266      * Returns the shared instance
267      *
268      * @return the shared instance
269      */
getDefault()270     public static DdmsPlugin getDefault() {
271         return sPlugin;
272     }
273 
getAdb()274     public static String getAdb() {
275         return sAdbLocation;
276     }
277 
getToolsFolder()278     public static String getToolsFolder() {
279         return sToolsFolder;
280     }
281 
getHprofConverter()282     public static String getHprofConverter() {
283         return sHprofConverter;
284     }
285 
286     /**
287      * Stores the adb location. This returns true if the location is an existing file.
288      */
setAdbLocation(String adbLocation)289     private static boolean setAdbLocation(String adbLocation) {
290         File adb = new File(adbLocation);
291         if (adb.isFile()) {
292             sAdbLocation = adbLocation;
293 
294             File toolsFolder = adb.getParentFile();
295             sToolsFolder = toolsFolder.getAbsolutePath();
296 
297             File hprofConverter = new File(toolsFolder, DdmConstants.FN_HPROF_CONVERTER);
298             sHprofConverter = hprofConverter.getAbsolutePath();
299 
300             File traceview = new File(toolsFolder, DdmConstants.FN_TRACEVIEW);
301             DdmUiPreferences.setTraceviewLocation(traceview.getAbsolutePath());
302 
303             return true;
304         }
305 
306         return false;
307     }
308 
309     /**
310      * Set the location of the adb executable and optionally starts adb
311      * @param adb location of adb
312      * @param startAdb flag to start adb
313      */
setAdb(String adb, boolean startAdb)314     public static void setAdb(String adb, boolean startAdb) {
315         if (adb != null) {
316             if (setAdbLocation(adb)) {
317                 // store the location for future ddms only start.
318                 sPlugin.getPreferenceStore().setValue(ADB_LOCATION, sAdbLocation);
319 
320                 // starts the server in a thread in case this is blocking.
321                 if (startAdb) {
322                     new Thread() {
323                         @Override
324                         public void run() {
325                             // init ddmlib if needed
326                             getDefault().initDdmlib();
327 
328                             // create and start the bridge
329                             AndroidDebugBridge.createBridge(sAdbLocation,
330                                     false /* forceNewBridge */);
331                         }
332                     }.start();
333                 }
334             }
335         }
336     }
337 
initDdmlib()338     private synchronized void initDdmlib() {
339         if (mDdmlibInitialized == false) {
340             // set the preferences.
341             PreferenceInitializer.setupPreferences();
342 
343             // init the lib
344             AndroidDebugBridge.init(true /* debugger support */);
345 
346             mDdmlibInitialized = true;
347         }
348     }
349 
350     /**
351      * Sets the launcher responsible for connecting the debugger to running applications.
352      * @param launcher The launcher.
353      */
setRunningAppDebugLauncher(IDebugLauncher launcher)354     public static void setRunningAppDebugLauncher(IDebugLauncher launcher) {
355         sRunningAppDebugLauncher = launcher;
356 
357         // if the process view is already running, give it the launcher.
358         // This method could be called from a non ui thread, so we make sure to do that
359         // in the ui thread.
360         Display display = getDisplay();
361         if (display != null && display.isDisposed() == false) {
362             display.asyncExec(new Runnable() {
363                 public void run() {
364                     DeviceView dv = DeviceView.getInstance();
365                     if (dv != null) {
366                         dv.setDebugLauncher(sRunningAppDebugLauncher);
367                     }
368                 }
369             });
370         }
371     }
372 
getRunningAppDebugLauncher()373     public static IDebugLauncher getRunningAppDebugLauncher() {
374         return sRunningAppDebugLauncher;
375     }
376 
addSelectionListener(ISelectionListener listener)377     public synchronized void addSelectionListener(ISelectionListener listener) {
378         mListeners.add(listener);
379 
380         // notify the new listener of the current selection
381        listener.selectionChanged(mCurrentDevice);
382        listener.selectionChanged(mCurrentClient);
383     }
384 
removeSelectionListener(ISelectionListener listener)385     public synchronized void removeSelectionListener(ISelectionListener listener) {
386         mListeners.remove(listener);
387     }
388 
setListeningState(boolean state)389     public synchronized void setListeningState(boolean state) {
390         mListeningToUiSelection = state;
391     }
392 
393     /**
394      * Sent when the a device is connected to the {@link AndroidDebugBridge}.
395      * <p/>
396      * This is sent from a non UI thread.
397      * @param device the new device.
398      *
399      * @see IDeviceChangeListener#deviceConnected(IDevice)
400      */
deviceConnected(IDevice device)401     public void deviceConnected(IDevice device) {
402         // if we are listening to selection coming from the ui, then we do nothing, as
403         // any change in the devices/clients, will be handled by the UI, and we'll receive
404         // selection notification through our implementation of IUiSelectionListener.
405         if (mListeningToUiSelection == false) {
406             if (mCurrentDevice == null) {
407                 handleDefaultSelection(device);
408             }
409         }
410     }
411 
412     /**
413      * Sent when the a device is disconnected to the {@link AndroidDebugBridge}.
414      * <p/>
415      * This is sent from a non UI thread.
416      * @param device the new device.
417      *
418      * @see IDeviceChangeListener#deviceDisconnected(IDevice)
419      */
deviceDisconnected(IDevice device)420     public void deviceDisconnected(IDevice device) {
421         // if we are listening to selection coming from the ui, then we do nothing, as
422         // any change in the devices/clients, will be handled by the UI, and we'll receive
423         // selection notification through our implementation of IUiSelectionListener.
424         if (mListeningToUiSelection == false) {
425             // test if the disconnected device was the default selection.
426             if (mCurrentDevice == device) {
427                 // try to find a new device
428                 AndroidDebugBridge bridge = AndroidDebugBridge.getBridge();
429                 if (bridge != null) {
430                     // get the device list
431                     IDevice[] devices = bridge.getDevices();
432 
433                     // check if we still have devices
434                     if (devices.length == 0) {
435                         handleDefaultSelection((IDevice)null);
436                     } else {
437                         handleDefaultSelection(devices[0]);
438                     }
439                 } else {
440                     handleDefaultSelection((IDevice)null);
441                 }
442             }
443         }
444     }
445 
446     /**
447      * Sent when a device data changed, or when clients are started/terminated on the device.
448      * <p/>
449      * This is sent from a non UI thread.
450      * @param device the device that was updated.
451      * @param changeMask the mask indicating what changed.
452      *
453      * @see IDeviceChangeListener#deviceChanged(IDevice)
454      */
deviceChanged(IDevice device, int changeMask)455     public void deviceChanged(IDevice device, int changeMask) {
456         // if we are listening to selection coming from the ui, then we do nothing, as
457         // any change in the devices/clients, will be handled by the UI, and we'll receive
458         // selection notification through our implementation of IUiSelectionListener.
459         if (mListeningToUiSelection == false) {
460 
461             // check if this is our device
462             if (device == mCurrentDevice) {
463                 if (mCurrentClient == null) {
464                     handleDefaultSelection(device);
465                 } else {
466                     // get the clients and make sure ours is still in there.
467                     Client[] clients = device.getClients();
468                     boolean foundClient = false;
469                     for (Client client : clients) {
470                         if (client == mCurrentClient) {
471                             foundClient = true;
472                             break;
473                         }
474                     }
475 
476                     // if we haven't found our client, lets look for a new one
477                     if (foundClient == false) {
478                         mCurrentClient = null;
479                         handleDefaultSelection(device);
480                     }
481                 }
482             }
483         }
484     }
485 
486     /**
487      * Sent when a new {@link IDevice} and {@link Client} are selected.
488      * @param selectedDevice the selected device. If null, no devices are selected.
489      * @param selectedClient The selected client. If null, no clients are selected.
490      */
selectionChanged(IDevice selectedDevice, Client selectedClient)491     public synchronized void selectionChanged(IDevice selectedDevice, Client selectedClient) {
492         if (mCurrentDevice != selectedDevice) {
493             mCurrentDevice = selectedDevice;
494 
495             // notify of the new default device
496             for (ISelectionListener listener : mListeners) {
497                 listener.selectionChanged(mCurrentDevice);
498             }
499         }
500 
501         if (mCurrentClient != selectedClient) {
502             mCurrentClient = selectedClient;
503 
504             // notify of the new default client
505             for (ISelectionListener listener : mListeners) {
506                 listener.selectionChanged(mCurrentClient);
507             }
508         }
509     }
510 
511     /**
512      * Handles a default selection of a {@link IDevice} and {@link Client}.
513      * @param device the selected device
514      */
handleDefaultSelection(final IDevice device)515     private void handleDefaultSelection(final IDevice device) {
516         // because the listener expect to receive this from the UI thread, and this is called
517         // from the AndroidDebugBridge notifications, we need to run this in the UI thread.
518         try {
519             Display display = getDisplay();
520 
521             display.asyncExec(new Runnable() {
522                 public void run() {
523                     // set the new device if different.
524                     boolean newDevice = false;
525                     if (mCurrentDevice != device) {
526                         mCurrentDevice = device;
527                         newDevice = true;
528 
529                         // notify of the new default device
530                         for (ISelectionListener listener : mListeners) {
531                             listener.selectionChanged(mCurrentDevice);
532                         }
533                     }
534 
535                     if (device != null) {
536                         // if this is a device switch or the same device but we didn't find a valid
537                         // client the last time, we go look for a client to use again.
538                         if (newDevice || mCurrentClient == null) {
539                             // now get the new client
540                             Client[] clients =  device.getClients();
541                             if (clients.length > 0) {
542                                 handleDefaultSelection(clients[0]);
543                             } else {
544                                 handleDefaultSelection((Client)null);
545                             }
546                         }
547                     } else {
548                         handleDefaultSelection((Client)null);
549                     }
550                 }
551             });
552         } catch (SWTException e) {
553             // display is disposed. Do nothing since we're quitting anyway.
554         }
555     }
556 
handleDefaultSelection(Client client)557     private void handleDefaultSelection(Client client) {
558         mCurrentClient = client;
559 
560         // notify of the new default client
561         for (ISelectionListener listener : mListeners) {
562             listener.selectionChanged(mCurrentClient);
563         }
564     }
565 
566     /**
567      * Prints a message, associated with a project to the specified stream
568      * @param stream The stream to write to
569      * @param tag The tag associated to the message. Can be null
570      * @param message The message to print.
571      */
printToStream(MessageConsoleStream stream, String tag, String message)572     private static synchronized void printToStream(MessageConsoleStream stream, String tag,
573             String message) {
574         String dateTag = getMessageTag(tag);
575 
576         stream.print(dateTag);
577         stream.println(message);
578     }
579 
580     /**
581      * Creates a string containing the current date/time, and the tag
582      * @param tag The tag associated to the message. Can be null
583      * @return The dateTag
584      */
getMessageTag(String tag)585     private static String getMessageTag(String tag) {
586         Calendar c = Calendar.getInstance();
587 
588         if (tag == null) {
589             return String.format("[%1$tF %1$tT]", c);
590         }
591 
592         return String.format("[%1$tF %1$tT - %2$s]", c, tag);
593     }
594 }
595