OSDN Git Service

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