blob: 0f8374c055263a66e6b87af2cf92336abd37af77 [file] [log] [blame]
// Copyright 2018 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.webapk.shell_apk;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Bundle;
import android.text.TextUtils;
import android.util.Pair;
import org.chromium.webapk.lib.common.WebApkConstants;
import org.chromium.webapk.lib.common.WebApkMetaDataKeys;
import java.util.ArrayList;
import java.util.Locale;
/** Convenience wrapper for parameters to {@link HostBrowserLauncher} methods. */
public class HostBrowserLauncherParams {
private String mHostBrowserPackageName;
private int mHostBrowserMajorChromiumVersion;
private boolean mDialogShown;
private Intent mOriginalIntent;
private String mStartUrl;
private int mSource;
private boolean mForceNavigation;
private long mLaunchTimeMs;
private String mSelectedShareTargetActivityClassName;
/**
* Constructs a HostBrowserLauncherParams object from the passed in Intent and from <meta-data>
* in the Android Manifest.
*/
public static HostBrowserLauncherParams createForIntent(Context context, Intent intent,
String hostBrowserPackageName, boolean dialogShown, long launchTimeMs) {
Bundle metadata = WebApkUtils.readMetaData(context);
if (metadata == null) return null;
int hostBrowserMajorChromiumVersion = HostBrowserUtils.queryHostBrowserMajorChromiumVersion(
context, hostBrowserPackageName);
String startUrl = null;
int source = WebApkConstants.SHORTCUT_SOURCE_UNKNOWN;
boolean forceNavigation = false;
String selectedShareTargetActivityClassName = intent.getStringExtra(
WebApkConstants.EXTRA_WEBAPK_SELECTED_SHARE_TARGET_ACTIVITY_CLASS_NAME);
if (Intent.ACTION_SEND.equals(intent.getAction())
|| Intent.ACTION_SEND_MULTIPLE.equals(intent.getAction())) {
selectedShareTargetActivityClassName = intent.getComponent().getClassName();
}
if (selectedShareTargetActivityClassName != null) {
Bundle shareTargetMetaData = fetchActivityMetaData(context,
new ComponentName(
context.getPackageName(), selectedShareTargetActivityClassName));
startUrl = computeStartUrlForShareTarget(shareTargetMetaData, intent);
source = WebApkConstants.SHORTCUT_SOURCE_SHARE;
forceNavigation = true;
} else if (!TextUtils.isEmpty(intent.getDataString())) {
startUrl = intent.getDataString();
source = intent.getIntExtra(
WebApkConstants.EXTRA_SOURCE, WebApkConstants.SHORTCUT_SOURCE_EXTERNAL_INTENT);
forceNavigation = intent.getBooleanExtra(WebApkConstants.EXTRA_FORCE_NAVIGATION, true);
} else {
startUrl = metadata.getString(WebApkMetaDataKeys.START_URL);
source = WebApkConstants.SHORTCUT_SOURCE_UNKNOWN;
forceNavigation = false;
}
if (startUrl == null) return null;
startUrl = WebApkUtils.rewriteIntentUrlIfNecessary(startUrl, metadata);
// Ignore deep links which came with non HTTP/HTTPS schemes and which were not rewritten.
if (!doesUrlUseHttpOrHttpsScheme(startUrl)) return null;
return new HostBrowserLauncherParams(hostBrowserPackageName,
hostBrowserMajorChromiumVersion, dialogShown, intent, startUrl, source,
forceNavigation, launchTimeMs, selectedShareTargetActivityClassName);
}
private static Bundle fetchActivityMetaData(
Context context, ComponentName shareTargetComponentName) {
ActivityInfo shareActivityInfo;
try {
shareActivityInfo = context.getPackageManager().getActivityInfo(
shareTargetComponentName, PackageManager.GET_META_DATA);
} catch (PackageManager.NameNotFoundException e) {
return null;
}
if (shareActivityInfo == null) {
return null;
}
return shareActivityInfo.metaData;
}
private static boolean doesShareTargetUsePost(Bundle shareTargetMetaData) {
String method = shareTargetMetaData.getString(WebApkMetaDataKeys.SHARE_METHOD);
if (TextUtils.isEmpty(method)) {
return false;
}
return "POST".equals(method.toUpperCase(Locale.ENGLISH));
}
/**
* Computes the start URL for the given share intent and share activity.
* @param shareTargetMetaData Meta data for the share target activity selected by the user.
* @param intent Share intent.
*/
protected static String computeStartUrlForShareTarget(
Bundle shareTargetMetaData, Intent intent) {
if (shareTargetMetaData == null) {
return null;
}
if (doesShareTargetUsePost(shareTargetMetaData)) {
return shareTargetMetaData.getString(WebApkMetaDataKeys.SHARE_ACTION);
}
return computeStartUrlForGETShareTarget(shareTargetMetaData, intent);
}
/**
* Computes the start URL for the given share intent and share activity which sends GET HTTP
* requests.
* @param shareTargetMetaData Meta data for the share target activity selected by the user.
* @param intent Share intent.
*/
private static String computeStartUrlForGETShareTarget(
Bundle shareTargetMetaData, Intent intent) {
String shareAction = shareTargetMetaData.getString(WebApkMetaDataKeys.SHARE_ACTION);
if (TextUtils.isEmpty(shareAction)) {
return null;
}
// These can be null, they are checked downstream.
ArrayList<Pair<String, String>> entryList = new ArrayList<>();
entryList.add(
new Pair<>(shareTargetMetaData.getString(WebApkMetaDataKeys.SHARE_PARAM_TITLE),
intent.getStringExtra(Intent.EXTRA_SUBJECT)));
entryList.add(new Pair<>(shareTargetMetaData.getString(WebApkMetaDataKeys.SHARE_PARAM_TEXT),
intent.getStringExtra(Intent.EXTRA_TEXT)));
return createGETWebShareTargetUriString(shareAction, entryList);
}
/**
* Converts the action url and parameters of a GET webshare target into a URI.
* Example:
* - action = "https://example.org/includinator/share.html"
* - params
* title param: "title"
* title intent: "news"
* text param: "description"
* text intent: "story"
* Becomes:
* https://example.org/includinator/share.html?title=news&description=story
* TODO(ckitagawa): The escaping behavior isn't entirely correct. The exact encoding is still
* being discussed at https://github.com/WICG/web-share-target/issues/59.
*/
protected static String createGETWebShareTargetUriString(
String action, ArrayList<Pair<String, String>> entryList) {
Uri.Builder queryBuilder = new Uri.Builder();
for (Pair<String, String> nameValue : entryList) {
if (!TextUtils.isEmpty(nameValue.first) && !TextUtils.isEmpty(nameValue.second)) {
// Uri.Builder does URL escaping.
queryBuilder.appendQueryParameter(nameValue.first, nameValue.second);
}
}
Uri shareUri = Uri.parse(action);
Uri.Builder builder = shareUri.buildUpon();
// Uri.Builder uses %20 rather than + for spaces, the spec requires +.
String queryString = queryBuilder.build().toString();
if (TextUtils.isEmpty(queryString)) {
return action;
}
builder.encodedQuery(queryString.replace("%20", "+").substring(1));
return builder.build().toString();
}
/** Returns whether the URL uses the HTTP or HTTPs schemes. */
private static boolean doesUrlUseHttpOrHttpsScheme(String url) {
return url != null && (url.startsWith("http:") || url.startsWith("https:"));
}
private HostBrowserLauncherParams(String hostBrowserPackageName,
int hostBrowserMajorChromiumVersion, boolean dialogShown, Intent originalIntent,
String startUrl, int source, boolean forceNavigation, long launchTimeMs,
String selectedShareTargetActivityClassName) {
mHostBrowserPackageName = hostBrowserPackageName;
mHostBrowserMajorChromiumVersion = hostBrowserMajorChromiumVersion;
mDialogShown = dialogShown;
mOriginalIntent = originalIntent;
mStartUrl = startUrl;
mSource = source;
mForceNavigation = forceNavigation;
mLaunchTimeMs = launchTimeMs;
mSelectedShareTargetActivityClassName = selectedShareTargetActivityClassName;
}
/** Returns the chosen host browser. */
public String getHostBrowserPackageName() {
return mHostBrowserPackageName;
}
/**
* Returns the major version of the host browser. Currently, only Chromium host browsers
* (Chrome Canary, Chrome Dev ...) are supported.
*/
public int getHostBrowserMajorChromiumVersion() {
return mHostBrowserMajorChromiumVersion;
}
/** Returns whether the choose-host-browser dialog was shown. */
public boolean wasDialogShown() {
return mDialogShown;
}
/** Returns intent used to launch WebAPK. */
public Intent getOriginalIntent() {
return mOriginalIntent;
}
/** Returns URL to launch WebAPK at. */
public String getStartUrl() {
return mStartUrl;
}
/** Returns the source which is launching/navigating the WebAPK. */
public int getSource() {
return mSource;
}
/**
* Returns whether the WebAPK should be navigated to {@link mStartUrl} if it is already
* running.
*/
public boolean getForceNavigation() {
return mForceNavigation;
}
/** Returns time in milliseconds that the WebAPK was launched. */
public long getLaunchTimeMs() {
return mLaunchTimeMs;
}
/** Returns the class name of the share activity that the user selected. */
public String getSelectedShareTargetActivityClassName() {
return mSelectedShareTargetActivityClassName;
}
}