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