blob: 9942d2580e68f79e2de24e9fb52160b527ec8fd5 [file] [log] [blame]
/*
* Copyright (C) 2011 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 android.webkit;
import android.text.TextUtils;
import android.util.Log;
import android.webkit.WebViewCore.EventHub;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.json.JSONStringer;
/**
* The default implementation of the SearchBox interface. Implemented
* as a java bridge object and a javascript adapter that is called into
* by the page hosted in the frame.
*/
final class SearchBoxImpl implements SearchBox {
private static final String TAG = "WebKit.SearchBoxImpl";
/* package */ static final String JS_INTERFACE_NAME = "searchBoxJavaBridge_";
/* package */ static final String JS_BRIDGE
= "(function()"
+ "{"
+ "if (!window.chrome) {"
+ " window.chrome = {};"
+ "}"
+ "if (!window.chrome.searchBox) {"
+ " var sb = window.chrome.searchBox = {};"
+ " sb.setSuggestions = function(suggestions) {"
+ " if (window.searchBoxJavaBridge_) {"
+ " window.searchBoxJavaBridge_.setSuggestions(JSON.stringify(suggestions));"
+ " }"
+ " };"
+ " sb.setValue = function(valueArray) { sb.value = valueArray[0]; };"
+ " sb.value = '';"
+ " sb.x = 0;"
+ " sb.y = 0;"
+ " sb.width = 0;"
+ " sb.height = 0;"
+ " sb.selectionStart = 0;"
+ " sb.selectionEnd = 0;"
+ " sb.verbatim = false;"
+ "}"
+ "})();";
private static final String SET_QUERY_SCRIPT
= "if (window.chrome && window.chrome.searchBox) {"
+ " window.chrome.searchBox.setValue(%s);"
+ "}";
private static final String SET_VERBATIM_SCRIPT
= "if (window.chrome && window.chrome.searchBox) {"
+ " window.chrome.searchBox.verbatim = %1$s;"
+ "}";
private static final String SET_SELECTION_SCRIPT
= "if (window.chrome && window.chrome.searchBox) {"
+ " var f = window.chrome.searchBox;"
+ " f.selectionStart = %d"
+ " f.selectionEnd = %d"
+ "}";
private static final String SET_DIMENSIONS_SCRIPT
= "if (window.chrome && window.chrome.searchBox) { "
+ " var f = window.chrome.searchBox;"
+ " f.x = %d;"
+ " f.y = %d;"
+ " f.width = %d;"
+ " f.height = %d;"
+ "}";
private static final String DISPATCH_EVENT_SCRIPT
= "if (window.chrome && window.chrome.searchBox && window.chrome.searchBox.on%1$s) {"
+ " window.chrome.searchBox.on%1$s();"
+ " window.searchBoxJavaBridge_.dispatchCompleteCallback('%1$s', %2$d, true);"
+ "} else {"
+ " window.searchBoxJavaBridge_.dispatchCompleteCallback('%1$s', %2$d, false);"
+ "}";
private static final String EVENT_CHANGE = "change";
private static final String EVENT_SUBMIT = "submit";
private static final String EVENT_RESIZE = "resize";
private static final String EVENT_CANCEL = "cancel";
private static final String IS_SUPPORTED_SCRIPT
= "if (window.searchBoxJavaBridge_) {"
+ " if (window.chrome && window.chrome.sv) {"
+ " window.searchBoxJavaBridge_.isSupportedCallback(true);"
+ " } else {"
+ " window.searchBoxJavaBridge_.isSupportedCallback(false);"
+ " }}";
private final List<SearchBoxListener> mListeners;
private final WebViewCore mWebViewCore;
private final CallbackProxy mCallbackProxy;
private IsSupportedCallback mSupportedCallback;
private int mNextEventId = 1;
private final HashMap<Integer, SearchBoxListener> mEventCallbacks;
SearchBoxImpl(WebViewCore webViewCore, CallbackProxy callbackProxy) {
mListeners = new ArrayList<SearchBoxListener>();
mWebViewCore = webViewCore;
mCallbackProxy = callbackProxy;
mEventCallbacks = new HashMap<Integer, SearchBoxListener>();
}
@Override
public void setQuery(String query) {
final String formattedQuery = jsonSerialize(query);
if (formattedQuery != null) {
final String js = String.format(SET_QUERY_SCRIPT, formattedQuery);
dispatchJs(js);
}
}
@Override
public void setVerbatim(boolean verbatim) {
final String js = String.format(SET_VERBATIM_SCRIPT, String.valueOf(verbatim));
dispatchJs(js);
}
@Override
public void setSelection(int selectionStart, int selectionEnd) {
final String js = String.format(SET_SELECTION_SCRIPT, selectionStart, selectionEnd);
dispatchJs(js);
}
@Override
public void setDimensions(int x, int y, int width, int height) {
final String js = String.format(SET_DIMENSIONS_SCRIPT, x, y, width, height);
dispatchJs(js);
}
@Override
public void onchange(SearchBoxListener callback) {
dispatchEvent(EVENT_CHANGE, callback);
}
@Override
public void onsubmit(SearchBoxListener callback) {
dispatchEvent(EVENT_SUBMIT, callback);
}
@Override
public void onresize(SearchBoxListener callback) {
dispatchEvent(EVENT_RESIZE, callback);
}
@Override
public void oncancel(SearchBoxListener callback) {
dispatchEvent(EVENT_CANCEL, callback);
}
private void dispatchEvent(String eventName, SearchBoxListener callback) {
int eventId;
if (callback != null) {
synchronized(this) {
eventId = mNextEventId++;
mEventCallbacks.put(eventId, callback);
}
} else {
eventId = 0;
}
final String js = String.format(DISPATCH_EVENT_SCRIPT, eventName, eventId);
dispatchJs(js);
}
private void dispatchJs(String js) {
mWebViewCore.sendMessage(EventHub.EXECUTE_JS, js);
}
@Override
public void addSearchBoxListener(SearchBoxListener l) {
synchronized (mListeners) {
mListeners.add(l);
}
}
@Override
public void removeSearchBoxListener(SearchBoxListener l) {
synchronized (mListeners) {
mListeners.remove(l);
}
}
@Override
public void isSupported(IsSupportedCallback callback) {
mSupportedCallback = callback;
dispatchJs(IS_SUPPORTED_SCRIPT);
}
// Called by Javascript through the Java bridge.
public void isSupportedCallback(boolean isSupported) {
mCallbackProxy.onIsSupportedCallback(isSupported);
}
public void handleIsSupportedCallback(boolean isSupported) {
IsSupportedCallback callback = mSupportedCallback;
mSupportedCallback = null;
if (callback != null) {
callback.searchBoxIsSupported(isSupported);
}
}
// Called by Javascript through the Java bridge.
public void dispatchCompleteCallback(String function, int id, boolean successful) {
mCallbackProxy.onSearchboxDispatchCompleteCallback(function, id, successful);
}
public void handleDispatchCompleteCallback(String function, int id, boolean successful) {
if (id != 0) {
SearchBoxListener listener;
synchronized(this) {
listener = mEventCallbacks.get(id);
mEventCallbacks.remove(id);
}
if (listener != null) {
if (TextUtils.equals(EVENT_CHANGE, function)) {
listener.onChangeComplete(successful);
} else if (TextUtils.equals(EVENT_SUBMIT, function)) {
listener.onSubmitComplete(successful);
} else if (TextUtils.equals(EVENT_RESIZE, function)) {
listener.onResizeComplete(successful);
} else if (TextUtils.equals(EVENT_CANCEL, function)) {
listener.onCancelComplete(successful);
}
}
}
}
// This is used as a hackish alternative to javascript escaping.
// There appears to be no such functionality in the core framework.
private static String jsonSerialize(String query) {
JSONStringer stringer = new JSONStringer();
try {
stringer.array().value(query).endArray();
} catch (JSONException e) {
Log.w(TAG, "Error serializing query : " + query);
return null;
}
return stringer.toString();
}
// Called by Javascript through the Java bridge.
public void setSuggestions(String jsonArguments) {
if (jsonArguments == null) {
return;
}
String query = null;
List<String> suggestions = new ArrayList<String>();
try {
JSONObject suggestionsJson = new JSONObject(jsonArguments);
query = suggestionsJson.getString("query");
final JSONArray suggestionsArray = suggestionsJson.getJSONArray("suggestions");
for (int i = 0; i < suggestionsArray.length(); ++i) {
final JSONObject suggestion = suggestionsArray.getJSONObject(i);
final String value = suggestion.getString("value");
if (value != null) {
suggestions.add(value);
}
// We currently ignore the "type" of the suggestion. This isn't
// documented anywhere in the API documents.
// final String type = suggestions.getString("type");
}
} catch (JSONException je) {
Log.w(TAG, "Error parsing json [" + jsonArguments + "], exception = " + je);
return;
}
mCallbackProxy.onSearchboxSuggestionsReceived(query, suggestions);
}
/* package */ void handleSuggestions(String query, List<String> suggestions) {
synchronized (mListeners) {
for (int i = mListeners.size() - 1; i >= 0; i--) {
mListeners.get(i).onSuggestionsReceived(query, suggestions);
}
}
}
}