Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(3551)

Unified Diff: chrome/android/java/src/org/chromium/chrome/browser/searchwidget/SearchWidgetProvider.java

Issue 2777283005: Add an experimental search widget (Closed)
Patch Set: Moved initialize to FinishNativeInitialization Created 3 years, 9 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: chrome/android/java/src/org/chromium/chrome/browser/searchwidget/SearchWidgetProvider.java
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/searchwidget/SearchWidgetProvider.java b/chrome/android/java/src/org/chromium/chrome/browser/searchwidget/SearchWidgetProvider.java
new file mode 100644
index 0000000000000000000000000000000000000000..035a48a8bdfe4d65939a4f0edb3b47ef77fa8bf4
--- /dev/null
+++ b/chrome/android/java/src/org/chromium/chrome/browser/searchwidget/SearchWidgetProvider.java
@@ -0,0 +1,281 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.chrome.browser.searchwidget;
+
+import android.app.PendingIntent;
+import android.appwidget.AppWidgetManager;
+import android.appwidget.AppWidgetProvider;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.graphics.Rect;
+import android.net.Uri;
+import android.os.Bundle;
+import android.support.v4.app.ActivityOptionsCompat;
+import android.text.TextUtils;
+import android.widget.RemoteViews;
+
+import org.chromium.base.ApplicationStatus;
+import org.chromium.base.ContextUtils;
+import org.chromium.base.Log;
+import org.chromium.base.ThreadUtils;
+import org.chromium.base.library_loader.LibraryLoader;
+import org.chromium.chrome.R;
+import org.chromium.chrome.browser.IntentHandler;
+import org.chromium.chrome.browser.init.AsyncInitializationActivity;
+import org.chromium.chrome.browser.search_engines.TemplateUrlService;
+import org.chromium.chrome.browser.search_engines.TemplateUrlService.LoadListener;
+import org.chromium.chrome.browser.search_engines.TemplateUrlService.TemplateUrlServiceObserver;
+
+/**
+ * Widget that lets the user search using the default search engine in Chrome.
+ * Because this is a BroadcastReceiver, it dies immediately after it runs. A new one is created
+ * for each new broadcast.
+ */
+public class SearchWidgetProvider extends AppWidgetProvider {
+ /** Monitors the TemplateUrlService for changes, updating the widget when necessary. */
+ private static final class SearchWidgetTemplateUrlServiceObserver
+ implements LoadListener, TemplateUrlServiceObserver {
+ @Override
+ public void onTemplateUrlServiceLoaded() {
+ TemplateUrlService.getInstance().unregisterLoadListener(this);
+ updateCachedEngineName();
+ }
+
+ @Override
+ public void onTemplateURLServiceChanged() {
+ updateCachedEngineName();
+ }
+ }
+
+ static final int INVALID_WIDGET_ID = -1;
+
+ static final boolean ANIMATE_TRANSITION = false;
+
+ private static final String ACTION_START_TEXT_QUERY =
+ "org.chromium.chrome.browser.searchwidget.START_TEXT_QUERY";
+ private static final String ACTION_START_VOICE_QUERY =
+ "org.chromium.chrome.browser.searchwidget.START_VOICE_QUERY";
+ private static final String ACTION_UPDATE_ALL_WIDGETS =
+ "org.chromium.chrome.browser.searchwidget.UPDATE_ALL_WIDGETS";
+
+ static final String EXTRA_START_VOICE_SEARCH =
+ "org.chromium.chrome.browser.searchwidget.START_VOICE_SEARCH";
+ private static final String EXTRA_WIDGET_ID =
+ "org.chromium.chrome.browser.searchwidget.WIDGET_ID";
+
+ private static final String PREF_LAUNCHING_WIDGET_ID =
+ "org.chromium.chrome.browser.searchwidget.LAUNCHING_WIDGET_ID";
+ private static final String PREF_SEARCH_ENGINE_SHORTNAME =
+ "org.chromium.chrome.browser.searchwidget.SEARCH_ENGINE_SHORTNAME";
+ static final String PREF_USE_HERB_TAB = "org.chromium.chrome.browser.searchwidget.USE_HERB_TAB";
+
+ private static final String TAG = "searchwidget";
+ private static final Object OBSERVER_LOCK = new Object();
+
+ private static SearchWidgetTemplateUrlServiceObserver sObserver;
+ private static String sCachedSearchEngineName;
+
+ /**
+ * Creates the singleton instance of the observer that will monitor for search engine changes.
+ * The native library and the browser process must have been fully loaded before calling this,
+ * after {@link AsyncInitializationActivity#finishNativeInitialization}.
+ */
+ public static void initialize() {
+ ThreadUtils.assertOnUiThread();
+ assert LibraryLoader.isInitialized();
+
+ // Set up an observer to monitor for changes.
+ synchronized (OBSERVER_LOCK) {
+ if (sObserver != null) return;
+ sObserver = new SearchWidgetTemplateUrlServiceObserver();
+
+ TemplateUrlService service = TemplateUrlService.getInstance();
+ service.addObserver(sObserver);
+ if (!service.isLoaded()) {
+ service.registerLoadListener(sObserver);
+ service.load();
+ }
+ }
+ updateCachedEngineName();
+ }
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (IntentHandler.isIntentChromeOrFirstParty(intent)) {
+ if (ACTION_START_TEXT_QUERY.equals(intent.getAction())) {
+ startSearchActivity(context, intent, false);
+ return;
+ } else if (ACTION_START_VOICE_QUERY.equals(intent.getAction())) {
+ startSearchActivity(context, intent, true);
+ return;
+ }
+ } else if (ACTION_UPDATE_ALL_WIDGETS.equals(intent.getAction())) {
+ performUpdate(context);
+ return;
+ }
+ super.onReceive(context, intent);
+ }
+
+ private void startSearchActivity(Context context, Intent intent, boolean startVoiceSearch) {
+ int widgetId = getLaunchingWidgetIdFromIntent(intent);
+ SearchWidgetProvider.setLaunchingWidgetId(widgetId);
+ Log.d(TAG, "Launching SearchActivity: ID=" + widgetId + " VOICE=" + startVoiceSearch);
+
+ // Launch the SearchActivity.
+ Intent searchIntent = new Intent();
+ searchIntent.setClassName(context, SearchActivity.class.getName());
+ searchIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ searchIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT);
+ searchIntent.putExtra(EXTRA_START_VOICE_SEARCH, startVoiceSearch);
+
+ Bundle optionsBundle;
+ if (ANIMATE_TRANSITION) {
+ // Pass the widget's bounds along to allow moving the box into place.
+ Rect rect = intent.getSourceBounds();
+ if (rect != null) searchIntent.setSourceBounds(rect);
+ optionsBundle = ActivityOptionsCompat.makeCustomAnimation(context, 0, 0).toBundle();
+ } else {
+ optionsBundle = ActivityOptionsCompat
+ .makeCustomAnimation(context, R.anim.activity_open_enter, 0)
+ .toBundle();
+ }
+ context.startActivity(searchIntent, optionsBundle);
+ }
+
+ @Override
+ public void onUpdate(Context context, AppWidgetManager manager, int[] ids) {
+ performUpdate(context, ids);
+ super.onUpdate(context, manager, ids);
+ }
+
+ private void performUpdate(Context context) {
+ AppWidgetManager manager = AppWidgetManager.getInstance(context);
+ performUpdate(context, getAllSearchWidgetIds(manager));
+ }
+
+ private void performUpdate(Context context, int[] ids) {
+ for (int id : ids) updateWidget(context, id);
+ }
+
+ private void updateWidget(Context context, int id) {
+ int launchingWidgetId = ANIMATE_TRANSITION ? getLaunchingWidgetId() : INVALID_WIDGET_ID;
+
+ AppWidgetManager manager = AppWidgetManager.getInstance(context);
+ RemoteViews views = new RemoteViews(context.getPackageName(),
+ id == launchingWidgetId ? R.layout.search_widget_template_transparent
+ : R.layout.search_widget_template);
+
+ // Clicking on the widget fires an Intent back at this BroadcastReceiver, allowing control
+ // over how the Activity is animated when it starts up.
+ Intent textIntent = createStartQueryIntent(context, ACTION_START_TEXT_QUERY, id);
+ views.setOnClickPendingIntent(R.id.text_container,
+ PendingIntent.getBroadcast(
+ context, 0, textIntent, PendingIntent.FLAG_UPDATE_CURRENT));
+
+ Intent voiceIntent = createStartQueryIntent(context, ACTION_START_VOICE_QUERY, id);
+ views.setOnClickPendingIntent(R.id.microphone_icon,
+ PendingIntent.getBroadcast(
+ context, 0, voiceIntent, PendingIntent.FLAG_UPDATE_CURRENT));
+
+ // Update what string is displayed by the widget.
+ String engineName = getCachedEngineName();
+ String text = TextUtils.isEmpty(engineName)
+ ? context.getString(R.string.search_widget_default)
+ : context.getString(R.string.search_with_product, engineName);
+ views.setTextViewText(R.id.title, text);
+
+ manager.updateAppWidget(id, views);
+ }
+
+ /** Force all widgets to update their state. */
+ static void updateAllWidgets() {
+ Intent intent = new Intent(ACTION_UPDATE_ALL_WIDGETS);
+ intent.setPackage(ContextUtils.getApplicationContext().getPackageName());
+ ContextUtils.getApplicationContext().sendBroadcast(intent);
+ }
+
+ /** Updates the name of the user's default search engine that is cached in SharedPreferences. */
+ static void updateCachedEngineName() {
+ assert LibraryLoader.isInitialized();
+
+ // Getting an instance of the TemplateUrlService requires that the native library be
+ // loaded, but the TemplateUrlService itself needs to be initialized.
+ TemplateUrlService service = TemplateUrlService.getInstance();
+ if (!service.isLoaded()) return;
+
+ String engineName = service.getDefaultSearchEngineTemplateUrl().getShortName();
+ if (!TextUtils.equals(sCachedSearchEngineName, engineName)) {
+ sCachedSearchEngineName = engineName;
+
+ SharedPreferences.Editor editor = ContextUtils.getAppSharedPreferences().edit();
+ editor.putString(PREF_SEARCH_ENGINE_SHORTNAME, engineName);
+ editor.apply();
+
+ updateAllWidgets();
+ }
+ }
+
+ /**
+ * Returns the cached name of the user's default search engine. Caching it in SharedPreferences
+ * prevents us from having to load the native library and the TemplateUrlService.
+ *
+ * @return The cached name of the user's default search engine.
+ */
+ private static String getCachedEngineName() {
+ if (sCachedSearchEngineName != null) return sCachedSearchEngineName;
+
+ SharedPreferences prefs = ContextUtils.getAppSharedPreferences();
+ sCachedSearchEngineName = prefs.getString(PREF_SEARCH_ENGINE_SHORTNAME, null);
+ return sCachedSearchEngineName;
+ }
+
+ /** Get the IDs of all of Chrome's search widgets. */
+ private static int[] getAllSearchWidgetIds(AppWidgetManager manager) {
+ return manager.getAppWidgetIds(new ComponentName(
+ ContextUtils.getApplicationContext(), SearchWidgetProvider.class.getName()));
+ }
+
+ private int getLaunchingWidgetId() {
+ int launchingWidgetId = INVALID_WIDGET_ID;
+
+ // If the SearchActivity isn't in the foreground, the user must have exited it.
+ if (ApplicationStatus.getLastTrackedFocusedActivity() instanceof SearchActivity) {
+ launchingWidgetId = ContextUtils.getAppSharedPreferences().getInt(
+ PREF_LAUNCHING_WIDGET_ID, INVALID_WIDGET_ID);
+ }
+
+ return launchingWidgetId;
+ }
+
+ /** Cache the ID of the widget that was used to launch the SearchActivity. */
+ static void setLaunchingWidgetId(int id) {
+ SharedPreferences prefs = ContextUtils.getAppSharedPreferences();
+ SharedPreferences.Editor editor = prefs.edit();
+ editor.putInt(PREF_LAUNCHING_WIDGET_ID, id);
+ editor.apply();
+ }
+
+ /** Parse out which widget launched the Activity from the given Intent. */
+ private static int getLaunchingWidgetIdFromIntent(Intent intent) {
+ String data = intent.getData() == null ? null : intent.getData().toString();
+ if (data == null) return INVALID_WIDGET_ID;
+
+ try {
+ return Integer.parseInt(data);
+ } catch (NumberFormatException e) {
+ return INVALID_WIDGET_ID;
+ }
+ }
+
+ /** Creates a trusted Intent that lets the user begin performing queries. */
+ private Intent createStartQueryIntent(Context context, String action, int widgetId) {
+ Intent intent = new Intent(action, Uri.parse(String.valueOf(widgetId)));
+ intent.setClass(context, getClass());
+ IntentHandler.addTrustedIntentExtras(intent);
+ return intent;
+ }
+}

Powered by Google App Engine
This is Rietveld 408576698