// Copyright 2015 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.contextualsearch;
import android.app.Activity;
import android.view.ContextMenu;
import org.chromium.base.annotations.CalledByNative;
import org.chromium.chrome.browser.ChromeActivity;
import org.chromium.chrome.browser.compositor.bottombar.OverlayPanel.StateChangeReason;
import org.chromium.chrome.browser.preferences.PrefServiceBridge;
import org.chromium.chrome.browser.profiles.Profile;
import org.chromium.chrome.browser.search_engines.TemplateUrlService;
import org.chromium.chrome.browser.search_engines.TemplateUrlService.TemplateUrlServiceObserver;
import org.chromium.chrome.browser.tab.EmptyTabObserver;
import org.chromium.chrome.browser.tab.Tab;
import org.chromium.content.browser.ContentViewCore;
import org.chromium.content_public.browser.GestureStateListener;
/**
* Manages the activation and gesture listeners for ContextualSearch on a given tab.
*/
public class ContextualSearchTabHelper extends EmptyTabObserver {
/**
* Notification handler for Contextual Search events.
*/
private TemplateUrlServiceObserver mTemplateUrlObserver;
/**
* The current ContentViewCore for the Tab which this helper is monitoring.
*/
private ContentViewCore mBaseContentViewCore;
/**
* The GestureListener used for handling events from the current ContentViewCore.
*/
private GestureStateListener mGestureStateListener;
private long mNativeHelper = 0;
private final Tab mTab;
/**
* Creates a contextual search tab helper for the given tab.
* @param tab The tab whose contextual search actions will be handled by this helper.
*/
public static void createForTab(Tab tab) {
new ContextualSearchTabHelper(tab);
}
private ContextualSearchTabHelper(Tab tab) {
mTab = tab;
tab.addObserver(this);
}
@Override
public void onPageLoadStarted(Tab tab, String url) {
if (tab.getContentViewCore() == null) {
// Nothing to do yet.
return;
}
mBaseContentViewCore = tab.getContentViewCore();
// Add Contextual Search here in case it couldn't get added in onContentChanged() due to
// being too early in initialization of Chrome (ContextualSearchManager being null).
updateContextualSearchHooks(mBaseContentViewCore);
ContextualSearchManager manager = getContextualSearchManager();
if (manager != null) {
manager.onBasePageLoadStarted();
}
}
@Override
public void onContentChanged(Tab tab) {
// Native initialization happens after a page loads or content is changed to ensure profile
// is initialized.
if (mNativeHelper == 0) {
mNativeHelper = nativeInit(tab.getProfile());
}
if (mTemplateUrlObserver == null) {
mTemplateUrlObserver =
new TemplateUrlServiceObserver() {
@Override
public void onTemplateURLServiceChanged() {
updateContextualSearchHooks(mBaseContentViewCore);
}
};
TemplateUrlService.getInstance().addObserver(mTemplateUrlObserver);
}
updateHooksForNewContentViewCore(tab);
}
@Override
public void onWebContentsSwapped(Tab tab, boolean didStartLoad, boolean didFinishLoad) {
updateHooksForNewContentViewCore(tab);
}
@Override
public void onDestroyed(Tab tab) {
if (mNativeHelper != 0) {
nativeDestroy(mNativeHelper);
mNativeHelper = 0;
}
if (mTemplateUrlObserver != null) {
TemplateUrlService.getInstance().removeObserver(mTemplateUrlObserver);
}
removeContextualSearchHooks(mBaseContentViewCore);
mBaseContentViewCore = null;
}
@Override
public void onToggleFullscreenMode(Tab tab, boolean enable) {
ContextualSearchManager manager = getContextualSearchManager();
if (manager != null) {
manager.hideContextualSearch(StateChangeReason.UNKNOWN);
}
}
@Override
public void onReparentingFinished(Tab tab) {
updateHooksForNewContentViewCore(tab);
}
@Override
public void onContextMenuShown(Tab tab, ContextMenu menu) {
ContextualSearchManager manager = getContextualSearchManager();
if (manager != null) {
manager.onContextMenuShown();
}
}
/**
* Should be called whenever the Tab's ContentViewCore changes. Removes hooks from the
* existing ContentViewCore, if necessary and then adds hooks for the new ContentViewCore.
* @param tab
*/
private void updateHooksForNewContentViewCore(Tab tab) {
removeContextualSearchHooks(mBaseContentViewCore);
mBaseContentViewCore = tab.getContentViewCore();
updateContextualSearchHooks(mBaseContentViewCore);
}
/**
* Updates the Contextual Search hooks, adding or removing them depending on whether it is
* currently active.
* @param cvc The content view core to attach the gesture state listener to.
*/
private void updateContextualSearchHooks(ContentViewCore cvc) {
if (cvc == null) return;
if (isContextualSearchActive(cvc)) {
addContextualSearchHooks(cvc);
} else {
removeContextualSearchHooks(cvc);
}
}
/**
* Adds Contextual Search hooks for its client and listener to the given content view core.
* @param cvc The content view core to attach the gesture state listener to.
*/
private void addContextualSearchHooks(ContentViewCore cvc) {
ContextualSearchManager manager = getContextualSearchManager();
if (mGestureStateListener == null && manager != null) {
mGestureStateListener = manager.getGestureStateListener();
cvc.addGestureStateListener(mGestureStateListener);
cvc.setContextualSearchClient(manager);
}
}
/**
* Removes Contextual Search hooks for its client and listener from the given content view core.
* @param cvc The content view core to detach the gesture state listener from.
*/
private void removeContextualSearchHooks(ContentViewCore cvc) {
if (cvc == null) return;
if (mGestureStateListener != null) {
cvc.removeGestureStateListener(mGestureStateListener);
mGestureStateListener = null;
cvc.setContextualSearchClient(null);
}
}
/**
* @return whether Contextual Search is enabled and active in this tab.
*/
private boolean isContextualSearchActive(ContentViewCore cvc) {
ContextualSearchManager manager = getContextualSearchManager();
if (manager == null) return false;
return !cvc.getWebContents().isIncognito()
&& !PrefServiceBridge.getInstance().isContextualSearchDisabled()
&& TemplateUrlService.getInstance().isDefaultSearchEngineGoogle()
// Svelte and Accessibility devices are incompatible with the first-run flow and
// Talkback has poor interaction with tap to search (see http://crbug.com/399708 and
// http://crbug.com/396934).
// TODO(jeremycho): Handle these cases.
&& !manager.isRunningInCompatibilityMode()
&& !(mTab.isShowingErrorPage() || mTab.isShowingInterstitialPage());
}
/**
* @return the Contextual Search manager.
*/
private ContextualSearchManager getContextualSearchManager() {
Activity activity = mTab.getWindowAndroid().getActivity().get();
if (activity instanceof ChromeActivity) {
return ((ChromeActivity) activity).getContextualSearchManager();
}
return null;
}
@CalledByNative
private void onContextualSearchPrefChanged() {
updateContextualSearchHooks(mBaseContentViewCore);
ContextualSearchManager manager = getContextualSearchManager();
if (manager != null) {
boolean isEnabled = !PrefServiceBridge.getInstance().isContextualSearchDisabled()
&& !PrefServiceBridge.getInstance().isContextualSearchUninitialized();
manager.onContextualSearchPrefChanged(isEnabled);
}
}
private native long nativeInit(Profile profile);
private native void nativeDestroy(long nativeContextualSearchTabHelper);
}