GoogleSuggestClient.java revision 516781305d7427e79928c95c6ea2b7689a4bf6ce
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
1949fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringertimport com.android.quicksearchbox.R;
2093bd2e70b8b08da1ec37fd0e990dac05551d2e90Bjorn Bringertimport com.android.quicksearchbox.Source;
2193bd2e70b8b08da1ec37fd0e990dac05551d2e90Bjorn Bringertimport com.android.quicksearchbox.SourceResult;
2293bd2e70b8b08da1ec37fd0e990dac05551d2e90Bjorn Bringertimport com.android.quicksearchbox.SuggestionCursor;
23516781305d7427e79928c95c6ea2b7689a4bf6ceMathew Inwoodimport com.android.quicksearchbox.util.NamedTaskExecutor;
2449fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert
2549fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringertimport org.apache.http.HttpResponse;
2649fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringertimport org.apache.http.client.HttpClient;
2749fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringertimport org.apache.http.client.methods.HttpGet;
2849fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringertimport org.apache.http.impl.client.DefaultHttpClient;
2949fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringertimport org.apache.http.params.HttpParams;
3049fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringertimport org.apache.http.params.HttpProtocolParams;
3149fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringertimport org.apache.http.util.EntityUtils;
3249fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringertimport org.json.JSONArray;
3349fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringertimport org.json.JSONException;
3449fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert
3549fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringertimport android.content.ComponentName;
3649fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringertimport android.content.Context;
3749fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringertimport android.net.ConnectivityManager;
3849fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringertimport android.net.NetworkInfo;
39e29d52aa72c96c3147fa91d83aeb8dafc6d1f578Mathew Inwoodimport android.os.Handler;
4049fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringertimport android.text.TextUtils;
4149fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringertimport android.util.Log;
4249fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert
4349fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringertimport java.io.IOException;
4449fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringertimport java.io.UnsupportedEncodingException;
4549fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringertimport java.net.URLEncoder;
4649fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringertimport java.util.Locale;
4749fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert
4849fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert/**
4949fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert * Use network-based Google Suggests to provide search suggestions.
5049fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert */
51848fa7a19abedc372452073abaf52780c7b6d78dAmith Yamasanipublic class GoogleSuggestClient extends AbstractGoogleSource {
5249fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert
5349fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert    private static final boolean DBG = false;
5449fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert    private static final String LOG_TAG = "GoogleSearch";
5549fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert
5649fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert    private static final String USER_AGENT = "Android/1.0";
5749fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert    private String mSuggestUri;
5849fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert    private static final int HTTP_TIMEOUT_MS = 1000;
5949fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert
6049fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert    // TODO: this should be defined somewhere
6149fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert    private static final String HTTP_TIMEOUT = "http.connection-manager.timeout";
6249fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert
6393bd2e70b8b08da1ec37fd0e990dac05551d2e90Bjorn Bringert    private final HttpClient mHttpClient;
6449fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert
65516781305d7427e79928c95c6ea2b7689a4bf6ceMathew Inwood    public GoogleSuggestClient(Context context, Handler uiThread, NamedTaskExecutor iconLoader) {
66516781305d7427e79928c95c6ea2b7689a4bf6ceMathew Inwood        super(context, uiThread, iconLoader);
6749fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert        mHttpClient = new DefaultHttpClient();
6849fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert        HttpParams params = mHttpClient.getParams();
6949fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert        HttpProtocolParams.setUserAgent(params, USER_AGENT);
7049fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert        params.setLongParameter(HTTP_TIMEOUT, HTTP_TIMEOUT_MS);
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 {
10549fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert            query = 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,
110c4e96e6c09422ab54984144ad618b2ecefb4b891Mathew Inwood                                                                    language)
11149fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert                        + "json=true&q=";
11249fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert            }
11349fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert
11449fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert            String suggestUri = mSuggestUri + query;
11549fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert            if (DBG) Log.d(LOG_TAG, "Sending request: " + suggestUri);
11649fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert            HttpGet method = new HttpGet(suggestUri);
11749fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert            HttpResponse response = mHttpClient.execute(method);
11849fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert            if (response.getStatusLine().getStatusCode() == 200) {
11949fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert
12049fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert                /* Goto http://www.google.com/complete/search?json=true&q=foo
12149fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert                 * to see what the data format looks like. It's basically a json
12249fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert                 * array containing 4 other arrays. We only care about the middle
12349fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert                 * 2 which contain the suggestions and their popularity.
12449fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert                 */
12549fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert                JSONArray results = new JSONArray(EntityUtils.toString(response.getEntity()));
12649fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert                JSONArray suggestions = results.getJSONArray(1);
12749fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert                JSONArray popularity = results.getJSONArray(2);
12849fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert                if (DBG) Log.d(LOG_TAG, "Got " + suggestions.length() + " results");
12969494b842a3f907164a457852c385f86dbe71d15Bjorn Bringert                return new GoogleSuggestCursor(this, query, suggestions, popularity);
13049fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert            } else {
13149fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert                if (DBG) Log.d(LOG_TAG, "Request failed " + response.getStatusLine());
13249fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert            }
13349fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert        } catch (UnsupportedEncodingException e) {
13449fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert            Log.w(LOG_TAG, "Error", e);
13549fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert        } catch (IOException e) {
13649fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert            Log.w(LOG_TAG, "Error", e);
13749fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert        } catch (JSONException e) {
13849fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert            Log.w(LOG_TAG, "Error", e);
13949fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert        }
14049fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert        return null;
14149fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert    }
14249fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert
143848fa7a19abedc372452073abaf52780c7b6d78dAmith Yamasani    @Override
14493bd2e70b8b08da1ec37fd0e990dac05551d2e90Bjorn Bringert    public SuggestionCursor refreshShortcut(String shortcutId, String oldExtraData) {
14549fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert        return null;
14649fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert    }
14749fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert
14849fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert    private boolean isNetworkConnected() {
14949fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert        NetworkInfo networkInfo = getActiveNetworkInfo();
15049fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert        return networkInfo != null && networkInfo.isConnected();
15149fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert    }
15249fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert
15349fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert    private NetworkInfo getActiveNetworkInfo() {
15449fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert        ConnectivityManager connectivity =
15549fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert                (ConnectivityManager) getContext().getSystemService(Context.CONNECTIVITY_SERVICE);
15649fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert        if (connectivity == null) {
15749fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert            return null;
15849fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert        }
15949fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert        return connectivity.getActiveNetworkInfo();
16049fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert    }
16149fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert
16293bd2e70b8b08da1ec37fd0e990dac05551d2e90Bjorn Bringert    private static class GoogleSuggestCursor extends AbstractGoogleSourceResult {
16349fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert
16449fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert        /* Contains the actual suggestions */
16593bd2e70b8b08da1ec37fd0e990dac05551d2e90Bjorn Bringert        private final JSONArray mSuggestions;
16649fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert
16749fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert        /* This contains the popularity of each suggestion
16849fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert         * i.e. 165,000 results. It's not related to sorting.
16949fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert         */
17093bd2e70b8b08da1ec37fd0e990dac05551d2e90Bjorn Bringert        private final JSONArray mPopularity;
17193bd2e70b8b08da1ec37fd0e990dac05551d2e90Bjorn Bringert
17293bd2e70b8b08da1ec37fd0e990dac05551d2e90Bjorn Bringert        public GoogleSuggestCursor(Source source, String userQuery,
17393bd2e70b8b08da1ec37fd0e990dac05551d2e90Bjorn Bringert                JSONArray suggestions, JSONArray popularity) {
17493bd2e70b8b08da1ec37fd0e990dac05551d2e90Bjorn Bringert            super(source, userQuery);
17549fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert            mSuggestions = suggestions;
17649fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert            mPopularity = popularity;
17749fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert        }
17849fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert
17949fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert        @Override
18049fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert        public int getCount() {
18149fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert            return mSuggestions.length();
18249fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert        }
18349fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert
18449fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert        @Override
18593bd2e70b8b08da1ec37fd0e990dac05551d2e90Bjorn Bringert        public String getSuggestionQuery() {
18649fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert            try {
18793bd2e70b8b08da1ec37fd0e990dac05551d2e90Bjorn Bringert                return mSuggestions.getString(getPosition());
18849fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert            } catch (JSONException e) {
18949fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert                Log.w(LOG_TAG, "Error parsing response: " + e);
19049fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert                return null;
19149fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert            }
19249fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert        }
19349fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert
19449fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert        @Override
19593bd2e70b8b08da1ec37fd0e990dac05551d2e90Bjorn Bringert        public String getSuggestionText2() {
19693bd2e70b8b08da1ec37fd0e990dac05551d2e90Bjorn Bringert            try {
19793bd2e70b8b08da1ec37fd0e990dac05551d2e90Bjorn Bringert                return mPopularity.getString(getPosition());
19893bd2e70b8b08da1ec37fd0e990dac05551d2e90Bjorn Bringert            } catch (JSONException e) {
19993bd2e70b8b08da1ec37fd0e990dac05551d2e90Bjorn Bringert                Log.w(LOG_TAG, "Error parsing response: " + e);
20093bd2e70b8b08da1ec37fd0e990dac05551d2e90Bjorn Bringert                return null;
20149fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert            }
20249fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert        }
20349fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert    }
20449fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert}
205