blob: 9795db4e59bc1bc69b9e4d44dcccc00ac7707c08 [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.AndroidDebugBridge;
import com.android.ddmlib.IDevice;
import com.android.tools.idea.ddms.EdtExecutor;
import com.android.tools.idea.ddms.adb.AdbService;
import com.android.tools.idea.explorer.FutureCallbackExecutor;
import com.android.tools.idea.run.*;
import com.android.tools.idea.run.tasks.ShellCommandLauncher;
import com.android.tools.idea.run.util.ProcessHandlerLaunchStatus;
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.util.text.StringUtil;
import com.intellij.ui.HyperlinkLabel;
import com.intellij.ui.components.JBLabel;
import org.jetbrains.android.facet.AndroidFacet;
import org.jetbrains.android.sdk.AndroidSdkUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import java.awt.*;
import java.io.File;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
/**
* 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 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();
}
@NotNull
public static AppIndexingResultPanel buildPanelFromFetchTask(@NotNull FetchAsGoogleTask task, @NotNull String deepLink) {
return new AppIndexingResultPanel(task, deepLink);
}
private void initializeTitle(@NotNull FetchAsGoogleTask task, @NotNull String deepLink) {
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.setHyperlinkTarget(deepLink);
}
}
private void fillPanelContent() {
FetchAsGoogleTask.ErrorCode errorCode = myTask.getErrorCode();
switch (myTask.getRequestType()) {
case APP_INDEXING:
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);
}
}, EdtExecutor.INSTANCE);
}
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();
AndroidProcessHandler androidProcessHandler = new AndroidProcessHandler(myTask.getPackageId());
androidProcessHandler.addTargetDevice(device);
FutureCallbackExecutor f = FutureCallbackExecutor.wrap(Executors.newSingleThreadExecutor());
ListenableFuture<Boolean> launchResult =
f.executeAsync(() -> ShellCommandLauncher.execute("am start -a \"com.google.android.gms.icing.APP_INDEXING_DEBUG\"", device,
new ProcessHandlerLaunchStatus(androidProcessHandler),
new ProcessHandlerConsolePrinter(androidProcessHandler), 10,
TimeUnit.SECONDS));
Futures.addCallback(launchResult, new FutureCallback<Boolean>() {
@Override
public void onSuccess(Boolean result) {
setOpenDeveloperToolResult(result, collapsablePanel);
}
@Override
public void onFailure(@NotNull Throwable t) {
setOpenDeveloperToolResult(false, collapsablePanel);
}
}, EdtExecutor.INSTANCE);
}
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"));
}
}