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