| Index: chrome/android/java/src/org/chromium/chrome/browser/physicalweb/PwsClientImpl.java
|
| diff --git a/chrome/android/java/src/org/chromium/chrome/browser/physicalweb/PwsClientImpl.java b/chrome/android/java/src/org/chromium/chrome/browser/physicalweb/PwsClientImpl.java
|
| index 20f0a611a287ed7e51c1a3c3d7a6c8507f0b204c..961e7344777830a3d74564de40fb70e9a3421427 100644
|
| --- a/chrome/android/java/src/org/chromium/chrome/browser/physicalweb/PwsClientImpl.java
|
| +++ b/chrome/android/java/src/org/chromium/chrome/browser/physicalweb/PwsClientImpl.java
|
| @@ -4,12 +4,16 @@
|
|
|
| package org.chromium.chrome.browser.physicalweb;
|
|
|
| +import android.content.Context;
|
| import android.graphics.Bitmap;
|
| import android.os.AsyncTask;
|
| +import android.os.Build;
|
|
|
| import org.chromium.base.Log;
|
| import org.chromium.base.ThreadUtils;
|
| +import org.chromium.base.VisibleForTesting;
|
| import org.chromium.chrome.GoogleAPIKeys;
|
| +import org.chromium.chrome.R;
|
| import org.chromium.chrome.browser.ChromeVersionInfo;
|
| import org.chromium.chrome.browser.physicalweb.PwsClient.FetchIconCallback;
|
| import org.chromium.chrome.browser.physicalweb.PwsClient.ResolveScanCallback;
|
| @@ -21,6 +25,8 @@ import org.json.JSONObject;
|
| import java.net.MalformedURLException;
|
| import java.util.ArrayList;
|
| import java.util.Collection;
|
| +import java.util.Formatter;
|
| +import java.util.Locale;
|
|
|
| /**
|
| * This class sends requests to the Physical Web Service.
|
| @@ -30,6 +36,30 @@ class PwsClientImpl implements PwsClient {
|
| private static final String ENDPOINT_URL =
|
| "https://physicalweb.googleapis.com/v1alpha1/urls:resolve";
|
|
|
| + // Format strings for creating the User-Agent string. It should somewhat resemble the Chrome for
|
| + // Android User-Agent but doesn't need to match perfectly as this value will only be seen by the
|
| + // Physical Web metadata service and favicon fetcher.
|
| + // The WebKit version is not accessible from here so it is reported as 0.0.
|
| + private static final String USER_AGENT_FORMAT =
|
| + "Mozilla/5.0 (%s) AppleWebKit/0.0 (KHTML, like Gecko) %s Safari/0.0";
|
| + private static final String OS_INFO_FORMAT = "Linux; Android %s; %s Build/%s";
|
| + private static final String PRODUCT_FORMAT = "Chrome/%s Mobile";
|
| +
|
| + // HTTP request header strings, lazily initialized.
|
| + private static String sUserAgent;
|
| + private static String sAcceptLanguage;
|
| +
|
| + // Cached locale string. When the default locale changes, recreate the Accept-Language header.
|
| + private static String sDefaultLocale;
|
| +
|
| + // The context must be valid for as long as this client is in use, since it is used to recreate
|
| + // the Accept-Language header when the locale changes.
|
| + private final Context mContext;
|
| +
|
| + public PwsClientImpl(Context context) {
|
| + mContext = context;
|
| + }
|
| +
|
| private String getApiKey() {
|
| if (ChromeVersionInfo.isStableBuild()) {
|
| return GoogleAPIKeys.GOOGLE_API_KEY;
|
| @@ -117,7 +147,8 @@ class PwsClientImpl implements PwsClient {
|
| try {
|
| JSONObject payload = createResolveScanPayload(broadcastUrls);
|
| String url = ENDPOINT_URL + "?key=" + getApiKey();
|
| - request = new JsonObjectHttpRequest(url, payload, requestCallback);
|
| + request = new JsonObjectHttpRequest(url, getUserAgent(), getAcceptLanguage(), payload,
|
| + requestCallback);
|
| } catch (MalformedURLException e) {
|
| Log.e(TAG, "Error creating PWS HTTP request", e);
|
| return;
|
| @@ -159,7 +190,8 @@ class PwsClientImpl implements PwsClient {
|
| // Create the request.
|
| BitmapHttpRequest request = null;
|
| try {
|
| - request = new BitmapHttpRequest(iconUrl, requestCallback);
|
| + request = new BitmapHttpRequest(iconUrl, getUserAgent(), getAcceptLanguage(),
|
| + requestCallback);
|
| } catch (MalformedURLException e) {
|
| Log.e(TAG, "Error creating icon request", e);
|
| return;
|
| @@ -167,4 +199,135 @@ class PwsClientImpl implements PwsClient {
|
| // The callback will be called on the main thread.
|
| AsyncTask.THREAD_POOL_EXECUTOR.execute(request);
|
| }
|
| +
|
| + /**
|
| + * Recreate the Chrome for Android User-Agent string as closely as possible without calling any
|
| + * native code.
|
| + * @return A User-Agent string
|
| + */
|
| + @VisibleForTesting
|
| + String getUserAgent() {
|
| + if (sUserAgent == null) {
|
| + // Build the OS info string.
|
| + // eg: Linux; Android 5.1.1; Nexus 4 Build/LMY48T
|
| + String osInfo = String.format(OS_INFO_FORMAT, Build.VERSION.RELEASE, Build.MODEL,
|
| + Build.ID);
|
| +
|
| + // Build the product string.
|
| + // eg: Chrome/50.0.2661.89 Mobile
|
| + String product = String.format(PRODUCT_FORMAT, ChromeVersionInfo.getProductVersion());
|
| +
|
| + // Build the User-Agent string.
|
| + // eg: Mozilla/5.0 (Linux; Android 5.1.1; Nexus 4 Build/LMY48T) AppleWebKit/0.0 (KHTML,
|
| + // like Gecko) Chrome/50.0.2661.89 Mobile Safari/0.0
|
| + sUserAgent = String.format(USER_AGENT_FORMAT, osInfo, product);
|
| + }
|
| + return sUserAgent;
|
| + }
|
| +
|
| + /**
|
| + * Construct the Accept-Language string based on the current locale.
|
| + * @return An Accept-Language string.
|
| + */
|
| + @VisibleForTesting
|
| + String getAcceptLanguage() {
|
| + String defaultLocale = Locale.getDefault().toString();
|
| + if (sDefaultLocale == null || !sDefaultLocale.equals(defaultLocale)) {
|
| + String acceptLanguages = mContext.getResources().getString(R.string.accept_languages);
|
| + acceptLanguages = prependToAcceptLanguagesIfNecessary(defaultLocale, acceptLanguages);
|
| + sAcceptLanguage = generateAcceptLanguageHeader(acceptLanguages);
|
| + sDefaultLocale = defaultLocale;
|
| + }
|
| + return sAcceptLanguage;
|
| + }
|
| +
|
| + /**
|
| + * Handle the special cases in converting a language code/region code pair into an ISO-639-1
|
| + * language tag.
|
| + * @param language The 2-character language code
|
| + * @param region The 2-character country code
|
| + * @return A language tag.
|
| + */
|
| + @VisibleForTesting
|
| + static String makeLanguageTag(String language, String region) {
|
| + // Java mostly follows ISO-639-1 and ICU, except for the following three.
|
| + // See documentation on java.util.Locale constructor for more.
|
| + String isoLanguage;
|
| + if ("iw".equals(language)) {
|
| + isoLanguage = "he";
|
| + } else if ("ji".equals(language)) {
|
| + isoLanguage = "yi";
|
| + } else if ("in".equals(language)) {
|
| + isoLanguage = "id";
|
| + } else {
|
| + isoLanguage = language;
|
| + }
|
| +
|
| + return isoLanguage + "-" + region;
|
| + }
|
| +
|
| + /**
|
| + * Get the language code for the default locale and prepend it to the Accept-Language string if
|
| + * it isn't already present. The logic should match PrependToAcceptLanguagesIfNecessary in
|
| + * chrome/browser/android/preferences/pref_service_bridge.cc
|
| + * @param locale A string representing the default locale.
|
| + * @param acceptLanguages The default language list for the language of the user's locale.
|
| + * @return An updated language list.
|
| + */
|
| + @VisibleForTesting
|
| + static String prependToAcceptLanguagesIfNecessary(String locale, String acceptLanguages)
|
| + {
|
| + if (locale.length() != 5 || locale.charAt(2) != '_') {
|
| + return acceptLanguages;
|
| + }
|
| +
|
| + String language = locale.substring(0, 2);
|
| + String region = locale.substring(3);
|
| + String languageTag = makeLanguageTag(language, region);
|
| +
|
| + if (acceptLanguages.contains(languageTag)) {
|
| + return acceptLanguages;
|
| + }
|
| +
|
| + Formatter parts = new Formatter();
|
| + parts.format("%s,", languageTag);
|
| + // If language is not in the accept languages list, also add language code.
|
| + // This will work with the IDS_ACCEPT_LANGUAGES localized strings bundled with Chrome but
|
| + // may fail on arbitrary lists of language tags due to differences in case and whitespace.
|
| + if (!acceptLanguages.contains(language + ",") && !acceptLanguages.endsWith(language)) {
|
| + parts.format("%s,", language);
|
| + }
|
| + parts.format("%s", acceptLanguages);
|
| + return parts.toString();
|
| + }
|
| +
|
| + /**
|
| + * Given a list of comma-delimited language codes, insert q-values to represent the relative
|
| + * quality/precedence of each language. The logic should match GenerateAcceptLanguageHeader in
|
| + * net/http/http_util.cc
|
| + * @param languageList A comma-delimited list of language codes containing no whitespace.
|
| + * @return An Accept-Language header with q-values.
|
| + */
|
| + @VisibleForTesting
|
| + static String generateAcceptLanguageHeader(String languageList) {
|
| + // We use integers for qvalue and qvalue decrement that are 10 times larger than actual
|
| + // values to avoid a problem with comparing two floating point numbers.
|
| + int kQvalueDecrement10 = 2;
|
| + int qvalue10 = 10;
|
| + String[] parts = languageList.split(",");
|
| + Formatter langListWithQ = new Formatter();
|
| + for (String language : parts) {
|
| + if (qvalue10 == 10) {
|
| + // q=1.0 is implicit
|
| + langListWithQ.format("%s", language);
|
| + } else {
|
| + langListWithQ.format(",%s;q=0.%d", language, qvalue10);
|
| + }
|
| + // It does not make sense to have 'q=0'.
|
| + if (qvalue10 > kQvalueDecrement10) {
|
| + qvalue10 -= kQvalueDecrement10;
|
| + }
|
| + }
|
| + return langListWithQ.toString();
|
| + }
|
| }
|
|
|