GoogleSuggestClient.java revision 9038d65a5a8ebcfada1ec3067f81a26f05622088
1/* 2 * Copyright (C) 2010 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package com.android.quicksearchbox.google; 18 19import com.android.quicksearchbox.R; 20import com.android.quicksearchbox.Source; 21import com.android.quicksearchbox.SourceResult; 22import com.android.quicksearchbox.SuggestionCursor; 23 24import org.apache.http.HttpResponse; 25import org.apache.http.client.HttpClient; 26import org.apache.http.client.methods.HttpGet; 27import org.apache.http.impl.client.DefaultHttpClient; 28import org.apache.http.params.HttpParams; 29import org.apache.http.params.HttpProtocolParams; 30import org.apache.http.util.EntityUtils; 31import org.json.JSONArray; 32import org.json.JSONException; 33 34import android.content.ComponentName; 35import android.content.Context; 36import android.net.ConnectivityManager; 37import android.net.NetworkInfo; 38import android.text.TextUtils; 39import android.util.Log; 40 41import java.io.IOException; 42import java.io.UnsupportedEncodingException; 43import java.net.URLEncoder; 44import java.util.Locale; 45 46/** 47 * Use network-based Google Suggests to provide search suggestions. 48 */ 49public class GoogleSuggestClient extends GoogleSource { 50 51 private static final boolean DBG = false; 52 private static final String LOG_TAG = "GoogleSearch"; 53 54 private static final String USER_AGENT = "Android/1.0"; 55 private String mSuggestUri; 56 private static final int HTTP_TIMEOUT_MS = 1000; 57 58 // TODO: this should be defined somewhere 59 private static final String HTTP_TIMEOUT = "http.connection-manager.timeout"; 60 61 private final HttpClient mHttpClient; 62 63 public GoogleSuggestClient(Context context) { 64 super(context); 65 mHttpClient = new DefaultHttpClient(); 66 HttpParams params = mHttpClient.getParams(); 67 HttpProtocolParams.setUserAgent(params, USER_AGENT); 68 params.setLongParameter(HTTP_TIMEOUT, HTTP_TIMEOUT_MS); 69 70 // NOTE: Do not look up the resource here; Localization changes may not have completed 71 // yet (e.g. we may still be reading the SIM card). 72 mSuggestUri = null; 73 } 74 75 @Override 76 public ComponentName getIntentComponent() { 77 return new ComponentName(getContext(), GoogleSearch.class); 78 } 79 80 @Override 81 public boolean isLocationAware() { 82 return false; 83 } 84 85 @Override 86 public SourceResult queryInternal(String query) { 87 return query(query); 88 } 89 90 @Override 91 public SourceResult queryExternal(String query) { 92 return query(query); 93 } 94 95 /** 96 * Queries for a given search term and returns a cursor containing 97 * suggestions ordered by best match. 98 */ 99 private SourceResult query(String query) { 100 if (TextUtils.isEmpty(query)) { 101 return null; 102 } 103 if (!isNetworkConnected()) { 104 Log.i(LOG_TAG, "Not connected to network."); 105 return null; 106 } 107 try { 108 query = URLEncoder.encode(query, "UTF-8"); 109 // NOTE: This code uses resources to optionally select the search Uri, based on the 110 // MCC value from the SIM. iThe default string will most likely be fine. It is 111 // paramerterized to accept info from the Locale, the language code is the first 112 // parameter (%1$s) and the country code is the second (%2$s). This code *must* 113 // function in the same way as a similar lookup in 114 // com.android.browser.BrowserActivity#onCreate(). If you change 115 // either of these functions, change them both. (The same is true for the underlying 116 // resource strings, which are stored in mcc-specific xml files.) 117 if (mSuggestUri == null) { 118 Locale l = Locale.getDefault(); 119 String language = l.getLanguage(); 120 String country = l.getCountry().toLowerCase(); 121 // Chinese and Portuguese have two langauge variants. 122 if ("zh".equals(language)) { 123 if ("cn".equals(country)) { 124 language = "zh-CN"; 125 } else if ("tw".equals(country)) { 126 language = "zh-TW"; 127 } 128 } else if ("pt".equals(language)) { 129 if ("br".equals(country)) { 130 language = "pt-BR"; 131 } else if ("pt".equals(country)) { 132 language = "pt-PT"; 133 } 134 } 135 mSuggestUri = getContext().getResources().getString(R.string.google_suggest_base, 136 language, 137 country) 138 + "json=true&q="; 139 } 140 141 String suggestUri = mSuggestUri + query; 142 if (DBG) Log.d(LOG_TAG, "Sending request: " + suggestUri); 143 HttpGet method = new HttpGet(suggestUri); 144 HttpResponse response = mHttpClient.execute(method); 145 if (response.getStatusLine().getStatusCode() == 200) { 146 147 /* Goto http://www.google.com/complete/search?json=true&q=foo 148 * to see what the data format looks like. It's basically a json 149 * array containing 4 other arrays. We only care about the middle 150 * 2 which contain the suggestions and their popularity. 151 */ 152 JSONArray results = new JSONArray(EntityUtils.toString(response.getEntity())); 153 JSONArray suggestions = results.getJSONArray(1); 154 JSONArray popularity = results.getJSONArray(2); 155 if (DBG) Log.d(LOG_TAG, "Got " + suggestions.length() + " results"); 156 return new GoogleSuggestCursor(this, query, suggestions, popularity); 157 } else { 158 if (DBG) Log.d(LOG_TAG, "Request failed " + response.getStatusLine()); 159 } 160 } catch (UnsupportedEncodingException e) { 161 Log.w(LOG_TAG, "Error", e); 162 } catch (IOException e) { 163 Log.w(LOG_TAG, "Error", e); 164 } catch (JSONException e) { 165 Log.w(LOG_TAG, "Error", e); 166 } 167 return null; 168 } 169 170 @Override 171 public SuggestionCursor refreshShortcut(String shortcutId, String oldExtraData) { 172 return null; 173 } 174 175 private boolean isNetworkConnected() { 176 NetworkInfo networkInfo = getActiveNetworkInfo(); 177 return networkInfo != null && networkInfo.isConnected(); 178 } 179 180 private NetworkInfo getActiveNetworkInfo() { 181 ConnectivityManager connectivity = 182 (ConnectivityManager) getContext().getSystemService(Context.CONNECTIVITY_SERVICE); 183 if (connectivity == null) { 184 return null; 185 } 186 return connectivity.getActiveNetworkInfo(); 187 } 188 189 private static class GoogleSuggestCursor extends AbstractGoogleSourceResult { 190 191 /* Contains the actual suggestions */ 192 private final JSONArray mSuggestions; 193 194 /* This contains the popularity of each suggestion 195 * i.e. 165,000 results. It's not related to sorting. 196 */ 197 private final JSONArray mPopularity; 198 199 public GoogleSuggestCursor(Source source, String userQuery, 200 JSONArray suggestions, JSONArray popularity) { 201 super(source, userQuery); 202 mSuggestions = suggestions; 203 mPopularity = popularity; 204 } 205 206 @Override 207 public int getCount() { 208 return mSuggestions.length(); 209 } 210 211 @Override 212 public String getSuggestionQuery() { 213 try { 214 return mSuggestions.getString(getPosition()); 215 } catch (JSONException e) { 216 Log.w(LOG_TAG, "Error parsing response: " + e); 217 return null; 218 } 219 } 220 221 @Override 222 public String getSuggestionText2() { 223 try { 224 return mPopularity.getString(getPosition()); 225 } catch (JSONException e) { 226 Log.w(LOG_TAG, "Error parsing response: " + e); 227 return null; 228 } 229 } 230 } 231} 232