| /* |
| * Copyright 2016 The Bazel Authors. All rights reserved. |
| * |
| * 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.google.idea.blaze.android.run.binary; |
| |
| import com.android.tools.idea.run.activity.ActivityLocatorUtils; |
| import com.google.idea.blaze.android.run.binary.BlazeAndroidBinaryLaunchMethodsProvider.AndroidBinaryLaunchMethod; |
| import com.google.idea.blaze.android.run.binary.BlazeAndroidBinaryLaunchMethodsProvider.AndroidBinaryLaunchMethodComboEntry; |
| import com.google.idea.blaze.base.run.state.RunConfigurationState; |
| import com.google.idea.blaze.base.run.state.RunConfigurationStateEditor; |
| import com.google.idea.blaze.base.ui.IntegerTextField; |
| import com.google.idea.blaze.base.ui.UiUtil; |
| import com.intellij.codeInsight.daemon.DaemonCodeAnalyzer; |
| import com.intellij.ide.util.TreeClassChooser; |
| import com.intellij.ide.util.TreeClassChooserFactory; |
| import com.intellij.openapi.editor.ex.EditorEx; |
| import com.intellij.openapi.fileTypes.PlainTextLanguage; |
| import com.intellij.openapi.project.Project; |
| import com.intellij.openapi.ui.ComponentWithBrowseButton; |
| import com.intellij.openapi.ui.Messages; |
| import com.intellij.openapi.util.Key; |
| import com.intellij.psi.JavaPsiFacade; |
| import com.intellij.psi.PsiClass; |
| import com.intellij.psi.PsiDocumentManager; |
| import com.intellij.psi.PsiFile; |
| import com.intellij.psi.search.GlobalSearchScope; |
| import com.intellij.psi.search.ProjectScope; |
| import com.intellij.ui.EditorTextField; |
| import com.intellij.ui.LanguageTextField; |
| import com.intellij.uiDesigner.core.GridConstraints; |
| import com.intellij.uiDesigner.core.GridLayoutManager; |
| import java.awt.Color; |
| import java.awt.Font; |
| import java.awt.Insets; |
| import java.awt.event.ActionEvent; |
| import java.awt.event.ActionListener; |
| import java.util.ResourceBundle; |
| import javax.swing.AbstractButton; |
| import javax.swing.BorderFactory; |
| import javax.swing.ButtonGroup; |
| import javax.swing.JCheckBox; |
| import javax.swing.JComboBox; |
| import javax.swing.JComponent; |
| import javax.swing.JLabel; |
| import javax.swing.JPanel; |
| import javax.swing.JRadioButton; |
| import javax.swing.border.TitledBorder; |
| import org.jetbrains.android.util.AndroidBundle; |
| import org.jetbrains.android.util.AndroidUtils; |
| |
| /** |
| * The part of the Blaze Android Binary handler editor that allows the user to pick an activity to |
| * launch. Patterned after {@link org.jetbrains.android.run.ApplicationRunParameters}. |
| */ |
| class BlazeAndroidBinaryRunConfigurationStateEditor implements RunConfigurationStateEditor { |
| public static final Key<BlazeAndroidBinaryRunConfigurationStateEditor> |
| ACTIVITY_CLASS_TEXT_FIELD_KEY = Key.create("BlazeActivityClassTextField"); |
| |
| private final RunConfigurationStateEditor commonStateEditor; |
| |
| private JPanel panel; |
| private ComponentWithBrowseButton<EditorTextField> activityField; |
| private JRadioButton launchNothingButton; |
| private JRadioButton launchDefaultButton; |
| private JRadioButton launchCustomButton; |
| private JComboBox<AndroidBinaryLaunchMethodComboEntry> launchMethodComboBox; |
| private JCheckBox splitApksCheckBox; |
| private JCheckBox useWorkProfileIfPresentCheckBox; |
| private JLabel userIdLabel; |
| private IntegerTextField userIdField; |
| |
| private boolean componentEnabled = true; |
| |
| BlazeAndroidBinaryRunConfigurationStateEditor( |
| RunConfigurationStateEditor commonStateEditor, Project project) { |
| this.commonStateEditor = commonStateEditor; |
| setupUI(project); |
| userIdField.setMinValue(0); |
| |
| activityField.addActionListener( |
| new ActionListener() { |
| @Override |
| public void actionPerformed(ActionEvent e) { |
| if (!project.isInitialized()) { |
| return; |
| } |
| // We find all Activity classes in the module for the selected variant |
| // (or any of its deps). |
| final JavaPsiFacade facade = JavaPsiFacade.getInstance(project); |
| PsiClass activityBaseClass = |
| facade.findClass( |
| AndroidUtils.ACTIVITY_BASE_CLASS_NAME, ProjectScope.getAllScope(project)); |
| if (activityBaseClass == null) { |
| Messages.showErrorDialog( |
| panel, AndroidBundle.message("cant.find.activity.class.error")); |
| return; |
| } |
| GlobalSearchScope searchScope = GlobalSearchScope.projectScope(project); |
| PsiClass initialSelection = |
| facade.findClass(activityField.getChildComponent().getText(), searchScope); |
| TreeClassChooser chooser = |
| TreeClassChooserFactory.getInstance(project) |
| .createInheritanceClassChooser( |
| "Select Activity Class", |
| searchScope, |
| activityBaseClass, |
| initialSelection, |
| null); |
| chooser.showDialog(); |
| PsiClass selClass = chooser.getSelected(); |
| if (selClass != null) { |
| // This must be done because Android represents |
| // inner static class paths differently than java. |
| String qualifiedActivityName = |
| ActivityLocatorUtils.getQualifiedActivityName(selClass); |
| activityField.getChildComponent().setText(qualifiedActivityName); |
| } |
| } |
| }); |
| ActionListener listener = e -> updateEnabledState(); |
| launchCustomButton.addActionListener(listener); |
| launchDefaultButton.addActionListener(listener); |
| launchNothingButton.addActionListener(listener); |
| |
| launchMethodComboBox.addActionListener( |
| e -> |
| splitApksCheckBox.setVisible( |
| ((AndroidBinaryLaunchMethodComboEntry) launchMethodComboBox.getSelectedItem()) |
| .launchMethod.equals(AndroidBinaryLaunchMethod.MOBILE_INSTALL) // v1 only |
| )); |
| |
| useWorkProfileIfPresentCheckBox.addActionListener(e -> updateEnabledState()); |
| } |
| |
| @Override |
| public void resetEditorFrom(RunConfigurationState genericState) { |
| BlazeAndroidBinaryRunConfigurationState state = |
| (BlazeAndroidBinaryRunConfigurationState) genericState; |
| commonStateEditor.resetEditorFrom(state.getCommonState()); |
| boolean launchSpecificActivity = |
| state.getMode().equals(BlazeAndroidBinaryRunConfigurationState.LAUNCH_SPECIFIC_ACTIVITY); |
| if (state.getMode().equals(BlazeAndroidBinaryRunConfigurationState.LAUNCH_DEFAULT_ACTIVITY)) { |
| launchDefaultButton.setSelected(true); |
| } else if (launchSpecificActivity) { |
| launchCustomButton.setSelected(true); |
| } else { |
| launchNothingButton.setSelected(true); |
| } |
| if (launchSpecificActivity) { |
| activityField.getChildComponent().setText(state.getActivityClass()); |
| } |
| |
| for (int i = 0; i < launchMethodComboBox.getItemCount(); ++i) { |
| if (launchMethodComboBox.getItemAt(i).launchMethod.equals(state.getLaunchMethod())) { |
| launchMethodComboBox.setSelectedIndex(i); |
| break; |
| } |
| } |
| splitApksCheckBox.setSelected(state.useSplitApksIfPossible()); |
| useWorkProfileIfPresentCheckBox.setSelected(state.useWorkProfileIfPresent()); |
| |
| userIdField.setValue(state.getUserId()); |
| splitApksCheckBox.setVisible( |
| state.getLaunchMethod().equals(AndroidBinaryLaunchMethod.MOBILE_INSTALL)); |
| |
| updateEnabledState(); |
| } |
| |
| @Override |
| public void applyEditorTo(RunConfigurationState genericState) { |
| BlazeAndroidBinaryRunConfigurationState state = |
| (BlazeAndroidBinaryRunConfigurationState) genericState; |
| commonStateEditor.applyEditorTo(state.getCommonState()); |
| |
| state.setUserId((Integer) userIdField.getValue()); |
| if (launchDefaultButton.isSelected()) { |
| state.setMode(BlazeAndroidBinaryRunConfigurationState.LAUNCH_DEFAULT_ACTIVITY); |
| } else if (launchCustomButton.isSelected()) { |
| state.setMode(BlazeAndroidBinaryRunConfigurationState.LAUNCH_SPECIFIC_ACTIVITY); |
| state.setActivityClass(activityField.getChildComponent().getText()); |
| } else { |
| state.setMode(BlazeAndroidBinaryRunConfigurationState.DO_NOTHING); |
| } |
| state.setLaunchMethod( |
| ((AndroidBinaryLaunchMethodComboEntry) launchMethodComboBox.getSelectedItem()) |
| .launchMethod); |
| state.setUseSplitApksIfPossible(splitApksCheckBox.isSelected()); |
| state.setUseWorkProfileIfPresent(useWorkProfileIfPresentCheckBox.isSelected()); |
| } |
| |
| @Override |
| public JComponent createComponent() { |
| return UiUtil.createBox(commonStateEditor.createComponent(), panel); |
| } |
| |
| private void updateEnabledState() { |
| boolean useWorkProfile = useWorkProfileIfPresentCheckBox.isSelected(); |
| userIdLabel.setEnabled(componentEnabled && !useWorkProfile); |
| userIdField.setEnabled(componentEnabled && !useWorkProfile); |
| commonStateEditor.setComponentEnabled(componentEnabled); |
| activityField.setEnabled(componentEnabled && launchCustomButton.isSelected()); |
| launchNothingButton.setEnabled(componentEnabled); |
| launchDefaultButton.setEnabled(componentEnabled); |
| launchCustomButton.setEnabled(componentEnabled); |
| launchMethodComboBox.setEnabled(componentEnabled); |
| splitApksCheckBox.setEnabled(componentEnabled); |
| useWorkProfileIfPresentCheckBox.setEnabled(componentEnabled); |
| } |
| |
| @Override |
| public void setComponentEnabled(boolean enabled) { |
| componentEnabled = enabled; |
| updateEnabledState(); |
| } |
| |
| private void createUIComponents(Project project) { |
| final EditorTextField editorTextField = |
| new LanguageTextField(PlainTextLanguage.INSTANCE, project, "") { |
| @Override |
| protected EditorEx createEditor() { |
| final EditorEx editor = super.createEditor(); |
| final PsiFile file = |
| PsiDocumentManager.getInstance(project).getPsiFile(editor.getDocument()); |
| |
| if (file != null) { |
| DaemonCodeAnalyzer.getInstance(project).setHighlightingEnabled(file, false); |
| } |
| editor.putUserData( |
| ACTIVITY_CLASS_TEXT_FIELD_KEY, BlazeAndroidBinaryRunConfigurationStateEditor.this); |
| return editor; |
| } |
| }; |
| activityField = new ComponentWithBrowseButton<EditorTextField>(editorTextField, null); |
| } |
| |
| /** Initially generated by IntelliJ from a .form file, then checked in as source. */ |
| private void setupUI(Project project) { |
| createUIComponents(project); |
| panel = new JPanel(); |
| panel.setLayout(new GridLayoutManager(5, 2, new Insets(0, 0, 0, 0), -1, -1)); |
| final JPanel activityPanel = new JPanel(); |
| activityPanel.setLayout(new GridLayoutManager(4, 2, new Insets(0, 0, 0, 0), -1, -1)); |
| panel.add( |
| activityPanel, |
| new GridConstraints( |
| 3, |
| 0, |
| 1, |
| 2, |
| GridConstraints.ANCHOR_CENTER, |
| GridConstraints.FILL_BOTH, |
| GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, |
| GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, |
| null, |
| null, |
| null, |
| 0, |
| false)); |
| activityPanel.setBorder( |
| BorderFactory.createTitledBorder( |
| BorderFactory.createEtchedBorder(), |
| "Activity", |
| TitledBorder.DEFAULT_JUSTIFICATION, |
| TitledBorder.DEFAULT_POSITION, |
| new Font( |
| activityPanel.getFont().getName(), |
| activityPanel.getFont().getStyle(), |
| activityPanel.getFont().getSize()), |
| new Color(-16777216))); |
| final JPanel userPanel = new JPanel(); |
| userPanel.setLayout(new GridLayoutManager(2, 2, new Insets(0, 0, 0, 0), -1, -1)); |
| panel.add( |
| userPanel, |
| new GridConstraints( |
| 4, |
| 0, |
| 1, |
| 2, |
| GridConstraints.ANCHOR_CENTER, |
| GridConstraints.FILL_BOTH, |
| GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, |
| GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, |
| null, |
| null, |
| null, |
| 0, |
| false)); |
| userPanel.setBorder( |
| BorderFactory.createTitledBorder( |
| BorderFactory.createEtchedBorder(), |
| "User", |
| TitledBorder.DEFAULT_JUSTIFICATION, |
| TitledBorder.DEFAULT_POSITION, |
| new Font( |
| userPanel.getFont().getName(), |
| userPanel.getFont().getStyle(), |
| userPanel.getFont().getSize()), |
| new Color(-16777216))); |
| launchNothingButton = new JRadioButton(); |
| this.loadButtonText( |
| launchNothingButton, |
| ResourceBundle.getBundle("messages/AndroidBundle") |
| .getString("android.run.configuration.do.nothing.label")); |
| activityPanel.add( |
| launchNothingButton, |
| new GridConstraints( |
| 0, |
| 0, |
| 1, |
| 2, |
| GridConstraints.ANCHOR_CENTER, |
| GridConstraints.FILL_HORIZONTAL, |
| GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, |
| GridConstraints.SIZEPOLICY_FIXED, |
| null, |
| null, |
| null, |
| 0, |
| false)); |
| launchDefaultButton = new JRadioButton(); |
| launchDefaultButton.setText("Launch default Activity"); |
| launchDefaultButton.setMnemonic('L'); |
| launchDefaultButton.setDisplayedMnemonicIndex(0); |
| activityPanel.add( |
| launchDefaultButton, |
| new GridConstraints( |
| 1, |
| 0, |
| 1, |
| 2, |
| GridConstraints.ANCHOR_CENTER, |
| GridConstraints.FILL_HORIZONTAL, |
| GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, |
| GridConstraints.SIZEPOLICY_FIXED, |
| null, |
| null, |
| null, |
| 0, |
| false)); |
| launchCustomButton = new JRadioButton(); |
| launchCustomButton.setText("Launch:"); |
| launchCustomButton.setMnemonic('A'); |
| launchCustomButton.setDisplayedMnemonicIndex(1); |
| activityPanel.add( |
| launchCustomButton, |
| new GridConstraints( |
| 2, |
| 0, |
| 1, |
| 1, |
| GridConstraints.ANCHOR_WEST, |
| GridConstraints.FILL_NONE, |
| GridConstraints.SIZEPOLICY_FIXED, |
| GridConstraints.SIZEPOLICY_FIXED, |
| null, |
| null, |
| null, |
| 0, |
| false)); |
| activityPanel.add( |
| activityField, |
| new GridConstraints( |
| 2, |
| 1, |
| 1, |
| 1, |
| GridConstraints.ANCHOR_CENTER, |
| GridConstraints.FILL_BOTH, |
| GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, |
| GridConstraints.SIZEPOLICY_FIXED, |
| null, |
| null, |
| null, |
| 0, |
| false)); |
| useWorkProfileIfPresentCheckBox = new JCheckBox(); |
| useWorkProfileIfPresentCheckBox.setText(" Use work profile if present"); |
| userPanel.add( |
| useWorkProfileIfPresentCheckBox, |
| new GridConstraints( |
| 0, |
| 0, |
| 1, |
| 2, |
| GridConstraints.ANCHOR_WEST, |
| GridConstraints.FILL_NONE, |
| GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, |
| GridConstraints.SIZEPOLICY_FIXED, |
| null, |
| null, |
| null, |
| 0, |
| false)); |
| userIdLabel = new JLabel(); |
| userIdLabel.setText("User ID"); |
| userPanel.add( |
| userIdLabel, |
| new GridConstraints( |
| 1, |
| 0, |
| 1, |
| 1, |
| GridConstraints.ANCHOR_WEST, |
| GridConstraints.FILL_NONE, |
| GridConstraints.SIZEPOLICY_FIXED, |
| GridConstraints.SIZEPOLICY_FIXED, |
| null, |
| null, |
| null, |
| 1, |
| false)); |
| userIdField = new IntegerTextField(); |
| userPanel.add( |
| userIdField, |
| new GridConstraints( |
| 1, |
| 1, |
| 1, |
| 1, |
| GridConstraints.ANCHOR_WEST, |
| GridConstraints.FILL_HORIZONTAL, |
| GridConstraints.SIZEPOLICY_WANT_GROW, |
| GridConstraints.SIZEPOLICY_FIXED, |
| null, |
| null, |
| null, |
| 0, |
| false)); |
| launchMethodComboBox = |
| new JComboBox<>(BlazeAndroidBinaryLaunchMethodsProvider.getAllLaunchMethods(project)); |
| panel.add( |
| launchMethodComboBox, |
| new GridConstraints( |
| 0, |
| 0, |
| 1, |
| 2, |
| GridConstraints.ANCHOR_WEST, |
| GridConstraints.FILL_NONE, |
| GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, |
| GridConstraints.SIZEPOLICY_FIXED, |
| null, |
| null, |
| null, |
| 0, |
| false)); |
| splitApksCheckBox = new JCheckBox(); |
| splitApksCheckBox.setText("Use --split_apks where possible"); |
| panel.add( |
| splitApksCheckBox, |
| new GridConstraints( |
| 1, |
| 0, |
| 1, |
| 2, |
| GridConstraints.ANCHOR_WEST, |
| GridConstraints.FILL_NONE, |
| GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, |
| GridConstraints.SIZEPOLICY_FIXED, |
| null, |
| null, |
| null, |
| 0, |
| false)); |
| ButtonGroup buttonGroup; |
| buttonGroup = new ButtonGroup(); |
| buttonGroup.add(launchDefaultButton); |
| buttonGroup.add(launchCustomButton); |
| buttonGroup.add(launchNothingButton); |
| } |
| |
| /** Initially generated by IntelliJ from a .form file. */ |
| private void loadButtonText(AbstractButton component, String text) { |
| StringBuffer result = new StringBuffer(); |
| boolean haveMnemonic = false; |
| char mnemonic = '\0'; |
| int mnemonicIndex = -1; |
| for (int i = 0; i < text.length(); i++) { |
| if (text.charAt(i) == '&') { |
| i++; |
| if (i == text.length()) { |
| break; |
| } |
| if (!haveMnemonic && text.charAt(i) != '&') { |
| haveMnemonic = true; |
| mnemonic = text.charAt(i); |
| mnemonicIndex = result.length(); |
| } |
| } |
| result.append(text.charAt(i)); |
| } |
| component.setText(result.toString()); |
| if (haveMnemonic) { |
| component.setMnemonic(mnemonic); |
| component.setDisplayedMnemonicIndex(mnemonicIndex); |
| } |
| } |
| } |