1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this file, 3 * You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 5 package org.mozilla.gecko; 6 7 import java.io.BufferedInputStream; 8 import java.io.IOException; 9 import java.io.InputStream; 10 import java.net.HttpURLConnection; 11 import java.net.URL; 12 import java.net.URLEncoder; 13 import java.util.ArrayList; 14 15 import org.json.JSONArray; 16 import org.mozilla.gecko.annotation.RobocopTarget; 17 import org.mozilla.gecko.util.HardwareUtils; 18 19 import android.content.Context; 20 import android.text.TextUtils; 21 import android.util.Log; 22 import org.mozilla.gecko.util.NetworkUtils; 23 24 /** 25 * Use network-based search suggestions. 26 */ 27 public class SuggestClient { 28 private static final String LOGTAG = "GeckoSuggestClient"; 29 30 // This should go through GeckoInterface to get the UA, but the search activity addDisplay(int displayType, int width, int height, float density)31 // doesn't use a GeckoView yet. Until it does, get the UA directly. 32 private static final String USER_AGENT = HardwareUtils.isTablet() ? 33 AppConstants.USER_AGENT_FENNEC_TABLET : AppConstants.USER_AGENT_FENNEC_MOBILE; 34 35 private final Context mContext; 36 private final int mTimeout; 37 38 // should contain the string "__searchTerms__", which is replaced with the query 39 private final String mSuggestTemplate; 40 41 // the maximum number of suggestions to return removeDisplay(int screenId)42 private final int mMaxResults; 43 44 // used by robocop for testing 45 private final boolean mCheckNetwork; 46 47 // used to make suggestions appear instantly after opt-in 48 private String mPrevQuery; 49 private ArrayList<String> mPrevResults; 50 51 @RobocopTarget 52 public SuggestClient(Context context, String suggestTemplate, int timeout, int maxResults, boolean checkNetwork) { 53 mContext = context; 54 mMaxResults = maxResults; 55 mSuggestTemplate = suggestTemplate; 56 mTimeout = timeout; 57 mCheckNetwork = checkNetwork; 58 } 59 60 public String getSuggestTemplate() { 61 return mSuggestTemplate; 62 } 63 64 /** 65 * Queries for a given search term and returns an ArrayList of suggestions. 66 */ 67 public ArrayList<String> query(String query) { 68 if (query.equals(mPrevQuery)) 69 return mPrevResults; 70 71 ArrayList<String> suggestions = new ArrayList<String>(); 72 if (TextUtils.isEmpty(mSuggestTemplate) || TextUtils.isEmpty(query)) { 73 return suggestions; 74 } 75 76 if (!NetworkUtils.isConnected(mContext) && mCheckNetwork) { 77 Log.i(LOGTAG, "Not connected to network"); 78 return suggestions; 79 } 80 81 try { 82 String encoded = URLEncoder.encode(query, "UTF-8"); 83 String suggestUri = mSuggestTemplate.replace("__searchTerms__", encoded); 84 85 URL url = new URL(suggestUri); 86 String json = null; 87 HttpURLConnection urlConnection = null; 88 InputStream in = null; 89 try { 90 urlConnection = (HttpURLConnection) url.openConnection(); 91 urlConnection.setConnectTimeout(mTimeout); 92 urlConnection.setRequestProperty("User-Agent", USER_AGENT); 93 in = new BufferedInputStream(urlConnection.getInputStream()); 94 json = convertStreamToString(in); 95 } finally { 96 if (urlConnection != null) 97 urlConnection.disconnect(); 98 if (in != null) { 99 try { 100 in.close(); 101 } catch (IOException e) { 102 Log.e(LOGTAG, "error", e); 103 } 104 } 105 } 106 107 if (json != null) { 108 /* 109 * Sample result: 110 * ["foo",["food network","foothill college","foot locker",...]] 111 */ 112 JSONArray results = new JSONArray(json); 113 JSONArray jsonSuggestions = results.getJSONArray(1); 114 115 int added = 0; 116 for (int i = 0; (i < jsonSuggestions.length()) && (added < mMaxResults); i++) { 117 String suggestion = jsonSuggestions.getString(i); 118 if (!suggestion.equalsIgnoreCase(query)) { 119 suggestions.add(suggestion); 120 added++; 121 } 122 } 123 } else { 124 Log.e(LOGTAG, "Suggestion query failed"); 125 } 126 } catch (Exception e) { 127 Log.e(LOGTAG, "Error", e); 128 } 129 130 mPrevQuery = query; 131 mPrevResults = suggestions; 132 return suggestions; 133 } 134 135 private String convertStreamToString(java.io.InputStream is) { 136 try { 137 return new java.util.Scanner(is).useDelimiter("\\A").next(); 138 } catch (java.util.NoSuchElementException e) { 139 return ""; 140 } 141 } 142 } 143