GoogleSuggestClient.java revision cd4accc7899fa7b756e1c430d6b196525abd5c3c
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.Config; 20import com.android.quicksearchbox.R; 21import com.android.quicksearchbox.Source; 22import com.android.quicksearchbox.SourceResult; 23import com.android.quicksearchbox.SuggestionCursor; 24import com.android.quicksearchbox.util.NamedTaskExecutor; 25 26import org.apache.http.HttpResponse; 27import org.apache.http.client.HttpClient; 28import org.apache.http.client.methods.HttpGet; 29import org.apache.http.impl.client.DefaultHttpClient; 30import org.apache.http.params.HttpParams; 31import org.apache.http.params.HttpProtocolParams; 32import org.apache.http.util.EntityUtils; 33import org.json.JSONArray; 34import org.json.JSONException; 35 36import android.content.ComponentName; 37import android.content.Context; 38import android.net.ConnectivityManager; 39import android.net.NetworkInfo; 40import android.os.Handler; 41import android.text.TextUtils; 42import android.util.Log; 43 44import java.io.IOException; 45import java.io.UnsupportedEncodingException; 46import java.net.URLEncoder; 47import java.util.Locale; 48 49/** 50 * Use network-based Google Suggests to provide search suggestions. 51 */ 52public class GoogleSuggestClient extends AbstractGoogleSource { 53 54 private static final boolean DBG = false; 55 private static final String LOG_TAG = "GoogleSearch"; 56 57 private static final String USER_AGENT = "Android/1.0"; 58 private String mSuggestUri; 59 60 // TODO: this should be defined somewhere 61 private static final String HTTP_TIMEOUT = "http.conn-manager.timeout"; 62 63 private final HttpClient mHttpClient; 64 65 public GoogleSuggestClient(Context context, Handler uiThread, 66 NamedTaskExecutor iconLoader, Config config) { 67 super(context, uiThread, iconLoader); 68 mHttpClient = new DefaultHttpClient(); 69 HttpParams params = mHttpClient.getParams(); 70 HttpProtocolParams.setUserAgent(params, USER_AGENT); 71 params.setLongParameter(HTTP_TIMEOUT, config.getHttpConnectTimeout()); 72 73 // NOTE: Do not look up the resource here; Localization changes may not have completed 74 // yet (e.g. we may still be reading the SIM card). 75 mSuggestUri = null; 76 } 77 78 @Override 79 public ComponentName getIntentComponent() { 80 return new ComponentName(getContext(), GoogleSearch.class); 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 if (mSuggestUri == null) { 108 Locale l = Locale.getDefault(); 109 String language = GoogleSearch.getLanguage(l); 110 mSuggestUri = getContext().getResources().getString(R.string.google_suggest_base, 111 language) 112 + "json=true&q="; 113 } 114 115 String suggestUri = mSuggestUri + query; 116 if (DBG) Log.d(LOG_TAG, "Sending request: " + suggestUri); 117 HttpGet method = new HttpGet(suggestUri); 118 HttpResponse response = mHttpClient.execute(method); 119 if (response.getStatusLine().getStatusCode() == 200) { 120 121 /* Goto http://www.google.com/complete/search?json=true&q=foo 122 * to see what the data format looks like. It's basically a json 123 * array containing 4 other arrays. We only care about the middle 124 * 2 which contain the suggestions and their popularity. 125 */ 126 JSONArray results = new JSONArray(EntityUtils.toString(response.getEntity())); 127 JSONArray suggestions = results.getJSONArray(1); 128 JSONArray popularity = results.getJSONArray(2); 129 if (DBG) Log.d(LOG_TAG, "Got " + suggestions.length() + " results"); 130 return new GoogleSuggestCursor(this, query, suggestions, popularity); 131 } else { 132 if (DBG) Log.d(LOG_TAG, "Request failed " + response.getStatusLine()); 133 } 134 } catch (UnsupportedEncodingException e) { 135 Log.w(LOG_TAG, "Error", e); 136 } catch (IOException e) { 137 Log.w(LOG_TAG, "Error", e); 138 } catch (JSONException e) { 139 Log.w(LOG_TAG, "Error", e); 140 } 141 return null; 142 } 143 144 @Override 145 public SuggestionCursor refreshShortcut(String shortcutId, String oldExtraData) { 146 return null; 147 } 148 149 private boolean isNetworkConnected() { 150 NetworkInfo networkInfo = getActiveNetworkInfo(); 151 return networkInfo != null && networkInfo.isConnected(); 152 } 153 154 private NetworkInfo getActiveNetworkInfo() { 155 ConnectivityManager connectivity = 156 (ConnectivityManager) getContext().getSystemService(Context.CONNECTIVITY_SERVICE); 157 if (connectivity == null) { 158 return null; 159 } 160 return connectivity.getActiveNetworkInfo(); 161 } 162 163 private static class GoogleSuggestCursor extends AbstractGoogleSourceResult { 164 165 /* Contains the actual suggestions */ 166 private final JSONArray mSuggestions; 167 168 /* This contains the popularity of each suggestion 169 * i.e. 165,000 results. It's not related to sorting. 170 */ 171 private final JSONArray mPopularity; 172 173 public GoogleSuggestCursor(Source source, String userQuery, 174 JSONArray suggestions, JSONArray popularity) { 175 super(source, userQuery); 176 mSuggestions = suggestions; 177 mPopularity = popularity; 178 } 179 180 @Override 181 public int getCount() { 182 return mSuggestions.length(); 183 } 184 185 @Override 186 public String getSuggestionQuery() { 187 try { 188 return mSuggestions.getString(getPosition()); 189 } catch (JSONException e) { 190 Log.w(LOG_TAG, "Error parsing response: " + e); 191 return null; 192 } 193 } 194 195 @Override 196 public String getSuggestionText2() { 197 try { 198 return mPopularity.getString(getPosition()); 199 } catch (JSONException e) { 200 Log.w(LOG_TAG, "Error parsing response: " + e); 201 return null; 202 } 203 } 204 } 205} 206