Java tutorial
/* * 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.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.diagnostic.Logger; 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.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 { private static final Logger LOG = Logger .getInstance("#org.jetbrains.android.actions.AndroidProcessChooserDialog"); @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 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) { getOKAction().setEnabled(getSelectedDevice() != null && getSelectedClient() != null); } }); 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 = getPresentableName((IDevice) userObject); } else if (userObject instanceof Client) { value = getClientDescription((Client) userObject); } } 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(); } } }); init(); } @Override public JComponent getPreferredFocusedComponent() { return myProcessTree; } @Override protected void dispose() { super.dispose(); AndroidDebugBridge.removeDeviceChangeListener(myDeviceChangeListener); AndroidDebugBridge.removeClientChangeListener(myClientChangeListener); } @NotNull private static String getPresentableName(@NotNull IDevice device) { String serialNumber = device.getSerialNumber(); final String avdName = device.getAvdName(); if (serialNumber == null || serialNumber.length() == 0) { serialNumber = "<unknown>"; } return avdName == null || avdName.length() == 0 ? serialNumber : serialNumber + " (" + avdName + ')'; } 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 && AndroidSdkUtils.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); final PropertiesComponent properties = PropertiesComponent.getInstance(myProject); final String prevProcess = properties.getValue(DEBUGGABLE_PROCESS_PROPERTY); final String prevDevice = properties.getValue(DEBUGGABLE_DEVICE_PROPERTY); 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); for (Client client : device.getClients()) { final String clientDescription = getClientDescription(client); if (clientDescription != null && (showAllProcesses || isRelatedProcess(processNames, clientDescription))) { final DefaultMutableTreeNode clientNode = new DefaultMutableTreeNode(client); deviceNode.add(clientNode); final String deviceName = getPresentableName(device); if (clientDescription.equals(prevProcess) && (selectedDeviceNode == null || deviceName.equals(prevDevice))) { selectedClientNode = clientNode; selectedDeviceNode = deviceNode; } if (firstTreePath == null) { firstTreePath = new Object[] { root, deviceNode, clientNode }; } } } } final Object[] pathToSelect = selectedDeviceNode != null ? new Object[] { root, selectedDeviceNode, selectedClientNode } : 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 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); } } } return result; } @Nullable private static String getClientDescription(Client client) { final ClientData clientData = client.getClientData(); return clientData != null ? clientData.getClientDescription() : null; } 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, getPresentableName(selectedDevice)); properties.setValue(DEBUGGABLE_PROCESS_PROPERTY, selectedClient.getClientData().getClientDescription()); 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(); if (remoteConfigurationType == null) { LOG.error("Cannot create remote configuration"); } 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; } }