Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 4a85a7dc authored by Bjorn Bringert's avatar Bjorn Bringert Committed by Android (Google) Code Review
Browse files

Merge "A Java implementation of the SearchBox API."

parents 1dda71ae 9497c5f8
Loading
Loading
Loading
Loading
+24 −16
Original line number Diff line number Diff line
@@ -46,7 +46,6 @@ import java.lang.ref.WeakReference;
import java.net.URLEncoder;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.Iterator;
@@ -89,6 +88,9 @@ class BrowserFrame extends Handler {
    // Key store handler when Chromium HTTP stack is used.
    private KeyStoreHandler mKeyStoreHandler = null;

    // Implementation of the searchbox API.
    private final SearchBoxImpl mSearchBox;

    // message ids
    // a message posted when a frame loading is completed
    static final int FRAME_COMPLETED = 1001;
@@ -225,6 +227,9 @@ class BrowserFrame extends Handler {
        sConfigCallback.addHandler(this);

        mJSInterfaceMap = javascriptInterfaces;
        if (mJSInterfaceMap == null) {
            mJSInterfaceMap = new HashMap<String, Object>();
        }

        mSettings = settings;
        mContext = context;
@@ -232,6 +237,9 @@ class BrowserFrame extends Handler {
        mDatabase = WebViewDatabase.getInstance(appContext);
        mWebViewCore = w;

        mSearchBox = new SearchBoxImpl(mWebViewCore, mCallbackProxy);
        mJSInterfaceMap.put(SearchBoxImpl.JS_INTERFACE_NAME, mSearchBox);

        AssetManager am = context.getAssets();
        nativeCreateFrame(w, am, proxy.getBackForwardList());

@@ -587,17 +595,17 @@ class BrowserFrame extends Handler {
     * We should re-attach any attached js interfaces.
     */
    private void windowObjectCleared(int nativeFramePointer) {
        if (mJSInterfaceMap != null) {
            Iterator iter = mJSInterfaceMap.keySet().iterator();
        Iterator<String> iter = mJSInterfaceMap.keySet().iterator();
        while (iter.hasNext())  {
                String interfaceName = (String) iter.next();
            String interfaceName = iter.next();
            Object object = mJSInterfaceMap.get(interfaceName);
            if (object != null) {
                nativeAddJavascriptInterface(nativeFramePointer,
                        mJSInterfaceMap.get(interfaceName), interfaceName);
            }
        }
        }

        stringByEvaluatingJavaScriptFromString(SearchBoxImpl.JS_BRIDGE);
    }

    /**
@@ -620,16 +628,11 @@ class BrowserFrame extends Handler {
    public void addJavascriptInterface(Object obj, String interfaceName) {
        assert obj != null;
        removeJavascriptInterface(interfaceName);
        if (mJSInterfaceMap == null) {
            mJSInterfaceMap = new HashMap<String, Object>();
        }

        mJSInterfaceMap.put(interfaceName, obj);
    }

    public void removeJavascriptInterface(String interfaceName) {
        if (mJSInterfaceMap == null) {
            return;
        }
        if (mJSInterfaceMap.containsKey(interfaceName)) {
            mJSInterfaceMap.remove(interfaceName);
        }
@@ -1234,6 +1237,11 @@ class BrowserFrame extends Handler {
        }
    }


    /*package*/ SearchBox getSearchBox() {
        return mSearchBox;
    }

    //==========================================================================
    // native functions
    //==========================================================================
+16 −0
Original line number Diff line number Diff line
@@ -42,6 +42,7 @@ import com.android.internal.R;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
@@ -115,6 +116,7 @@ class CallbackProxy extends Handler {
    private static final int HISTORY_INDEX_CHANGED               = 136;
    private static final int AUTH_CREDENTIALS                    = 137;
    private static final int SET_INSTALLABLE_WEBAPP              = 138;
    private static final int NOTIFY_SEARCHBOX_LISTENERS          = 139;

    // Message triggered by the client to resume execution
    private static final int NOTIFY                              = 200;
@@ -781,6 +783,12 @@ class CallbackProxy extends Handler {
                    mWebChromeClient.setInstallableWebApp();
                }
                break;
            case NOTIFY_SEARCHBOX_LISTENERS:
                SearchBoxImpl searchBox = (SearchBoxImpl) mWebView.getSearchBox();

                @SuppressWarnings("unchecked")
                List<String> suggestions = (List<String>) msg.obj;
                searchBox.handleSuggestions(msg.getData().getString("query"), suggestions);
        }
    }

@@ -1557,4 +1565,12 @@ class CallbackProxy extends Handler {
        // See bug 3166409
        return mContext instanceof Activity;
    }

    void onSearchboxSuggestionsReceived(String query, List<String> suggestions) {
        Message msg = obtainMessage(NOTIFY_SEARCHBOX_LISTENERS);
        msg.obj = suggestions;
        msg.getData().putString("query", query);

        sendMessage(msg);
    }
}
+92 −0
Original line number Diff line number Diff line
/*
 * 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 java.util.List;

/**
 * Defines the interaction between the browser/renderer and the page running on
 * a given WebView frame, if the page supports the chromium SearchBox API.
 *
 * http://dev.chromium.org/searchbox
 *
 * The browser or container app can query the page for search results using
 * SearchBox.query() and receive suggestions by registering a listener on the
 * SearchBox object.
 *
 * @hide pending API council approval.
 */
public interface SearchBox {
    /**
     * Sets the current searchbox query. Note that the caller must call
     * onchange() to ensure that the search page processes this query.
     */
    void setQuery(String query);

    /**
     * Verbatim is true if the caller suggests that the search page
     * treat the current query as a verbatim search query (as opposed to a
     * partially typed search query). As with setQuery, onchange() must be
     * called to ensure that the search page processes the query.
     */
    void setVerbatim(boolean verbatim);

    /**
     * These attributes must contain the offset to the characters that immediately
     * follow the start and end of the selection in the search box. If there is
     * no such selection, then both selectionStart and selectionEnd must be the offset
     * to the character that immediately follows the text entry cursor. In the case
     * that there is no explicit text entry cursor, the cursor is
     * implicitly at the end of the input.
     */
    void setSelection(int selectionStart, int selectionEnd);

    /**
     * Sets the dimensions of the view (if any) that overlaps the current
     * window object. This is to ensure that the page renders results in
     * a manner that allows them to not be obscured by such a view. Note
     * that a call to onresize() is required if these dimensions change.
     */
    void setDimensions(int x, int y, int width, int height);

    /**
     * Notify the search page of any changes to the searchbox. Such as
     * a change in the typed query (onchange), the user commiting a given query
     * (onsubmit), or a change in size of a suggestions dropdown (onresize).
     */
    void onchange();
    void onsubmit();
    void onresize();
    void oncancel();

    /**
     * Add and remove listeners to the given Searchbox. Listeners are notified
     * of any suggestions to the query that the underlying search engine might
     * provide.
     */
    void addSearchBoxListener(SearchBoxListener l);
    void removeSearchBoxListener(SearchBoxListener l);

    /**
     * Listeners (if any) will be called on the thread that created the
     * webview.
     */
    interface SearchBoxListener {
        void onSuggestionsReceived(String query, List<String> suggestions);
    }
}
+227 −0
Original line number Diff line number Diff line
/*
 * 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.util.Log;
import android.webkit.WebViewCore.EventHub;

import java.util.ArrayList;
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 = %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(); }";

    private final List<SearchBoxListener> mListeners;
    private final WebViewCore mWebViewCore;
    private final CallbackProxy mCallbackProxy;

    SearchBoxImpl(WebViewCore webViewCore, CallbackProxy callbackProxy) {
        mListeners = new ArrayList<SearchBoxListener>();
        mWebViewCore = webViewCore;
        mCallbackProxy = callbackProxy;
    }

    @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() {
        dispatchEvent("change");
    }

    @Override
    public void onsubmit() {
        dispatchEvent("submit");
    }

    @Override
    public void onresize() {
        dispatchEvent("resize");
    }

    @Override
    public void oncancel() {
        dispatchEvent("cancel");
    }

    private void dispatchEvent(String eventName) {
        final String js = String.format(DISPATCH_EVENT_SCRIPT, eventName);
        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);
        }
    }

    // This is used as a hackish alternative to javascript escaping.
    // There appears to be no such functionality in the core framework.
    private 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);
            }
        }
    }
}
+8 −1
Original line number Diff line number Diff line
@@ -5032,6 +5032,13 @@ public class WebView extends AbsoluteLayout
        return copiedSomething;
    }

    /**
     * @hide pending API Council approval.
     */
    public SearchBox getSearchBox() {
        return mWebViewCore.getBrowserFrame().getSearchBox();
    }

    /**
     * Returns the currently highlighted text as a string.
     */
@@ -8389,7 +8396,7 @@ public class WebView extends AbsoluteLayout
    private native void     nativeSetFindIsUp(boolean isUp);
    private native void     nativeSetHeightCanMeasure(boolean measure);
    private native void     nativeSetBaseLayer(int layer, Rect invalRect,
            boolean showVisualIndciator);
            boolean showVisualIndicator);
    private native void     nativeShowCursorTimed();
    private native void     nativeReplaceBaseContent(int content);
    private native void     nativeCopyBaseContentToPicture(Picture pict);
Loading