2 * Copyright (C) 2007 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 package com.android.ide.eclipse.ddms;
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;
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;
51 import java.util.ArrayList;
52 import java.util.Calendar;
55 * The activator class controls the plug-in life cycle
57 public final class DdmsPlugin extends AbstractUIPlugin implements IDeviceChangeListener,
58 IUiSelectionListener {
62 public static final String PLUGIN_ID = "com.android.ide.eclipse.ddms"; // $NON-NLS-1$
64 private static final String ADB_LOCATION = PLUGIN_ID + ".adb"; // $NON-NLS-1$
66 /** The singleton instance */
67 private static DdmsPlugin sPlugin;
69 /** Location of the adb command line executable */
70 private static String sAdbLocation;
71 private static String sToolsFolder;
72 private static String sHprofConverter;
75 * Debug Launcher for already running apps
77 private static IDebugLauncher sRunningAppDebugLauncher;
80 /** Console for DDMS log message */
81 private MessageConsole mDdmsConsole;
83 /** Image loader object */
84 private ImageLoader mLoader;
86 private IDevice mCurrentDevice;
87 private Client mCurrentClient;
88 private boolean mListeningToUiSelection = false;
90 private final ArrayList<ISelectionListener> mListeners = new ArrayList<ISelectionListener>();
94 private boolean mDdmlibInitialized;
97 * Interface to provide debugger launcher for running apps.
99 public interface IDebugLauncher {
100 public boolean debug(String packageName, int port);
104 * Classes which implement this interface provide methods that deals
105 * with {@link IDevice} and {@link Client} selectionchanges.
107 public interface ISelectionListener {
110 * Sent when a new {@link Client} is selected.
111 * @param selectedClient The selected client. If null, no clients are selected.
113 public void selectionChanged(Client selectedClient);
116 * Sent when a new {@link IDevice} is selected.
117 * @param selectedDevice the selected device. If null, no devices are selected.
119 public void selectionChanged(IDevice selectedDevice);
125 public DdmsPlugin() {
132 * @see org.eclipse.ui.plugin.AbstractUIPlugin#start(org.osgi.framework.BundleContext)
135 public void start(BundleContext context) throws Exception {
136 super.start(context);
138 final Display display = getDisplay();
140 // get the eclipse store
141 final IPreferenceStore eclipseStore = getPreferenceStore();
143 AndroidDebugBridge.addDeviceChangeListener(this);
145 DdmUiPreferences.setStore(eclipseStore);
147 //DdmUiPreferences.displayCharts();
150 mDdmsConsole = new MessageConsole("DDMS", null); // $NON-NLS-1$
151 ConsolePlugin.getDefault().getConsoleManager().addConsoles(
156 final MessageConsoleStream consoleStream = mDdmsConsole.newMessageStream();
157 final MessageConsoleStream errorConsoleStream = mDdmsConsole.newMessageStream();
158 mRed = new Color(display, 0xFF, 0x00, 0x00);
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
163 display.asyncExec(new Runnable() {
165 errorConsoleStream.setColor(mRed);
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);
176 printToStream(consoleStream, tag, message);
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() {
186 Shell shell = display.getActiveShell();
187 if (logLevel == LogLevel.ERROR) {
188 MessageDialog.openError(shell, tag, message);
190 MessageDialog.openWarning(shell, tag, message);
198 // create the loader that's able to load the images
199 mLoader = new ImageLoader(this);
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();
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));
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));
231 // start it in a thread to return from start() asap.
235 // init ddmlib if needed
236 getDefault().initDdmlib();
238 // create and start the first bridge
240 AndroidDebugBridge.createBridge(sAdbLocation, true /* forceNewBridge */);
246 public static Display getDisplay() {
247 IWorkbench bench = sPlugin.getWorkbench();
249 return bench.getDisplay();
257 * @see org.eclipse.ui.plugin.AbstractUIPlugin#stop(org.osgi.framework.BundleContext)
260 public void stop(BundleContext context) throws Exception {
261 AndroidDebugBridge.removeDeviceChangeListener(this);
263 AndroidDebugBridge.terminate();
272 * Returns the shared instance
274 * @return the shared instance
276 public static DdmsPlugin getDefault() {
280 /** Return the image loader for the plugin */
281 public static ImageLoader getImageLoader() {
282 if (sPlugin != null) {
283 return sPlugin.mLoader;
288 public static String getAdb() {
292 public static String getToolsFolder() {
296 public static String getHprofConverter() {
297 return sHprofConverter;
301 * Stores the adb location. This returns true if the location is an existing file.
303 private static boolean setAdbLocation(String adbLocation) {
304 File adb = new File(adbLocation);
306 sAdbLocation = adbLocation;
308 File toolsFolder = adb.getParentFile();
309 sToolsFolder = toolsFolder.getAbsolutePath();
311 File hprofConverter = new File(toolsFolder, DdmConstants.FN_HPROF_CONVERTER);
312 sHprofConverter = hprofConverter.getAbsolutePath();
314 File traceview = new File(toolsFolder, DdmConstants.FN_TRACEVIEW);
315 DdmUiPreferences.setTraceviewLocation(traceview.getAbsolutePath());
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
328 public static void setAdb(String adb, boolean startAdb) {
330 if (setAdbLocation(adb)) {
331 // store the location for future ddms only start.
332 sPlugin.getPreferenceStore().setValue(ADB_LOCATION, sAdbLocation);
334 // starts the server in a thread in case this is blocking.
339 // init ddmlib if needed
340 getDefault().initDdmlib();
342 // create and start the bridge
343 AndroidDebugBridge.createBridge(sAdbLocation,
344 false /* forceNewBridge */);
352 private synchronized void initDdmlib() {
353 if (mDdmlibInitialized == false) {
354 // set the preferences.
355 PreferenceInitializer.setupPreferences();
358 AndroidDebugBridge.init(true /* debugger support */);
360 mDdmlibInitialized = true;
365 * Sets the launcher responsible for connecting the debugger to running applications.
366 * @param launcher The launcher.
368 public static void setRunningAppDebugLauncher(IDebugLauncher launcher) {
369 sRunningAppDebugLauncher = launcher;
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
374 Display display = getDisplay();
375 if (display != null && display.isDisposed() == false) {
376 display.asyncExec(new Runnable() {
378 DeviceView dv = DeviceView.getInstance();
380 dv.setDebugLauncher(sRunningAppDebugLauncher);
387 public static IDebugLauncher getRunningAppDebugLauncher() {
388 return sRunningAppDebugLauncher;
391 public synchronized void addSelectionListener(ISelectionListener listener) {
392 mListeners.add(listener);
394 // notify the new listener of the current selection
395 listener.selectionChanged(mCurrentDevice);
396 listener.selectionChanged(mCurrentClient);
399 public synchronized void removeSelectionListener(ISelectionListener listener) {
400 mListeners.remove(listener);
403 public synchronized void setListeningState(boolean state) {
404 mListeningToUiSelection = state;
408 * Sent when the a device is connected to the {@link AndroidDebugBridge}.
410 * This is sent from a non UI thread.
411 * @param device the new device.
413 * @see IDeviceChangeListener#deviceConnected(IDevice)
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);
427 * Sent when the a device is disconnected to the {@link AndroidDebugBridge}.
429 * This is sent from a non UI thread.
430 * @param device the new device.
432 * @see IDeviceChangeListener#deviceDisconnected(IDevice)
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();
447 // check if we still have devices
448 if (devices.length == 0) {
449 handleDefaultSelection((IDevice)null);
451 handleDefaultSelection(devices[0]);
454 handleDefaultSelection((IDevice)null);
461 * Sent when a device data changed, or when clients are started/terminated on the device.
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.
467 * @see IDeviceChangeListener#deviceChanged(IDevice)
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) {
475 // check if this is our device
476 if (device == mCurrentDevice) {
477 if (mCurrentClient == null) {
478 handleDefaultSelection(device);
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) {
490 // if we haven't found our client, lets look for a new one
491 if (foundClient == false) {
492 mCurrentClient = null;
493 handleDefaultSelection(device);
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.
505 public synchronized void selectionChanged(IDevice selectedDevice, Client selectedClient) {
506 if (mCurrentDevice != selectedDevice) {
507 mCurrentDevice = selectedDevice;
509 // notify of the new default device
510 for (ISelectionListener listener : mListeners) {
511 listener.selectionChanged(mCurrentDevice);
515 if (mCurrentClient != selectedClient) {
516 mCurrentClient = selectedClient;
518 // notify of the new default client
519 for (ISelectionListener listener : mListeners) {
520 listener.selectionChanged(mCurrentClient);
526 * Handles a default selection of a {@link IDevice} and {@link Client}.
527 * @param device the selected device
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.
533 Display display = getDisplay();
535 display.asyncExec(new Runnable() {
537 // set the new device if different.
538 boolean newDevice = false;
539 if (mCurrentDevice != device) {
540 mCurrentDevice = device;
543 // notify of the new default device
544 for (ISelectionListener listener : mListeners) {
545 listener.selectionChanged(mCurrentDevice);
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]);
558 handleDefaultSelection((Client)null);
562 handleDefaultSelection((Client)null);
566 } catch (SWTException e) {
567 // display is disposed. Do nothing since we're quitting anyway.
571 private void handleDefaultSelection(Client client) {
572 mCurrentClient = client;
574 // notify of the new default client
575 for (ISelectionListener listener : mListeners) {
576 listener.selectionChanged(mCurrentClient);
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.
586 private static synchronized void printToStream(MessageConsoleStream stream, String tag,
588 String dateTag = getMessageTag(tag);
590 stream.print(dateTag);
591 stream.println(message);
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
599 private static String getMessageTag(String tag) {
600 Calendar c = Calendar.getInstance();
603 return String.format("[%1$tF %1$tT]", c);
606 return String.format("[%1$tF %1$tT - %2$s]", c, tag);