149fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert/* 249fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert * Copyright (C) 2010 The Android Open Source Project 349fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert * 449fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert * Licensed under the Apache License, Version 2.0 (the "License"); 549fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert * you may not use this file except in compliance with the License. 649fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert * You may obtain a copy of the License at 749fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert * 849fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert * http://www.apache.org/licenses/LICENSE-2.0 949fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert * 1049fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert * Unless required by applicable law or agreed to in writing, software 1149fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert * distributed under the License is distributed on an "AS IS" BASIS, 1249fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 1349fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert * See the License for the specific language governing permissions and 1449fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert * limitations under the License. 1549fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert */ 1649fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert 1749fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringertpackage com.android.quicksearchbox.google; 1849fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert 19ecf356c15143ab0583c64682de16d94a57f7dd1cMathew Inwoodimport android.content.ComponentName; 20ecf356c15143ab0583c64682de16d94a57f7dd1cMathew Inwoodimport android.content.Context; 21ecf356c15143ab0583c64682de16d94a57f7dd1cMathew Inwoodimport android.net.ConnectivityManager; 22ecf356c15143ab0583c64682de16d94a57f7dd1cMathew Inwoodimport android.net.NetworkInfo; 23ecf356c15143ab0583c64682de16d94a57f7dd1cMathew Inwoodimport android.net.http.AndroidHttpClient; 24ecf356c15143ab0583c64682de16d94a57f7dd1cMathew Inwoodimport android.os.Build; 25ecf356c15143ab0583c64682de16d94a57f7dd1cMathew Inwoodimport android.os.Handler; 26ecf356c15143ab0583c64682de16d94a57f7dd1cMathew Inwoodimport android.text.TextUtils; 27ecf356c15143ab0583c64682de16d94a57f7dd1cMathew Inwoodimport android.util.Log; 28ecf356c15143ab0583c64682de16d94a57f7dd1cMathew Inwood 29cd4accc7899fa7b756e1c430d6b196525abd5c3cMathew Inwoodimport com.android.quicksearchbox.Config; 3049fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringertimport com.android.quicksearchbox.R; 3193bd2e70b8b08da1ec37fd0e990dac05551d2e90Bjorn Bringertimport com.android.quicksearchbox.Source; 3293bd2e70b8b08da1ec37fd0e990dac05551d2e90Bjorn Bringertimport com.android.quicksearchbox.SourceResult; 3393bd2e70b8b08da1ec37fd0e990dac05551d2e90Bjorn Bringertimport com.android.quicksearchbox.SuggestionCursor; 34516781305d7427e79928c95c6ea2b7689a4bf6ceMathew Inwoodimport com.android.quicksearchbox.util.NamedTaskExecutor; 3549fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert 3649fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringertimport org.apache.http.HttpResponse; 3749fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringertimport org.apache.http.client.HttpClient; 3849fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringertimport org.apache.http.client.methods.HttpGet; 3949fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringertimport org.apache.http.params.HttpParams; 4049fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringertimport org.apache.http.util.EntityUtils; 4149fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringertimport org.json.JSONArray; 4249fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringertimport org.json.JSONException; 4349fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert 4449fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringertimport java.io.IOException; 4549fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringertimport java.io.UnsupportedEncodingException; 4649fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringertimport java.net.URLEncoder; 4749fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringertimport java.util.Locale; 4849fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert 4949fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert/** 5049fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert * Use network-based Google Suggests to provide search suggestions. 5149fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert */ 52848fa7a19abedc372452073abaf52780c7b6d78dAmith Yamasanipublic class GoogleSuggestClient extends AbstractGoogleSource { 5349fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert 5449fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert private static final boolean DBG = false; 5549fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert private static final String LOG_TAG = "GoogleSearch"; 5649fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert 5709bfc59959dc7bab36edb66a41cb93c354e74626Mathew Inwood private static final String USER_AGENT = "Android/" + Build.VERSION.RELEASE; 5849fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert private String mSuggestUri; 5949fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert 6049fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert // TODO: this should be defined somewhere 6149ecac0f60307c1b8c6a29f4f9aca8ee07728869Jean-Baptiste Queru private static final String HTTP_TIMEOUT = "http.conn-manager.timeout"; 6249fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert 6393bd2e70b8b08da1ec37fd0e990dac05551d2e90Bjorn Bringert private final HttpClient mHttpClient; 6449fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert 65cd4accc7899fa7b756e1c430d6b196525abd5c3cMathew Inwood public GoogleSuggestClient(Context context, Handler uiThread, 66cd4accc7899fa7b756e1c430d6b196525abd5c3cMathew Inwood NamedTaskExecutor iconLoader, Config config) { 67516781305d7427e79928c95c6ea2b7689a4bf6ceMathew Inwood super(context, uiThread, iconLoader); 689ddfa44af5b4211cd3a88b1d8a08f1afd0a9be56Bjorn Bringert mHttpClient = AndroidHttpClient.newInstance(USER_AGENT, context); 6949fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert HttpParams params = mHttpClient.getParams(); 70cd4accc7899fa7b756e1c430d6b196525abd5c3cMathew Inwood params.setLongParameter(HTTP_TIMEOUT, config.getHttpConnectTimeout()); 7149fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert 7249fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert // NOTE: Do not look up the resource here; Localization changes may not have completed 7349fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert // yet (e.g. we may still be reading the SIM card). 7449fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert mSuggestUri = null; 7549fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert } 7649fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert 77848fa7a19abedc372452073abaf52780c7b6d78dAmith Yamasani @Override 7849fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert public ComponentName getIntentComponent() { 7949fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert return new ComponentName(getContext(), GoogleSearch.class); 8049fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert } 8149fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert 82848fa7a19abedc372452073abaf52780c7b6d78dAmith Yamasani @Override 8369494b842a3f907164a457852c385f86dbe71d15Bjorn Bringert public SourceResult queryInternal(String query) { 8469494b842a3f907164a457852c385f86dbe71d15Bjorn Bringert return query(query); 8569494b842a3f907164a457852c385f86dbe71d15Bjorn Bringert } 8669494b842a3f907164a457852c385f86dbe71d15Bjorn Bringert 8769494b842a3f907164a457852c385f86dbe71d15Bjorn Bringert @Override 8869494b842a3f907164a457852c385f86dbe71d15Bjorn Bringert public SourceResult queryExternal(String query) { 8969494b842a3f907164a457852c385f86dbe71d15Bjorn Bringert return query(query); 9069494b842a3f907164a457852c385f86dbe71d15Bjorn Bringert } 9169494b842a3f907164a457852c385f86dbe71d15Bjorn Bringert 9249fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert /** 9349fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert * Queries for a given search term and returns a cursor containing 9449fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert * suggestions ordered by best match. 9549fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert */ 9669494b842a3f907164a457852c385f86dbe71d15Bjorn Bringert private SourceResult query(String query) { 9749fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert if (TextUtils.isEmpty(query)) { 9849fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert return null; 9949fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert } 10049fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert if (!isNetworkConnected()) { 10149fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert Log.i(LOG_TAG, "Not connected to network."); 10249fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert return null; 10349fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert } 10449fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert try { 105ecf356c15143ab0583c64682de16d94a57f7dd1cMathew Inwood String encodedQuery = URLEncoder.encode(query, "UTF-8"); 10649fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert if (mSuggestUri == null) { 10749fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert Locale l = Locale.getDefault(); 108c4e96e6c09422ab54984144ad618b2ecefb4b891Mathew Inwood String language = GoogleSearch.getLanguage(l); 10949fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert mSuggestUri = getContext().getResources().getString(R.string.google_suggest_base, 11009bfc59959dc7bab36edb66a41cb93c354e74626Mathew Inwood language); 11149fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert } 11249fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert 113ecf356c15143ab0583c64682de16d94a57f7dd1cMathew Inwood String suggestUri = mSuggestUri + encodedQuery; 11449fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert if (DBG) Log.d(LOG_TAG, "Sending request: " + suggestUri); 11549fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert HttpGet method = new HttpGet(suggestUri); 11649fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert HttpResponse response = mHttpClient.execute(method); 11749fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert if (response.getStatusLine().getStatusCode() == 200) { 11849fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert 11949fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert /* Goto http://www.google.com/complete/search?json=true&q=foo 12049fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert * to see what the data format looks like. It's basically a json 12149fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert * array containing 4 other arrays. We only care about the middle 12249fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert * 2 which contain the suggestions and their popularity. 12349fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert */ 12449fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert JSONArray results = new JSONArray(EntityUtils.toString(response.getEntity())); 12549fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert JSONArray suggestions = results.getJSONArray(1); 12649fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert JSONArray popularity = results.getJSONArray(2); 12749fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert if (DBG) Log.d(LOG_TAG, "Got " + suggestions.length() + " results"); 12869494b842a3f907164a457852c385f86dbe71d15Bjorn Bringert return new GoogleSuggestCursor(this, query, suggestions, popularity); 12949fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert } else { 13049fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert if (DBG) Log.d(LOG_TAG, "Request failed " + response.getStatusLine()); 13149fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert } 13249fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert } catch (UnsupportedEncodingException e) { 13349fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert Log.w(LOG_TAG, "Error", e); 13449fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert } catch (IOException e) { 13549fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert Log.w(LOG_TAG, "Error", e); 13649fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert } catch (JSONException e) { 13749fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert Log.w(LOG_TAG, "Error", e); 13849fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert } 13949fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert return null; 14049fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert } 14149fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert 142848fa7a19abedc372452073abaf52780c7b6d78dAmith Yamasani @Override 14393bd2e70b8b08da1ec37fd0e990dac05551d2e90Bjorn Bringert public SuggestionCursor refreshShortcut(String shortcutId, String oldExtraData) { 14449fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert return null; 14549fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert } 14649fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert 14749fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert private boolean isNetworkConnected() { 14849fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert NetworkInfo networkInfo = getActiveNetworkInfo(); 14949fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert return networkInfo != null && networkInfo.isConnected(); 15049fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert } 15149fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert 15249fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert private NetworkInfo getActiveNetworkInfo() { 15349fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert ConnectivityManager connectivity = 15449fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert (ConnectivityManager) getContext().getSystemService(Context.CONNECTIVITY_SERVICE); 15549fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert if (connectivity == null) { 15649fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert return null; 15749fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert } 15849fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert return connectivity.getActiveNetworkInfo(); 15949fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert } 16049fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert 16193bd2e70b8b08da1ec37fd0e990dac05551d2e90Bjorn Bringert private static class GoogleSuggestCursor extends AbstractGoogleSourceResult { 16249fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert 16349fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert /* Contains the actual suggestions */ 16493bd2e70b8b08da1ec37fd0e990dac05551d2e90Bjorn Bringert private final JSONArray mSuggestions; 16549fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert 16649fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert /* This contains the popularity of each suggestion 16749fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert * i.e. 165,000 results. It's not related to sorting. 16849fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert */ 16993bd2e70b8b08da1ec37fd0e990dac05551d2e90Bjorn Bringert private final JSONArray mPopularity; 17093bd2e70b8b08da1ec37fd0e990dac05551d2e90Bjorn Bringert 17193bd2e70b8b08da1ec37fd0e990dac05551d2e90Bjorn Bringert public GoogleSuggestCursor(Source source, String userQuery, 17293bd2e70b8b08da1ec37fd0e990dac05551d2e90Bjorn Bringert JSONArray suggestions, JSONArray popularity) { 17393bd2e70b8b08da1ec37fd0e990dac05551d2e90Bjorn Bringert super(source, userQuery); 17449fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert mSuggestions = suggestions; 17549fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert mPopularity = popularity; 17649fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert } 17749fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert 17849fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert @Override 17949fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert public int getCount() { 18049fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert return mSuggestions.length(); 18149fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert } 18249fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert 18349fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert @Override 18493bd2e70b8b08da1ec37fd0e990dac05551d2e90Bjorn Bringert public String getSuggestionQuery() { 18549fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert try { 18693bd2e70b8b08da1ec37fd0e990dac05551d2e90Bjorn Bringert return mSuggestions.getString(getPosition()); 18749fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert } catch (JSONException e) { 18849fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert Log.w(LOG_TAG, "Error parsing response: " + e); 18949fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert return null; 19049fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert } 19149fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert } 19249fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert 19349fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert @Override 19493bd2e70b8b08da1ec37fd0e990dac05551d2e90Bjorn Bringert public String getSuggestionText2() { 19593bd2e70b8b08da1ec37fd0e990dac05551d2e90Bjorn Bringert try { 19693bd2e70b8b08da1ec37fd0e990dac05551d2e90Bjorn Bringert return mPopularity.getString(getPosition()); 19793bd2e70b8b08da1ec37fd0e990dac05551d2e90Bjorn Bringert } catch (JSONException e) { 19893bd2e70b8b08da1ec37fd0e990dac05551d2e90Bjorn Bringert Log.w(LOG_TAG, "Error parsing response: " + e); 19993bd2e70b8b08da1ec37fd0e990dac05551d2e90Bjorn Bringert return null; 20049fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert } 20149fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert } 20249fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert } 20349fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert} 204