blob: cd5bedc5fd2eff04c077eb49f46b21324f2403b3 [file] [log] [blame]
/*
* Copyright (C) 2015 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.ddmlib.AdbCommandRejectedException;
import com.android.ddmlib.AndroidDebugBridge;
import com.android.ddmlib.IDevice;
import com.android.ddmlib.ShellCommandUnresponsiveException;
import com.android.ddmlib.TimeoutException;
import com.android.tools.idea.adb.AdbService;
import com.android.tools.idea.concurrency.FutureCallbackExecutor;
import com.android.tools.idea.execution.common.adb.shell.ErrorMatchingReceiver;
import com.android.tools.idea.run.DeviceCount;
import com.android.tools.idea.run.DeviceSelectionUtils;
import com.android.tools.idea.run.TargetDeviceFilter;
import com.google.api.services.fetchasgoogle_pa.model.AppIndexingStats;
import com.google.appindexing.fetchasgoogle.FetchAsGoogleTask;
import com.google.appindexing.util.AppIndexingBundle;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.intellij.icons.AllIcons;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.ui.HyperlinkLabel;
import com.intellij.ui.components.JBLabel;
import com.intellij.util.concurrency.EdtExecutorService;
import java.awt.BorderLayout;
import java.io.File;
import java.io.IOException;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import javax.swing.Icon;
import javax.swing.JButton;
import javax.swing.JPanel;
import javax.swing.LayoutFocusTraversalPolicy;
import org.jetbrains.android.facet.AndroidFacet;
import org.jetbrains.android.sdk.AndroidSdkUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* The result panel for both public and personal content app indexing testing tool
*/
public class AppIndexingResultPanel extends JPanel {
private JPanel myRootPanel;
private JPanel myLeftPanel;
private JPanel myTitlePanel;
private JBLabel myDateLabel;
private HyperlinkLabel myDeepLinkUrl;
private JBLabel myPackageLabel;
private JBLabel myUrlTitle;
private JPanel myDetailPanel;
private final FetchAsGoogleTask myTask;
private AppIndexingResultPanel(@NotNull FetchAsGoogleTask task, @NotNull String deepLink) {
super(new BorderLayout());
myTask = task;
initializeTitle(task, deepLink);
myPackageLabel.setText(task.getPackageId());
myRootPanel.setFocusTraversalPolicy(new LayoutFocusTraversalPolicy());
add(myRootPanel);
fillPanelContent();
}
private void initializeTitle(@NotNull FetchAsGoogleTask task, @NotNull String deepLink) {
myPackageLabel.setText(task.getPackageId());
myDateLabel.setText(task.getCreatedTime().toString());
if (FetchAsGoogleTask.RequestType.USER_ACTION_LOGGING_DEBUGGING == task.getRequestType() ||
FetchAsGoogleTask.RequestType.PERSONAL_CONTENT_INDEXING_DEBUGGING == task.getRequestType()) {
myUrlTitle.setText("App Version in Play Store:");
if (task.getFetchResponse() != null) {
myDeepLinkUrl.setText(task.getFetchResponse().getAppVersion());
}
}
else {
myDeepLinkUrl.setHyperlinkText(deepLink);
myDeepLinkUrl.setHyperlinkTarget(deepLink);
}
}
private void fillPanelContent() {
FetchAsGoogleTask.ErrorCode errorCode = myTask.getErrorCode();
switch (myTask.getRequestType()) {
case APP_INDEXING:
case INSTANT_APP:
if (errorCode == null) {
addCollapsablePanel(AllIcons.RunConfigurations.TestPassed, AppIndexingBundle.message("app.indexing.fetch.as.google.test.success"),
"Please see <a href=\"" + myTask.getFetchResponse().getUrl() + "\">" +
AppIndexingBundle.message("app.indexing.fetch.as.google.test.result.text") + "</a>");
return;
}
else {
addCollapsablePanel(AllIcons.RunConfigurations.TestError, errorCode.name(),
errorCode.getMessage() + " <a href='https://g.co/AppIndexing/AndroidStudio'>" +
AppIndexingBundle.message("app.indexing.fetch.as.google.test.learn.more") +
"</a>");
}
break;
case USER_ACTION_LOGGING_DEBUGGING:
case PERSONAL_CONTENT_INDEXING_DEBUGGING:
if (errorCode == null ||
errorCode.equals(FetchAsGoogleTask.ErrorCode.OK)) {
handleAppIndexingStats();
}
else {
switch (errorCode) {
case NOT_FOUND:
addCollapsablePanel(AllIcons.RunConfigurations.TestPassed, errorCode.name(), errorCode.getMessage());
break;
case APP_OWNERSHIP_VERIFICATION_FAILED:
case BACKEND_ERROR:
case RESOURCE_EXHAUSTED:
addCollapsablePanel(AllIcons.RunConfigurations.TestError, errorCode.name(), errorCode.getMessage());
break;
default:
break;
}
}
break;
default:
break;
}
}
private void handleAppIndexingStats() {
if (myTask.getFetchResponse() == null) {
FetchAsGoogleTask.ErrorCode errorCode = FetchAsGoogleTask.ErrorCode.BACKEND_ERROR;
addCollapsablePanel(AllIcons.RunConfigurations.TestError, errorCode.name(), errorCode.getMessage());
return;
}
if (StringUtil.isEmpty(myTask.getFetchResponse().getAppVersion())) {
addCollapsablePanel(AllIcons.RunConfigurations.TestError, FetchAsGoogleTask.ErrorCode.APK_NOT_FOUND.name(),
FetchAsGoogleTask.ErrorCode.APK_NOT_FOUND.getMessage());
return;
}
List<AppIndexingStats> stats = myTask.getFetchResponse().getStats();
if (stats == null || stats.isEmpty()) {
addCollapsablePanel(AllIcons.RunConfigurations.TestPassed, AppIndexingBundle.message("app.indexing.fetch.as.google.test.success"),
AppIndexingBundle.message("app.indexing.fetch.as.google.test.success.detail.message"));
return;
}
for (AppIndexingStats stat : stats) {
long value = stat.getValue();
switch (stat.getName()) {
case "DAILY_UNRESOLVED_URI":
handleUnresolvedUri(value);
break;
case "DAILY_UPDATE_CALL":
addCollapsablePanel(AllIcons.RunConfigurations.TestError,
AppIndexingBundle.message("app.indexing.fetch.as.google.test.error.daily.update.call", value),
AppIndexingBundle.message("app.indexing.fetch.as.google.test.error.daily.update.call.detail.message"));
break;
case "DAILY_UPLOADED_PERSONAL_CONTENT_URI":
addCollapsablePanel(AllIcons.RunConfigurations.TestError,
AppIndexingBundle.message("app.indexing.fetch.as.google.test.error.upload.call.missing"),
AppIndexingBundle.message("app.indexing.fetch.as.google.test.error.upload.call.missing.detail.message"));
break;
case "DAILY_WRONG_USER_ACTION_START_CALL":
addCollapsablePanel(AllIcons.RunConfigurations.TestError,
AppIndexingBundle.message("app.indexing.fetch.as.google.test.error.wrong.start.call"),
AppIndexingBundle.message("app.indexing.fetch.as.google.test.error.wrong.start.call.detail.message"));
break;
case "DAILY_WRONG_USER_ACTION_END_CALL":
addCollapsablePanel(AllIcons.RunConfigurations.TestError,
AppIndexingBundle.message("app.indexing.fetch.as.google.test.error.wrong.end.call"),
AppIndexingBundle.message("app.indexing.fetch.as.google.test.error.wrong.start.call.detail.message"));
break;
}
}
}
private void handleUnresolvedUri(long value) {
CollapsablePanel collapsablePanel =
addCollapsablePanel(AllIcons.RunConfigurations.TestError,
AppIndexingBundle.message("app.indexing.fetch.as.google.test.error.unresolved.uri", value),
AppIndexingBundle.message("app.indexing.fetch.as.google.test.error.unresolved.uri.detail.message"));
JButton button = collapsablePanel.getButton();
button.setVisible(true);
button.setText(AppIndexingBundle.message("app.indexing.button.open.developer.tool"));
button.addActionListener(e -> initializeAdbAndOpenDebuggingTool(collapsablePanel));
}
@NotNull
private CollapsablePanel addCollapsablePanel(@NotNull Icon icon, @NotNull String titleMessage, @NotNull String detailMessage) {
CollapsablePanel collapsablePanel = new CollapsablePanel(icon, titleMessage, detailMessage);
myDetailPanel.add(collapsablePanel.getRootPanel(), BorderLayout.SOUTH);
return collapsablePanel;
}
private void initializeAdbAndOpenDebuggingTool(@NotNull CollapsablePanel collapsablePanel) {
collapsablePanel.startLoading();
AndroidFacet facet = AndroidFacet.getInstance(myTask.getModule());
if (facet == null || myTask.getPackageId() == null) {
setOpenDeveloperToolResult(false, collapsablePanel);
return;
}
File adb = AndroidSdkUtils.getAdb(myTask.getProject());
if (adb == null) {
setOpenDeveloperToolResult(false, collapsablePanel);
return;
}
ListenableFuture<AndroidDebugBridge> debugBridge = AdbService.getInstance().getDebugBridge(adb);
Futures.addCallback(debugBridge, new FutureCallback<AndroidDebugBridge>() {
@Override
public void onSuccess(AndroidDebugBridge result) {
launchDebuggingTool(facet, collapsablePanel);
}
@Override
public void onFailure(@NotNull Throwable t) {
setOpenDeveloperToolResult(false, collapsablePanel);
}
}, EdtExecutorService.getInstance());
}
private void launchDebuggingTool(@NotNull AndroidFacet facet, @NotNull CollapsablePanel collapsablePanel) {
Collection<IDevice> collection =
DeviceSelectionUtils.chooseRunningDevice(facet, new TargetDeviceFilter.UsbDeviceFilter(), DeviceCount.SINGLE);
if (collection == null) {
// null means the user cancelled while selecting device, so we don't need to show any error message.
setOpenDeveloperToolResult(true, collapsablePanel);
return;
}
if (collection.isEmpty()) {
setOpenDeveloperToolResult(false, collapsablePanel);
return;
}
IDevice device = collection.iterator().next();
FutureCallbackExecutor f = FutureCallbackExecutor.wrap(Executors.newSingleThreadExecutor());
ApplicationManager.getApplication().executeOnPooledThread(() -> {
ErrorMatchingReceiver receiver = new ErrorMatchingReceiver();
try {
device.executeShellCommand("am start -a \"com.google.android.gms.icing.APP_INDEXING_DEBUG\"",
receiver,
10,
TimeUnit.SECONDS);
}
catch (TimeoutException | AdbCommandRejectedException | ShellCommandUnresponsiveException | IOException e) {
setOpenDeveloperToolResult(false, collapsablePanel);
return;
}
setOpenDeveloperToolResult(!receiver.hasError(), collapsablePanel);
});
}
private void setOpenDeveloperToolResult(@Nullable Boolean succeeded, @NotNull CollapsablePanel collapsablePanel) {
if (myTask.getProject().isDisposed()) {
return;
}
collapsablePanel.stopLoading();
collapsablePanel.setActionErrorMessage(
(succeeded != null && succeeded) ? "" : AppIndexingBundle.message("app.indexing.open.developer.tool.fail.message"));
}
@NotNull
public static AppIndexingResultPanel buildPanelFromFetchTask(@NotNull FetchAsGoogleTask task, @NotNull String deepLink) {
return new AppIndexingResultPanel(task, deepLink);
}
}