| /* |
| * 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")); |
| } |
| } |