blob: 481a308051d080d75be7bb1ab04086c7bae6c5d6 [file] [log] [blame]
/*
* Copyright (C) 2017 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.google.appindexing.ui;
import com.android.tools.analytics.UsageTracker;
import com.android.tools.idea.stats.UsageTrackerUtils;
import com.google.appindexing.api.ApiCreator;
import com.google.appindexing.util.AppIndexingBundle;
import com.google.urlassistant.ActivityData;
import com.google.urlassistant.util.ManifestUtils;
import com.google.wireless.android.sdk.stats.AndroidStudioEvent;
import com.intellij.ide.BrowserUtil;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.ModalityState;
import com.intellij.openapi.application.Result;
import com.intellij.openapi.command.WriteCommandAction;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.fileEditor.FileEditorManager;
import com.intellij.openapi.module.ModuleManager;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.DialogWrapper;
import com.intellij.openapi.ui.Messages;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiDocumentManager;
import com.intellij.psi.PsiFile;
import com.intellij.ui.DoubleClickListener;
import com.intellij.ui.components.JBLabel;
import com.intellij.ui.components.JBList;
import com.intellij.ui.components.JBPanel;
import com.intellij.ui.components.JBScrollPane;
import com.intellij.uiDesigner.core.GridConstraints;
import com.intellij.uiDesigner.core.GridLayoutManager;
import icons.StudioIcons;
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.event.MouseEvent;
import java.util.List;
import javax.swing.AbstractListModel;
import javax.swing.Action;
import javax.swing.BorderFactory;
import javax.swing.JComponent;
import javax.swing.JList;
import javax.swing.JPanel;
import javax.swing.JTextPane;
import javax.swing.ListSelectionModel;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* A dialog to select activity for code inserting.
*/
public class InsertApiCodeDialog extends DialogWrapper {
private final Project myProject;
private final FileEditorManager myFileEditorManager;
private JList myJList;
private JPanel myContentPanel = new JBPanel(new BorderLayout(0, 0));
private final JTextPane myWarningMessage = new JTextPane();
private final JBPanel myMessagePanel = new JBPanel(new GridLayoutManager(1, 2));
// This list contains all activities which support app links.
private List<ActivityData> myActivities;
private ApiCreator myCreator;
@Nullable
@Override
protected JComponent createCenterPanel() {
return myContentPanel;
}
@Override
public JComponent getPreferredFocusedComponent() {
return myJList;
}
public InsertApiCodeDialog(@NotNull Project project) {
super(project);
myProject = project;
myFileEditorManager = FileEditorManager.getInstance(myProject);
myMessagePanel.setBorder(BorderFactory.createEmptyBorder(5, 0, 5, 0));
GridConstraints constraints = new GridConstraints();
constraints.setAnchor(GridConstraints.ANCHOR_WEST);
JBLabel iconLabel = new JBLabel();
iconLabel.setIcon(StudioIcons.Common.ERROR);
myMessagePanel.add(iconLabel, constraints);
constraints.setColumn(1);
myWarningMessage.setAlignmentX(Component.LEFT_ALIGNMENT);
myWarningMessage.setBackground(myContentPanel.getBackground());
myMessagePanel.add(myWarningMessage, constraints);
myMessagePanel.setVisible(false);
myContentPanel.setPreferredSize(new Dimension(400, 200));
myContentPanel.add(myMessagePanel, BorderLayout.SOUTH);
initializeActivityList();
JBScrollPane scrollPane = new JBScrollPane(myJList);
myContentPanel.add(scrollPane, BorderLayout.CENTER);
getOKAction().putValue(Action.NAME, AppIndexingBundle.message("app.indexing.code.insert.dialog.ok.button"));
setOKActionEnabled(false);
setTitle(AppIndexingBundle.message("app.indexing.code.insert.dialog.title"));
init();
}
private void initializeActivityList() {
ApplicationManager.getApplication().invokeAndWait(
() -> myActivities = ManifestUtils.getAllActivities(ModuleManager.getInstance(myProject).getModules(), true, true),
ModalityState.any());
AbstractListModel model = new AbstractListModel() {
@Override
public int getSize() {
return myActivities.size();
}
@Override
public Object getElementAt(int i) {
return myActivities.get(i).getActivityAndModuleName();
}
};
myJList = new JBList(model);
if (myActivities.isEmpty()) {
myWarningMessage.setText(AppIndexingBundle.message("app.indexing.code.insert.error.deep_link_missing"));
myMessagePanel.setVisible(true);
return;
}
myJList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
myJList.addListSelectionListener(e -> {
if (!e.getValueIsAdjusting() && myJList.getSelectedValue() != null) {
handleListMouseClickEvent(myJList.getSelectedIndex());
}
});
new DoubleClickListener() {
@Override
protected boolean onDoubleClick(MouseEvent e) {
if (isOKActionEnabled()) {
doOKAction();
return true;
}
return false;
}
}.installOn(myJList);
}
private void handleListMouseClickEvent(int index) {
setOKActionEnabled(false);
// Opens the selected activity source file in file editor.
ActivityData activityData = myActivities.get(index);
VirtualFile file = activityData.getActivityVirtualFile();
// file must be valid here because all activities without virtual file have already been removed previously.
assert file != null;
ApplicationManager.getApplication().invokeAndWait(() -> {
myFileEditorManager.openFile(file, true);
}, ModalityState.any());
Editor editor = myFileEditorManager.getSelectedTextEditor();
assert editor != null;
PsiFile selectedPsiFile = PsiDocumentManager.getInstance(myProject).getPsiFile(editor.getDocument());
assert selectedPsiFile != null;
myCreator = new ApiCreator(myProject, selectedPsiFile, activityData.getActivityClass());
ApiCreator.InsertStatusCode statusCode = myCreator.eligibleForInsertingAppIndexingApiCode();
if (statusCode != ApiCreator.InsertStatusCode.SUCCESS) {
myWarningMessage.setText(statusCode.getMessage());
myMessagePanel.setVisible(true);
return;
}
myMessagePanel.setVisible(false);
setOKActionEnabled(true);
}
@Override
protected void doOKAction() {
try {
new WriteCommandAction(myProject) {
@Override
protected void run(@NotNull final Result result) throws Throwable {
ApiCreator.InsertStatusCode statusCode = myCreator.insertAppIndexingApiCodeForActivity();
if (statusCode != ApiCreator.InsertStatusCode.SUCCESS) {
Messages.showErrorDialog(statusCode.getMessage(), AppIndexingBundle.message("app.indexing.code.insert.error.title"));
return;
}
UsageTracker.log(UsageTrackerUtils.withProjectId(
AndroidStudioEvent.newBuilder()
.setCategory(AndroidStudioEvent.EventCategory.APP_INDEXING)
.setKind(AndroidStudioEvent.EventKind.APP_INDEXING_API_CODE_CREATED),
myProject));
}
}.execute();
}
finally {
super.doOKAction();
}
}
@Nullable
@Override
protected String getHelpId() {
return "https://firebase.google.com/docs/app-indexing/android/migrate";
}
@Override
protected void doHelpAction() {
String helpId = getHelpId();
assert helpId != null; // Otherwise, doHelpAction would not be triggered
BrowserUtil.browse(helpId);
}
}