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