Bug 1129576 - Fetch the country code in the Search Activity. r=margaret, f=rnewman, a=sledru
authorMark Finkle <mfinkle@mozilla.com>
Fri, 06 Feb 2015 17:08:31 -0500
changeset 243732 8180eb5904de
parent 243731 d342ccaee6d5
child 243733 8600a7b2e3a6
push id4454
push userryanvm@gmail.com
push date2015-02-09 19:34 +0000
treeherdermozilla-beta@8600a7b2e3a6 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmargaret, sledru
bugs1129576
milestone36.0
Bug 1129576 - Fetch the country code in the Search Activity. r=margaret, f=rnewman, a=sledru
mobile/android/search/java/org/mozilla/search/providers/SearchEngineManager.java
--- a/mobile/android/search/java/org/mozilla/search/providers/SearchEngineManager.java
+++ b/mobile/android/search/java/org/mozilla/search/providers/SearchEngineManager.java
@@ -13,43 +13,59 @@ import org.json.JSONException;
 import org.json.JSONObject;
 import org.mozilla.gecko.AppConstants;
 import org.mozilla.gecko.BrowserLocaleManager;
 import org.mozilla.gecko.GeckoProfile;
 import org.mozilla.gecko.GeckoSharedPrefs;
 import org.mozilla.gecko.R;
 import org.mozilla.gecko.util.FileUtils;
 import org.mozilla.gecko.util.GeckoJarReader;
+import org.mozilla.gecko.util.HardwareUtils;
 import org.mozilla.gecko.util.RawResource;
 import org.mozilla.gecko.util.ThreadUtils;
 import org.mozilla.gecko.distribution.Distribution;
 import org.mozilla.search.Constants;
 import org.xmlpull.v1.XmlPullParserException;
 
+import java.io.BufferedInputStream;
 import java.io.BufferedReader;
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.InputStreamReader;
+import java.io.OutputStream;
 import java.io.UnsupportedEncodingException;
+import java.net.HttpURLConnection;
+import java.net.URL;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Locale;
 
 public class SearchEngineManager implements SharedPreferences.OnSharedPreferenceChangeListener {
     private static final String LOG_TAG = "GeckoSearchEngineManager";
 
     // Gecko pref that defines the name of the default search engine.
     private static final String PREF_GECKO_DEFAULT_ENGINE = "browser.search.defaultenginename";
 
     // Key for shared preference that stores default engine name.
     private static final String PREF_DEFAULT_ENGINE_KEY = "search.engines.defaultname";
 
+    // Key for shared preference that stores search region.
+    private static final String PREF_REGION_KEY = "search.region";
+
+    // URL for the geo-ip location service. Keep in sync with "browser.search.geoip.url" perference in Gecko.
+    private static final String GEOIP_LOCATION_URL = "https://location.services.mozilla.com/v1/country?key=" + AppConstants.MOZ_STUMBLER_API_KEY;
+
+    // This should go through GeckoInterface to get the UA, but the search activity
+    // doesn't use a GeckoView yet. Until it does, get the UA directly.
+    private static final String USER_AGENT = HardwareUtils.isTablet() ?
+        AppConstants.USER_AGENT_FENNEC_TABLET : AppConstants.USER_AGENT_FENNEC_MOBILE;
+
     private Context context;
     private Distribution distribution;
     private SearchEngineCallback changeCallback;
     private SearchEngine engine;
 
     // Cached version of default locale included in Gecko chrome manifest.
     // This should only be accessed from the background thread.
     private String fallbackLocale;
@@ -174,17 +190,21 @@ public class SearchEngineManager impleme
                 final SearchEngine engine = createEngineFromName(name);
                 runCallback(engine, callback);
             }
 
             private void defaultBehavior() {
                 // First look for a default name stored in shared preferences.
                 String name = GeckoSharedPrefs.forApp(context).getString(PREF_DEFAULT_ENGINE_KEY, null);
 
-                if (name != null) {
+                // Check for a region stored in shared preferences. If we don't have a region,
+                // we should force a recheck of the default engine.
+                String region = GeckoSharedPrefs.forApp(context).getString(PREF_REGION_KEY, null);
+
+                if (name != null && region != null) {
                     Log.d(LOG_TAG, "Found default engine name in SharedPreferences: " + name);
                 } else {
                     // First, look for the default search engine in a distribution.
                     name = getDefaultEngineNameFromDistribution();
                     if (name == null) {
                         // Otherwise, get the default engine that we ship.
                         name = getDefaultEngineNameFromLocale();
                     }
@@ -247,23 +267,126 @@ public class SearchEngineManager impleme
             Log.e(LOG_TAG, "Error getting search engine name from preferences.json", e);
         } catch (JSONException e) {
             Log.e(LOG_TAG, "Error parsing preferences.json", e);
         }
         return null;
     }
 
     /**
+     * Helper function for converting an InputStream to a String.
+     * @param is InputStream you want to convert to a String
+     *
+     * @return String containing the data
+     */
+    private String getHttpResponse(HttpURLConnection conn) {
+        InputStream is = null;
+        try {
+            is = new BufferedInputStream(conn.getInputStream());
+            return new java.util.Scanner(is).useDelimiter("\\A").next();
+        } catch (Exception e) {
+            return "";
+        } finally {
+            if (is != null) {
+                try {
+                    is.close();
+                } catch (IOException e) {
+                    Log.e(LOG_TAG, "Error closing InputStream", e);
+                }
+            }
+        }
+    }
+
+    /**
+     * Gets the country code based on the current IP, using the Mozilla Location Service.
+     * We cache the country code in a shared preference, so we only fetch from the network
+     * once.
+     *
+     * @return String containing the country code
+     */
+    private String fetchCountryCode() {
+        // First, we look to see if we have a cached code.
+        final String region = GeckoSharedPrefs.forApp(context).getString(PREF_REGION_KEY, null);
+        if (region != null) {
+            return region;
+        }
+
+        // Since we didn't have a cached code, we need to fetch a code from the service.
+        try {
+            String responseText = null;
+
+            URL url = new URL(GEOIP_LOCATION_URL);
+            HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
+            try {
+                // POST an empty JSON object.
+                final String message = "{}";
+
+                urlConnection.setDoOutput(true);
+                urlConnection.setConnectTimeout(10000);
+                urlConnection.setReadTimeout(10000);
+                urlConnection.setRequestMethod("POST");
+                urlConnection.setRequestProperty("User-Agent", USER_AGENT);
+                urlConnection.setRequestProperty("Content-Type", "application/json");
+                urlConnection.setFixedLengthStreamingMode(message.getBytes().length);
+
+                final OutputStream out = urlConnection.getOutputStream();
+                out.write(message.getBytes());
+                out.close();
+
+                responseText = getHttpResponse(urlConnection);
+            } finally {
+                urlConnection.disconnect();
+            }
+
+            if (responseText == null) {
+                Log.e(LOG_TAG, "Country code fetch failed");
+                return null;
+            }
+
+            // Extract the country code and save it for later in a cache.
+            final JSONObject response = new JSONObject(responseText);
+            return response.optString("country_code", null);
+        } catch (Exception e) {
+            Log.e(LOG_TAG, "Country code fetch failed", e);
+        }
+
+        return null;
+    }
+
+    /**
      * Looks for the default search engine shipped in the locale.
      *
      * @return search engine name.
      */
     private String getDefaultEngineNameFromLocale() {
         try {
             final JSONObject browsersearch = new JSONObject(RawResource.getAsString(context, R.raw.browsersearch));
+
+            // Get the region used to fence search engines.
+            String region = fetchCountryCode();
+
+            // Store the result, even if it's empty. If we fail to get a region, we never
+            // try to get it again, and we will always fallback to the non-region engine.
+            GeckoSharedPrefs.forApp(context)
+                            .edit()
+                            .putString(PREF_REGION_KEY, (region == null ? "" : region))
+                            .apply();
+
+            if (region != null) {
+                if (browsersearch.has("regions")) {
+                    final JSONObject regions = browsersearch.getJSONObject("regions");
+                    if (regions.has(region)) {
+                        final JSONObject regionData = regions.getJSONObject(region);
+                        Log.d(LOG_TAG, "Found region-specific default engine name in browsersearch.json.");
+                        return regionData.getString("default");
+                    }
+                }
+            }
+
+            // Either we have no geoip region, or we didn't find the right region and we are falling back to the default.
             if (browsersearch.has("default")) {
                 Log.d(LOG_TAG, "Found default engine name in browsersearch.json.");
                 return browsersearch.getString("default");
             }
         } catch (IOException e) {
             Log.e(LOG_TAG, "Error getting search engine name from browsersearch.json", e);
         } catch (JSONException e) {
             Log.e(LOG_TAG, "Error parsing browsersearch.json", e);