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