1 /* 2 * Copyright (C) 2008 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 18 package com.android.ide.eclipse.ddms.views; 19 20 import com.android.ddmlib.AndroidDebugBridge; 21 import com.android.ddmlib.Client; 22 import com.android.ddmlib.ClientData; 23 import com.android.ddmlib.IDevice; 24 import com.android.ddmlib.SyncService; 25 import com.android.ddmlib.AndroidDebugBridge.IClientChangeListener; 26 import com.android.ddmlib.ClientData.IHprofDumpHandler; 27 import com.android.ddmlib.ClientData.MethodProfilingStatus; 28 import com.android.ddmlib.SyncService.SyncResult; 29 import com.android.ddmuilib.handler.BaseFileHandler; 30 import com.android.ddmuilib.handler.MethodProfilingHandler; 31 import com.android.ddmuilib.DevicePanel; 32 import com.android.ddmuilib.ScreenShotDialog; 33 import com.android.ddmuilib.DevicePanel.IUiSelectionListener; 34 import com.android.ide.eclipse.ddms.DdmsPlugin; 35 import com.android.ide.eclipse.ddms.DdmsPlugin.IDebugLauncher; 36 import com.android.ide.eclipse.ddms.preferences.PreferenceInitializer; 37 38 import org.eclipse.core.filesystem.EFS; 39 import org.eclipse.core.filesystem.IFileStore; 40 import org.eclipse.core.runtime.Path; 41 import org.eclipse.jface.action.Action; 42 import org.eclipse.jface.action.IAction; 43 import org.eclipse.jface.action.IMenuManager; 44 import org.eclipse.jface.action.IToolBarManager; 45 import org.eclipse.jface.action.Separator; 46 import org.eclipse.jface.dialogs.MessageDialog; 47 import org.eclipse.jface.preference.IPreferenceStore; 48 import org.eclipse.jface.resource.ImageDescriptor; 49 import org.eclipse.swt.widgets.Composite; 50 import org.eclipse.swt.widgets.Display; 51 import org.eclipse.swt.widgets.Shell; 52 import org.eclipse.ui.IActionBars; 53 import org.eclipse.ui.ISharedImages; 54 import org.eclipse.ui.PartInitException; 55 import org.eclipse.ui.PlatformUI; 56 import org.eclipse.ui.ide.IDE; 57 import org.eclipse.ui.part.ViewPart; 58 59 import java.io.File; 60 import java.io.IOException; 61 62 public class DeviceView extends ViewPart implements IUiSelectionListener, IClientChangeListener { 63 64 private final static boolean USE_SELECTED_DEBUG_PORT = true; 65 66 public static final String ID = 67 "com.android.ide.eclipse.ddms.views.DeviceView"; //$NON-NLS-1$ 68 69 private static DeviceView sThis; 70 71 private Shell mParentShell; 72 private DevicePanel mDeviceList; 73 74 private Action mResetAdbAction; 75 private Action mCaptureAction; 76 private Action mUpdateThreadAction; 77 private Action mUpdateHeapAction; 78 private Action mGcAction; 79 private Action mKillAppAction; 80 private Action mDebugAction; 81 private Action mHprofAction; 82 private Action mTracingAction; 83 private IDebugLauncher mDebugLauncher; 84 85 private ImageDescriptor mTracingStartImage; 86 private ImageDescriptor mTracingStopImage; 87 88 public class HProfHandler extends BaseFileHandler implements IHprofDumpHandler { 89 public final static String ACTION_SAVE ="hprof.save"; //$NON-NLS-1$ 90 public final static String ACTION_OPEN = "hprof.open"; //$NON-NLS-1$ 91 92 public final static String DOT_HPROF = ".hprof"; //$NON-NLS-1$ 93 HProfHandler(Shell parentShell)94 HProfHandler(Shell parentShell) { 95 super(parentShell); 96 } 97 onFailure(final Client client)98 public void onFailure(final Client client) { 99 mParentShell.getDisplay().asyncExec(new Runnable() { 100 public void run() { 101 try { 102 displayError("Unable to create HPROF file for application '%1$s'.\n" + 103 "Check logcat for more information.", 104 client.getClientData().getClientDescription()); 105 } finally { 106 // this will make sure the dump hprof button is re-enabled for the 107 // current selection. as the client is finished dumping an hprof file 108 doSelectionChanged(mDeviceList.getSelectedClient()); 109 } 110 } 111 }); 112 } 113 onSuccess(final String remoteFilePath, final Client client)114 public void onSuccess(final String remoteFilePath, final Client client) { 115 mParentShell.getDisplay().asyncExec(new Runnable() { 116 public void run() { 117 final IDevice device = client.getDevice(); 118 try { 119 // get the sync service to pull the HPROF file 120 final SyncService sync = client.getDevice().getSyncService(); 121 if (sync != null) { 122 // get from the preference what action to take 123 IPreferenceStore store = DdmsPlugin.getDefault().getPreferenceStore(); 124 String value = store.getString(PreferenceInitializer.ATTR_HPROF_ACTION); 125 126 SyncResult result = null; 127 if (ACTION_OPEN.equals(value)) { 128 File temp = File.createTempFile("android", DOT_HPROF); //$NON-NLS-1$ 129 String tempPath = temp.getAbsolutePath(); 130 result = pull(sync, tempPath, remoteFilePath); 131 if (result != null && result.getCode() == SyncService.RESULT_OK) { 132 open(tempPath); 133 } 134 } else { 135 // default action is ACTION_SAVE 136 result = promptAndPull(sync, 137 client.getClientData().getClientDescription() + DOT_HPROF, 138 remoteFilePath, "Save HPROF file"); 139 140 } 141 142 if (result != null && result.getCode() != SyncService.RESULT_OK) { 143 displayError( 144 "Unable to download HPROF file from device '%1$s'.\n\n%2$s", 145 device.getSerialNumber(), result.getMessage()); 146 } 147 } else { 148 displayError("Unable to download HPROF file from device '%1$s'.", 149 device.getSerialNumber()); 150 } 151 } catch (Exception e) { 152 displayError("Unable to download HPROF file from device '%1$s'.", 153 device.getSerialNumber()); 154 155 } finally { 156 // this will make sure the dump hprof button is re-enabled for the 157 // current selection. as the client is finished dumping an hprof file 158 doSelectionChanged(mDeviceList.getSelectedClient()); 159 } 160 } 161 }); 162 } 163 open(String path)164 private void open(String path) throws IOException, InterruptedException, PartInitException { 165 // make a temp file to convert the hprof into something 166 // readable by normal tools 167 File temp = File.createTempFile("android", DOT_HPROF); 168 String tempPath = temp.getAbsolutePath(); 169 170 String[] command = new String[3]; 171 command[0] = DdmsPlugin.getHprofConverter(); 172 command[1] = path; 173 command[2] = tempPath; 174 175 Process p = Runtime.getRuntime().exec(command); 176 p.waitFor(); 177 178 IFileStore fileStore = EFS.getLocalFileSystem().getStore(new Path(tempPath)); 179 if (!fileStore.fetchInfo().isDirectory() && fileStore.fetchInfo().exists()) { 180 IDE.openEditorOnFileStore( 181 getSite().getWorkbenchWindow().getActivePage(), 182 fileStore); 183 } 184 } 185 displayError(String format, Object... args)186 private void displayError(String format, Object... args) { 187 MessageDialog.openError(mParentShell, "HPROF Error", 188 String.format(format, args)); 189 } 190 } 191 192 DeviceView()193 public DeviceView() { 194 // the view is declared with allowMultiple="false" so we 195 // can safely do this. 196 sThis = this; 197 } 198 getInstance()199 public static DeviceView getInstance() { 200 return sThis; 201 } 202 203 /** 204 * Sets the {@link IDebugLauncher}. 205 * @param debugLauncher 206 */ setDebugLauncher(DdmsPlugin.IDebugLauncher debugLauncher)207 public void setDebugLauncher(DdmsPlugin.IDebugLauncher debugLauncher) { 208 mDebugLauncher = debugLauncher; 209 if (mDebugAction != null && mDeviceList != null) { 210 Client currentClient = mDeviceList.getSelectedClient(); 211 if (currentClient != null) { 212 mDebugAction.setEnabled(true); 213 } 214 } 215 } 216 217 @Override createPartControl(Composite parent)218 public void createPartControl(Composite parent) { 219 mParentShell = parent.getShell(); 220 ClientData.setHprofDumpHandler(new HProfHandler(mParentShell)); 221 AndroidDebugBridge.addClientChangeListener(this); 222 ClientData.setMethodProfilingHandler(new MethodProfilingHandler(mParentShell)); 223 224 mDeviceList = new DevicePanel(DdmsPlugin.getImageLoader(), USE_SELECTED_DEBUG_PORT); 225 mDeviceList.createPanel(parent); 226 mDeviceList.addSelectionListener(this); 227 228 DdmsPlugin plugin = DdmsPlugin.getDefault(); 229 mDeviceList.addSelectionListener(plugin); 230 plugin.setListeningState(true); 231 232 mCaptureAction = new Action("Screen Capture") { 233 @Override 234 public void run() { 235 ScreenShotDialog dlg = new ScreenShotDialog( 236 DdmsPlugin.getDisplay().getActiveShell()); 237 dlg.open(mDeviceList.getSelectedDevice()); 238 } 239 }; 240 mCaptureAction.setToolTipText("Screen Capture"); 241 mCaptureAction.setImageDescriptor( 242 DdmsPlugin.getImageLoader().loadDescriptor("capture.png")); //$NON-NLS-1$ 243 244 mResetAdbAction = new Action("Reset adb") { 245 @Override 246 public void run() { 247 AndroidDebugBridge bridge = AndroidDebugBridge.getBridge(); 248 if (bridge != null) { 249 if (bridge.restart() == false) { 250 // get the current Display 251 final Display display = DdmsPlugin.getDisplay(); 252 253 // dialog box only run in ui thread.. 254 display.asyncExec(new Runnable() { 255 public void run() { 256 Shell shell = display.getActiveShell(); 257 MessageDialog.openError(shell, "Adb Error", 258 "Adb failed to restart!\n\nMake sure the plugin is properly configured."); 259 } 260 }); 261 } 262 } 263 } 264 }; 265 mResetAdbAction.setToolTipText("Reset the adb host daemon"); 266 mResetAdbAction.setImageDescriptor(PlatformUI.getWorkbench() 267 .getSharedImages().getImageDescriptor( 268 ISharedImages.IMG_OBJS_WARN_TSK)); 269 270 mKillAppAction = new Action() { 271 @Override 272 public void run() { 273 mDeviceList.killSelectedClient(); 274 } 275 }; 276 277 mKillAppAction.setText("Stop Process"); 278 mKillAppAction.setToolTipText("Stop Process"); 279 mKillAppAction.setImageDescriptor(DdmsPlugin.getImageLoader() 280 .loadDescriptor(DevicePanel.ICON_HALT)); 281 282 mGcAction = new Action() { 283 @Override 284 public void run() { 285 mDeviceList.forceGcOnSelectedClient(); 286 } 287 }; 288 289 mGcAction.setText("Cause GC"); 290 mGcAction.setToolTipText("Cause GC"); 291 mGcAction.setImageDescriptor(DdmsPlugin.getImageLoader() 292 .loadDescriptor(DevicePanel.ICON_GC)); 293 294 mHprofAction = new Action() { 295 @Override 296 public void run() { 297 mDeviceList.dumpHprof(); 298 doSelectionChanged(mDeviceList.getSelectedClient()); 299 } 300 }; 301 mHprofAction.setText("Dump HPROF file"); 302 mHprofAction.setToolTipText("Dump HPROF file"); 303 mHprofAction.setImageDescriptor(DdmsPlugin.getImageLoader() 304 .loadDescriptor(DevicePanel.ICON_HPROF)); 305 306 mUpdateHeapAction = new Action("Update Heap", IAction.AS_CHECK_BOX) { 307 @Override 308 public void run() { 309 boolean enable = mUpdateHeapAction.isChecked(); 310 mDeviceList.setEnabledHeapOnSelectedClient(enable); 311 } 312 }; 313 mUpdateHeapAction.setToolTipText("Update Heap"); 314 mUpdateHeapAction.setImageDescriptor(DdmsPlugin.getImageLoader() 315 .loadDescriptor(DevicePanel.ICON_HEAP)); 316 317 mUpdateThreadAction = new Action("Update Threads", IAction.AS_CHECK_BOX) { 318 @Override 319 public void run() { 320 boolean enable = mUpdateThreadAction.isChecked(); 321 mDeviceList.setEnabledThreadOnSelectedClient(enable); 322 } 323 }; 324 mUpdateThreadAction.setToolTipText("Update Threads"); 325 mUpdateThreadAction.setImageDescriptor(DdmsPlugin.getImageLoader() 326 .loadDescriptor(DevicePanel.ICON_THREAD)); 327 328 mTracingAction = new Action() { 329 @Override 330 public void run() { 331 mDeviceList.toggleMethodProfiling(); 332 } 333 }; 334 mTracingAction.setText("Start Method Profiling"); 335 mTracingAction.setToolTipText("Start Method Profiling"); 336 mTracingStartImage = DdmsPlugin.getImageLoader().loadDescriptor( 337 DevicePanel.ICON_TRACING_START); 338 mTracingStopImage = DdmsPlugin.getImageLoader().loadDescriptor( 339 DevicePanel.ICON_TRACING_STOP); 340 mTracingAction.setImageDescriptor(mTracingStartImage); 341 342 // check if there's already a debug launcher set up in the plugin class 343 mDebugLauncher = DdmsPlugin.getRunningAppDebugLauncher(); 344 345 mDebugAction = new Action("Debug Process") { 346 @Override 347 public void run() { 348 if (mDebugLauncher != null) { 349 Client currentClient = mDeviceList.getSelectedClient(); 350 if (currentClient != null) { 351 ClientData clientData = currentClient.getClientData(); 352 353 // make sure the client can be debugged 354 switch (clientData.getDebuggerConnectionStatus()) { 355 case ERROR: { 356 Display display = DdmsPlugin.getDisplay(); 357 Shell shell = display.getActiveShell(); 358 MessageDialog.openError(shell, "Process Debug", 359 "The process debug port is already in use!"); 360 return; 361 } 362 case ATTACHED: { 363 Display display = DdmsPlugin.getDisplay(); 364 Shell shell = display.getActiveShell(); 365 MessageDialog.openError(shell, "Process Debug", 366 "The process is already being debugged!"); 367 return; 368 } 369 } 370 371 // get the name of the client 372 String packageName = clientData.getClientDescription(); 373 if (packageName != null) { 374 if (mDebugLauncher.debug(packageName, 375 currentClient.getDebuggerListenPort()) == false) { 376 377 // if we get to this point, then we failed to find a project 378 // that matched the application to debug 379 Display display = DdmsPlugin.getDisplay(); 380 Shell shell = display.getActiveShell(); 381 MessageDialog.openError(shell, "Process Debug", 382 String.format( 383 "No opened project found for %1$s. Debug session failed!", 384 packageName)); 385 } 386 } 387 } 388 } 389 } 390 }; 391 mDebugAction.setToolTipText("Debug the selected process, provided its source project is present and opened in the workspace."); 392 mDebugAction.setImageDescriptor(DdmsPlugin.getImageLoader() 393 .loadDescriptor("debug-attach.png")); //$NON-NLS-1$ 394 if (mDebugLauncher == null) { 395 mDebugAction.setEnabled(false); 396 } 397 398 placeActions(); 399 } 400 401 @Override setFocus()402 public void setFocus() { 403 mDeviceList.setFocus(); 404 } 405 406 /** 407 * Sent when a new {@link IDevice} and {@link Client} are selected. 408 * @param selectedDevice the selected device. If null, no devices are selected. 409 * @param selectedClient The selected client. If null, no clients are selected. 410 */ selectionChanged(IDevice selectedDevice, Client selectedClient)411 public void selectionChanged(IDevice selectedDevice, Client selectedClient) { 412 // update the buttons 413 doSelectionChanged(selectedClient); 414 doSelectionChanged(selectedDevice); 415 } 416 doSelectionChanged(Client selectedClient)417 private void doSelectionChanged(Client selectedClient) { 418 // update the buttons 419 if (selectedClient != null) { 420 if (USE_SELECTED_DEBUG_PORT) { 421 // set the client as the debug client 422 selectedClient.setAsSelectedClient(); 423 } 424 425 mDebugAction.setEnabled(mDebugLauncher != null); 426 mKillAppAction.setEnabled(true); 427 mGcAction.setEnabled(true); 428 429 mUpdateHeapAction.setEnabled(true); 430 mUpdateHeapAction.setChecked(selectedClient.isHeapUpdateEnabled()); 431 432 mUpdateThreadAction.setEnabled(true); 433 mUpdateThreadAction.setChecked(selectedClient.isThreadUpdateEnabled()); 434 435 ClientData data = selectedClient.getClientData(); 436 437 if (data.hasFeature(ClientData.FEATURE_HPROF)) { 438 mHprofAction.setEnabled(data.hasPendingHprofDump() == false); 439 mHprofAction.setToolTipText("Dump HPROF file"); 440 } else { 441 mHprofAction.setEnabled(false); 442 mHprofAction.setToolTipText("Dump HPROF file (not supported by this VM)"); 443 } 444 445 if (data.hasFeature(ClientData.FEATURE_PROFILING)) { 446 mTracingAction.setEnabled(true); 447 if (data.getMethodProfilingStatus() == MethodProfilingStatus.ON) { 448 mTracingAction.setToolTipText("Stop Method Profiling"); 449 mTracingAction.setText("Stop Method Profiling"); 450 mTracingAction.setImageDescriptor(mTracingStopImage); 451 } else { 452 mTracingAction.setToolTipText("Start Method Profiling"); 453 mTracingAction.setImageDescriptor(mTracingStartImage); 454 mTracingAction.setText("Start Method Profiling"); 455 } 456 } else { 457 mTracingAction.setEnabled(false); 458 mTracingAction.setImageDescriptor(mTracingStartImage); 459 mTracingAction.setToolTipText("Start Method Profiling (not supported by this VM)"); 460 mTracingAction.setText("Start Method Profiling"); 461 } 462 } else { 463 if (USE_SELECTED_DEBUG_PORT) { 464 // set the client as the debug client 465 AndroidDebugBridge bridge = AndroidDebugBridge.getBridge(); 466 if (bridge != null) { 467 bridge.setSelectedClient(null); 468 } 469 } 470 471 mDebugAction.setEnabled(false); 472 mKillAppAction.setEnabled(false); 473 mGcAction.setEnabled(false); 474 mUpdateHeapAction.setChecked(false); 475 mUpdateHeapAction.setEnabled(false); 476 mUpdateThreadAction.setEnabled(false); 477 mUpdateThreadAction.setChecked(false); 478 mHprofAction.setEnabled(false); 479 480 mHprofAction.setEnabled(false); 481 mHprofAction.setToolTipText("Dump HPROF file"); 482 483 mTracingAction.setEnabled(false); 484 mTracingAction.setImageDescriptor(mTracingStartImage); 485 mTracingAction.setToolTipText("Start Method Profiling"); 486 mTracingAction.setText("Start Method Profiling"); 487 } 488 } 489 doSelectionChanged(IDevice selectedDevice)490 private void doSelectionChanged(IDevice selectedDevice) { 491 mCaptureAction.setEnabled(selectedDevice != null); 492 } 493 494 /** 495 * Place the actions in the ui. 496 */ placeActions()497 private final void placeActions() { 498 IActionBars actionBars = getViewSite().getActionBars(); 499 500 // first in the menu 501 IMenuManager menuManager = actionBars.getMenuManager(); 502 menuManager.removeAll(); 503 menuManager.add(mDebugAction); 504 menuManager.add(new Separator()); 505 menuManager.add(mUpdateHeapAction); 506 menuManager.add(mHprofAction); 507 menuManager.add(mGcAction); 508 menuManager.add(new Separator()); 509 menuManager.add(mUpdateThreadAction); 510 menuManager.add(mTracingAction); 511 menuManager.add(new Separator()); 512 menuManager.add(mKillAppAction); 513 menuManager.add(new Separator()); 514 menuManager.add(mCaptureAction); 515 menuManager.add(new Separator()); 516 menuManager.add(mResetAdbAction); 517 518 // and then in the toolbar 519 IToolBarManager toolBarManager = actionBars.getToolBarManager(); 520 toolBarManager.removeAll(); 521 toolBarManager.add(mDebugAction); 522 toolBarManager.add(new Separator()); 523 toolBarManager.add(mUpdateHeapAction); 524 toolBarManager.add(mHprofAction); 525 toolBarManager.add(mGcAction); 526 toolBarManager.add(new Separator()); 527 toolBarManager.add(mUpdateThreadAction); 528 toolBarManager.add(mTracingAction); 529 toolBarManager.add(new Separator()); 530 toolBarManager.add(mKillAppAction); 531 toolBarManager.add(new Separator()); 532 toolBarManager.add(mCaptureAction); 533 } 534 clientChanged(final Client client, int changeMask)535 public void clientChanged(final Client client, int changeMask) { 536 if ((changeMask & Client.CHANGE_METHOD_PROFILING_STATUS) == 537 Client.CHANGE_METHOD_PROFILING_STATUS) { 538 if (mDeviceList.getSelectedClient() == client) { 539 mParentShell.getDisplay().asyncExec(new Runnable() { 540 public void run() { 541 // force refresh of the button enabled state. 542 doSelectionChanged(client); 543 } 544 }); 545 } 546 } 547 } 548 } 549