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 */ 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 */ 122 public void selectionChanged(IDevice selectedDevice); 123 } 124 125 /** 126 * The constructor 127 */ 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 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 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. */ 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 */ 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 */ 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 */ 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 */ 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 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 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 */ 485 public static DdmsPlugin getDefault() { 486 return sPlugin; 487 } 488 489 public static String getAdb() { 490 return sAdbLocation; 491 } 492 493 public static String getToolsFolder() { 494 return sToolsFolder; 495 } 496 497 public static String getHprofConverter() { 498 return sHprofConverter; 499 } 500 501 /** 502 * Stores the adb location. This returns true if the location is an existing file. 503 */ 504 private static boolean setToolsLocation(String adbLocation, String hprofConvLocation, 505 String traceViewLocation) { 506 507 File adb = new File(adbLocation); 508 File hprofConverter = new File(hprofConvLocation); 509 File traceview = new File(traceViewLocation); 510 511 String missing = ""; 512 if (adb.isFile() == false) { 513 missing += adb.getAbsolutePath() + " "; 514 } 515 if (hprofConverter.isFile() == false) { 516 missing += hprofConverter.getAbsolutePath() + " "; 517 } 518 if (traceview.isFile() == false) { 519 missing += traceview.getAbsolutePath() + " "; 520 } 521 522 if (missing.length() > 0) { 523 String msg = String.format("DDMS files not found: %1$s", missing); 524 Log.e("DDMS", msg); 525 Status status = new Status(IStatus.ERROR, PLUGIN_ID, msg, null /*exception*/); 526 getDefault().getLog().log(status); 527 return false; 528 } 529 530 sAdbLocation = adbLocation; 531 sHprofConverter = hprofConverter.getAbsolutePath(); 532 DdmUiPreferences.setTraceviewLocation(traceview.getAbsolutePath()); 533 534 sToolsFolder = traceview.getParent(); 535 536 return true; 537 } 538 539 /** 540 * Set the location of the adb executable and optionally starts adb 541 * @param adb location of adb 542 * @param startAdb flag to start adb 543 */ 544 public static void setToolsLocation(String adbLocation, boolean startAdb, 545 String hprofConvLocation, String traceViewLocation) { 546 547 if (setToolsLocation(adbLocation, hprofConvLocation, traceViewLocation)) { 548 // starts the server in a thread in case this is blocking. 549 if (startAdb) { 550 new Thread() { 551 @Override 552 public void run() { 553 // create and start the bridge 554 try { 555 AndroidDebugBridge.createBridge(sAdbLocation, 556 false /* forceNewBridge */); 557 } catch (Throwable t) { 558 Status status = new Status(IStatus.ERROR, PLUGIN_ID, 559 "Failed to create AndroidDebugBridge", t); 560 getDefault().getLog().log(status); 561 } 562 } 563 }.start(); 564 } 565 } 566 } 567 568 /** 569 * Returns whether there are implementations of the debuggerConnectors extension point. 570 * <p/> 571 * This is guaranteed to return the correct value as soon as the plug-in is loaded. 572 */ 573 public boolean hasDebuggerConnectors() { 574 return mHasDebuggerConnectors; 575 } 576 577 /** 578 * Returns the implementations of {@link IDebuggerConnector}. 579 * <p/> 580 * There may be a small amount of time right after the plug-in load where this can return 581 * null even if there are implementation. 582 * <p/> 583 * Since the use of the implementation likely require user input, the UI can use 584 * {@link #hasDebuggerConnectors()} to know if there are implementations before they are loaded. 585 */ 586 public IDebuggerConnector[] getDebuggerConnectors() { 587 return mDebuggerConnectors; 588 } 589 590 public synchronized void addSelectionListener(ISelectionListener listener) { 591 mListeners.add(listener); 592 593 // notify the new listener of the current selection 594 listener.selectionChanged(mCurrentDevice); 595 listener.selectionChanged(mCurrentClient); 596 } 597 598 public synchronized void removeSelectionListener(ISelectionListener listener) { 599 mListeners.remove(listener); 600 } 601 602 public synchronized void setListeningState(boolean state) { 603 mListeningToUiSelection = state; 604 } 605 606 /** 607 * Sent when the a device is connected to the {@link AndroidDebugBridge}. 608 * <p/> 609 * This is sent from a non UI thread. 610 * @param device the new device. 611 * 612 * @see IDeviceChangeListener#deviceConnected(IDevice) 613 */ 614 @Override 615 public void deviceConnected(IDevice device) { 616 // if we are listening to selection coming from the ui, then we do nothing, as 617 // any change in the devices/clients, will be handled by the UI, and we'll receive 618 // selection notification through our implementation of IUiSelectionListener. 619 if (mListeningToUiSelection == false) { 620 if (mCurrentDevice == null) { 621 handleDefaultSelection(device); 622 } 623 } 624 } 625 626 /** 627 * Sent when the a device is disconnected to the {@link AndroidDebugBridge}. 628 * <p/> 629 * This is sent from a non UI thread. 630 * @param device the new device. 631 * 632 * @see IDeviceChangeListener#deviceDisconnected(IDevice) 633 */ 634 @Override 635 public void deviceDisconnected(IDevice device) { 636 // if we are listening to selection coming from the ui, then we do nothing, as 637 // any change in the devices/clients, will be handled by the UI, and we'll receive 638 // selection notification through our implementation of IUiSelectionListener. 639 if (mListeningToUiSelection == false) { 640 // test if the disconnected device was the default selection. 641 if (mCurrentDevice == device) { 642 // try to find a new device 643 AndroidDebugBridge bridge = AndroidDebugBridge.getBridge(); 644 if (bridge != null) { 645 // get the device list 646 IDevice[] devices = bridge.getDevices(); 647 648 // check if we still have devices 649 if (devices.length == 0) { 650 handleDefaultSelection((IDevice)null); 651 } else { 652 handleDefaultSelection(devices[0]); 653 } 654 } else { 655 handleDefaultSelection((IDevice)null); 656 } 657 } 658 } 659 } 660 661 /** 662 * Sent when a device data changed, or when clients are started/terminated on the device. 663 * <p/> 664 * This is sent from a non UI thread. 665 * @param device the device that was updated. 666 * @param changeMask the mask indicating what changed. 667 * 668 * @see IDeviceChangeListener#deviceChanged(IDevice) 669 */ 670 @Override 671 public void deviceChanged(IDevice device, int changeMask) { 672 // if we are listening to selection coming from the ui, then we do nothing, as 673 // any change in the devices/clients, will be handled by the UI, and we'll receive 674 // selection notification through our implementation of IUiSelectionListener. 675 if (mListeningToUiSelection == false) { 676 677 // check if this is our device 678 if (device == mCurrentDevice) { 679 if (mCurrentClient == null) { 680 handleDefaultSelection(device); 681 } else { 682 // get the clients and make sure ours is still in there. 683 Client[] clients = device.getClients(); 684 boolean foundClient = false; 685 for (Client client : clients) { 686 if (client == mCurrentClient) { 687 foundClient = true; 688 break; 689 } 690 } 691 692 // if we haven't found our client, lets look for a new one 693 if (foundClient == false) { 694 mCurrentClient = null; 695 handleDefaultSelection(device); 696 } 697 } 698 } 699 } 700 } 701 702 /** 703 * Sent when a new {@link IDevice} and {@link Client} are selected. 704 * @param selectedDevice the selected device. If null, no devices are selected. 705 * @param selectedClient The selected client. If null, no clients are selected. 706 */ 707 @Override 708 public synchronized void selectionChanged(IDevice selectedDevice, Client selectedClient) { 709 if (mCurrentDevice != selectedDevice) { 710 mCurrentDevice = selectedDevice; 711 712 // notify of the new default device 713 for (ISelectionListener listener : mListeners) { 714 listener.selectionChanged(mCurrentDevice); 715 } 716 } 717 718 if (mCurrentClient != selectedClient) { 719 mCurrentClient = selectedClient; 720 721 // notify of the new default client 722 for (ISelectionListener listener : mListeners) { 723 listener.selectionChanged(mCurrentClient); 724 } 725 } 726 } 727 728 /** 729 * Handles a default selection of a {@link IDevice} and {@link Client}. 730 * @param device the selected device 731 */ 732 private void handleDefaultSelection(final IDevice device) { 733 // because the listener expect to receive this from the UI thread, and this is called 734 // from the AndroidDebugBridge notifications, we need to run this in the UI thread. 735 try { 736 Display display = getDisplay(); 737 738 display.asyncExec(new Runnable() { 739 @Override 740 public void run() { 741 // set the new device if different. 742 boolean newDevice = false; 743 if (mCurrentDevice != device) { 744 mCurrentDevice = device; 745 newDevice = true; 746 747 // notify of the new default device 748 for (ISelectionListener listener : mListeners) { 749 listener.selectionChanged(mCurrentDevice); 750 } 751 } 752 753 if (device != null) { 754 // if this is a device switch or the same device but we didn't find a valid 755 // client the last time, we go look for a client to use again. 756 if (newDevice || mCurrentClient == null) { 757 // now get the new client 758 Client[] clients = device.getClients(); 759 if (clients.length > 0) { 760 handleDefaultSelection(clients[0]); 761 } else { 762 handleDefaultSelection((Client)null); 763 } 764 } 765 } else { 766 handleDefaultSelection((Client)null); 767 } 768 } 769 }); 770 } catch (SWTException e) { 771 // display is disposed. Do nothing since we're quitting anyway. 772 } 773 } 774 775 private void handleDefaultSelection(Client client) { 776 mCurrentClient = client; 777 778 // notify of the new default client 779 for (ISelectionListener listener : mListeners) { 780 listener.selectionChanged(mCurrentClient); 781 } 782 } 783 784 /** 785 * Prints a message, associated with a project to the specified stream 786 * @param stream The stream to write to 787 * @param tag The tag associated to the message. Can be null 788 * @param message The message to print. 789 */ 790 private static synchronized void printToStream(MessageConsoleStream stream, String tag, 791 String message) { 792 String dateTag = getMessageTag(tag); 793 794 stream.print(dateTag); 795 if (!dateTag.endsWith(" ")) { 796 stream.print(" "); //$NON-NLS-1$ 797 } 798 stream.println(message); 799 } 800 801 /** 802 * Creates a string containing the current date/time, and the tag 803 * @param tag The tag associated to the message. Can be null 804 * @return The dateTag 805 */ 806 private static String getMessageTag(String tag) { 807 Calendar c = Calendar.getInstance(); 808 809 if (tag == null) { 810 return String.format(Messages.DdmsPlugin_Message_Tag_Mask_1, c); 811 } 812 813 return String.format(Messages.DdmsPlugin_Message_Tag_Mask_2, c, tag); 814 } 815 816 /** 817 * Implementation of com.android.ddmuilib.StackTracePanel.ISourceRevealer. 818 */ 819 @Override 820 public void reveal(String applicationName, String className, int line) { 821 JavaSourceRevealer.reveal(applicationName, className, line); 822 } 823 824 public boolean launchTraceview(String osPath) { 825 if (mTraceviewLaunchers != null) { 826 for (ITraceviewLauncher launcher : mTraceviewLaunchers) { 827 try { 828 if (launcher.openFile(osPath)) { 829 return true; 830 } 831 } catch (Throwable t) { 832 // ignore, we'll just not use this implementation. 833 } 834 } 835 } 836 837 return false; 838 } 839 840 /** 841 * Returns the list of clients that extend the clientAction extension point. 842 */ 843 @NonNull 844 public synchronized List<IClientAction> getClientSpecificActions() { 845 if (mClientSpecificActions == null) { 846 // get available client specific action extensions 847 IConfigurationElement[] elements = 848 findConfigElements("com.android.ide.eclipse.ddms.clientAction"); //$NON-NLS-1$ 849 try { 850 mClientSpecificActions = instantiateClientSpecificActions(elements); 851 } catch (CoreException e) { 852 mClientSpecificActions = Collections.emptyList(); 853 } 854 } 855 856 return mClientSpecificActions; 857 } 858 859 private LogCatMonitor mLogCatMonitor; 860 public void startLogCatMonitor(IDevice device) { 861 if (mLogCatMonitor == null) { 862 mLogCatMonitor = new LogCatMonitor(getDebuggerConnectors(), getPreferenceStore()); 863 } 864 865 mLogCatMonitor.monitorDevice(device); 866 } 867 868 /** Returns an image descriptor for the image file at the given plug-in relative path */ 869 public static ImageDescriptor getImageDescriptor(String path) { 870 return imageDescriptorFromPlugin(PLUGIN_ID, path); 871 } 872 } 873