blob: 50eee2633f4d3c46e09682d00628d51a12fb56d6 [file] [log] [blame]
/*
* 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.Client;
import com.android.ddmlib.ClientData;
import com.android.ddmlib.IDevice;
import com.android.ddmlib.SyncService;
import com.android.ddmlib.ClientData.IHprofDumpHandler;
import com.android.ddmlib.SyncService.SyncResult;
import com.android.ddmuilib.DevicePanel;
import com.android.ddmuilib.ScreenShotDialog;
import com.android.ddmuilib.SyncProgressMonitor;
import com.android.ddmuilib.DevicePanel.IUiSelectionListener;
import com.android.ide.eclipse.ddms.DdmsPlugin;
import com.android.ide.eclipse.ddms.DdmsPlugin.IDebugLauncher;
import com.android.ide.eclipse.ddms.preferences.PreferenceInitializer;
import org.eclipse.core.filesystem.EFS;
import org.eclipse.core.filesystem.IFileStore;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.Path;
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.MessageDialog;
import org.eclipse.jface.dialogs.ProgressMonitorDialog;
import org.eclipse.jface.operation.IRunnableWithProgress;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.swt.SWT;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.FileDialog;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.ui.IActionBars;
import org.eclipse.ui.ISharedImages;
import org.eclipse.ui.PartInitException;
import org.eclipse.ui.PlatformUI;
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;
public class DeviceView extends ViewPart implements IUiSelectionListener {
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 DevicePanel mDeviceList;
private Action mResetAdbAction;
private Action mCaptureAction;
private Action mUpdateThreadAction;
private Action mUpdateHeapAction;
private Action mGcAction;
private Action mKillAppAction;
private Action mDebugAction;
private Action mHprofAction;
private IDebugLauncher mDebugLauncher;
public class HProfHandler 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$
private final Shell mParentShell;
HProfHandler(Shell parentShell) {
mParentShell = parentShell;
}
public void onFailure(final Client client) {
mParentShell.getDisplay().asyncExec(new Runnable() {
public void run() {
try {
MessageDialog.openError(mParentShell, "HPROF Error",
String.format(
"Unable to create HPROF file for application '%1$s'.\n" +
"Check logcat for more information.",
client.getClientData().getClientDescription()));
} 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());
}
}
});
}
public void onSuccess(final String remoteFile, final Client client) {
mParentShell.getDisplay().asyncExec(new Runnable() {
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$
String tempPath = temp.getAbsolutePath();
pull(sync, tempPath, remoteFile);
open(tempPath);
} else {
// default action is ACTION_SAVE
promptAndPull(device, client, sync, remoteFile);
}
} else {
MessageDialog.openError(mParentShell, "HPROF Error",
String.format(
"Unable to download HPROF file from device '%1$s'.",
device.getSerialNumber()));
}
} catch (Exception e) {
MessageDialog.openError(mParentShell, "HPROF Error",
String.format("Unable to download HPROF file from device '%1$s'.",
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());
}
}
});
}
private void promptAndPull(final IDevice device, final Client client,
final SyncService sync, final String remoteFile) {
try {
FileDialog fileDialog = new FileDialog(mParentShell, SWT.SAVE);
fileDialog.setText("Save HPROF file");
fileDialog.setFileName(
client.getClientData().getClientDescription() + DOT_HPROF);
final String localFileName = fileDialog.open();
if (localFileName != null) {
pull(sync, localFileName, remoteFile);
}
} catch (Exception e) {
MessageDialog.openError(mParentShell, "HPROF Error",
String.format("Unable to download HPROF file from device '%1$s'.",
device.getSerialNumber()));
}
}
private void pull(final SyncService sync,
final String localFileName, final String remoteFile)
throws InvocationTargetException, InterruptedException {
new ProgressMonitorDialog(mParentShell).run(true, true,
new IRunnableWithProgress() {
public void run(IProgressMonitor monitor) {
SyncResult result = sync.pullFile(remoteFile, localFileName,
new SyncProgressMonitor(monitor, String.format(
"Pulling %1$s from the device",
remoteFile)));
if (result.getCode() != SyncService.RESULT_OK) {
MessageDialog.openError(mParentShell, "HPROF Error",
String.format("Failed to pull %1$s: %2$s", remoteFile,
result.getMessage()));
}
sync.close();
}
});
}
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);
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()) {
IDE.openEditorOnFileStore(
getSite().getWorkbenchWindow().getActivePage(),
fileStore);
}
}
}
public DeviceView() {
// the view is declared with allowMultiple="false" so we
// can safely do this.
sThis = this;
}
public static DeviceView getInstance() {
return sThis;
}
/**
* Sets the {@link IDebugLauncher}.
* @param debugLauncher
*/
public void setDebugLauncher(DdmsPlugin.IDebugLauncher debugLauncher) {
mDebugLauncher = debugLauncher;
if (mDebugAction != null && mDeviceList != null) {
Client currentClient = mDeviceList.getSelectedClient();
if (currentClient != null) {
mDebugAction.setEnabled(true);
}
}
}
@Override
public void createPartControl(Composite parent) {
ClientData.setHprofDumpHandler(new HProfHandler(parent.getShell()));
mDeviceList = new DevicePanel(DdmsPlugin.getImageLoader(), USE_SELECTED_DEBUG_PORT);
mDeviceList.createPanel(parent);
mDeviceList.addSelectionListener(this);
DdmsPlugin plugin = DdmsPlugin.getDefault();
mDeviceList.addSelectionListener(plugin);
plugin.setListeningState(true);
mCaptureAction = new Action("Screen Capture") {
@Override
public void run() {
ScreenShotDialog dlg = new ScreenShotDialog(
DdmsPlugin.getDisplay().getActiveShell());
dlg.open(mDeviceList.getSelectedDevice());
}
};
mCaptureAction.setToolTipText("Screen Capture");
mCaptureAction.setImageDescriptor(
DdmsPlugin.getImageLoader().loadDescriptor("capture.png")); //$NON-NLS-1$
mResetAdbAction = new Action("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() {
public void run() {
Shell shell = display.getActiveShell();
MessageDialog.openError(shell, "Adb Error",
"Adb failed to restart!\n\nMake sure the plugin is properly configured.");
}
});
}
}
}
};
mResetAdbAction.setToolTipText("Reset the adb host daemon");
mResetAdbAction.setImageDescriptor(PlatformUI.getWorkbench()
.getSharedImages().getImageDescriptor(
ISharedImages.IMG_OBJS_WARN_TSK));
mKillAppAction = new Action() {
@Override
public void run() {
mDeviceList.killSelectedClient();
}
};
mKillAppAction.setText("Stop Process");
mKillAppAction.setToolTipText("Stop Process");
mKillAppAction.setImageDescriptor(DdmsPlugin.getImageLoader()
.loadDescriptor(DevicePanel.ICON_HALT));
mGcAction = new Action() {
@Override
public void run() {
mDeviceList.forceGcOnSelectedClient();
}
};
mGcAction.setText("Cause GC");
mGcAction.setToolTipText("Cause GC");
mGcAction.setImageDescriptor(DdmsPlugin.getImageLoader()
.loadDescriptor(DevicePanel.ICON_GC));
mHprofAction = new Action() {
@Override
public void run() {
mDeviceList.dumpHprof();
doSelectionChanged(mDeviceList.getSelectedClient());
}
};
mHprofAction.setText("Dump HPROF file");
mHprofAction.setToolTipText("Dump HPROF file");
mHprofAction.setImageDescriptor(DdmsPlugin.getImageLoader()
.loadDescriptor(DevicePanel.ICON_HPROF));
mUpdateHeapAction = new Action("Update Heap", IAction.AS_CHECK_BOX) {
@Override
public void run() {
boolean enable = mUpdateHeapAction.isChecked();
mDeviceList.setEnabledHeapOnSelectedClient(enable);
}
};
mUpdateHeapAction.setToolTipText("Update Heap");
mUpdateHeapAction.setImageDescriptor(DdmsPlugin.getImageLoader()
.loadDescriptor(DevicePanel.ICON_HEAP));
mUpdateThreadAction = new Action("Update Threads", IAction.AS_CHECK_BOX) {
@Override
public void run() {
boolean enable = mUpdateThreadAction.isChecked();
mDeviceList.setEnabledThreadOnSelectedClient(enable);
}
};
mUpdateThreadAction.setToolTipText("Update Threads");
mUpdateThreadAction.setImageDescriptor(DdmsPlugin.getImageLoader()
.loadDescriptor(DevicePanel.ICON_THREAD));
// check if there's already a debug launcher set up in the plugin class
mDebugLauncher = DdmsPlugin.getRunningAppDebugLauncher();
mDebugAction = new Action("Debug Process") {
@Override
public void run() {
if (mDebugLauncher != null) {
Client currentClient = mDeviceList.getSelectedClient();
if (currentClient != null) {
ClientData clientData = currentClient.getClientData();
// make sure the client can be debugged
switch (clientData.getDebuggerConnectionStatus()) {
case ClientData.DEBUGGER_ERROR: {
Display display = DdmsPlugin.getDisplay();
Shell shell = display.getActiveShell();
MessageDialog.openError(shell, "Process Debug",
"The process debug port is already in use!");
return;
}
case ClientData.DEBUGGER_ATTACHED: {
Display display = DdmsPlugin.getDisplay();
Shell shell = display.getActiveShell();
MessageDialog.openError(shell, "Process Debug",
"The process is already being debugged!");
return;
}
}
// get the name of the client
String packageName = clientData.getClientDescription();
if (packageName != null) {
if (mDebugLauncher.debug(packageName,
currentClient.getDebuggerListenPort()) == false) {
// 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, "Process Debug",
String.format(
"No opened project found for %1$s. Debug session failed!",
packageName));
}
}
}
}
}
};
mDebugAction.setToolTipText("Debug the selected process, provided its source project is present and opened in the workspace.");
mDebugAction.setImageDescriptor(DdmsPlugin.getImageLoader()
.loadDescriptor("debug-attach.png")); //$NON-NLS-1$
if (mDebugLauncher == null) {
mDebugAction.setEnabled(false);
}
placeActions();
}
@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.
*/
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(mDebugLauncher != null);
mKillAppAction.setEnabled(true);
mGcAction.setEnabled(true);
mUpdateHeapAction.setEnabled(true);
mUpdateHeapAction.setChecked(selectedClient.isHeapUpdateEnabled());
mUpdateThreadAction.setEnabled(true);
mUpdateThreadAction.setChecked(selectedClient.isThreadUpdateEnabled());
mHprofAction.setEnabled(
selectedClient.getClientData().hasFeature(ClientData.FEATURE_HPROF) &&
selectedClient.getClientData().hasPendingHprofDump() == false);
} 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);
}
}
private void doSelectionChanged(IDevice selectedDevice) {
mCaptureAction.setEnabled(selectedDevice != null);
}
/**
* Place the actions in the ui.
*/
private final void placeActions() {
IActionBars actionBars = getViewSite().getActionBars();
// first in the menu
IMenuManager menuManager = actionBars.getMenuManager();
menuManager.add(mDebugAction);
menuManager.add(new Separator());
menuManager.add(mUpdateThreadAction);
menuManager.add(mUpdateHeapAction);
menuManager.add(new Separator());
menuManager.add(mGcAction);
menuManager.add(mHprofAction);
menuManager.add(new Separator());
menuManager.add(mKillAppAction);
menuManager.add(new Separator());
menuManager.add(mCaptureAction);
menuManager.add(new Separator());
menuManager.add(mResetAdbAction);
// and then in the toolbar
IToolBarManager toolBarManager = actionBars.getToolBarManager();
toolBarManager.add(mDebugAction);
toolBarManager.add(new Separator());
toolBarManager.add(mUpdateThreadAction);
toolBarManager.add(mUpdateHeapAction);
toolBarManager.add(new Separator());
toolBarManager.add(mGcAction);
toolBarManager.add(mHprofAction);
toolBarManager.add(new Separator());
toolBarManager.add(mKillAppAction);
toolBarManager.add(new Separator());
toolBarManager.add(mCaptureAction);
}
}