GoogleSuggestClient.java revision 69494b842a3f907164a457852c385f86dbe71d15
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 public ComponentName getIntentComponent() { 76 return new ComponentName(getContext(), GoogleSearch.class); 77 } 78 79 public boolean isLocationAware() { 80 return false; 81 } 82 83 @Override 84 public SourceResult queryInternal(String query) { 85 return query(query); 86 } 87 88 @Override 89 public SourceResult queryExternal(String query) { 90 return query(query); 91 } 92 93 /** 94 * Queries for a given search term and returns a cursor containing 95 * suggestions ordered by best match. 96 */ 97 private SourceResult query(String query) { 98 if (TextUtils.isEmpty(query)) { 99 return null; 100 } 101 if (!isNetworkConnected()) { 102 Log.i(LOG_TAG, "Not connected to network."); 103 return null; 104 } 105 try { 106 query = URLEncoder.encode(query, "UTF-8"); 107 // NOTE: This code uses resources to optionally select the search Uri, based on the 108 // MCC value from the SIM. iThe default string will most likely be fine. It is 109 // paramerterized to accept info from the Locale, the language code is the first 110 // parameter (%1$s) and the country code is the second (%2$s). This code *must* 111 // function in the same way as a similar lookup in 112 // com.android.browser.BrowserActivity#onCreate(). If you change 113 // either of these functions, change them both. (The same is true for the underlying 114 // resource strings, which are stored in mcc-specific xml files.) 115 if (mSuggestUri == null) { 116 Locale l = Locale.getDefault(); 117 String language = l.getLanguage(); 118 String country = l.getCountry().toLowerCase(); 119 // Chinese and Portuguese have two langauge variants. 120 if ("zh".equals(language)) { 121 if ("cn".equals(country)) { 122 language = "zh-CN"; 123 } else if ("tw".equals(country)) { 124 language = "zh-TW"; 125 } 126 } else if ("pt".equals(language)) { 127 if ("br".equals(country)) { 128 language = "pt-BR"; 129 } else if ("pt".equals(country)) { 130 language = "pt-PT"; 131 } 132 } 133 mSuggestUri = getContext().getResources().getString(R.string.google_suggest_base, 134 language, 135 country) 136 + "json=true&q="; 137 } 138 139 String suggestUri = mSuggestUri + query; 140 if (DBG) Log.d(LOG_TAG, "Sending request: " + suggestUri); 141 HttpGet method = new HttpGet(suggestUri); 142 HttpResponse response = mHttpClient.execute(method); 143 if (response.getStatusLine().getStatusCode() == 200) { 144 145 /* Goto http://www.google.com/complete/search?json=true&q=foo 146 * to see what the data format looks like. It's basically a json 147 * array containing 4 other arrays. We only care about the middle 148 * 2 which contain the suggestions and their popularity. 149 */ 150 JSONArray results = new JSONArray(EntityUtils.toString(response.getEntity())); 151 JSONArray suggestions = results.getJSONArray(1); 152 JSONArray popularity = results.getJSONArray(2); 153 if (DBG) Log.d(LOG_TAG, "Got " + suggestions.length() + " results"); 154 return new GoogleSuggestCursor(this, query, suggestions, popularity); 155 } else { 156 if (DBG) Log.d(LOG_TAG, "Request failed " + response.getStatusLine()); 157 } 158 } catch (UnsupportedEncodingException e) { 159 Log.w(LOG_TAG, "Error", e); 160 } catch (IOException e) { 161 Log.w(LOG_TAG, "Error", e); 162 } catch (JSONException e) { 163 Log.w(LOG_TAG, "Error", e); 164 } 165 return null; 166 } 167 168 public SuggestionCursor refreshShortcut(String shortcutId, String oldExtraData) { 169 return null; 170 } 171 172 private boolean isNetworkConnected() { 173 NetworkInfo networkInfo = getActiveNetworkInfo(); 174 return networkInfo != null && networkInfo.isConnected(); 175 } 176 177 private NetworkInfo getActiveNetworkInfo() { 178 ConnectivityManager connectivity = 179 (ConnectivityManager) getContext().getSystemService(Context.CONNECTIVITY_SERVICE); 180 if (connectivity == null) { 181 return null; 182 } 183 return connectivity.getActiveNetworkInfo(); 184 } 185 186 private static class GoogleSuggestCursor extends AbstractGoogleSourceResult { 187 188 /* Contains the actual suggestions */ 189 private final JSONArray mSuggestions; 190 191 /* This contains the popularity of each suggestion 192 * i.e. 165,000 results. It's not related to sorting. 193 */ 194 private final JSONArray mPopularity; 195 196 public GoogleSuggestCursor(Source source, String userQuery, 197 JSONArray suggestions, JSONArray popularity) { 198 super(source, userQuery); 199 mSuggestions = suggestions; 200 mPopularity = popularity; 201 } 202 203 @Override 204 public int getCount() { 205 return mSuggestions.length(); 206 } 207 208 @Override 209 public String getSuggestionQuery() { 210 try { 211 return mSuggestions.getString(getPosition()); 212 } catch (JSONException e) { 213 Log.w(LOG_TAG, "Error parsing response: " + e); 214 return null; 215 } 216 } 217 218 @Override 219 public String getSuggestionText2() { 220 try { 221 return mPopularity.getString(getPosition()); 222 } catch (JSONException e) { 223 Log.w(LOG_TAG, "Error parsing response: " + e); 224 return null; 225 } 226 } 227 } 228} 229