diff options
Diffstat (limited to 'src/com/android')
-rw-r--r-- | src/com/android/googlesearch/GoogleSearch.java | 120 | ||||
-rw-r--r-- | src/com/android/googlesearch/LocationUtils.java | 104 | ||||
-rw-r--r-- | src/com/android/googlesearch/SuggestionProvider.java | 82 |
3 files changed, 270 insertions, 36 deletions
diff --git a/src/com/android/googlesearch/GoogleSearch.java b/src/com/android/googlesearch/GoogleSearch.java index 63a870d..e08f2b1 100644 --- a/src/com/android/googlesearch/GoogleSearch.java +++ b/src/com/android/googlesearch/GoogleSearch.java @@ -16,37 +16,131 @@ package com.android.googlesearch; +import com.google.android.providers.GoogleSettings.Partner; + +import java.io.UnsupportedEncodingException; +import java.net.URLEncoder; +import java.util.Locale; + import android.app.Activity; import android.app.SearchManager; +import android.content.ContentResolver; +import android.content.Context; import android.content.Intent; +import android.location.Location; +import android.location.LocationManager; +import android.net.Uri; import android.os.Bundle; -import android.provider.SearchRecentSuggestions; +import android.provider.Browser; +import android.provider.Settings; import android.text.TextUtils; +import android.util.Log; /** * This class is purely here to get search queries and route them to * the global {@link Intent#ACTION_WEB_SEARCH}. */ public class GoogleSearch extends Activity { + private static final String TAG = "GoogleSearch"; + + // The template URL we should use to format google search requests. + private String googleSearchUrlBase = null; + + // "source" parameter for Google search requests from unknown sources (e.g. apps). This will get + // prefixed with the string 'android-' before being sent on the wire. + final static String GOOGLE_SEARCH_SOURCE_UNKNOWN = "unknown"; + private LocationUtils mLocationUtils; + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + mLocationUtils = LocationUtils.getLocationUtils(this); Intent intent = getIntent(); - if ((intent != null) && Intent.ACTION_SEARCH.equals(intent.getAction())) { - String query = intent.getStringExtra(SearchManager.QUERY); - if (!TextUtils.isEmpty(query)) { - // forward query to browser for Google search - Intent search = new Intent(Intent.ACTION_WEB_SEARCH); - search.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - search.putExtra(SearchManager.QUERY, query); - final Bundle appData = intent.getBundleExtra(SearchManager.APP_DATA); - if (appData != null) { - search.putExtra(SearchManager.APP_DATA, appData); + if ((intent != null) && Intent.ACTION_WEB_SEARCH.equals(intent.getAction())) { + handleWebSearchIntent(intent); + } + finish(); + } + + /** + * NOTE: This function is similar to the one found in + * com.google.android.providers.enhancedgooglesearch.Launcher. If you are changing this + * make sure you change both. + */ + private void handleWebSearchIntent(Intent intent) { + String query = intent.getStringExtra(SearchManager.QUERY); + if (TextUtils.isEmpty(query)) { + Log.w(TAG, "Got search intent with no query."); + return; + } + + if (googleSearchUrlBase == null) { + Locale l = Locale.getDefault(); + String language = l.getLanguage(); + String country = l.getCountry().toLowerCase(); + // Chinese and Portuguese have two langauge variants. + if ("zh".equals(language)) { + if ("cn".equals(country)) { + language = "zh-CN"; + } else if ("tw".equals(country)) { + language = "zh-TW"; + } + } else if ("pt".equals(language)) { + if ("br".equals(country)) { + language = "pt-BR"; + } else if ("pt".equals(country)) { + language = "pt-PT"; } - startActivity(search); } + googleSearchUrlBase = getResources().getString( + R.string.google_search_base, language, country) + + "client=ms-" + + Partner.getString(this.getContentResolver(), Partner.CLIENT_ID); } - finish(); + + // If the caller specified a 'source' url parameter, use that and if not use default. + Bundle appSearchData = intent.getBundleExtra(SearchManager.APP_DATA); + String source = GOOGLE_SEARCH_SOURCE_UNKNOWN; + if (appSearchData != null) { + source = appSearchData.getString(SearchManager.SOURCE); + } + + try { + String searchUri = googleSearchUrlBase + + "&source=android-" + source + + "&q=" + URLEncoder.encode(query, "UTF-8"); + Intent launchUriIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(searchUri)); + launchUriIntent.putExtra(Browser.EXTRA_POST_DATA, getLocationData()); + launchUriIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + startActivity(launchUriIntent); + } catch (UnsupportedEncodingException e) { + Log.w(TAG, "Error", e); + } + } + + private byte[] getLocationData() { + byte[] postData = null; + ContentResolver cr = getContentResolver(); + + // Don't send any location if the system does not have GoogleSettingsProvider. + if (!mLocationUtils.systemHasGoogleSettingsProvider()) return postData; + + if (!mLocationUtils.userRespondedToLocationOptIn()) { + // Bring up the consent dialog if it the user has yet responded to it. We + // will not send the location info for this query. + mLocationUtils.showLocationOptIn(); + } else if (mLocationUtils.userAcceptedLocationOptIn() && + Settings.Secure.isLocationProviderEnabled(cr, LocationManager.NETWORK_PROVIDER)) { + Location location = ((LocationManager) getSystemService( + Context.LOCATION_SERVICE)).getLastKnownLocation( + LocationManager.NETWORK_PROVIDER); + if (location != null) { + StringBuilder str = new StringBuilder("action=devloc&sll="); + str.append(location.getLatitude()).append(',').append(location.getLongitude()); + postData = str.toString().getBytes(); + } + } + return postData; } } diff --git a/src/com/android/googlesearch/LocationUtils.java b/src/com/android/googlesearch/LocationUtils.java new file mode 100644 index 0000000..e684776 --- /dev/null +++ b/src/com/android/googlesearch/LocationUtils.java @@ -0,0 +1,104 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.googlesearch; + +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageManager; + +/** + * Utility methods for dealing with location (such as opt-in stuff). + */ +public class LocationUtils { + private Context mContext; + + // The singleton object. + private static LocationUtils sLocationUtils; + + /** + * Gets the singleton. + */ + public static synchronized LocationUtils getLocationUtils(Context context) { + if (sLocationUtils == null) { + sLocationUtils = new LocationUtils(context); + } + return sLocationUtils; + } + + /** + * Private constructor for singleton class; use {@link #getLocationUtils(Context)}. + */ + private LocationUtils(Context context) { + mContext = context; + } + + /** + * Identifies whether this system has the GoogleSettingsProvider, which determines + * whether the other methods in this class are relevant, or if we should just avoid + * using location. + */ + public boolean systemHasGoogleSettingsProvider() { + try { + return mContext.getPackageManager().getPackageInfo( + "com.google.android.providers.settings", 0) != null; + } catch (PackageManager.NameNotFoundException e) { + return false; + } + } + + /** + * Checks whether the user has responded (either positively or negatively) to the + * Google location opt-in. + */ + public boolean userRespondedToLocationOptIn() { + return android.provider.Settings.Secure.getInt(mContext.getContentResolver(), + android.provider.Settings.Secure.USE_LOCATION_FOR_SERVICES, 2) != 2; + } + + /** + * Shows the location opt-in because the user has not yet responded to it. If + * we have GoogleSettingsProvider, this fires up the 'security & location' settings + * and requests to show the opt-in. If we do not, this does nothing. + */ + public void showLocationOptIn() { + if (systemHasGoogleSettingsProvider()) { + Intent consent = new Intent( + android.provider.Settings.ACTION_SECURITY_SETTINGS); + consent.putExtra("SHOW_USE_LOCATION", true); + consent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + mContext.startActivity(consent); + } + } + + /** + * Indicates whether the user has accepted the Google location opt-in. Checks the appropriate + * setting depending on whether we are using the GoogleSettingsProvider setting or our + * own package-local setting. + * + * If the answer is false, it could be because the user responded negatively to the opt-in, + * or because the system does not have GoogleSettingsProvider. Use + * {@link #userRespondedToLocationOptIn()} to distinguish between these two cases. + */ + public boolean userAcceptedLocationOptIn() { + if (systemHasGoogleSettingsProvider()) { + return android.provider.Settings.Secure.getInt(mContext.getContentResolver(), + android.provider.Settings.Secure.USE_LOCATION_FOR_SERVICES, 2) == 1; + } else { + return false; + } + } +} diff --git a/src/com/android/googlesearch/SuggestionProvider.java b/src/com/android/googlesearch/SuggestionProvider.java index 416f9d0..c6ccfb1 100644 --- a/src/com/android/googlesearch/SuggestionProvider.java +++ b/src/com/android/googlesearch/SuggestionProvider.java @@ -31,8 +31,12 @@ import org.json.JSONException; import android.app.SearchManager; import android.content.ContentProvider; import android.content.ContentValues; +import android.content.Context; +import android.content.Intent; import android.database.AbstractCursor; import android.database.Cursor; +import android.net.ConnectivityManager; +import android.net.NetworkInfo; import android.net.Uri; import android.text.TextUtils; import android.util.Log; @@ -45,29 +49,31 @@ import java.util.Locale; /** * Use network-based Google Suggests to provide search suggestions. - * + * * Future: Merge live suggestions with saved recent queries */ public class SuggestionProvider extends ContentProvider { - + public static final Uri CONTENT_URI = Uri.parse( "content://com.android.googlesearch.SuggestionProvider"); private static final String USER_AGENT = "Android/1.0"; private String mSuggestUri; private static final int HTTP_TIMEOUT_MS = 1000; - + // TODO: this should be defined somewhere private static final String HTTP_TIMEOUT = "http.connection-manager.timeout"; private static final String LOG_TAG = "GoogleSearch.SuggestionProvider"; - + /* The suggestion columns used */ private static final String[] COLUMNS = new String[] { - "_id", - SearchManager.SUGGEST_COLUMN_TEXT_1, - SearchManager.SUGGEST_COLUMN_TEXT_2, - SearchManager.SUGGEST_COLUMN_QUERY}; + "_id", + SearchManager.SUGGEST_COLUMN_TEXT_1, + SearchManager.SUGGEST_COLUMN_TEXT_2, + SearchManager.SUGGEST_COLUMN_QUERY, + SearchManager.SUGGEST_COLUMN_INTENT_ACTION, + }; private HttpClient mHttpClient; @@ -84,10 +90,6 @@ public class SuggestionProvider extends ContentProvider { return true; } - private static ArrayListCursor makeEmptyCursor() { - return new ArrayListCursor(COLUMNS, new ArrayList<ArrayList>()); - } - /** * This will always return {@link SearchManager#SUGGEST_MIME_TYPE} as this * provider is purely to provide suggestions. @@ -106,25 +108,43 @@ public class SuggestionProvider extends ContentProvider { String[] selectionArgs, String sortOrder) { String query = selectionArgs[0]; if (TextUtils.isEmpty(query)) { - - /* Can't pass back null, things blow up */ - return makeEmptyCursor(); + return null; + } + if (!isNetworkConnected()) { + Log.i(LOG_TAG, "Not connected to network."); + return null; } try { query = URLEncoder.encode(query, "UTF-8"); // NOTE: This code uses resources to optionally select the search Uri, based on the // MCC value from the SIM. iThe default string will most likely be fine. It is // paramerterized to accept info from the Locale, the language code is the first - // parameter (%1$s) and the country code is the second (%2$s). This code *must* + // parameter (%1$s) and the country code is the second (%2$s). This code *must* // function in the same way as a similar lookup in // com.android.browser.BrowserActivity#onCreate(). If you change // either of these functions, change them both. (The same is true for the underlying // resource strings, which are stored in mcc-specific xml files.) if (mSuggestUri == null) { Locale l = Locale.getDefault(); - mSuggestUri = getContext().getResources().getString(R.string.google_search_base, - l.getLanguage(), - l.getCountry().toLowerCase()) + String language = l.getLanguage(); + String country = l.getCountry().toLowerCase(); + // Chinese and Portuguese have two langauge variants. + if ("zh".equals(language)) { + if ("cn".equals(country)) { + language = "zh-CN"; + } else if ("tw".equals(country)) { + language = "zh-TW"; + } + } else if ("pt".equals(language)) { + if ("br".equals(country)) { + language = "pt-BR"; + } else if ("pt".equals(country)) { + language = "pt-PT"; + } + } + mSuggestUri = getContext().getResources().getString(R.string.google_suggest_base, + language, + country) + "json=true&q="; } @@ -133,7 +153,7 @@ public class SuggestionProvider extends ContentProvider { method.setEntity(content); HttpResponse response = mHttpClient.execute(method); if (response.getStatusLine().getStatusCode() == 200) { - + /* Goto http://www.google.com/complete/search?json=true&q=foo * to see what the data format looks like. It's basically a json * array containing 4 other arrays. We only care about the middle @@ -151,14 +171,28 @@ public class SuggestionProvider extends ContentProvider { } catch (JSONException e) { Log.w(LOG_TAG, "Error", e); } - return makeEmptyCursor(); + return null; + } + + private boolean isNetworkConnected() { + NetworkInfo networkInfo = getActiveNetworkInfo(); + return networkInfo != null && networkInfo.isConnected(); + } + + private NetworkInfo getActiveNetworkInfo() { + ConnectivityManager connectivity = + (ConnectivityManager) getContext().getSystemService(Context.CONNECTIVITY_SERVICE); + if (connectivity == null) { + return null; + } + return connectivity.getActiveNetworkInfo(); } private static class SuggestionsCursor extends AbstractCursor { /* Contains the actual suggestions */ final JSONArray mSuggestions; - + /* This contains the popularity of each suggestion * i.e. 165,000 results. It's not related to sorting. */ @@ -193,6 +227,8 @@ public class SuggestionProvider extends ContentProvider { } catch (JSONException e) { Log.w(LOG_TAG, "Error", e); } + } else if (column == 4) { + return Intent.ACTION_WEB_SEARCH; } } return null; @@ -231,7 +267,7 @@ public class SuggestionProvider extends ContentProvider { throw new UnsupportedOperationException(); } } - + @Override public Uri insert(Uri uri, ContentValues values) { throw new UnsupportedOperationException(); |