blob: fbe8fd67d13c9082b801af17f5350c4d5f5ea521 [file] [log] [blame]
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.chrome.browser.share;
import android.app.Activity;
import android.net.Uri;
import android.text.TextUtils;
import org.chromium.base.Callback;
import org.chromium.base.VisibleForTesting;
import org.chromium.base.metrics.RecordHistogram;
import org.chromium.base.metrics.RecordUserAction;
import org.chromium.chrome.browser.UrlConstants;
import org.chromium.chrome.browser.offlinepages.OfflinePageUtils;
import org.chromium.chrome.browser.printing.PrintShareActivity;
import org.chromium.chrome.browser.tab.Tab;
import org.chromium.chrome.browser.util.ChromeFileProvider;
import org.chromium.components.ui_metrics.CanonicalURLResult;
import org.chromium.content_public.browser.WebContents;
import org.chromium.net.GURLUtils;
import java.util.ArrayList;
import java.util.List;
/**
* Handles the action of selecting the share item in the menu.
*/
public class ShareMenuActionHandler {
private static boolean sScreenshotCaptureSkippedForTesting;
private static ShareMenuActionHandler sInstance;
private final ShareMenuActionDelegate mDelegate;
static final String CANONICAL_URL_RESULT_HISTOGRAM = "Mobile.CanonicalURLResult";
/**
* @return The singleton share menu handler.
*/
public static ShareMenuActionHandler getInstance() {
if (sInstance == null) {
sInstance = new ShareMenuActionHandler(new ShareMenuActionDelegate());
}
return sInstance;
}
@VisibleForTesting
ShareMenuActionHandler(ShareMenuActionDelegate delegate) {
mDelegate = delegate;
}
@VisibleForTesting
public static void setScreenshotCaptureSkippedForTesting(boolean value) {
sScreenshotCaptureSkippedForTesting = value;
}
/**
* Triggered when the share menu item is selected.
* This creates and shows a share intent picker dialog or starts a share intent directly.
* @param shareDirectly Whether it should share directly with the activity that was most
* recently used to share.
* @param isIncognito Whether currentTab is incognito.
*/
public void onShareMenuItemSelected(
Activity activity, Tab currentTab, boolean shareDirectly, boolean isIncognito) {
if (currentTab == null) return;
List<Class<? extends ShareActivity>> classesToEnable = new ArrayList<>(2);
if (PrintShareActivity.featureIsAvailable(currentTab)) {
classesToEnable.add(PrintShareActivity.class);
}
if (!classesToEnable.isEmpty()) {
OptionalShareTargetsManager.enableOptionalShareActivities(activity, classesToEnable,
() -> triggerShare(activity, currentTab, shareDirectly, isIncognito));
return;
}
triggerShare(activity, currentTab, shareDirectly, isIncognito);
}
@VisibleForTesting
static boolean shouldFetchCanonicalUrl(final Tab currentTab) {
WebContents webContents = currentTab.getWebContents();
if (webContents == null) return false;
if (webContents.getMainFrame() == null) return false;
String url = currentTab.getUrl();
if (TextUtils.isEmpty(url)) return false;
if (currentTab.isShowingErrorPage() || currentTab.isShowingInterstitialPage()
|| currentTab.isShowingSadTab()) {
return false;
}
return true;
}
@VisibleForTesting
static String getUrlToShare(String visibleUrl, String canonicalUrl) {
if (TextUtils.isEmpty(canonicalUrl)) return visibleUrl;
// TODO(tedchoc): Can we replace GURLUtils.getScheme with Uri.parse(...).getScheme()
// https://crbug.com/783819
if (!UrlConstants.HTTPS_SCHEME.equals(GURLUtils.getScheme(visibleUrl))) {
return visibleUrl;
}
String canonicalScheme = GURLUtils.getScheme(canonicalUrl);
if (!UrlConstants.HTTP_SCHEME.equals(canonicalScheme)
&& !UrlConstants.HTTPS_SCHEME.equals(canonicalScheme)) {
return visibleUrl;
}
return canonicalUrl;
}
private void logCanonicalUrlResult(String visibleUrl, String canonicalUrl) {
@CanonicalURLResult
int result = getCanonicalUrlResult(visibleUrl, canonicalUrl);
RecordHistogram.recordEnumeratedHistogram(CANONICAL_URL_RESULT_HISTOGRAM, result,
CanonicalURLResult.CANONICAL_URL_RESULT_COUNT);
}
@CanonicalURLResult
private int getCanonicalUrlResult(String visibleUrl, String canonicalUrl) {
if (!UrlConstants.HTTPS_SCHEME.equals(GURLUtils.getScheme(visibleUrl))) {
return CanonicalURLResult.FAILED_VISIBLE_URL_NOT_HTTPS;
}
if (TextUtils.isEmpty(canonicalUrl)) {
return CanonicalURLResult.FAILED_NO_CANONICAL_URL_DEFINED;
}
String canonicalScheme = GURLUtils.getScheme(canonicalUrl);
if (!UrlConstants.HTTPS_SCHEME.equals(canonicalScheme)) {
if (!UrlConstants.HTTP_SCHEME.equals(canonicalScheme)) {
return CanonicalURLResult.FAILED_CANONICAL_URL_INVALID;
} else {
return CanonicalURLResult.SUCCESS_CANONICAL_URL_NOT_HTTPS;
}
}
if (TextUtils.equals(visibleUrl, canonicalUrl)) {
return CanonicalURLResult.SUCCESS_CANONICAL_URL_SAME_AS_VISIBLE;
} else {
return CanonicalURLResult.SUCCESS_CANONICAL_URL_DIFFERENT_FROM_VISIBLE;
}
}
private void triggerShare(final Activity activity, final Tab currentTab,
final boolean shareDirectly, boolean isIncognito) {
if (OfflinePageUtils.maybeShareOfflinePage(
activity, currentTab, (ShareParams p) -> mDelegate.share(p))) {
return;
}
if (shouldFetchCanonicalUrl(currentTab)) {
WebContents webContents = currentTab.getWebContents();
String title = currentTab.getTitle();
String visibleUrl = currentTab.getUrl();
webContents.getMainFrame().getCanonicalUrlForSharing(new Callback<String>() {
@Override
public void onResult(String result) {
logCanonicalUrlResult(visibleUrl, result);
triggerShareWithCanonicalUrlResolved(activity, webContents, title, visibleUrl,
result, shareDirectly, isIncognito);
}
});
} else {
triggerShareWithCanonicalUrlResolved(activity, currentTab.getWebContents(),
currentTab.getTitle(), currentTab.getUrl(), null, shareDirectly, isIncognito);
}
}
private void triggerShareWithCanonicalUrlResolved(final Activity mainActivity,
final WebContents webContents, final String title, final String visibleUrl,
final String canonicalUrl, final boolean shareDirectly, boolean isIncognito) {
// Share an empty blockingUri in place of screenshot file. The file ready notification is
// sent by onScreenshotReady call below when the file is written.
final Uri blockingUri = (isIncognito || webContents == null)
? null
: ChromeFileProvider.generateUriAndBlockAccess(mainActivity);
ShareParams.Builder builder =
new ShareParams
.Builder(mainActivity, title, getUrlToShare(visibleUrl, canonicalUrl))
.setShareDirectly(shareDirectly)
.setSaveLastUsed(!shareDirectly)
.setScreenshotUri(blockingUri);
mDelegate.share(builder.build());
if (shareDirectly) {
RecordUserAction.record("MobileMenuDirectShare");
} else {
RecordUserAction.record("MobileMenuShare");
}
if (blockingUri == null) return;
// Start screenshot capture and notify the provider when it is ready.
Callback<Uri> callback = (saveFile) -> {
// Unblock the file once it is saved to disk.
ChromeFileProvider.notifyFileReady(blockingUri, saveFile);
};
if (sScreenshotCaptureSkippedForTesting) {
callback.onResult(null);
} else {
ShareHelper.captureScreenshotForContents(webContents, 0, 0, callback);
}
}
/**
* Delegate for share handling.
*/
static class ShareMenuActionDelegate {
/**
* Trigger the share action for the specified params.
*/
void share(ShareParams params) {
ShareHelper.share(params);
}
}
}