blob: e55974da85fb9c1152bd7fd9869500e6f154ad0a [file] [log] [blame]
/*
* Copyright 2000-2011 JetBrains s.r.o.
*
* 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 org.jetbrains.android.actions;
import com.android.ddmlib.AndroidDebugBridge;
import com.android.ddmlib.Client;
import com.android.ddmlib.ClientData;
import com.android.ddmlib.IDevice;
import com.android.tools.idea.ddms.adb.AdbService;
import com.android.tools.idea.model.AndroidModel;
import com.intellij.execution.*;
import com.intellij.execution.configurations.ConfigurationFactory;
import com.intellij.execution.executors.DefaultDebugExecutor;
import com.intellij.execution.process.ProcessHandler;
import com.intellij.execution.remote.RemoteConfiguration;
import com.intellij.execution.remote.RemoteConfigurationType;
import com.intellij.execution.ui.RunContentDescriptor;
import com.intellij.facet.ProjectFacetManager;
import com.intellij.ide.util.PropertiesComponent;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.DialogWrapper;
import com.intellij.openapi.ui.Messages;
import com.intellij.openapi.wm.ToolWindow;
import com.intellij.openapi.wm.ToolWindowManager;
import com.intellij.psi.XmlRecursiveElementVisitor;
import com.intellij.psi.xml.XmlAttribute;
import com.intellij.psi.xml.XmlElement;
import com.intellij.ui.DoubleClickListener;
import com.intellij.ui.JBDefaultTreeCellRenderer;
import com.intellij.ui.TreeSpeedSearch;
import com.intellij.ui.components.JBCheckBox;
import com.intellij.ui.content.Content;
import com.intellij.ui.treeStructure.Tree;
import com.intellij.util.NotNullFunction;
import com.intellij.util.containers.HashSet;
import com.intellij.util.ui.UIUtil;
import com.intellij.util.ui.tree.TreeUtil;
import com.intellij.util.ui.update.MergingUpdateQueue;
import com.intellij.util.ui.update.Update;
import org.jetbrains.android.compiler.AndroidCompileUtil;
import org.jetbrains.android.dom.manifest.Manifest;
import org.jetbrains.android.facet.AndroidFacet;
import org.jetbrains.android.sdk.AndroidSdkUtils;
import org.jetbrains.android.util.AndroidBundle;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import javax.swing.event.TreeSelectionEvent;
import javax.swing.event.TreeSelectionListener;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.TreeNode;
import javax.swing.tree.TreePath;
import java.awt.*;
import java.awt.event.*;
import java.util.Collection;
import java.util.List;
import java.util.Set;
/**
* @author Eugene.Kudelevsky
*/
public class AndroidProcessChooserDialog extends DialogWrapper {
@NonNls private static final String DEBUGGABLE_PROCESS_PROPERTY = "DEBUGGABLE_PROCESS";
@NonNls private static final String SHOW_ALL_PROCESSES_PROPERTY = "SHOW_ALL_PROCESSES";
@NonNls private static final String DEBUGGABLE_DEVICE_PROPERTY = "DEBUGGABLE_DEVICE";
@NonNls private static final String RUN_CONFIGURATION_NAME_PATTERN = "Android Debugger (%s)";
private final Project myProject;
private JPanel myContentPanel;
private Tree myProcessTree;
private JBCheckBox myShowAllProcessesCheckBox;
private String myLastSelectedDevice;
private String myLastSelectedProcess;
private final MergingUpdateQueue myUpdatesQueue;
private final AndroidDebugBridge.IClientChangeListener myClientChangeListener;
private final AndroidDebugBridge.IDeviceChangeListener myDeviceChangeListener;
protected AndroidProcessChooserDialog(@NotNull Project project) {
super(project);
setTitle("Choose Process");
myProject = project;
myUpdatesQueue =
new MergingUpdateQueue("AndroidProcessChooserDialogUpdatingQueue", 500, true, MergingUpdateQueue.ANY_COMPONENT, myProject);
final String showAllProcessesStr = PropertiesComponent.getInstance(project).getValue(SHOW_ALL_PROCESSES_PROPERTY);
final boolean showAllProcesses = Boolean.parseBoolean(showAllProcessesStr);
myShowAllProcessesCheckBox.setSelected(showAllProcesses);
doUpdateTree(showAllProcesses);
myClientChangeListener = new AndroidDebugBridge.IClientChangeListener() {
@Override
public void clientChanged(Client client, int changeMask) {
updateTree();
}
};
AndroidDebugBridge.addClientChangeListener(myClientChangeListener);
myDeviceChangeListener = new AndroidDebugBridge.IDeviceChangeListener() {
@Override
public void deviceConnected(IDevice device) {
updateTree();
}
@Override
public void deviceDisconnected(IDevice device) {
updateTree();
}
@Override
public void deviceChanged(IDevice device, int changeMask) {
updateTree();
}
};
AndroidDebugBridge.addDeviceChangeListener(myDeviceChangeListener);
myShowAllProcessesCheckBox.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
updateTree();
}
});
myProcessTree.addTreeSelectionListener(new TreeSelectionListener() {
@Override
public void valueChanged(TreeSelectionEvent e) {
IDevice selectedDevice = getSelectedDevice();
Client selectedClient = getSelectedClient();
myLastSelectedDevice = getPersistableName(selectedDevice);
myLastSelectedProcess = getPersistableName(selectedClient);
getOKAction().setEnabled(selectedDevice != null && selectedClient != null);
}
});
new TreeSpeedSearch(myProcessTree) {
@Override
protected boolean isMatchingElement(Object element, String pattern) {
if (element instanceof TreePath) {
Object lastComponent = ((TreePath)element).getLastPathComponent();
if (lastComponent instanceof DefaultMutableTreeNode) {
Object userObject = ((DefaultMutableTreeNode)lastComponent).getUserObject();
if (userObject instanceof Client) {
String pkg = ((Client)userObject).getClientData().getClientDescription();
return pkg != null && pkg.contains(pattern);
}
}
}
return false;
}
};
myProcessTree.setCellRenderer(new JBDefaultTreeCellRenderer(myProcessTree) {
@Override
public Component getTreeCellRendererComponent(JTree tree,
Object value,
boolean sel,
boolean expanded,
boolean leaf,
int row,
boolean hasFocus) {
if (value instanceof DefaultMutableTreeNode) {
final Object userObject = ((DefaultMutableTreeNode)value).getUserObject();
if (userObject instanceof IDevice) {
value = ((IDevice)userObject).getName();
}
else if (userObject instanceof Client) {
final ClientData clientData = ((Client)userObject).getClientData();
String description = clientData.getClientDescription();
if (clientData.isValidUserId() && clientData.getUserId() != 0) {
description += " (user " + Integer.toString(clientData.getUserId()) + ")";
}
value = description;
}
}
return super.getTreeCellRendererComponent(tree, value, sel, expanded, leaf, row, hasFocus);
}
@Override
public Icon getLeafIcon() {
return null;
}
@Override
public Icon getOpenIcon() {
return null;
}
@Override
public Icon getClosedIcon() {
return null;
}
});
new DoubleClickListener() {
@Override
protected boolean onDoubleClick(MouseEvent event) {
if (isOKActionEnabled()) {
doOKAction();
return true;
}
return false;
}
}.installOn(myProcessTree);
myProcessTree.addKeyListener(new KeyAdapter() {
@Override
public void keyPressed(KeyEvent e) {
if (e.getKeyCode() == KeyEvent.VK_ENTER && isOKActionEnabled()) {
doOKAction();
}
}
});
final PropertiesComponent properties = PropertiesComponent.getInstance(myProject);
myLastSelectedProcess = properties.getValue(DEBUGGABLE_PROCESS_PROPERTY);
myLastSelectedDevice = properties.getValue(DEBUGGABLE_DEVICE_PROPERTY);
init();
}
@NotNull
private static String getPersistableName(@Nullable Client client) {
return client == null ? "" : client.getClientData().getClientDescription();
}
@NotNull
private static String getPersistableName(@Nullable IDevice device) {
return device == null ? "" : device.getName();
}
@Override
public JComponent getPreferredFocusedComponent() {
return myProcessTree;
}
@Override
protected void dispose() {
super.dispose();
AndroidDebugBridge.removeDeviceChangeListener(myDeviceChangeListener);
AndroidDebugBridge.removeClientChangeListener(myClientChangeListener);
}
private void updateTree() {
final boolean showAllProcesses = myShowAllProcessesCheckBox.isSelected();
myUpdatesQueue.queue(new Update(AndroidProcessChooserDialog.this) {
@Override
public void run() {
final AndroidDebugBridge debugBridge = AndroidSdkUtils.getDebugBridge(myProject);
if (debugBridge != null && AdbService.isDdmsCorrupted(debugBridge)) {
ApplicationManager.getApplication().invokeLater(new Runnable() {
@Override
public void run() {
Messages.showErrorDialog(myContentPanel, AndroidBundle.message("ddms.corrupted.error"));
AndroidProcessChooserDialog.this.close(1);
}
});
return;
}
doUpdateTree(showAllProcesses);
}
@Override
public boolean canEat(Update update) {
return true;
}
});
}
private void doUpdateTree(boolean showAllProcesses) {
final AndroidDebugBridge debugBridge = AndroidSdkUtils.getDebugBridge(myProject);
final DefaultMutableTreeNode root = new DefaultMutableTreeNode();
final DefaultTreeModel model = new DefaultTreeModel(root);
if (debugBridge == null) {
myProcessTree.setModel(model);
return;
}
final Set<String> processNames = collectAllProcessNames(myProject);
TreeNode selectedDeviceNode = null;
TreeNode selectedClientNode = null;
Object[] firstTreePath = null;
final IDevice[] devices = debugBridge.getDevices();
for (IDevice device : devices) {
final DefaultMutableTreeNode deviceNode = new DefaultMutableTreeNode(device);
root.add(deviceNode);
final String deviceName = device.getName();
if (deviceName.equals(myLastSelectedDevice)) {
selectedDeviceNode = deviceNode;
}
for (Client client : device.getClients()) {
final String clientDescription = client.getClientData().getClientDescription();
if (clientDescription != null &&
(showAllProcesses || isRelatedProcess(processNames, clientDescription))) {
final DefaultMutableTreeNode clientNode = new DefaultMutableTreeNode(client);
deviceNode.add(clientNode);
if (clientDescription.equals(myLastSelectedProcess) &&
(selectedDeviceNode == null || deviceName.equals(myLastSelectedDevice))) {
selectedClientNode = clientNode;
selectedDeviceNode = deviceNode;
}
if (firstTreePath == null) {
firstTreePath = new Object[]{root, deviceNode, clientNode};
}
}
}
}
final Object[] pathToSelect;
if (selectedDeviceNode != null && selectedClientNode != null) {
pathToSelect = new Object[]{root, selectedDeviceNode, selectedClientNode};
}
else if (selectedDeviceNode != null) {
pathToSelect = new Object[]{root, selectedDeviceNode};
}
else {
pathToSelect = firstTreePath;
}
UIUtil.invokeLaterIfNeeded(new Runnable() {
@Override
public void run() {
myProcessTree.setModel(model);
if (pathToSelect != null) {
myProcessTree.getSelectionModel().setSelectionPath(new TreePath(pathToSelect));
}
else {
getOKAction().setEnabled(false);
}
TreeUtil.expandAll(myProcessTree);
}
});
}
private static boolean isRelatedProcess(Set<String> processNames, String clientDescription) {
final String lc = clientDescription.toLowerCase();
for (String processName : processNames) {
if (lc.startsWith(processName)) {
return true;
}
}
return false;
}
@NotNull
private static Set<String> collectAllProcessNames(Project project) {
final List<AndroidFacet> facets = ProjectFacetManager.getInstance(project).getFacets(AndroidFacet.ID);
final Set<String> result = new HashSet<String>();
for (AndroidFacet facet : facets) {
final String packageName = AndroidCompileUtil.getAaptManifestPackage(facet);
if (packageName != null) {
result.add(packageName.toLowerCase());
}
final Manifest manifest = facet.getManifest();
if (manifest != null) {
final XmlElement xmlElement = manifest.getXmlElement();
if (xmlElement != null) {
collectProcessNames(xmlElement, result);
}
}
final AndroidModel androidModel = facet.getAndroidModel();
if (androidModel != null) {
result.addAll(androidModel.getAllApplicationIds());
}
}
return result;
}
private static void collectProcessNames(XmlElement xmlElement, final Set<String> result) {
xmlElement.accept(new XmlRecursiveElementVisitor() {
@Override
public void visitXmlAttribute(XmlAttribute attribute) {
if ("process".equals(attribute.getLocalName())) {
final String value = attribute.getValue();
if (value != null) {
result.add(value.toLowerCase());
}
}
}
});
}
@Override
protected JComponent createCenterPanel() {
return myContentPanel;
}
@Override
protected void doOKAction() {
final PropertiesComponent properties = PropertiesComponent.getInstance(myProject);
final IDevice selectedDevice = getSelectedDevice();
if (selectedDevice == null) {
return;
}
final Client selectedClient = getSelectedClient();
if (selectedClient == null) {
return;
}
super.doOKAction();
properties.setValue(DEBUGGABLE_DEVICE_PROPERTY, getPersistableName(selectedDevice));
properties.setValue(DEBUGGABLE_PROCESS_PROPERTY, getPersistableName(selectedClient));
properties.setValue(SHOW_ALL_PROCESSES_PROPERTY, Boolean.toString(myShowAllProcessesCheckBox.isSelected()));
final String debugPort = Integer.toString(selectedClient.getDebuggerListenPort());
closeOldSessionAndRun(debugPort);
}
@Override
protected String getDimensionServiceKey() {
return "AndroidProcessChooserDialog";
}
private void closeOldSessionAndRun(final String debugPort) {
final String configurationName = getRunConfigurationName(debugPort);
final Collection<RunContentDescriptor> descriptors =
ExecutionHelper.findRunningConsoleByTitle(myProject, new NotNullFunction<String, Boolean>() {
@NotNull
@Override
public Boolean fun(String title) {
return configurationName.equals(title);
}
});
if (descriptors.size() > 0) {
final RunContentDescriptor descriptor = descriptors.iterator().next();
final ProcessHandler processHandler = descriptor.getProcessHandler();
final Content content = descriptor.getAttachedContent();
if (processHandler != null && content != null) {
final Executor executor = DefaultDebugExecutor.getDebugExecutorInstance();
if (processHandler.isProcessTerminated()) {
ExecutionManager.getInstance(myProject).getContentManager()
.removeRunContent(executor, descriptor);
}
else {
content.getManager().setSelectedContent(content);
ToolWindow window = ToolWindowManager.getInstance(myProject).getToolWindow(executor.getToolWindowId());
window.activate(null, false, true);
return;
}
}
}
runSession(debugPort);
}
private void runSession(String debugPort) {
final RunnerAndConfigurationSettings settings = createRunConfiguration(myProject, debugPort);
ProgramRunnerUtil.executeConfiguration(myProject, settings, DefaultDebugExecutor.getDebugExecutorInstance());
}
private static RunnerAndConfigurationSettings createRunConfiguration(Project project, String debugPort) {
final RemoteConfigurationType remoteConfigurationType = RemoteConfigurationType.getInstance();
final ConfigurationFactory factory = remoteConfigurationType.getFactory();
final RunnerAndConfigurationSettings runSettings =
RunManager.getInstance(project).createRunConfiguration(getRunConfigurationName(debugPort), factory);
final RemoteConfiguration configuration = (RemoteConfiguration)runSettings.getConfiguration();
configuration.HOST = "localhost";
configuration.PORT = debugPort;
configuration.USE_SOCKET_TRANSPORT = true;
configuration.SERVER_MODE = false;
return runSettings;
}
@NotNull
private static String getRunConfigurationName(String debugPort) {
return String.format(RUN_CONFIGURATION_NAME_PATTERN, debugPort);
}
@Nullable
private IDevice getSelectedDevice() {
final TreePath selectionPath = myProcessTree.getSelectionPath();
if (selectionPath == null || selectionPath.getPathCount() < 2) {
return null;
}
DefaultMutableTreeNode selectedNode = (DefaultMutableTreeNode)selectionPath.getPathComponent(1);
final Object obj = selectedNode.getUserObject();
return obj instanceof IDevice ? (IDevice)obj : null;
}
@Nullable
private Client getSelectedClient() {
final TreePath selectionPath = myProcessTree.getSelectionPath();
if (selectionPath == null || selectionPath.getPathCount() < 3) {
return null;
}
DefaultMutableTreeNode selectedNode = (DefaultMutableTreeNode)selectionPath.getPathComponent(2);
final Object obj = selectedNode.getUserObject();
return obj instanceof Client ? (Client)obj : null;
}
}