Home | History | Annotate | Download | only in google
      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 
     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
     78     public ComponentName getIntentComponent() {
     79         return new ComponentName(getContext(), GoogleSearch.class);
     80     }
     81 
     82     @Override
     83     public SourceResult queryInternal(String query) {
     84         return query(query);
     85     }
     86 
     87     @Override
     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      */
     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
    143     public SuggestionCursor refreshShortcut(String shortcutId, String oldExtraData) {
    144         return null;
    145     }
    146 
    147     private boolean isNetworkConnected() {
    148         NetworkInfo networkInfo = getActiveNetworkInfo();
    149         return networkInfo != null && networkInfo.isConnected();
    150     }
    151 
    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 
    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
    179         public int getCount() {
    180             return mSuggestions.length();
    181         }
    182 
    183         @Override
    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
    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