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