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 package com.android.ide.eclipse.ddms.views;
18 
19 import com.android.ddmlib.AndroidDebugBridge;
20 import com.android.ddmlib.AndroidDebugBridge.IClientChangeListener;
21 import com.android.ddmlib.Client;
22 import com.android.ddmlib.ClientData;
23 import com.android.ddmlib.ClientData.IHprofDumpHandler;
24 import com.android.ddmlib.ClientData.MethodProfilingStatus;
25 import com.android.ddmlib.CollectingOutputReceiver;
26 import com.android.ddmlib.DdmPreferences;
27 import com.android.ddmlib.IDevice;
28 import com.android.ddmlib.SyncException;
29 import com.android.ddmlib.SyncService;
30 import com.android.ddmlib.SyncService.ISyncProgressMonitor;
31 import com.android.ddmlib.TimeoutException;
32 import com.android.ddmuilib.DevicePanel;
33 import com.android.ddmuilib.DevicePanel.IUiSelectionListener;
34 import com.android.ddmuilib.ImageLoader;
35 import com.android.ddmuilib.ScreenShotDialog;
36 import com.android.ddmuilib.SyncProgressHelper;
37 import com.android.ddmuilib.SyncProgressHelper.SyncRunnable;
38 import com.android.ddmuilib.handler.BaseFileHandler;
39 import com.android.ddmuilib.handler.MethodProfilingHandler;
40 import com.android.ide.eclipse.ddms.DdmsPlugin;
41 import com.android.ide.eclipse.ddms.IClientAction;
42 import com.android.ide.eclipse.ddms.IDebuggerConnector;
43 import com.android.ide.eclipse.ddms.editors.UiAutomatorViewer;
44 import com.android.ide.eclipse.ddms.i18n.Messages;
45 import com.android.ide.eclipse.ddms.preferences.PreferenceInitializer;
46 import com.android.ide.eclipse.ddms.systrace.ISystraceOptions;
47 import com.android.ide.eclipse.ddms.systrace.ISystraceOptionsDialog;
48 import com.android.ide.eclipse.ddms.systrace.SystraceOptionsDialogV1;
49 import com.android.ide.eclipse.ddms.systrace.SystraceOptionsDialogV2;
50 import com.android.ide.eclipse.ddms.systrace.SystraceOutputParser;
51 import com.android.ide.eclipse.ddms.systrace.SystraceTask;
52 import com.android.ide.eclipse.ddms.systrace.SystraceVersionDetector;
53 import com.android.uiautomator.UiAutomatorHelper;
54 import com.android.uiautomator.UiAutomatorHelper.UiAutomatorException;
55 import com.android.uiautomator.UiAutomatorHelper.UiAutomatorResult;
56 import com.google.common.io.Files;
57 
58 import org.eclipse.core.filesystem.EFS;
59 import org.eclipse.core.filesystem.IFileStore;
60 import org.eclipse.core.runtime.IAdaptable;
61 import org.eclipse.core.runtime.IProgressMonitor;
62 import org.eclipse.core.runtime.IStatus;
63 import org.eclipse.core.runtime.Path;
64 import org.eclipse.core.runtime.Status;
65 import org.eclipse.jface.action.Action;
66 import org.eclipse.jface.action.IAction;
67 import org.eclipse.jface.action.IMenuManager;
68 import org.eclipse.jface.action.IToolBarManager;
69 import org.eclipse.jface.action.Separator;
70 import org.eclipse.jface.dialogs.ErrorDialog;
71 import org.eclipse.jface.dialogs.MessageDialog;
72 import org.eclipse.jface.dialogs.ProgressMonitorDialog;
73 import org.eclipse.jface.operation.IRunnableWithProgress;
74 import org.eclipse.jface.preference.IPreferenceStore;
75 import org.eclipse.jface.resource.ImageDescriptor;
76 import org.eclipse.swt.widgets.Composite;
77 import org.eclipse.swt.widgets.Display;
78 import org.eclipse.swt.widgets.Shell;
79 import org.eclipse.ui.IActionBars;
80 import org.eclipse.ui.ISharedImages;
81 import org.eclipse.ui.IWorkbench;
82 import org.eclipse.ui.IWorkbenchPage;
83 import org.eclipse.ui.IWorkbenchWindow;
84 import org.eclipse.ui.PartInitException;
85 import org.eclipse.ui.PlatformUI;
86 import org.eclipse.ui.WorkbenchException;
87 import org.eclipse.ui.ide.IDE;
88 import org.eclipse.ui.part.ViewPart;
89 
90 import java.io.File;
91 import java.io.IOException;
92 import java.lang.reflect.InvocationTargetException;
93 import java.util.ArrayList;
94 import java.util.List;
95 import java.util.concurrent.CountDownLatch;
96 import java.util.concurrent.TimeUnit;
97 
98 public class DeviceView extends ViewPart implements IUiSelectionListener, IClientChangeListener {
99 
100     private final static boolean USE_SELECTED_DEBUG_PORT = true;
101 
102     public static final String ID = "com.android.ide.eclipse.ddms.views.DeviceView"; //$NON-NLS-1$
103 
104     private static DeviceView sThis;
105 
106     private Shell mParentShell;
107     private DevicePanel mDeviceList;
108 
109     private Action mResetAdbAction;
110     private Action mCaptureAction;
111     private Action mViewUiAutomatorHierarchyAction;
112     private Action mSystraceAction;
113     private Action mUpdateThreadAction;
114     private Action mUpdateHeapAction;
115     private Action mGcAction;
116     private Action mKillAppAction;
117     private Action mDebugAction;
118     private Action mHprofAction;
119     private Action mTracingAction;
120 
121     private ImageDescriptor mTracingStartImage;
122     private ImageDescriptor mTracingStopImage;
123 
124     public class HProfHandler extends BaseFileHandler implements IHprofDumpHandler {
125         public final static String ACTION_SAVE = "hprof.save"; //$NON-NLS-1$
126         public final static String ACTION_OPEN = "hprof.open"; //$NON-NLS-1$
127 
128         public final static String DOT_HPROF = ".hprof"; //$NON-NLS-1$
129 
HProfHandler(Shell parentShell)130         HProfHandler(Shell parentShell) {
131             super(parentShell);
132         }
133 
134         @Override
getDialogTitle()135         protected String getDialogTitle() {
136             return Messages.DeviceView_HPROF_Error;
137         }
138 
139         @Override
onEndFailure(final Client client, final String message)140         public void onEndFailure(final Client client, final String message) {
141             mParentShell.getDisplay().asyncExec(new Runnable() {
142                 @Override
143                 public void run() {
144                     try {
145                         displayErrorFromUiThread(
146                                 Messages.DeviceView_Unable_Create_HPROF_For_Application,
147                                 client.getClientData().getClientDescription(),
148                                 message != null ? message + "\n\n" : ""); //$NON-NLS-1$ //$NON-NLS-2$
149                     } finally {
150                         // this will make sure the dump hprof button is
151                         // re-enabled for the
152                         // current selection. as the client is finished dumping
153                         // an hprof file
154                         doSelectionChanged(mDeviceList.getSelectedClient());
155                     }
156                 }
157             });
158         }
159 
160         @Override
onSuccess(final String remoteFilePath, final Client client)161         public void onSuccess(final String remoteFilePath, final Client client) {
162             mParentShell.getDisplay().asyncExec(new Runnable() {
163                 @Override
164                 public void run() {
165                     final IDevice device = client.getDevice();
166                     try {
167                         // get the sync service to pull the HPROF file
168                         final SyncService sync = client.getDevice().getSyncService();
169                         if (sync != null) {
170                             // get from the preference what action to take
171                             IPreferenceStore store = DdmsPlugin.getDefault().getPreferenceStore();
172                             String value = store.getString(PreferenceInitializer.ATTR_HPROF_ACTION);
173 
174                             if (ACTION_OPEN.equals(value)) {
175                                 File temp = File.createTempFile("android", DOT_HPROF); //$NON-NLS-1$
176                                 final String tempPath = temp.getAbsolutePath();
177                                 SyncProgressHelper.run(new SyncRunnable() {
178 
179                                     @Override
180                                     public void run(ISyncProgressMonitor monitor)
181                                                 throws SyncException, IOException,
182                                                 TimeoutException {
183                                         sync.pullFile(remoteFilePath, tempPath, monitor);
184                                     }
185 
186                                     @Override
187                                     public void close() {
188                                         sync.close();
189                                     }
190                                 },
191                                         String.format(Messages.DeviceView_Pulling_From_Device,
192                                                 remoteFilePath),
193                                         mParentShell);
194 
195                                 open(tempPath);
196                             } else {
197                                 // default action is ACTION_SAVE
198                                 promptAndPull(sync,
199                                         client.getClientData().getClientDescription() + DOT_HPROF,
200                                         remoteFilePath, Messages.DeviceView_Save_HPROF_File);
201 
202                             }
203                         } else {
204                             displayErrorFromUiThread(
205                                     Messages.DeviceView_Unable_Download_HPROF_From_Device_One_Param_First_Message,
206                                     device.getSerialNumber());
207                         }
208                     } catch (SyncException e) {
209                         if (e.wasCanceled() == false) {
210                             displayErrorFromUiThread(
211                                     Messages.DeviceView_Unable_Download_HPROF_From_Device_Two_Param,
212                                     device.getSerialNumber(), e.getMessage());
213                         }
214                     } catch (Exception e) {
215                         displayErrorFromUiThread(
216                                 Messages.DeviceView_Unable_Download_HPROF_From_Device_One_Param_Second_Message,
217                                 device.getSerialNumber());
218 
219                     } finally {
220                         // this will make sure the dump hprof button is
221                         // re-enabled for the
222                         // current selection. as the client is finished dumping
223                         // an hprof file
224                         doSelectionChanged(mDeviceList.getSelectedClient());
225                     }
226                 }
227             });
228         }
229 
230         @Override
onSuccess(final byte[] data, final Client client)231         public void onSuccess(final byte[] data, final Client client) {
232             mParentShell.getDisplay().asyncExec(new Runnable() {
233                 @Override
234                 public void run() {
235                     // get from the preference what action to take
236                     IPreferenceStore store = DdmsPlugin.getDefault().getPreferenceStore();
237                     String value = store.getString(PreferenceInitializer.ATTR_HPROF_ACTION);
238 
239                     if (ACTION_OPEN.equals(value)) {
240                         try {
241                             // no need to give an extension since we're going to
242                             // convert the
243                             // file anyway after.
244                             File tempFile = saveTempFile(data, null /* extension */);
245                             open(tempFile.getAbsolutePath());
246                         } catch (Exception e) {
247                             String errorMsg = e.getMessage();
248                             displayErrorFromUiThread(
249                                     Messages.DeviceView_Failed_To_Save_HPROF_Data,
250                                     errorMsg != null ? ":\n" + errorMsg : "."); //$NON-NLS-1$ //$NON-NLS-2$
251                         }
252                     } else {
253                         // default action is ACTION_SAVE
254                         promptAndSave(client.getClientData().getClientDescription() + DOT_HPROF,
255                                 data, Messages.DeviceView_Save_HPROF_File);
256                     }
257                 }
258             });
259         }
260 
open(String path)261         private void open(String path) throws IOException, InterruptedException, PartInitException {
262             // make a temp file to convert the hprof into something
263             // readable by normal tools
264             File temp = File.createTempFile("android", DOT_HPROF); //$NON-NLS-1$
265             String tempPath = temp.getAbsolutePath();
266 
267             String[] command = new String[3];
268             command[0] = DdmsPlugin.getHprofConverter();
269             command[1] = path;
270             command[2] = tempPath;
271 
272             Process p = Runtime.getRuntime().exec(command);
273             p.waitFor();
274 
275             IFileStore fileStore = EFS.getLocalFileSystem().getStore(new Path(tempPath));
276             if (!fileStore.fetchInfo().isDirectory() && fileStore.fetchInfo().exists()) {
277                 // before we open the file in an editor window, we make sure the
278                 // current
279                 // workbench page has an editor area (typically the ddms
280                 // perspective doesn't).
281                 IWorkbench workbench = PlatformUI.getWorkbench();
282                 IWorkbenchWindow window = workbench.getActiveWorkbenchWindow();
283                 IWorkbenchPage page = window.getActivePage();
284                 if (page == null) {
285                     return;
286                 }
287 
288                 if (page.isEditorAreaVisible() == false) {
289                     IAdaptable input;
290                     input = page.getInput();
291                     try {
292                         workbench.showPerspective("org.eclipse.debug.ui.DebugPerspective", //$NON-NLS-1$
293                                 window, input);
294                     } catch (WorkbenchException e) {
295                     }
296                 }
297 
298                 IDE.openEditorOnFileStore(page, fileStore);
299             }
300         }
301     }
302 
DeviceView()303     public DeviceView() {
304         // the view is declared with allowMultiple="false" so we
305         // can safely do this.
306         sThis = this;
307     }
308 
getInstance()309     public static DeviceView getInstance() {
310         return sThis;
311     }
312 
313     @Override
createPartControl(Composite parent)314     public void createPartControl(Composite parent) {
315         mParentShell = parent.getShell();
316 
317         ImageLoader loader = ImageLoader.getDdmUiLibLoader();
318 
319         mDeviceList = new DevicePanel(USE_SELECTED_DEBUG_PORT);
320         mDeviceList.createPanel(parent);
321         mDeviceList.addSelectionListener(this);
322 
323         DdmsPlugin plugin = DdmsPlugin.getDefault();
324         mDeviceList.addSelectionListener(plugin);
325         plugin.setListeningState(true);
326 
327         mCaptureAction = new Action(Messages.DeviceView_Screen_Capture) {
328             @Override
329             public void run() {
330                 ScreenShotDialog dlg = new ScreenShotDialog(
331                         DdmsPlugin.getDisplay().getActiveShell());
332                 dlg.open(mDeviceList.getSelectedDevice());
333             }
334         };
335         mCaptureAction.setToolTipText(Messages.DeviceView_Screen_Capture_Tooltip);
336         mCaptureAction.setImageDescriptor(loader.loadDescriptor("capture.png")); //$NON-NLS-1$
337 
338         mViewUiAutomatorHierarchyAction = new Action("Dump View Hierarchy for UI Automator") {
339             @Override
340             public void run() {
341                 takeUiAutomatorSnapshot(mDeviceList.getSelectedDevice(),
342                         DdmsPlugin.getDisplay().getActiveShell());
343             }
344         };
345         mViewUiAutomatorHierarchyAction.setToolTipText("Dump View Hierarchy for UI Automator");
346         mViewUiAutomatorHierarchyAction.setImageDescriptor(
347                 DdmsPlugin.getImageDescriptor("icons/uiautomator.png")); //$NON-NLS-1$
348 
349         mSystraceAction = new Action("Capture System Wide Trace") {
350             @Override
351             public void run() {
352                 launchSystrace(mDeviceList.getSelectedDevice(),
353                         DdmsPlugin.getDisplay().getActiveShell());
354             }
355         };
356         mSystraceAction.setToolTipText("Capture system wide trace using Android systrace");
357         mSystraceAction.setImageDescriptor(
358                 DdmsPlugin.getImageDescriptor("icons/systrace.png")); //$NON-NLS-1$
359         mSystraceAction.setEnabled(true);
360 
361         mResetAdbAction = new Action(Messages.DeviceView_Reset_ADB) {
362             @Override
363             public void run() {
364                 AndroidDebugBridge bridge = AndroidDebugBridge.getBridge();
365                 if (bridge != null) {
366                     if (bridge.restart() == false) {
367                         // get the current Display
368                         final Display display = DdmsPlugin.getDisplay();
369 
370                         // dialog box only run in ui thread..
371                         display.asyncExec(new Runnable() {
372                             @Override
373                             public void run() {
374                                 Shell shell = display.getActiveShell();
375                                 MessageDialog.openError(shell, Messages.DeviceView_ADB_Error,
376                                         Messages.DeviceView_ADB_Failed_Restart);
377                             }
378                         });
379                     }
380                 }
381             }
382         };
383         mResetAdbAction.setToolTipText(Messages.DeviceView_Reset_ADB_Host_Deamon);
384         mResetAdbAction.setImageDescriptor(PlatformUI.getWorkbench()
385                 .getSharedImages().getImageDescriptor(
386                         ISharedImages.IMG_OBJS_WARN_TSK));
387 
388         mKillAppAction = new Action() {
389             @Override
390             public void run() {
391                 mDeviceList.killSelectedClient();
392             }
393         };
394 
395         mKillAppAction.setText(Messages.DeviceView_Stop_Process);
396         mKillAppAction.setToolTipText(Messages.DeviceView_Stop_Process_Tooltip);
397         mKillAppAction.setImageDescriptor(loader.loadDescriptor(DevicePanel.ICON_HALT));
398 
399         mGcAction = new Action() {
400             @Override
401             public void run() {
402                 mDeviceList.forceGcOnSelectedClient();
403             }
404         };
405 
406         mGcAction.setText(Messages.DeviceView_Cause_GC);
407         mGcAction.setToolTipText(Messages.DeviceView_Cause_GC_Tooltip);
408         mGcAction.setImageDescriptor(loader.loadDescriptor(DevicePanel.ICON_GC));
409 
410         mHprofAction = new Action() {
411             @Override
412             public void run() {
413                 mDeviceList.dumpHprof();
414                 doSelectionChanged(mDeviceList.getSelectedClient());
415             }
416         };
417         mHprofAction.setText(Messages.DeviceView_Dump_HPROF_File);
418         mHprofAction.setToolTipText(Messages.DeviceView_Dump_HPROF_File_Tooltip);
419         mHprofAction.setImageDescriptor(loader.loadDescriptor(DevicePanel.ICON_HPROF));
420 
421         mUpdateHeapAction = new Action(Messages.DeviceView_Update_Heap, IAction.AS_CHECK_BOX) {
422             @Override
423             public void run() {
424                 boolean enable = mUpdateHeapAction.isChecked();
425                 mDeviceList.setEnabledHeapOnSelectedClient(enable);
426             }
427         };
428         mUpdateHeapAction.setToolTipText(Messages.DeviceView_Update_Heap_Tooltip);
429         mUpdateHeapAction.setImageDescriptor(loader.loadDescriptor(DevicePanel.ICON_HEAP));
430 
431         mUpdateThreadAction = new Action(Messages.DeviceView_Threads, IAction.AS_CHECK_BOX) {
432             @Override
433             public void run() {
434                 boolean enable = mUpdateThreadAction.isChecked();
435                 mDeviceList.setEnabledThreadOnSelectedClient(enable);
436             }
437         };
438         mUpdateThreadAction.setToolTipText(Messages.DeviceView_Threads_Tooltip);
439         mUpdateThreadAction.setImageDescriptor(loader.loadDescriptor(DevicePanel.ICON_THREAD));
440 
441         mTracingAction = new Action() {
442             @Override
443             public void run() {
444                 mDeviceList.toggleMethodProfiling();
445             }
446         };
447         mTracingAction.setText(Messages.DeviceView_Start_Method_Profiling);
448         mTracingAction.setToolTipText(Messages.DeviceView_Start_Method_Profiling_Tooltip);
449         mTracingStartImage = loader.loadDescriptor(DevicePanel.ICON_TRACING_START);
450         mTracingStopImage = loader.loadDescriptor(DevicePanel.ICON_TRACING_STOP);
451         mTracingAction.setImageDescriptor(mTracingStartImage);
452 
453         mDebugAction = new Action(Messages.DeviceView_Debug_Process) {
454             @Override
455             public void run() {
456                 if (DdmsPlugin.getDefault().hasDebuggerConnectors()) {
457                     Client currentClient = mDeviceList.getSelectedClient();
458                     if (currentClient != null) {
459                         ClientData clientData = currentClient.getClientData();
460 
461                         // make sure the client can be debugged
462                         switch (clientData.getDebuggerConnectionStatus()) {
463                             case ERROR: {
464                                 Display display = DdmsPlugin.getDisplay();
465                                 Shell shell = display.getActiveShell();
466                                 MessageDialog.openError(shell,
467                                         Messages.DeviceView_Debug_Process_Title,
468                                         Messages.DeviceView_Process_Debug_Already_In_Use);
469                                 return;
470                             }
471                             case ATTACHED: {
472                                 Display display = DdmsPlugin.getDisplay();
473                                 Shell shell = display.getActiveShell();
474                                 MessageDialog.openError(shell,
475                                         Messages.DeviceView_Debug_Process_Title,
476                                         Messages.DeviceView_Process_Already_Being_Debugged);
477                                 return;
478                             }
479                         }
480 
481                         // get the name of the client
482                         String packageName = clientData.getClientDescription();
483                         if (packageName != null) {
484 
485                             // try all connectors till one returns true.
486                             IDebuggerConnector[] connectors =
487                                     DdmsPlugin.getDefault().getDebuggerConnectors();
488 
489                             if (connectors != null) {
490                                 for (IDebuggerConnector connector : connectors) {
491                                     try {
492                                         if (connector.connectDebugger(packageName,
493                                                 currentClient.getDebuggerListenPort(),
494                                                 DdmPreferences.getSelectedDebugPort())) {
495                                             return;
496                                         }
497                                     } catch (Throwable t) {
498                                         // ignore, we'll just not use this
499                                         // implementation
500                                     }
501                                 }
502                             }
503 
504                             // if we get to this point, then we failed to find a
505                             // project
506                             // that matched the application to debug
507                             Display display = DdmsPlugin.getDisplay();
508                             Shell shell = display.getActiveShell();
509                             MessageDialog.openError(shell, Messages.DeviceView_Debug_Process_Title,
510                                     String.format(
511                                             Messages.DeviceView_Debug_Session_Failed,
512                                             packageName));
513                         }
514                     }
515                 }
516             }
517         };
518         mDebugAction.setToolTipText(Messages.DeviceView_Debug_Process_Tooltip);
519         mDebugAction.setImageDescriptor(loader.loadDescriptor("debug-attach.png")); //$NON-NLS-1$
520         mDebugAction.setEnabled(DdmsPlugin.getDefault().hasDebuggerConnectors());
521 
522         placeActions();
523 
524         // disabling all action buttons
525         selectionChanged(null, null);
526 
527         ClientData.setHprofDumpHandler(new HProfHandler(mParentShell));
528         AndroidDebugBridge.addClientChangeListener(this);
529         ClientData.setMethodProfilingHandler(new MethodProfilingHandler(mParentShell) {
530             @Override
531             protected void open(String tempPath) {
532                 if (DdmsPlugin.getDefault().launchTraceview(tempPath) == false) {
533                     super.open(tempPath);
534                 }
535             }
536         });
537     }
538 
takeUiAutomatorSnapshot(final IDevice device, final Shell shell)539     private void takeUiAutomatorSnapshot(final IDevice device, final Shell shell) {
540         ProgressMonitorDialog dialog = new ProgressMonitorDialog(shell);
541         try {
542             dialog.run(true, false, new IRunnableWithProgress() {
543                 @Override
544                 public void run(IProgressMonitor monitor) throws InvocationTargetException,
545                                                                         InterruptedException {
546                     UiAutomatorResult result = null;
547                     try {
548                         result = UiAutomatorHelper.takeSnapshot(device, monitor);
549                     } catch (UiAutomatorException e) {
550                         throw new InvocationTargetException(e);
551                     } finally {
552                         monitor.done();
553                     }
554 
555                     UiAutomatorViewer.openEditor(result);
556                 }
557             });
558         } catch (Exception e) {
559             Throwable t = e;
560             if (e instanceof InvocationTargetException) {
561                 t = ((InvocationTargetException) e).getTargetException();
562             }
563             Status s = new Status(IStatus.ERROR, DdmsPlugin.PLUGIN_ID,
564                                             "Error obtaining UI hierarchy", t);
565             ErrorDialog.openError(shell, "UI Automator",
566                     "Unexpected error while obtaining UI hierarchy", s);
567         }
568     };
569 
launchSystrace(final IDevice device, final Shell parentShell)570     private void launchSystrace(final IDevice device, final Shell parentShell) {
571         final File systraceAssets = new File(DdmsPlugin.getPlatformToolsFolder(), "systrace"); //$NON-NLS-1$
572         if (!systraceAssets.isDirectory()) {
573             MessageDialog.openError(parentShell, "Systrace",
574                     "Updated version of platform-tools (18.0.1 or greater) is required.\n"
575                     + "Please update your platform-tools using SDK Manager.");
576             return;
577         }
578 
579         SystraceVersionDetector detector = new SystraceVersionDetector(device);
580         try {
581             new ProgressMonitorDialog(parentShell).run(true, false, detector);
582         } catch (InvocationTargetException e) {
583             MessageDialog.openError(parentShell,
584                     "Systrace",
585                     "Unexpected error while detecting atrace version: " + e);
586             return;
587         } catch (InterruptedException e) {
588             return;
589         }
590 
591         final ISystraceOptionsDialog dlg;
592         if (detector.getVersion() == SystraceVersionDetector.SYSTRACE_V1) {
593             dlg = new SystraceOptionsDialogV1(parentShell);
594         } else {
595             Client[] clients = device.getClients();
596             List<String> apps = new ArrayList<String>(clients.length);
597             for (int i = 0; i < clients.length; i++) {
598                 String name = clients[i].getClientData().getClientDescription();
599                 if (name != null && !name.isEmpty()) {
600                     apps.add(name);
601                 }
602             }
603             dlg = new SystraceOptionsDialogV2(parentShell, detector.getTags(), apps);
604         }
605 
606         if (dlg.open() != SystraceOptionsDialogV1.OK) {
607             return;
608         }
609 
610         final ISystraceOptions options = dlg.getSystraceOptions();
611 
612         // set trace tag if necessary:
613         //      adb shell setprop debug.atrace.tags.enableflags <tag>
614         String tag = options.getTags();
615         if (tag != null) {
616             CountDownLatch setTagLatch = new CountDownLatch(1);
617             CollectingOutputReceiver receiver = new CollectingOutputReceiver(setTagLatch);
618             try {
619                 String cmd = "setprop debug.atrace.tags.enableflags " + tag;
620                 device.executeShellCommand(cmd, receiver);
621                 setTagLatch.await(5, TimeUnit.SECONDS);
622             } catch (Exception e) {
623                 MessageDialog.openError(parentShell,
624                         "Systrace",
625                         "Unexpected error while setting trace tags: " + e);
626                 return;
627             }
628 
629             String shellOutput = receiver.getOutput();
630             if (shellOutput.contains("Error type")) {                   //$NON-NLS-1$
631                 throw new RuntimeException(receiver.getOutput());
632             }
633         }
634 
635         // obtain the output of "adb shell atrace <trace-options>" and generate the html file
636         ProgressMonitorDialog d = new ProgressMonitorDialog(parentShell);
637         try {
638             d.run(true, true, new IRunnableWithProgress() {
639                 @Override
640                 public void run(IProgressMonitor monitor) throws InvocationTargetException,
641                         InterruptedException {
642                     boolean COMPRESS_DATA = true;
643 
644                     monitor.setTaskName("Collecting Trace Information");
645                     final String atraceOptions = options.getOptions()
646                                                 + (COMPRESS_DATA ? " -z" : "");
647                     SystraceTask task = new SystraceTask(device, atraceOptions);
648                     Thread t = new Thread(task, "Systrace Output Receiver");
649                     t.start();
650 
651                     // check if the user has cancelled tracing every so often
652                     while (true) {
653                         t.join(1000);
654 
655                         if (t.isAlive()) {
656                             if (monitor.isCanceled()) {
657                                 task.cancel();
658                                 return;
659                             }
660                         } else {
661                             break;
662                         }
663                     }
664 
665                     if (task.getError() != null) {
666                         throw new RuntimeException(task.getError());
667                     }
668 
669                     monitor.setTaskName("Saving trace information");
670                     SystraceOutputParser parser = new SystraceOutputParser(
671                             COMPRESS_DATA,
672                             SystraceOutputParser.getJs(systraceAssets),
673                             SystraceOutputParser.getCss(systraceAssets),
674                             SystraceOutputParser.getHtmlPrefix(systraceAssets),
675                             SystraceOutputParser.getHtmlSuffix(systraceAssets));
676 
677                     parser.parse(task.getAtraceOutput());
678 
679                     String html = parser.getSystraceHtml();
680                     try {
681                         Files.write(html.getBytes(), new File(dlg.getTraceFilePath()));
682                     } catch (IOException e) {
683                         throw new InvocationTargetException(e);
684                     }
685                 }
686             });
687         } catch (InvocationTargetException e) {
688             ErrorDialog.openError(parentShell, "Systrace",
689                     "Unable to collect system trace.",
690                     new Status(Status.ERROR,
691                             DdmsPlugin.PLUGIN_ID,
692                             "Unexpected error while collecting system trace.",
693                             e.getCause()));
694         } catch (InterruptedException ignore) {
695         }
696     }
697 
698     @Override
setFocus()699     public void setFocus() {
700         mDeviceList.setFocus();
701     }
702 
703     /**
704      * Sent when a new {@link IDevice} and {@link Client} are selected.
705      *
706      * @param selectedDevice the selected device. If null, no devices are
707      *            selected.
708      * @param selectedClient The selected client. If null, no clients are
709      *            selected.
710      */
711     @Override
selectionChanged(IDevice selectedDevice, Client selectedClient)712     public void selectionChanged(IDevice selectedDevice, Client selectedClient) {
713         // update the buttons
714         doSelectionChanged(selectedClient);
715         doSelectionChanged(selectedDevice);
716     }
717 
doSelectionChanged(Client selectedClient)718     private void doSelectionChanged(Client selectedClient) {
719         // update the buttons
720         if (selectedClient != null) {
721             if (USE_SELECTED_DEBUG_PORT) {
722                 // set the client as the debug client
723                 selectedClient.setAsSelectedClient();
724             }
725 
726             mDebugAction.setEnabled(DdmsPlugin.getDefault().hasDebuggerConnectors());
727             mKillAppAction.setEnabled(true);
728             mGcAction.setEnabled(true);
729 
730             mUpdateHeapAction.setEnabled(true);
731             mUpdateHeapAction.setChecked(selectedClient.isHeapUpdateEnabled());
732 
733             mUpdateThreadAction.setEnabled(true);
734             mUpdateThreadAction.setChecked(selectedClient.isThreadUpdateEnabled());
735 
736             ClientData data = selectedClient.getClientData();
737 
738             if (data.hasFeature(ClientData.FEATURE_HPROF)) {
739                 mHprofAction.setEnabled(data.hasPendingHprofDump() == false);
740                 mHprofAction.setToolTipText(Messages.DeviceView_Dump_HPROF_File);
741             } else {
742                 mHprofAction.setEnabled(false);
743                 mHprofAction
744                         .setToolTipText(Messages.DeviceView_Dump_HPROF_File_Not_Supported_By_VM);
745             }
746 
747             if (data.hasFeature(ClientData.FEATURE_PROFILING)) {
748                 mTracingAction.setEnabled(true);
749                 if (data.getMethodProfilingStatus() == MethodProfilingStatus.TRACER_ON
750                         || data.getMethodProfilingStatus() == MethodProfilingStatus.SAMPLER_ON) {
751                     mTracingAction
752                             .setToolTipText(Messages.DeviceView_Stop_Method_Profiling_Tooltip);
753                     mTracingAction.setText(Messages.DeviceView_Stop_Method_Profiling);
754                     mTracingAction.setImageDescriptor(mTracingStopImage);
755                 } else {
756                     mTracingAction
757                             .setToolTipText(Messages.DeviceView_Start_Method_Profiling_Tooltip);
758                     mTracingAction.setImageDescriptor(mTracingStartImage);
759                     mTracingAction.setText(Messages.DeviceView_Start_Method_Profiling);
760                 }
761             } else {
762                 mTracingAction.setEnabled(false);
763                 mTracingAction.setImageDescriptor(mTracingStartImage);
764                 mTracingAction
765                         .setToolTipText(Messages.DeviceView_Start_Method_Profiling_Not_Suported_By_Vm);
766                 mTracingAction.setText(Messages.DeviceView_Start_Method_Profiling);
767             }
768         } else {
769             if (USE_SELECTED_DEBUG_PORT) {
770                 // set the client as the debug client
771                 AndroidDebugBridge bridge = AndroidDebugBridge.getBridge();
772                 if (bridge != null) {
773                     bridge.setSelectedClient(null);
774                 }
775             }
776 
777             mDebugAction.setEnabled(false);
778             mKillAppAction.setEnabled(false);
779             mGcAction.setEnabled(false);
780             mUpdateHeapAction.setChecked(false);
781             mUpdateHeapAction.setEnabled(false);
782             mUpdateThreadAction.setEnabled(false);
783             mUpdateThreadAction.setChecked(false);
784             mHprofAction.setEnabled(false);
785 
786             mHprofAction.setEnabled(false);
787             mHprofAction.setToolTipText(Messages.DeviceView_Dump_HPROF_File);
788 
789             mTracingAction.setEnabled(false);
790             mTracingAction.setImageDescriptor(mTracingStartImage);
791             mTracingAction.setToolTipText(Messages.DeviceView_Start_Method_Profiling_Tooltip);
792             mTracingAction.setText(Messages.DeviceView_Start_Method_Profiling);
793         }
794 
795         for (IClientAction a : DdmsPlugin.getDefault().getClientSpecificActions()) {
796             a.selectedClientChanged(selectedClient);
797         }
798     }
799 
doSelectionChanged(IDevice selectedDevice)800     private void doSelectionChanged(IDevice selectedDevice) {
801         boolean validDevice = selectedDevice != null;
802 
803         mCaptureAction.setEnabled(validDevice);
804         mViewUiAutomatorHierarchyAction.setEnabled(validDevice);
805         mSystraceAction.setEnabled(validDevice);
806     }
807 
808     /**
809      * Place the actions in the ui.
810      */
placeActions()811     private final void placeActions() {
812         IActionBars actionBars = getViewSite().getActionBars();
813 
814         // first in the menu
815         IMenuManager menuManager = actionBars.getMenuManager();
816         menuManager.removeAll();
817         menuManager.add(mDebugAction);
818         menuManager.add(new Separator());
819         menuManager.add(mUpdateHeapAction);
820         menuManager.add(mHprofAction);
821         menuManager.add(mGcAction);
822         menuManager.add(new Separator());
823         menuManager.add(mUpdateThreadAction);
824         menuManager.add(mTracingAction);
825         menuManager.add(new Separator());
826         menuManager.add(mKillAppAction);
827         menuManager.add(new Separator());
828         menuManager.add(mCaptureAction);
829         menuManager.add(new Separator());
830         menuManager.add(mViewUiAutomatorHierarchyAction);
831         menuManager.add(new Separator());
832         menuManager.add(mSystraceAction);
833         menuManager.add(new Separator());
834         menuManager.add(mResetAdbAction);
835         for (IClientAction a : DdmsPlugin.getDefault().getClientSpecificActions()) {
836             menuManager.add(a.getAction());
837         }
838 
839         // and then in the toolbar
840         IToolBarManager toolBarManager = actionBars.getToolBarManager();
841         toolBarManager.removeAll();
842         toolBarManager.add(mDebugAction);
843         toolBarManager.add(new Separator());
844         toolBarManager.add(mUpdateHeapAction);
845         toolBarManager.add(mHprofAction);
846         toolBarManager.add(mGcAction);
847         toolBarManager.add(new Separator());
848         toolBarManager.add(mUpdateThreadAction);
849         toolBarManager.add(mTracingAction);
850         toolBarManager.add(new Separator());
851         toolBarManager.add(mKillAppAction);
852         toolBarManager.add(new Separator());
853         toolBarManager.add(mCaptureAction);
854         toolBarManager.add(new Separator());
855         toolBarManager.add(mViewUiAutomatorHierarchyAction);
856         toolBarManager.add(new Separator());
857         toolBarManager.add(mSystraceAction);
858         for (IClientAction a : DdmsPlugin.getDefault().getClientSpecificActions()) {
859             toolBarManager.add(a.getAction());
860         }
861     }
862 
863     @Override
clientChanged(final Client client, int changeMask)864     public void clientChanged(final Client client, int changeMask) {
865         if ((changeMask & Client.CHANGE_METHOD_PROFILING_STATUS) == Client.CHANGE_METHOD_PROFILING_STATUS) {
866             if (mDeviceList.getSelectedClient() == client) {
867                 mParentShell.getDisplay().asyncExec(new Runnable() {
868                     @Override
869                     public void run() {
870                         // force refresh of the button enabled state.
871                         doSelectionChanged(client);
872                     }
873                 });
874             }
875         }
876     }
877 }
878