Home | History | Annotate | Download | only in views
      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 
    130         HProfHandler(Shell parentShell) {
    131             super(parentShell);
    132         }
    133 
    134         @Override
    135         protected String getDialogTitle() {
    136             return Messages.DeviceView_HPROF_Error;
    137         }
    138 
    139         @Override
    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
    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
    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 
    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 
    303     public DeviceView() {
    304         // the view is declared with allowMultiple="false" so we
    305         // can safely do this.
    306         sThis = this;
    307     }
    308 
    309     public static DeviceView getInstance() {
    310         return sThis;
    311     }
    312 
    313     @Override
    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 
    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 
    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
    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
    712     public void selectionChanged(IDevice selectedDevice, Client selectedClient) {
    713         // update the buttons
    714         doSelectionChanged(selectedClient);
    715         doSelectionChanged(selectedDevice);
    716     }
    717 
    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 
    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      */
    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
    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