com.android.ide.eclipse.ddms.views.DeviceView.java Source code

Java tutorial

Introduction

Here is the source code for com.android.ide.eclipse.ddms.views.DeviceView.java

Source

/*
 * Copyright (C) 2008 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.ide.eclipse.ddms.views;

import com.android.ddmlib.AndroidDebugBridge;
import com.android.ddmlib.AndroidDebugBridge.IClientChangeListener;
import com.android.ddmlib.Client;
import com.android.ddmlib.ClientData;
import com.android.ddmlib.ClientData.IHprofDumpHandler;
import com.android.ddmlib.ClientData.MethodProfilingStatus;
import com.android.ddmlib.CollectingOutputReceiver;
import com.android.ddmlib.DdmPreferences;
import com.android.ddmlib.IDevice;
import com.android.ddmlib.SyncException;
import com.android.ddmlib.SyncService;
import com.android.ddmlib.SyncService.ISyncProgressMonitor;
import com.android.ddmlib.TimeoutException;
import com.android.ddmuilib.DevicePanel;
import com.android.ddmuilib.DevicePanel.IUiSelectionListener;
import com.android.ddmuilib.ImageLoader;
import com.android.ddmuilib.ScreenShotDialog;
import com.android.ddmuilib.SyncProgressHelper;
import com.android.ddmuilib.SyncProgressHelper.SyncRunnable;
import com.android.ddmuilib.handler.BaseFileHandler;
import com.android.ddmuilib.handler.MethodProfilingHandler;
import com.android.ide.eclipse.ddms.DdmsPlugin;
import com.android.ide.eclipse.ddms.IClientAction;
import com.android.ide.eclipse.ddms.IDebuggerConnector;
import com.android.ide.eclipse.ddms.editors.UiAutomatorViewer;
import com.android.ide.eclipse.ddms.i18n.Messages;
import com.android.ide.eclipse.ddms.preferences.PreferenceInitializer;
import com.android.ide.eclipse.ddms.systrace.ISystraceOptions;
import com.android.ide.eclipse.ddms.systrace.ISystraceOptionsDialog;
import com.android.ide.eclipse.ddms.systrace.SystraceOptionsDialogV1;
import com.android.ide.eclipse.ddms.systrace.SystraceOptionsDialogV2;
import com.android.ide.eclipse.ddms.systrace.SystraceOutputParser;
import com.android.ide.eclipse.ddms.systrace.SystraceTask;
import com.android.ide.eclipse.ddms.systrace.SystraceVersionDetector;
import com.android.uiautomator.UiAutomatorHelper;
import com.android.uiautomator.UiAutomatorHelper.UiAutomatorException;
import com.android.uiautomator.UiAutomatorHelper.UiAutomatorResult;
import com.google.common.io.Files;

import org.eclipse.core.filesystem.EFS;
import org.eclipse.core.filesystem.IFileStore;
import org.eclipse.core.runtime.IAdaptable;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Path;
import org.eclipse.core.runtime.Status;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.action.IAction;
import org.eclipse.jface.action.IMenuManager;
import org.eclipse.jface.action.IToolBarManager;
import org.eclipse.jface.action.Separator;
import org.eclipse.jface.dialogs.ErrorDialog;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.dialogs.ProgressMonitorDialog;
import org.eclipse.jface.operation.IRunnableWithProgress;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.ui.IActionBars;
import org.eclipse.ui.ISharedImages;
import org.eclipse.ui.IWorkbench;
import org.eclipse.ui.IWorkbenchPage;
import org.eclipse.ui.IWorkbenchWindow;
import org.eclipse.ui.PartInitException;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.WorkbenchException;
import org.eclipse.ui.ide.IDE;
import org.eclipse.ui.part.ViewPart;

import java.io.File;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;

public class DeviceView extends ViewPart implements IUiSelectionListener, IClientChangeListener {

    private final static boolean USE_SELECTED_DEBUG_PORT = true;

    public static final String ID = "com.android.ide.eclipse.ddms.views.DeviceView"; //$NON-NLS-1$

    private static DeviceView sThis;

    private Shell mParentShell;
    private DevicePanel mDeviceList;

    private Action mResetAdbAction;
    private Action mCaptureAction;
    private Action mViewUiAutomatorHierarchyAction;
    private Action mSystraceAction;
    private Action mUpdateThreadAction;
    private Action mUpdateHeapAction;
    private Action mGcAction;
    private Action mKillAppAction;
    private Action mDebugAction;
    private Action mHprofAction;
    private Action mTracingAction;

    private ImageDescriptor mTracingStartImage;
    private ImageDescriptor mTracingStopImage;

    public class HProfHandler extends BaseFileHandler implements IHprofDumpHandler {
        public final static String ACTION_SAVE = "hprof.save"; //$NON-NLS-1$
        public final static String ACTION_OPEN = "hprof.open"; //$NON-NLS-1$

        public final static String DOT_HPROF = ".hprof"; //$NON-NLS-1$

        HProfHandler(Shell parentShell) {
            super(parentShell);
        }

        @Override
        protected String getDialogTitle() {
            return Messages.DeviceView_HPROF_Error;
        }

        @Override
        public void onEndFailure(final Client client, final String message) {
            mParentShell.getDisplay().asyncExec(new Runnable() {
                @Override
                public void run() {
                    try {
                        displayErrorFromUiThread(Messages.DeviceView_Unable_Create_HPROF_For_Application,
                                client.getClientData().getClientDescription(),
                                message != null ? message + "\n\n" : ""); //$NON-NLS-1$ //$NON-NLS-2$
                    } finally {
                        // this will make sure the dump hprof button is
                        // re-enabled for the
                        // current selection. as the client is finished dumping
                        // an hprof file
                        doSelectionChanged(mDeviceList.getSelectedClient());
                    }
                }
            });
        }

        @Override
        public void onSuccess(final String remoteFilePath, final Client client) {
            mParentShell.getDisplay().asyncExec(new Runnable() {
                @Override
                public void run() {
                    final IDevice device = client.getDevice();
                    try {
                        // get the sync service to pull the HPROF file
                        final SyncService sync = client.getDevice().getSyncService();
                        if (sync != null) {
                            // get from the preference what action to take
                            IPreferenceStore store = DdmsPlugin.getDefault().getPreferenceStore();
                            String value = store.getString(PreferenceInitializer.ATTR_HPROF_ACTION);

                            if (ACTION_OPEN.equals(value)) {
                                File temp = File.createTempFile("android", DOT_HPROF); //$NON-NLS-1$
                                final String tempPath = temp.getAbsolutePath();
                                SyncProgressHelper.run(new SyncRunnable() {

                                    @Override
                                    public void run(ISyncProgressMonitor monitor)
                                            throws SyncException, IOException, TimeoutException {
                                        sync.pullFile(remoteFilePath, tempPath, monitor);
                                    }

                                    @Override
                                    public void close() {
                                        sync.close();
                                    }
                                }, String.format(Messages.DeviceView_Pulling_From_Device, remoteFilePath),
                                        mParentShell);

                                open(tempPath);
                            } else {
                                // default action is ACTION_SAVE
                                promptAndPull(sync, client.getClientData().getClientDescription() + DOT_HPROF,
                                        remoteFilePath, Messages.DeviceView_Save_HPROF_File);

                            }
                        } else {
                            displayErrorFromUiThread(
                                    Messages.DeviceView_Unable_Download_HPROF_From_Device_One_Param_First_Message,
                                    device.getSerialNumber());
                        }
                    } catch (SyncException e) {
                        if (e.wasCanceled() == false) {
                            displayErrorFromUiThread(
                                    Messages.DeviceView_Unable_Download_HPROF_From_Device_Two_Param,
                                    device.getSerialNumber(), e.getMessage());
                        }
                    } catch (Exception e) {
                        displayErrorFromUiThread(
                                Messages.DeviceView_Unable_Download_HPROF_From_Device_One_Param_Second_Message,
                                device.getSerialNumber());

                    } finally {
                        // this will make sure the dump hprof button is
                        // re-enabled for the
                        // current selection. as the client is finished dumping
                        // an hprof file
                        doSelectionChanged(mDeviceList.getSelectedClient());
                    }
                }
            });
        }

        @Override
        public void onSuccess(final byte[] data, final Client client) {
            mParentShell.getDisplay().asyncExec(new Runnable() {
                @Override
                public void run() {
                    // get from the preference what action to take
                    IPreferenceStore store = DdmsPlugin.getDefault().getPreferenceStore();
                    String value = store.getString(PreferenceInitializer.ATTR_HPROF_ACTION);

                    if (ACTION_OPEN.equals(value)) {
                        try {
                            // no need to give an extension since we're going to
                            // convert the
                            // file anyway after.
                            File tempFile = saveTempFile(data, null /* extension */);
                            open(tempFile.getAbsolutePath());
                        } catch (Exception e) {
                            String errorMsg = e.getMessage();
                            displayErrorFromUiThread(Messages.DeviceView_Failed_To_Save_HPROF_Data,
                                    errorMsg != null ? ":\n" + errorMsg : "."); //$NON-NLS-1$ //$NON-NLS-2$
                        }
                    } else {
                        // default action is ACTION_SAVE
                        promptAndSave(client.getClientData().getClientDescription() + DOT_HPROF, data,
                                Messages.DeviceView_Save_HPROF_File);
                    }
                }
            });
        }

        private void open(String path) throws IOException, InterruptedException, PartInitException {
            // make a temp file to convert the hprof into something
            // readable by normal tools
            File temp = File.createTempFile("android", DOT_HPROF); //$NON-NLS-1$
            String tempPath = temp.getAbsolutePath();

            String[] command = new String[3];
            command[0] = DdmsPlugin.getHprofConverter();
            command[1] = path;
            command[2] = tempPath;

            Process p = Runtime.getRuntime().exec(command);
            p.waitFor();

            IFileStore fileStore = EFS.getLocalFileSystem().getStore(new Path(tempPath));
            if (!fileStore.fetchInfo().isDirectory() && fileStore.fetchInfo().exists()) {
                // before we open the file in an editor window, we make sure the
                // current
                // workbench page has an editor area (typically the ddms
                // perspective doesn't).
                IWorkbench workbench = PlatformUI.getWorkbench();
                IWorkbenchWindow window = workbench.getActiveWorkbenchWindow();
                IWorkbenchPage page = window.getActivePage();
                if (page == null) {
                    return;
                }

                if (page.isEditorAreaVisible() == false) {
                    IAdaptable input;
                    input = page.getInput();
                    try {
                        workbench.showPerspective("org.eclipse.debug.ui.DebugPerspective", //$NON-NLS-1$
                                window, input);
                    } catch (WorkbenchException e) {
                    }
                }

                IDE.openEditorOnFileStore(page, fileStore);
            }
        }
    }

    public DeviceView() {
        // the view is declared with allowMultiple="false" so we
        // can safely do this.
        sThis = this;
    }

    public static DeviceView getInstance() {
        return sThis;
    }

    @Override
    public void createPartControl(Composite parent) {
        mParentShell = parent.getShell();

        ImageLoader loader = ImageLoader.getDdmUiLibLoader();

        mDeviceList = new DevicePanel(USE_SELECTED_DEBUG_PORT);
        mDeviceList.createPanel(parent);
        mDeviceList.addSelectionListener(this);

        DdmsPlugin plugin = DdmsPlugin.getDefault();
        mDeviceList.addSelectionListener(plugin);
        plugin.setListeningState(true);

        mCaptureAction = new Action(Messages.DeviceView_Screen_Capture) {
            @Override
            public void run() {
                ScreenShotDialog dlg = new ScreenShotDialog(DdmsPlugin.getDisplay().getActiveShell());
                dlg.open(mDeviceList.getSelectedDevice());
            }
        };
        mCaptureAction.setToolTipText(Messages.DeviceView_Screen_Capture_Tooltip);
        mCaptureAction.setImageDescriptor(loader.loadDescriptor("capture.png")); //$NON-NLS-1$

        mViewUiAutomatorHierarchyAction = new Action("Dump View Hierarchy for UI Automator") {
            @Override
            public void run() {
                takeUiAutomatorSnapshot(mDeviceList.getSelectedDevice(), DdmsPlugin.getDisplay().getActiveShell());
            }
        };
        mViewUiAutomatorHierarchyAction.setToolTipText("Dump View Hierarchy for UI Automator");
        mViewUiAutomatorHierarchyAction.setImageDescriptor(DdmsPlugin.getImageDescriptor("icons/uiautomator.png")); //$NON-NLS-1$

        mSystraceAction = new Action("Capture System Wide Trace") {
            @Override
            public void run() {
                launchSystrace(mDeviceList.getSelectedDevice(), DdmsPlugin.getDisplay().getActiveShell());
            }
        };
        mSystraceAction.setToolTipText("Capture system wide trace using Android systrace");
        mSystraceAction.setImageDescriptor(DdmsPlugin.getImageDescriptor("icons/systrace.png")); //$NON-NLS-1$
        mSystraceAction.setEnabled(true);

        mResetAdbAction = new Action(Messages.DeviceView_Reset_ADB) {
            @Override
            public void run() {
                AndroidDebugBridge bridge = AndroidDebugBridge.getBridge();
                if (bridge != null) {
                    if (bridge.restart() == false) {
                        // get the current Display
                        final Display display = DdmsPlugin.getDisplay();

                        // dialog box only run in ui thread..
                        display.asyncExec(new Runnable() {
                            @Override
                            public void run() {
                                Shell shell = display.getActiveShell();
                                MessageDialog.openError(shell, Messages.DeviceView_ADB_Error,
                                        Messages.DeviceView_ADB_Failed_Restart);
                            }
                        });
                    }
                }
            }
        };
        mResetAdbAction.setToolTipText(Messages.DeviceView_Reset_ADB_Host_Deamon);
        mResetAdbAction.setImageDescriptor(
                PlatformUI.getWorkbench().getSharedImages().getImageDescriptor(ISharedImages.IMG_OBJS_WARN_TSK));

        mKillAppAction = new Action() {
            @Override
            public void run() {
                mDeviceList.killSelectedClient();
            }
        };

        mKillAppAction.setText(Messages.DeviceView_Stop_Process);
        mKillAppAction.setToolTipText(Messages.DeviceView_Stop_Process_Tooltip);
        mKillAppAction.setImageDescriptor(loader.loadDescriptor(DevicePanel.ICON_HALT));

        mGcAction = new Action() {
            @Override
            public void run() {
                mDeviceList.forceGcOnSelectedClient();
            }
        };

        mGcAction.setText(Messages.DeviceView_Cause_GC);
        mGcAction.setToolTipText(Messages.DeviceView_Cause_GC_Tooltip);
        mGcAction.setImageDescriptor(loader.loadDescriptor(DevicePanel.ICON_GC));

        mHprofAction = new Action() {
            @Override
            public void run() {
                mDeviceList.dumpHprof();
                doSelectionChanged(mDeviceList.getSelectedClient());
            }
        };
        mHprofAction.setText(Messages.DeviceView_Dump_HPROF_File);
        mHprofAction.setToolTipText(Messages.DeviceView_Dump_HPROF_File_Tooltip);
        mHprofAction.setImageDescriptor(loader.loadDescriptor(DevicePanel.ICON_HPROF));

        mUpdateHeapAction = new Action(Messages.DeviceView_Update_Heap, IAction.AS_CHECK_BOX) {
            @Override
            public void run() {
                boolean enable = mUpdateHeapAction.isChecked();
                mDeviceList.setEnabledHeapOnSelectedClient(enable);
            }
        };
        mUpdateHeapAction.setToolTipText(Messages.DeviceView_Update_Heap_Tooltip);
        mUpdateHeapAction.setImageDescriptor(loader.loadDescriptor(DevicePanel.ICON_HEAP));

        mUpdateThreadAction = new Action(Messages.DeviceView_Threads, IAction.AS_CHECK_BOX) {
            @Override
            public void run() {
                boolean enable = mUpdateThreadAction.isChecked();
                mDeviceList.setEnabledThreadOnSelectedClient(enable);
            }
        };
        mUpdateThreadAction.setToolTipText(Messages.DeviceView_Threads_Tooltip);
        mUpdateThreadAction.setImageDescriptor(loader.loadDescriptor(DevicePanel.ICON_THREAD));

        mTracingAction = new Action() {
            @Override
            public void run() {
                mDeviceList.toggleMethodProfiling();
            }
        };
        mTracingAction.setText(Messages.DeviceView_Start_Method_Profiling);
        mTracingAction.setToolTipText(Messages.DeviceView_Start_Method_Profiling_Tooltip);
        mTracingStartImage = loader.loadDescriptor(DevicePanel.ICON_TRACING_START);
        mTracingStopImage = loader.loadDescriptor(DevicePanel.ICON_TRACING_STOP);
        mTracingAction.setImageDescriptor(mTracingStartImage);

        mDebugAction = new Action(Messages.DeviceView_Debug_Process) {
            @Override
            public void run() {
                if (DdmsPlugin.getDefault().hasDebuggerConnectors()) {
                    Client currentClient = mDeviceList.getSelectedClient();
                    if (currentClient != null) {
                        ClientData clientData = currentClient.getClientData();

                        // make sure the client can be debugged
                        switch (clientData.getDebuggerConnectionStatus()) {
                        case ERROR: {
                            Display display = DdmsPlugin.getDisplay();
                            Shell shell = display.getActiveShell();
                            MessageDialog.openError(shell, Messages.DeviceView_Debug_Process_Title,
                                    Messages.DeviceView_Process_Debug_Already_In_Use);
                            return;
                        }
                        case ATTACHED: {
                            Display display = DdmsPlugin.getDisplay();
                            Shell shell = display.getActiveShell();
                            MessageDialog.openError(shell, Messages.DeviceView_Debug_Process_Title,
                                    Messages.DeviceView_Process_Already_Being_Debugged);
                            return;
                        }
                        }

                        // get the name of the client
                        String packageName = clientData.getClientDescription();
                        if (packageName != null) {

                            // try all connectors till one returns true.
                            IDebuggerConnector[] connectors = DdmsPlugin.getDefault().getDebuggerConnectors();

                            if (connectors != null) {
                                for (IDebuggerConnector connector : connectors) {
                                    try {
                                        if (connector.connectDebugger(packageName,
                                                currentClient.getDebuggerListenPort(),
                                                DdmPreferences.getSelectedDebugPort())) {
                                            return;
                                        }
                                    } catch (Throwable t) {
                                        // ignore, we'll just not use this
                                        // implementation
                                    }
                                }
                            }

                            // if we get to this point, then we failed to find a
                            // project
                            // that matched the application to debug
                            Display display = DdmsPlugin.getDisplay();
                            Shell shell = display.getActiveShell();
                            MessageDialog.openError(shell, Messages.DeviceView_Debug_Process_Title,
                                    String.format(Messages.DeviceView_Debug_Session_Failed, packageName));
                        }
                    }
                }
            }
        };
        mDebugAction.setToolTipText(Messages.DeviceView_Debug_Process_Tooltip);
        mDebugAction.setImageDescriptor(loader.loadDescriptor("debug-attach.png")); //$NON-NLS-1$
        mDebugAction.setEnabled(DdmsPlugin.getDefault().hasDebuggerConnectors());

        placeActions();

        // disabling all action buttons
        selectionChanged(null, null);

        ClientData.setHprofDumpHandler(new HProfHandler(mParentShell));
        AndroidDebugBridge.addClientChangeListener(this);
        ClientData.setMethodProfilingHandler(new MethodProfilingHandler(mParentShell) {
            @Override
            protected void open(String tempPath) {
                if (DdmsPlugin.getDefault().launchTraceview(tempPath) == false) {
                    super.open(tempPath);
                }
            }
        });
    }

    private void takeUiAutomatorSnapshot(final IDevice device, final Shell shell) {
        ProgressMonitorDialog dialog = new ProgressMonitorDialog(shell);
        try {
            dialog.run(true, false, new IRunnableWithProgress() {
                @Override
                public void run(IProgressMonitor monitor) throws InvocationTargetException, InterruptedException {
                    UiAutomatorResult result = null;
                    try {
                        result = UiAutomatorHelper.takeSnapshot(device, monitor);
                    } catch (UiAutomatorException e) {
                        throw new InvocationTargetException(e);
                    } finally {
                        monitor.done();
                    }

                    UiAutomatorViewer.openEditor(result);
                }
            });
        } catch (Exception e) {
            Throwable t = e;
            if (e instanceof InvocationTargetException) {
                t = ((InvocationTargetException) e).getTargetException();
            }
            Status s = new Status(IStatus.ERROR, DdmsPlugin.PLUGIN_ID, "Error obtaining UI hierarchy", t);
            ErrorDialog.openError(shell, "UI Automator", "Unexpected error while obtaining UI hierarchy", s);
        }
    };

    private void launchSystrace(final IDevice device, final Shell parentShell) {
        final File systraceAssets = new File(DdmsPlugin.getPlatformToolsFolder(), "systrace"); //$NON-NLS-1$
        if (!systraceAssets.isDirectory()) {
            MessageDialog.openError(parentShell, "Systrace",
                    "Updated version of platform-tools (18.0.1 or greater) is required.\n"
                            + "Please update your platform-tools using SDK Manager.");
            return;
        }

        SystraceVersionDetector detector = new SystraceVersionDetector(device);
        try {
            new ProgressMonitorDialog(parentShell).run(true, false, detector);
        } catch (InvocationTargetException e) {
            MessageDialog.openError(parentShell, "Systrace",
                    "Unexpected error while detecting atrace version: " + e);
            return;
        } catch (InterruptedException e) {
            return;
        }

        final ISystraceOptionsDialog dlg;
        if (detector.getVersion() == SystraceVersionDetector.SYSTRACE_V1) {
            dlg = new SystraceOptionsDialogV1(parentShell);
        } else {
            Client[] clients = device.getClients();
            List<String> apps = new ArrayList<String>(clients.length);
            for (int i = 0; i < clients.length; i++) {
                String name = clients[i].getClientData().getClientDescription();
                if (name != null && !name.isEmpty()) {
                    apps.add(name);
                }
            }
            dlg = new SystraceOptionsDialogV2(parentShell, detector.getTags(), apps);
        }

        if (dlg.open() != SystraceOptionsDialogV1.OK) {
            return;
        }

        final ISystraceOptions options = dlg.getSystraceOptions();

        // set trace tag if necessary:
        //      adb shell setprop debug.atrace.tags.enableflags <tag>
        String tag = options.getTags();
        if (tag != null) {
            CountDownLatch setTagLatch = new CountDownLatch(1);
            CollectingOutputReceiver receiver = new CollectingOutputReceiver(setTagLatch);
            try {
                String cmd = "setprop debug.atrace.tags.enableflags " + tag;
                device.executeShellCommand(cmd, receiver);
                setTagLatch.await(5, TimeUnit.SECONDS);
            } catch (Exception e) {
                MessageDialog.openError(parentShell, "Systrace", "Unexpected error while setting trace tags: " + e);
                return;
            }

            String shellOutput = receiver.getOutput();
            if (shellOutput.contains("Error type")) { //$NON-NLS-1$
                throw new RuntimeException(receiver.getOutput());
            }
        }

        // obtain the output of "adb shell atrace <trace-options>" and generate the html file
        ProgressMonitorDialog d = new ProgressMonitorDialog(parentShell);
        try {
            d.run(true, true, new IRunnableWithProgress() {
                @Override
                public void run(IProgressMonitor monitor) throws InvocationTargetException, InterruptedException {
                    boolean COMPRESS_DATA = true;

                    monitor.setTaskName("Collecting Trace Information");
                    final String atraceOptions = options.getOptions() + (COMPRESS_DATA ? " -z" : "");
                    SystraceTask task = new SystraceTask(device, atraceOptions);
                    Thread t = new Thread(task, "Systrace Output Receiver");
                    t.start();

                    // check if the user has cancelled tracing every so often
                    while (true) {
                        t.join(1000);

                        if (t.isAlive()) {
                            if (monitor.isCanceled()) {
                                task.cancel();
                                return;
                            }
                        } else {
                            break;
                        }
                    }

                    if (task.getError() != null) {
                        throw new RuntimeException(task.getError());
                    }

                    monitor.setTaskName("Saving trace information");
                    SystraceOutputParser parser = new SystraceOutputParser(COMPRESS_DATA,
                            SystraceOutputParser.getJs(systraceAssets), SystraceOutputParser.getCss(systraceAssets),
                            SystraceOutputParser.getHtmlPrefix(systraceAssets),
                            SystraceOutputParser.getHtmlSuffix(systraceAssets));

                    parser.parse(task.getAtraceOutput());

                    String html = parser.getSystraceHtml();
                    try {
                        Files.write(html.getBytes(), new File(dlg.getTraceFilePath()));
                    } catch (IOException e) {
                        throw new InvocationTargetException(e);
                    }
                }
            });
        } catch (InvocationTargetException e) {
            ErrorDialog.openError(parentShell, "Systrace", "Unable to collect system trace.",
                    new Status(Status.ERROR, DdmsPlugin.PLUGIN_ID,
                            "Unexpected error while collecting system trace.", e.getCause()));
        } catch (InterruptedException ignore) {
        }
    }

    @Override
    public void setFocus() {
        mDeviceList.setFocus();
    }

    /**
     * Sent when a new {@link IDevice} and {@link Client} are selected.
     *
     * @param selectedDevice the selected device. If null, no devices are
     *            selected.
     * @param selectedClient The selected client. If null, no clients are
     *            selected.
     */
    @Override
    public void selectionChanged(IDevice selectedDevice, Client selectedClient) {
        // update the buttons
        doSelectionChanged(selectedClient);
        doSelectionChanged(selectedDevice);
    }

    private void doSelectionChanged(Client selectedClient) {
        // update the buttons
        if (selectedClient != null) {
            if (USE_SELECTED_DEBUG_PORT) {
                // set the client as the debug client
                selectedClient.setAsSelectedClient();
            }

            mDebugAction.setEnabled(DdmsPlugin.getDefault().hasDebuggerConnectors());
            mKillAppAction.setEnabled(true);
            mGcAction.setEnabled(true);

            mUpdateHeapAction.setEnabled(true);
            mUpdateHeapAction.setChecked(selectedClient.isHeapUpdateEnabled());

            mUpdateThreadAction.setEnabled(true);
            mUpdateThreadAction.setChecked(selectedClient.isThreadUpdateEnabled());

            ClientData data = selectedClient.getClientData();

            if (data.hasFeature(ClientData.FEATURE_HPROF)) {
                mHprofAction.setEnabled(data.hasPendingHprofDump() == false);
                mHprofAction.setToolTipText(Messages.DeviceView_Dump_HPROF_File);
            } else {
                mHprofAction.setEnabled(false);
                mHprofAction.setToolTipText(Messages.DeviceView_Dump_HPROF_File_Not_Supported_By_VM);
            }

            if (data.hasFeature(ClientData.FEATURE_PROFILING)) {
                mTracingAction.setEnabled(true);
                if (data.getMethodProfilingStatus() == MethodProfilingStatus.TRACER_ON
                        || data.getMethodProfilingStatus() == MethodProfilingStatus.SAMPLER_ON) {
                    mTracingAction.setToolTipText(Messages.DeviceView_Stop_Method_Profiling_Tooltip);
                    mTracingAction.setText(Messages.DeviceView_Stop_Method_Profiling);
                    mTracingAction.setImageDescriptor(mTracingStopImage);
                } else {
                    mTracingAction.setToolTipText(Messages.DeviceView_Start_Method_Profiling_Tooltip);
                    mTracingAction.setImageDescriptor(mTracingStartImage);
                    mTracingAction.setText(Messages.DeviceView_Start_Method_Profiling);
                }
            } else {
                mTracingAction.setEnabled(false);
                mTracingAction.setImageDescriptor(mTracingStartImage);
                mTracingAction.setToolTipText(Messages.DeviceView_Start_Method_Profiling_Not_Suported_By_Vm);
                mTracingAction.setText(Messages.DeviceView_Start_Method_Profiling);
            }
        } else {
            if (USE_SELECTED_DEBUG_PORT) {
                // set the client as the debug client
                AndroidDebugBridge bridge = AndroidDebugBridge.getBridge();
                if (bridge != null) {
                    bridge.setSelectedClient(null);
                }
            }

            mDebugAction.setEnabled(false);
            mKillAppAction.setEnabled(false);
            mGcAction.setEnabled(false);
            mUpdateHeapAction.setChecked(false);
            mUpdateHeapAction.setEnabled(false);
            mUpdateThreadAction.setEnabled(false);
            mUpdateThreadAction.setChecked(false);
            mHprofAction.setEnabled(false);

            mHprofAction.setEnabled(false);
            mHprofAction.setToolTipText(Messages.DeviceView_Dump_HPROF_File);

            mTracingAction.setEnabled(false);
            mTracingAction.setImageDescriptor(mTracingStartImage);
            mTracingAction.setToolTipText(Messages.DeviceView_Start_Method_Profiling_Tooltip);
            mTracingAction.setText(Messages.DeviceView_Start_Method_Profiling);
        }

        for (IClientAction a : DdmsPlugin.getDefault().getClientSpecificActions()) {
            a.selectedClientChanged(selectedClient);
        }
    }

    private void doSelectionChanged(IDevice selectedDevice) {
        boolean validDevice = selectedDevice != null;

        mCaptureAction.setEnabled(validDevice);
        mViewUiAutomatorHierarchyAction.setEnabled(validDevice);
        mSystraceAction.setEnabled(validDevice);
    }

    /**
     * Place the actions in the ui.
     */
    private final void placeActions() {
        IActionBars actionBars = getViewSite().getActionBars();

        // first in the menu
        IMenuManager menuManager = actionBars.getMenuManager();
        menuManager.removeAll();
        menuManager.add(mDebugAction);
        menuManager.add(new Separator());
        menuManager.add(mUpdateHeapAction);
        menuManager.add(mHprofAction);
        menuManager.add(mGcAction);
        menuManager.add(new Separator());
        menuManager.add(mUpdateThreadAction);
        menuManager.add(mTracingAction);
        menuManager.add(new Separator());
        menuManager.add(mKillAppAction);
        menuManager.add(new Separator());
        menuManager.add(mCaptureAction);
        menuManager.add(new Separator());
        menuManager.add(mViewUiAutomatorHierarchyAction);
        menuManager.add(new Separator());
        menuManager.add(mSystraceAction);
        menuManager.add(new Separator());
        menuManager.add(mResetAdbAction);
        for (IClientAction a : DdmsPlugin.getDefault().getClientSpecificActions()) {
            menuManager.add(a.getAction());
        }

        // and then in the toolbar
        IToolBarManager toolBarManager = actionBars.getToolBarManager();
        toolBarManager.removeAll();
        toolBarManager.add(mDebugAction);
        toolBarManager.add(new Separator());
        toolBarManager.add(mUpdateHeapAction);
        toolBarManager.add(mHprofAction);
        toolBarManager.add(mGcAction);
        toolBarManager.add(new Separator());
        toolBarManager.add(mUpdateThreadAction);
        toolBarManager.add(mTracingAction);
        toolBarManager.add(new Separator());
        toolBarManager.add(mKillAppAction);
        toolBarManager.add(new Separator());
        toolBarManager.add(mCaptureAction);
        toolBarManager.add(new Separator());
        toolBarManager.add(mViewUiAutomatorHierarchyAction);
        toolBarManager.add(new Separator());
        toolBarManager.add(mSystraceAction);
        for (IClientAction a : DdmsPlugin.getDefault().getClientSpecificActions()) {
            toolBarManager.add(a.getAction());
        }
    }

    @Override
    public void clientChanged(final Client client, int changeMask) {
        if ((changeMask & Client.CHANGE_METHOD_PROFILING_STATUS) == Client.CHANGE_METHOD_PROFILING_STATUS) {
            if (mDeviceList.getSelectedClient() == client) {
                mParentShell.getDisplay().asyncExec(new Runnable() {
                    @Override
                    public void run() {
                        // force refresh of the button enabled state.
                        doSelectionChanged(client);
                    }
                });
            }
        }
    }
}