+++ /dev/null
-/*
- * Copyright (C) 2012 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.content.Context;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.SystemClock;
-import android.provider.Settings;
-import android.speech.tts.TextToSpeech;
-import android.speech.tts.TextToSpeech.Engine;
-import android.speech.tts.TextToSpeech.OnInitListener;
-import android.speech.tts.UtteranceProgressListener;
-import android.util.Log;
-import android.view.KeyEvent;
-import android.view.View;
-import android.view.accessibility.AccessibilityManager;
-import android.view.accessibility.AccessibilityNodeInfo;
-import android.webkit.WebViewCore.EventHub;
-
-import org.apache.http.NameValuePair;
-import org.apache.http.client.utils.URLEncodedUtils;
-import org.json.JSONException;
-import org.json.JSONObject;
-
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.List;
-import java.util.concurrent.atomic.AtomicInteger;
-
-/**
- * Handles injecting accessibility JavaScript and related JavaScript -> Java
- * APIs.
- */
-class AccessibilityInjector {
- private static final String TAG = AccessibilityInjector.class.getSimpleName();
-
- private static boolean DEBUG = false;
-
- // The WebViewClassic this injector is responsible for managing.
- private final WebViewClassic mWebViewClassic;
-
- // Cached reference to mWebViewClassic.getContext(), for convenience.
- private final Context mContext;
-
- // Cached reference to mWebViewClassic.getWebView(), for convenience.
- private final WebView mWebView;
-
- // The Java objects that are exposed to JavaScript.
- private TextToSpeechWrapper mTextToSpeech;
- private CallbackHandler mCallback;
-
- // Lazily loaded helper objects.
- private AccessibilityManager mAccessibilityManager;
- private AccessibilityInjectorFallback mAccessibilityInjectorFallback;
- private JSONObject mAccessibilityJSONObject;
-
- // Whether the accessibility script has been injected into the current page.
- private boolean mAccessibilityScriptInjected;
-
- // Constants for determining script injection strategy.
- private static final int ACCESSIBILITY_SCRIPT_INJECTION_UNDEFINED = -1;
- private static final int ACCESSIBILITY_SCRIPT_INJECTION_OPTED_OUT = 0;
- @SuppressWarnings("unused")
- private static final int ACCESSIBILITY_SCRIPT_INJECTION_PROVIDED = 1;
-
- // Alias for TTS API exposed to JavaScript.
- private static final String ALIAS_TTS_JS_INTERFACE = "accessibility";
-
- // Alias for traversal callback exposed to JavaScript.
- private static final String ALIAS_TRAVERSAL_JS_INTERFACE = "accessibilityTraversal";
-
- // Template for JavaScript that injects a screen-reader.
- private static final String ACCESSIBILITY_SCREEN_READER_JAVASCRIPT_TEMPLATE =
- "javascript:(function() {" +
- " var chooser = document.createElement('script');" +
- " chooser.type = 'text/javascript';" +
- " chooser.src = '%1s';" +
- " document.getElementsByTagName('head')[0].appendChild(chooser);" +
- " })();";
-
- // Template for JavaScript that performs AndroidVox actions.
- private static final String ACCESSIBILITY_ANDROIDVOX_TEMPLATE =
- "(function() {" +
- " if ((typeof(cvox) != 'undefined')" +
- " && (cvox != null)" +
- " && (typeof(cvox.ChromeVox) != 'undefined')" +
- " && (cvox.ChromeVox != null)" +
- " && (typeof(cvox.AndroidVox) != 'undefined')" +
- " && (cvox.AndroidVox != null)" +
- " && cvox.ChromeVox.isActive) {" +
- " return cvox.AndroidVox.performAction('%1s');" +
- " } else {" +
- " return false;" +
- " }" +
- "})()";
-
- // JS code used to shut down an active AndroidVox instance.
- private static final String TOGGLE_CVOX_TEMPLATE =
- "javascript:(function() {" +
- " if ((typeof(cvox) != 'undefined')" +
- " && (cvox != null)" +
- " && (typeof(cvox.ChromeVox) != 'undefined')" +
- " && (cvox.ChromeVox != null)" +
- " && (typeof(cvox.ChromeVox.host) != 'undefined')" +
- " && (cvox.ChromeVox.host != null)) {" +
- " cvox.ChromeVox.host.activateOrDeactivateChromeVox(%b);" +
- " }" +
- "})();";
-
- /**
- * Creates an instance of the AccessibilityInjector based on
- * {@code webViewClassic}.
- *
- * @param webViewClassic The WebViewClassic that this AccessibilityInjector
- * manages.
- */
- public AccessibilityInjector(WebViewClassic webViewClassic) {
- mWebViewClassic = webViewClassic;
- mWebView = webViewClassic.getWebView();
- mContext = webViewClassic.getContext();
- mAccessibilityManager = AccessibilityManager.getInstance(mContext);
- }
-
- /**
- * If JavaScript is enabled, pauses or resumes AndroidVox.
- *
- * @param enabled Whether feedback should be enabled.
- */
- public void toggleAccessibilityFeedback(boolean enabled) {
- if (!isAccessibilityEnabled() || !isJavaScriptEnabled()) {
- return;
- }
-
- toggleAndroidVox(enabled);
-
- if (!enabled && (mTextToSpeech != null)) {
- mTextToSpeech.stop();
- }
- }
-
- /**
- * Attempts to load scripting interfaces for accessibility.
- * <p>
- * This should only be called before a page loads.
- */
- public void addAccessibilityApisIfNecessary() {
- if (!isAccessibilityEnabled() || !isJavaScriptEnabled()) {
- return;
- }
-
- addTtsApis();
- addCallbackApis();
- }
-
- /**
- * Attempts to unload scripting interfaces for accessibility.
- * <p>
- * This should only be called before a page loads.
- */
- private void removeAccessibilityApisIfNecessary() {
- removeTtsApis();
- removeCallbackApis();
- }
-
- /**
- * Destroys this accessibility injector.
- */
- public void destroy() {
- if (mTextToSpeech != null) {
- mTextToSpeech.shutdown();
- mTextToSpeech = null;
- }
-
- if (mCallback != null) {
- mCallback = null;
- }
- }
-
- private void toggleAndroidVox(boolean state) {
- if (!mAccessibilityScriptInjected) {
- return;
- }
-
- final String code = String.format(TOGGLE_CVOX_TEMPLATE, state);
- mWebView.loadUrl(code);
- }
-
- /**
- * Initializes an {@link AccessibilityNodeInfo} with the actions and
- * movement granularity levels supported by this
- * {@link AccessibilityInjector}.
- * <p>
- * If an action identifier is added in this method, this
- * {@link AccessibilityInjector} should also return {@code true} from
- * {@link #supportsAccessibilityAction(int)}.
- * </p>
- *
- * @param info The info to initialize.
- * @see View#onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo)
- */
- public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
- info.setMovementGranularities(AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER
- | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD
- | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE
- | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PARAGRAPH
- | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PAGE);
- info.addAction(AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY);
- info.addAction(AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY);
- info.addAction(AccessibilityNodeInfo.ACTION_NEXT_HTML_ELEMENT);
- info.addAction(AccessibilityNodeInfo.ACTION_PREVIOUS_HTML_ELEMENT);
- info.addAction(AccessibilityNodeInfo.ACTION_CLICK);
- info.setClickable(true);
- }
-
- /**
- * Returns {@code true} if this {@link AccessibilityInjector} should handle
- * the specified action.
- *
- * @param action An accessibility action identifier.
- * @return {@code true} if this {@link AccessibilityInjector} should handle
- * the specified action.
- */
- public boolean supportsAccessibilityAction(int action) {
- switch (action) {
- case AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY:
- case AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY:
- case AccessibilityNodeInfo.ACTION_NEXT_HTML_ELEMENT:
- case AccessibilityNodeInfo.ACTION_PREVIOUS_HTML_ELEMENT:
- case AccessibilityNodeInfo.ACTION_CLICK:
- return true;
- default:
- return false;
- }
- }
-
- /**
- * Performs the specified accessibility action.
- *
- * @param action The identifier of the action to perform.
- * @param arguments The action arguments, or {@code null} if no arguments.
- * @return {@code true} if the action was successful.
- * @see View#performAccessibilityAction(int, Bundle)
- */
- public boolean performAccessibilityAction(int action, Bundle arguments) {
- if (!isAccessibilityEnabled()) {
- mAccessibilityScriptInjected = false;
- toggleFallbackAccessibilityInjector(false);
- return false;
- }
-
- if (mAccessibilityScriptInjected) {
- return sendActionToAndroidVox(action, arguments);
- }
-
- if (mAccessibilityInjectorFallback != null) {
- return mAccessibilityInjectorFallback.performAccessibilityAction(action, arguments);
- }
-
- return false;
- }
-
- /**
- * Attempts to handle key events when accessibility is turned on.
- *
- * @param event The key event to handle.
- * @return {@code true} if the event was handled.
- */
- public boolean handleKeyEventIfNecessary(KeyEvent event) {
- if (!isAccessibilityEnabled()) {
- mAccessibilityScriptInjected = false;
- toggleFallbackAccessibilityInjector(false);
- return false;
- }
-
- if (mAccessibilityScriptInjected) {
- // if an accessibility script is injected we delegate to it the key
- // handling. this script is a screen reader which is a fully fledged
- // solution for blind users to navigate in and interact with web
- // pages.
- if (event.getAction() == KeyEvent.ACTION_UP) {
- mWebViewClassic.sendBatchableInputMessage(EventHub.KEY_UP, 0, 0, event);
- } else if (event.getAction() == KeyEvent.ACTION_DOWN) {
- mWebViewClassic.sendBatchableInputMessage(EventHub.KEY_DOWN, 0, 0, event);
- } else {
- return false;
- }
-
- return true;
- }
-
- if (mAccessibilityInjectorFallback != null) {
- // if an accessibility injector is present (no JavaScript enabled or
- // the site opts out injecting our JavaScript screen reader) we let
- // it decide whether to act on and consume the event.
- return mAccessibilityInjectorFallback.onKeyEvent(event);
- }
-
- return false;
- }
-
- /**
- * Attempts to handle selection change events when accessibility is using a
- * non-JavaScript method.
- * <p>
- * This must not be called from the main thread.
- *
- * @param selection The selection string.
- * @param token The selection request token.
- */
- public void onSelectionStringChangedWebCoreThread(String selection, int token) {
- if (mAccessibilityInjectorFallback != null) {
- mAccessibilityInjectorFallback.onSelectionStringChangedWebCoreThread(selection, token);
- }
- }
-
- /**
- * Prepares for injecting accessibility scripts into a new page.
- *
- * @param url The URL that will be loaded.
- */
- public void onPageStarted(String url) {
- mAccessibilityScriptInjected = false;
- if (DEBUG) {
- Log.w(TAG, "[" + mWebView.hashCode() + "] Started loading new page");
- }
- addAccessibilityApisIfNecessary();
- }
-
- /**
- * Attempts to inject the accessibility script using a {@code <script>} tag.
- * <p>
- * This should be called after a page has finished loading.
- * </p>
- *
- * @param url The URL that just finished loading.
- */
- public void onPageFinished(String url) {
- if (!isAccessibilityEnabled()) {
- toggleFallbackAccessibilityInjector(false);
- return;
- }
-
- toggleFallbackAccessibilityInjector(true);
-
- if (shouldInjectJavaScript(url)) {
- // If we're supposed to use the JS screen reader, request a
- // callback to confirm that CallbackHandler is working.
- if (DEBUG) {
- Log.d(TAG, "[" + mWebView.hashCode() + "] Request callback ");
- }
-
- mCallback.requestCallback(mWebView, mInjectScriptRunnable);
- }
- }
-
- /**
- * Runnable used to inject the JavaScript-based screen reader if the
- * {@link CallbackHandler} API was successfully exposed to JavaScript.
- */
- private Runnable mInjectScriptRunnable = new Runnable() {
- @Override
- public void run() {
- if (DEBUG) {
- Log.d(TAG, "[" + mWebView.hashCode() + "] Received callback");
- }
-
- injectJavaScript();
- }
- };
-
- /**
- * Called by {@link #mInjectScriptRunnable} to inject the JavaScript-based
- * screen reader after confirming that the {@link CallbackHandler} API is
- * functional.
- */
- private void injectJavaScript() {
- toggleFallbackAccessibilityInjector(false);
-
- if (!mAccessibilityScriptInjected) {
- mAccessibilityScriptInjected = true;
- final String injectionUrl = getScreenReaderInjectionUrl();
- mWebView.loadUrl(injectionUrl);
- if (DEBUG) {
- Log.d(TAG, "[" + mWebView.hashCode() + "] Loading screen reader into WebView");
- }
- } else {
- if (DEBUG) {
- Log.w(TAG, "[" + mWebView.hashCode() + "] Attempted to inject screen reader twice");
- }
- }
- }
-
- /**
- * Adjusts the accessibility injection state to reflect changes in the
- * JavaScript enabled state.
- *
- * @param enabled Whether JavaScript is enabled.
- */
- public void updateJavaScriptEnabled(boolean enabled) {
- if (enabled) {
- addAccessibilityApisIfNecessary();
- } else {
- removeAccessibilityApisIfNecessary();
- }
-
- // We have to reload the page after adding or removing APIs.
- mWebView.reload();
- }
-
- /**
- * Toggles the non-JavaScript method for handling accessibility.
- *
- * @param enabled {@code true} to enable the non-JavaScript method, or
- * {@code false} to disable it.
- */
- private void toggleFallbackAccessibilityInjector(boolean enabled) {
- if (enabled && (mAccessibilityInjectorFallback == null)) {
- mAccessibilityInjectorFallback = new AccessibilityInjectorFallback(mWebViewClassic);
- } else {
- mAccessibilityInjectorFallback = null;
- }
- }
-
- /**
- * Determines whether it's okay to inject JavaScript into a given URL.
- *
- * @param url The URL to check.
- * @return {@code true} if JavaScript should be injected, {@code false} if a
- * non-JavaScript method should be used.
- */
- private boolean shouldInjectJavaScript(String url) {
- // Respect the WebView's JavaScript setting.
- if (!isJavaScriptEnabled()) {
- return false;
- }
-
- // Allow the page to opt out of Accessibility script injection.
- if (getAxsUrlParameterValue(url) == ACCESSIBILITY_SCRIPT_INJECTION_OPTED_OUT) {
- return false;
- }
-
- // The user must explicitly enable Accessibility script injection.
- if (!isScriptInjectionEnabled()) {
- return false;
- }
-
- return true;
- }
-
- /**
- * @return {@code true} if the user has explicitly enabled Accessibility
- * script injection.
- */
- private boolean isScriptInjectionEnabled() {
- final int injectionSetting = Settings.Secure.getInt(
- mContext.getContentResolver(), Settings.Secure.ACCESSIBILITY_SCRIPT_INJECTION, 0);
- return (injectionSetting == 1);
- }
-
- /**
- * Attempts to initialize and add interfaces for TTS, if that hasn't already
- * been done.
- */
- private void addTtsApis() {
- if (mTextToSpeech == null) {
- mTextToSpeech = new TextToSpeechWrapper(mContext);
- }
-
- mWebView.addJavascriptInterface(mTextToSpeech, ALIAS_TTS_JS_INTERFACE);
- }
-
- /**
- * Attempts to shutdown and remove interfaces for TTS, if that hasn't
- * already been done.
- */
- private void removeTtsApis() {
- if (mTextToSpeech != null) {
- mTextToSpeech.stop();
- mTextToSpeech.shutdown();
- mTextToSpeech = null;
- }
-
- mWebView.removeJavascriptInterface(ALIAS_TTS_JS_INTERFACE);
- }
-
- private void addCallbackApis() {
- if (mCallback == null) {
- mCallback = new CallbackHandler(ALIAS_TRAVERSAL_JS_INTERFACE);
- }
-
- mWebView.addJavascriptInterface(mCallback, ALIAS_TRAVERSAL_JS_INTERFACE);
- }
-
- private void removeCallbackApis() {
- if (mCallback != null) {
- mCallback = null;
- }
-
- mWebView.removeJavascriptInterface(ALIAS_TRAVERSAL_JS_INTERFACE);
- }
-
- /**
- * Returns the script injection preference requested by the URL, or
- * {@link #ACCESSIBILITY_SCRIPT_INJECTION_UNDEFINED} if the page has no
- * preference.
- *
- * @param url The URL to check.
- * @return A script injection preference.
- */
- private int getAxsUrlParameterValue(String url) {
- if (url == null) {
- return ACCESSIBILITY_SCRIPT_INJECTION_UNDEFINED;
- }
-
- try {
- final List<NameValuePair> params = URLEncodedUtils.parse(new URI(url), null);
-
- for (NameValuePair param : params) {
- if ("axs".equals(param.getName())) {
- return verifyInjectionValue(param.getValue());
- }
- }
- } catch (URISyntaxException e) {
- // Do nothing.
- } catch (IllegalArgumentException e) {
- // Catch badly-formed URLs.
- }
-
- return ACCESSIBILITY_SCRIPT_INJECTION_UNDEFINED;
- }
-
- private int verifyInjectionValue(String value) {
- try {
- final int parsed = Integer.parseInt(value);
-
- switch (parsed) {
- case ACCESSIBILITY_SCRIPT_INJECTION_OPTED_OUT:
- return ACCESSIBILITY_SCRIPT_INJECTION_OPTED_OUT;
- case ACCESSIBILITY_SCRIPT_INJECTION_PROVIDED:
- return ACCESSIBILITY_SCRIPT_INJECTION_PROVIDED;
- }
- } catch (NumberFormatException e) {
- // Do nothing.
- }
-
- return ACCESSIBILITY_SCRIPT_INJECTION_UNDEFINED;
- }
-
- /**
- * @return The URL for injecting the screen reader.
- */
- private String getScreenReaderInjectionUrl() {
- final String screenReaderUrl = Settings.Secure.getString(
- mContext.getContentResolver(), Settings.Secure.ACCESSIBILITY_SCREEN_READER_URL);
- return String.format(ACCESSIBILITY_SCREEN_READER_JAVASCRIPT_TEMPLATE, screenReaderUrl);
- }
-
- /**
- * @return {@code true} if JavaScript is enabled in the {@link WebView}
- * settings.
- */
- private boolean isJavaScriptEnabled() {
- final WebSettings settings = mWebView.getSettings();
- if (settings == null) {
- return false;
- }
-
- return settings.getJavaScriptEnabled();
- }
-
- /**
- * @return {@code true} if accessibility is enabled.
- */
- private boolean isAccessibilityEnabled() {
- return mAccessibilityManager.isEnabled();
- }
-
- /**
- * Packs an accessibility action into a JSON object and sends it to AndroidVox.
- *
- * @param action The action identifier.
- * @param arguments The action arguments, if applicable.
- * @return The result of the action.
- */
- private boolean sendActionToAndroidVox(int action, Bundle arguments) {
- if (mAccessibilityJSONObject == null) {
- mAccessibilityJSONObject = new JSONObject();
- } else {
- // Remove all keys from the object.
- final Iterator<?> keys = mAccessibilityJSONObject.keys();
- while (keys.hasNext()) {
- keys.next();
- keys.remove();
- }
- }
-
- try {
- mAccessibilityJSONObject.accumulate("action", action);
-
- switch (action) {
- case AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY:
- case AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY:
- if (arguments != null) {
- final int granularity = arguments.getInt(
- AccessibilityNodeInfo.ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT);
- mAccessibilityJSONObject.accumulate("granularity", granularity);
- }
- break;
- case AccessibilityNodeInfo.ACTION_NEXT_HTML_ELEMENT:
- case AccessibilityNodeInfo.ACTION_PREVIOUS_HTML_ELEMENT:
- if (arguments != null) {
- final String element = arguments.getString(
- AccessibilityNodeInfo.ACTION_ARGUMENT_HTML_ELEMENT_STRING);
- mAccessibilityJSONObject.accumulate("element", element);
- }
- break;
- }
- } catch (JSONException e) {
- return false;
- }
-
- final String jsonString = mAccessibilityJSONObject.toString();
- final String jsCode = String.format(ACCESSIBILITY_ANDROIDVOX_TEMPLATE, jsonString);
- return mCallback.performAction(mWebView, jsCode);
- }
-
- /**
- * Used to protect the TextToSpeech class, only exposing the methods we want to expose.
- */
- private static class TextToSpeechWrapper {
- private static final String WRAP_TAG = TextToSpeechWrapper.class.getSimpleName();
-
- /** Lock used to control access to the TextToSpeech object. */
- private final Object mTtsLock = new Object();
-
- private final HashMap<String, String> mTtsParams;
- private final TextToSpeech mTextToSpeech;
-
- /**
- * Whether this wrapper is ready to speak. If this is {@code true} then
- * {@link #mShutdown} is guaranteed to be {@code false}.
- */
- private volatile boolean mReady;
-
- /**
- * Whether this wrapper was shut down. If this is {@code true} then
- * {@link #mReady} is guaranteed to be {@code false}.
- */
- private volatile boolean mShutdown;
-
- public TextToSpeechWrapper(Context context) {
- if (DEBUG) {
- Log.d(WRAP_TAG, "[" + hashCode() + "] Initializing text-to-speech on thread "
- + Thread.currentThread().getId() + "...");
- }
-
- final String pkgName = context.getPackageName();
-
- mReady = false;
- mShutdown = false;
-
- mTtsParams = new HashMap<String, String>();
- mTtsParams.put(Engine.KEY_PARAM_UTTERANCE_ID, WRAP_TAG);
-
- mTextToSpeech = new TextToSpeech(
- context, mInitListener, null, pkgName + ".**webview**", true);
- mTextToSpeech.setOnUtteranceProgressListener(mErrorListener);
- }
-
- @JavascriptInterface
- @SuppressWarnings("unused")
- public boolean isSpeaking() {
- synchronized (mTtsLock) {
- if (!mReady) {
- return false;
- }
-
- return mTextToSpeech.isSpeaking();
- }
- }
-
- @JavascriptInterface
- @SuppressWarnings("unused")
- public int speak(String text, int queueMode, HashMap<String, String> params) {
- synchronized (mTtsLock) {
- if (!mReady) {
- if (DEBUG) {
- Log.w(WRAP_TAG, "[" + hashCode() + "] Attempted to speak before TTS init");
- }
- return TextToSpeech.ERROR;
- } else {
- if (DEBUG) {
- Log.i(WRAP_TAG, "[" + hashCode() + "] Speak called from JS binder");
- }
- }
-
- return mTextToSpeech.speak(text, queueMode, params);
- }
- }
-
- @JavascriptInterface
- @SuppressWarnings("unused")
- public int stop() {
- synchronized (mTtsLock) {
- if (!mReady) {
- if (DEBUG) {
- Log.w(WRAP_TAG, "[" + hashCode() + "] Attempted to stop before initialize");
- }
- return TextToSpeech.ERROR;
- } else {
- if (DEBUG) {
- Log.i(WRAP_TAG, "[" + hashCode() + "] Stop called from JS binder");
- }
- }
-
- return mTextToSpeech.stop();
- }
- }
-
- @SuppressWarnings("unused")
- protected void shutdown() {
- synchronized (mTtsLock) {
- if (!mReady) {
- if (DEBUG) {
- Log.w(WRAP_TAG, "[" + hashCode() + "] Called shutdown before initialize");
- }
- } else {
- if (DEBUG) {
- Log.i(WRAP_TAG, "[" + hashCode() + "] Shutting down text-to-speech from "
- + "thread " + Thread.currentThread().getId() + "...");
- }
- }
- mShutdown = true;
- mReady = false;
- mTextToSpeech.shutdown();
- }
- }
-
- private final OnInitListener mInitListener = new OnInitListener() {
- @Override
- public void onInit(int status) {
- synchronized (mTtsLock) {
- if (!mShutdown && (status == TextToSpeech.SUCCESS)) {
- if (DEBUG) {
- Log.d(WRAP_TAG, "[" + TextToSpeechWrapper.this.hashCode()
- + "] Initialized successfully");
- }
- mReady = true;
- } else {
- if (DEBUG) {
- Log.w(WRAP_TAG, "[" + TextToSpeechWrapper.this.hashCode()
- + "] Failed to initialize");
- }
- mReady = false;
- }
- }
- }
- };
-
- private final UtteranceProgressListener mErrorListener = new UtteranceProgressListener() {
- @Override
- public void onStart(String utteranceId) {
- // Do nothing.
- }
-
- @Override
- public void onError(String utteranceId) {
- if (DEBUG) {
- Log.w(WRAP_TAG, "[" + TextToSpeechWrapper.this.hashCode()
- + "] Failed to speak utterance");
- }
- }
-
- @Override
- public void onDone(String utteranceId) {
- // Do nothing.
- }
- };
- }
-
- /**
- * Exposes result interface to JavaScript.
- */
- private static class CallbackHandler {
- private static final String JAVASCRIPT_ACTION_TEMPLATE =
- "javascript:(function() { %s.onResult(%d, %s); })();";
-
- // Time in milliseconds to wait for a result before failing.
- private static final long RESULT_TIMEOUT = 5000;
-
- private final AtomicInteger mResultIdCounter = new AtomicInteger();
- private final Object mResultLock = new Object();
- private final String mInterfaceName;
- private final Handler mMainHandler;
-
- private Runnable mCallbackRunnable;
-
- private boolean mResult = false;
- private int mResultId = -1;
-
- private CallbackHandler(String interfaceName) {
- mInterfaceName = interfaceName;
- mMainHandler = new Handler();
- }
-
- /**
- * Performs an action and attempts to wait for a result.
- *
- * @param webView The WebView to perform the action on.
- * @param code JavaScript code that evaluates to a result.
- * @return The result of the action, or false if it timed out.
- */
- private boolean performAction(WebView webView, String code) {
- final int resultId = mResultIdCounter.getAndIncrement();
- final String url = String.format(
- JAVASCRIPT_ACTION_TEMPLATE, mInterfaceName, resultId, code);
- webView.loadUrl(url);
-
- return getResultAndClear(resultId);
- }
-
- /**
- * Gets the result of a request to perform an accessibility action.
- *
- * @param resultId The result id to match the result with the request.
- * @return The result of the request.
- */
- private boolean getResultAndClear(int resultId) {
- synchronized (mResultLock) {
- final boolean success = waitForResultTimedLocked(resultId);
- final boolean result = success ? mResult : false;
- clearResultLocked();
- return result;
- }
- }
-
- /**
- * Clears the result state.
- */
- private void clearResultLocked() {
- mResultId = -1;
- mResult = false;
- }
-
- /**
- * Waits up to a given bound for a result of a request and returns it.
- *
- * @param resultId The result id to match the result with the request.
- * @return Whether the result was received.
- */
- private boolean waitForResultTimedLocked(int resultId) {
- final long startTimeMillis = SystemClock.uptimeMillis();
-
- if (DEBUG) {
- Log.d(TAG, "Waiting for CVOX result with ID " + resultId + "...");
- }
-
- while (true) {
- // Fail if we received a callback from the future.
- if (mResultId > resultId) {
- if (DEBUG) {
- Log.w(TAG, "Aborted CVOX result");
- }
- return false;
- }
-
- final long elapsedTimeMillis = (SystemClock.uptimeMillis() - startTimeMillis);
-
- // Succeed if we received the callback we were expecting.
- if (DEBUG) {
- Log.w(TAG, "Check " + mResultId + " versus expected " + resultId);
- }
- if (mResultId == resultId) {
- if (DEBUG) {
- Log.w(TAG, "Received CVOX result after " + elapsedTimeMillis + " ms");
- }
- return true;
- }
-
- final long waitTimeMillis = (RESULT_TIMEOUT - elapsedTimeMillis);
-
- // Fail if we've already exceeded the timeout.
- if (waitTimeMillis <= 0) {
- if (DEBUG) {
- Log.w(TAG, "Timed out while waiting for CVOX result");
- }
- return false;
- }
-
- try {
- if (DEBUG) {
- Log.w(TAG, "Start waiting...");
- }
- mResultLock.wait(waitTimeMillis);
- } catch (InterruptedException ie) {
- if (DEBUG) {
- Log.w(TAG, "Interrupted while waiting for CVOX result");
- }
- }
- }
- }
-
- /**
- * Callback exposed to JavaScript. Handles returning the result of a
- * request to a waiting (or potentially timed out) thread.
- *
- * @param id The result id of the request as a {@link String}.
- * @param result The result of the request as a {@link String}.
- */
- @JavascriptInterface
- @SuppressWarnings("unused")
- public void onResult(String id, String result) {
- if (DEBUG) {
- Log.w(TAG, "Saw CVOX result of '" + result + "' for ID " + id);
- }
- final int resultId;
-
- try {
- resultId = Integer.parseInt(id);
- } catch (NumberFormatException e) {
- return;
- }
-
- synchronized (mResultLock) {
- if (resultId > mResultId) {
- mResult = Boolean.parseBoolean(result);
- mResultId = resultId;
- } else {
- if (DEBUG) {
- Log.w(TAG, "Result with ID " + resultId + " was stale vesus " + mResultId);
- }
- }
- mResultLock.notifyAll();
- }
- }
-
- /**
- * Requests a callback to ensure that the JavaScript interface for this
- * object has been added successfully.
- *
- * @param webView The web view to request a callback from.
- * @param callbackRunnable Runnable to execute if a callback is received.
- */
- public void requestCallback(WebView webView, Runnable callbackRunnable) {
- mCallbackRunnable = callbackRunnable;
-
- webView.loadUrl("javascript:(function() { " + mInterfaceName + ".callback(); })();");
- }
-
- @JavascriptInterface
- @SuppressWarnings("unused")
- public void callback() {
- if (mCallbackRunnable != null) {
- mMainHandler.post(mCallbackRunnable);
- mCallbackRunnable = null;
- }
- }
- }
-}
+++ /dev/null
-/*
- * Copyright (C) 2010 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.os.Bundle;
-import android.provider.Settings;
-import android.text.TextUtils;
-import android.text.TextUtils.SimpleStringSplitter;
-import android.util.Log;
-import android.view.KeyEvent;
-import android.view.accessibility.AccessibilityEvent;
-import android.view.accessibility.AccessibilityManager;
-import android.view.accessibility.AccessibilityNodeInfo;
-import android.webkit.WebViewCore.EventHub;
-
-import com.android.internal.os.SomeArgs;
-
-import java.util.ArrayList;
-
-/**
- * This class injects accessibility into WebViews with disabled JavaScript or
- * WebViews with enabled JavaScript but for which we have no accessibility
- * script to inject.
- * </p>
- * Note: To avoid changes in the framework upon changing the available
- * navigation axis, or reordering the navigation axis, or changing
- * the key bindings, or defining sequence of actions to be bound to
- * a given key this class is navigation axis agnostic. It is only
- * aware of one navigation axis which is in fact the default behavior
- * of webViews while using the DPAD/TrackBall.
- * </p>
- * In general a key binding is a mapping from modifiers + key code to
- * a sequence of actions. For more detail how to specify key bindings refer to
- * {@link android.provider.Settings.Secure#ACCESSIBILITY_WEB_CONTENT_KEY_BINDINGS}.
- * </p>
- * The possible actions are invocations to
- * {@link #setCurrentAxis(int, boolean, String)}, or
- * {@link #traverseGivenAxis(int, int, boolean, String, boolean)}
- * {@link #performAxisTransition(int, int, boolean, String)}
- * referred via the values of:
- * {@link #ACTION_SET_CURRENT_AXIS},
- * {@link #ACTION_TRAVERSE_CURRENT_AXIS},
- * {@link #ACTION_TRAVERSE_GIVEN_AXIS},
- * {@link #ACTION_PERFORM_AXIS_TRANSITION},
- * respectively.
- * The arguments for the action invocation are specified as offset
- * hexademical pairs. Note the last argument of the invocation
- * should NOT be specified in the binding as it is provided by
- * this class. For details about the key binding implementation
- * refer to {@link AccessibilityWebContentKeyBinding}.
- */
-class AccessibilityInjectorFallback {
- private static final String LOG_TAG = "AccessibilityInjector";
-
- private static final boolean DEBUG = true;
-
- private static final int ACTION_SET_CURRENT_AXIS = 0;
- private static final int ACTION_TRAVERSE_CURRENT_AXIS = 1;
- private static final int ACTION_TRAVERSE_GIVEN_AXIS = 2;
- private static final int ACTION_PERFORM_AXIS_TRANSITION = 3;
- private static final int ACTION_TRAVERSE_DEFAULT_WEB_VIEW_BEHAVIOR_AXIS = 4;
-
- /** Timeout after which asynchronous granular movement is aborted. */
- private static final int MODIFY_SELECTION_TIMEOUT = 500;
-
- // WebView navigation axes from WebViewCore.h, plus an additional axis for
- // the default behavior.
- private static final int NAVIGATION_AXIS_CHARACTER = 0;
- private static final int NAVIGATION_AXIS_WORD = 1;
- private static final int NAVIGATION_AXIS_SENTENCE = 2;
- @SuppressWarnings("unused")
- private static final int NAVIGATION_AXIS_HEADING = 3;
- @SuppressWarnings("unused")
- private static final int NAVIGATION_AXIS_SIBLING = 4;
- @SuppressWarnings("unused")
- private static final int NAVIGATION_AXIS_PARENT_FIRST_CHILD = 5;
- private static final int NAVIGATION_AXIS_DOCUMENT = 6;
- private static final int NAVIGATION_AXIS_DEFAULT_WEB_VIEW_BEHAVIOR = 7;
-
- // WebView navigation directions from WebViewCore.h.
- private static final int NAVIGATION_DIRECTION_BACKWARD = 0;
- private static final int NAVIGATION_DIRECTION_FORWARD = 1;
-
- // these are the same for all instances so make them process wide
- private static ArrayList<AccessibilityWebContentKeyBinding> sBindings =
- new ArrayList<AccessibilityWebContentKeyBinding>();
-
- // handle to the WebViewClassic this injector is associated with.
- private final WebViewClassic mWebView;
- private final WebView mWebViewInternal;
-
- // Event scheduled for sending as soon as we receive the selected text.
- private AccessibilityEvent mScheduledEvent;
-
- // Token required to send the scheduled event.
- private int mScheduledToken = 0;
-
- // the current traversal axis
- private int mCurrentAxis = 2; // sentence
-
- // we need to consume the up if we have handled the last down
- private boolean mLastDownEventHandled;
-
- // getting two empty selection strings in a row we let the WebView handle the event
- private boolean mIsLastSelectionStringNull;
-
- // keep track of last direction
- private int mLastDirection;
-
- // Lock used for asynchronous selection callback.
- private final Object mCallbackLock = new Object();
-
- // Whether the asynchronous selection callback was received.
- private boolean mCallbackReceived;
-
- // Whether the asynchronous selection callback succeeded.
- private boolean mCallbackResult;
-
- /**
- * Creates a new injector associated with a given {@link WebViewClassic}.
- *
- * @param webView The associated WebViewClassic.
- */
- public AccessibilityInjectorFallback(WebViewClassic webView) {
- mWebView = webView;
- mWebViewInternal = mWebView.getWebView();
- ensureWebContentKeyBindings();
- }
-
- /**
- * Processes a key down <code>event</code>.
- *
- * @return True if the event was processed.
- */
- public boolean onKeyEvent(KeyEvent event) {
- // We do not handle ENTER in any circumstances.
- if (isEnterActionKey(event.getKeyCode())) {
- return false;
- }
-
- if (event.getAction() == KeyEvent.ACTION_UP) {
- return mLastDownEventHandled;
- }
-
- mLastDownEventHandled = false;
-
- AccessibilityWebContentKeyBinding binding = null;
- for (AccessibilityWebContentKeyBinding candidate : sBindings) {
- if (event.getKeyCode() == candidate.getKeyCode()
- && event.hasModifiers(candidate.getModifiers())) {
- binding = candidate;
- break;
- }
- }
-
- if (binding == null) {
- return false;
- }
-
- for (int i = 0, count = binding.getActionCount(); i < count; i++) {
- int actionCode = binding.getActionCode(i);
- String contentDescription = Integer.toHexString(binding.getAction(i));
- switch (actionCode) {
- case ACTION_SET_CURRENT_AXIS:
- int axis = binding.getFirstArgument(i);
- boolean sendEvent = (binding.getSecondArgument(i) == 1);
- setCurrentAxis(axis, sendEvent, contentDescription);
- mLastDownEventHandled = true;
- break;
- case ACTION_TRAVERSE_CURRENT_AXIS:
- int direction = binding.getFirstArgument(i);
- // on second null selection string in same direction - WebView handles the event
- if (direction == mLastDirection && mIsLastSelectionStringNull) {
- mIsLastSelectionStringNull = false;
- return false;
- }
- mLastDirection = direction;
- sendEvent = (binding.getSecondArgument(i) == 1);
- mLastDownEventHandled = traverseGivenAxis(
- direction, mCurrentAxis, sendEvent, contentDescription, false);
- break;
- case ACTION_TRAVERSE_GIVEN_AXIS:
- direction = binding.getFirstArgument(i);
- // on second null selection string in same direction => WebView handle the event
- if (direction == mLastDirection && mIsLastSelectionStringNull) {
- mIsLastSelectionStringNull = false;
- return false;
- }
- mLastDirection = direction;
- axis = binding.getSecondArgument(i);
- sendEvent = (binding.getThirdArgument(i) == 1);
- traverseGivenAxis(direction, axis, sendEvent, contentDescription, false);
- mLastDownEventHandled = true;
- break;
- case ACTION_PERFORM_AXIS_TRANSITION:
- int fromAxis = binding.getFirstArgument(i);
- int toAxis = binding.getSecondArgument(i);
- sendEvent = (binding.getThirdArgument(i) == 1);
- performAxisTransition(fromAxis, toAxis, sendEvent, contentDescription);
- mLastDownEventHandled = true;
- break;
- case ACTION_TRAVERSE_DEFAULT_WEB_VIEW_BEHAVIOR_AXIS:
- // This is a special case since we treat the default WebView navigation
- // behavior as one of the possible navigation axis the user can use.
- // If we are not on the default WebView navigation axis this is NOP.
- if (mCurrentAxis == NAVIGATION_AXIS_DEFAULT_WEB_VIEW_BEHAVIOR) {
- // While WebVew handles navigation we do not get null selection
- // strings so do not check for that here as the cases above.
- mLastDirection = binding.getFirstArgument(i);
- sendEvent = (binding.getSecondArgument(i) == 1);
- traverseGivenAxis(mLastDirection, NAVIGATION_AXIS_DEFAULT_WEB_VIEW_BEHAVIOR,
- sendEvent, contentDescription, false);
- mLastDownEventHandled = false;
- } else {
- mLastDownEventHandled = true;
- }
- break;
- default:
- Log.w(LOG_TAG, "Unknown action code: " + actionCode);
- }
- }
-
- return mLastDownEventHandled;
- }
-
- /**
- * Set the current navigation axis.
- *
- * @param axis The axis to set.
- * @param sendEvent Whether to send an accessibility event to
- * announce the change.
- */
- private void setCurrentAxis(int axis, boolean sendEvent, String contentDescription) {
- mCurrentAxis = axis;
- if (sendEvent) {
- final AccessibilityEvent event = getPartialyPopulatedAccessibilityEvent(
- AccessibilityEvent.TYPE_ANNOUNCEMENT);
- event.getText().add(String.valueOf(axis));
- event.setContentDescription(contentDescription);
- sendAccessibilityEvent(event);
- }
- }
-
- /**
- * Performs conditional transition one axis to another.
- *
- * @param fromAxis The axis which must be the current for the transition to occur.
- * @param toAxis The axis to which to transition.
- * @param sendEvent Flag if to send an event to announce successful transition.
- * @param contentDescription A description of the performed action.
- */
- private void performAxisTransition(int fromAxis, int toAxis, boolean sendEvent,
- String contentDescription) {
- if (mCurrentAxis == fromAxis) {
- setCurrentAxis(toAxis, sendEvent, contentDescription);
- }
- }
-
- boolean performAccessibilityAction(int action, Bundle arguments) {
- switch (action) {
- case AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY:
- case AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY: {
- final int direction = getDirectionForAction(action);
- final int axis = getAxisForGranularity(arguments.getInt(
- AccessibilityNodeInfo.ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT));
- return traverseGivenAxis(direction, axis, true, null, true);
- }
- case AccessibilityNodeInfo.ACTION_NEXT_HTML_ELEMENT:
- case AccessibilityNodeInfo.ACTION_PREVIOUS_HTML_ELEMENT: {
- final int direction = getDirectionForAction(action);
- // TODO: Add support for moving by object.
- final int axis = NAVIGATION_AXIS_SENTENCE;
- return traverseGivenAxis(direction, axis, true, null, true);
- }
- default:
- return false;
- }
- }
-
- /**
- * Returns the {@link WebView}-defined direction for the given
- * {@link AccessibilityNodeInfo}-defined action.
- *
- * @param action An accessibility action identifier.
- * @return A web view navigation direction.
- */
- private static int getDirectionForAction(int action) {
- switch (action) {
- case AccessibilityNodeInfo.ACTION_NEXT_HTML_ELEMENT:
- case AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY:
- return NAVIGATION_DIRECTION_FORWARD;
- case AccessibilityNodeInfo.ACTION_PREVIOUS_HTML_ELEMENT:
- case AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY:
- return NAVIGATION_DIRECTION_BACKWARD;
- default:
- return -1;
- }
- }
-
- /**
- * Returns the {@link WebView}-defined axis for the given
- * {@link AccessibilityNodeInfo}-defined granularity.
- *
- * @param granularity An accessibility granularity identifier.
- * @return A web view navigation axis.
- */
- private static int getAxisForGranularity(int granularity) {
- switch (granularity) {
- case AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER:
- return NAVIGATION_AXIS_CHARACTER;
- case AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD:
- return NAVIGATION_AXIS_WORD;
- case AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE:
- return NAVIGATION_AXIS_SENTENCE;
- case AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PARAGRAPH:
- // TODO: This should map to object once we implement it.
- return NAVIGATION_AXIS_SENTENCE;
- case AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PAGE:
- return NAVIGATION_AXIS_DOCUMENT;
- default:
- return -1;
- }
- }
-
- /**
- * Traverse the document along the given navigation axis.
- *
- * @param direction The direction of traversal.
- * @param axis The axis along which to traverse.
- * @param sendEvent Whether to send an accessibility event to
- * announce the change.
- * @param contentDescription A description of the performed action.
- */
- private boolean traverseGivenAxis(int direction, int axis, boolean sendEvent,
- String contentDescription, boolean sychronous) {
- final WebViewCore webViewCore = mWebView.getWebViewCore();
- if (webViewCore == null) {
- return false;
- }
-
- if (sendEvent) {
- final AccessibilityEvent event = getPartialyPopulatedAccessibilityEvent(
- AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY);
- // The text will be set upon receiving the selection string.
- event.setContentDescription(contentDescription);
- mScheduledEvent = event;
- mScheduledToken++;
- }
-
- // if the axis is the default let WebView handle the event which will
- // result in cursor ring movement and selection of its content
- if (axis == NAVIGATION_AXIS_DEFAULT_WEB_VIEW_BEHAVIOR) {
- return false;
- }
-
- final SomeArgs args = SomeArgs.obtain();
- args.argi1 = direction;
- args.argi2 = axis;
- args.argi3 = mScheduledToken;
-
- // If we don't need synchronous results, just return true.
- if (!sychronous) {
- webViewCore.sendMessage(EventHub.MODIFY_SELECTION, args);
- return true;
- }
-
- final boolean callbackResult;
-
- synchronized (mCallbackLock) {
- mCallbackReceived = false;
-
- // Asynchronously changes the selection in WebView, which responds by
- // calling onSelectionStringChanged().
- webViewCore.sendMessage(EventHub.MODIFY_SELECTION, args);
-
- try {
- mCallbackLock.wait(MODIFY_SELECTION_TIMEOUT);
- } catch (InterruptedException e) {
- // Do nothing.
- }
-
- callbackResult = mCallbackResult;
- }
-
- return (mCallbackReceived && callbackResult);
- }
-
- /* package */ void onSelectionStringChangedWebCoreThread(
- final String selection, final int token) {
- synchronized (mCallbackLock) {
- mCallbackReceived = true;
- mCallbackResult = (selection != null);
- mCallbackLock.notifyAll();
- }
-
- // Managing state and sending events must take place on the UI thread.
- mWebViewInternal.post(new Runnable() {
- @Override
- public void run() {
- onSelectionStringChangedMainThread(selection, token);
- }
- });
- }
-
- private void onSelectionStringChangedMainThread(String selection, int token) {
- if (DEBUG) {
- Log.d(LOG_TAG, "Selection string: " + selection);
- }
-
- if (token != mScheduledToken) {
- if (DEBUG) {
- Log.d(LOG_TAG, "Selection string has incorrect token: " + token);
- }
- return;
- }
-
- mIsLastSelectionStringNull = (selection == null);
-
- final AccessibilityEvent event = mScheduledEvent;
- mScheduledEvent = null;
-
- if ((event != null) && (selection != null)) {
- event.getText().add(selection);
- event.setFromIndex(0);
- event.setToIndex(selection.length());
- sendAccessibilityEvent(event);
- }
- }
-
- /**
- * Sends an {@link AccessibilityEvent}.
- *
- * @param event The event to send.
- */
- private void sendAccessibilityEvent(AccessibilityEvent event) {
- if (DEBUG) {
- Log.d(LOG_TAG, "Dispatching: " + event);
- }
- // accessibility may be disabled while waiting for the selection string
- AccessibilityManager accessibilityManager =
- AccessibilityManager.getInstance(mWebView.getContext());
- if (accessibilityManager.isEnabled()) {
- accessibilityManager.sendAccessibilityEvent(event);
- }
- }
-
- /**
- * @return An accessibility event whose members are populated except its
- * text and content description.
- */
- private AccessibilityEvent getPartialyPopulatedAccessibilityEvent(int eventType) {
- AccessibilityEvent event = AccessibilityEvent.obtain(eventType);
- mWebViewInternal.onInitializeAccessibilityEvent(event);
- return event;
- }
-
- /**
- * Ensures that the Web content key bindings are loaded.
- */
- private void ensureWebContentKeyBindings() {
- if (sBindings.size() > 0) {
- return;
- }
-
- String webContentKeyBindingsString = Settings.Secure.getString(
- mWebView.getContext().getContentResolver(),
- Settings.Secure.ACCESSIBILITY_WEB_CONTENT_KEY_BINDINGS);
-
- SimpleStringSplitter semiColonSplitter = new SimpleStringSplitter(';');
- semiColonSplitter.setString(webContentKeyBindingsString);
-
- while (semiColonSplitter.hasNext()) {
- String bindingString = semiColonSplitter.next();
- if (TextUtils.isEmpty(bindingString)) {
- Log.e(LOG_TAG, "Disregarding malformed Web content key binding: "
- + webContentKeyBindingsString);
- continue;
- }
- String[] keyValueArray = bindingString.split("=");
- if (keyValueArray.length != 2) {
- Log.e(LOG_TAG, "Disregarding malformed Web content key binding: " + bindingString);
- continue;
- }
- try {
- long keyCodeAndModifiers = Long.decode(keyValueArray[0].trim());
- String[] actionStrings = keyValueArray[1].split(":");
- int[] actions = new int[actionStrings.length];
- for (int i = 0, count = actions.length; i < count; i++) {
- actions[i] = Integer.decode(actionStrings[i].trim());
- }
- sBindings.add(new AccessibilityWebContentKeyBinding(keyCodeAndModifiers, actions));
- } catch (NumberFormatException nfe) {
- Log.e(LOG_TAG, "Disregarding malformed key binding: " + bindingString);
- }
- }
- }
-
- private boolean isEnterActionKey(int keyCode) {
- return keyCode == KeyEvent.KEYCODE_DPAD_CENTER
- || keyCode == KeyEvent.KEYCODE_ENTER
- || keyCode == KeyEvent.KEYCODE_NUMPAD_ENTER;
- }
-
- /**
- * Represents a web content key-binding.
- */
- private static final class AccessibilityWebContentKeyBinding {
-
- private static final int MODIFIERS_OFFSET = 32;
- private static final long MODIFIERS_MASK = 0xFFFFFFF00000000L;
-
- private static final int KEY_CODE_OFFSET = 0;
- private static final long KEY_CODE_MASK = 0x00000000FFFFFFFFL;
-
- private static final int ACTION_OFFSET = 24;
- private static final int ACTION_MASK = 0xFF000000;
-
- private static final int FIRST_ARGUMENT_OFFSET = 16;
- private static final int FIRST_ARGUMENT_MASK = 0x00FF0000;
-
- private static final int SECOND_ARGUMENT_OFFSET = 8;
- private static final int SECOND_ARGUMENT_MASK = 0x0000FF00;
-
- private static final int THIRD_ARGUMENT_OFFSET = 0;
- private static final int THIRD_ARGUMENT_MASK = 0x000000FF;
-
- private final long mKeyCodeAndModifiers;
-
- private final int [] mActionSequence;
-
- /**
- * @return The key code of the binding key.
- */
- public int getKeyCode() {
- return (int) ((mKeyCodeAndModifiers & KEY_CODE_MASK) >> KEY_CODE_OFFSET);
- }
-
- /**
- * @return The meta state of the binding key.
- */
- public int getModifiers() {
- return (int) ((mKeyCodeAndModifiers & MODIFIERS_MASK) >> MODIFIERS_OFFSET);
- }
-
- /**
- * @return The number of actions in the key binding.
- */
- public int getActionCount() {
- return mActionSequence.length;
- }
-
- /**
- * @param index The action for a given action <code>index</code>.
- */
- public int getAction(int index) {
- return mActionSequence[index];
- }
-
- /**
- * @param index The action code for a given action <code>index</code>.
- */
- public int getActionCode(int index) {
- return (mActionSequence[index] & ACTION_MASK) >> ACTION_OFFSET;
- }
-
- /**
- * @param index The first argument for a given action <code>index</code>.
- */
- public int getFirstArgument(int index) {
- return (mActionSequence[index] & FIRST_ARGUMENT_MASK) >> FIRST_ARGUMENT_OFFSET;
- }
-
- /**
- * @param index The second argument for a given action <code>index</code>.
- */
- public int getSecondArgument(int index) {
- return (mActionSequence[index] & SECOND_ARGUMENT_MASK) >> SECOND_ARGUMENT_OFFSET;
- }
-
- /**
- * @param index The third argument for a given action <code>index</code>.
- */
- public int getThirdArgument(int index) {
- return (mActionSequence[index] & THIRD_ARGUMENT_MASK) >> THIRD_ARGUMENT_OFFSET;
- }
-
- /**
- * Creates a new instance.
- * @param keyCodeAndModifiers The key for the binding (key and modifiers).
- * @param actionSequence The sequence of action for the binding.
- */
- public AccessibilityWebContentKeyBinding(long keyCodeAndModifiers, int[] actionSequence) {
- mKeyCodeAndModifiers = keyCodeAndModifiers;
- mActionSequence = actionSequence;
- }
-
- @Override
- public String toString() {
- StringBuilder builder = new StringBuilder();
- builder.append("modifiers: ");
- builder.append(getModifiers());
- builder.append(", keyCode: ");
- builder.append(getKeyCode());
- builder.append(", actions[");
- for (int i = 0, count = getActionCount(); i < count; i++) {
- builder.append("{actionCode");
- builder.append(i);
- builder.append(": ");
- builder.append(getActionCode(i));
- builder.append(", firstArgument: ");
- builder.append(getFirstArgument(i));
- builder.append(", secondArgument: ");
- builder.append(getSecondArgument(i));
- builder.append(", thirdArgument: ");
- builder.append(getThirdArgument(i));
- builder.append("}");
- }
- builder.append("]");
- return builder.toString();
- }
- }
-}
+++ /dev/null
-/*
- * Copyright (C) 2012 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.content.Context;
-import android.os.Handler;
-import android.os.Message;
-import android.text.Editable;
-import android.view.KeyEvent;
-import android.view.View;
-import android.widget.AbsoluteLayout;
-import android.widget.AdapterView;
-import android.widget.AdapterView.OnItemClickListener;
-import android.widget.Filter;
-import android.widget.Filterable;
-import android.widget.ListAdapter;
-import android.widget.ListPopupWindow;
-import android.widget.PopupWindow.OnDismissListener;
-
-class AutoCompletePopup implements OnItemClickListener, Filter.FilterListener,
- OnDismissListener{
- private static class AnchorView extends View {
- AnchorView(Context context) {
- super(context);
- setFocusable(false);
- setVisibility(INVISIBLE);
- }
- }
- private static final int AUTOFILL_FORM = 100;
- private boolean mIsAutoFillProfileSet;
- private Handler mHandler;
- private int mQueryId;
- private ListPopupWindow mPopup;
- private Filter mFilter;
- private CharSequence mText;
- private ListAdapter mAdapter;
- private View mAnchor;
- private WebViewClassic.WebViewInputConnection mInputConnection;
- private WebViewClassic mWebView;
-
- public AutoCompletePopup(WebViewClassic webView,
- WebViewClassic.WebViewInputConnection inputConnection) {
- mInputConnection = inputConnection;
- mWebView = webView;
- mHandler = new Handler() {
- @Override
- public void handleMessage(Message msg) {
- switch (msg.what) {
- case AUTOFILL_FORM:
- mWebView.autoFillForm(mQueryId);
- break;
- }
- }
- };
- }
-
- public boolean onKeyPreIme(int keyCode, KeyEvent event) {
- if (mPopup == null) {
- return false;
- }
- if (keyCode == KeyEvent.KEYCODE_BACK && mPopup.isShowing()) {
- // special case for the back key, we do not even try to send it
- // to the drop down list but instead, consume it immediately
- if (event.getAction() == KeyEvent.ACTION_DOWN && event.getRepeatCount() == 0) {
- KeyEvent.DispatcherState state = mAnchor.getKeyDispatcherState();
- if (state != null) {
- state.startTracking(event, this);
- }
- return true;
- } else if (event.getAction() == KeyEvent.ACTION_UP) {
- KeyEvent.DispatcherState state = mAnchor.getKeyDispatcherState();
- if (state != null) {
- state.handleUpEvent(event);
- }
- if (event.isTracking() && !event.isCanceled()) {
- mPopup.dismiss();
- return true;
- }
- }
- }
- if (mPopup.isShowing()) {
- return mPopup.onKeyPreIme(keyCode, event);
- }
- return false;
- }
-
- public void setText(CharSequence text) {
- mText = text;
- if (mFilter != null) {
- mFilter.filter(text, this);
- }
- }
-
- public void setAutoFillQueryId(int queryId) {
- mQueryId = queryId;
- }
-
- public void clearAdapter() {
- mAdapter = null;
- mFilter = null;
- if (mPopup != null) {
- mPopup.dismiss();
- mPopup.setAdapter(null);
- }
- }
-
- public <T extends ListAdapter & Filterable> void setAdapter(T adapter) {
- ensurePopup();
- mPopup.setAdapter(adapter);
- mAdapter = adapter;
- if (adapter != null) {
- mFilter = adapter.getFilter();
- mFilter.filter(mText, this);
- } else {
- mFilter = null;
- }
- resetRect();
- }
-
- public void resetRect() {
- ensurePopup();
- int left = mWebView.contentToViewX(mWebView.mEditTextContentBounds.left);
- int right = mWebView.contentToViewX(mWebView.mEditTextContentBounds.right);
- int width = right - left;
- mPopup.setWidth(width);
-
- int bottom = mWebView.contentToViewY(mWebView.mEditTextContentBounds.bottom);
- int top = mWebView.contentToViewY(mWebView.mEditTextContentBounds.top);
- int height = bottom - top;
-
- AbsoluteLayout.LayoutParams lp =
- (AbsoluteLayout.LayoutParams) mAnchor.getLayoutParams();
- boolean needsUpdate = false;
- if (null == lp) {
- lp = new AbsoluteLayout.LayoutParams(width, height, left, top);
- } else {
- if ((lp.x != left) || (lp.y != top) || (lp.width != width)
- || (lp.height != height)) {
- needsUpdate = true;
- lp.x = left;
- lp.y = top;
- lp.width = width;
- lp.height = height;
- }
- }
- if (needsUpdate) {
- mAnchor.setLayoutParams(lp);
- }
- if (mPopup.isShowing()) {
- mPopup.show(); // update its position
- }
- }
-
- // AdapterView.OnItemClickListener implementation
- @Override
- public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
- if (mPopup == null) {
- return;
- }
- if (id == 0 && position == 0 && mInputConnection.getIsAutoFillable()) {
- mText = "";
- pushTextToInputConnection();
- // Blank out the text box while we wait for WebCore to fill the form.
- if (mIsAutoFillProfileSet) {
- // Call a webview method to tell WebCore to autofill the form.
- mWebView.autoFillForm(mQueryId);
- } else {
- // There is no autofill profile setup yet and the user has
- // elected to try and set one up. Call through to the
- // embedder to action that.
- WebChromeClient webChromeClient = mWebView.getWebChromeClient();
- if (webChromeClient != null) {
- webChromeClient.setupAutoFill(
- mHandler.obtainMessage(AUTOFILL_FORM));
- }
- }
- } else {
- Object selectedItem;
- if (position < 0) {
- selectedItem = mPopup.getSelectedItem();
- } else {
- selectedItem = mAdapter.getItem(position);
- }
- if (selectedItem != null) {
- setText(mFilter.convertResultToString(selectedItem));
- pushTextToInputConnection();
- }
- }
- mPopup.dismiss();
- }
-
- public void setIsAutoFillProfileSet(boolean isAutoFillProfileSet) {
- mIsAutoFillProfileSet = isAutoFillProfileSet;
- }
-
- private void pushTextToInputConnection() {
- Editable oldText = mInputConnection.getEditable();
- mInputConnection.setSelection(0, oldText.length());
- mInputConnection.replaceSelection(mText);
- mInputConnection.setSelection(mText.length(), mText.length());
- }
-
- @Override
- public void onFilterComplete(int count) {
- ensurePopup();
- boolean showDropDown = (count > 0) &&
- (mInputConnection.getIsAutoFillable() || mText.length() > 0);
- if (showDropDown) {
- if (!mPopup.isShowing()) {
- // Make sure the list does not obscure the IME when shown for the first time.
- mPopup.setInputMethodMode(ListPopupWindow.INPUT_METHOD_NEEDED);
- }
- mPopup.show();
- mPopup.getListView().setOverScrollMode(View.OVER_SCROLL_ALWAYS);
- } else {
- mPopup.dismiss();
- }
- }
-
- @Override
- public void onDismiss() {
- mWebView.getWebView().removeView(mAnchor);
- }
-
- private void ensurePopup() {
- if (mPopup == null) {
- mPopup = new ListPopupWindow(mWebView.getContext());
- mAnchor = new AnchorView(mWebView.getContext());
- mWebView.getWebView().addView(mAnchor);
- mPopup.setOnItemClickListener(this);
- mPopup.setAnchorView(mAnchor);
- mPopup.setPromptPosition(ListPopupWindow.POSITION_PROMPT_BELOW);
- } else if (mWebView.getWebView().indexOfChild(mAnchor) < 0) {
- mWebView.getWebView().addView(mAnchor);
- }
- }
-}
-
+++ /dev/null
-/*
- * Copyright (C) 2006 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.app.ActivityManager;
-import android.content.ComponentCallbacks;
-import android.content.Context;
-import android.content.res.AssetManager;
-import android.content.res.Configuration;
-import android.content.res.Resources;
-import android.content.res.Resources.NotFoundException;
-import android.graphics.Bitmap;
-import android.net.ParseException;
-import android.net.Uri;
-import android.net.WebAddress;
-import android.net.http.ErrorStrings;
-import android.net.http.SslCertificate;
-import android.net.http.SslError;
-import android.os.Handler;
-import android.os.Message;
-import android.util.Log;
-import android.util.TypedValue;
-import android.view.Surface;
-import android.view.ViewRootImpl;
-import android.view.WindowManager;
-
-import junit.framework.Assert;
-
-import java.io.ByteArrayInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.lang.ref.WeakReference;
-import java.net.URLEncoder;
-import java.security.PrivateKey;
-import java.security.cert.CertificateFactory;
-import java.security.cert.X509Certificate;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.Map;
-import java.util.Set;
-
-import com.android.org.conscrypt.OpenSSLKey;
-import com.android.org.conscrypt.OpenSSLKeyHolder;
-
-class BrowserFrame extends Handler {
-
- private static final String LOGTAG = "webkit";
-
- /**
- * Cap the number of LoadListeners that will be instantiated, so
- * we don't blow the GREF count. Attempting to queue more than
- * this many requests will prompt an error() callback on the
- * request's LoadListener
- */
- private final static int MAX_OUTSTANDING_REQUESTS = 300;
-
- private final CallbackProxy mCallbackProxy;
- private final WebSettingsClassic mSettings;
- private final Context mContext;
- private final WebViewDatabaseClassic mDatabase;
- private final WebViewCore mWebViewCore;
- /* package */ boolean mLoadInitFromJava;
- private int mLoadType;
- private boolean mFirstLayoutDone = true;
- private boolean mCommitted = true;
- // Flag for blocking messages. This is used during destroy() so
- // that if the UI thread posts any messages after the message
- // queue has been cleared,they are ignored.
- private boolean mBlockMessages = false;
- private int mOrientation = -1;
-
- // Is this frame the main frame?
- private boolean mIsMainFrame;
-
- // Javascript interface object
- private class JSObject {
- Object object;
- boolean requireAnnotation;
-
- public JSObject(Object object, boolean requireAnnotation) {
- this.object = object;
- this.requireAnnotation = requireAnnotation;
- }
- }
-
- // Attached Javascript interfaces
- private Map<String, JSObject> mJavaScriptObjects;
- private Set<Object> mRemovedJavaScriptObjects;
-
- // Key store handler when Chromium HTTP stack is used.
- private KeyStoreHandler mKeyStoreHandler = null;
-
- // message ids
- // a message posted when a frame loading is completed
- static final int FRAME_COMPLETED = 1001;
- // orientation change message
- static final int ORIENTATION_CHANGED = 1002;
- // a message posted when the user decides the policy
- static final int POLICY_FUNCTION = 1003;
-
- // Note: need to keep these in sync with FrameLoaderTypes.h in native
- static final int FRAME_LOADTYPE_STANDARD = 0;
- static final int FRAME_LOADTYPE_BACK = 1;
- static final int FRAME_LOADTYPE_FORWARD = 2;
- static final int FRAME_LOADTYPE_INDEXEDBACKFORWARD = 3;
- static final int FRAME_LOADTYPE_RELOAD = 4;
- static final int FRAME_LOADTYPE_RELOADALLOWINGSTALEDATA = 5;
- static final int FRAME_LOADTYPE_SAME = 6;
- static final int FRAME_LOADTYPE_REDIRECT = 7;
- static final int FRAME_LOADTYPE_REPLACE = 8;
-
- // A progress threshold to switch from history Picture to live Picture
- private static final int TRANSITION_SWITCH_THRESHOLD = 75;
-
- // This is a field accessed by native code as well as package classes.
- /*package*/ int mNativeFrame;
-
- // Static instance of a JWebCoreJavaBridge to handle timer and cookie
- // requests from WebCore.
- static JWebCoreJavaBridge sJavaBridge;
-
- private static class ConfigCallback implements ComponentCallbacks {
- private final ArrayList<WeakReference<Handler>> mHandlers =
- new ArrayList<WeakReference<Handler>>();
- private final WindowManager mWindowManager;
-
- ConfigCallback(WindowManager wm) {
- mWindowManager = wm;
- }
-
- public synchronized void addHandler(Handler h) {
- // No need to ever remove a Handler. If the BrowserFrame is
- // destroyed, it will be collected and the WeakReference set to
- // null. If it happens to still be around during a configuration
- // change, the message will be ignored.
- mHandlers.add(new WeakReference<Handler>(h));
- }
-
- public void onConfigurationChanged(Configuration newConfig) {
- if (mHandlers.size() == 0) {
- return;
- }
- int orientation =
- mWindowManager.getDefaultDisplay().getOrientation();
- switch (orientation) {
- case Surface.ROTATION_90:
- orientation = 90;
- break;
- case Surface.ROTATION_180:
- orientation = 180;
- break;
- case Surface.ROTATION_270:
- orientation = -90;
- break;
- case Surface.ROTATION_0:
- orientation = 0;
- break;
- default:
- break;
- }
- synchronized (this) {
- // Create a list of handlers to remove. Go ahead and make it
- // the same size to avoid resizing.
- ArrayList<WeakReference> handlersToRemove =
- new ArrayList<WeakReference>(mHandlers.size());
- for (WeakReference<Handler> wh : mHandlers) {
- Handler h = wh.get();
- if (h != null) {
- h.sendMessage(h.obtainMessage(ORIENTATION_CHANGED,
- orientation, 0));
- } else {
- handlersToRemove.add(wh);
- }
- }
- // Now remove all the null references.
- for (WeakReference weak : handlersToRemove) {
- mHandlers.remove(weak);
- }
- }
- }
-
- public void onLowMemory() {}
- }
- static ConfigCallback sConfigCallback;
-
- /**
- * Create a new BrowserFrame to be used in an application.
- * @param context An application context to use when retrieving assets.
- * @param w A WebViewCore used as the view for this frame.
- * @param proxy A CallbackProxy for posting messages to the UI thread and
- * querying a client for information.
- * @param settings A WebSettings object that holds all settings.
- * XXX: Called by WebCore thread.
- */
- public BrowserFrame(Context context, WebViewCore w, CallbackProxy proxy,
- WebSettingsClassic settings, Map<String, Object> javascriptInterfaces) {
-
- Context appContext = context.getApplicationContext();
-
- // Create a global JWebCoreJavaBridge to handle timers and
- // cookies in the WebCore thread.
- if (sJavaBridge == null) {
- sJavaBridge = new JWebCoreJavaBridge();
- // set WebCore native cache size
- ActivityManager am = (ActivityManager) context
- .getSystemService(Context.ACTIVITY_SERVICE);
- if (am.getMemoryClass() > 16) {
- sJavaBridge.setCacheSize(8 * 1024 * 1024);
- } else {
- sJavaBridge.setCacheSize(4 * 1024 * 1024);
- }
- // create CookieSyncManager with current Context
- CookieSyncManager.createInstance(appContext);
- // create PluginManager with current Context
- PluginManager.getInstance(appContext);
- }
-
- if (sConfigCallback == null) {
- sConfigCallback = new ConfigCallback(
- (WindowManager) appContext.getSystemService(
- Context.WINDOW_SERVICE));
- ViewRootImpl.addConfigCallback(sConfigCallback);
- }
- sConfigCallback.addHandler(this);
-
- mJavaScriptObjects = new HashMap<String, JSObject>();
- addJavaScriptObjects(javascriptInterfaces);
- mRemovedJavaScriptObjects = new HashSet<Object>();
-
- mSettings = settings;
- mContext = context;
- mCallbackProxy = proxy;
- mDatabase = WebViewDatabaseClassic.getInstance(appContext);
- mWebViewCore = w;
-
- AssetManager am = context.getAssets();
- nativeCreateFrame(w, am, proxy.getBackForwardList());
-
- if (DebugFlags.BROWSER_FRAME) {
- Log.v(LOGTAG, "BrowserFrame constructor: this=" + this);
- }
- }
-
- /**
- * Load a url from the network or the filesystem into the main frame.
- * Following the same behaviour as Safari, javascript: URLs are not passed
- * to the main frame, instead they are evaluated immediately.
- * @param url The url to load.
- * @param extraHeaders The extra headers sent with this url. This should not
- * include the common headers like "user-agent". If it does, it
- * will be replaced by the intrinsic value of the WebView.
- */
- public void loadUrl(String url, Map<String, String> extraHeaders) {
- mLoadInitFromJava = true;
- if (URLUtil.isJavaScriptUrl(url)) {
- // strip off the scheme and evaluate the string
- stringByEvaluatingJavaScriptFromString(
- url.substring("javascript:".length()));
- } else {
- nativeLoadUrl(url, extraHeaders);
- }
- mLoadInitFromJava = false;
- }
-
- /**
- * Load a url with "POST" method from the network into the main frame.
- * @param url The url to load.
- * @param data The data for POST request.
- */
- public void postUrl(String url, byte[] data) {
- mLoadInitFromJava = true;
- nativePostUrl(url, data);
- mLoadInitFromJava = false;
- }
-
- /**
- * Load the content as if it was loaded by the provided base URL. The
- * historyUrl is used as the history entry for the load data.
- *
- * @param baseUrl Base URL used to resolve relative paths in the content
- * @param data Content to render in the browser
- * @param mimeType Mimetype of the data being passed in
- * @param encoding Character set encoding of the provided data.
- * @param historyUrl URL to use as the history entry.
- */
- public void loadData(String baseUrl, String data, String mimeType,
- String encoding, String historyUrl) {
- mLoadInitFromJava = true;
- if (historyUrl == null || historyUrl.length() == 0) {
- historyUrl = "about:blank";
- }
- if (data == null) {
- data = "";
- }
-
- // Setup defaults for missing values. These defaults where taken from
- // WebKit's WebFrame.mm
- if (baseUrl == null || baseUrl.length() == 0) {
- baseUrl = "about:blank";
- }
- if (mimeType == null || mimeType.length() == 0) {
- mimeType = "text/html";
- }
- nativeLoadData(baseUrl, data, mimeType, encoding, historyUrl);
- mLoadInitFromJava = false;
- }
-
- /**
- * Saves the contents of the frame as a web archive.
- *
- * @param basename The filename where the archive should be placed.
- * @param autoname If false, takes filename to be a file. If true, filename
- * is assumed to be a directory in which a filename will be
- * chosen according to the url of the current page.
- */
- /* package */ String saveWebArchive(String basename, boolean autoname) {
- return nativeSaveWebArchive(basename, autoname);
- }
-
- /**
- * Go back or forward the number of steps given.
- * @param steps A negative or positive number indicating the direction
- * and number of steps to move.
- */
- public void goBackOrForward(int steps) {
- mLoadInitFromJava = true;
- nativeGoBackOrForward(steps);
- mLoadInitFromJava = false;
- }
-
- /**
- * native callback
- * Report an error to an activity.
- * @param errorCode The HTTP error code.
- * @param description Optional human-readable description. If no description
- * is given, we'll use a standard localized error message.
- * @param failingUrl The URL that was being loaded when the error occurred.
- * TODO: Report all errors including resource errors but include some kind
- * of domain identifier. Change errorCode to an enum for a cleaner
- * interface.
- */
- private void reportError(int errorCode, String description, String failingUrl) {
- // As this is called for the main resource and loading will be stopped
- // after, reset the state variables.
- resetLoadingStates();
- if (description == null || description.isEmpty()) {
- description = ErrorStrings.getString(errorCode, mContext);
- }
- mCallbackProxy.onReceivedError(errorCode, description, failingUrl);
- }
-
- private void resetLoadingStates() {
- mCommitted = true;
- mFirstLayoutDone = true;
- }
-
- /* package */boolean committed() {
- return mCommitted;
- }
-
- /* package */boolean firstLayoutDone() {
- return mFirstLayoutDone;
- }
-
- /* package */int loadType() {
- return mLoadType;
- }
-
- /* package */void didFirstLayout() {
- if (!mFirstLayoutDone) {
- mFirstLayoutDone = true;
- // ensure {@link WebViewCore#webkitDraw} is called as we were
- // blocking the update in {@link #loadStarted}
- mWebViewCore.contentDraw();
- }
- }
-
- /**
- * native callback
- * Indicates the beginning of a new load.
- * This method will be called once for the main frame.
- */
- private void loadStarted(String url, Bitmap favicon, int loadType,
- boolean isMainFrame) {
- mIsMainFrame = isMainFrame;
-
- if (isMainFrame || loadType == FRAME_LOADTYPE_STANDARD) {
- mLoadType = loadType;
-
- if (isMainFrame) {
- // Call onPageStarted for main frames.
- mCallbackProxy.onPageStarted(url, favicon);
- // as didFirstLayout() is only called for the main frame, reset
- // mFirstLayoutDone only for the main frames
- mFirstLayoutDone = false;
- mCommitted = false;
- // remove pending draw to block update until mFirstLayoutDone is
- // set to true in didFirstLayout()
- mWebViewCore.clearContent();
- mWebViewCore.removeMessages(WebViewCore.EventHub.WEBKIT_DRAW);
- }
- }
- }
-
- @SuppressWarnings("unused")
- private void saveFormData(HashMap<String, String> data) {
- if (mSettings.getSaveFormData()) {
- final WebHistoryItem h = mCallbackProxy.getBackForwardList()
- .getCurrentItem();
- if (h != null) {
- String url = WebTextView.urlForAutoCompleteData(h.getUrl());
- if (url != null) {
- mDatabase.setFormData(url, data);
- }
- }
- }
- }
-
- @SuppressWarnings("unused")
- private boolean shouldSaveFormData() {
- if (mSettings.getSaveFormData()) {
- final WebHistoryItem h = mCallbackProxy.getBackForwardList()
- .getCurrentItem();
- return h != null && h.getUrl() != null;
- }
- return false;
- }
-
- /**
- * native callback
- * Indicates the WebKit has committed to the new load
- */
- private void transitionToCommitted(int loadType, boolean isMainFrame) {
- // loadType is not used yet
- if (isMainFrame) {
- mCommitted = true;
- mWebViewCore.getWebViewClassic().mViewManager.postResetStateAll();
- }
- }
-
- /**
- * native callback
- * <p>
- * Indicates the end of a new load.
- * This method will be called once for the main frame.
- */
- private void loadFinished(String url, int loadType, boolean isMainFrame) {
- // mIsMainFrame and isMainFrame are better be equal!!!
-
- if (isMainFrame || loadType == FRAME_LOADTYPE_STANDARD) {
- if (isMainFrame) {
- resetLoadingStates();
- mCallbackProxy.switchOutDrawHistory();
- mCallbackProxy.onPageFinished(url);
- }
- }
- }
-
- /**
- * Destroy all native components of the BrowserFrame.
- */
- public void destroy() {
- nativeDestroyFrame();
- mBlockMessages = true;
- removeCallbacksAndMessages(null);
- }
-
- /**
- * Handle messages posted to us.
- * @param msg The message to handle.
- */
- @Override
- public void handleMessage(Message msg) {
- if (mBlockMessages) {
- return;
- }
- switch (msg.what) {
- case FRAME_COMPLETED: {
- if (mSettings.getSavePassword() && hasPasswordField()) {
- WebHistoryItem item = mCallbackProxy.getBackForwardList()
- .getCurrentItem();
- if (item != null) {
- WebAddress uri = new WebAddress(item.getUrl());
- String schemePlusHost = uri.getScheme() + uri.getHost();
- String[] up =
- WebViewDatabaseClassic.getInstance(mContext)
- .getUsernamePassword(schemePlusHost);
- if (up != null && up[0] != null) {
- setUsernamePassword(up[0], up[1]);
- }
- }
- }
- break;
- }
-
- case POLICY_FUNCTION: {
- nativeCallPolicyFunction(msg.arg1, msg.arg2);
- break;
- }
-
- case ORIENTATION_CHANGED: {
- if (mOrientation != msg.arg1) {
- mOrientation = msg.arg1;
- nativeOrientationChanged(msg.arg1);
- }
- break;
- }
-
- default:
- break;
- }
- }
-
- /**
- * Punch-through for WebCore to set the document
- * title. Inform the Activity of the new title.
- * @param title The new title of the document.
- */
- private void setTitle(String title) {
- // FIXME: The activity must call getTitle (a native method) to get the
- // title. We should try and cache the title if we can also keep it in
- // sync with the document.
- mCallbackProxy.onReceivedTitle(title);
- }
-
- /**
- * Retrieves the render tree of this frame and puts it as the object for
- * the message and sends the message.
- * @param callback the message to use to send the render tree
- */
- public void externalRepresentation(Message callback) {
- callback.obj = externalRepresentation();;
- callback.sendToTarget();
- }
-
- /**
- * Return the render tree as a string
- */
- private native String externalRepresentation();
-
- /**
- * Retrieves the visual text of the frames, puts it as the object for
- * the message and sends the message.
- * @param callback the message to use to send the visual text
- */
- public void documentAsText(Message callback) {
- StringBuilder text = new StringBuilder();
- if (callback.arg1 != 0) {
- // Dump top frame as text.
- text.append(documentAsText());
- }
- if (callback.arg2 != 0) {
- // Dump child frames as text.
- text.append(childFramesAsText());
- }
- callback.obj = text.toString();
- callback.sendToTarget();
- }
-
- /**
- * Return the text drawn on the screen as a string
- */
- private native String documentAsText();
-
- /**
- * Return the text drawn on the child frames as a string
- */
- private native String childFramesAsText();
-
- /*
- * This method is called by WebCore to inform the frame that
- * the Javascript window object has been cleared.
- * We should re-attach any attached js interfaces.
- */
- private void windowObjectCleared(int nativeFramePointer) {
- Iterator<String> iter = mJavaScriptObjects.keySet().iterator();
- while (iter.hasNext()) {
- String interfaceName = iter.next();
- JSObject jsobject = mJavaScriptObjects.get(interfaceName);
- if (jsobject != null && jsobject.object != null) {
- nativeAddJavascriptInterface(nativeFramePointer,
- jsobject.object, interfaceName, jsobject.requireAnnotation);
- }
- }
- mRemovedJavaScriptObjects.clear();
- }
-
- /*
- * Add javascript objects to the internal list of objects. The default behavior
- * is to allow access to inherited methods (no annotation needed). This is only
- * used when js objects are passed through a constructor (via a hidden constructor).
- *
- * @TODO change the default behavior to be compatible with the public addjavascriptinterface
- */
- private void addJavaScriptObjects(Map<String, Object> javascriptInterfaces) {
-
- // TODO in a separate CL provide logic to enable annotations for API level JB_MR1 and above.
- if (javascriptInterfaces == null) return;
- Iterator<String> iter = javascriptInterfaces.keySet().iterator();
- while (iter.hasNext()) {
- String interfaceName = iter.next();
- Object object = javascriptInterfaces.get(interfaceName);
- if (object != null) {
- mJavaScriptObjects.put(interfaceName, new JSObject(object, false));
- }
- }
- }
-
- /**
- * This method is called by WebCore to check whether application
- * wants to hijack url loading
- */
- public boolean handleUrl(String url) {
- if (mLoadInitFromJava == true) {
- return false;
- }
- if (mCallbackProxy.shouldOverrideUrlLoading(url)) {
- // if the url is hijacked, reset the state of the BrowserFrame
- didFirstLayout();
- return true;
- } else {
- return false;
- }
- }
-
- public void addJavascriptInterface(Object obj, String interfaceName,
- boolean requireAnnotation) {
- assert obj != null;
- removeJavascriptInterface(interfaceName);
- mJavaScriptObjects.put(interfaceName, new JSObject(obj, requireAnnotation));
- }
-
- public void removeJavascriptInterface(String interfaceName) {
- // We keep a reference to the removed object because the native side holds only a weak
- // reference and we need to allow the object to continue to be used until the page has been
- // navigated.
- if (mJavaScriptObjects.containsKey(interfaceName)) {
- mRemovedJavaScriptObjects.add(mJavaScriptObjects.remove(interfaceName));
- }
- }
-
- /**
- * Called by JNI. Given a URI, find the associated file and return its size
- * @param uri A String representing the URI of the desired file.
- * @return int The size of the given file.
- */
- private int getFileSize(String uri) {
- int size = 0;
- try {
- InputStream stream = mContext.getContentResolver()
- .openInputStream(Uri.parse(uri));
- size = stream.available();
- stream.close();
- } catch (Exception e) {}
- return size;
- }
-
- /**
- * Called by JNI. Given a URI, a buffer, and an offset into the buffer,
- * copy the resource into buffer.
- * @param uri A String representing the URI of the desired file.
- * @param buffer The byte array to copy the data into.
- * @param offset The offet into buffer to place the data.
- * @param expectedSize The size that the buffer has allocated for this file.
- * @return int The size of the given file, or zero if it fails.
- */
- private int getFile(String uri, byte[] buffer, int offset,
- int expectedSize) {
- int size = 0;
- try {
- InputStream stream = mContext.getContentResolver()
- .openInputStream(Uri.parse(uri));
- size = stream.available();
- if (size <= expectedSize && buffer != null
- && buffer.length - offset >= size) {
- stream.read(buffer, offset, size);
- } else {
- size = 0;
- }
- stream.close();
- } catch (java.io.FileNotFoundException e) {
- Log.e(LOGTAG, "FileNotFoundException:" + e);
- size = 0;
- } catch (java.io.IOException e2) {
- Log.e(LOGTAG, "IOException: " + e2);
- size = 0;
- }
- return size;
- }
-
- /**
- * Get the InputStream for an Android resource
- * There are three different kinds of android resources:
- * - file:///android_res
- * - file:///android_asset
- * - content://
- * @param url The url to load.
- * @return An InputStream to the android resource
- */
- private InputStream inputStreamForAndroidResource(String url) {
- final String ANDROID_ASSET = URLUtil.ASSET_BASE;
- final String ANDROID_RESOURCE = URLUtil.RESOURCE_BASE;
- final String ANDROID_CONTENT = URLUtil.CONTENT_BASE;
-
- if (url.startsWith(ANDROID_RESOURCE)) {
- url = url.replaceFirst(ANDROID_RESOURCE, "");
- if (url == null || url.length() == 0) {
- Log.e(LOGTAG, "url has length 0 " + url);
- return null;
- }
- int slash = url.indexOf('/');
- int dot = url.indexOf('.', slash);
- if (slash == -1 || dot == -1) {
- Log.e(LOGTAG, "Incorrect res path: " + url);
- return null;
- }
- String subClassName = url.substring(0, slash);
- String fieldName = url.substring(slash + 1, dot);
- String errorMsg = null;
- try {
- final Class<?> d = mContext.getApplicationContext()
- .getClassLoader().loadClass(
- mContext.getPackageName() + ".R$"
- + subClassName);
- final java.lang.reflect.Field field = d.getField(fieldName);
- final int id = field.getInt(null);
- TypedValue value = new TypedValue();
- mContext.getResources().getValue(id, value, true);
- if (value.type == TypedValue.TYPE_STRING) {
- return mContext.getAssets().openNonAsset(
- value.assetCookie, value.string.toString(),
- AssetManager.ACCESS_STREAMING);
- } else {
- // Old stack only supports TYPE_STRING for res files
- Log.e(LOGTAG, "not of type string: " + url);
- return null;
- }
- } catch (Exception e) {
- Log.e(LOGTAG, "Exception: " + url);
- return null;
- }
- } else if (url.startsWith(ANDROID_ASSET)) {
- String assetUrl = url.replaceFirst(ANDROID_ASSET, "");
- try {
- AssetManager assets = mContext.getAssets();
- Uri uri = Uri.parse(assetUrl);
- return assets.open(uri.getPath(), AssetManager.ACCESS_STREAMING);
- } catch (IOException e) {
- return null;
- } catch (Exception e) {
- Log.w(LOGTAG, "Problem loading url: " + url, e);
- return null;
- }
- } else if (mSettings.getAllowContentAccess() &&
- url.startsWith(ANDROID_CONTENT)) {
- try {
- // Strip off MIME type. If we don't do this, we can fail to
- // load Gmail attachments, because the URL being loaded doesn't
- // exactly match the URL we have permission to read.
- int mimeIndex = url.lastIndexOf('?');
- if (mimeIndex != -1) {
- url = url.substring(0, mimeIndex);
- }
- Uri uri = Uri.parse(url);
- return mContext.getContentResolver().openInputStream(uri);
- } catch (Exception e) {
- Log.e(LOGTAG, "Exception: " + url);
- return null;
- }
- } else {
- return null;
- }
- }
-
- /**
- * If this looks like a POST request (form submission) containing a username
- * and password, give the user the option of saving them. Will either do
- * nothing, or block until the UI interaction is complete.
- *
- * Called directly by WebKit.
- *
- * @param postData The data about to be sent as the body of a POST request.
- * @param username The username entered by the user (sniffed from the DOM).
- * @param password The password entered by the user (sniffed from the DOM).
- */
- private void maybeSavePassword(
- byte[] postData, String username, String password) {
- if (postData == null
- || username == null || username.isEmpty()
- || password == null || password.isEmpty()) {
- return; // No password to save.
- }
-
- if (!mSettings.getSavePassword()) {
- return; // User doesn't want to save passwords.
- }
-
- try {
- if (DebugFlags.BROWSER_FRAME) {
- Assert.assertNotNull(mCallbackProxy.getBackForwardList()
- .getCurrentItem());
- }
- WebAddress uri = new WebAddress(mCallbackProxy
- .getBackForwardList().getCurrentItem().getUrl());
- String schemePlusHost = uri.getScheme() + uri.getHost();
- // Check to see if the username & password appear in
- // the post data (there could be another form on the
- // page and that was posted instead.
- String postString = new String(postData);
- if (postString.contains(URLEncoder.encode(username)) &&
- postString.contains(URLEncoder.encode(password))) {
- String[] saved = mDatabase.getUsernamePassword(
- schemePlusHost);
- if (saved != null) {
- // null username implies that user has chosen not to
- // save password
- if (saved[0] != null) {
- // non-null username implies that user has
- // chosen to save password, so update the
- // recorded password
- mDatabase.setUsernamePassword(schemePlusHost, username, password);
- }
- } else {
- // CallbackProxy will handle creating the resume
- // message
- mCallbackProxy.onSavePassword(schemePlusHost, username,
- password, null);
- }
- }
- } catch (ParseException ex) {
- // if it is bad uri, don't save its password
- }
- }
-
- // Called by jni from the chrome network stack.
- private WebResourceResponse shouldInterceptRequest(String url) {
- InputStream androidResource = inputStreamForAndroidResource(url);
- if (androidResource != null) {
- return new WebResourceResponse(null, null, androidResource);
- }
-
- // Note that we check this after looking for an android_asset or
- // android_res URL, as we allow those even if file access is disabled.
- if (!mSettings.getAllowFileAccess() && url.startsWith("file://")) {
- return new WebResourceResponse(null, null, null);
- }
-
- WebResourceResponse response = mCallbackProxy.shouldInterceptRequest(url);
- if (response == null && "browser:incognito".equals(url)) {
- try {
- Resources res = mContext.getResources();
- InputStream ins = res.openRawResource(
- com.android.internal.R.raw.incognito_mode_start_page);
- response = new WebResourceResponse("text/html", "utf8", ins);
- } catch (NotFoundException ex) {
- // This shouldn't happen, but try and gracefully handle it jic
- Log.w(LOGTAG, "Failed opening raw.incognito_mode_start_page", ex);
- }
- }
- return response;
- }
-
- /**
- * Set the progress for the browser activity. Called by native code.
- * Uses a delay so it does not happen too often.
- * @param newProgress An int between zero and one hundred representing
- * the current progress percentage of loading the page.
- */
- private void setProgress(int newProgress) {
- mCallbackProxy.onProgressChanged(newProgress);
- if (newProgress == 100) {
- sendMessageDelayed(obtainMessage(FRAME_COMPLETED), 100);
- }
- // FIXME: Need to figure out a better way to switch out of the history
- // drawing mode. Maybe we can somehow compare the history picture with
- // the current picture, and switch when it contains more content.
- if (mFirstLayoutDone && newProgress > TRANSITION_SWITCH_THRESHOLD) {
- mCallbackProxy.switchOutDrawHistory();
- }
- }
-
- /**
- * Send the icon to the activity for display.
- * @param icon A Bitmap representing a page's favicon.
- */
- private void didReceiveIcon(Bitmap icon) {
- mCallbackProxy.onReceivedIcon(icon);
- }
-
- // Called by JNI when an apple-touch-icon attribute was found.
- private void didReceiveTouchIconUrl(String url, boolean precomposed) {
- mCallbackProxy.onReceivedTouchIconUrl(url, precomposed);
- }
-
- /**
- * Request a new window from the client.
- * @return The BrowserFrame object stored in the new WebView.
- */
- private BrowserFrame createWindow(boolean dialog, boolean userGesture) {
- return mCallbackProxy.createWindow(dialog, userGesture);
- }
-
- /**
- * Try to focus this WebView.
- */
- private void requestFocus() {
- mCallbackProxy.onRequestFocus();
- }
-
- /**
- * Close this frame and window.
- */
- private void closeWindow(WebViewCore w) {
- mCallbackProxy.onCloseWindow(w.getWebViewClassic());
- }
-
- // XXX: Must match PolicyAction in FrameLoaderTypes.h in webcore
- static final int POLICY_USE = 0;
- static final int POLICY_IGNORE = 2;
-
- private void decidePolicyForFormResubmission(int policyFunction) {
- Message dontResend = obtainMessage(POLICY_FUNCTION, policyFunction,
- POLICY_IGNORE);
- Message resend = obtainMessage(POLICY_FUNCTION, policyFunction,
- POLICY_USE);
- mCallbackProxy.onFormResubmission(dontResend, resend);
- }
-
- /**
- * Tell the activity to update its global history.
- */
- private void updateVisitedHistory(String url, boolean isReload) {
- mCallbackProxy.doUpdateVisitedHistory(url, isReload);
- }
-
- /**
- * Get the CallbackProxy for sending messages to the UI thread.
- */
- /* package */ CallbackProxy getCallbackProxy() {
- return mCallbackProxy;
- }
-
- /**
- * Returns the User Agent used by this frame
- */
- String getUserAgentString() {
- return mSettings.getUserAgentString();
- }
-
- // These ids need to be in sync with enum rawResId in PlatformBridge.h
- private static final int NODOMAIN = 1;
- private static final int LOADERROR = 2;
- /* package */ static final int DRAWABLEDIR = 3;
- private static final int FILE_UPLOAD_LABEL = 4;
- private static final int RESET_LABEL = 5;
- private static final int SUBMIT_LABEL = 6;
- private static final int FILE_UPLOAD_NO_FILE_CHOSEN = 7;
-
- private String getRawResFilename(int id) {
- return getRawResFilename(id, mContext);
- }
- /* package */ static String getRawResFilename(int id, Context context) {
- int resid;
- switch (id) {
- case NODOMAIN:
- resid = com.android.internal.R.raw.nodomain;
- break;
-
- case LOADERROR:
- resid = com.android.internal.R.raw.loaderror;
- break;
-
- case DRAWABLEDIR:
- // use one known resource to find the drawable directory
- resid = com.android.internal.R.drawable.btn_check_off;
- break;
-
- case FILE_UPLOAD_LABEL:
- return context.getResources().getString(
- com.android.internal.R.string.upload_file);
-
- case RESET_LABEL:
- return context.getResources().getString(
- com.android.internal.R.string.reset);
-
- case SUBMIT_LABEL:
- return context.getResources().getString(
- com.android.internal.R.string.submit);
-
- case FILE_UPLOAD_NO_FILE_CHOSEN:
- return context.getResources().getString(
- com.android.internal.R.string.no_file_chosen);
-
- default:
- Log.e(LOGTAG, "getRawResFilename got incompatible resource ID");
- return "";
- }
- TypedValue value = new TypedValue();
- context.getResources().getValue(resid, value, true);
- if (id == DRAWABLEDIR) {
- String path = value.string.toString();
- int index = path.lastIndexOf('/');
- if (index < 0) {
- Log.e(LOGTAG, "Can't find drawable directory.");
- return "";
- }
- return path.substring(0, index + 1);
- }
- return value.string.toString();
- }
-
- private float density() {
- return WebViewCore.getFixedDisplayDensity(mContext);
- }
-
- /**
- * Called by JNI when the native HTTP stack gets an authentication request.
- *
- * We delegate the request to CallbackProxy, and route its response to
- * {@link #nativeAuthenticationProceed(int, String, String)} or
- * {@link #nativeAuthenticationCancel(int)}.
- *
- * We don't care what thread the callback is invoked on. All threading is
- * handled on the C++ side, because the WebKit thread may be blocked on a
- * synchronous call and unable to pump our MessageQueue.
- */
- private void didReceiveAuthenticationChallenge(
- final int handle, String host, String realm, final boolean useCachedCredentials,
- final boolean suppressDialog) {
-
- HttpAuthHandler handler = new HttpAuthHandler() {
-
- @Override
- public boolean useHttpAuthUsernamePassword() {
- return useCachedCredentials;
- }
-
- @Override
- public void proceed(String username, String password) {
- nativeAuthenticationProceed(handle, username, password);
- }
-
- @Override
- public void cancel() {
- nativeAuthenticationCancel(handle);
- }
-
- @Override
- public boolean suppressDialog() {
- return suppressDialog;
- }
- };
- mCallbackProxy.onReceivedHttpAuthRequest(handler, host, realm);
- }
-
- /**
- * Called by JNI when the Chromium HTTP stack gets an invalid certificate chain.
- *
- * We delegate the request to CallbackProxy, and route its response to
- * {@link #nativeSslCertErrorProceed(int)} or
- * {@link #nativeSslCertErrorCancel(int, int)}.
- */
- private void reportSslCertError(final int handle, final int certError, byte certDER[],
- String url) {
- final SslError sslError;
- try {
- CertificateFactory cf = CertificateFactory.getInstance("X.509");
- X509Certificate cert = (X509Certificate) cf.generateCertificate(
- new ByteArrayInputStream(certDER));
- SslCertificate sslCert = new SslCertificate(cert);
- sslError = SslError.SslErrorFromChromiumErrorCode(certError, sslCert, url);
- } catch (Exception e) {
- // Can't get the certificate, not much to do.
- Log.e(LOGTAG, "Can't get the certificate from WebKit, canceling");
- nativeSslCertErrorCancel(handle, certError);
- return;
- }
-
- if (SslCertLookupTable.getInstance().isAllowed(sslError)) {
- nativeSslCertErrorProceed(handle);
- mCallbackProxy.onProceededAfterSslError(sslError);
- return;
- }
-
- SslErrorHandler handler = new SslErrorHandler() {
- @Override
- public void proceed() {
- SslCertLookupTable.getInstance().setIsAllowed(sslError);
- post(new Runnable() {
- public void run() {
- nativeSslCertErrorProceed(handle);
- }
- });
- }
- @Override
- public void cancel() {
- post(new Runnable() {
- public void run() {
- nativeSslCertErrorCancel(handle, certError);
- }
- });
- }
- };
- mCallbackProxy.onReceivedSslError(handler, sslError);
- }
-
- /**
- * Called by JNI when the native HTTPS stack gets a client
- * certificate request.
- *
- * We delegate the request to CallbackProxy, and route its response to
- * {@link #nativeSslClientCert(int, X509Certificate)}.
- */
- private void requestClientCert(int handle, String hostAndPort) {
- SslClientCertLookupTable table = SslClientCertLookupTable.getInstance();
- if (table.IsAllowed(hostAndPort)) {
- // previously allowed
- PrivateKey pkey = table.PrivateKey(hostAndPort);
- if (pkey instanceof OpenSSLKeyHolder) {
- OpenSSLKey sslKey = ((OpenSSLKeyHolder) pkey).getOpenSSLKey();
- nativeSslClientCert(handle,
- sslKey.getPkeyContext(),
- table.CertificateChain(hostAndPort));
- } else {
- nativeSslClientCert(handle,
- pkey.getEncoded(),
- table.CertificateChain(hostAndPort));
- }
- } else if (table.IsDenied(hostAndPort)) {
- // previously denied
- nativeSslClientCert(handle, 0, null);
- } else {
- // previously ignored or new
- mCallbackProxy.onReceivedClientCertRequest(
- new ClientCertRequestHandler(this, handle, hostAndPort, table), hostAndPort);
- }
- }
-
- /**
- * Called by JNI when the native HTTP stack needs to download a file.
- *
- * We delegate the request to CallbackProxy, which owns the current app's
- * DownloadListener.
- */
- private void downloadStart(String url, String userAgent,
- String contentDisposition, String mimeType, String referer, long contentLength) {
- // This will only work if the url ends with the filename
- if (mimeType.isEmpty()) {
- try {
- String extension = url.substring(url.lastIndexOf('.') + 1);
- mimeType = libcore.net.MimeUtils.guessMimeTypeFromExtension(extension);
- // MimeUtils might return null, not sure if downloadmanager is happy with that
- if (mimeType == null)
- mimeType = "";
- } catch(IndexOutOfBoundsException exception) {
- // mimeType string end with a '.', not much to do
- }
- }
- mimeType = MimeTypeMap.getSingleton().remapGenericMimeType(
- mimeType, url, contentDisposition);
-
- if (CertTool.getCertType(mimeType) != null) {
- mKeyStoreHandler = new KeyStoreHandler(mimeType);
- } else {
- mCallbackProxy.onDownloadStart(url, userAgent,
- contentDisposition, mimeType, referer, contentLength);
- }
- }
-
- /**
- * Called by JNI for Chrome HTTP stack when the Java side needs to access the data.
- */
- private void didReceiveData(byte data[], int size) {
- if (mKeyStoreHandler != null) mKeyStoreHandler.didReceiveData(data, size);
- }
-
- private void didFinishLoading() {
- if (mKeyStoreHandler != null) {
- mKeyStoreHandler.installCert(mContext);
- mKeyStoreHandler = null;
- }
- }
-
- /**
- * Called by JNI when we recieve a certificate for the page's main resource.
- * Used by the Chromium HTTP stack only.
- */
- private void setCertificate(byte cert_der[]) {
- try {
- CertificateFactory cf = CertificateFactory.getInstance("X.509");
- X509Certificate cert = (X509Certificate) cf.generateCertificate(
- new ByteArrayInputStream(cert_der));
- mCallbackProxy.onReceivedCertificate(new SslCertificate(cert));
- } catch (Exception e) {
- // Can't get the certificate, not much to do.
- Log.e(LOGTAG, "Can't get the certificate from WebKit, canceling");
- return;
- }
- }
-
- /**
- * Called by JNI when processing the X-Auto-Login header.
- */
- private void autoLogin(String realm, String account, String args) {
- mCallbackProxy.onReceivedLoginRequest(realm, account, args);
- }
-
- //==========================================================================
- // native functions
- //==========================================================================
-
- /**
- * Create a new native frame for a given WebView
- * @param w A WebView that the frame draws into.
- * @param am AssetManager to use to get assets.
- * @param list The native side will add and remove items from this list as
- * the native list changes.
- */
- private native void nativeCreateFrame(WebViewCore w, AssetManager am,
- WebBackForwardList list);
-
- /**
- * Destroy the native frame.
- */
- public native void nativeDestroyFrame();
-
- private native void nativeCallPolicyFunction(int policyFunction,
- int decision);
-
- /**
- * Reload the current main frame.
- */
- public native void reload(boolean allowStale);
-
- /**
- * Go back or forward the number of steps given.
- * @param steps A negative or positive number indicating the direction
- * and number of steps to move.
- */
- private native void nativeGoBackOrForward(int steps);
-
- /**
- * stringByEvaluatingJavaScriptFromString will execute the
- * JS passed in in the context of this browser frame.
- * @param script A javascript string to execute
- *
- * @return string result of execution or null
- */
- public native String stringByEvaluatingJavaScriptFromString(String script);
-
- /**
- * Add a javascript interface to the main frame.
- */
- private native void nativeAddJavascriptInterface(int nativeFramePointer,
- Object obj, String interfaceName, boolean requireAnnotation);
-
- public native void clearCache();
-
- /**
- * Returns false if the url is bad.
- */
- private native void nativeLoadUrl(String url, Map<String, String> headers);
-
- private native void nativePostUrl(String url, byte[] postData);
-
- private native void nativeLoadData(String baseUrl, String data,
- String mimeType, String encoding, String historyUrl);
-
- /**
- * Stop loading the current page.
- */
- public void stopLoading() {
- if (mIsMainFrame) {
- resetLoadingStates();
- }
- nativeStopLoading();
- }
-
- private native void nativeStopLoading();
-
- /**
- * Return true if the document has images.
- */
- public native boolean documentHasImages();
-
- /**
- * @return TRUE if there is a password field in the current frame
- */
- private native boolean hasPasswordField();
-
- /**
- * Get username and password in the current frame. If found, String[0] is
- * username and String[1] is password. Otherwise return NULL.
- * @return String[]
- */
- private native String[] getUsernamePassword();
-
- /**
- * Set username and password to the proper fields in the current frame
- * @param username
- * @param password
- */
- private native void setUsernamePassword(String username, String password);
-
- private native String nativeSaveWebArchive(String basename, boolean autoname);
-
- private native void nativeOrientationChanged(int orientation);
-
- private native void nativeAuthenticationProceed(int handle, String username, String password);
- private native void nativeAuthenticationCancel(int handle);
-
- private native void nativeSslCertErrorProceed(int handle);
- private native void nativeSslCertErrorCancel(int handle, int certError);
-
- native void nativeSslClientCert(int handle,
- long ctx,
- byte[][] asn1DerEncodedCertificateChain);
-
- native void nativeSslClientCert(int handle,
- byte[] pkey,
- byte[][] asn1DerEncodedCertificateChain);
-
- /**
- * Returns true when the contents of the frame is an RTL or vertical-rl
- * page. This is used for determining whether a frame should be initially
- * scrolled right-most as opposed to left-most.
- * @return true when the frame should be initially scrolled right-most
- * based on the text direction and writing mode.
- */
- /* package */ boolean getShouldStartScrolledRight() {
- return nativeGetShouldStartScrolledRight(mNativeFrame);
- }
-
- private native boolean nativeGetShouldStartScrolledRight(int nativeBrowserFrame);
-}
+++ /dev/null
-/*
- * Copyright (C) 2006 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.lang.ref.ReferenceQueue;
-import java.lang.ref.SoftReference;
-import java.util.LinkedList;
-import java.util.ListIterator;
-
-/** Utility class optimized for accumulating bytes, and then spitting
- them back out. It does not optimize for returning the result in a
- single array, though this is supported in the API. It is fastest
- if the retrieval can be done via iterating through chunks.
-*/
-class ByteArrayBuilder {
-
- private static final int DEFAULT_CAPACITY = 8192;
-
- // Global pool of chunks to be used by other ByteArrayBuilders.
- private static final LinkedList<SoftReference<Chunk>> sPool =
- new LinkedList<SoftReference<Chunk>>();
- // Reference queue for processing gc'd entries.
- private static final ReferenceQueue<Chunk> sQueue =
- new ReferenceQueue<Chunk>();
-
- private LinkedList<Chunk> mChunks;
-
- public ByteArrayBuilder() {
- mChunks = new LinkedList<Chunk>();
- }
-
- public synchronized void append(byte[] array, int offset, int length) {
- while (length > 0) {
- Chunk c = null;
- if (mChunks.isEmpty()) {
- c = obtainChunk(length);
- mChunks.addLast(c);
- } else {
- c = mChunks.getLast();
- if (c.mLength == c.mArray.length) {
- c = obtainChunk(length);
- mChunks.addLast(c);
- }
- }
- int amount = Math.min(length, c.mArray.length - c.mLength);
- System.arraycopy(array, offset, c.mArray, c.mLength, amount);
- c.mLength += amount;
- length -= amount;
- offset += amount;
- }
- }
-
- /**
- * The fastest way to retrieve the data is to iterate through the
- * chunks. This returns the first chunk. Note: this pulls the
- * chunk out of the queue. The caller must call Chunk.release() to
- * dispose of it.
- */
- public synchronized Chunk getFirstChunk() {
- if (mChunks.isEmpty()) return null;
- return mChunks.removeFirst();
- }
-
- public synchronized boolean isEmpty() {
- return mChunks.isEmpty();
- }
-
- public synchronized int getByteSize() {
- int total = 0;
- ListIterator<Chunk> it = mChunks.listIterator(0);
- while (it.hasNext()) {
- Chunk c = it.next();
- total += c.mLength;
- }
- return total;
- }
-
- public synchronized void clear() {
- Chunk c = getFirstChunk();
- while (c != null) {
- c.release();
- c = getFirstChunk();
- }
- }
-
- // Must be called with lock held on sPool.
- private void processPoolLocked() {
- while (true) {
- SoftReference<Chunk> entry = (SoftReference<Chunk>) sQueue.poll();
- if (entry == null) {
- break;
- }
- sPool.remove(entry);
- }
- }
-
- private Chunk obtainChunk(int length) {
- // Correct a small length.
- if (length < DEFAULT_CAPACITY) {
- length = DEFAULT_CAPACITY;
- }
- synchronized (sPool) {
- // Process any queued references and remove them from the pool.
- processPoolLocked();
- if (!sPool.isEmpty()) {
- Chunk c = sPool.removeFirst().get();
- // The first item may have been queued after processPoolLocked
- // so check for null.
- if (c != null) {
- return c;
- }
- }
- return new Chunk(length);
- }
- }
-
- public static class Chunk {
- public byte[] mArray;
- public int mLength;
-
- public Chunk(int length) {
- mArray = new byte[length];
- mLength = 0;
- }
-
- /**
- * Release the chunk and make it available for reuse.
- */
- public void release() {
- mLength = 0;
- synchronized (sPool) {
- // Add the chunk back to the pool as a SoftReference so it can
- // be gc'd if needed.
- sPool.offer(new SoftReference<Chunk>(this, sQueue));
- sPool.notifyAll();
- }
- }
-
- }
-}
+++ /dev/null
-/*
- * Copyright (C) 2006 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.content.Context;
-import android.net.http.Headers;
-import android.util.Log;
-
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.util.Map;
-
-
-/**
- * Manages the HTTP cache used by an application's {@link WebView} instances.
- * @deprecated Access to the HTTP cache will be removed in a future release.
- * @hide Since {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR1}
- */
-// The class CacheManager provides the persistent cache of content that is
-// received over the network. The component handles parsing of HTTP headers and
-// utilizes the relevant cache headers to determine if the content should be
-// stored and if so, how long it is valid for. Network requests are provided to
-// this component and if they can not be resolved by the cache, the HTTP headers
-// are attached, as appropriate, to the request for revalidation of content. The
-// class also manages the cache size.
-//
-// CacheManager may only be used if your activity contains a WebView.
-@Deprecated
-public final class CacheManager {
- /**
- * Represents a resource stored in the HTTP cache. Instances of this class
- * can be obtained by calling
- * {@link CacheManager#getCacheFile CacheManager.getCacheFile(String, Map<String, String>))}.
- *
- * @deprecated Access to the HTTP cache will be removed in a future release.
- */
- @Deprecated
- public static class CacheResult {
- // these fields are saved to the database
- int httpStatusCode;
- long contentLength;
- long expires;
- String expiresString;
- String localPath;
- String lastModified;
- String etag;
- String mimeType;
- String location;
- String encoding;
- String contentdisposition;
- String crossDomain;
-
- // these fields are NOT saved to the database
- InputStream inStream;
- OutputStream outStream;
- File outFile;
-
- /**
- * Gets the status code of this cache entry.
- *
- * @return the status code of this cache entry
- */
- public int getHttpStatusCode() {
- return httpStatusCode;
- }
-
- /**
- * Gets the content length of this cache entry.
- *
- * @return the content length of this cache entry
- */
- public long getContentLength() {
- return contentLength;
- }
-
- /**
- * Gets the path of the file used to store the content of this cache
- * entry, relative to the base directory of the cache. See
- * {@link CacheManager#getCacheFileBaseDir CacheManager.getCacheFileBaseDir()}.
- *
- * @return the path of the file used to store this cache entry
- */
- public String getLocalPath() {
- return localPath;
- }
-
- /**
- * Gets the expiry date of this cache entry, expressed in milliseconds
- * since midnight, January 1, 1970 UTC.
- *
- * @return the expiry date of this cache entry
- */
- public long getExpires() {
- return expires;
- }
-
- /**
- * Gets the expiry date of this cache entry, expressed as a string.
- *
- * @return the expiry date of this cache entry
- *
- */
- public String getExpiresString() {
- return expiresString;
- }
-
- /**
- * Gets the date at which this cache entry was last modified, expressed
- * as a string.
- *
- * @return the date at which this cache entry was last modified
- */
- public String getLastModified() {
- return lastModified;
- }
-
- /**
- * Gets the entity tag of this cache entry.
- *
- * @return the entity tag of this cache entry
- */
- public String getETag() {
- return etag;
- }
-
- /**
- * Gets the MIME type of this cache entry.
- *
- * @return the MIME type of this cache entry
- */
- public String getMimeType() {
- return mimeType;
- }
-
- /**
- * Gets the value of the HTTP 'Location' header with which this cache
- * entry was received.
- *
- * @return the HTTP 'Location' header for this cache entry
- */
- public String getLocation() {
- return location;
- }
-
- /**
- * Gets the encoding of this cache entry.
- *
- * @return the encoding of this cache entry
- */
- public String getEncoding() {
- return encoding;
- }
-
- /**
- * Gets the value of the HTTP 'Content-Disposition' header with which
- * this cache entry was received.
- *
- * @return the HTTP 'Content-Disposition' header for this cache entry
- *
- */
- public String getContentDisposition() {
- return contentdisposition;
- }
-
- /**
- * Gets the input stream to the content of this cache entry, to allow
- * content to be read. See
- * {@link CacheManager#getCacheFile CacheManager.getCacheFile(String, Map<String, String>)}.
- *
- * @return an input stream to the content of this cache entry
- */
- public InputStream getInputStream() {
- return inStream;
- }
-
- /**
- * Gets an output stream to the content of this cache entry, to allow
- * content to be written. See
- * {@link CacheManager#saveCacheFile CacheManager.saveCacheFile(String, CacheResult)}.
- *
- * @return an output stream to the content of this cache entry
- */
- // Note that this is always null for objects returned by getCacheFile()!
- public OutputStream getOutputStream() {
- return outStream;
- }
-
-
- /**
- * Sets an input stream to the content of this cache entry.
- *
- * @param stream an input stream to the content of this cache entry
- */
- public void setInputStream(InputStream stream) {
- this.inStream = stream;
- }
-
- /**
- * Sets the encoding of this cache entry.
- *
- * @param encoding the encoding of this cache entry
- */
- public void setEncoding(String encoding) {
- this.encoding = encoding;
- }
-
- /**
- * @hide
- */
- public void setContentLength(long contentLength) {
- this.contentLength = contentLength;
- }
- }
-
- /**
- * Gets the base directory in which the files used to store the contents of
- * cache entries are placed. See
- * {@link CacheManager.CacheResult#getLocalPath CacheManager.CacheResult.getLocalPath()}.
- *
- * @return the base directory of the cache
- * @deprecated This method no longer has any effect and always returns null.
- */
- @Deprecated
- public static File getCacheFileBaseDir() {
- return null;
- }
-
- /**
- * Gets whether the HTTP cache is disabled.
- *
- * @return true if the HTTP cache is disabled
- * @deprecated This method no longer has any effect and always returns false.
- */
- @Deprecated
- public static boolean cacheDisabled() {
- return false;
- }
-
- /**
- * Starts a cache transaction. Returns true if this is the only running
- * transaction. Otherwise, this transaction is nested inside currently
- * running transactions and false is returned.
- *
- * @return true if this is the only running transaction
- * @deprecated This method no longer has any effect and always returns false.
- */
- @Deprecated
- public static boolean startCacheTransaction() {
- return false;
- }
-
- /**
- * Ends the innermost cache transaction and returns whether this was the
- * only running transaction.
- *
- * @return true if this was the only running transaction
- * @deprecated This method no longer has any effect and always returns false.
- */
- @Deprecated
- public static boolean endCacheTransaction() {
- return false;
- }
-
- /**
- * Gets the cache entry for the specified URL, or null if none is found.
- * If a non-null value is provided for the HTTP headers map, and the cache
- * entry needs validation, appropriate headers will be added to the map.
- * The input stream of the CacheEntry object should be closed by the caller
- * when access to the underlying file is no longer required.
- *
- * @param url the URL for which a cache entry is requested
- * @param headers a map from HTTP header name to value, to be populated
- * for the returned cache entry
- * @return the cache entry for the specified URL
- * @deprecated This method no longer has any effect and always returns null.
- */
- @Deprecated
- public static CacheResult getCacheFile(String url,
- Map<String, String> headers) {
- return null;
- }
-
- /**
- * Adds a cache entry to the HTTP cache for the specicifed URL. Also closes
- * the cache entry's output stream.
- *
- * @param url the URL for which the cache entry should be added
- * @param cacheResult the cache entry to add
- * @deprecated Access to the HTTP cache will be removed in a future release.
- */
- @Deprecated
- public static void saveCacheFile(String url, CacheResult cacheResult) {
- saveCacheFile(url, 0, cacheResult);
- }
-
- static void saveCacheFile(String url, long postIdentifier,
- CacheResult cacheRet) {
- try {
- cacheRet.outStream.close();
- } catch (IOException e) {
- return;
- }
-
- // This method is exposed in the public API but the API provides no
- // way to obtain a new CacheResult object with a non-null output
- // stream ...
- // - CacheResult objects returned by getCacheFile() have a null
- // output stream.
- // - new CacheResult objects have a null output stream and no
- // setter is provided.
- // Since this method throws a null pointer exception in this case,
- // it is effectively useless from the point of view of the public
- // API.
- //
- // With the Chromium HTTP stack we continue to throw the same
- // exception for 'backwards compatibility' with the Android HTTP
- // stack.
- //
- // This method is not used from within this package, and for public API
- // use, we should already have thrown an exception above.
- assert false;
- }
-}
+++ /dev/null
-/*
- * Copyright (C) 2007 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.app.Activity;
-import android.content.ActivityNotFoundException;
-import android.content.Context;
-import android.content.Intent;
-import android.graphics.Bitmap;
-import android.net.Uri;
-import android.net.http.SslCertificate;
-import android.net.http.SslError;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.Message;
-import android.os.SystemClock;
-import android.provider.Browser;
-import android.util.Log;
-import android.view.KeyEvent;
-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;
-
-/**
- * This class is a proxy class for handling WebCore -> UI thread messaging. All
- * the callback functions are called from the WebCore thread and messages are
- * posted to the UI thread for the actual client callback.
- */
-/*
- * This class is created in the UI thread so its handler and any private classes
- * that extend Handler will operate in the UI thread.
- */
-class CallbackProxy extends Handler {
- // Logging tag
- static final String LOGTAG = "WebViewCallback";
- // Enables API callback tracing
- private static final boolean TRACE = DebugFlags.TRACE_CALLBACK;
- // Instance of WebViewClient that is the client callback.
- private volatile WebViewClient mWebViewClient;
- // Instance of WebChromeClient for handling all chrome functions.
- private volatile WebChromeClient mWebChromeClient;
- // Instance of WebViewClassic for handling UI requests.
- private final WebViewClassic mWebView;
- // Client registered callback listener for download events
- private volatile DownloadListener mDownloadListener;
- // Keep track of multiple progress updates.
- private boolean mProgressUpdatePending;
- // Keep track of the last progress amount.
- // Start with 100 to indicate it is not in load for the empty page.
- private volatile int mLatestProgress = 100;
- // Back/Forward list
- private final WebBackForwardListClassic mBackForwardList;
- // Back/Forward list client
- private volatile WebBackForwardListClient mWebBackForwardListClient;
- // Used to call startActivity during url override.
- private final Context mContext;
- // block messages flag for destroy
- private boolean mBlockMessages;
-
- // Message IDs
- private static final int PAGE_STARTED = 100;
- private static final int RECEIVED_ICON = 101;
- private static final int RECEIVED_TITLE = 102;
- private static final int OVERRIDE_URL = 103;
- private static final int AUTH_REQUEST = 104;
- private static final int SSL_ERROR = 105;
- private static final int PROGRESS = 106;
- private static final int UPDATE_VISITED = 107;
- private static final int LOAD_RESOURCE = 108;
- private static final int CREATE_WINDOW = 109;
- private static final int CLOSE_WINDOW = 110;
- private static final int SAVE_PASSWORD = 111;
- private static final int JS_DIALOG = 112;
- private static final int ASYNC_KEYEVENTS = 116;
- private static final int DOWNLOAD_FILE = 118;
- private static final int REPORT_ERROR = 119;
- private static final int RESEND_POST_DATA = 120;
- private static final int PAGE_FINISHED = 121;
- private static final int REQUEST_FOCUS = 122;
- private static final int SCALE_CHANGED = 123;
- private static final int RECEIVED_CERTIFICATE = 124;
- private static final int SWITCH_OUT_HISTORY = 125;
- private static final int EXCEEDED_DATABASE_QUOTA = 126;
- private static final int REACHED_APPCACHE_MAXSIZE = 127;
- private static final int JS_TIMEOUT = 128;
- private static final int ADD_MESSAGE_TO_CONSOLE = 129;
- private static final int GEOLOCATION_PERMISSIONS_SHOW_PROMPT = 130;
- private static final int GEOLOCATION_PERMISSIONS_HIDE_PROMPT = 131;
- private static final int RECEIVED_TOUCH_ICON_URL = 132;
- private static final int GET_VISITED_HISTORY = 133;
- private static final int OPEN_FILE_CHOOSER = 134;
- private static final int ADD_HISTORY_ITEM = 135;
- private static final int HISTORY_INDEX_CHANGED = 136;
- private static final int AUTH_CREDENTIALS = 137;
- private static final int AUTO_LOGIN = 140;
- private static final int CLIENT_CERT_REQUEST = 141;
- private static final int PROCEEDED_AFTER_SSL_ERROR = 144;
-
- // Message triggered by the client to resume execution
- private static final int NOTIFY = 200;
-
- // Result transportation object for returning results across thread
- // boundaries.
- private static class ResultTransport<E> {
- // Private result object
- private E mResult;
-
- public ResultTransport(E defaultResult) {
- mResult = defaultResult;
- }
-
- public synchronized void setResult(E result) {
- mResult = result;
- }
-
- public synchronized E getResult() {
- return mResult;
- }
- }
-
- private class JsResultReceiver implements JsResult.ResultReceiver {
- // This prevents a user from interacting with the result before WebCore is
- // ready to handle it.
- private boolean mReady;
- // Tells us if the user tried to confirm or cancel the result before WebCore
- // is ready.
- private boolean mTriedToNotifyBeforeReady;
-
- public JsPromptResult mJsResult = new JsPromptResult(this);
-
- final void setReady() {
- mReady = true;
- if (mTriedToNotifyBeforeReady) {
- notifyCallbackProxy();
- }
- }
-
- /* Wake up the WebCore thread. */
- @Override
- public void onJsResultComplete(JsResult result) {
- if (mReady) {
- notifyCallbackProxy();
- } else {
- mTriedToNotifyBeforeReady = true;
- }
- }
-
- private void notifyCallbackProxy() {
- synchronized (CallbackProxy.this) {
- CallbackProxy.this.notify();
- }
- }
-}
-
- /**
- * Construct a new CallbackProxy.
- */
- public CallbackProxy(Context context, WebViewClassic w) {
- // Used to start a default activity.
- mContext = context;
- mWebView = w;
- mBackForwardList = new WebBackForwardListClassic(this);
- }
-
- protected synchronized void blockMessages() {
- mBlockMessages = true;
- }
-
- protected synchronized boolean messagesBlocked() {
- return mBlockMessages;
- }
-
- protected void shutdown() {
- removeCallbacksAndMessages(null);
- setWebViewClient(null);
- setWebChromeClient(null);
- }
-
- /**
- * Set the WebViewClient.
- * @param client An implementation of WebViewClient.
- */
- public void setWebViewClient(WebViewClient client) {
- mWebViewClient = client;
- }
-
- /**
- * Get the WebViewClient.
- * @return the current WebViewClient instance.
- */
- public WebViewClient getWebViewClient() {
- return mWebViewClient;
- }
-
- /**
- * Set the WebChromeClient.
- * @param client An implementation of WebChromeClient.
- */
- public void setWebChromeClient(WebChromeClient client) {
- mWebChromeClient = client;
- }
-
- /**
- * Get the WebChromeClient.
- * @return the current WebChromeClient instance.
- */
- public WebChromeClient getWebChromeClient() {
- return mWebChromeClient;
- }
-
- /**
- * Set the client DownloadListener.
- * @param client An implementation of DownloadListener.
- */
- public void setDownloadListener(DownloadListener client) {
- mDownloadListener = client;
- }
-
- /**
- * Get the Back/Forward list to return to the user or to update the cached
- * history list.
- */
- public WebBackForwardListClassic getBackForwardList() {
- return mBackForwardList;
- }
-
- void setWebBackForwardListClient(WebBackForwardListClient client) {
- mWebBackForwardListClient = client;
- }
-
- WebBackForwardListClient getWebBackForwardListClient() {
- return mWebBackForwardListClient;
- }
-
- /**
- * Called by the UI side. Calling overrideUrlLoading from the WebCore
- * side will post a message to call this method.
- */
- public boolean uiOverrideUrlLoading(String overrideUrl) {
- if (overrideUrl == null || overrideUrl.length() == 0) {
- return false;
- }
- boolean override = false;
- if (mWebViewClient != null) {
- if (TRACE) Log.d(LOGTAG, "shouldOverrideUrlLoading=" + overrideUrl);
- override = mWebViewClient.shouldOverrideUrlLoading(mWebView.getWebView(),
- overrideUrl);
- } else {
- Intent intent = new Intent(Intent.ACTION_VIEW,
- Uri.parse(overrideUrl));
- intent.addCategory(Intent.CATEGORY_BROWSABLE);
- // If another application is running a WebView and launches the
- // Browser through this Intent, we want to reuse the same window if
- // possible.
- intent.putExtra(Browser.EXTRA_APPLICATION_ID,
- mContext.getPackageName());
- try {
- mContext.startActivity(intent);
- override = true;
- } catch (ActivityNotFoundException ex) {
- // If no application can handle the URL, assume that the
- // browser can handle it.
- }
- }
- return override;
- }
-
- /**
- * Called by UI side.
- */
- public boolean uiOverrideKeyEvent(KeyEvent event) {
- if (mWebViewClient != null) {
- return mWebViewClient.shouldOverrideKeyEvent(mWebView.getWebView(), event);
- }
- return false;
- }
-
- @Override
- public void handleMessage(Message msg) {
- // We don't have to do synchronization because this function operates
- // in the UI thread. The WebViewClient and WebChromeClient functions
- // that check for a non-null callback are ok because java ensures atomic
- // 32-bit reads and writes.
- if (messagesBlocked()) {
- synchronized (this) {
- notify();
- }
- return;
- }
- switch (msg.what) {
- case PAGE_STARTED:
- String startedUrl = msg.getData().getString("url");
- mWebView.onPageStarted(startedUrl);
- if (mWebViewClient != null) {
- if (TRACE) Log.d(LOGTAG, "onPageStarted=" + startedUrl);
- mWebViewClient.onPageStarted(mWebView.getWebView(), startedUrl,
- (Bitmap) msg.obj);
- }
- break;
-
- case PAGE_FINISHED:
- String finishedUrl = (String) msg.obj;
- mWebView.onPageFinished(finishedUrl);
- if (mWebViewClient != null) {
- if (TRACE) Log.d(LOGTAG, "onPageFinished=" + finishedUrl);
- mWebViewClient.onPageFinished(mWebView.getWebView(), finishedUrl);
- }
- break;
-
- case RECEIVED_ICON:
- if (mWebChromeClient != null) {
- if (TRACE) Log.d(LOGTAG, "onReceivedIcon");
- mWebChromeClient.onReceivedIcon(mWebView.getWebView(), (Bitmap) msg.obj);
- }
- break;
-
- case RECEIVED_TOUCH_ICON_URL:
- if (mWebChromeClient != null) {
- if (TRACE) Log.d(LOGTAG, "onReceivedTouchIconUrl");
- mWebChromeClient.onReceivedTouchIconUrl(mWebView.getWebView(),
- (String) msg.obj, msg.arg1 == 1);
- }
- break;
-
- case RECEIVED_TITLE:
- if (mWebChromeClient != null) {
- if (TRACE) Log.d(LOGTAG, "onReceivedTitle");
- mWebChromeClient.onReceivedTitle(mWebView.getWebView(),
- (String) msg.obj);
- }
- break;
-
- case REPORT_ERROR:
- if (mWebViewClient != null) {
- int reasonCode = msg.arg1;
- final String description = msg.getData().getString("description");
- final String failUrl = msg.getData().getString("failingUrl");
- if (TRACE) Log.d(LOGTAG, "onReceivedError=" + failUrl);
- mWebViewClient.onReceivedError(mWebView.getWebView(), reasonCode,
- description, failUrl);
- }
- break;
-
- case RESEND_POST_DATA:
- Message resend =
- (Message) msg.getData().getParcelable("resend");
- Message dontResend =
- (Message) msg.getData().getParcelable("dontResend");
- if (mWebViewClient != null) {
- if (TRACE) Log.d(LOGTAG, "onFormResubmission");
- mWebViewClient.onFormResubmission(mWebView.getWebView(), dontResend,
- resend);
- } else {
- dontResend.sendToTarget();
- }
- break;
-
- case OVERRIDE_URL:
- String overrideUrl = msg.getData().getString("url");
- boolean override = uiOverrideUrlLoading(overrideUrl);
- ResultTransport<Boolean> result =
- (ResultTransport<Boolean>) msg.obj;
- synchronized (this) {
- result.setResult(override);
- notify();
- }
- break;
-
- case AUTH_REQUEST:
- if (mWebViewClient != null) {
- HttpAuthHandler handler = (HttpAuthHandler) msg.obj;
- String host = msg.getData().getString("host");
- String realm = msg.getData().getString("realm");
- if (TRACE) Log.d(LOGTAG, "onReceivedHttpAuthRequest");
- mWebViewClient.onReceivedHttpAuthRequest(mWebView.getWebView(), handler,
- host, realm);
- }
- break;
-
- case SSL_ERROR:
- if (mWebViewClient != null) {
- HashMap<String, Object> map =
- (HashMap<String, Object>) msg.obj;
- if (TRACE) Log.d(LOGTAG, "onReceivedSslError");
- mWebViewClient.onReceivedSslError(mWebView.getWebView(),
- (SslErrorHandler) map.get("handler"),
- (SslError) map.get("error"));
- }
- break;
-
- case PROCEEDED_AFTER_SSL_ERROR:
- if (mWebViewClient != null && mWebViewClient instanceof WebViewClientClassicExt) {
- if (TRACE) Log.d(LOGTAG, "onProceededAfterSslError");
- ((WebViewClientClassicExt) mWebViewClient).onProceededAfterSslError(
- mWebView.getWebView(),
- (SslError) msg.obj);
- }
- break;
-
- case CLIENT_CERT_REQUEST:
- if (mWebViewClient != null && mWebViewClient instanceof WebViewClientClassicExt) {
- if (TRACE) Log.d(LOGTAG, "onReceivedClientCertRequest");
- HashMap<String, Object> map = (HashMap<String, Object>) msg.obj;
- ((WebViewClientClassicExt) mWebViewClient).onReceivedClientCertRequest(
- mWebView.getWebView(),
- (ClientCertRequestHandler) map.get("handler"),
- (String) map.get("host_and_port"));
- }
- break;
-
- case PROGRESS:
- // Synchronize to ensure mLatestProgress is not modified after
- // setProgress is called and before mProgressUpdatePending is
- // changed.
- synchronized (this) {
- if (mWebChromeClient != null) {
- if (TRACE) Log.d(LOGTAG, "onProgressChanged=" + mLatestProgress);
- mWebChromeClient.onProgressChanged(mWebView.getWebView(),
- mLatestProgress);
- }
- mProgressUpdatePending = false;
- }
- break;
-
- case UPDATE_VISITED:
- if (mWebViewClient != null) {
- String url = (String) msg.obj;
- if (TRACE) Log.d(LOGTAG, "doUpdateVisitedHistory=" + url);
- mWebViewClient.doUpdateVisitedHistory(mWebView.getWebView(),
- url, msg.arg1 != 0);
- }
- break;
-
- case LOAD_RESOURCE:
- if (mWebViewClient != null) {
- String url = (String) msg.obj;
- if (TRACE) Log.d(LOGTAG, "onLoadResource=" + url);
- mWebViewClient.onLoadResource(mWebView.getWebView(), url);
- }
- break;
-
- case DOWNLOAD_FILE:
- if (mDownloadListener != null) {
- String url = msg.getData().getString("url");
- String userAgent = msg.getData().getString("userAgent");
- String contentDisposition =
- msg.getData().getString("contentDisposition");
- String mimetype = msg.getData().getString("mimetype");
- String referer = msg.getData().getString("referer");
- Long contentLength = msg.getData().getLong("contentLength");
-
- if (TRACE) Log.d(LOGTAG, "onDownloadStart");
- if (mDownloadListener instanceof BrowserDownloadListener) {
- ((BrowserDownloadListener) mDownloadListener).onDownloadStart(url,
- userAgent, contentDisposition, mimetype, referer, contentLength);
- } else {
- mDownloadListener.onDownloadStart(url, userAgent,
- contentDisposition, mimetype, contentLength);
- }
- }
- break;
-
- case CREATE_WINDOW:
- if (mWebChromeClient != null) {
- if (TRACE) Log.d(LOGTAG, "onCreateWindow");
- if (!mWebChromeClient.onCreateWindow(mWebView.getWebView(),
- msg.arg1 == 1, msg.arg2 == 1,
- (Message) msg.obj)) {
- synchronized (this) {
- notify();
- }
- }
- mWebView.dismissZoomControl();
- }
- break;
-
- case REQUEST_FOCUS:
- if (mWebChromeClient != null) {
- if (TRACE) Log.d(LOGTAG, "onRequestFocus");
- mWebChromeClient.onRequestFocus(mWebView.getWebView());
- }
- break;
-
- case CLOSE_WINDOW:
- if (mWebChromeClient != null) {
- if (TRACE) Log.d(LOGTAG, "onCloseWindow");
- mWebChromeClient.onCloseWindow(((WebViewClassic) msg.obj).getWebView());
- }
- break;
-
- case SAVE_PASSWORD:
- Bundle bundle = msg.getData();
- String schemePlusHost = bundle.getString("host");
- String username = bundle.getString("username");
- String password = bundle.getString("password");
- // If the client returned false it means that the notify message
- // will not be sent and we should notify WebCore ourselves.
- if (!mWebView.onSavePassword(schemePlusHost, username, password,
- (Message) msg.obj)) {
- synchronized (this) {
- notify();
- }
- }
- break;
-
- case ASYNC_KEYEVENTS:
- if (mWebViewClient != null) {
- if (TRACE) Log.d(LOGTAG, "onUnhandledKeyEvent");
- mWebViewClient.onUnhandledKeyEvent(mWebView.getWebView(),
- (KeyEvent) msg.obj);
- }
- break;
-
- case EXCEEDED_DATABASE_QUOTA:
- if (mWebChromeClient != null) {
- HashMap<String, Object> map =
- (HashMap<String, Object>) msg.obj;
- String databaseIdentifier =
- (String) map.get("databaseIdentifier");
- String url = (String) map.get("url");
- long quota =
- ((Long) map.get("quota")).longValue();
- long totalQuota =
- ((Long) map.get("totalQuota")).longValue();
- long estimatedDatabaseSize =
- ((Long) map.get("estimatedDatabaseSize")).longValue();
- WebStorage.QuotaUpdater quotaUpdater =
- (WebStorage.QuotaUpdater) map.get("quotaUpdater");
-
- if (TRACE) Log.d(LOGTAG, "onExceededDatabaseQuota");
- mWebChromeClient.onExceededDatabaseQuota(url,
- databaseIdentifier, quota, estimatedDatabaseSize,
- totalQuota, quotaUpdater);
- }
- break;
-
- case REACHED_APPCACHE_MAXSIZE:
- if (mWebChromeClient != null) {
- HashMap<String, Object> map =
- (HashMap<String, Object>) msg.obj;
- long requiredStorage =
- ((Long) map.get("requiredStorage")).longValue();
- long quota =
- ((Long) map.get("quota")).longValue();
- WebStorage.QuotaUpdater quotaUpdater =
- (WebStorage.QuotaUpdater) map.get("quotaUpdater");
-
- if (TRACE) Log.d(LOGTAG, "onReachedMaxAppCacheSize");
- mWebChromeClient.onReachedMaxAppCacheSize(requiredStorage,
- quota, quotaUpdater);
- }
- break;
-
- case GEOLOCATION_PERMISSIONS_SHOW_PROMPT:
- if (mWebChromeClient != null) {
- HashMap<String, Object> map =
- (HashMap<String, Object>) msg.obj;
- String origin = (String) map.get("origin");
- GeolocationPermissions.Callback callback =
- (GeolocationPermissions.Callback)
- map.get("callback");
- if (TRACE) Log.d(LOGTAG, "onGeolocationPermissionsShowPrompt");
- mWebChromeClient.onGeolocationPermissionsShowPrompt(origin,
- callback);
- }
- break;
-
- case GEOLOCATION_PERMISSIONS_HIDE_PROMPT:
- if (mWebChromeClient != null) {
- if (TRACE) Log.d(LOGTAG, "onGeolocationPermissionsHidePrompt");
- mWebChromeClient.onGeolocationPermissionsHidePrompt();
- }
- break;
-
- case JS_DIALOG:
- if (mWebChromeClient != null) {
- final JsResultReceiver receiver = (JsResultReceiver) msg.obj;
- JsDialogHelper helper = new JsDialogHelper(receiver.mJsResult, msg);
- if (TRACE) Log.d(LOGTAG, "onJsAlert");
- if (!helper.invokeCallback(mWebChromeClient, mWebView.getWebView())) {
- helper.showDialog(mContext);
- }
- receiver.setReady();
- }
- break;
-
- case JS_TIMEOUT:
- if(mWebChromeClient != null) {
- final JsResultReceiver receiver = (JsResultReceiver) msg.obj;
- final JsResult res = receiver.mJsResult;
- if (TRACE) Log.d(LOGTAG, "onJsTimeout");
- if (mWebChromeClient.onJsTimeout()) {
- res.confirm();
- } else {
- res.cancel();
- }
- receiver.setReady();
- }
- break;
-
- case RECEIVED_CERTIFICATE:
- mWebView.setCertificate((SslCertificate) msg.obj);
- break;
-
- case NOTIFY:
- synchronized (this) {
- notify();
- }
- break;
-
- case SCALE_CHANGED:
- if (mWebViewClient != null) {
- if (TRACE) Log.d(LOGTAG, "onScaleChanged");
- mWebViewClient.onScaleChanged(mWebView.getWebView(), msg.getData()
- .getFloat("old"), msg.getData().getFloat("new"));
- }
- break;
-
- case SWITCH_OUT_HISTORY:
- mWebView.switchOutDrawHistory();
- break;
-
- case ADD_MESSAGE_TO_CONSOLE:
- if (mWebChromeClient == null) {
- break;
- }
- String message = msg.getData().getString("message");
- String sourceID = msg.getData().getString("sourceID");
- int lineNumber = msg.getData().getInt("lineNumber");
- int msgLevel = msg.getData().getInt("msgLevel");
- int numberOfMessageLevels = ConsoleMessage.MessageLevel.values().length;
- // Sanity bounds check as we'll index an array with msgLevel
- if (msgLevel < 0 || msgLevel >= numberOfMessageLevels) {
- msgLevel = 0;
- }
-
- ConsoleMessage.MessageLevel messageLevel =
- ConsoleMessage.MessageLevel.values()[msgLevel];
-
- if (TRACE) Log.d(LOGTAG, "onConsoleMessage");
- if (!mWebChromeClient.onConsoleMessage(new ConsoleMessage(message, sourceID,
- lineNumber, messageLevel))) {
- // If false was returned the user did not provide their own console function so
- // we should output some default messages to the system log.
- String logTag = "Web Console";
- String logMessage = message + " at " + sourceID + ":" + lineNumber;
-
- switch (messageLevel) {
- case TIP:
- Log.v(logTag, logMessage);
- break;
- case LOG:
- Log.i(logTag, logMessage);
- break;
- case WARNING:
- Log.w(logTag, logMessage);
- break;
- case ERROR:
- Log.e(logTag, logMessage);
- break;
- case DEBUG:
- Log.d(logTag, logMessage);
- break;
- }
- }
-
- break;
-
- case GET_VISITED_HISTORY:
- if (mWebChromeClient != null) {
- if (TRACE) Log.d(LOGTAG, "getVisitedHistory");
- mWebChromeClient.getVisitedHistory((ValueCallback<String[]>)msg.obj);
- }
- break;
-
- case OPEN_FILE_CHOOSER:
- if (mWebChromeClient != null) {
- if (TRACE) Log.d(LOGTAG, "openFileChooser");
- UploadFileMessageData data = (UploadFileMessageData)msg.obj;
- mWebChromeClient.openFileChooser(data.getUploadFile(), data.getAcceptType(),
- data.getCapture());
- }
- break;
-
- case ADD_HISTORY_ITEM:
- if (mWebBackForwardListClient != null) {
- if (TRACE) Log.d(LOGTAG, "onNewHistoryItem");
- mWebBackForwardListClient.onNewHistoryItem(
- (WebHistoryItem) msg.obj);
- }
- break;
-
- case HISTORY_INDEX_CHANGED:
- if (mWebBackForwardListClient != null) {
- mWebBackForwardListClient.onIndexChanged(
- (WebHistoryItem) msg.obj, msg.arg1);
- }
- break;
- case AUTH_CREDENTIALS: {
- String host = msg.getData().getString("host");
- String realm = msg.getData().getString("realm");
- username = msg.getData().getString("username");
- password = msg.getData().getString("password");
- mWebView.setHttpAuthUsernamePassword(
- host, realm, username, password);
- break;
- }
- case AUTO_LOGIN: {
- if (mWebViewClient != null) {
- String realm = msg.getData().getString("realm");
- String account = msg.getData().getString("account");
- String args = msg.getData().getString("args");
- if (TRACE) Log.d(LOGTAG, "onReceivedLoginRequest");
- mWebViewClient.onReceivedLoginRequest(mWebView.getWebView(), realm,
- account, args);
- }
- break;
- }
- }
- }
-
- /**
- * Return the latest progress.
- */
- public int getProgress() {
- return mLatestProgress;
- }
-
- /**
- * Called by WebCore side to switch out of history Picture drawing mode
- */
- void switchOutDrawHistory() {
- sendMessage(obtainMessage(SWITCH_OUT_HISTORY));
- }
-
- //--------------------------------------------------------------------------
- // WebViewClient functions.
- // NOTE: shouldOverrideKeyEvent is never called from the WebCore thread so
- // it is not necessary to include it here.
- //--------------------------------------------------------------------------
-
- // Performance probe
- private static final boolean PERF_PROBE = false;
- private long mWebCoreThreadTime;
- private long mWebCoreIdleTime;
-
- /*
- * If PERF_PROBE is true, this block needs to be added to MessageQueue.java.
- * startWait() and finishWait() should be called before and after wait().
-
- private WaitCallback mWaitCallback = null;
- public static interface WaitCallback {
- void startWait();
- void finishWait();
- }
- public final void setWaitCallback(WaitCallback callback) {
- mWaitCallback = callback;
- }
- */
-
- // un-comment this block if PERF_PROBE is true
- /*
- private IdleCallback mIdleCallback = new IdleCallback();
-
- private final class IdleCallback implements MessageQueue.WaitCallback {
- private long mStartTime = 0;
-
- public void finishWait() {
- mWebCoreIdleTime += SystemClock.uptimeMillis() - mStartTime;
- }
-
- public void startWait() {
- mStartTime = SystemClock.uptimeMillis();
- }
- }
- */
-
- public void onPageStarted(String url, Bitmap favicon) {
- // We need to send the message even if no WebViewClient is set, because we need to call
- // WebView.onPageStarted().
-
- // Performance probe
- if (PERF_PROBE) {
- mWebCoreThreadTime = SystemClock.currentThreadTimeMillis();
- mWebCoreIdleTime = 0;
- // un-comment this if PERF_PROBE is true
-// Looper.myQueue().setWaitCallback(mIdleCallback);
- }
- Message msg = obtainMessage(PAGE_STARTED);
- msg.obj = favicon;
- msg.getData().putString("url", url);
- sendMessage(msg);
- }
-
- public void onPageFinished(String url) {
- // Performance probe
- if (PERF_PROBE) {
- // un-comment this if PERF_PROBE is true
-// Looper.myQueue().setWaitCallback(null);
- Log.d("WebCore", "WebCore thread used " +
- (SystemClock.currentThreadTimeMillis() - mWebCoreThreadTime)
- + " ms and idled " + mWebCoreIdleTime + " ms");
- }
- Message msg = obtainMessage(PAGE_FINISHED, url);
- sendMessage(msg);
- }
-
- // Because this method is public and because CallbackProxy is mistakenly
- // party of the public classes, we cannot remove this method.
- public void onTooManyRedirects(Message cancelMsg, Message continueMsg) {
- // deprecated.
- }
-
- public void onReceivedError(int errorCode, String description,
- String failingUrl) {
- // Do an unsynchronized quick check to avoid posting if no callback has
- // been set.
- if (mWebViewClient == null) {
- return;
- }
-
- Message msg = obtainMessage(REPORT_ERROR);
- msg.arg1 = errorCode;
- msg.getData().putString("description", description);
- msg.getData().putString("failingUrl", failingUrl);
- sendMessage(msg);
- }
-
- public void onFormResubmission(Message dontResend,
- Message resend) {
- // Do an unsynchronized quick check to avoid posting if no callback has
- // been set.
- if (mWebViewClient == null) {
- dontResend.sendToTarget();
- return;
- }
-
- Message msg = obtainMessage(RESEND_POST_DATA);
- Bundle bundle = msg.getData();
- bundle.putParcelable("resend", resend);
- bundle.putParcelable("dontResend", dontResend);
- sendMessage(msg);
- }
-
- /**
- * Called by the WebCore side
- */
- public boolean shouldOverrideUrlLoading(String url) {
- // We have a default behavior if no client exists so always send the
- // message.
- ResultTransport<Boolean> res = new ResultTransport<Boolean>(false);
- Message msg = obtainMessage(OVERRIDE_URL);
- msg.getData().putString("url", url);
- msg.obj = res;
- sendMessageToUiThreadSync(msg);
- return res.getResult().booleanValue();
- }
-
- public void onReceivedHttpAuthRequest(HttpAuthHandler handler,
- String hostName, String realmName) {
- // Do an unsynchronized quick check to avoid posting if no callback has
- // been set.
- if (mWebViewClient == null) {
- handler.cancel();
- return;
- }
- Message msg = obtainMessage(AUTH_REQUEST, handler);
- msg.getData().putString("host", hostName);
- msg.getData().putString("realm", realmName);
- sendMessage(msg);
- }
-
- public void onReceivedSslError(SslErrorHandler handler, SslError error) {
- // Do an unsynchronized quick check to avoid posting if no callback has
- // been set.
- if (mWebViewClient == null) {
- handler.cancel();
- return;
- }
- Message msg = obtainMessage(SSL_ERROR);
- HashMap<String, Object> map = new HashMap();
- map.put("handler", handler);
- map.put("error", error);
- msg.obj = map;
- sendMessage(msg);
- }
-
- public void onProceededAfterSslError(SslError error) {
- if (mWebViewClient == null || !(mWebViewClient instanceof WebViewClientClassicExt)) {
- return;
- }
- Message msg = obtainMessage(PROCEEDED_AFTER_SSL_ERROR);
- msg.obj = error;
- sendMessage(msg);
- }
-
- public void onReceivedClientCertRequest(ClientCertRequestHandler handler, String host_and_port) {
- // Do an unsynchronized quick check to avoid posting if no callback has
- // been set.
- if (mWebViewClient == null || !(mWebViewClient instanceof WebViewClientClassicExt)) {
- handler.cancel();
- return;
- }
- Message msg = obtainMessage(CLIENT_CERT_REQUEST);
- HashMap<String, Object> map = new HashMap();
- map.put("handler", handler);
- map.put("host_and_port", host_and_port);
- msg.obj = map;
- sendMessage(msg);
- }
-
- public void onReceivedCertificate(SslCertificate certificate) {
- // here, certificate can be null (if the site is not secure)
- sendMessage(obtainMessage(RECEIVED_CERTIFICATE, certificate));
- }
-
- public void doUpdateVisitedHistory(String url, boolean isReload) {
- // Do an unsynchronized quick check to avoid posting if no callback has
- // been set.
- if (mWebViewClient == null) {
- return;
- }
- sendMessage(obtainMessage(UPDATE_VISITED, isReload ? 1 : 0, 0, url));
- }
-
- WebResourceResponse shouldInterceptRequest(String url) {
- if (mWebViewClient == null) {
- return null;
- }
- // Note: This method does _not_ send a message.
- if (TRACE) Log.d(LOGTAG, "shouldInterceptRequest=" + url);
- WebResourceResponse r =
- mWebViewClient.shouldInterceptRequest(mWebView.getWebView(), url);
- if (r == null) {
- sendMessage(obtainMessage(LOAD_RESOURCE, url));
- }
- return r;
- }
-
- public void onUnhandledKeyEvent(KeyEvent event) {
- // Do an unsynchronized quick check to avoid posting if no callback has
- // been set.
- if (mWebViewClient == null) {
- return;
- }
- sendMessage(obtainMessage(ASYNC_KEYEVENTS, event));
- }
-
- public void onScaleChanged(float oldScale, float newScale) {
- // Do an unsynchronized quick check to avoid posting if no callback has
- // been set.
- if (mWebViewClient == null) {
- return;
- }
- Message msg = obtainMessage(SCALE_CHANGED);
- Bundle bundle = msg.getData();
- bundle.putFloat("old", oldScale);
- bundle.putFloat("new", newScale);
- sendMessage(msg);
- }
-
- void onReceivedLoginRequest(String realm, String account, String args) {
- // Do an unsynchronized quick check to avoid posting if no callback has
- // been set.
- if (mWebViewClient == null) {
- return;
- }
- Message msg = obtainMessage(AUTO_LOGIN);
- Bundle bundle = msg.getData();
- bundle.putString("realm", realm);
- bundle.putString("account", account);
- bundle.putString("args", args);
- sendMessage(msg);
- }
-
- //--------------------------------------------------------------------------
- // DownloadListener functions.
- //--------------------------------------------------------------------------
-
- /**
- * Starts a download if a download listener has been registered, otherwise
- * return false.
- */
- public boolean onDownloadStart(String url, String userAgent,
- String contentDisposition, String mimetype, String referer,
- long contentLength) {
- // Do an unsynchronized quick check to avoid posting if no callback has
- // been set.
- if (mDownloadListener == null) {
- // Cancel the download if there is no browser client.
- return false;
- }
-
- Message msg = obtainMessage(DOWNLOAD_FILE);
- Bundle bundle = msg.getData();
- bundle.putString("url", url);
- bundle.putString("userAgent", userAgent);
- bundle.putString("mimetype", mimetype);
- bundle.putString("referer", referer);
- bundle.putLong("contentLength", contentLength);
- bundle.putString("contentDisposition", contentDisposition);
- sendMessage(msg);
- return true;
- }
-
-
- //--------------------------------------------------------------------------
- // WebView specific functions that do not interact with a client. These
- // functions just need to operate within the UI thread.
- //--------------------------------------------------------------------------
-
- public boolean onSavePassword(String schemePlusHost, String username,
- String password, Message resumeMsg) {
- // resumeMsg should be null at this point because we want to create it
- // within the CallbackProxy.
- if (DebugFlags.CALLBACK_PROXY) {
- junit.framework.Assert.assertNull(resumeMsg);
- }
- resumeMsg = obtainMessage(NOTIFY);
-
- Message msg = obtainMessage(SAVE_PASSWORD, resumeMsg);
- Bundle bundle = msg.getData();
- bundle.putString("host", schemePlusHost);
- bundle.putString("username", username);
- bundle.putString("password", password);
- sendMessageToUiThreadSync(msg);
- // Doesn't matter here
- return false;
- }
-
- public void onReceivedHttpAuthCredentials(String host, String realm,
- String username, String password) {
- Message msg = obtainMessage(AUTH_CREDENTIALS);
- msg.getData().putString("host", host);
- msg.getData().putString("realm", realm);
- msg.getData().putString("username", username);
- msg.getData().putString("password", password);
- sendMessage(msg);
- }
-
- //--------------------------------------------------------------------------
- // WebChromeClient methods
- //--------------------------------------------------------------------------
-
- public void onProgressChanged(int newProgress) {
- // Synchronize so that mLatestProgress is up-to-date.
- synchronized (this) {
- // update mLatestProgress even mWebChromeClient is null as
- // WebView.getProgress() needs it
- if (mLatestProgress == newProgress) {
- return;
- }
- mLatestProgress = newProgress;
- if (mWebChromeClient == null) {
- return;
- }
- if (!mProgressUpdatePending) {
- sendEmptyMessage(PROGRESS);
- mProgressUpdatePending = true;
- }
- }
- }
-
- public BrowserFrame createWindow(boolean dialog, boolean userGesture) {
- // Do an unsynchronized quick check to avoid posting if no callback has
- // been set.
- if (mWebChromeClient == null) {
- return null;
- }
-
- WebView.WebViewTransport transport =
- mWebView.getWebView().new WebViewTransport();
- final Message msg = obtainMessage(NOTIFY);
- msg.obj = transport;
- sendMessageToUiThreadSync(obtainMessage(CREATE_WINDOW, dialog ? 1 : 0,
- userGesture ? 1 : 0, msg));
- WebViewClassic w = WebViewClassic.fromWebView(transport.getWebView());
- if (w != null) {
- WebViewCore core = w.getWebViewCore();
- // If WebView.destroy() has been called, core may be null. Skip
- // initialization in that case and return null.
- if (core != null) {
- core.initializeSubwindow();
- return core.getBrowserFrame();
- }
- }
- return null;
- }
-
- public void onRequestFocus() {
- // Do an unsynchronized quick check to avoid posting if no callback has
- // been set.
- if (mWebChromeClient == null) {
- return;
- }
-
- sendEmptyMessage(REQUEST_FOCUS);
- }
-
- public void onCloseWindow(WebViewClassic window) {
- // Do an unsynchronized quick check to avoid posting if no callback has
- // been set.
- if (mWebChromeClient == null) {
- return;
- }
- sendMessage(obtainMessage(CLOSE_WINDOW, window));
- }
-
- public void onReceivedIcon(Bitmap icon) {
- // The current item might be null if the icon was already stored in the
- // database and this is a new WebView.
- WebHistoryItemClassic i = mBackForwardList.getCurrentItem();
- if (i != null) {
- i.setFavicon(icon);
- }
- // Do an unsynchronized quick check to avoid posting if no callback has
- // been set.
- if (mWebChromeClient == null) {
- return;
- }
- sendMessage(obtainMessage(RECEIVED_ICON, icon));
- }
-
- /* package */ void onReceivedTouchIconUrl(String url, boolean precomposed) {
- // We should have a current item but we do not want to crash so check
- // for null.
- WebHistoryItemClassic i = mBackForwardList.getCurrentItem();
- if (i != null) {
- i.setTouchIconUrl(url, precomposed);
- }
- // Do an unsynchronized quick check to avoid posting if no callback has
- // been set.
- if (mWebChromeClient == null) {
- return;
- }
- sendMessage(obtainMessage(RECEIVED_TOUCH_ICON_URL,
- precomposed ? 1 : 0, 0, url));
- }
-
- public void onReceivedTitle(String title) {
- // Do an unsynchronized quick check to avoid posting if no callback has
- // been set.
- if (mWebChromeClient == null) {
- return;
- }
- sendMessage(obtainMessage(RECEIVED_TITLE, title));
- }
-
- public void onJsAlert(String url, String message) {
- // Do an unsynchronized quick check to avoid posting if no callback has
- // been set.
- if (mWebChromeClient == null) {
- return;
- }
- JsResultReceiver result = new JsResultReceiver();
- Message alert = obtainMessage(JS_DIALOG, result);
- alert.getData().putString("message", message);
- alert.getData().putString("url", url);
- alert.getData().putInt("type", JsDialogHelper.ALERT);
- sendMessageToUiThreadSync(alert);
- }
-
- public boolean onJsConfirm(String url, String message) {
- // Do an unsynchronized quick check to avoid posting if no callback has
- // been set.
- if (mWebChromeClient == null) {
- return false;
- }
- JsResultReceiver result = new JsResultReceiver();
- Message confirm = obtainMessage(JS_DIALOG, result);
- confirm.getData().putString("message", message);
- confirm.getData().putString("url", url);
- confirm.getData().putInt("type", JsDialogHelper.CONFIRM);
- sendMessageToUiThreadSync(confirm);
- return result.mJsResult.getResult();
- }
-
- public String onJsPrompt(String url, String message, String defaultValue) {
- // Do an unsynchronized quick check to avoid posting if no callback has
- // been set.
- if (mWebChromeClient == null) {
- return null;
- }
- JsResultReceiver result = new JsResultReceiver();
- Message prompt = obtainMessage(JS_DIALOG, result);
- prompt.getData().putString("message", message);
- prompt.getData().putString("default", defaultValue);
- prompt.getData().putString("url", url);
- prompt.getData().putInt("type", JsDialogHelper.PROMPT);
- sendMessageToUiThreadSync(prompt);
- return result.mJsResult.getStringResult();
- }
-
- public boolean onJsBeforeUnload(String url, String message) {
- // Do an unsynchronized quick check to avoid posting if no callback has
- // been set.
- if (mWebChromeClient == null) {
- return true;
- }
- JsResultReceiver result = new JsResultReceiver();
- Message unload = obtainMessage(JS_DIALOG, result);
- unload.getData().putString("message", message);
- unload.getData().putString("url", url);
- unload.getData().putInt("type", JsDialogHelper.UNLOAD);
- sendMessageToUiThreadSync(unload);
- return result.mJsResult.getResult();
- }
-
- /**
- * Called by WebViewCore to inform the Java side that the current origin
- * has overflowed it's database quota. Called in the WebCore thread so
- * posts a message to the UI thread that will prompt the WebChromeClient
- * for what to do. On return back to C++ side, the WebCore thread will
- * sleep pending a new quota value.
- * @param url The URL that caused the quota overflow.
- * @param databaseIdentifier The identifier of the database that the
- * transaction that caused the overflow was running on.
- * @param quota The current quota the origin is allowed.
- * @param estimatedDatabaseSize The estimated size of the database.
- * @param totalQuota is the sum of all origins' quota.
- * @param quotaUpdater An instance of a class encapsulating a callback
- * to WebViewCore to run when the decision to allow or deny more
- * quota has been made.
- */
- public void onExceededDatabaseQuota(
- String url, String databaseIdentifier, long quota,
- long estimatedDatabaseSize, long totalQuota,
- WebStorage.QuotaUpdater quotaUpdater) {
- if (mWebChromeClient == null) {
- // Native-side logic prevents the quota being updated to a smaller
- // value.
- quotaUpdater.updateQuota(quota);
- return;
- }
-
- Message exceededQuota = obtainMessage(EXCEEDED_DATABASE_QUOTA);
- HashMap<String, Object> map = new HashMap();
- map.put("databaseIdentifier", databaseIdentifier);
- map.put("url", url);
- map.put("quota", quota);
- map.put("estimatedDatabaseSize", estimatedDatabaseSize);
- map.put("totalQuota", totalQuota);
- map.put("quotaUpdater", quotaUpdater);
- exceededQuota.obj = map;
- sendMessage(exceededQuota);
- }
-
- /**
- * Called by WebViewCore to inform the Java side that the appcache has
- * exceeded its max size.
- * @param requiredStorage is the amount of storage, in bytes, that would be
- * needed in order for the last appcache operation to succeed.
- * @param quota is the current quota (for all origins).
- * @param quotaUpdater An instance of a class encapsulating a callback
- * to WebViewCore to run when the decision to allow or deny a bigger
- * app cache size has been made.
- */
- public void onReachedMaxAppCacheSize(long requiredStorage,
- long quota, WebStorage.QuotaUpdater quotaUpdater) {
- if (mWebChromeClient == null) {
- // Native-side logic prevents the quota being updated to a smaller
- // value.
- quotaUpdater.updateQuota(quota);
- return;
- }
-
- Message msg = obtainMessage(REACHED_APPCACHE_MAXSIZE);
- HashMap<String, Object> map = new HashMap();
- map.put("requiredStorage", requiredStorage);
- map.put("quota", quota);
- map.put("quotaUpdater", quotaUpdater);
- msg.obj = map;
- sendMessage(msg);
- }
-
- /**
- * Called by WebViewCore to instruct the browser to display a prompt to ask
- * the user to set the Geolocation permission state for the given origin.
- * @param origin The origin requesting Geolocation permsissions.
- * @param callback The callback to call once a permission state has been
- * obtained.
- */
- public void onGeolocationPermissionsShowPrompt(String origin,
- GeolocationPermissions.Callback callback) {
- if (mWebChromeClient == null) {
- return;
- }
-
- Message showMessage =
- obtainMessage(GEOLOCATION_PERMISSIONS_SHOW_PROMPT);
- HashMap<String, Object> map = new HashMap();
- map.put("origin", origin);
- map.put("callback", callback);
- showMessage.obj = map;
- sendMessage(showMessage);
- }
-
- /**
- * Called by WebViewCore to instruct the browser to hide the Geolocation
- * permissions prompt.
- */
- public void onGeolocationPermissionsHidePrompt() {
- if (mWebChromeClient == null) {
- return;
- }
-
- Message hideMessage = obtainMessage(GEOLOCATION_PERMISSIONS_HIDE_PROMPT);
- sendMessage(hideMessage);
- }
-
- /**
- * Called by WebViewCore when we have a message to be added to the JavaScript
- * error console. Sends a message to the Java side with the details.
- * @param message The message to add to the console.
- * @param lineNumber The lineNumber of the source file on which the error
- * occurred.
- * @param sourceID The filename of the source file in which the error
- * occurred.
- * @param msgLevel The message level, corresponding to the MessageLevel enum in
- * WebCore/page/Console.h
- */
- public void addMessageToConsole(String message, int lineNumber, String sourceID, int msgLevel) {
- if (mWebChromeClient == null) {
- return;
- }
-
- Message msg = obtainMessage(ADD_MESSAGE_TO_CONSOLE);
- msg.getData().putString("message", message);
- msg.getData().putString("sourceID", sourceID);
- msg.getData().putInt("lineNumber", lineNumber);
- msg.getData().putInt("msgLevel", msgLevel);
- sendMessage(msg);
- }
-
- public boolean onJsTimeout() {
- //always interrupt timedout JS by default
- if (mWebChromeClient == null) {
- return true;
- }
- JsResultReceiver result = new JsResultReceiver();
- Message timeout = obtainMessage(JS_TIMEOUT, result);
- sendMessageToUiThreadSync(timeout);
- return result.mJsResult.getResult();
- }
-
- public void getVisitedHistory(ValueCallback<String[]> callback) {
- if (mWebChromeClient == null) {
- return;
- }
- Message msg = obtainMessage(GET_VISITED_HISTORY);
- msg.obj = callback;
- sendMessage(msg);
- }
-
- private static class UploadFileMessageData {
- private UploadFile mCallback;
- private String mAcceptType;
- private String mCapture;
-
- public UploadFileMessageData(UploadFile uploadFile, String acceptType, String capture) {
- mCallback = uploadFile;
- mAcceptType = acceptType;
- mCapture = capture;
- }
-
- public UploadFile getUploadFile() {
- return mCallback;
- }
-
- public String getAcceptType() {
- return mAcceptType;
- }
-
- public String getCapture() {
- return mCapture;
- }
- }
-
- private class UploadFile implements ValueCallback<Uri> {
- private Uri mValue;
- public void onReceiveValue(Uri value) {
- mValue = value;
- synchronized (CallbackProxy.this) {
- CallbackProxy.this.notify();
- }
- }
- public Uri getResult() {
- return mValue;
- }
- }
-
- /**
- * Called by WebViewCore to open a file chooser.
- */
- /* package */ Uri openFileChooser(String acceptType, String capture) {
- if (mWebChromeClient == null) {
- return null;
- }
- Message myMessage = obtainMessage(OPEN_FILE_CHOOSER);
- UploadFile uploadFile = new UploadFile();
- UploadFileMessageData data = new UploadFileMessageData(uploadFile, acceptType, capture);
- myMessage.obj = data;
- sendMessageToUiThreadSync(myMessage);
- return uploadFile.getResult();
- }
-
- void onNewHistoryItem(WebHistoryItem item) {
- if (mWebBackForwardListClient == null) {
- return;
- }
- Message msg = obtainMessage(ADD_HISTORY_ITEM, item);
- sendMessage(msg);
- }
-
- void onIndexChanged(WebHistoryItem item, int index) {
- if (mWebBackForwardListClient == null) {
- return;
- }
- Message msg = obtainMessage(HISTORY_INDEX_CHANGED, index, 0, item);
- sendMessage(msg);
- }
-
- private synchronized void sendMessageToUiThreadSync(Message msg) {
- sendMessage(msg);
- WebCoreThreadWatchdog.pause();
- try {
- wait();
- } catch (InterruptedException e) {
- Log.e(LOGTAG, "Caught exception waiting for synchronous UI message to be processed");
- Log.e(LOGTAG, Log.getStackTraceString(e));
- }
- WebCoreThreadWatchdog.resume();
- }
-}
+++ /dev/null
-/*
- * 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 android.webkit;
-
-import com.android.org.bouncycastle.asn1.ASN1Encoding;
-import com.android.org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
-import com.android.org.bouncycastle.asn1.x509.AlgorithmIdentifier;
-import com.android.org.bouncycastle.jce.netscape.NetscapeCertRequest;
-import com.android.org.bouncycastle.util.encoders.Base64;
-
-import android.content.Context;
-import android.security.Credentials;
-import android.security.KeyChain;
-import android.util.Log;
-
-import java.security.KeyPair;
-import java.security.KeyPairGenerator;
-import java.util.HashMap;
-
-final class CertTool {
- private static final String LOGTAG = "CertTool";
-
- private static final AlgorithmIdentifier MD5_WITH_RSA =
- new AlgorithmIdentifier(PKCSObjectIdentifiers.md5WithRSAEncryption);
-
- private static HashMap<String, String> sCertificateTypeMap;
- static {
- sCertificateTypeMap = new HashMap<String, String>();
- sCertificateTypeMap.put("application/x-x509-ca-cert", KeyChain.EXTRA_CERTIFICATE);
- sCertificateTypeMap.put("application/x-x509-user-cert", KeyChain.EXTRA_CERTIFICATE);
- sCertificateTypeMap.put("application/x-pkcs12", KeyChain.EXTRA_PKCS12);
- }
-
- static String[] getKeyStrengthList() {
- return new String[] {"High Grade", "Medium Grade"};
- }
-
- static String getSignedPublicKey(Context context, int index, String challenge) {
- try {
- KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA");
- generator.initialize((index == 0) ? 2048 : 1024);
- KeyPair pair = generator.genKeyPair();
-
- NetscapeCertRequest request = new NetscapeCertRequest(challenge,
- MD5_WITH_RSA, pair.getPublic());
- request.sign(pair.getPrivate());
- byte[] signed = request.toASN1Primitive().getEncoded(ASN1Encoding.DER);
-
- Credentials.getInstance().install(context, pair);
- return new String(Base64.encode(signed));
- } catch (Exception e) {
- Log.w(LOGTAG, e);
- }
- return null;
- }
-
- static void addCertificate(Context context, String type, byte[] value) {
- Credentials.getInstance().install(context, type, value);
- }
-
- static String getCertType(String mimeType) {
- return sCertificateTypeMap.get(mimeType);
- }
-
- private CertTool() {}
-}
+++ /dev/null
-/*
- * 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.os.Handler;
-import java.security.PrivateKey;
-import java.security.cert.CertificateEncodingException;
-import java.security.cert.X509Certificate;
-import com.android.org.conscrypt.NativeCrypto;
-import com.android.org.conscrypt.OpenSSLKey;
-import com.android.org.conscrypt.OpenSSLKeyHolder;
-
-/**
- * ClientCertRequestHandler: class responsible for handling client
- * certificate requests. This class is passed as a parameter to
- * BrowserCallback.displayClientCertRequestDialog and is meant to
- * receive the user's response.
- *
- * @hide
- */
-public final class ClientCertRequestHandler extends Handler {
-
- private final BrowserFrame mBrowserFrame;
- private final int mHandle;
- private final String mHostAndPort;
- private final SslClientCertLookupTable mTable;
- ClientCertRequestHandler(BrowserFrame browserFrame,
- int handle,
- String host_and_port,
- SslClientCertLookupTable table) {
- mBrowserFrame = browserFrame;
- mHandle = handle;
- mHostAndPort = host_and_port;
- mTable = table;
- }
-
- /**
- * Proceed with the specified private key and client certificate chain.
- */
- public void proceed(PrivateKey privateKey, X509Certificate[] chain) {
- try {
- byte[][] chainBytes = NativeCrypto.encodeCertificates(chain);
- mTable.Allow(mHostAndPort, privateKey, chainBytes);
-
- if (privateKey instanceof OpenSSLKeyHolder) {
- OpenSSLKey pkey = ((OpenSSLKeyHolder) privateKey).getOpenSSLKey();
- setSslClientCertFromCtx(pkey.getPkeyContext(), chainBytes);
- } else {
- setSslClientCertFromPKCS8(privateKey.getEncoded(), chainBytes);
- }
- } catch (CertificateEncodingException e) {
- post(new Runnable() {
- public void run() {
- mBrowserFrame.nativeSslClientCert(mHandle, 0, null);
- return;
- }
- });
- }
- }
-
- /**
- * Proceed with the specified private key bytes and client certificate chain.
- */
- private void setSslClientCertFromCtx(final long ctx, final byte[][] chainBytes) {
- post(new Runnable() {
- public void run() {
- mBrowserFrame.nativeSslClientCert(mHandle, ctx, chainBytes);
- }
- });
- }
-
- /**
- * Proceed with the specified private key context and client certificate chain.
- */
- private void setSslClientCertFromPKCS8(final byte[] key, final byte[][] chainBytes) {
- post(new Runnable() {
- public void run() {
- mBrowserFrame.nativeSslClientCert(mHandle, key, chainBytes);
- }
- });
- }
-
- /**
- * Igore the request for now, the user may be prompted again.
- */
- public void ignore() {
- post(new Runnable() {
- public void run() {
- mBrowserFrame.nativeSslClientCert(mHandle, 0, null);
- }
- });
- }
-
- /**
- * Cancel this request, remember the users negative choice.
- */
- public void cancel() {
- mTable.Deny(mHostAndPort);
- post(new Runnable() {
- public void run() {
- mBrowserFrame.nativeSslClientCert(mHandle, 0, null);
- }
- });
- }
-}
+++ /dev/null
-/*
- * Copyright (C) 2012 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.net.ParseException;
-import android.net.WebAddress;
-import android.os.AsyncTask;
-import android.util.Log;
-
-class CookieManagerClassic extends CookieManager {
-
- private static CookieManagerClassic sRef;
-
- private static final String LOGTAG = "webkit";
-
- private int mPendingCookieOperations = 0;
-
- private CookieManagerClassic() {
- }
-
- public static synchronized CookieManagerClassic getInstance() {
- if (sRef == null) {
- sRef = new CookieManagerClassic();
- }
- return sRef;
- }
-
- @Override
- public synchronized void setAcceptCookie(boolean accept) {
- nativeSetAcceptCookie(accept);
- }
-
- @Override
- public synchronized boolean acceptCookie() {
- return nativeAcceptCookie();
- }
-
- @Override
- public void setCookie(String url, String value) {
- setCookie(url, value, false);
- }
-
- /**
- * See {@link #setCookie(String, String)}
- * @param url The URL for which the cookie is set
- * @param value The value of the cookie, as a string, using the format of
- * the 'Set-Cookie' HTTP response header
- * @param privateBrowsing Whether to use the private browsing cookie jar
- */
- void setCookie(String url, String value, boolean privateBrowsing) {
- WebAddress uri;
- try {
- uri = new WebAddress(url);
- } catch (ParseException ex) {
- Log.e(LOGTAG, "Bad address: " + url);
- return;
- }
-
- nativeSetCookie(uri.toString(), value, privateBrowsing);
- }
-
- @Override
- public String getCookie(String url) {
- return getCookie(url, false);
- }
-
- @Override
- public String getCookie(String url, boolean privateBrowsing) {
- WebAddress uri;
- try {
- uri = new WebAddress(url);
- } catch (ParseException ex) {
- Log.e(LOGTAG, "Bad address: " + url);
- return null;
- }
-
- return nativeGetCookie(uri.toString(), privateBrowsing);
- }
-
- @Override
- public synchronized String getCookie(WebAddress uri) {
- return nativeGetCookie(uri.toString(), false);
- }
-
- /**
- * Waits for pending operations to completed.
- */
- void waitForCookieOperationsToComplete() {
- // Note that this function is applicable for both the java
- // and native http stacks, and works correctly with either.
- synchronized (this) {
- while (mPendingCookieOperations > 0) {
- try {
- wait();
- } catch (InterruptedException e) { }
- }
- }
- }
-
- private synchronized void signalCookieOperationsComplete() {
- mPendingCookieOperations--;
- assert mPendingCookieOperations > -1;
- notify();
- }
-
- private synchronized void signalCookieOperationsStart() {
- mPendingCookieOperations++;
- }
-
- @Override
- public void removeSessionCookie() {
- signalCookieOperationsStart();
- new AsyncTask<Void, Void, Void>() {
- @Override
- protected Void doInBackground(Void... none) {
- nativeRemoveSessionCookie();
- signalCookieOperationsComplete();
- return null;
- }
- }.execute();
- }
-
- @Override
- public void removeAllCookie() {
- nativeRemoveAllCookie();
- }
-
- @Override
- public synchronized boolean hasCookies() {
- return hasCookies(false);
- }
-
- @Override
- public synchronized boolean hasCookies(boolean privateBrowsing) {
- return nativeHasCookies(privateBrowsing);
- }
-
- @Override
- public void removeExpiredCookie() {
- nativeRemoveExpiredCookie();
- }
-
- @Override
- protected void flushCookieStore() {
- nativeFlushCookieStore();
- }
-
- @Override
- protected boolean allowFileSchemeCookiesImpl() {
- return nativeAcceptFileSchemeCookies();
- }
-
- @Override
- protected void setAcceptFileSchemeCookiesImpl(boolean accept) {
- nativeSetAcceptFileSchemeCookies(accept);
- }
-
- // Native functions
- private static native boolean nativeAcceptCookie();
- private static native String nativeGetCookie(String url, boolean privateBrowsing);
- private static native boolean nativeHasCookies(boolean privateBrowsing);
- private static native void nativeRemoveAllCookie();
- private static native void nativeRemoveExpiredCookie();
- private static native void nativeRemoveSessionCookie();
- private static native void nativeSetAcceptCookie(boolean accept);
- private static native void nativeSetCookie(String url, String value, boolean privateBrowsing);
- private static native void nativeFlushCookieStore();
- private static native boolean nativeAcceptFileSchemeCookies();
- private static native void nativeSetAcceptFileSchemeCookies(boolean accept);
-}
if (context == null) {
throw new IllegalArgumentException("Invalid context argument");
}
- // TODO: Remove this workaround after webview classic is no longer supported.
- if (WebViewFactory.getProvider().getClass().getName().contains("WebViewClassic")) {
- WebViewDatabase.getInstance(context);
- }
setGetInstanceIsAllowed();
return getInstance();
public static final boolean URL_UTIL = false;
public static final boolean WEB_SYNC_MANAGER = false;
- // TODO: Delete these when WebViewClassic is moved
- public static final boolean BROWSER_FRAME = false;
- public static final boolean CACHE_MANAGER = false;
- public static final boolean CALLBACK_PROXY = false;
- public static final boolean COOKIE_MANAGER = false;
- public static final boolean FRAME_LOADER = false;
- public static final boolean J_WEB_CORE_JAVA_BRIDGE = false;// HIGHLY VERBOSE
- public static final boolean LOAD_LISTENER = false;
- public static final boolean MEASURE_PAGE_SWAP_FPS = false;
- public static final boolean NETWORK = false;
- public static final boolean SSL_ERROR_HANDLER = false;
- public static final boolean STREAM_LOADER = false;
- public static final boolean WEB_BACK_FORWARD_LIST = false;
- public static final boolean WEB_SETTINGS = false;
- public static final boolean WEB_VIEW = false;
- public static final boolean WEB_VIEW_CORE = false;
-
-
}
+++ /dev/null
-/*
- * Copyright (C) 2010 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;
-
-/**
- * This class is simply a container for the methods used to implement DeviceMotion and
- * DeviceOrientation, including the mock DeviceOrientationClient for use in LayoutTests.
- *
- * This could be part of WebViewCore, but have moved it to its own class to
- * avoid bloat there.
- */
-final class DeviceMotionAndOrientationManager {
- private WebViewCore mWebViewCore;
-
- public DeviceMotionAndOrientationManager(WebViewCore webViewCore) {
- mWebViewCore = webViewCore;
- }
-
- /**
- * Sets that the Page for this WebViewCore should use a mock DeviceOrientation
- * client.
- */
- public void setUseMock() {
- assert WebViewCore.THREAD_NAME.equals(Thread.currentThread().getName());
- nativeSetUseMock(mWebViewCore);
- }
-
- /**
- * Set the position for the mock DeviceOrientation service for this WebViewCore.
- */
- public void setMockOrientation(boolean canProvideAlpha, double alpha, boolean canProvideBeta,
- double beta, boolean canProvideGamma, double gamma) {
- assert WebViewCore.THREAD_NAME.equals(Thread.currentThread().getName());
- nativeSetMockOrientation(mWebViewCore, canProvideAlpha, alpha, canProvideBeta, beta,
- canProvideGamma, gamma);
- }
-
- // We only provide accelerationIncludingGravity.
- public void onMotionChange(Double x, Double y, Double z, double interval) {
- nativeOnMotionChange(mWebViewCore,
- x != null, x != null ? x.doubleValue() : 0.0,
- y != null, y != null ? y.doubleValue() : 0.0,
- z != null, z != null ? z.doubleValue() : 0.0,
- interval);
- }
- public void onOrientationChange(Double alpha, Double beta, Double gamma) {
- nativeOnOrientationChange(mWebViewCore,
- alpha != null, alpha != null ? alpha.doubleValue() : 0.0,
- beta != null, beta != null ? beta.doubleValue() : 0.0,
- gamma != null, gamma != null ? gamma.doubleValue() : 0.0);
- }
-
- // Native functions
- private static native void nativeSetUseMock(WebViewCore webViewCore);
- private static native void nativeSetMockOrientation(WebViewCore webViewCore,
- boolean canProvideAlpha, double alpha, boolean canProvideBeta, double beta,
- boolean canProvideGamma, double gamma);
- private static native void nativeOnMotionChange(WebViewCore webViewCore,
- boolean canProvideX, double x, boolean canProvideY, double y,
- boolean canProvideZ, double z, double interval);
- private static native void nativeOnOrientationChange(WebViewCore webViewCore,
- boolean canProvideAlpha, double alpha, boolean canProvideBeta, double beta,
- boolean canProvideGamma, double gamma);
-}
+++ /dev/null
-/*
- * Copyright (C) 2010 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.content.Context;
-import android.hardware.Sensor;
-import android.hardware.SensorEvent;
-import android.hardware.SensorEventListener;
-import android.hardware.SensorManager;
-import android.os.Handler;
-import android.os.Message;
-import android.webkit.DeviceMotionAndOrientationManager;
-import java.lang.Runnable;
-import java.util.List;
-
-
-final class DeviceMotionService implements SensorEventListener {
- private DeviceMotionAndOrientationManager mManager;
- private boolean mIsRunning;
- private Handler mHandler;
- private SensorManager mSensorManager;
- private Context mContext;
- private boolean mHaveSentErrorEvent;
- private Runnable mUpdateRunnable;
- private float mLastAcceleration[];
-
- private static final int INTERVAL_MILLIS = 100;
-
- public DeviceMotionService(DeviceMotionAndOrientationManager manager, Context context) {
- mManager = manager;
- assert(mManager != null);
- mContext = context;
- assert(mContext != null);
- }
-
- public void start() {
- mIsRunning = true;
- registerForSensor();
- }
-
- public void stop() {
- mIsRunning = false;
- stopSendingUpdates();
- unregisterFromSensor();
- }
-
- public void suspend() {
- if (mIsRunning) {
- stopSendingUpdates();
- unregisterFromSensor();
- }
- }
-
- public void resume() {
- if (mIsRunning) {
- registerForSensor();
- }
- }
-
- private void sendErrorEvent() {
- assert WebViewCore.THREAD_NAME.equals(Thread.currentThread().getName());
- // The spec requires that each listener receives the error event only once.
- if (mHaveSentErrorEvent)
- return;
- mHaveSentErrorEvent = true;
- createHandler();
- mHandler.post(new Runnable() {
- @Override
- public void run() {
- assert WebViewCore.THREAD_NAME.equals(Thread.currentThread().getName());
- if (mIsRunning) {
- // The special case of all nulls is used to signify a failure to get data.
- mManager.onMotionChange(null, null, null, 0.0);
- }
- }
- });
- }
-
- private void createHandler() {
- if (mHandler != null) {
- return;
- }
-
- mHandler = new Handler();
- mUpdateRunnable = new Runnable() {
- @Override
- public void run() {
- assert mIsRunning;
- mManager.onMotionChange(new Double(mLastAcceleration[0]),
- new Double(mLastAcceleration[1]), new Double(mLastAcceleration[2]),
- INTERVAL_MILLIS);
- mHandler.postDelayed(mUpdateRunnable, INTERVAL_MILLIS);
- // Now that we have successfully sent some data, reset whether we've sent an error.
- mHaveSentErrorEvent = false;
- }
- };
- }
-
- private void startSendingUpdates() {
- createHandler();
- mUpdateRunnable.run();
- }
-
- private void stopSendingUpdates() {
- mHandler.removeCallbacks(mUpdateRunnable);
- mLastAcceleration = null;
- }
-
- private void registerForSensor() {
- if (!registerForAccelerometerSensor()) {
- sendErrorEvent();
- }
- }
-
- private SensorManager getSensorManager() {
- assert WebViewCore.THREAD_NAME.equals(Thread.currentThread().getName());
- if (mSensorManager == null) {
- mSensorManager = (SensorManager) mContext.getSystemService(Context.SENSOR_SERVICE);
- }
- return mSensorManager;
- }
-
- private boolean registerForAccelerometerSensor() {
- List<Sensor> sensors = getSensorManager().getSensorList(Sensor.TYPE_ACCELEROMETER);
- if (sensors.isEmpty()) {
- return false;
- }
- createHandler();
- // TODO: Consider handling multiple sensors.
- return getSensorManager().registerListener(
- this, sensors.get(0), SensorManager.SENSOR_DELAY_UI, mHandler);
- }
-
- private void unregisterFromSensor() {
- getSensorManager().unregisterListener(this);
- }
-
- /**
- * SensorEventListener implementation.
- * Callbacks happen on the thread on which we registered - the WebCore thread.
- */
- @Override
- public void onSensorChanged(SensorEvent event) {
- assert(event.values.length == 3);
- assert WebViewCore.THREAD_NAME.equals(Thread.currentThread().getName());
- assert(event.sensor.getType() == Sensor.TYPE_ACCELEROMETER);
-
- // We may get callbacks after the call to getSensorManager().unregisterListener() returns.
- if (!mIsRunning) {
- return;
- }
-
- boolean firstData = mLastAcceleration == null;
- mLastAcceleration = event.values;
- if (firstData) {
- startSendingUpdates();
- }
- }
-
- @Override
- public void onAccuracyChanged(Sensor sensor, int accuracy) {
- assert WebViewCore.THREAD_NAME.equals(Thread.currentThread().getName());
- }
-}
+++ /dev/null
-/*
- * Copyright (C) 2010 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.content.Context;
-import android.hardware.Sensor;
-import android.hardware.SensorEvent;
-import android.hardware.SensorEventListener;
-import android.hardware.SensorManager;
-import android.os.Handler;
-import android.webkit.DeviceMotionAndOrientationManager;
-import java.lang.Runnable;
-import java.util.List;
-
-
-final class DeviceOrientationService implements SensorEventListener {
- // The gravity vector expressed in the body frame.
- private float[] mGravityVector;
- // The geomagnetic vector expressed in the body frame.
- private float[] mMagneticFieldVector;
-
- private DeviceMotionAndOrientationManager mManager;
- private boolean mIsRunning;
- private Handler mHandler;
- private SensorManager mSensorManager;
- private Context mContext;
- private Double mAlpha;
- private Double mBeta;
- private Double mGamma;
- private boolean mHaveSentErrorEvent;
-
- private static final double DELTA_DEGRESS = 1.0;
-
- public DeviceOrientationService(DeviceMotionAndOrientationManager manager, Context context) {
- mManager = manager;
- assert(mManager != null);
- mContext = context;
- assert(mContext != null);
- }
-
- public void start() {
- mIsRunning = true;
- registerForSensors();
- }
-
- public void stop() {
- mIsRunning = false;
- unregisterFromSensors();
- }
-
- public void suspend() {
- if (mIsRunning) {
- unregisterFromSensors();
- }
- }
-
- public void resume() {
- if (mIsRunning) {
- registerForSensors();
- }
- }
-
- private void sendErrorEvent() {
- assert WebViewCore.THREAD_NAME.equals(Thread.currentThread().getName());
- // The spec requires that each listener receives the error event only once.
- if (mHaveSentErrorEvent)
- return;
- mHaveSentErrorEvent = true;
- mHandler.post(new Runnable() {
- @Override
- public void run() {
- assert WebViewCore.THREAD_NAME.equals(Thread.currentThread().getName());
- if (mIsRunning) {
- // The special case of all nulls is used to signify a failure to get data.
- mManager.onOrientationChange(null, null, null);
- }
- }
- });
- }
-
- private void registerForSensors() {
- if (mHandler == null) {
- mHandler = new Handler();
- }
- if (!registerForAccelerometerSensor() || !registerForMagneticFieldSensor()) {
- unregisterFromSensors();
- sendErrorEvent();
- }
- }
-
- private void getOrientationUsingGetRotationMatrix() {
- if (mGravityVector == null || mMagneticFieldVector == null) {
- return;
- }
-
- // Get the rotation matrix.
- // The rotation matrix that transforms from the body frame to the earth frame.
- float[] deviceRotationMatrix = new float[9];
- if (!SensorManager.getRotationMatrix(
- deviceRotationMatrix, null, mGravityVector, mMagneticFieldVector)) {
- return;
- }
-
- // Convert rotation matrix to rotation angles.
- // Assuming that the rotations are appied in the order listed at
- // http://developer.android.com/reference/android/hardware/SensorEvent.html#values
- // the rotations are applied about the same axes and in the same order as required by the
- // API. The only conversions are sign changes as follows.
- // The angles are in radians
- float[] rotationAngles = new float[3];
- SensorManager.getOrientation(deviceRotationMatrix, rotationAngles);
- double alpha = Math.toDegrees(-rotationAngles[0]);
- while (alpha < 0.0) { alpha += 360.0; } // [0, 360)
- double beta = Math.toDegrees(-rotationAngles[1]);
- while (beta < -180.0) { beta += 360.0; } // [-180, 180)
- double gamma = Math.toDegrees(rotationAngles[2]);
- while (gamma < -90.0) { gamma += 360.0; } // [-90, 90)
-
- maybeSendChange(alpha, beta, gamma);
- }
-
- private SensorManager getSensorManager() {
- assert WebViewCore.THREAD_NAME.equals(Thread.currentThread().getName());
- if (mSensorManager == null) {
- mSensorManager = (SensorManager) mContext.getSystemService(Context.SENSOR_SERVICE);
- }
- return mSensorManager;
- }
-
- private boolean registerForAccelerometerSensor() {
- List<Sensor> sensors = getSensorManager().getSensorList(Sensor.TYPE_ACCELEROMETER);
- if (sensors.isEmpty()) {
- return false;
- }
- // TODO: Consider handling multiple sensors.
- return getSensorManager().registerListener(
- this, sensors.get(0), SensorManager.SENSOR_DELAY_FASTEST, mHandler);
- }
-
- private boolean registerForMagneticFieldSensor() {
- List<Sensor> sensors = getSensorManager().getSensorList(Sensor.TYPE_MAGNETIC_FIELD);
- if (sensors.isEmpty()) {
- return false;
- }
- // TODO: Consider handling multiple sensors.
- return getSensorManager().registerListener(
- this, sensors.get(0), SensorManager.SENSOR_DELAY_FASTEST, mHandler);
- }
-
- private void unregisterFromSensors() {
- getSensorManager().unregisterListener(this);
- }
-
- private void maybeSendChange(double alpha, double beta, double gamma) {
- assert WebViewCore.THREAD_NAME.equals(Thread.currentThread().getName());
- if (mAlpha == null || mBeta == null || mGamma == null
- || Math.abs(alpha - mAlpha) > DELTA_DEGRESS
- || Math.abs(beta - mBeta) > DELTA_DEGRESS
- || Math.abs(gamma - mGamma) > DELTA_DEGRESS) {
- mAlpha = alpha;
- mBeta = beta;
- mGamma = gamma;
- mManager.onOrientationChange(mAlpha, mBeta, mGamma);
- // Now that we have successfully sent some data, reset whether we've sent an error.
- mHaveSentErrorEvent = false;
- }
- }
-
- /**
- * SensorEventListener implementation.
- * Callbacks happen on the thread on which we registered - the WebCore thread.
- */
- @Override
- public void onSensorChanged(SensorEvent event) {
- assert(event.values.length == 3);
- assert WebViewCore.THREAD_NAME.equals(Thread.currentThread().getName());
-
- // We may get callbacks after the call to getSensorManager().unregisterListener() returns.
- if (!mIsRunning) {
- return;
- }
-
- switch (event.sensor.getType()) {
- case Sensor.TYPE_ACCELEROMETER:
- if (mGravityVector == null) {
- mGravityVector = new float[3];
- }
- mGravityVector[0] = event.values[0];
- mGravityVector[1] = event.values[1];
- mGravityVector[2] = event.values[2];
- getOrientationUsingGetRotationMatrix();
- break;
- case Sensor.TYPE_MAGNETIC_FIELD:
- if (mMagneticFieldVector == null) {
- mMagneticFieldVector = new float[3];
- }
- mMagneticFieldVector[0] = event.values[0];
- mMagneticFieldVector[1] = event.values[1];
- mMagneticFieldVector[2] = event.values[2];
- getOrientationUsingGetRotationMatrix();
- break;
- default:
- assert(false);
- }
- }
-
- @Override
- public void onAccuracyChanged(Sensor sensor, int accuracy) {
- assert WebViewCore.THREAD_NAME.equals(Thread.currentThread().getName());
- }
-}
+++ /dev/null
-/*
- * Copyright (C) 2012 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.os.Handler;
-import android.os.Message;
-
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Set;
-import java.util.Vector;
-
-// This class is the Java counterpart of the WebKit C++ GeolocationPermissions
-// class. It simply marshals calls from the UI thread to the WebKit thread.
-final class GeolocationPermissionsClassic extends GeolocationPermissions {
- private Handler mHandler;
- private Handler mUIHandler;
-
- // A queue to store messages until the handler is ready.
- private Vector<Message> mQueuedMessages;
-
- // Message ids
- static final int GET_ORIGINS = 0;
- static final int GET_ALLOWED = 1;
- static final int CLEAR = 2;
- static final int ALLOW = 3;
- static final int CLEAR_ALL = 4;
-
- // Message ids on the UI thread
- static final int RETURN_ORIGINS = 0;
- static final int RETURN_ALLOWED = 1;
-
- private static final String ORIGINS = "origins";
- private static final String ORIGIN = "origin";
- private static final String CALLBACK = "callback";
- private static final String ALLOWED = "allowed";
-
- // Global instance
- private static GeolocationPermissionsClassic sInstance;
-
- public static GeolocationPermissionsClassic getInstance() {
- if (sInstance == null) {
- sInstance = new GeolocationPermissionsClassic();
- }
- return sInstance;
- }
-
- /**
- * Creates the UI message handler. Must be called on the UI thread.
- * @hide
- */
- public void createUIHandler() {
- if (mUIHandler == null) {
- mUIHandler = new Handler() {
- @Override
- public void handleMessage(Message msg) {
- // Runs on the UI thread.
- switch (msg.what) {
- case RETURN_ORIGINS: {
- Map values = (Map) msg.obj;
- Set<String> origins = (Set<String>) values.get(ORIGINS);
- ValueCallback<Set<String> > callback = (ValueCallback<Set<String> >) values.get(CALLBACK);
- callback.onReceiveValue(origins);
- } break;
- case RETURN_ALLOWED: {
- Map values = (Map) msg.obj;
- Boolean allowed = (Boolean) values.get(ALLOWED);
- ValueCallback<Boolean> callback = (ValueCallback<Boolean>) values.get(CALLBACK);
- callback.onReceiveValue(allowed);
- } break;
- }
- }
- };
- }
- }
-
- /**
- * Creates the message handler. Must be called on the WebKit thread.
- * @hide
- */
- public synchronized void createHandler() {
- if (mHandler == null) {
- mHandler = new Handler() {
- @Override
- public void handleMessage(Message msg) {
- // Runs on the WebKit thread.
- switch (msg.what) {
- case GET_ORIGINS: {
- Set origins = nativeGetOrigins();
- ValueCallback callback = (ValueCallback) msg.obj;
- Map values = new HashMap<String, Object>();
- values.put(CALLBACK, callback);
- values.put(ORIGINS, origins);
- postUIMessage(Message.obtain(null, RETURN_ORIGINS, values));
- } break;
- case GET_ALLOWED: {
- Map values = (Map) msg.obj;
- String origin = (String) values.get(ORIGIN);
- ValueCallback callback = (ValueCallback) values.get(CALLBACK);
- boolean allowed = nativeGetAllowed(origin);
- Map retValues = new HashMap<String, Object>();
- retValues.put(CALLBACK, callback);
- retValues.put(ALLOWED, Boolean.valueOf(allowed));
- postUIMessage(Message.obtain(null, RETURN_ALLOWED, retValues));
- } break;
- case CLEAR:
- nativeClear((String) msg.obj);
- break;
- case ALLOW:
- nativeAllow((String) msg.obj);
- break;
- case CLEAR_ALL:
- nativeClearAll();
- break;
- }
- }
- };
-
- // Handle the queued messages
- if (mQueuedMessages != null) {
- while (!mQueuedMessages.isEmpty()) {
- mHandler.sendMessage(mQueuedMessages.remove(0));
- }
- mQueuedMessages = null;
- }
- }
- }
-
- /**
- * Utility function to send a message to our handler.
- */
- private synchronized void postMessage(Message msg) {
- if (mHandler == null) {
- if (mQueuedMessages == null) {
- mQueuedMessages = new Vector<Message>();
- }
- mQueuedMessages.add(msg);
- } else {
- mHandler.sendMessage(msg);
- }
- }
-
- /**
- * Utility function to send a message to the handler on the UI thread
- */
- private void postUIMessage(Message msg) {
- if (mUIHandler != null) {
- mUIHandler.sendMessage(msg);
- }
- }
-
- // Note that we represent the origins as strings. These are created using
- // WebCore::SecurityOrigin::toString(). As long as all 'HTML 5 modules'
- // (Database, Geolocation etc) do so, it's safe to match up origins based
- // on this string.
- @Override
- public void getOrigins(ValueCallback<Set<String> > callback) {
- if (callback != null) {
- if (WebViewCore.THREAD_NAME.equals(Thread.currentThread().getName())) {
- Set origins = nativeGetOrigins();
- callback.onReceiveValue(origins);
- } else {
- postMessage(Message.obtain(null, GET_ORIGINS, callback));
- }
- }
- }
-
- @Override
- public void getAllowed(String origin, ValueCallback<Boolean> callback) {
- if (callback == null) {
- return;
- }
- if (origin == null) {
- callback.onReceiveValue(null);
- return;
- }
- if (WebViewCore.THREAD_NAME.equals(Thread.currentThread().getName())) {
- boolean allowed = nativeGetAllowed(origin);
- callback.onReceiveValue(Boolean.valueOf(allowed));
- } else {
- Map values = new HashMap<String, Object>();
- values.put(ORIGIN, origin);
- values.put(CALLBACK, callback);
- postMessage(Message.obtain(null, GET_ALLOWED, values));
- }
- }
-
- // This method may be called before the WebKit
- // thread has intialized the message handler. Messages will be queued until
- // this time.
- @Override
- public void clear(String origin) {
- // Called on the UI thread.
- postMessage(Message.obtain(null, CLEAR, origin));
- }
-
- // This method may be called before the WebKit
- // thread has intialized the message handler. Messages will be queued until
- // this time.
- @Override
- public void allow(String origin) {
- // Called on the UI thread.
- postMessage(Message.obtain(null, ALLOW, origin));
- }
-
- @Override
- public void clearAll() {
- // Called on the UI thread.
- postMessage(Message.obtain(null, CLEAR_ALL));
- }
-
- GeolocationPermissionsClassic() {}
-
- // Native functions, run on the WebKit thread.
- private static native Set nativeGetOrigins();
- private static native boolean nativeGetAllowed(String origin);
- private static native void nativeClear(String origin);
- private static native void nativeAllow(String origin);
- private static native void nativeClearAll();
-}
+++ /dev/null
-/*
- * 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 android.webkit;
-
-import android.app.ActivityThread;
-import android.content.Context;
-import android.location.Location;
-import android.location.LocationListener;
-import android.location.LocationManager;
-import android.location.LocationProvider;
-import android.os.Bundle;
-import android.util.Log;
-import android.webkit.WebViewCore;
-
-
-/**
- * Implements the Java side of GeolocationServiceAndroid.
- */
-final class GeolocationService implements LocationListener {
-
- // Log tag
- private static final String TAG = "geolocationService";
-
- private long mNativeObject;
- private LocationManager mLocationManager;
- private boolean mIsGpsEnabled;
- private boolean mIsRunning;
- private boolean mIsNetworkProviderAvailable;
- private boolean mIsGpsProviderAvailable;
-
- /**
- * Constructor
- * @param context The context from which we obtain the system service.
- * @param nativeObject The native object to which this object will report position updates and
- * errors.
- */
- public GeolocationService(Context context, long nativeObject) {
- mNativeObject = nativeObject;
- // Register newLocationAvailable with platform service.
- mLocationManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE);
- if (mLocationManager == null) {
- Log.e(TAG, "Could not get location manager.");
- }
- }
-
- /**
- * Start listening for location updates.
- */
- public boolean start() {
- registerForLocationUpdates();
- mIsRunning = true;
- return mIsNetworkProviderAvailable || mIsGpsProviderAvailable;
- }
-
- /**
- * Stop listening for location updates.
- */
- public void stop() {
- unregisterFromLocationUpdates();
- mIsRunning = false;
- }
-
- /**
- * Sets whether to use the GPS.
- * @param enable Whether to use the GPS.
- */
- public void setEnableGps(boolean enable) {
- if (mIsGpsEnabled != enable) {
- mIsGpsEnabled = enable;
- if (mIsRunning) {
- // There's no way to unregister from a single provider, so we can
- // only unregister from all, then reregister with all but the GPS.
- unregisterFromLocationUpdates();
- registerForLocationUpdates();
- // Check that the providers are still available after we re-register.
- maybeReportError("The last location provider is no longer available");
- }
- }
- }
-
- /**
- * LocationListener implementation.
- * Called when the location has changed.
- * @param location The new location, as a Location object.
- */
- public void onLocationChanged(Location location) {
- // Callbacks from the system location sevice are queued to this thread, so it's possible
- // that we receive callbacks after unregistering. At this point, the native object will no
- // longer exist.
- if (mIsRunning) {
- nativeNewLocationAvailable(mNativeObject, location);
- }
- }
-
- /**
- * LocationListener implementation.
- * Called when the provider status changes.
- * @param provider The name of the provider.
- * @param status The new status of the provider.
- * @param extras an optional Bundle with provider specific data.
- */
- public void onStatusChanged(String providerName, int status, Bundle extras) {
- boolean isAvailable = (status == LocationProvider.AVAILABLE);
- if (LocationManager.NETWORK_PROVIDER.equals(providerName)) {
- mIsNetworkProviderAvailable = isAvailable;
- } else if (LocationManager.GPS_PROVIDER.equals(providerName)) {
- mIsGpsProviderAvailable = isAvailable;
- }
- maybeReportError("The last location provider is no longer available");
- }
-
- /**
- * LocationListener implementation.
- * Called when the provider is enabled.
- * @param provider The name of the location provider that is now enabled.
- */
- public void onProviderEnabled(String providerName) {
- // No need to notify the native side. It's enough to start sending
- // valid position fixes again.
- if (LocationManager.NETWORK_PROVIDER.equals(providerName)) {
- mIsNetworkProviderAvailable = true;
- } else if (LocationManager.GPS_PROVIDER.equals(providerName)) {
- mIsGpsProviderAvailable = true;
- }
- }
-
- /**
- * LocationListener implementation.
- * Called when the provider is disabled.
- * @param provider The name of the location provider that is now disabled.
- */
- public void onProviderDisabled(String providerName) {
- if (LocationManager.NETWORK_PROVIDER.equals(providerName)) {
- mIsNetworkProviderAvailable = false;
- } else if (LocationManager.GPS_PROVIDER.equals(providerName)) {
- mIsGpsProviderAvailable = false;
- }
- maybeReportError("The last location provider was disabled");
- }
-
- /**
- * Registers this object with the location service.
- */
- private void registerForLocationUpdates() {
- try {
- // Registration may fail if providers are not present on the device.
- try {
- mLocationManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, 0, 0, this);
- mIsNetworkProviderAvailable = true;
- } catch(IllegalArgumentException e) { }
- if (mIsGpsEnabled) {
- try {
- mLocationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 0, 0, this);
- mIsGpsProviderAvailable = true;
- } catch(IllegalArgumentException e) { }
- }
- } catch(SecurityException e) {
- Log.e(TAG, "Caught security exception registering for location updates from system. " +
- "This should only happen in DumpRenderTree.");
- }
- }
-
- /**
- * Unregisters this object from the location service.
- */
- private void unregisterFromLocationUpdates() {
- mLocationManager.removeUpdates(this);
- mIsNetworkProviderAvailable = false;
- mIsGpsProviderAvailable = false;
- }
-
- /**
- * Reports an error if neither the network nor the GPS provider is available.
- */
- private void maybeReportError(String message) {
- // Callbacks from the system location sevice are queued to this thread, so it's possible
- // that we receive callbacks after unregistering. At this point, the native object will no
- // longer exist.
- if (mIsRunning && !mIsNetworkProviderAvailable && !mIsGpsProviderAvailable) {
- nativeNewErrorAvailable(mNativeObject, message);
- }
- }
-
- // Native functions
- private static native void nativeNewLocationAvailable(long nativeObject, Location location);
- private static native void nativeNewErrorAvailable(long nativeObject, String message);
-}
+++ /dev/null
-/*
- * Copyright (C) 2010 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.content.Context;
-import android.media.AudioManager;
-import android.media.MediaPlayer;
-import android.net.Uri;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.Message;
-import android.util.Log;
-
-import java.io.IOException;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Timer;
-import java.util.TimerTask;
-
-/**
- * HTML5 support class for Audio.
- *
- * This class runs almost entirely on the WebCore thread. The exception is when
- * accessing the WebView object to determine whether private browsing is
- * enabled.
- */
-class HTML5Audio extends Handler
- implements MediaPlayer.OnBufferingUpdateListener,
- MediaPlayer.OnCompletionListener,
- MediaPlayer.OnErrorListener,
- MediaPlayer.OnPreparedListener,
- MediaPlayer.OnSeekCompleteListener,
- AudioManager.OnAudioFocusChangeListener {
- // Logging tag.
- private static final String LOGTAG = "HTML5Audio";
-
- private MediaPlayer mMediaPlayer;
-
- // The C++ MediaPlayerPrivateAndroid object.
- private int mNativePointer;
- // The private status of the view that created this player
- private IsPrivateBrowsingEnabledGetter mIsPrivateBrowsingEnabledGetter;
-
- private static int IDLE = 0;
- private static int INITIALIZED = 1;
- private static int PREPARED = 2;
- private static int STARTED = 4;
- private static int COMPLETE = 5;
- private static int PAUSED = 6;
- private static int PAUSED_TRANSITORILY = 7;
- private static int STOPPED = -2;
- private static int ERROR = -1;
-
- private int mState = IDLE;
-
- private String mUrl;
- private boolean mAskToPlay = false;
- private boolean mLoopEnabled = false;
- private boolean mProcessingOnEnd = false;
- private Context mContext;
-
- // Timer thread -> UI thread
- private static final int TIMEUPDATE = 100;
-
- private static final String COOKIE = "Cookie";
- private static final String HIDE_URL_LOGS = "x-hide-urls-from-log";
-
- // The spec says the timer should fire every 250 ms or less.
- private static final int TIMEUPDATE_PERIOD = 250; // ms
- // The timer for timeupate events.
- // See http://www.whatwg.org/specs/web-apps/current-work/#event-media-timeupdate
- private Timer mTimer;
- private final class TimeupdateTask extends TimerTask {
- @Override
- public void run() {
- HTML5Audio.this.obtainMessage(TIMEUPDATE).sendToTarget();
- }
- }
-
- // Helper class to determine whether private browsing is enabled in the
- // given WebView. Queries the WebView on the UI thread. Calls to get()
- // block until the data is available.
- private class IsPrivateBrowsingEnabledGetter {
- private boolean mIsReady;
- private boolean mIsPrivateBrowsingEnabled;
- IsPrivateBrowsingEnabledGetter(Looper uiThreadLooper, final WebViewClassic webView) {
- new Handler(uiThreadLooper).post(new Runnable() {
- @Override
- public void run() {
- synchronized(IsPrivateBrowsingEnabledGetter.this) {
- mIsPrivateBrowsingEnabled = webView.isPrivateBrowsingEnabled();
- mIsReady = true;
- IsPrivateBrowsingEnabledGetter.this.notify();
- }
- }
- });
- }
- synchronized boolean get() {
- while (!mIsReady) {
- try {
- wait();
- } catch (InterruptedException e) {
- }
- }
- return mIsPrivateBrowsingEnabled;
- }
- };
-
- @Override
- public void handleMessage(Message msg) {
- switch (msg.what) {
- case TIMEUPDATE: {
- try {
- if (mState != ERROR && mMediaPlayer.isPlaying()) {
- int position = mMediaPlayer.getCurrentPosition();
- nativeOnTimeupdate(position, mNativePointer);
- }
- } catch (IllegalStateException e) {
- mState = ERROR;
- }
- }
- }
- }
-
- // event listeners for MediaPlayer
- // Those are called from the same thread we created the MediaPlayer
- // (i.e. the webviewcore thread here)
-
- // MediaPlayer.OnBufferingUpdateListener
- @Override
- public void onBufferingUpdate(MediaPlayer mp, int percent) {
- nativeOnBuffering(percent, mNativePointer);
- }
-
- // MediaPlayer.OnCompletionListener;
- @Override
- public void onCompletion(MediaPlayer mp) {
- mState = COMPLETE;
- mProcessingOnEnd = true;
- nativeOnEnded(mNativePointer);
- mProcessingOnEnd = false;
- if (mLoopEnabled == true) {
- nativeOnRequestPlay(mNativePointer);
- mLoopEnabled = false;
- }
- }
-
- // MediaPlayer.OnErrorListener
- @Override
- public boolean onError(MediaPlayer mp, int what, int extra) {
- mState = ERROR;
- resetMediaPlayer();
- mState = IDLE;
- return false;
- }
-
- // MediaPlayer.OnPreparedListener
- @Override
- public void onPrepared(MediaPlayer mp) {
- mState = PREPARED;
- if (mTimer != null) {
- mTimer.schedule(new TimeupdateTask(),
- TIMEUPDATE_PERIOD, TIMEUPDATE_PERIOD);
- }
- nativeOnPrepared(mp.getDuration(), 0, 0, mNativePointer);
- if (mAskToPlay) {
- mAskToPlay = false;
- play();
- }
- }
-
- // MediaPlayer.OnSeekCompleteListener
- @Override
- public void onSeekComplete(MediaPlayer mp) {
- nativeOnTimeupdate(mp.getCurrentPosition(), mNativePointer);
- }
-
-
- /**
- * @param nativePtr is the C++ pointer to the MediaPlayerPrivate object.
- */
- public HTML5Audio(WebViewCore webViewCore, int nativePtr) {
- // Save the native ptr
- mNativePointer = nativePtr;
- resetMediaPlayer();
- mContext = webViewCore.getContext();
- mIsPrivateBrowsingEnabledGetter = new IsPrivateBrowsingEnabledGetter(
- webViewCore.getContext().getMainLooper(), webViewCore.getWebViewClassic());
- }
-
- private void resetMediaPlayer() {
- if (mMediaPlayer == null) {
- mMediaPlayer = new MediaPlayer();
- } else {
- mMediaPlayer.reset();
- }
- mMediaPlayer.setOnBufferingUpdateListener(this);
- mMediaPlayer.setOnCompletionListener(this);
- mMediaPlayer.setOnErrorListener(this);
- mMediaPlayer.setOnPreparedListener(this);
- mMediaPlayer.setOnSeekCompleteListener(this);
-
- if (mTimer != null) {
- mTimer.cancel();
- }
- mTimer = new Timer();
- mState = IDLE;
- }
-
- private void setDataSource(String url) {
- mUrl = url;
- try {
- if (mState != IDLE) {
- resetMediaPlayer();
- }
- String cookieValue = CookieManager.getInstance().getCookie(
- url, mIsPrivateBrowsingEnabledGetter.get());
- Map<String, String> headers = new HashMap<String, String>();
-
- if (cookieValue != null) {
- headers.put(COOKIE, cookieValue);
- }
- if (mIsPrivateBrowsingEnabledGetter.get()) {
- headers.put(HIDE_URL_LOGS, "true");
- }
-
- mMediaPlayer.setDataSource(mContext, Uri.parse(url), headers);
- mState = INITIALIZED;
- mMediaPlayer.prepareAsync();
- } catch (IOException e) {
- String debugUrl = url.length() > 128 ? url.substring(0, 128) + "..." : url;
- Log.e(LOGTAG, "couldn't load the resource: "+ debugUrl +" exc: " + e);
- resetMediaPlayer();
- }
- }
-
- @Override
- public void onAudioFocusChange(int focusChange) {
- switch (focusChange) {
- case AudioManager.AUDIOFOCUS_GAIN:
- // resume playback
- if (mMediaPlayer == null) {
- resetMediaPlayer();
- } else if (mState == PAUSED_TRANSITORILY && !mMediaPlayer.isPlaying()) {
- mMediaPlayer.start();
- mState = STARTED;
- }
- break;
-
- case AudioManager.AUDIOFOCUS_LOSS:
- // Lost focus for an unbounded amount of time: stop playback.
- if (mState != ERROR && mMediaPlayer.isPlaying()) {
- mMediaPlayer.stop();
- mState = STOPPED;
- }
- break;
-
- case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
- case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
- // Lost focus for a short time, but we have to stop
- // playback.
- if (mState != ERROR && mMediaPlayer.isPlaying()) {
- pause(PAUSED_TRANSITORILY);
- }
- break;
- }
- }
-
-
- private void play() {
- if (mState == COMPLETE && mLoopEnabled == true) {
- // Play it again, Sam
- mMediaPlayer.start();
- mState = STARTED;
- return;
- }
-
- if (((mState >= ERROR && mState < PREPARED)) && mUrl != null) {
- resetMediaPlayer();
- setDataSource(mUrl);
- mAskToPlay = true;
- }
-
- if (mState >= PREPARED) {
- AudioManager audioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
- int result = audioManager.requestAudioFocus(this, AudioManager.STREAM_MUSIC,
- AudioManager.AUDIOFOCUS_GAIN);
-
- if (result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
- mMediaPlayer.start();
- mState = STARTED;
- }
- }
- }
-
- private void pause() {
- pause(PAUSED);
- }
-
- private void pause(int state) {
- if (mState == STARTED) {
- if (mTimer != null) {
- mTimer.purge();
- }
- mMediaPlayer.pause();
- mState = state;
- }
- }
-
- private void seek(int msec) {
- if (mProcessingOnEnd == true && mState == COMPLETE && msec == 0) {
- mLoopEnabled = true;
- }
- if (mState >= PREPARED) {
- mMediaPlayer.seekTo(msec);
- }
- }
-
- /**
- * Called only over JNI when WebKit is happy to
- * destroy the media player.
- */
- private void teardown() {
- mMediaPlayer.release();
- mMediaPlayer = null;
- mState = ERROR;
- mNativePointer = 0;
- }
-
- private float getMaxTimeSeekable() {
- if (mState >= PREPARED) {
- return mMediaPlayer.getDuration() / 1000.0f;
- } else {
- return 0;
- }
- }
-
- private native void nativeOnBuffering(int percent, int nativePointer);
- private native void nativeOnEnded(int nativePointer);
- private native void nativeOnRequestPlay(int nativePointer);
- private native void nativeOnPrepared(int duration, int width, int height, int nativePointer);
- private native void nativeOnTimeupdate(int position, int nativePointer);
-
-}
+++ /dev/null
-/*
- * Copyright (C) 2012 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.content.Context;
-import android.media.MediaPlayer;
-import android.media.Metadata;
-import android.util.Log;
-import android.view.Gravity;
-import android.view.MotionEvent;
-import android.view.SurfaceHolder;
-import android.view.SurfaceView;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.FrameLayout;
-import android.widget.MediaController;
-import android.widget.MediaController.MediaPlayerControl;
-
-
-/**
- * @hide This is only used by the browser
- */
-public class HTML5VideoFullScreen extends HTML5VideoView
- implements MediaPlayerControl, MediaPlayer.OnPreparedListener,
- View.OnTouchListener {
-
- // Add this sub-class to handle the resizing when rotating screen.
- private class VideoSurfaceView extends SurfaceView {
-
- public VideoSurfaceView(Context context) {
- super(context);
- }
-
- @Override
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- int width = getDefaultSize(mVideoWidth, widthMeasureSpec);
- int height = getDefaultSize(mVideoHeight, heightMeasureSpec);
- if (mVideoWidth > 0 && mVideoHeight > 0) {
- if ( mVideoWidth * height > width * mVideoHeight ) {
- height = width * mVideoHeight / mVideoWidth;
- } else if ( mVideoWidth * height < width * mVideoHeight ) {
- width = height * mVideoWidth / mVideoHeight;
- }
- }
- setMeasuredDimension(width, height);
- }
- }
-
- // This view will contain the video.
- private VideoSurfaceView mVideoSurfaceView;
-
- // We need the full screen state to decide which surface to render to and
- // when to create the MediaPlayer accordingly.
- static final int FULLSCREEN_OFF = 0;
- static final int FULLSCREEN_SURFACECREATING = 1;
- static final int FULLSCREEN_SURFACECREATED = 2;
-
- private int mFullScreenMode;
- // The Media Controller only used for full screen mode
- private MediaController mMediaController;
-
- // SurfaceHolder for full screen
- private SurfaceHolder mSurfaceHolder = null;
-
- // Data only for MediaController
- private boolean mCanSeekBack;
- private boolean mCanSeekForward;
- private boolean mCanPause;
- private int mCurrentBufferPercentage;
-
- // The progress view.
- private static View mProgressView;
- // The container for the progress view and video view
- private static FrameLayout mLayout;
-
- // The video size will be ready when prepared. Used to make sure the aspect
- // ratio is correct.
- private int mVideoWidth;
- private int mVideoHeight;
- private boolean mPlayingWhenDestroyed = false;
- SurfaceHolder.Callback mSHCallback = new SurfaceHolder.Callback()
- {
- @Override
- public void surfaceChanged(SurfaceHolder holder, int format,
- int w, int h)
- {
- if (mPlayer != null && mMediaController != null
- && mCurrentState == STATE_PREPARED) {
- if (mMediaController.isShowing()) {
- // ensure the controller will get repositioned later
- mMediaController.hide();
- }
- mMediaController.show();
- }
- }
-
- @Override
- public void surfaceCreated(SurfaceHolder holder)
- {
- mSurfaceHolder = holder;
- mFullScreenMode = FULLSCREEN_SURFACECREATED;
-
- prepareForFullScreen();
- }
-
- @Override
- public void surfaceDestroyed(SurfaceHolder holder)
- {
- mPlayingWhenDestroyed = mPlayer.isPlaying();
- pauseAndDispatch(mProxy);
- // We need to set the display to null before switching into inline
- // mode to avoid error.
- mPlayer.setDisplay(null);
- mSurfaceHolder = null;
- if (mMediaController != null) {
- mMediaController.hide();
- }
- }
- };
-
- MediaPlayer.OnVideoSizeChangedListener mSizeChangedListener =
- new MediaPlayer.OnVideoSizeChangedListener() {
- @Override
- public void onVideoSizeChanged(MediaPlayer mp, int width, int height) {
- mVideoWidth = mp.getVideoWidth();
- mVideoHeight = mp.getVideoHeight();
- if (mVideoWidth != 0 && mVideoHeight != 0) {
- mVideoSurfaceView.getHolder().setFixedSize(mVideoWidth, mVideoHeight);
- }
- }
- };
-
- private SurfaceView getSurfaceView() {
- return mVideoSurfaceView;
- }
-
- HTML5VideoFullScreen(Context context, int videoLayerId, int position, boolean skipPrepare) {
- mVideoSurfaceView = new VideoSurfaceView(context);
- mFullScreenMode = FULLSCREEN_OFF;
- mVideoWidth = 0;
- mVideoHeight = 0;
- init(videoLayerId, position, skipPrepare);
- }
-
- private void setMediaController(MediaController m) {
- mMediaController = m;
- attachMediaController();
- }
-
- private void attachMediaController() {
- if (mPlayer != null && mMediaController != null) {
- mMediaController.setMediaPlayer(this);
- mMediaController.setAnchorView(mVideoSurfaceView);
- //Will be enabled when prepared
- mMediaController.setEnabled(false);
- }
- }
-
- @Override
- public void decideDisplayMode() {
- mPlayer.setDisplay(mSurfaceHolder);
- }
-
- private void prepareForFullScreen() {
- MediaController mc = new FullScreenMediaController(mProxy.getContext(), mLayout);
- mc.setSystemUiVisibility(mLayout.getSystemUiVisibility());
- setMediaController(mc);
- mPlayer.setScreenOnWhilePlaying(true);
- mPlayer.setOnVideoSizeChangedListener(mSizeChangedListener);
- prepareDataAndDisplayMode(mProxy);
- }
-
-
- private void toggleMediaControlsVisiblity() {
- if (mMediaController.isShowing()) {
- mMediaController.hide();
- } else {
- mMediaController.show();
- }
- }
-
- @Override
- public void onPrepared(MediaPlayer mp) {
- super.onPrepared(mp);
-
- mVideoSurfaceView.setOnTouchListener(this);
- // Get the capabilities of the player for this stream
- Metadata data = mp.getMetadata(MediaPlayer.METADATA_ALL,
- MediaPlayer.BYPASS_METADATA_FILTER);
- if (data != null) {
- mCanPause = !data.has(Metadata.PAUSE_AVAILABLE)
- || data.getBoolean(Metadata.PAUSE_AVAILABLE);
- mCanSeekBack = !data.has(Metadata.SEEK_BACKWARD_AVAILABLE)
- || data.getBoolean(Metadata.SEEK_BACKWARD_AVAILABLE);
- mCanSeekForward = !data.has(Metadata.SEEK_FORWARD_AVAILABLE)
- || data.getBoolean(Metadata.SEEK_FORWARD_AVAILABLE);
- } else {
- mCanPause = mCanSeekBack = mCanSeekForward = true;
- }
-
- if (getStartWhenPrepared()) {
- mPlayer.start();
- // Clear the flag.
- setStartWhenPrepared(false);
- }
-
- // mMediaController status depends on the Metadata result, so put it
- // after reading the MetaData.
- // And make sure mPlayer state is updated before showing the controller.
- if (mMediaController != null) {
- mMediaController.setEnabled(true);
- mMediaController.show();
- }
-
- if (mProgressView != null) {
- mProgressView.setVisibility(View.GONE);
- }
-
- mVideoWidth = mp.getVideoWidth();
- mVideoHeight = mp.getVideoHeight();
- // This will trigger the onMeasure to get the display size right.
- mVideoSurfaceView.getHolder().setFixedSize(mVideoWidth, mVideoHeight);
-
- }
-
- @Override
- public boolean fullScreenExited() {
- return (mLayout == null);
- }
-
- private final WebChromeClient.CustomViewCallback mCallback =
- new WebChromeClient.CustomViewCallback() {
- @Override
- public void onCustomViewHidden() {
- // It listens to SurfaceHolder.Callback.SurfaceDestroyed event
- // which happens when the video view is detached from its parent
- // view. This happens in the WebChromeClient before this method
- // is invoked.
- mLayout.removeView(getSurfaceView());
-
- if (mProgressView != null) {
- mLayout.removeView(mProgressView);
- mProgressView = null;
- }
- mLayout = null;
- // Re enable plugin views.
- mProxy.getWebView().getViewManager().showAll();
- // Don't show the controller after exiting the full screen.
- mMediaController = null;
- // Continue the inline mode playing if necessary.
- mProxy.dispatchOnStopFullScreen(mPlayingWhenDestroyed);
- mProxy = null;
- }
- };
-
- @Override
- public void enterFullScreenVideoState(int layerId,
- HTML5VideoViewProxy proxy, WebViewClassic webView) {
- mFullScreenMode = FULLSCREEN_SURFACECREATING;
- mCurrentBufferPercentage = 0;
- mPlayer.setOnBufferingUpdateListener(mBufferingUpdateListener);
- mProxy = proxy;
-
- mVideoSurfaceView.getHolder().addCallback(mSHCallback);
- mVideoSurfaceView.getHolder().setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
- mVideoSurfaceView.setFocusable(true);
- mVideoSurfaceView.setFocusableInTouchMode(true);
- mVideoSurfaceView.requestFocus();
- mVideoSurfaceView.setOnKeyListener(mProxy);
- // Create a FrameLayout that will contain the VideoView and the
- // progress view (if any).
- mLayout = new FrameLayout(mProxy.getContext());
- FrameLayout.LayoutParams layoutParams = new FrameLayout.LayoutParams(
- ViewGroup.LayoutParams.WRAP_CONTENT,
- ViewGroup.LayoutParams.WRAP_CONTENT,
- Gravity.CENTER);
-
- mLayout.addView(getSurfaceView(), layoutParams);
-
- mLayout.setVisibility(View.VISIBLE);
- WebChromeClient client = webView.getWebChromeClient();
- if (client != null) {
- if (DebugFlags.TRACE_CALLBACK) Log.d(CallbackProxy.LOGTAG, "onShowCustomView");
- client.onShowCustomView(mLayout, mCallback);
- // Plugins like Flash will draw over the video so hide
- // them while we're playing.
- if (webView.getViewManager() != null)
- webView.getViewManager().hideAll();
-
- if (DebugFlags.TRACE_CALLBACK) {
- Log.d(CallbackProxy.LOGTAG, "getVideoLoadingProgressView");
- }
- mProgressView = client.getVideoLoadingProgressView();
- if (mProgressView != null) {
- mLayout.addView(mProgressView, layoutParams);
- mProgressView.setVisibility(View.VISIBLE);
- }
- }
- }
-
- /**
- * @return true when we are in full screen mode, even the surface not fully
- * created.
- */
- @Override
- public boolean isFullScreenMode() {
- return true;
- }
-
- // MediaController FUNCTIONS:
- @Override
- public boolean canPause() {
- return mCanPause;
- }
-
- @Override
- public boolean canSeekBackward() {
- return mCanSeekBack;
- }
-
- @Override
- public boolean canSeekForward() {
- return mCanSeekForward;
- }
-
- @Override
- public int getBufferPercentage() {
- if (mPlayer != null) {
- return mCurrentBufferPercentage;
- }
- return 0;
- }
-
- @Override
- public int getAudioSessionId() {
- if (mPlayer == null) {
- return 0;
- }
- return mPlayer.getAudioSessionId();
- }
-
- @Override
- public void showControllerInFullScreen() {
- if (mMediaController != null) {
- mMediaController.show(0);
- }
- }
-
- // Other listeners functions:
- private MediaPlayer.OnBufferingUpdateListener mBufferingUpdateListener =
- new MediaPlayer.OnBufferingUpdateListener() {
- @Override
- public void onBufferingUpdate(MediaPlayer mp, int percent) {
- mCurrentBufferPercentage = percent;
- }
- };
-
- @Override
- public boolean onTouch(View v, MotionEvent event) {
- if (mFullScreenMode >= FULLSCREEN_SURFACECREATED
- && mMediaController != null) {
- toggleMediaControlsVisiblity();
- }
- return false;
- }
-
- @Override
- protected void switchProgressView(boolean playerBuffering) {
- if (mProgressView != null) {
- if (playerBuffering) {
- mProgressView.setVisibility(View.VISIBLE);
- } else {
- mProgressView.setVisibility(View.GONE);
- }
- }
- return;
- }
-
- static class FullScreenMediaController extends MediaController {
-
- View mVideoView;
-
- public FullScreenMediaController(Context context, View video) {
- super(context);
- mVideoView = video;
- }
-
- @Override
- public void show() {
- super.show();
- if (mVideoView != null) {
- mVideoView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_VISIBLE);
- }
- }
-
- @Override
- public void hide() {
- if (mVideoView != null) {
- mVideoView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LOW_PROFILE
- | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION);
- }
- super.hide();
- }
-
- }
-
-}
+++ /dev/null
-/*
- * Copyright (C) 2012 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.Manifest.permission;
-import android.content.pm.PackageManager;
-import android.graphics.SurfaceTexture;
-import android.webkit.HTML5VideoView;
-import android.webkit.HTML5VideoViewProxy;
-import android.view.Surface;
-import android.opengl.GLES20;
-import android.os.PowerManager;
-
-/**
- * @hide This is only used by the browser
- */
-public class HTML5VideoInline extends HTML5VideoView{
-
- // Due to the fact that the decoder consume a lot of memory, we make the
- // surface texture as singleton. But the GL texture (m_textureNames)
- // associated with the surface texture can be used for showing the screen
- // shot when paused, so they are not singleton.
- private static SurfaceTexture mSurfaceTexture = null;
- private static int[] mTextureNames = null;
- // Every time when the VideoLayer Id change, we need to recreate the
- // SurfaceTexture in order to delete the old video's decoder memory.
- private static int mVideoLayerUsingSurfaceTexture = -1;
-
- // Video control FUNCTIONS:
- @Override
- public void start() {
- if (!getPauseDuringPreparing()) {
- super.start();
- }
- }
-
- HTML5VideoInline(int videoLayerId, int position, boolean skipPrepare) {
- init(videoLayerId, position, skipPrepare);
- }
-
- @Override
- public void decideDisplayMode() {
- SurfaceTexture surfaceTexture = getSurfaceTexture(getVideoLayerId());
- Surface surface = new Surface(surfaceTexture);
- mPlayer.setSurface(surface);
- surface.release();
- }
-
- // Normally called immediately after setVideoURI. But for full screen,
- // this should be after surface holder created
- @Override
- public void prepareDataAndDisplayMode(HTML5VideoViewProxy proxy) {
- super.prepareDataAndDisplayMode(proxy);
- setFrameAvailableListener(proxy);
- // TODO: This is a workaround, after b/5375681 fixed, we should switch
- // to the better way.
- if (mProxy.getContext().checkCallingOrSelfPermission(permission.WAKE_LOCK)
- == PackageManager.PERMISSION_GRANTED) {
- mPlayer.setWakeMode(proxy.getContext(), PowerManager.FULL_WAKE_LOCK);
- }
- }
-
- // Pause the play and update the play/pause button
- @Override
- public void pauseAndDispatch(HTML5VideoViewProxy proxy) {
- super.pauseAndDispatch(proxy);
- }
-
- // Inline Video specific FUNCTIONS:
-
- public static SurfaceTexture getSurfaceTexture(int videoLayerId) {
- // Create the surface texture.
- if (videoLayerId != mVideoLayerUsingSurfaceTexture
- || mSurfaceTexture == null
- || mTextureNames == null) {
- // The GL texture will store in the VideoLayerManager at native side.
- // They will be clean up when requested.
- // The reason we recreated GL texture name is for screen shot support.
- mTextureNames = new int[1];
- GLES20.glGenTextures(1, mTextureNames, 0);
- mSurfaceTexture = new SurfaceTexture(mTextureNames[0]);
- }
- mVideoLayerUsingSurfaceTexture = videoLayerId;
- return mSurfaceTexture;
- }
-
- public static boolean surfaceTextureDeleted() {
- return (mSurfaceTexture == null);
- }
-
- @Override
- public void deleteSurfaceTexture() {
- cleanupSurfaceTexture();
- return;
- }
-
- public static void cleanupSurfaceTexture() {
- mSurfaceTexture = null;
- mVideoLayerUsingSurfaceTexture = -1;
- return;
- }
-
- @Override
- public int getTextureName() {
- if (mTextureNames != null) {
- return mTextureNames[0];
- } else {
- return 0;
- }
- }
-
- private void setFrameAvailableListener(SurfaceTexture.OnFrameAvailableListener l) {
- if (mSurfaceTexture != null) {
- mSurfaceTexture.setOnFrameAvailableListener(l);
- }
- }
-
-}
+++ /dev/null
-/*
- * Copyright (C) 2012 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.media.MediaPlayer;
-import android.net.Uri;
-import android.webkit.HTML5VideoViewProxy;
-import java.io.IOException;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Timer;
-import java.util.TimerTask;
-
-/**
- * @hide This is only used by the browser
- */
-public class HTML5VideoView implements MediaPlayer.OnPreparedListener {
-
- protected static final String LOGTAG = "HTML5VideoView";
-
- protected static final String COOKIE = "Cookie";
- protected static final String HIDE_URL_LOGS = "x-hide-urls-from-log";
-
- // For handling the seekTo before prepared, we need to know whether or not
- // the video is prepared. Therefore, we differentiate the state between
- // prepared and not prepared.
- // When the video is not prepared, we will have to save the seekTo time,
- // and use it when prepared to play.
- // NOTE: these values are in sync with VideoLayerAndroid.h in webkit side.
- // Please keep them in sync when changed.
- static final int STATE_INITIALIZED = 0;
- static final int STATE_PREPARING = 1;
- static final int STATE_PREPARED = 2;
- static final int STATE_PLAYING = 3;
- static final int STATE_RESETTED = 4;
- static final int STATE_RELEASED = 5;
-
- protected HTML5VideoViewProxy mProxy;
-
- // Save the seek time when not prepared. This can happen when switching
- // video besides initial load.
- protected int mSaveSeekTime;
-
- // This is used to find the VideoLayer on the native side.
- protected int mVideoLayerId;
-
- // Given the fact we only have one SurfaceTexture, we cannot support multiple
- // player at the same time. We may recreate a new one and abandon the old
- // one at transition time.
- protected static MediaPlayer mPlayer = null;
- protected static int mCurrentState = -1;
-
- // We need to save such info.
- protected Uri mUri;
- protected Map<String, String> mHeaders;
-
- // The timer for timeupate events.
- // See http://www.whatwg.org/specs/web-apps/current-work/#event-media-timeupdate
- protected static Timer mTimer;
-
- protected boolean mPauseDuringPreparing;
-
- // The spec says the timer should fire every 250 ms or less.
- private static final int TIMEUPDATE_PERIOD = 250; // ms
- private boolean mSkipPrepare = false;
-
- // common Video control FUNCTIONS:
- public void start() {
- if (mCurrentState == STATE_PREPARED) {
- // When replaying the same video, there is no onPrepared call.
- // Therefore, the timer should be set up here.
- if (mTimer == null)
- {
- mTimer = new Timer();
- mTimer.schedule(new TimeupdateTask(mProxy), TIMEUPDATE_PERIOD,
- TIMEUPDATE_PERIOD);
- }
- mPlayer.start();
- setPlayerBuffering(false);
- }
- }
-
- public void pause() {
- if (isPlaying()) {
- mPlayer.pause();
- } else if (mCurrentState == STATE_PREPARING) {
- mPauseDuringPreparing = true;
- }
- // Delete the Timer to stop it since there is no stop call.
- if (mTimer != null) {
- mTimer.purge();
- mTimer.cancel();
- mTimer = null;
- }
- }
-
- public int getDuration() {
- if (mCurrentState == STATE_PREPARED) {
- return mPlayer.getDuration();
- } else {
- return -1;
- }
- }
-
- public int getCurrentPosition() {
- if (mCurrentState == STATE_PREPARED) {
- return mPlayer.getCurrentPosition();
- }
- return 0;
- }
-
- public void seekTo(int pos) {
- if (mCurrentState == STATE_PREPARED)
- mPlayer.seekTo(pos);
- else
- mSaveSeekTime = pos;
- }
-
- public boolean isPlaying() {
- if (mCurrentState == STATE_PREPARED) {
- return mPlayer.isPlaying();
- } else {
- return false;
- }
- }
-
- public void reset() {
- if (mCurrentState < STATE_RESETTED) {
- mPlayer.reset();
- }
- mCurrentState = STATE_RESETTED;
- }
-
- public void stopPlayback() {
- if (mCurrentState == STATE_PREPARED) {
- mPlayer.stop();
- }
- }
-
- public static void release() {
- if (mPlayer != null && mCurrentState != STATE_RELEASED) {
- mPlayer.release();
- mPlayer = null;
- }
- mCurrentState = STATE_RELEASED;
- }
-
- public boolean isReleased() {
- return mCurrentState == STATE_RELEASED;
- }
-
- public boolean getPauseDuringPreparing() {
- return mPauseDuringPreparing;
- }
-
- // Every time we start a new Video, we create a VideoView and a MediaPlayer
- public void init(int videoLayerId, int position, boolean skipPrepare) {
- if (mPlayer == null) {
- mPlayer = new MediaPlayer();
- mCurrentState = STATE_INITIALIZED;
- }
- mSkipPrepare = skipPrepare;
- // If we want to skip the prepare, then we keep the state.
- if (!mSkipPrepare) {
- mCurrentState = STATE_INITIALIZED;
- }
- mProxy = null;
- mVideoLayerId = videoLayerId;
- mSaveSeekTime = position;
- mTimer = null;
- mPauseDuringPreparing = false;
- }
-
- protected HTML5VideoView() {
- }
-
- protected static Map<String, String> generateHeaders(String url,
- HTML5VideoViewProxy proxy) {
- boolean isPrivate = proxy.getWebView().isPrivateBrowsingEnabled();
- String cookieValue = CookieManager.getInstance().getCookie(url, isPrivate);
- Map<String, String> headers = new HashMap<String, String>();
- if (cookieValue != null) {
- headers.put(COOKIE, cookieValue);
- }
- if (isPrivate) {
- headers.put(HIDE_URL_LOGS, "true");
- }
-
- return headers;
- }
-
- public void setVideoURI(String uri, HTML5VideoViewProxy proxy) {
- // When switching players, surface texture will be reused.
- mUri = Uri.parse(uri);
- mHeaders = generateHeaders(uri, proxy);
- }
-
- // Listeners setup FUNCTIONS:
- public void setOnCompletionListener(HTML5VideoViewProxy proxy) {
- mPlayer.setOnCompletionListener(proxy);
- }
-
- public void setOnErrorListener(HTML5VideoViewProxy proxy) {
- mPlayer.setOnErrorListener(proxy);
- }
-
- public void setOnPreparedListener(HTML5VideoViewProxy proxy) {
- mProxy = proxy;
- mPlayer.setOnPreparedListener(this);
- }
-
- public void setOnInfoListener(HTML5VideoViewProxy proxy) {
- mPlayer.setOnInfoListener(proxy);
- }
-
- public void prepareDataCommon(HTML5VideoViewProxy proxy) {
- if (!mSkipPrepare) {
- try {
- mPlayer.reset();
- mPlayer.setDataSource(proxy.getContext(), mUri, mHeaders);
- mPlayer.prepareAsync();
- } catch (IllegalArgumentException e) {
- e.printStackTrace();
- } catch (IllegalStateException e) {
- e.printStackTrace();
- } catch (IOException e) {
- e.printStackTrace();
- }
- mCurrentState = STATE_PREPARING;
- } else {
- // If we skip prepare and the onPrepared happened in inline mode, we
- // don't need to call prepare again, we just need to call onPrepared
- // to refresh the state here.
- if (mCurrentState >= STATE_PREPARED) {
- onPrepared(mPlayer);
- }
- mSkipPrepare = false;
- }
- }
-
- public void reprepareData(HTML5VideoViewProxy proxy) {
- mPlayer.reset();
- prepareDataCommon(proxy);
- }
-
- // Normally called immediately after setVideoURI. But for full screen,
- // this should be after surface holder created
- public void prepareDataAndDisplayMode(HTML5VideoViewProxy proxy) {
- // SurfaceTexture will be created lazily here for inline mode
- decideDisplayMode();
-
- setOnCompletionListener(proxy);
- setOnPreparedListener(proxy);
- setOnErrorListener(proxy);
- setOnInfoListener(proxy);
-
- prepareDataCommon(proxy);
- }
-
-
- // Common code
- public int getVideoLayerId() {
- return mVideoLayerId;
- }
-
-
- public int getCurrentState() {
- if (isPlaying()) {
- return STATE_PLAYING;
- } else {
- return mCurrentState;
- }
- }
-
- private static final class TimeupdateTask extends TimerTask {
- private HTML5VideoViewProxy mProxy;
-
- public TimeupdateTask(HTML5VideoViewProxy proxy) {
- mProxy = proxy;
- }
-
- @Override
- public void run() {
- mProxy.onTimeupdate();
- }
- }
-
- @Override
- public void onPrepared(MediaPlayer mp) {
- mCurrentState = STATE_PREPARED;
- seekTo(mSaveSeekTime);
- if (mProxy != null) {
- mProxy.onPrepared(mp);
- }
- if (mPauseDuringPreparing) {
- pauseAndDispatch(mProxy);
- mPauseDuringPreparing = false;
- }
- }
-
- // Pause the play and update the play/pause button
- public void pauseAndDispatch(HTML5VideoViewProxy proxy) {
- pause();
- if (proxy != null) {
- proxy.dispatchOnPaused();
- }
- }
-
- // Below are functions that are different implementation on inline and full-
- // screen mode. Some are specific to one type, but currently are called
- // directly from the proxy.
- public void enterFullScreenVideoState(int layerId,
- HTML5VideoViewProxy proxy, WebViewClassic webView) {
- }
-
- public boolean isFullScreenMode() {
- return false;
- }
-
- public void decideDisplayMode() {
- }
-
- public boolean getReadyToUseSurfTex() {
- return false;
- }
-
- public void deleteSurfaceTexture() {
- }
-
- public int getTextureName() {
- return 0;
- }
-
- // This is true only when the player is buffering and paused
- public boolean mPlayerBuffering = false;
-
- public boolean getPlayerBuffering() {
- return mPlayerBuffering;
- }
-
- public void setPlayerBuffering(boolean playerBuffering) {
- mPlayerBuffering = playerBuffering;
- switchProgressView(playerBuffering);
- }
-
-
- protected void switchProgressView(boolean playerBuffering) {
- // Only used in HTML5VideoFullScreen
- }
-
- public boolean fullScreenExited() {
- // Only meaningful for HTML5VideoFullScreen
- return false;
- }
-
- private boolean mStartWhenPrepared = false;
-
- public void setStartWhenPrepared(boolean willPlay) {
- mStartWhenPrepared = willPlay;
- }
-
- public boolean getStartWhenPrepared() {
- return mStartWhenPrepared;
- }
-
- public void showControllerInFullScreen() {
- }
-
-}
+++ /dev/null
-/*
- * 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 android.webkit;
-
-import android.content.Context;
-import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
-import android.graphics.SurfaceTexture;
-import android.media.MediaPlayer;
-import android.net.http.EventHandler;
-import android.net.http.Headers;
-import android.net.http.RequestHandle;
-import android.net.http.RequestQueue;
-import android.net.http.SslCertificate;
-import android.net.http.SslError;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.Message;
-import android.util.Log;
-import android.view.KeyEvent;
-import android.view.View;
-
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.net.MalformedURLException;
-import java.net.URL;
-import java.util.HashMap;
-import java.util.Map;
-
-/**
- * <p>Proxy for HTML5 video views.
- */
-class HTML5VideoViewProxy extends Handler
- implements MediaPlayer.OnPreparedListener,
- MediaPlayer.OnCompletionListener,
- MediaPlayer.OnErrorListener,
- MediaPlayer.OnInfoListener,
- SurfaceTexture.OnFrameAvailableListener,
- View.OnKeyListener {
- // Logging tag.
- private static final String LOGTAG = "HTML5VideoViewProxy";
-
- // Message Ids for WebCore thread -> UI thread communication.
- private static final int PLAY = 100;
- private static final int SEEK = 101;
- private static final int PAUSE = 102;
- private static final int ERROR = 103;
- private static final int LOAD_DEFAULT_POSTER = 104;
- private static final int BUFFERING_START = 105;
- private static final int BUFFERING_END = 106;
- private static final int ENTER_FULLSCREEN = 107;
-
- // Message Ids to be handled on the WebCore thread
- private static final int PREPARED = 200;
- private static final int ENDED = 201;
- private static final int POSTER_FETCHED = 202;
- private static final int PAUSED = 203;
- private static final int STOPFULLSCREEN = 204;
- private static final int RESTORESTATE = 205;
-
- // Timer thread -> UI thread
- private static final int TIMEUPDATE = 300;
-
- // The C++ MediaPlayerPrivateAndroid object.
- int mNativePointer;
- // The handler for WebCore thread messages;
- private Handler mWebCoreHandler;
- // The WebViewClassic instance that created this view.
- private WebViewClassic mWebView;
- // The poster image to be shown when the video is not playing.
- // This ref prevents the bitmap from being GC'ed.
- private Bitmap mPoster;
- // The poster downloader.
- private PosterDownloader mPosterDownloader;
- // The seek position.
- private int mSeekPosition;
- // A helper class to control the playback. This executes on the UI thread!
- private static final class VideoPlayer {
- // The proxy that is currently playing (if any).
- private static HTML5VideoViewProxy mCurrentProxy;
- // The VideoView instance. This is a singleton for now, at least until
- // http://b/issue?id=1973663 is fixed.
- private static HTML5VideoView mHTML5VideoView;
-
- private static boolean isVideoSelfEnded = false;
-
- private static void setPlayerBuffering(boolean playerBuffering) {
- mHTML5VideoView.setPlayerBuffering(playerBuffering);
- }
-
- // Every time webView setBaseLayer, this will be called.
- // When we found the Video layer, then we set the Surface Texture to it.
- // Otherwise, we may want to delete the Surface Texture to save memory.
- public static void setBaseLayer(int layer) {
- // Don't do this for full screen mode.
- if (mHTML5VideoView != null
- && !mHTML5VideoView.isFullScreenMode()
- && !mHTML5VideoView.isReleased()) {
- int currentVideoLayerId = mHTML5VideoView.getVideoLayerId();
- SurfaceTexture surfTexture =
- HTML5VideoInline.getSurfaceTexture(currentVideoLayerId);
- int textureName = mHTML5VideoView.getTextureName();
-
- if (layer != 0 && surfTexture != null && currentVideoLayerId != -1) {
- int playerState = mHTML5VideoView.getCurrentState();
- if (mHTML5VideoView.getPlayerBuffering())
- playerState = HTML5VideoView.STATE_PREPARING;
- boolean foundInTree = nativeSendSurfaceTexture(surfTexture,
- layer, currentVideoLayerId, textureName,
- playerState);
- if (playerState >= HTML5VideoView.STATE_PREPARED
- && !foundInTree) {
- mHTML5VideoView.pauseAndDispatch(mCurrentProxy);
- }
- }
- }
- }
-
- // When a WebView is paused, we also want to pause the video in it.
- public static void pauseAndDispatch() {
- if (mHTML5VideoView != null) {
- mHTML5VideoView.pauseAndDispatch(mCurrentProxy);
- }
- }
-
- public static void enterFullScreenVideo(int layerId, String url,
- HTML5VideoViewProxy proxy, WebViewClassic webView) {
- // Save the inline video info and inherit it in the full screen
- int savePosition = 0;
- boolean canSkipPrepare = false;
- boolean forceStart = false;
- if (mHTML5VideoView != null) {
- // We don't allow enter full screen mode while the previous
- // full screen video hasn't finished yet.
- if (!mHTML5VideoView.fullScreenExited() && mHTML5VideoView.isFullScreenMode()) {
- Log.w(LOGTAG, "Try to reenter the full screen mode");
- return;
- }
- int playerState = mHTML5VideoView.getCurrentState();
- // If we are playing the same video, then it is better to
- // save the current position.
- if (layerId == mHTML5VideoView.getVideoLayerId()) {
- savePosition = mHTML5VideoView.getCurrentPosition();
- canSkipPrepare = (playerState == HTML5VideoView.STATE_PREPARING
- || playerState == HTML5VideoView.STATE_PREPARED
- || playerState == HTML5VideoView.STATE_PLAYING)
- && !mHTML5VideoView.isFullScreenMode();
- }
- if (!canSkipPrepare) {
- mHTML5VideoView.reset();
- } else {
- forceStart = playerState == HTML5VideoView.STATE_PREPARING
- || playerState == HTML5VideoView.STATE_PLAYING;
- }
- }
- mHTML5VideoView = new HTML5VideoFullScreen(proxy.getContext(),
- layerId, savePosition, canSkipPrepare);
- mHTML5VideoView.setStartWhenPrepared(forceStart);
- mCurrentProxy = proxy;
- mHTML5VideoView.setVideoURI(url, mCurrentProxy);
- mHTML5VideoView.enterFullScreenVideoState(layerId, proxy, webView);
- }
-
- public static void exitFullScreenVideo(HTML5VideoViewProxy proxy,
- WebViewClassic webView) {
- if (!mHTML5VideoView.fullScreenExited() && mHTML5VideoView.isFullScreenMode()) {
- WebChromeClient client = webView.getWebChromeClient();
- if (client != null) {
- if (DebugFlags.TRACE_CALLBACK) Log.d(CallbackProxy.LOGTAG, "onHideCustomView");
- client.onHideCustomView();
- }
- }
- }
-
- // This is on the UI thread.
- // When native tell Java to play, we need to check whether or not it is
- // still the same video by using videoLayerId and treat it differently.
- public static void play(String url, int time, HTML5VideoViewProxy proxy,
- WebChromeClient client, int videoLayerId) {
- int currentVideoLayerId = -1;
- boolean backFromFullScreenMode = false;
- if (mHTML5VideoView != null) {
- currentVideoLayerId = mHTML5VideoView.getVideoLayerId();
- backFromFullScreenMode = mHTML5VideoView.fullScreenExited();
-
- // When playing video back to back in full screen mode,
- // javascript will switch the src and call play.
- // In this case, we can just reuse the same full screen view,
- // and play the video after prepared.
- if (mHTML5VideoView.isFullScreenMode()
- && !backFromFullScreenMode
- && currentVideoLayerId != videoLayerId
- && mCurrentProxy != proxy) {
- mCurrentProxy = proxy;
- mHTML5VideoView.setStartWhenPrepared(true);
- mHTML5VideoView.setVideoURI(url, proxy);
- mHTML5VideoView.reprepareData(proxy);
- return;
- }
- }
-
- boolean skipPrepare = false;
- boolean createInlineView = false;
- if (backFromFullScreenMode
- && currentVideoLayerId == videoLayerId
- && !mHTML5VideoView.isReleased()) {
- skipPrepare = true;
- createInlineView = true;
- } else if(backFromFullScreenMode
- || currentVideoLayerId != videoLayerId
- || HTML5VideoInline.surfaceTextureDeleted()) {
- // Here, we handle the case when switching to a new video,
- // either inside a WebView or across WebViews
- // For switching videos within a WebView or across the WebView,
- // we need to pause the old one and re-create a new media player
- // inside the HTML5VideoView.
- if (mHTML5VideoView != null) {
- if (!backFromFullScreenMode) {
- mHTML5VideoView.pauseAndDispatch(mCurrentProxy);
- }
- mHTML5VideoView.reset();
- }
- createInlineView = true;
- }
- if (createInlineView) {
- mCurrentProxy = proxy;
- mHTML5VideoView = new HTML5VideoInline(videoLayerId, time, skipPrepare);
-
- mHTML5VideoView.setVideoURI(url, mCurrentProxy);
- mHTML5VideoView.prepareDataAndDisplayMode(proxy);
- return;
- }
-
- if (mCurrentProxy == proxy) {
- // Here, we handle the case when we keep playing with one video
- if (!mHTML5VideoView.isPlaying()) {
- mHTML5VideoView.seekTo(time);
- mHTML5VideoView.start();
- }
- } else if (mCurrentProxy != null) {
- // Some other video is already playing. Notify the caller that
- // its playback ended.
- proxy.dispatchOnEnded();
- }
- }
-
- public static boolean isPlaying(HTML5VideoViewProxy proxy) {
- return (mCurrentProxy == proxy && mHTML5VideoView != null
- && mHTML5VideoView.isPlaying());
- }
-
- public static int getCurrentPosition() {
- int currentPosMs = 0;
- if (mHTML5VideoView != null) {
- currentPosMs = mHTML5VideoView.getCurrentPosition();
- }
- return currentPosMs;
- }
-
- public static void seek(int time, HTML5VideoViewProxy proxy) {
- if (mCurrentProxy == proxy && time >= 0 && mHTML5VideoView != null) {
- mHTML5VideoView.seekTo(time);
- }
- }
-
- public static void pause(HTML5VideoViewProxy proxy) {
- if (mCurrentProxy == proxy && mHTML5VideoView != null) {
- mHTML5VideoView.pause();
- }
- }
-
- public static void onPrepared() {
- if (!mHTML5VideoView.isFullScreenMode()) {
- mHTML5VideoView.start();
- }
- }
-
- public static void end() {
- mHTML5VideoView.showControllerInFullScreen();
- if (mCurrentProxy != null) {
- if (isVideoSelfEnded)
- mCurrentProxy.dispatchOnEnded();
- else
- mCurrentProxy.dispatchOnPaused();
- }
- isVideoSelfEnded = false;
- }
- }
-
- // A bunch event listeners for our VideoView
- // MediaPlayer.OnPreparedListener
- @Override
- public void onPrepared(MediaPlayer mp) {
- VideoPlayer.onPrepared();
- Message msg = Message.obtain(mWebCoreHandler, PREPARED);
- Map<String, Object> map = new HashMap<String, Object>();
- map.put("dur", new Integer(mp.getDuration()));
- map.put("width", new Integer(mp.getVideoWidth()));
- map.put("height", new Integer(mp.getVideoHeight()));
- msg.obj = map;
- mWebCoreHandler.sendMessage(msg);
- }
-
- // MediaPlayer.OnCompletionListener;
- @Override
- public void onCompletion(MediaPlayer mp) {
- // The video ended by itself, so we need to
- // send a message to the UI thread to dismiss
- // the video view and to return to the WebView.
- // arg1 == 1 means the video ends by itself.
- sendMessage(obtainMessage(ENDED, 1, 0));
- }
-
- // MediaPlayer.OnErrorListener
- @Override
- public boolean onError(MediaPlayer mp, int what, int extra) {
- sendMessage(obtainMessage(ERROR));
- return false;
- }
-
- public void dispatchOnEnded() {
- Message msg = Message.obtain(mWebCoreHandler, ENDED);
- mWebCoreHandler.sendMessage(msg);
- }
-
- public void dispatchOnPaused() {
- Message msg = Message.obtain(mWebCoreHandler, PAUSED);
- mWebCoreHandler.sendMessage(msg);
- }
-
- public void dispatchOnStopFullScreen(boolean stillPlaying) {
- Message msg = Message.obtain(mWebCoreHandler, STOPFULLSCREEN);
- msg.arg1 = stillPlaying ? 1 : 0;
- mWebCoreHandler.sendMessage(msg);
- }
-
- public void dispatchOnRestoreState() {
- Message msg = Message.obtain(mWebCoreHandler, RESTORESTATE);
- mWebCoreHandler.sendMessage(msg);
- }
-
- public void onTimeupdate() {
- sendMessage(obtainMessage(TIMEUPDATE));
- }
-
- // When there is a frame ready from surface texture, we should tell WebView
- // to refresh.
- @Override
- public void onFrameAvailable(SurfaceTexture surfaceTexture) {
- // TODO: This should support partial invalidation too.
- mWebView.invalidate();
- }
-
- // Handler for the messages from WebCore or Timer thread to the UI thread.
- @Override
- public void handleMessage(Message msg) {
- // This executes on the UI thread.
- switch (msg.what) {
- case PLAY: {
- String url = (String) msg.obj;
- WebChromeClient client = mWebView.getWebChromeClient();
- int videoLayerID = msg.arg1;
- if (client != null) {
- VideoPlayer.play(url, mSeekPosition, this, client, videoLayerID);
- }
- break;
- }
- case ENTER_FULLSCREEN:{
- String url = (String) msg.obj;
- WebChromeClient client = mWebView.getWebChromeClient();
- int videoLayerID = msg.arg1;
- if (client != null) {
- VideoPlayer.enterFullScreenVideo(videoLayerID, url, this, mWebView);
- }
- break;
- }
- case SEEK: {
- Integer time = (Integer) msg.obj;
- mSeekPosition = time;
- VideoPlayer.seek(mSeekPosition, this);
- break;
- }
- case PAUSE: {
- VideoPlayer.pause(this);
- break;
- }
- case ENDED:
- if (msg.arg1 == 1)
- VideoPlayer.isVideoSelfEnded = true;
- VideoPlayer.end();
- break;
- case ERROR: {
- WebChromeClient client = mWebView.getWebChromeClient();
- if (client != null) {
- if (DebugFlags.TRACE_CALLBACK) Log.d(CallbackProxy.LOGTAG, "onHideCustomView");
- client.onHideCustomView();
- }
- break;
- }
- case LOAD_DEFAULT_POSTER: {
- WebChromeClient client = mWebView.getWebChromeClient();
- if (client != null) {
- if (DebugFlags.TRACE_CALLBACK) {
- Log.d(CallbackProxy.LOGTAG, "getDefaultVideoPoster");
- }
- doSetPoster(client.getDefaultVideoPoster());
- }
- break;
- }
- case TIMEUPDATE: {
- if (VideoPlayer.isPlaying(this)) {
- sendTimeupdate();
- }
- break;
- }
- case BUFFERING_START: {
- VideoPlayer.setPlayerBuffering(true);
- break;
- }
- case BUFFERING_END: {
- VideoPlayer.setPlayerBuffering(false);
- break;
- }
- }
- }
-
- // Everything below this comment executes on the WebCore thread, except for
- // the EventHandler methods, which are called on the network thread.
-
- // A helper class that knows how to download posters
- private static final class PosterDownloader implements EventHandler {
- // The request queue. This is static as we have one queue for all posters.
- private static RequestQueue mRequestQueue;
- private static int mQueueRefCount = 0;
- // The poster URL
- private URL mUrl;
- // The proxy we're doing this for.
- private final HTML5VideoViewProxy mProxy;
- // The poster bytes. We only touch this on the network thread.
- private ByteArrayOutputStream mPosterBytes;
- // The request handle. We only touch this on the WebCore thread.
- private RequestHandle mRequestHandle;
- // The response status code.
- private int mStatusCode;
- // The response headers.
- private Headers mHeaders;
- // The handler to handle messages on the WebCore thread.
- private Handler mHandler;
-
- public PosterDownloader(String url, HTML5VideoViewProxy proxy) {
- try {
- mUrl = new URL(url);
- } catch (MalformedURLException e) {
- mUrl = null;
- }
- mProxy = proxy;
- mHandler = new Handler();
- }
- // Start the download. Called on WebCore thread.
- public void start() {
- retainQueue();
-
- if (mUrl == null) {
- return;
- }
-
- // Only support downloading posters over http/https.
- // FIXME: Add support for other schemes. WebKit seems able to load
- // posters over other schemes e.g. file://, but gets the dimensions wrong.
- String protocol = mUrl.getProtocol();
- if ("http".equals(protocol) || "https".equals(protocol)) {
- mRequestHandle = mRequestQueue.queueRequest(mUrl.toString(), "GET", null,
- this, null, 0);
- }
- }
- // Cancel the download if active and release the queue. Called on WebCore thread.
- public void cancelAndReleaseQueue() {
- if (mRequestHandle != null) {
- mRequestHandle.cancel();
- mRequestHandle = null;
- }
- releaseQueue();
- }
- // EventHandler methods. Executed on the network thread.
- @Override
- public void status(int major_version,
- int minor_version,
- int code,
- String reason_phrase) {
- mStatusCode = code;
- }
-
- @Override
- public void headers(Headers headers) {
- mHeaders = headers;
- }
-
- @Override
- public void data(byte[] data, int len) {
- if (mPosterBytes == null) {
- mPosterBytes = new ByteArrayOutputStream();
- }
- mPosterBytes.write(data, 0, len);
- }
-
- @Override
- public void endData() {
- if (mStatusCode == 200) {
- if (mPosterBytes.size() > 0) {
- Bitmap poster = BitmapFactory.decodeByteArray(
- mPosterBytes.toByteArray(), 0, mPosterBytes.size());
- mProxy.doSetPoster(poster);
- }
- cleanup();
- } else if (mStatusCode >= 300 && mStatusCode < 400) {
- // We have a redirect.
- try {
- mUrl = new URL(mHeaders.getLocation());
- } catch (MalformedURLException e) {
- mUrl = null;
- }
- if (mUrl != null) {
- mHandler.post(new Runnable() {
- @Override
- public void run() {
- if (mRequestHandle != null) {
- mRequestHandle.setupRedirect(mUrl.toString(), mStatusCode,
- new HashMap<String, String>());
- }
- }
- });
- }
- }
- }
-
- @Override
- public void certificate(SslCertificate certificate) {
- // Don't care.
- }
-
- @Override
- public void error(int id, String description) {
- cleanup();
- }
-
- @Override
- public boolean handleSslErrorRequest(SslError error) {
- // Don't care. If this happens, data() will never be called so
- // mPosterBytes will never be created, so no need to call cleanup.
- return false;
- }
- // Tears down the poster bytes stream. Called on network thread.
- private void cleanup() {
- if (mPosterBytes != null) {
- try {
- mPosterBytes.close();
- } catch (IOException ignored) {
- // Ignored.
- } finally {
- mPosterBytes = null;
- }
- }
- }
-
- // Queue management methods. Called on WebCore thread.
- private void retainQueue() {
- if (mRequestQueue == null) {
- mRequestQueue = new RequestQueue(mProxy.getContext());
- }
- mQueueRefCount++;
- }
-
- private void releaseQueue() {
- if (mQueueRefCount == 0) {
- return;
- }
- if (--mQueueRefCount == 0) {
- mRequestQueue.shutdown();
- mRequestQueue = null;
- }
- }
- }
-
- /**
- * Private constructor.
- * @param webView is the WebView that hosts the video.
- * @param nativePtr is the C++ pointer to the MediaPlayerPrivate object.
- */
- private HTML5VideoViewProxy(WebViewClassic webView, int nativePtr) {
- // This handler is for the main (UI) thread.
- super(Looper.getMainLooper());
- // Save the WebView object.
- mWebView = webView;
- // Pass Proxy into webview, such that every time we have a setBaseLayer
- // call, we tell this Proxy to call the native to update the layer tree
- // for the Video Layer's surface texture info
- mWebView.setHTML5VideoViewProxy(this);
- // Save the native ptr
- mNativePointer = nativePtr;
- // create the message handler for this thread
- createWebCoreHandler();
- }
-
- private void createWebCoreHandler() {
- mWebCoreHandler = new Handler() {
- @Override
- public void handleMessage(Message msg) {
- switch (msg.what) {
- case PREPARED: {
- Map<String, Object> map = (Map<String, Object>) msg.obj;
- Integer duration = (Integer) map.get("dur");
- Integer width = (Integer) map.get("width");
- Integer height = (Integer) map.get("height");
- nativeOnPrepared(duration.intValue(), width.intValue(),
- height.intValue(), mNativePointer);
- break;
- }
- case ENDED:
- mSeekPosition = 0;
- nativeOnEnded(mNativePointer);
- break;
- case PAUSED:
- nativeOnPaused(mNativePointer);
- break;
- case POSTER_FETCHED:
- Bitmap poster = (Bitmap) msg.obj;
- nativeOnPosterFetched(poster, mNativePointer);
- break;
- case TIMEUPDATE:
- nativeOnTimeupdate(msg.arg1, mNativePointer);
- break;
- case STOPFULLSCREEN:
- nativeOnStopFullscreen(msg.arg1, mNativePointer);
- break;
- case RESTORESTATE:
- nativeOnRestoreState(mNativePointer);
- break;
- }
- }
- };
- }
-
- private void doSetPoster(Bitmap poster) {
- if (poster == null) {
- return;
- }
- // Save a ref to the bitmap and send it over to the WebCore thread.
- mPoster = poster;
- Message msg = Message.obtain(mWebCoreHandler, POSTER_FETCHED);
- msg.obj = poster;
- mWebCoreHandler.sendMessage(msg);
- }
-
- private void sendTimeupdate() {
- Message msg = Message.obtain(mWebCoreHandler, TIMEUPDATE);
- msg.arg1 = VideoPlayer.getCurrentPosition();
- mWebCoreHandler.sendMessage(msg);
- }
-
- public Context getContext() {
- return mWebView.getContext();
- }
-
- // The public methods below are all called from WebKit only.
- /**
- * Play a video stream.
- * @param url is the URL of the video stream.
- */
- public void play(String url, int position, int videoLayerID) {
- if (url == null) {
- return;
- }
-
- if (position > 0) {
- seek(position);
- }
- Message message = obtainMessage(PLAY);
- message.arg1 = videoLayerID;
- message.obj = url;
- sendMessage(message);
- }
-
- /**
- * Play a video stream in full screen mode.
- * @param url is the URL of the video stream.
- */
- public void enterFullscreenForVideoLayer(String url, int videoLayerID) {
- if (url == null) {
- return;
- }
-
- Message message = obtainMessage(ENTER_FULLSCREEN);
- message.arg1 = videoLayerID;
- message.obj = url;
- sendMessage(message);
- }
-
- /**
- * Seek into the video stream.
- * @param time is the position in the video stream.
- */
- public void seek(int time) {
- Message message = obtainMessage(SEEK);
- message.obj = new Integer(time);
- sendMessage(message);
- }
-
- /**
- * Pause the playback.
- */
- public void pause() {
- Message message = obtainMessage(PAUSE);
- sendMessage(message);
- }
-
- /**
- * Tear down this proxy object.
- */
- public void teardown() {
- // This is called by the C++ MediaPlayerPrivate dtor.
- // Cancel any active poster download.
- if (mPosterDownloader != null) {
- mPosterDownloader.cancelAndReleaseQueue();
- }
- mNativePointer = 0;
- }
-
- /**
- * Load the poster image.
- * @param url is the URL of the poster image.
- */
- public void loadPoster(String url) {
- if (url == null) {
- Message message = obtainMessage(LOAD_DEFAULT_POSTER);
- sendMessage(message);
- return;
- }
- // Cancel any active poster download.
- if (mPosterDownloader != null) {
- mPosterDownloader.cancelAndReleaseQueue();
- }
- // Load the poster asynchronously
- mPosterDownloader = new PosterDownloader(url, this);
- mPosterDownloader.start();
- }
-
- // These three function are called from UI thread only by WebView.
- public void setBaseLayer(int layer) {
- VideoPlayer.setBaseLayer(layer);
- }
-
- public void pauseAndDispatch() {
- VideoPlayer.pauseAndDispatch();
- }
-
- public void enterFullScreenVideo(int layerId, String url) {
- VideoPlayer.enterFullScreenVideo(layerId, url, this, mWebView);
- }
-
- public void exitFullScreenVideo() {
- VideoPlayer.exitFullScreenVideo(this, mWebView);
- }
-
- /**
- * The factory for HTML5VideoViewProxy instances.
- * @param webViewCore is the WebViewCore that is requesting the proxy.
- *
- * @return a new HTML5VideoViewProxy object.
- */
- public static HTML5VideoViewProxy getInstance(WebViewCore webViewCore, int nativePtr) {
- return new HTML5VideoViewProxy(webViewCore.getWebViewClassic(), nativePtr);
- }
-
- /* package */ WebViewClassic getWebView() {
- return mWebView;
- }
-
- private native void nativeOnPrepared(int duration, int width, int height, int nativePointer);
- private native void nativeOnEnded(int nativePointer);
- private native void nativeOnPaused(int nativePointer);
- private native void nativeOnPosterFetched(Bitmap poster, int nativePointer);
- private native void nativeOnTimeupdate(int position, int nativePointer);
- private native void nativeOnStopFullscreen(int stillPlaying, int nativePointer);
- private native void nativeOnRestoreState(int nativePointer);
- private native static boolean nativeSendSurfaceTexture(SurfaceTexture texture,
- int baseLayer, int videoLayerId, int textureName,
- int playerState);
-
- @Override
- public boolean onInfo(MediaPlayer mp, int what, int extra) {
- if (what == MediaPlayer.MEDIA_INFO_BUFFERING_START) {
- sendMessage(obtainMessage(BUFFERING_START, what, extra));
- } else if (what == MediaPlayer.MEDIA_INFO_BUFFERING_END) {
- sendMessage(obtainMessage(BUFFERING_END, what, extra));
- }
- return false;
- }
-
- @Override
- public boolean onKey(View v, int keyCode, KeyEvent event) {
- if (keyCode == KeyEvent.KEYCODE_BACK) {
- if (event.getAction() == KeyEvent.ACTION_DOWN) {
- return true;
- } else if (event.getAction() == KeyEvent.ACTION_UP && !event.isCanceled()) {
- VideoPlayer.exitFullScreenVideo(this, mWebView);
- return true;
- }
- }
- return false;
- }
-}
+++ /dev/null
-/*
- * Copyright (C) 2006 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.net.ProxyProperties;
-import android.net.Uri;
-import android.os.Handler;
-import android.os.Message;
-import android.util.Log;
-
-import java.lang.ref.WeakReference;
-import java.util.HashMap;
-import java.util.Set;
-
-final class JWebCoreJavaBridge extends Handler {
- // Identifier for the timer message.
- private static final int TIMER_MESSAGE = 1;
- // ID for servicing functionptr queue
- private static final int FUNCPTR_MESSAGE = 2;
- // Log system identifier.
- private static final String LOGTAG = "webkit-timers";
-
- // Native object pointer for interacting in native code.
- private int mNativeBridge;
- // Instant timer is used to implement a timer that needs to fire almost
- // immediately.
- private boolean mHasInstantTimer;
-
- private boolean mTimerPaused;
- private boolean mHasDeferredTimers;
-
- // keep track of the main WebViewClassic attached to the current window so that we
- // can get the proper Context.
- private static WeakReference<WebViewClassic> sCurrentMainWebView =
- new WeakReference<WebViewClassic>(null);
-
- /* package */
- static final int REFRESH_PLUGINS = 100;
-
- private HashMap<String, String> mContentUriToFilePathMap;
-
- /**
- * Construct a new JWebCoreJavaBridge to interface with
- * WebCore timers and cookies.
- */
- public JWebCoreJavaBridge() {
- nativeConstructor();
-
- }
-
- @Override
- protected void finalize() {
- nativeFinalize();
- }
-
- static synchronized void setActiveWebView(WebViewClassic webview) {
- if (sCurrentMainWebView.get() != null) {
- // it is possible if there is a sub-WebView. Do nothing.
- return;
- }
- sCurrentMainWebView = new WeakReference<WebViewClassic>(webview);
- }
-
- static synchronized void removeActiveWebView(WebViewClassic webview) {
- if (sCurrentMainWebView.get() != webview) {
- // it is possible if there is a sub-WebView. Do nothing.
- return;
- }
- sCurrentMainWebView.clear();
- }
-
- /**
- * Call native timer callbacks.
- */
- private void fireSharedTimer() {
- // clear the flag so that sharedTimerFired() can set a new timer
- mHasInstantTimer = false;
- sharedTimerFired();
- }
-
- /**
- * handleMessage
- * @param msg The dispatched message.
- *
- * The only accepted message currently is TIMER_MESSAGE
- */
- @Override
- public void handleMessage(Message msg) {
- switch (msg.what) {
- case TIMER_MESSAGE: {
- if (mTimerPaused) {
- mHasDeferredTimers = true;
- } else {
- fireSharedTimer();
- }
- break;
- }
- case FUNCPTR_MESSAGE:
- nativeServiceFuncPtrQueue();
- break;
- case REFRESH_PLUGINS:
- nativeUpdatePluginDirectories(PluginManager.getInstance(null)
- .getPluginDirectories(), ((Boolean) msg.obj)
- .booleanValue());
- break;
- }
- }
-
- // called from JNI side
- private void signalServiceFuncPtrQueue() {
- Message msg = obtainMessage(FUNCPTR_MESSAGE);
- sendMessage(msg);
- }
-
- private native void nativeServiceFuncPtrQueue();
-
- /**
- * Pause all timers.
- */
- public void pause() {
- if (!mTimerPaused) {
- mTimerPaused = true;
- mHasDeferredTimers = false;
- }
- }
-
- /**
- * Resume all timers.
- */
- public void resume() {
- if (mTimerPaused) {
- mTimerPaused = false;
- if (mHasDeferredTimers) {
- mHasDeferredTimers = false;
- fireSharedTimer();
- }
- }
- }
-
- /**
- * Set WebCore cache size.
- * @param bytes The cache size in bytes.
- */
- public native void setCacheSize(int bytes);
-
- /**
- * Store a cookie string associated with a url.
- * @param url The url to be used as a key for the cookie.
- * @param value The cookie string to be stored.
- */
- private void setCookies(String url, String value) {
- if (value.contains("\r") || value.contains("\n")) {
- // for security reason, filter out '\r' and '\n' from the cookie
- int size = value.length();
- StringBuilder buffer = new StringBuilder(size);
- int i = 0;
- while (i != -1 && i < size) {
- int ir = value.indexOf('\r', i);
- int in = value.indexOf('\n', i);
- int newi = (ir == -1) ? in : (in == -1 ? ir : (ir < in ? ir
- : in));
- if (newi > i) {
- buffer.append(value.subSequence(i, newi));
- } else if (newi == -1) {
- buffer.append(value.subSequence(i, size));
- break;
- }
- i = newi + 1;
- }
- value = buffer.toString();
- }
- CookieManager.getInstance().setCookie(url, value);
- }
-
- /**
- * Retrieve the cookie string for the given url.
- * @param url The resource's url.
- * @return A String representing the cookies for the given resource url.
- */
- private String cookies(String url) {
- return CookieManager.getInstance().getCookie(url);
- }
-
- /**
- * Returns whether cookies are enabled or not.
- */
- private boolean cookiesEnabled() {
- return CookieManager.getInstance().acceptCookie();
- }
-
- /**
- * Returns an array of plugin directoies
- */
- private String[] getPluginDirectories() {
- return PluginManager.getInstance(null).getPluginDirectories();
- }
-
- /**
- * Returns the path of the plugin data directory
- */
- private String getPluginSharedDataDirectory() {
- return PluginManager.getInstance(null).getPluginSharedDataDirectory();
- }
-
- /**
- * setSharedTimer
- * @param timemillis The relative time when the timer should fire
- */
- private void setSharedTimer(long timemillis) {
- if (DebugFlags.J_WEB_CORE_JAVA_BRIDGE) Log.v(LOGTAG, "setSharedTimer " + timemillis);
-
- if (timemillis <= 0) {
- // we don't accumulate the sharedTimer unless it is a delayed
- // request. This way we won't flood the message queue with
- // WebKit messages. This should improve the browser's
- // responsiveness to key events.
- if (mHasInstantTimer) {
- return;
- } else {
- mHasInstantTimer = true;
- Message msg = obtainMessage(TIMER_MESSAGE);
- sendMessageDelayed(msg, timemillis);
- }
- } else {
- Message msg = obtainMessage(TIMER_MESSAGE);
- sendMessageDelayed(msg, timemillis);
- }
- }
-
- /**
- * Stop the shared timer.
- */
- private void stopSharedTimer() {
- if (DebugFlags.J_WEB_CORE_JAVA_BRIDGE) {
- Log.v(LOGTAG, "stopSharedTimer removing all timers");
- }
- removeMessages(TIMER_MESSAGE);
- mHasInstantTimer = false;
- mHasDeferredTimers = false;
- }
-
- private String[] getKeyStrengthList() {
- return CertTool.getKeyStrengthList();
- }
-
- synchronized private String getSignedPublicKey(int index, String challenge,
- String url) {
- WebViewClassic current = sCurrentMainWebView.get();
- if (current != null) {
- // generateKeyPair expects organizations which we don't have. Ignore
- // url.
- return CertTool.getSignedPublicKey(
- current.getContext(), index, challenge);
- } else {
- Log.e(LOGTAG, "There is no active WebView for getSignedPublicKey");
- return "";
- }
- }
-
- // Called on the WebCore thread through JNI.
- private String resolveFilePathForContentUri(String uri) {
- if (mContentUriToFilePathMap != null) {
- String fileName = mContentUriToFilePathMap.get(uri);
- if (fileName != null) {
- return fileName;
- }
- }
-
- // Failsafe fallback to just use the last path segment.
- // (See OpenableColumns documentation in the SDK)
- Uri jUri = Uri.parse(uri);
- return jUri.getLastPathSegment();
- }
-
- public void storeFilePathForContentUri(String path, String contentUri) {
- if (mContentUriToFilePathMap == null) {
- mContentUriToFilePathMap = new HashMap<String, String>();
- }
- mContentUriToFilePathMap.put(contentUri, path);
- }
-
- public void updateProxy(ProxyProperties proxyProperties) {
- if (proxyProperties == null) {
- nativeUpdateProxy("", "");
- return;
- }
-
- String host = proxyProperties.getHost();
- int port = proxyProperties.getPort();
- if (port != 0)
- host += ":" + port;
-
- nativeUpdateProxy(host, proxyProperties.getExclusionList());
- }
-
- private native void nativeConstructor();
- private native void nativeFinalize();
- private native void sharedTimerFired();
- private native void nativeUpdatePluginDirectories(String[] directories,
- boolean reload);
- public native void setNetworkOnLine(boolean online);
- public native void setNetworkType(String type, String subtype);
- public native void addPackageNames(Set<String> packageNames);
- public native void addPackageName(String packageName);
- public native void removePackageName(String packageName);
- public native void nativeUpdateProxy(String newProxy, String exclusionList);
-}
+++ /dev/null
-/*
- * Copyright (C) 2010 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.app.ActivityManager;
-import android.content.Context;
-import android.net.Uri;
-import android.provider.Settings;
-import android.util.Log;
-
-import java.io.File;
-import java.io.InputStream;
-
-class JniUtil {
-
- static {
- System.loadLibrary("webcore");
- System.loadLibrary("chromium_net");
- }
- private static final String LOGTAG = "webkit";
- private JniUtil() {} // Utility class, do not instantiate.
-
- // Used by the Chromium HTTP stack.
- private static String sDatabaseDirectory;
- private static String sCacheDirectory;
- private static Context sContext;
-
- private static void checkInitialized() {
- if (sContext == null) {
- throw new IllegalStateException("Call CookieSyncManager::createInstance() or create a webview before using this class");
- }
- }
-
- protected static synchronized void setContext(Context context) {
- if (sContext != null) {
- return;
- }
-
- sContext = context.getApplicationContext();
- }
-
- protected static synchronized Context getContext() {
- return sContext;
- }
-
- /**
- * Called by JNI. Gets the application's database directory, excluding the trailing slash.
- * @return String The application's database directory
- */
- private static synchronized String getDatabaseDirectory() {
- checkInitialized();
-
- if (sDatabaseDirectory == null) {
- sDatabaseDirectory = sContext.getDatabasePath("dummy").getParent();
- }
-
- return sDatabaseDirectory;
- }
-
- /**
- * Called by JNI. Gets the application's cache directory, excluding the trailing slash.
- * @return String The application's cache directory
- */
- private static synchronized String getCacheDirectory() {
- checkInitialized();
-
- if (sCacheDirectory == null) {
- File cacheDir = sContext.getCacheDir();
- if (cacheDir == null) {
- sCacheDirectory = "";
- } else {
- sCacheDirectory = cacheDir.getAbsolutePath();
- }
- }
-
- return sCacheDirectory;
- }
-
- /**
- * Called by JNI. Gets the application's package name.
- * @return String The application's package name
- */
- private static synchronized String getPackageName() {
- checkInitialized();
-
- return sContext.getPackageName();
- }
-
- private static final String ANDROID_CONTENT = URLUtil.CONTENT_BASE;
-
- /**
- * Called by JNI. Calculates the size of an input stream by reading it.
- * @return long The size of the stream
- */
- private static synchronized long contentUrlSize(String url) {
- // content://
- if (url.startsWith(ANDROID_CONTENT)) {
- try {
- // Strip off MIME type. If we don't do this, we can fail to
- // load Gmail attachments, because the URL being loaded doesn't
- // exactly match the URL we have permission to read.
- int mimeIndex = url.lastIndexOf('?');
- if (mimeIndex != -1) {
- url = url.substring(0, mimeIndex);
- }
- Uri uri = Uri.parse(url);
- InputStream is = sContext.getContentResolver().openInputStream(uri);
- byte[] buffer = new byte[1024];
- int n;
- long size = 0;
- try {
- while ((n = is.read(buffer)) != -1) {
- size += n;
- }
- } finally {
- is.close();
- }
- return size;
- } catch (Exception e) {
- Log.e(LOGTAG, "Exception: " + url);
- return 0;
- }
- } else {
- return 0;
- }
- }
-
- /**
- * Called by JNI.
- *
- * @return Opened input stream to content
- * TODO: Make all content loading use this instead of BrowserFrame.java
- */
- private static synchronized InputStream contentUrlStream(String url) {
- // content://
- if (url.startsWith(ANDROID_CONTENT)) {
- try {
- // Strip off mimetype, for compatibility with ContentLoader.java
- // (used with Android HTTP stack, now removed).
- // If we don't do this, we can fail to load Gmail attachments,
- // because the URL being loaded doesn't exactly match the URL we
- // have permission to read.
- int mimeIndex = url.lastIndexOf('?');
- if (mimeIndex != -1) {
- url = url.substring(0, mimeIndex);
- }
- Uri uri = Uri.parse(url);
- return sContext.getContentResolver().openInputStream(uri);
- } catch (Exception e) {
- Log.e(LOGTAG, "Exception: " + url);
- return null;
- }
- } else {
- return null;
- }
- }
-
- private static synchronized String getAutofillQueryUrl() {
- checkInitialized();
- // If the device has not checked in it won't have pulled down the system setting for the
- // Autofill Url. In that case we will not make autofill server requests.
- return Settings.Global.getString(sContext.getContentResolver(),
- Settings.Global.WEB_AUTOFILL_QUERY_URL);
- }
-
- private static boolean canSatisfyMemoryAllocation(long bytesRequested) {
- checkInitialized();
- ActivityManager manager = (ActivityManager) sContext.getSystemService(
- Context.ACTIVITY_SERVICE);
- ActivityManager.MemoryInfo memInfo = new ActivityManager.MemoryInfo();
- manager.getMemoryInfo(memInfo);
- long leftToAllocate = memInfo.availMem - memInfo.threshold;
- return !memInfo.lowMemory && bytesRequested < leftToAllocate;
- }
-}
+++ /dev/null
-/*
- * 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.content.Context;
-import android.os.Handler;
-import android.util.Log;
-
-/**
- * KeyStoreHandler: class responsible for certificate installation to
- * the system key store. It reads the certificates file from network
- * then pass the bytes to class CertTool.
- * This class is only needed if the Chromium HTTP stack is used.
- */
-class KeyStoreHandler extends Handler {
- private static final String LOGTAG = "KeyStoreHandler";
-
- private final ByteArrayBuilder mDataBuilder = new ByteArrayBuilder();
-
- private String mMimeType;
-
- public KeyStoreHandler(String mimeType) {
- mMimeType = mimeType;
- }
-
- /**
- * Add data to the internal collection of data.
- * @param data A byte array containing the content.
- * @param length The length of data.
- */
- public void didReceiveData(byte[] data, int length) {
- synchronized (mDataBuilder) {
- mDataBuilder.append(data, 0, length);
- }
- }
-
- public void installCert(Context context) {
- String type = CertTool.getCertType(mMimeType);
- if (type == null) return;
-
- // This must be synchronized so that no more data can be added
- // after getByteSize returns.
- synchronized (mDataBuilder) {
- // In the case of downloading certificate, we will save it
- // to the KeyStore and stop the current loading so that it
- // will not generate a new history page
- byte[] cert = new byte[mDataBuilder.getByteSize()];
- int offset = 0;
- while (true) {
- ByteArrayBuilder.Chunk c = mDataBuilder.getFirstChunk();
- if (c == null) break;
-
- if (c.mLength != 0) {
- System.arraycopy(c.mArray, 0, cert, offset, c.mLength);
- offset += c.mLength;
- }
- c.release();
- }
- CertTool.addCertificate(context, type, cert);
- return;
- }
- }
-}
+++ /dev/null
-/*
- * Copyright (C) 2010 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.content.Context;
-
-import java.lang.ref.SoftReference;
-import java.util.Map;
-import java.util.HashMap;
-
-/**
- * @hide
- */
-public class L10nUtils {
-
- // These array elements must be kept in sync with those defined in
- // external/chromium/android/app/l10n_utils.h
- private static int[] mIdsArray = {
- com.android.internal.R.string.autofill_address_name_separator, // IDS_AUTOFILL_DIALOG_ADDRESS_NAME_SEPARATOR
- com.android.internal.R.string.autofill_address_summary_name_format, // IDS_AUTOFILL_DIALOG_ADDRESS_SUMMARY_NAME_FORMAT
- com.android.internal.R.string.autofill_address_summary_separator, // IDS_AUTOFILL_DIALOG_ADDRESS_SUMMARY_SEPARATOR
- com.android.internal.R.string.autofill_address_summary_format, // IDS_AUTOFILL_DIALOG_ADDRESS_SUMMARY_FORMAT
- com.android.internal.R.string.autofill_attention_ignored_re, // IDS_AUTOFILL_ATTENTION_IGNORED_RE
- com.android.internal.R.string.autofill_region_ignored_re, // IDS_AUTOFILL_REGION_IGNORED_RE
- com.android.internal.R.string.autofill_company_re, // IDS_AUTOFILL_COMPANY_RE
- com.android.internal.R.string.autofill_address_line_1_re, // IDS_AUTOFILL_ADDRESS_LINE_1_RE
- com.android.internal.R.string.autofill_address_line_1_label_re, // IDS_AUTOFILL_ADDRESS_LINE_1_LABEL_RE
- com.android.internal.R.string.autofill_address_line_2_re, // IDS_AUTOFILL_ADDRESS_LINE_2_RE
- com.android.internal.R.string.autofill_address_line_3_re, // IDS_AUTOFILL_ADDRESS_LINE_3_RE
- com.android.internal.R.string.autofill_country_re, // IDS_AUTOFILL_COUNTRY_RE
- com.android.internal.R.string.autofill_zip_code_re, // IDS_AUTOFILL_ZIP_CODE_RE
- com.android.internal.R.string.autofill_zip_4_re, // IDS_AUTOFILL_ZIP_4_RE
- com.android.internal.R.string.autofill_city_re, // IDS_AUTOFILL_CITY_RE
- com.android.internal.R.string.autofill_state_re, // IDS_AUTOFILL_STATE_RE
- com.android.internal.R.string.autofill_address_type_same_as_re, // IDS_AUTOFILL_SAME_AS_RE
- com.android.internal.R.string.autofill_address_type_use_my_re, // IDS_AUTOFILL_USE_MY_RE
- com.android.internal.R.string.autofill_billing_designator_re, // IDS_AUTOFILL_BILLING_DESIGNATOR_RE
- com.android.internal.R.string.autofill_shipping_designator_re, // IDS_AUTOFILL_SHIPPING_DESIGNATOR_RE
- com.android.internal.R.string.autofill_email_re, // IDS_AUTOFILL_EMAIL_RE
- com.android.internal.R.string.autofill_username_re, // IDS_AUTOFILL_USERNAME_RE
- com.android.internal.R.string.autofill_name_re, // IDS_AUTOFILL_NAME_RE
- com.android.internal.R.string.autofill_name_specific_re, // IDS_AUTOFILL_NAME_SPECIFIC_RE
- com.android.internal.R.string.autofill_first_name_re, // IDS_AUTOFILL_FIRST_NAME_RE
- com.android.internal.R.string.autofill_middle_initial_re, // IDS_AUTOFILL_MIDDLE_INITIAL_RE
- com.android.internal.R.string.autofill_middle_name_re, // IDS_AUTOFILL_MIDDLE_NAME_RE
- com.android.internal.R.string.autofill_last_name_re, // IDS_AUTOFILL_LAST_NAME_RE
- com.android.internal.R.string.autofill_phone_re, // IDS_AUTOFILL_PHONE_RE
- com.android.internal.R.string.autofill_area_code_re, // IDS_AUTOFILL_AREA_CODE_RE
- com.android.internal.R.string.autofill_phone_prefix_re, // IDS_AUTOFILL_PHONE_PREFIX_RE
- com.android.internal.R.string.autofill_phone_suffix_re, // IDS_AUTOFILL_PHONE_SUFFIX_RE
- com.android.internal.R.string.autofill_phone_extension_re, // IDS_AUTOFILL_PHONE_EXTENSION_RE
- com.android.internal.R.string.autofill_name_on_card_re, // IDS_AUTOFILL_NAME_ON_CARD_RE
- com.android.internal.R.string.autofill_name_on_card_contextual_re, // IDS_AUTOFILL_NAME_ON_CARD_CONTEXTUAL_RE
- com.android.internal.R.string.autofill_card_cvc_re, // IDS_AUTOFILL_CARD_CVC_RE
- com.android.internal.R.string.autofill_card_number_re, // IDS_AUTOFILL_CARD_NUMBER_RE
- com.android.internal.R.string.autofill_expiration_month_re, // IDS_AUTOFILL_EXPIRATION_MONTH_RE
- com.android.internal.R.string.autofill_expiration_date_re, // IDS_AUTOFILL_EXPIRATION_DATE_RE
- com.android.internal.R.string.autofill_card_ignored_re, // IDS_AUTOFILL_CARD_IGNORED_RE
- com.android.internal.R.string.autofill_fax_re, // IDS_AUTOFILL_FAX_RE
- com.android.internal.R.string.autofill_country_code_re, // IDS_AUTOFILL_COUNTRY_CODE_RE
- com.android.internal.R.string.autofill_area_code_notext_re, // IDS_AUTOFILL_AREA_CODE_NOTEXT_RE
- com.android.internal.R.string.autofill_phone_prefix_separator_re, // IDS_AUTOFILL_PHONE_PREFIX_SEPARATOR_RE
- com.android.internal.R.string.autofill_phone_suffix_separator_re, // IDS_AUTOFILL_PHONE_SUFFIX_SEPARATOR_RE
- com.android.internal.R.string.autofill_province, // IDS_AUTOFILL_DIALOG_PROVINCE
- com.android.internal.R.string.autofill_postal_code, // IDS_AUTOFILL_DIALOG_POSTAL_CODE
- com.android.internal.R.string.autofill_state, // IDS_AUTOFILL_DIALOG_STATE
- com.android.internal.R.string.autofill_zip_code, // IDS_AUTOFILL_DIALOG_ZIP_CODE
- com.android.internal.R.string.autofill_county, // IDS_AUTOFILL_DIALOG_COUNTY
- com.android.internal.R.string.autofill_island, // IDS_AUTOFILL_DIALOG_ISLAND
- com.android.internal.R.string.autofill_district, // IDS_AUTOFILL_DIALOG_DISTRICT
- com.android.internal.R.string.autofill_department, // IDS_AUTOFILL_DIALOG_DEPARTMENT
- com.android.internal.R.string.autofill_prefecture, // IDS_AUTOFILL_DIALOG_PREFECTURE
- com.android.internal.R.string.autofill_parish, // IDS_AUTOFILL_DIALOG_PARISH
- com.android.internal.R.string.autofill_area, // IDS_AUTOFILL_DIALOG_AREA
- com.android.internal.R.string.autofill_emirate // IDS_AUTOFILL_DIALOG_EMIRATE
- };
-
- private static Context mApplicationContext;
- private static Map<Integer, SoftReference<String> > mStrings;
-
- public static void setApplicationContext(Context applicationContext) {
- mApplicationContext = applicationContext.getApplicationContext();
- }
-
- private static String loadString(int id) {
- if (mStrings == null) {
- mStrings = new HashMap<Integer, SoftReference<String> >(mIdsArray.length);
- }
-
- String localisedString = mApplicationContext.getResources().getString(mIdsArray[id]);
- mStrings.put(id, new SoftReference<String>(localisedString));
- return localisedString;
- }
-
- public static String getLocalisedString(int id) {
- if (mStrings == null) {
- // This is the first time we need a localised string.
- // loadString will create the Map.
- return loadString(id);
- }
-
- SoftReference<String> ref = mStrings.get(id);
- boolean needToLoad = ref == null || ref.get() == null;
- return needToLoad ? loadString(id) : ref.get();
- }
-}
+++ /dev/null
-/*
- * 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 android.webkit;
-
-/**
- * Used to configure the mock Geolocation client for the LayoutTests.
- * @hide
- */
-public final class MockGeolocation {
- private WebViewCore mWebViewCore;
-
- public MockGeolocation(WebViewCore webViewCore) {
- mWebViewCore = webViewCore;
- }
-
- /**
- * Sets use of the mock Geolocation client. Also resets that client.
- */
- public void setUseMock() {
- nativeSetUseMock(mWebViewCore);
- }
-
- /**
- * Set the position for the mock Geolocation service.
- */
- public void setPosition(double latitude, double longitude, double accuracy) {
- // This should only ever be called on the WebKit thread.
- nativeSetPosition(mWebViewCore, latitude, longitude, accuracy);
- }
-
- /**
- * Set the error for the mock Geolocation service.
- */
- public void setError(int code, String message) {
- // This should only ever be called on the WebKit thread.
- nativeSetError(mWebViewCore, code, message);
- }
-
- public void setPermission(boolean allow) {
- // This should only ever be called on the WebKit thread.
- nativeSetPermission(mWebViewCore, allow);
- }
-
- // Native functions
- private static native void nativeSetUseMock(WebViewCore webViewCore);
- private static native void nativeSetPosition(WebViewCore webViewCore, double latitude,
- double longitude, double accuracy);
- private static native void nativeSetError(WebViewCore webViewCore, int code, String message);
- private static native void nativeSetPermission(WebViewCore webViewCore, boolean allow);
-}
+++ /dev/null
-/*
- * Copyright (C) 2010 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 com.android.internal.R;
-
-import android.content.Context;
-import android.content.res.Resources;
-import android.graphics.Canvas;
-import android.graphics.drawable.Drawable;
-import android.view.View;
-import android.widget.EdgeEffect;
-
-/**
- * This class manages the edge glow effect when a WebView is flung or pulled beyond the edges.
- * @hide
- */
-public class OverScrollGlow {
- private WebViewClassic mHostView;
-
- private EdgeEffect mEdgeGlowTop;
- private EdgeEffect mEdgeGlowBottom;
- private EdgeEffect mEdgeGlowLeft;
- private EdgeEffect mEdgeGlowRight;
-
- private int mOverScrollDeltaX;
- private int mOverScrollDeltaY;
-
- public OverScrollGlow(WebViewClassic host) {
- mHostView = host;
- Context context = host.getContext();
- mEdgeGlowTop = new EdgeEffect(context);
- mEdgeGlowBottom = new EdgeEffect(context);
- mEdgeGlowLeft = new EdgeEffect(context);
- mEdgeGlowRight = new EdgeEffect(context);
- }
-
- /**
- * Pull leftover touch scroll distance into one of the edge glows as appropriate.
- *
- * @param x Current X scroll offset
- * @param y Current Y scroll offset
- * @param oldX Old X scroll offset
- * @param oldY Old Y scroll offset
- * @param maxX Maximum range for horizontal scrolling
- * @param maxY Maximum range for vertical scrolling
- */
- public void pullGlow(int x, int y, int oldX, int oldY, int maxX, int maxY) {
- // Only show overscroll bars if there was no movement in any direction
- // as a result of scrolling.
- if (oldX == mHostView.getScrollX() && oldY == mHostView.getScrollY()) {
- // Don't show left/right glows if we fit the whole content.
- // Also don't show if there was vertical movement.
- if (maxX > 0) {
- final int pulledToX = oldX + mOverScrollDeltaX;
- if (pulledToX < 0) {
- mEdgeGlowLeft.onPull((float) mOverScrollDeltaX / mHostView.getWidth());
- if (!mEdgeGlowRight.isFinished()) {
- mEdgeGlowRight.onRelease();
- }
- } else if (pulledToX > maxX) {
- mEdgeGlowRight.onPull((float) mOverScrollDeltaX / mHostView.getWidth());
- if (!mEdgeGlowLeft.isFinished()) {
- mEdgeGlowLeft.onRelease();
- }
- }
- mOverScrollDeltaX = 0;
- }
-
- if (maxY > 0 || mHostView.getWebView().getOverScrollMode() == View.OVER_SCROLL_ALWAYS) {
- final int pulledToY = oldY + mOverScrollDeltaY;
- if (pulledToY < 0) {
- mEdgeGlowTop.onPull((float) mOverScrollDeltaY / mHostView.getHeight());
- if (!mEdgeGlowBottom.isFinished()) {
- mEdgeGlowBottom.onRelease();
- }
- } else if (pulledToY > maxY) {
- mEdgeGlowBottom.onPull((float) mOverScrollDeltaY / mHostView.getHeight());
- if (!mEdgeGlowTop.isFinished()) {
- mEdgeGlowTop.onRelease();
- }
- }
- mOverScrollDeltaY = 0;
- }
- }
- }
-
- /**
- * Set touch delta values indicating the current amount of overscroll.
- *
- * @param deltaX
- * @param deltaY
- */
- public void setOverScrollDeltas(int deltaX, int deltaY) {
- mOverScrollDeltaX = deltaX;
- mOverScrollDeltaY = deltaY;
- }
-
- /**
- * Absorb leftover fling velocity into one of the edge glows as appropriate.
- *
- * @param x Current X scroll offset
- * @param y Current Y scroll offset
- * @param oldX Old X scroll offset
- * @param oldY Old Y scroll offset
- * @param rangeX Maximum range for horizontal scrolling
- * @param rangeY Maximum range for vertical scrolling
- */
- public void absorbGlow(int x, int y, int oldX, int oldY, int rangeX, int rangeY) {
- if (rangeY > 0 || mHostView.getWebView().getOverScrollMode() == View.OVER_SCROLL_ALWAYS) {
- if (y < 0 && oldY >= 0) {
- mEdgeGlowTop.onAbsorb((int) mHostView.mScroller.getCurrVelocity());
- if (!mEdgeGlowBottom.isFinished()) {
- mEdgeGlowBottom.onRelease();
- }
- } else if (y > rangeY && oldY <= rangeY) {
- mEdgeGlowBottom.onAbsorb((int) mHostView.mScroller.getCurrVelocity());
- if (!mEdgeGlowTop.isFinished()) {
- mEdgeGlowTop.onRelease();
- }
- }
- }
-
- if (rangeX > 0) {
- if (x < 0 && oldX >= 0) {
- mEdgeGlowLeft.onAbsorb((int) mHostView.mScroller.getCurrVelocity());
- if (!mEdgeGlowRight.isFinished()) {
- mEdgeGlowRight.onRelease();
- }
- } else if (x > rangeX && oldX <= rangeX) {
- mEdgeGlowRight.onAbsorb((int) mHostView.mScroller.getCurrVelocity());
- if (!mEdgeGlowLeft.isFinished()) {
- mEdgeGlowLeft.onRelease();
- }
- }
- }
- }
-
- /**
- * Draw the glow effect along the sides of the widget. mEdgeGlow* must be non-null.
- *
- * @param canvas Canvas to draw into, transformed into view coordinates.
- * @return true if glow effects are still animating and the view should invalidate again.
- */
- public boolean drawEdgeGlows(Canvas canvas) {
- final int scrollX = mHostView.getScrollX();
- final int scrollY = mHostView.getScrollY();
- final int width = mHostView.getWidth();
- int height = mHostView.getHeight();
-
- boolean invalidateForGlow = false;
- if (!mEdgeGlowTop.isFinished()) {
- final int restoreCount = canvas.save();
-
- canvas.translate(scrollX, mHostView.getVisibleTitleHeight() + Math.min(0, scrollY));
- mEdgeGlowTop.setSize(width, height);
- invalidateForGlow |= mEdgeGlowTop.draw(canvas);
- canvas.restoreToCount(restoreCount);
- }
- if (!mEdgeGlowBottom.isFinished()) {
- final int restoreCount = canvas.save();
-
- canvas.translate(-width + scrollX, Math.max(mHostView.computeMaxScrollY(), scrollY)
- + height);
- canvas.rotate(180, width, 0);
- mEdgeGlowBottom.setSize(width, height);
- invalidateForGlow |= mEdgeGlowBottom.draw(canvas);
- canvas.restoreToCount(restoreCount);
- }
- if (!mEdgeGlowLeft.isFinished()) {
- final int restoreCount = canvas.save();
-
- canvas.rotate(270);
- canvas.translate(-height - scrollY, Math.min(0, scrollX));
- mEdgeGlowLeft.setSize(height, width);
- invalidateForGlow |= mEdgeGlowLeft.draw(canvas);
- canvas.restoreToCount(restoreCount);
- }
- if (!mEdgeGlowRight.isFinished()) {
- final int restoreCount = canvas.save();
-
- canvas.rotate(90);
- canvas.translate(scrollY,
- -(Math.max(mHostView.computeMaxScrollX(), scrollX) + width));
- mEdgeGlowRight.setSize(height, width);
- invalidateForGlow |= mEdgeGlowRight.draw(canvas);
- canvas.restoreToCount(restoreCount);
- }
- return invalidateForGlow;
- }
-
- /**
- * @return True if any glow is still animating
- */
- public boolean isAnimating() {
- return (!mEdgeGlowTop.isFinished() || !mEdgeGlowBottom.isFinished() ||
- !mEdgeGlowLeft.isFinished() || !mEdgeGlowRight.isFinished());
- }
-
- /**
- * Release all glows from any touch pulls in progress.
- */
- public void releaseAll() {
- mEdgeGlowTop.onRelease();
- mEdgeGlowBottom.onRelease();
- mEdgeGlowLeft.onRelease();
- mEdgeGlowRight.onRelease();
- }
-}
+++ /dev/null
-/*
- * 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 android.webkit;
-
-import java.io.InputStream;
-import java.util.Map;
-
-/**
- * This class encapsulates the content generated by a plugin. The
- * data itself is meant to be loaded into webkit via the
- * PluginContentLoader class, which needs to be able to construct an
- * HTTP response. For this, it needs a stream with the response body,
- * the length of the body, the response headers, and the response
- * status code. The PluginData class is the container for all these
- * parts.
- *
- * @hide
- * @deprecated This class was intended to be used by Gears. Since Gears was
- * deprecated, so is this class.
- */
-@Deprecated
-public final class PluginData {
- /**
- * The content stream.
- */
- private InputStream mStream;
- /**
- * The content length.
- */
- private long mContentLength;
- /**
- * The associated HTTP response headers stored as a map of
- * lowercase header name to [ unmodified header name, header value].
- * TODO: This design was always a hack. Remove (involves updating
- * the Gears C++ side).
- */
- private Map<String, String[]> mHeaders;
-
- /**
- * The associated HTTP response code.
- */
- private int mStatusCode;
-
- /**
- * Creates a PluginData instance.
- *
- * @param stream The stream that supplies content for the plugin.
- * @param length The length of the plugin content.
- * @param headers The response headers. Map of
- * lowercase header name to [ unmodified header name, header value]
- * @param length The HTTP response status code.
- *
- * @hide
- * @deprecated This class was intended to be used by Gears. Since Gears was
- * deprecated, so is this class.
- */
- @Deprecated
- public PluginData(
- InputStream stream,
- long length,
- Map<String, String[]> headers,
- int code) {
- mStream = stream;
- mContentLength = length;
- mHeaders = headers;
- mStatusCode = code;
- }
-
- /**
- * Returns the input stream that contains the plugin content.
- *
- * @return An InputStream instance with the plugin content.
- *
- * @hide
- * @deprecated This class was intended to be used by Gears. Since Gears was
- * deprecated, so is this class.
- */
- @Deprecated
- public InputStream getInputStream() {
- return mStream;
- }
-
- /**
- * Returns the length of the plugin content.
- *
- * @return the length of the plugin content.
- *
- * @hide
- * @deprecated This class was intended to be used by Gears. Since Gears was
- * deprecated, so is this class.
- */
- @Deprecated
- public long getContentLength() {
- return mContentLength;
- }
-
- /**
- * Returns the HTTP response headers associated with the plugin
- * content.
- *
- * @return A Map<String, String[]> containing all headers. The
- * mapping is 'lowercase header name' to ['unmodified header
- * name', header value].
- *
- * @hide
- * @deprecated This class was intended to be used by Gears. Since Gears was
- * deprecated, so is this class.
- */
- @Deprecated
- public Map<String, String[]> getHeaders() {
- return mHeaders;
- }
-
- /**
- * Returns the HTTP status code for the response.
- *
- * @return The HTTP statue code, e.g 200.
- *
- * @hide
- * @deprecated This class was intended to be used by Gears. Since Gears was
- * deprecated, so is this class.
- */
- @Deprecated
- public int getStatusCode() {
- return mStatusCode;
- }
-}
+++ /dev/null
-/*
- * Copyright 2009, The Android Open Source Project
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- * * Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
- * * Redistributions in binary form must reproduce the above copyright
- * notice, this list of conditions and the following disclaimer in the
- * documentation and/or other materials provided with the distribution.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY
- * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
- * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
- * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
- * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
- * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
- * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
- * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
- * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
- * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-package android.webkit;
-
-import android.content.Context;
-import android.view.Gravity;
-import android.view.KeyEvent;
-import android.view.MotionEvent;
-import android.view.SurfaceView;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.FrameLayout;
-
-class PluginFullScreenHolder {
-
- private final WebViewClassic mWebView;
- private final int mNpp;
- private final int mOrientation;
-
- // The container for the plugin view
- private static CustomFrameLayout mLayout;
-
- private View mContentView;
-
- PluginFullScreenHolder(WebViewClassic webView, int orientation, int npp) {
- mWebView = webView;
- mNpp = npp;
- mOrientation = orientation;
- }
-
- public void setContentView(View contentView) {
-
- // Create a FrameLayout that will contain the plugin's view
- mLayout = new CustomFrameLayout(mWebView.getContext());
- FrameLayout.LayoutParams layoutParams = new FrameLayout.LayoutParams(
- ViewGroup.LayoutParams.MATCH_PARENT,
- ViewGroup.LayoutParams.MATCH_PARENT,
- Gravity.CENTER);
-
- mLayout.addView(contentView, layoutParams);
- mLayout.setVisibility(View.VISIBLE);
-
- // fixed size is only used either during pinch zoom or surface is too
- // big. Make sure it is not fixed size before setting it to the full
- // screen content view. The SurfaceView will be set to the correct mode
- // by the ViewManager when it is re-attached to the WebView.
- if (contentView instanceof SurfaceView) {
- final SurfaceView sView = (SurfaceView) contentView;
- if (sView.isFixedSize()) {
- sView.getHolder().setSizeFromLayout();
- }
- }
-
- mContentView = contentView;
- }
-
- public void show() {
- // Other plugins may attempt to draw so hide them while we're active.
- if (mWebView.getViewManager() != null)
- mWebView.getViewManager().hideAll();
-
- WebChromeClient client = mWebView.getWebChromeClient();
- client.onShowCustomView(mLayout, mOrientation, mCallback);
- }
-
- public void hide() {
- WebChromeClient client = mWebView.getWebChromeClient();
- client.onHideCustomView();
- }
-
- private class CustomFrameLayout extends FrameLayout {
-
- CustomFrameLayout(Context context) {
- super(context);
- }
-
- @Override
- public boolean onKeyDown(int keyCode, KeyEvent event) {
- if (event.isSystem()) {
- return super.onKeyDown(keyCode, event);
- }
- mWebView.onKeyDown(keyCode, event);
- // always return true as we are the handler
- return true;
- }
-
- @Override
- public boolean onKeyUp(int keyCode, KeyEvent event) {
- if (event.isSystem()) {
- return super.onKeyUp(keyCode, event);
- }
- mWebView.onKeyUp(keyCode, event);
- // always return true as we are the handler
- return true;
- }
-
- @Override
- public boolean onTouchEvent(MotionEvent event) {
- // always return true as we don't want the event to propagate any further
- return true;
- }
-
- @Override
- public boolean onTrackballEvent(MotionEvent event) {
- mWebView.onTrackballEvent(event);
- // always return true as we are the handler
- return true;
- }
- }
-
- private final WebChromeClient.CustomViewCallback mCallback =
- new WebChromeClient.CustomViewCallback() {
- public void onCustomViewHidden() {
-
- mWebView.mPrivateHandler.obtainMessage(WebViewClassic.HIDE_FULLSCREEN)
- .sendToTarget();
-
- mWebView.getWebViewCore().sendMessage(
- WebViewCore.EventHub.HIDE_FULLSCREEN, mNpp, 0);
-
- mLayout.removeView(mContentView);
- mLayout = null;
-
- // Re enable plugin views.
- mWebView.getViewManager().showAll();
- }
- };
-}
+++ /dev/null
-/*
- * 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 android.webkit;
-
-import java.util.ArrayList;
-import java.util.List;
-
-import android.annotation.SdkConstant;
-import android.annotation.SdkConstant.SdkConstantType;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageInfo;
-import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
-import android.content.pm.ServiceInfo;
-import android.content.pm.Signature;
-import android.content.pm.PackageManager.NameNotFoundException;
-import android.os.SystemProperties;
-import android.util.Log;
-
-/**
- * Class for managing the relationship between the {@link WebViewClassic} and installed
- * plugins in the system. You can find this class through
- * {@link PluginManager#getInstance}.
- *
- * @hide pending API solidification
- */
-public class PluginManager {
-
- /**
- * Service Action: A plugin wishes to be loaded in the WebView must provide
- * {@link android.content.IntentFilter IntentFilter} that accepts this
- * action in their AndroidManifest.xml.
- * <p>
- * TODO: we may change this to a new PLUGIN_ACTION if this is going to be
- * public.
- */
- @SdkConstant(SdkConstantType.SERVICE_ACTION)
- public static final String PLUGIN_ACTION = "android.webkit.PLUGIN";
-
- /**
- * A plugin wishes to be loaded in the WebView must provide this permission
- * in their AndroidManifest.xml.
- */
- public static final String PLUGIN_PERMISSION = "android.webkit.permission.PLUGIN";
-
- private static final String LOGTAG = "PluginManager";
-
- private static final String PLUGIN_SYSTEM_LIB = "/system/lib/plugins/";
-
- private static final String PLUGIN_TYPE = "type";
- private static final String TYPE_NATIVE = "native";
-
- private static PluginManager mInstance = null;
-
- private final Context mContext;
-
- private ArrayList<PackageInfo> mPackageInfoCache;
-
- // Only plugin matches one of the signatures in the list can be loaded
- // inside the WebView process
- private static final String SIGNATURE_1 = "308204c5308203ada003020102020900d7cb412f75f4887e300d06092a864886f70d010105050030819d310b3009060355040613025553311330110603550408130a43616c69666f726e69613111300f0603550407130853616e204a6f736531233021060355040a131a41646f62652053797374656d7320496e636f72706f7261746564311c301a060355040b1313496e666f726d6174696f6e2053797374656d73312330210603550403131a41646f62652053797374656d7320496e636f72706f7261746564301e170d3039313030313030323331345a170d3337303231363030323331345a30819d310b3009060355040613025553311330110603550408130a43616c69666f726e69613111300f0603550407130853616e204a6f736531233021060355040a131a41646f62652053797374656d7320496e636f72706f7261746564311c301a060355040b1313496e666f726d6174696f6e2053797374656d73312330210603550403131a41646f62652053797374656d7320496e636f72706f726174656430820120300d06092a864886f70d01010105000382010d0030820108028201010099724f3e05bbd78843794f357776e04b340e13cb1c9ccb3044865180d7d8fec8166c5bbd876da8b80aa71eb6ba3d4d3455c9a8de162d24a25c4c1cd04c9523affd06a279fc8f0d018f242486bdbb2dbfbf6fcb21ed567879091928b876f7ccebc7bccef157366ebe74e33ae1d7e9373091adab8327482154afc0693a549522f8c796dd84d16e24bb221f5dbb809ca56dd2b6e799c5fa06b6d9c5c09ada54ea4c5db1523a9794ed22a3889e5e05b29f8ee0a8d61efe07ae28f65dece2ff7edc5b1416d7c7aad7f0d35e8f4a4b964dbf50ae9aa6d620157770d974131b3e7e3abd6d163d65758e2f0822db9c88598b9db6263d963d13942c91fc5efe34fc1e06e3020103a382010630820102301d0603551d0e041604145af418e419a639e1657db960996364a37ef20d403081d20603551d230481ca3081c780145af418e419a639e1657db960996364a37ef20d40a181a3a481a030819d310b3009060355040613025553311330110603550408130a43616c69666f726e69613111300f0603550407130853616e204a6f736531233021060355040a131a41646f62652053797374656d7320496e636f72706f7261746564311c301a060355040b1313496e666f726d6174696f6e2053797374656d73312330210603550403131a41646f62652053797374656d7320496e636f72706f7261746564820900d7cb412f75f4887e300c0603551d13040530030101ff300d06092a864886f70d0101050500038201010076c2a11fe303359689c2ebc7b2c398eff8c3f9ad545cdbac75df63bf7b5395b6988d1842d6aa1556d595b5692e08224d667a4c9c438f05e74906c53dd8016dde7004068866f01846365efd146e9bfaa48c9ecf657f87b97c757da11f225c4a24177bf2d7188e6cce2a70a1e8a841a14471eb51457398b8a0addd8b6c8c1538ca8f1e40b4d8b960009ea22c188d28924813d2c0b4a4d334b7cf05507e1fcf0a06fe946c7ffc435e173af6fc3e3400643710acc806f830a14788291d46f2feed9fb5c70423ca747ed1572d752894ac1f19f93989766308579393fabb43649aa8806a313b1ab9a50922a44c2467b9062037f2da0d484d9ffd8fe628eeea629ba637";
-
- private static final Signature[] SIGNATURES = new Signature[] {
- new Signature(SIGNATURE_1)
- };
-
- private PluginManager(Context context) {
- mContext = context;
- mPackageInfoCache = new ArrayList<PackageInfo>();
- }
-
- public static synchronized PluginManager getInstance(Context context) {
- if (mInstance == null) {
- if (context == null) {
- throw new IllegalStateException(
- "First call to PluginManager need a valid context.");
- }
- mInstance = new PluginManager(context.getApplicationContext());
- }
- return mInstance;
- }
-
- /**
- * Signal the WebCore thread to refresh its list of plugins. Use this if the
- * directory contents of one of the plugin directories has been modified and
- * needs its changes reflecting. May cause plugin load and/or unload.
- *
- * @param reloadOpenPages Set to true to reload all open pages.
- */
- public void refreshPlugins(boolean reloadOpenPages) {
- BrowserFrame.sJavaBridge.obtainMessage(
- JWebCoreJavaBridge.REFRESH_PLUGINS, reloadOpenPages)
- .sendToTarget();
- }
-
- String[] getPluginDirectories() {
-
- ArrayList<String> directories = new ArrayList<String>();
- PackageManager pm = mContext.getPackageManager();
- List<ResolveInfo> plugins = pm.queryIntentServices(new Intent(PLUGIN_ACTION),
- PackageManager.GET_SERVICES | PackageManager.GET_META_DATA);
-
- synchronized(mPackageInfoCache) {
-
- // clear the list of existing packageInfo objects
- mPackageInfoCache.clear();
-
- for (ResolveInfo info : plugins) {
-
- // retrieve the plugin's service information
- ServiceInfo serviceInfo = info.serviceInfo;
- if (serviceInfo == null) {
- Log.w(LOGTAG, "Ignore bad plugin");
- continue;
- }
-
- // retrieve information from the plugin's manifest
- PackageInfo pkgInfo;
- try {
- pkgInfo = pm.getPackageInfo(serviceInfo.packageName,
- PackageManager.GET_PERMISSIONS
- | PackageManager.GET_SIGNATURES);
- } catch (NameNotFoundException e) {
- Log.w(LOGTAG, "Can't find plugin: " + serviceInfo.packageName);
- continue;
- }
- if (pkgInfo == null) {
- continue;
- }
-
- /*
- * find the location of the plugin's shared library. The default
- * is to assume the app is either a user installed app or an
- * updated system app. In both of these cases the library is
- * stored in the app's data directory.
- */
- String directory = pkgInfo.applicationInfo.dataDir + "/lib";
- final int appFlags = pkgInfo.applicationInfo.flags;
- final int updatedSystemFlags = ApplicationInfo.FLAG_SYSTEM |
- ApplicationInfo.FLAG_UPDATED_SYSTEM_APP;
- // preloaded system app with no user updates
- if ((appFlags & updatedSystemFlags) == ApplicationInfo.FLAG_SYSTEM) {
- directory = PLUGIN_SYSTEM_LIB + pkgInfo.packageName;
- }
-
- // check if the plugin has the required permissions and
- // signatures
- if (!containsPluginPermissionAndSignatures(pkgInfo)) {
- continue;
- }
-
- // determine the type of plugin from the manifest
- if (serviceInfo.metaData == null) {
- Log.e(LOGTAG, "The plugin '" + serviceInfo.name + "' has no type defined");
- continue;
- }
-
- String pluginType = serviceInfo.metaData.getString(PLUGIN_TYPE);
- if (!TYPE_NATIVE.equals(pluginType)) {
- Log.e(LOGTAG, "Unrecognized plugin type: " + pluginType);
- continue;
- }
-
- try {
- Class<?> cls = getPluginClass(serviceInfo.packageName, serviceInfo.name);
-
- //TODO implement any requirements of the plugin class here!
- boolean classFound = true;
-
- if (!classFound) {
- Log.e(LOGTAG, "The plugin's class' " + serviceInfo.name + "' does not extend the appropriate class.");
- continue;
- }
-
- } catch (NameNotFoundException e) {
- Log.e(LOGTAG, "Can't find plugin: " + serviceInfo.packageName);
- continue;
- } catch (ClassNotFoundException e) {
- Log.e(LOGTAG, "Can't find plugin's class: " + serviceInfo.name);
- continue;
- }
-
- // if all checks have passed then make the plugin available
- mPackageInfoCache.add(pkgInfo);
- directories.add(directory);
- }
- }
-
- return directories.toArray(new String[directories.size()]);
- }
-
- /* package */
- boolean containsPluginPermissionAndSignatures(String pluginAPKName) {
- PackageManager pm = mContext.getPackageManager();
-
- // retrieve information from the plugin's manifest
- try {
- PackageInfo pkgInfo = pm.getPackageInfo(pluginAPKName, PackageManager.GET_PERMISSIONS
- | PackageManager.GET_SIGNATURES);
- if (pkgInfo != null) {
- return containsPluginPermissionAndSignatures(pkgInfo);
- }
- } catch (NameNotFoundException e) {
- Log.w(LOGTAG, "Can't find plugin: " + pluginAPKName);
- }
- return false;
- }
-
- private static boolean containsPluginPermissionAndSignatures(PackageInfo pkgInfo) {
-
- // check if the plugin has the required permissions
- String permissions[] = pkgInfo.requestedPermissions;
- if (permissions == null) {
- return false;
- }
- boolean permissionOk = false;
- for (String permit : permissions) {
- if (PLUGIN_PERMISSION.equals(permit)) {
- permissionOk = true;
- break;
- }
- }
- if (!permissionOk) {
- return false;
- }
-
- // check to ensure the plugin is properly signed
- Signature signatures[] = pkgInfo.signatures;
- if (signatures == null) {
- return false;
- }
- if (SystemProperties.getBoolean("ro.secure", false)) {
- boolean signatureMatch = false;
- for (Signature signature : signatures) {
- for (int i = 0; i < SIGNATURES.length; i++) {
- if (SIGNATURES[i].equals(signature)) {
- signatureMatch = true;
- break;
- }
- }
- }
- if (!signatureMatch) {
- return false;
- }
- }
-
- return true;
- }
-
- /* package */
- String getPluginsAPKName(String pluginLib) {
-
- // basic error checking on input params
- if (pluginLib == null || pluginLib.length() == 0) {
- return null;
- }
-
- // must be synchronized to ensure the consistency of the cache
- synchronized(mPackageInfoCache) {
- for (PackageInfo pkgInfo : mPackageInfoCache) {
- if (pluginLib.contains(pkgInfo.packageName)) {
- return pkgInfo.packageName;
- }
- }
- }
-
- // if no apk was found then return null
- return null;
- }
-
- String getPluginSharedDataDirectory() {
- return mContext.getDir("plugins", 0).getPath();
- }
-
- /* package */
- Class<?> getPluginClass(String packageName, String className)
- throws NameNotFoundException, ClassNotFoundException {
- Context pluginContext = mContext.createPackageContext(packageName,
- Context.CONTEXT_INCLUDE_CODE |
- Context.CONTEXT_IGNORE_SECURITY);
- ClassLoader pluginCL = pluginContext.getClassLoader();
- return pluginCL.loadClass(className);
- }
-}
+++ /dev/null
-/*
- * Copyright (C) 2012 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.graphics.PointF;
-
-/**
- * A quadrilateral, determined by four points, clockwise order. Typically
- * p1 is "top-left" and p4 is "bottom-left" following webkit's rectangle-to-
- * FloatQuad conversion.
- */
-class QuadF {
- public PointF p1;
- public PointF p2;
- public PointF p3;
- public PointF p4;
-
- public QuadF() {
- p1 = new PointF();
- p2 = new PointF();
- p3 = new PointF();
- p4 = new PointF();
- }
-
- public void offset(float dx, float dy) {
- p1.offset(dx, dy);
- p2.offset(dx, dy);
- p3.offset(dx, dy);
- p4.offset(dx, dy);
- }
-
- /**
- * Determines if the quadrilateral contains the given point. This does
- * not work if the quadrilateral is self-intersecting or if any inner
- * angle is reflex (greater than 180 degrees).
- */
- public boolean containsPoint(float x, float y) {
- return isPointInTriangle(x, y, p1, p2, p3) ||
- isPointInTriangle(x, y, p1, p3, p4);
- }
-
- @Override
- public String toString() {
- StringBuilder s = new StringBuilder("QuadF(");
- s.append(p1.x).append(",").append(p1.y);
- s.append(" - ");
- s.append(p2.x).append(",").append(p2.y);
- s.append(" - ");
- s.append(p3.x).append(",").append(p3.y);
- s.append(" - ");
- s.append(p4.x).append(",").append(p4.y);
- s.append(")");
- return s.toString();
- }
-
- private static boolean isPointInTriangle(float x0, float y0,
- PointF r1, PointF r2, PointF r3) {
- // Use the barycentric technique
- float x13 = r1.x - r3.x;
- float y13 = r1.y - r3.y;
- float x23 = r2.x - r3.x;
- float y23 = r2.y - r3.y;
- float x03 = x0 - r3.x;
- float y03 = y0 - r3.y;
-
- float determinant = (y23 * x13) - (x23 * y13);
- float lambda1 = ((y23 * x03) - (x23 * y03))/determinant;
- float lambda2 = ((x13 * y03) - (y13 * x03))/determinant;
- float lambda3 = 1 - lambda1 - lambda2;
- return lambda1 >= 0.0f && lambda2 >= 0.0f && lambda3 >= 0.0f;
- }
-}
+++ /dev/null
-/*
- * Copyright (C) 2010 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.app.Activity;
-import android.app.SearchManager;
-import android.content.ClipboardManager;
-import android.content.Context;
-import android.content.Intent;
-import android.provider.Browser;
-import android.view.ActionMode;
-import android.view.Menu;
-import android.view.MenuItem;
-
-class SelectActionModeCallback implements ActionMode.Callback {
- private WebViewClassic mWebView;
- private ActionMode mActionMode;
- private boolean mIsTextSelected = true;
-
- void setWebView(WebViewClassic webView) {
- mWebView = webView;
- }
-
- void setTextSelected(boolean isTextSelected) {
- mIsTextSelected = isTextSelected;
- }
-
- void finish() {
- // It is possible that onCreateActionMode was never called, in the case
- // where there is no ActionBar, for example.
- if (mActionMode != null) {
- mActionMode.finish();
- }
- }
-
- // ActionMode.Callback implementation
-
- @Override
- public boolean onCreateActionMode(ActionMode mode, Menu menu) {
- mode.getMenuInflater().inflate(com.android.internal.R.menu.webview_copy, menu);
-
- final Context context = mWebView.getContext();
- mode.setTitle(context.getString(com.android.internal.R.string.textSelectionCABTitle));
- mode.setTitleOptionalHint(true);
-
- // If the action mode UI we're running in isn't capable of taking window focus
- // the user won't be able to type into the find on page UI. Disable this functionality.
- // (Note that this should only happen in floating dialog windows.)
- // This can be removed once we can handle multiple focusable windows at a time
- // in a better way.
- ClipboardManager cm = (ClipboardManager)(context
- .getSystemService(Context.CLIPBOARD_SERVICE));
- boolean isFocusable = mode.isUiFocusable();
- boolean isEditable = mWebView.focusCandidateIsEditableText();
- boolean canPaste = isEditable && cm.hasPrimaryClip() && isFocusable;
- boolean canFind = !isEditable && isFocusable;
- boolean canCut = isEditable && mIsTextSelected && isFocusable;
- boolean canCopy = mIsTextSelected;
- boolean canWebSearch = mIsTextSelected;
- setMenuVisibility(menu, canFind, com.android.internal.R.id.find);
- setMenuVisibility(menu, canPaste, com.android.internal.R.id.paste);
- setMenuVisibility(menu, canCut, com.android.internal.R.id.cut);
- setMenuVisibility(menu, canCopy, com.android.internal.R.id.copy);
- setMenuVisibility(menu, canWebSearch, com.android.internal.R.id.websearch);
- mActionMode = mode;
- return true;
- }
-
- @Override
- public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
- return true;
- }
-
- @Override
- public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
- switch(item.getItemId()) {
- case android.R.id.cut:
- mWebView.cutSelection();
- mode.finish();
- break;
-
- case android.R.id.copy:
- mWebView.copySelection();
- mode.finish();
- break;
-
- case android.R.id.paste:
- mWebView.pasteFromClipboard();
- mode.finish();
- break;
-
- case com.android.internal.R.id.share:
- String selection = mWebView.getSelection();
- Browser.sendString(mWebView.getContext(), selection);
- mode.finish();
- break;
-
- case com.android.internal.R.id.select_all:
- mWebView.selectAll();
- break;
-
- case com.android.internal.R.id.find:
- String sel= mWebView.getSelection();
- mode.finish();
- mWebView.showFindDialog(sel, false);
- break;
- case com.android.internal.R.id.websearch:
- mode.finish();
- Intent i = new Intent(Intent.ACTION_WEB_SEARCH);
- i.putExtra(SearchManager.EXTRA_NEW_SEARCH, true);
- i.putExtra(SearchManager.QUERY, mWebView.getSelection());
- if (!(mWebView.getContext() instanceof Activity)) {
- i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- }
- mWebView.getContext().startActivity(i);
- break;
-
- default:
- return false;
- }
- return true;
- }
-
- @Override
- public void onDestroyActionMode(ActionMode mode) {
- mWebView.selectionDone();
- }
-
- private void setMenuVisibility(Menu menu, boolean visible, int resourceId) {
- final MenuItem item = menu.findItem(resourceId);
- if (item != null) {
- item.setVisible(visible);
- }
- }
-}
+++ /dev/null
-/*
- * Copyright (C) 2010 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.os.Bundle;
-import android.net.http.SslError;
-
-import java.net.MalformedURLException;
-import java.net.URL;
-
-/**
- * Stores the user's decision of whether to allow or deny an invalid certificate.
- *
- * This class is not threadsafe. It is used only on the WebCore thread. Also, it
- * is used only by the Chromium HTTP stack.
- */
-final class SslCertLookupTable {
- private static SslCertLookupTable sTable;
- // We store the most severe error we're willing to allow for each host.
- private final Bundle table;
-
- public static SslCertLookupTable getInstance() {
- if (sTable == null) {
- sTable = new SslCertLookupTable();
- }
- return sTable;
- }
-
- private SslCertLookupTable() {
- table = new Bundle();
- }
-
- public void setIsAllowed(SslError sslError) {
- String host;
- try {
- host = new URL(sslError.getUrl()).getHost();
- } catch(MalformedURLException e) {
- return;
- }
- table.putInt(host, sslError.getPrimaryError());
- }
-
- // We allow the decision to be re-used if it's for the same host and is for
- // an error of equal or greater severity than this error.
- public boolean isAllowed(SslError sslError) {
- String host;
- try {
- host = new URL(sslError.getUrl()).getHost();
- } catch(MalformedURLException e) {
- return false;
- }
- return table.containsKey(host) && sslError.getPrimaryError() <= table.getInt(host);
- }
-
- public void clear() {
- table.clear();
- }
-}
+++ /dev/null
-/*
- * 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.security.PrivateKey;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Map;
-import java.util.Set;
-
-/**
- * A simple class to store client certificates that user has chosen.
- */
-final class SslClientCertLookupTable {
- private static SslClientCertLookupTable sTable;
- private final Map<String, PrivateKey> privateKeys;
- private final Map<String, byte[][]> certificateChains;
- private final Set<String> denied;
-
- public static synchronized SslClientCertLookupTable getInstance() {
- if (sTable == null) {
- sTable = new SslClientCertLookupTable();
- }
- return sTable;
- }
-
- private SslClientCertLookupTable() {
- privateKeys = new HashMap<String, PrivateKey>();
- certificateChains = new HashMap<String, byte[][]>();
- denied = new HashSet<String>();
- }
-
- public void Allow(String host_and_port, PrivateKey privateKey, byte[][] chain) {
- privateKeys.put(host_and_port, privateKey);
- certificateChains.put(host_and_port, chain);
- denied.remove(host_and_port);
- }
-
- public void Deny(String host_and_port) {
- privateKeys.remove(host_and_port);
- certificateChains.remove(host_and_port);
- denied.add(host_and_port);
- }
-
- public boolean IsAllowed(String host_and_port) {
- return privateKeys.containsKey(host_and_port);
- }
-
- public boolean IsDenied(String host_and_port) {
- return denied.contains(host_and_port);
- }
-
- public PrivateKey PrivateKey(String host_and_port) {
- return privateKeys.get(host_and_port);
- }
-
- public byte[][] CertificateChain(String host_and_port) {
- return certificateChains.get(host_and_port);
- }
-}
+++ /dev/null
-/*
- * Copyright (C) 2008 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.webkit.CacheManager.CacheResult;
-import android.webkit.PluginData;
-import java.util.Map;
-
-/**
- * @hide
- * @deprecated This interface was inteded to be used by Gears. Since Gears was
- * deprecated, so is this class.
- */
-@Deprecated
-public interface UrlInterceptHandler {
-
- /**
- * Given an URL, returns the CacheResult which contains the
- * surrogate response for the request, or null if the handler is
- * not interested.
- *
- * @param url URL string.
- * @param headers The headers associated with the request. May be null.
- * @return The CacheResult containing the surrogate response.
- *
- * @hide
- * @deprecated Do not use, this interface is deprecated.
- */
- @Deprecated
- public CacheResult service(String url, Map<String, String> headers);
-
- /**
- * Given an URL, returns the PluginData which contains the
- * surrogate response for the request, or null if the handler is
- * not interested.
- *
- * @param url URL string.
- * @param headers The headers associated with the request. May be null.
- * @return The PluginData containing the surrogate response.
- *
- * @hide
- * @deprecated Do not use, this interface is deprecated.
- */
- @Deprecated
- public PluginData getPluginData(String url, Map<String, String> headers);
-}
+++ /dev/null
-/*
- * Copyright (C) 2008 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.webkit.CacheManager.CacheResult;
-import android.webkit.PluginData;
-import android.webkit.UrlInterceptHandler;
-
-import java.util.Iterator;
-import java.util.LinkedList;
-import java.util.Map;
-
-/**
- * @hide
- * @deprecated This class was intended to be used by Gears. Since Gears was
- * deprecated, so is this class.
- */
-@Deprecated
-public final class UrlInterceptRegistry {
-
- private final static String LOGTAG = "intercept";
-
- private static boolean mDisabled = false;
-
- private static LinkedList mHandlerList;
-
- private static synchronized LinkedList getHandlers() {
- if(mHandlerList == null)
- mHandlerList = new LinkedList<UrlInterceptHandler>();
- return mHandlerList;
- }
-
- /**
- * set the flag to control whether url intercept is enabled or disabled
- *
- * @param disabled true to disable the cache
- *
- * @hide
- * @deprecated This class was intended to be used by Gears. Since Gears was
- * deprecated, so is this class.
- */
- @Deprecated
- public static synchronized void setUrlInterceptDisabled(boolean disabled) {
- mDisabled = disabled;
- }
-
- /**
- * get the state of the url intercept, enabled or disabled
- *
- * @return return if it is disabled
- *
- * @hide
- * @deprecated This class was intended to be used by Gears. Since Gears was
- * deprecated, so is this class.
- */
- @Deprecated
- public static synchronized boolean urlInterceptDisabled() {
- return mDisabled;
- }
-
- /**
- * Register a new UrlInterceptHandler. This handler will be called
- * before any that were previously registered.
- *
- * @param handler The new UrlInterceptHandler object
- * @return true if the handler was not previously registered.
- *
- * @hide
- * @deprecated This class was intended to be used by Gears. Since Gears was
- * deprecated, so is this class.
- */
- @Deprecated
- public static synchronized boolean registerHandler(
- UrlInterceptHandler handler) {
- if (!getHandlers().contains(handler)) {
- getHandlers().addFirst(handler);
- return true;
- } else {
- return false;
- }
- }
-
- /**
- * Unregister a previously registered UrlInterceptHandler.
- *
- * @param handler A previously registered UrlInterceptHandler.
- * @return true if the handler was found and removed from the list.
- *
- * @hide
- * @deprecated This class was intended to be used by Gears. Since Gears was
- * deprecated, so is this class.
- */
- @Deprecated
- public static synchronized boolean unregisterHandler(
- UrlInterceptHandler handler) {
- return getHandlers().remove(handler);
- }
-
- /**
- * Given an url, returns the CacheResult of the first
- * UrlInterceptHandler interested, or null if none are.
- *
- * @return A CacheResult containing surrogate content.
- *
- * @hide
- * @deprecated This class was intended to be used by Gears. Since Gears was
- * deprecated, so is this class.
- */
- @Deprecated
- public static synchronized CacheResult getSurrogate(
- String url, Map<String, String> headers) {
- if (urlInterceptDisabled()) {
- return null;
- }
- Iterator iter = getHandlers().listIterator();
- while (iter.hasNext()) {
- UrlInterceptHandler handler = (UrlInterceptHandler) iter.next();
- CacheResult result = handler.service(url, headers);
- if (result != null) {
- return result;
- }
- }
- return null;
- }
-
- /**
- * Given an url, returns the PluginData of the first
- * UrlInterceptHandler interested, or null if none are or if
- * intercepts are disabled.
- *
- * @return A PluginData instance containing surrogate content.
- *
- * @hide
- * @deprecated This class was intended to be used by Gears. Since Gears was
- * deprecated, so is this class.
- */
- @Deprecated
- public static synchronized PluginData getPluginData(
- String url, Map<String, String> headers) {
- if (urlInterceptDisabled()) {
- return null;
- }
- Iterator iter = getHandlers().listIterator();
- while (iter.hasNext()) {
- UrlInterceptHandler handler = (UrlInterceptHandler) iter.next();
- PluginData data = handler.getPluginData(url, headers);
- if (data != null) {
- return data;
- }
- }
- return null;
- }
-}
+++ /dev/null
-/*
- * 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 android.webkit;
-
-import android.util.DisplayMetrics;
-import android.view.SurfaceView;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.AbsoluteLayout;
-
-import java.util.ArrayList;
-
-class ViewManager {
- private final WebViewClassic mWebView;
- private final ArrayList<ChildView> mChildren = new ArrayList<ChildView>();
- private boolean mHidden;
- private boolean mReadyToDraw;
- private boolean mZoomInProgress = false;
-
- // Threshold at which a surface is prevented from further increasing in size
- private final int MAX_SURFACE_AREA;
- // GPU Limit (hard coded for now)
- private static final int MAX_SURFACE_DIMENSION = 2048;
-
- class ChildView {
- int x;
- int y;
- int width;
- int height;
- View mView; // generic view to show
-
- ChildView() {
- }
-
- void setBounds(int x, int y, int width, int height) {
- this.x = x;
- this.y = y;
- this.width = width;
- this.height = height;
- }
-
- void attachView(int x, int y, int width, int height) {
- if (mView == null) {
- return;
- }
- setBounds(x, y, width, height);
-
- mWebView.mPrivateHandler.post(new Runnable() {
- public void run() {
- // This method may be called multiple times. If the view is
- // already attached, just set the new LayoutParams,
- // otherwise attach the view and add it to the list of
- // children.
- requestLayout(ChildView.this);
-
- if (mView.getParent() == null) {
- attachViewOnUIThread();
- }
- }
- });
- }
-
- private void attachViewOnUIThread() {
- mWebView.getWebView().addView(mView);
- mChildren.add(this);
- if (!mReadyToDraw) {
- mView.setVisibility(View.GONE);
- }
- }
-
- void removeView() {
- if (mView == null) {
- return;
- }
- mWebView.mPrivateHandler.post(new Runnable() {
- public void run() {
- removeViewOnUIThread();
- }
- });
- }
-
- private void removeViewOnUIThread() {
- mWebView.getWebView().removeView(mView);
- mChildren.remove(this);
- }
- }
-
- ViewManager(WebViewClassic w) {
- mWebView = w;
- DisplayMetrics metrics = w.getWebView().getResources().getDisplayMetrics();
- int pixelArea = metrics.widthPixels * metrics.heightPixels;
- /* set the threshold to be 275% larger than the screen size. The
- percentage is simply an estimation and is not based on anything but
- basic trial-and-error tests run on multiple devices.
- */
- MAX_SURFACE_AREA = (int)(pixelArea * 2.75);
- }
-
- ChildView createView() {
- return new ChildView();
- }
-
- /**
- * This should only be called from the UI thread.
- */
- private void requestLayout(ChildView v) {
-
- int width = mWebView.contentToViewDimension(v.width);
- int height = mWebView.contentToViewDimension(v.height);
- int x = mWebView.contentToViewX(v.x);
- int y = mWebView.contentToViewY(v.y);
-
- AbsoluteLayout.LayoutParams lp;
- ViewGroup.LayoutParams layoutParams = v.mView.getLayoutParams();
-
- if (layoutParams instanceof AbsoluteLayout.LayoutParams) {
- lp = (AbsoluteLayout.LayoutParams) layoutParams;
- lp.width = width;
- lp.height = height;
- lp.x = x;
- lp.y = y;
- } else {
- lp = new AbsoluteLayout.LayoutParams(width, height, x, y);
- }
-
- // apply the layout to the view
- v.mView.setLayoutParams(lp);
-
- if(v.mView instanceof SurfaceView) {
-
- final SurfaceView sView = (SurfaceView) v.mView;
-
- if (sView.isFixedSize() && mZoomInProgress) {
- /* If we're already fixed, and we're in a zoom, then do nothing
- about the size. Just wait until we get called at the end of
- the zoom session (with mZoomInProgress false) and we'll
- fixup our size then.
- */
- return;
- }
-
- /* Compute proportional fixed width/height if necessary.
- *
- * NOTE: plugins (e.g. Flash) must not explicitly fix the size of
- * their surface. The logic below will result in unexpected behavior
- * for the plugin if they attempt to fix the size of the surface.
- */
- int fixedW = width;
- int fixedH = height;
- if (fixedW > MAX_SURFACE_DIMENSION || fixedH > MAX_SURFACE_DIMENSION) {
- if (v.width > v.height) {
- fixedW = MAX_SURFACE_DIMENSION;
- fixedH = v.height * MAX_SURFACE_DIMENSION / v.width;
- } else {
- fixedH = MAX_SURFACE_DIMENSION;
- fixedW = v.width * MAX_SURFACE_DIMENSION / v.height;
- }
- }
- if (fixedW * fixedH > MAX_SURFACE_AREA) {
- float area = MAX_SURFACE_AREA;
- if (v.width > v.height) {
- fixedW = (int)Math.sqrt(area * v.width / v.height);
- fixedH = v.height * fixedW / v.width;
- } else {
- fixedH = (int)Math.sqrt(area * v.height / v.width);
- fixedW = v.width * fixedH / v.height;
- }
- }
-
- if (fixedW != width || fixedH != height) {
- // if we get here, either our dimensions or area (or both)
- // exeeded our max, so we had to compute fixedW and fixedH
- sView.getHolder().setFixedSize(fixedW, fixedH);
- } else if (!sView.isFixedSize() && mZoomInProgress) {
- // just freeze where we were (view size) until we're done with
- // the zoom progress
- sView.getHolder().setFixedSize(sView.getWidth(),
- sView.getHeight());
- } else if (sView.isFixedSize() && !mZoomInProgress) {
- /* The changing of visibility is a hack to get around a bug in
- * the framework that causes the surface to revert to the size
- * it was prior to being fixed before it redraws using the
- * values currently in its layout.
- *
- * The surface is destroyed when it is set to invisible and then
- * recreated at the new dimensions when it is made visible. The
- * same destroy/create step occurs without the change in
- * visibility, but then exhibits the behavior described in the
- * previous paragraph.
- */
- if (sView.getVisibility() == View.VISIBLE) {
- sView.setVisibility(View.INVISIBLE);
- sView.getHolder().setSizeFromLayout();
- // setLayoutParams() only requests the layout. If we set it
- // to VISIBLE now, it will use the old dimension to set the
- // size. Post a message to ensure that it shows the new size.
- mWebView.mPrivateHandler.post(new Runnable() {
- public void run() {
- sView.setVisibility(View.VISIBLE);
- }
- });
- } else {
- sView.getHolder().setSizeFromLayout();
- }
- }
- }
- }
-
- void startZoom() {
- mZoomInProgress = true;
- for (ChildView v : mChildren) {
- requestLayout(v);
- }
- }
-
- void endZoom() {
- mZoomInProgress = false;
- for (ChildView v : mChildren) {
- requestLayout(v);
- }
- }
-
- void scaleAll() {
- for (ChildView v : mChildren) {
- requestLayout(v);
- }
- }
-
- void hideAll() {
- if (mHidden) {
- return;
- }
- for (ChildView v : mChildren) {
- v.mView.setVisibility(View.GONE);
- }
- mHidden = true;
- }
-
- void showAll() {
- if (!mHidden) {
- return;
- }
- for (ChildView v : mChildren) {
- v.mView.setVisibility(View.VISIBLE);
- }
- mHidden = false;
- }
-
- void postResetStateAll() {
- mWebView.mPrivateHandler.post(new Runnable() {
- public void run() {
- mReadyToDraw = false;
- }
- });
- }
-
- void postReadyToDrawAll() {
- mWebView.mPrivateHandler.post(new Runnable() {
- public void run() {
- mReadyToDraw = true;
- for (ChildView v : mChildren) {
- v.mView.setVisibility(View.VISIBLE);
- }
- }
- });
- }
-
- ChildView hitTest(int contentX, int contentY) {
- if (mHidden) {
- return null;
- }
- for (ChildView v : mChildren) {
- if (v.mView.getVisibility() == View.VISIBLE) {
- if (contentX >= v.x && contentX < (v.x + v.width)
- && contentY >= v.y && contentY < (v.y + v.height)) {
- return v;
- }
- }
- }
- return null;
- }
-}
+++ /dev/null
-/*
- * 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.graphics.Point;
-import android.webkit.WebViewCore.DrawData;
-
-import java.io.DataInputStream;
-import java.io.DataOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-
-/**
- * @hide
- */
-class ViewStateSerializer {
-
- private static final int WORKING_STREAM_STORAGE = 16 * 1024;
-
- // VERSION = 1 was for pictures encoded using a previous copy of libskia
- static final int VERSION = 2;
-
- static boolean serializeViewState(OutputStream stream, DrawData draw)
- throws IOException {
- int baseLayer = draw.mBaseLayer;
- if (baseLayer == 0) {
- return false;
- }
- DataOutputStream dos = new DataOutputStream(stream);
- dos.writeInt(VERSION);
- dos.writeInt(draw.mContentSize.x);
- dos.writeInt(draw.mContentSize.y);
- return nativeSerializeViewState(baseLayer, dos,
- new byte[WORKING_STREAM_STORAGE]);
- }
-
- static DrawData deserializeViewState(InputStream stream)
- throws IOException {
- DataInputStream dis = new DataInputStream(stream);
- int version = dis.readInt();
- if (version > VERSION) {
- throw new IOException("Unexpected version: " + version);
- }
- int contentWidth = dis.readInt();
- int contentHeight = dis.readInt();
- int baseLayer = nativeDeserializeViewState(version, dis,
- new byte[WORKING_STREAM_STORAGE]);
-
- final WebViewCore.DrawData draw = new WebViewCore.DrawData();
- draw.mViewState = new WebViewCore.ViewState();
- draw.mContentSize = new Point(contentWidth, contentHeight);
- draw.mBaseLayer = baseLayer;
- stream.close();
- return draw;
- }
-
- public static void dumpLayerHierarchy(int baseLayer, OutputStream out, int level) {
- nativeDumpLayerHierarchy(baseLayer, level, out,
- new byte[WORKING_STREAM_STORAGE]);
- }
-
-
- private static native void nativeDumpLayerHierarchy(int baseLayer, int level,
- OutputStream out, byte[] storage);
-
- private static native boolean nativeSerializeViewState(int baseLayer,
- OutputStream stream, byte[] storage);
-
- // Returns a pointer to the BaseLayer
- private static native int nativeDeserializeViewState(int version,
- InputStream stream, byte[] storage);
-
- private ViewStateSerializer() {}
-}
+++ /dev/null
-/*
- * Copyright (C) 2012 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.io.Serializable;
-import java.util.ArrayList;
-
-/* package */ class WebBackForwardListClassic extends WebBackForwardList implements Cloneable,
- Serializable {
-
- // Current position in the list.
- private int mCurrentIndex;
- // ArrayList of WebHistoryItems for maintaining our copy.
- private ArrayList<WebHistoryItemClassic> mArray;
- // Flag to indicate that the list is invalid
- private boolean mClearPending;
- // CallbackProxy to issue client callbacks.
- private final CallbackProxy mCallbackProxy;
-
- /*package*/ WebBackForwardListClassic(CallbackProxy proxy) {
- mCurrentIndex = -1;
- mArray = new ArrayList<WebHistoryItemClassic>();
- mCallbackProxy = proxy;
- }
-
- public synchronized WebHistoryItemClassic getCurrentItem() {
- return getItemAtIndex(mCurrentIndex);
- }
-
- public synchronized int getCurrentIndex() {
- return mCurrentIndex;
- }
-
- public synchronized WebHistoryItemClassic getItemAtIndex(int index) {
- if (index < 0 || index >= getSize()) {
- return null;
- }
- return mArray.get(index);
- }
-
- public synchronized int getSize() {
- return mArray.size();
- }
-
- /**
- * Mark the back/forward list as having a pending clear. This is used on the
- * UI side to mark the list as being invalid during the clearHistory method.
- */
- /*package*/ synchronized void setClearPending() {
- mClearPending = true;
- }
-
- /**
- * Return the status of the clear flag. This is used on the UI side to
- * determine if the list is valid for checking things like canGoBack.
- */
- /*package*/ synchronized boolean getClearPending() {
- return mClearPending;
- }
-
- /**
- * Add a new history item to the list. This will remove all items after the
- * current item and append the new item to the end of the list. Called from
- * the WebCore thread only. Synchronized because the UI thread may be
- * reading the array or the current index.
- * @param item A new history item.
- */
- /*package*/ synchronized void addHistoryItem(WebHistoryItem item) {
- // Update the current position because we are going to add the new item
- // in that slot.
- ++mCurrentIndex;
- // If the current position is not at the end, remove all history items
- // after the current item.
- final int size = mArray.size();
- final int newPos = mCurrentIndex;
- if (newPos != size) {
- for (int i = size - 1; i >= newPos; i--) {
- final WebHistoryItem h = mArray.remove(i);
- }
- }
- // Add the item to the list.
- mArray.add((WebHistoryItemClassic) item);
- if (mCallbackProxy != null) {
- mCallbackProxy.onNewHistoryItem(item);
- }
- }
-
- /**
- * Clear the back/forward list. Called from the WebCore thread.
- */
- /*package*/ synchronized void close(int nativeFrame) {
- // Clear the array first because nativeClose will call addHistoryItem
- // with the current item.
- mArray.clear();
- mCurrentIndex = -1;
- nativeClose(nativeFrame);
- // Reset the clear flag
- mClearPending = false;
- }
-
- /* Remove the item at the given index. Called by JNI only. */
- private synchronized void removeHistoryItem(int index) {
- // XXX: This is a special case. Since the callback is only triggered
- // when removing the first item, we can assert that the index is 0.
- // This lets us change the current index without having to query the
- // native BackForwardList.
- if (DebugFlags.WEB_BACK_FORWARD_LIST && (index != 0)) {
- throw new AssertionError();
- }
- final WebHistoryItem h = mArray.remove(index);
- // XXX: If we ever add another callback for removing history items at
- // any index, this will no longer be valid.
- mCurrentIndex--;
- }
-
- public synchronized WebBackForwardListClassic clone() {
- WebBackForwardListClassic l = new WebBackForwardListClassic(null);
- if (mClearPending) {
- // If a clear is pending, return a copy with only the current item.
- l.addHistoryItem(getCurrentItem());
- return l;
- }
- l.mCurrentIndex = mCurrentIndex;
- int size = getSize();
- l.mArray = new ArrayList<WebHistoryItemClassic>(size);
- for (int i = 0; i < size; i++) {
- // Add a copy of each WebHistoryItem
- l.mArray.add(mArray.get(i).clone());
- }
- return l;
- }
-
- /**
- * Set the new history index.
- * @param newIndex The new history index.
- */
- /*package*/ synchronized void setCurrentIndex(int newIndex) {
- mCurrentIndex = newIndex;
- if (mCallbackProxy != null) {
- mCallbackProxy.onIndexChanged(getItemAtIndex(newIndex), newIndex);
- }
- }
-
- /**
- * Restore the history index.
- */
- /*package*/ static native synchronized void restoreIndex(int nativeFrame,
- int index);
-
- /* Close the native list. */
- private static native void nativeClose(int nativeFrame);
-}
+++ /dev/null
-/*
- * Copyright (C) 2012 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.app.Activity;
-import android.app.AlertDialog;
-import android.content.Context;
-import android.content.DialogInterface;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.Message;
-import android.os.Process;
-import android.webkit.WebViewCore.EventHub;
-
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.Set;
-
-// A Runnable that will monitor if the WebCore thread is still
-// processing messages by pinging it every so often. It is safe
-// to call the public methods of this class from any thread.
-class WebCoreThreadWatchdog implements Runnable {
-
- // A message with this id is sent by the WebCore thread to notify the
- // Watchdog that the WebCore thread is still processing messages
- // (i.e. everything is OK).
- private static final int IS_ALIVE = 100;
-
- // This message is placed in the Watchdog's queue and removed when we
- // receive an IS_ALIVE. If it is ever processed, we consider the
- // WebCore thread unresponsive.
- private static final int TIMED_OUT = 101;
-
- // Wait 10s after hearing back from the WebCore thread before checking it's still alive.
- private static final int HEARTBEAT_PERIOD = 10 * 1000;
-
- // If there's no callback from the WebCore thread for 30s, prompt the user the page has
- // become unresponsive.
- private static final int TIMEOUT_PERIOD = 30 * 1000;
-
- // After the first timeout, use a shorter period before re-prompting the user.
- private static final int SUBSEQUENT_TIMEOUT_PERIOD = 15 * 1000;
-
- private Handler mWebCoreThreadHandler;
- private Handler mHandler;
- private boolean mPaused;
-
- private Set<WebViewClassic> mWebViews;
-
- private static WebCoreThreadWatchdog sInstance;
-
- public synchronized static WebCoreThreadWatchdog start(Handler webCoreThreadHandler) {
- if (sInstance == null) {
- sInstance = new WebCoreThreadWatchdog(webCoreThreadHandler);
- new Thread(sInstance, "WebCoreThreadWatchdog").start();
- }
- return sInstance;
- }
-
- public synchronized static void registerWebView(WebViewClassic w) {
- if (sInstance != null) {
- sInstance.addWebView(w);
- }
- }
-
- public synchronized static void unregisterWebView(WebViewClassic w) {
- if (sInstance != null) {
- sInstance.removeWebView(w);
- }
- }
-
- public synchronized static void pause() {
- if (sInstance != null) {
- sInstance.pauseWatchdog();
- }
- }
-
- public synchronized static void resume() {
- if (sInstance != null) {
- sInstance.resumeWatchdog();
- }
- }
-
- private void addWebView(WebViewClassic w) {
- if (mWebViews == null) {
- mWebViews = new HashSet<WebViewClassic>();
- }
- mWebViews.add(w);
- }
-
- private void removeWebView(WebViewClassic w) {
- mWebViews.remove(w);
- }
-
- private WebCoreThreadWatchdog(Handler webCoreThreadHandler) {
- mWebCoreThreadHandler = webCoreThreadHandler;
- }
-
- private void pauseWatchdog() {
- mPaused = true;
-
- if (mHandler == null) {
- return;
- }
-
- mHandler.removeMessages(TIMED_OUT);
- mHandler.removeMessages(IS_ALIVE);
- mWebCoreThreadHandler.removeMessages(EventHub.HEARTBEAT);
- }
-
- private void resumeWatchdog() {
- if (!mPaused) {
- // Do nothing if we get a call to resume without being paused.
- // This can happen during the initialisation of the WebView.
- return;
- }
-
- mPaused = false;
-
- if (mHandler == null) {
- return;
- }
-
- mWebCoreThreadHandler.obtainMessage(EventHub.HEARTBEAT,
- mHandler.obtainMessage(IS_ALIVE)).sendToTarget();
- mHandler.sendMessageDelayed(mHandler.obtainMessage(TIMED_OUT), TIMEOUT_PERIOD);
- }
-
- private void createHandler() {
- synchronized (WebCoreThreadWatchdog.class) {
- mHandler = new Handler() {
- @Override
- public void handleMessage(Message msg) {
- switch (msg.what) {
- case IS_ALIVE:
- synchronized(WebCoreThreadWatchdog.class) {
- if (mPaused) {
- return;
- }
-
- // The WebCore thread still seems alive. Reset the countdown timer.
- removeMessages(TIMED_OUT);
- sendMessageDelayed(obtainMessage(TIMED_OUT), TIMEOUT_PERIOD);
- mWebCoreThreadHandler.sendMessageDelayed(
- mWebCoreThreadHandler.obtainMessage(EventHub.HEARTBEAT,
- mHandler.obtainMessage(IS_ALIVE)),
- HEARTBEAT_PERIOD);
- }
- break;
-
- case TIMED_OUT:
- boolean postedDialog = false;
- synchronized (WebCoreThreadWatchdog.class) {
- Iterator<WebViewClassic> it = mWebViews.iterator();
- // Check each WebView we are aware of and find one that is capable of
- // showing the user a prompt dialog.
- while (it.hasNext()) {
- WebView activeView = it.next().getWebView();
-
- if (activeView.getWindowToken() != null &&
- activeView.getViewRootImpl() != null) {
- postedDialog = activeView.post(new PageNotRespondingRunnable(
- activeView.getContext(), this));
-
- if (postedDialog) {
- // We placed the message into the UI thread for an attached
- // WebView so we've made our best attempt to display the
- // "page not responding" dialog to the user. Although the
- // message is in the queue, there is no guarantee when/if
- // the runnable will execute. In the case that the runnable
- // never executes, the user will need to terminate the
- // process manually.
- break;
- }
- }
- }
-
- if (!postedDialog) {
- // There's no active webview we can use to show the dialog, so
- // wait again. If we never get a usable view, the user will
- // never get the chance to terminate the process, and will
- // need to do it manually.
- sendMessageDelayed(obtainMessage(TIMED_OUT),
- SUBSEQUENT_TIMEOUT_PERIOD);
- }
- }
- break;
- }
- }
- };
- }
- }
-
- @Override
- public void run() {
- Looper.prepare();
-
- createHandler();
-
- // Send the initial control to WebViewCore and start the timeout timer as long as we aren't
- // paused.
- synchronized (WebCoreThreadWatchdog.class) {
- if (!mPaused) {
- mWebCoreThreadHandler.obtainMessage(EventHub.HEARTBEAT,
- mHandler.obtainMessage(IS_ALIVE)).sendToTarget();
- mHandler.sendMessageDelayed(mHandler.obtainMessage(TIMED_OUT), TIMEOUT_PERIOD);
- }
- }
-
- Looper.loop();
- }
-
- private class PageNotRespondingRunnable implements Runnable {
- Context mContext;
- private Handler mWatchdogHandler;
-
- public PageNotRespondingRunnable(Context context, Handler watchdogHandler) {
- mContext = context;
- mWatchdogHandler = watchdogHandler;
- }
-
- @Override
- public void run() {
- // This must run on the UI thread as it is displaying an AlertDialog.
- assert Looper.getMainLooper().getThread() == Thread.currentThread();
- new AlertDialog.Builder(mContext)
- .setMessage(com.android.internal.R.string.webpage_unresponsive)
- .setPositiveButton(com.android.internal.R.string.force_close,
- new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int which) {
- // User chose to force close.
- Process.killProcess(Process.myPid());
- }
- })
- .setNegativeButton(com.android.internal.R.string.wait,
- new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int which) {
- // The user chose to wait. The last HEARTBEAT message
- // will still be in the WebCore thread's queue, so all
- // we need to do is post another TIMED_OUT so that the
- // user will get prompted again if the WebCore thread
- // doesn't sort itself out.
- mWatchdogHandler.sendMessageDelayed(
- mWatchdogHandler.obtainMessage(TIMED_OUT),
- SUBSEQUENT_TIMEOUT_PERIOD);
- }
- })
- .setOnCancelListener(
- new DialogInterface.OnCancelListener() {
- @Override
- public void onCancel(DialogInterface dialog) {
- mWatchdogHandler.sendMessageDelayed(
- mWatchdogHandler.obtainMessage(TIMED_OUT),
- SUBSEQUENT_TIMEOUT_PERIOD);
- }
- })
- .setIconAttribute(android.R.attr.alertDialogIcon)
- .show();
- }
- }
-}
+++ /dev/null
-/*
- * Copyright (C) 2012 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.graphics.Bitmap;
-
-import java.net.MalformedURLException;
-import java.net.URL;
-
-/* package */ class WebHistoryItemClassic extends WebHistoryItem implements Cloneable {
- // Global identifier count.
- private static int sNextId = 0;
- // Unique identifier.
- private final int mId;
- // A point to a native WebHistoryItem instance which contains the actual data
- private int mNativeBridge;
- // The favicon for this item.
- private Bitmap mFavicon;
- // The pre-flattened data used for saving the state.
- private byte[] mFlattenedData;
- // The apple-touch-icon url for use when adding the site to the home screen,
- // as obtained from a <link> element in the page.
- private String mTouchIconUrlFromLink;
- // If no <link> is specified, this holds the default location of the
- // apple-touch-icon.
- private String mTouchIconUrlServerDefault;
- // Custom client data that is not flattened or read by native code.
- private Object mCustomData;
-
- /**
- * Basic constructor that assigns a unique id to the item. Called by JNI
- * only.
- */
- private WebHistoryItemClassic(int nativeBridge) {
- synchronized (WebHistoryItemClassic.class) {
- mId = sNextId++;
- }
- mNativeBridge = nativeBridge;
- nativeRef(mNativeBridge);
- }
-
- protected void finalize() throws Throwable {
- if (mNativeBridge != 0) {
- nativeUnref(mNativeBridge);
- mNativeBridge = 0;
- }
- }
-
- /**
- * Construct a new WebHistoryItem with initial flattened data.
- * @param data The pre-flattened data coming from restoreState.
- */
- /*package*/ WebHistoryItemClassic(byte[] data) {
- mFlattenedData = data;
- synchronized (WebHistoryItemClassic.class) {
- mId = sNextId++;
- }
- }
-
- /**
- * Construct a clone of a WebHistoryItem from the given item.
- * @param item The history item to clone.
- */
- private WebHistoryItemClassic(WebHistoryItemClassic item) {
- mFlattenedData = item.mFlattenedData;
- mId = item.mId;
- mFavicon = item.mFavicon;
- mNativeBridge = item.mNativeBridge;
- if (mNativeBridge != 0) {
- nativeRef(mNativeBridge);
- }
- }
-
- @Deprecated
- public int getId() {
- return mId;
- }
-
- public String getUrl() {
- if (mNativeBridge == 0) return null;
- return nativeGetUrl(mNativeBridge);
- }
-
- public String getOriginalUrl() {
- if (mNativeBridge == 0) return null;
- return nativeGetOriginalUrl(mNativeBridge);
- }
-
- public String getTitle() {
- if (mNativeBridge == 0) return null;
- return nativeGetTitle(mNativeBridge);
- }
-
- public Bitmap getFavicon() {
- if (mFavicon == null && mNativeBridge != 0) {
- mFavicon = nativeGetFavicon(mNativeBridge);
- }
- return mFavicon;
- }
-
- /**
- * Return the touch icon url.
- * If no touch icon <link> tag was specified, returns
- * <host>/apple-touch-icon.png. The DownloadTouchIcon class that
- * attempts to retrieve the touch icon will handle the case where
- * that file does not exist. An icon set by a <link> tag is always
- * used in preference to an icon saved on the server.
- * @hide
- */
- public String getTouchIconUrl() {
- if (mTouchIconUrlFromLink != null) {
- return mTouchIconUrlFromLink;
- } else if (mTouchIconUrlServerDefault != null) {
- return mTouchIconUrlServerDefault;
- }
-
- try {
- URL url = new URL(getOriginalUrl());
- mTouchIconUrlServerDefault = new URL(url.getProtocol(), url.getHost(), url.getPort(),
- "/apple-touch-icon.png").toString();
- } catch (MalformedURLException e) {
- return null;
- }
- return mTouchIconUrlServerDefault;
- }
-
- /**
- * Return the custom data provided by the client.
- * @hide
- */
- public Object getCustomData() {
- return mCustomData;
- }
-
- /**
- * Set the custom data field.
- * @param data An Object containing any data the client wishes to associate
- * with the item.
- * @hide
- */
- public void setCustomData(Object data) {
- // NOTE: WebHistoryItems are used in multiple threads. However, the
- // public facing apis are all getters with the exception of this one
- // api. Since this api is exclusive to clients, we don't make any
- // promises about thread safety.
- mCustomData = data;
- }
-
- /**
- * Set the favicon.
- * @param icon A Bitmap containing the favicon for this history item.
- * Note: The VM ensures 32-bit atomic read/write operations so we don't have
- * to synchronize this method.
- */
- /*package*/ void setFavicon(Bitmap icon) {
- mFavicon = icon;
- }
-
- /**
- * Set the touch icon url. Will not overwrite an icon that has been
- * set already from a <link> tag, unless the new icon is precomposed.
- * @hide
- */
- /*package*/ void setTouchIconUrl(String url, boolean precomposed) {
- if (precomposed || mTouchIconUrlFromLink == null) {
- mTouchIconUrlFromLink = url;
- }
- }
-
- /**
- * Get the pre-flattened data.
- * Note: The VM ensures 32-bit atomic read/write operations so we don't have
- * to synchronize this method.
- */
- /*package*/ byte[] getFlattenedData() {
- if (mNativeBridge != 0) {
- return nativeGetFlattenedData(mNativeBridge);
- }
- return mFlattenedData;
- }
-
- /**
- * Inflate this item.
- * Note: The VM ensures 32-bit atomic read/write operations so we don't have
- * to synchronize this method.
- */
- /*package*/ void inflate(int nativeFrame) {
- mNativeBridge = inflate(nativeFrame, mFlattenedData);
- mFlattenedData = null;
- }
-
- public synchronized WebHistoryItemClassic clone() {
- return new WebHistoryItemClassic(this);
- }
-
- /* Natively inflate this item, this method is called in the WebCore thread.
- */
- private native int inflate(int nativeFrame, byte[] data);
- private native void nativeRef(int nptr);
- private native void nativeUnref(int nptr);
- private native String nativeGetTitle(int nptr);
- private native String nativeGetUrl(int nptr);
- private native String nativeGetOriginalUrl(int nptr);
- private native byte[] nativeGetFlattenedData(int nptr);
- private native Bitmap nativeGetFavicon(int nptr);
-
-}
+++ /dev/null
-/*
- * Copyright (C) 2012 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.content.ContentResolver;
-import android.database.Cursor;
-import android.graphics.Bitmap;
-import android.os.Handler;
-import android.os.Message;
-import android.provider.Browser;
-import android.util.Log;
-
-import java.io.File;
-import java.util.HashMap;
-import java.util.Vector;
-
-class WebIconDatabaseClassic extends WebIconDatabase {
- private static final String LOGTAG = "WebIconDatabase";
- // Global instance of a WebIconDatabase
- private static WebIconDatabaseClassic sIconDatabase;
- // EventHandler for handling messages before and after the WebCore thread is
- // ready.
- private final EventHandler mEventHandler = new EventHandler();
-
- // Class to handle messages before WebCore is ready
- private static class EventHandler extends Handler {
- // Message ids
- static final int OPEN = 0;
- static final int CLOSE = 1;
- static final int REMOVE_ALL = 2;
- static final int REQUEST_ICON = 3;
- static final int RETAIN_ICON = 4;
- static final int RELEASE_ICON = 5;
- static final int BULK_REQUEST_ICON = 6;
- // Message for dispatching icon request results
- private static final int ICON_RESULT = 10;
- // Actual handler that runs in WebCore thread
- private Handler mHandler;
- // Vector of messages before the WebCore thread is ready
- private Vector<Message> mMessages = new Vector<Message>();
- // Class to handle a result dispatch
- private class IconResult {
- private final String mUrl;
- private final Bitmap mIcon;
- private final IconListener mListener;
- IconResult(String url, Bitmap icon, IconListener l) {
- mUrl = url;
- mIcon = icon;
- mListener = l;
- }
- void dispatch() {
- mListener.onReceivedIcon(mUrl, mIcon);
- }
- }
-
- @Override
- public void handleMessage(Message msg) {
- // Note: This is the message handler for the UI thread.
- switch (msg.what) {
- case ICON_RESULT:
- ((IconResult) msg.obj).dispatch();
- break;
- }
- }
-
- // Called by WebCore thread to create the actual handler
- private synchronized void createHandler() {
- if (mHandler == null) {
- mHandler = new Handler() {
- @Override
- public void handleMessage(Message msg) {
- // Note: This is the message handler for the WebCore
- // thread.
- switch (msg.what) {
- case OPEN:
- nativeOpen((String) msg.obj);
- break;
-
- case CLOSE:
- nativeClose();
- break;
-
- case REMOVE_ALL:
- nativeRemoveAllIcons();
- break;
-
- case REQUEST_ICON:
- IconListener l = (IconListener) msg.obj;
- String url = msg.getData().getString("url");
- requestIconAndSendResult(url, l);
- break;
-
- case BULK_REQUEST_ICON:
- bulkRequestIcons(msg);
- break;
-
- case RETAIN_ICON:
- nativeRetainIconForPageUrl((String) msg.obj);
- break;
-
- case RELEASE_ICON:
- nativeReleaseIconForPageUrl((String) msg.obj);
- break;
- }
- }
- };
- // Transfer all pending messages
- for (int size = mMessages.size(); size > 0; size--) {
- mHandler.sendMessage(mMessages.remove(0));
- }
- mMessages = null;
- }
- }
-
- private synchronized boolean hasHandler() {
- return mHandler != null;
- }
-
- private synchronized void postMessage(Message msg) {
- if (mMessages != null) {
- mMessages.add(msg);
- } else {
- mHandler.sendMessage(msg);
- }
- }
-
- private void bulkRequestIcons(Message msg) {
- HashMap map = (HashMap) msg.obj;
- IconListener listener = (IconListener) map.get("listener");
- ContentResolver cr = (ContentResolver) map.get("contentResolver");
- String where = (String) map.get("where");
-
- Cursor c = null;
- try {
- c = cr.query(
- Browser.BOOKMARKS_URI,
- new String[] { Browser.BookmarkColumns.URL },
- where, null, null);
- if (c.moveToFirst()) {
- do {
- String url = c.getString(0);
- requestIconAndSendResult(url, listener);
- } while (c.moveToNext());
- }
- } catch (IllegalStateException e) {
- Log.e(LOGTAG, "BulkRequestIcons", e);
- } finally {
- if (c != null) c.close();
- }
- }
-
- private void requestIconAndSendResult(String url, IconListener listener) {
- Bitmap icon = nativeIconForPageUrl(url);
- if (icon != null) {
- sendMessage(obtainMessage(ICON_RESULT,
- new IconResult(url, icon, listener)));
- }
- }
- }
-
- @Override
- public void open(String path) {
- if (path != null) {
- // Make the directories and parents if they don't exist
- File db = new File(path);
- if (!db.exists()) {
- db.mkdirs();
- }
- mEventHandler.postMessage(
- Message.obtain(null, EventHandler.OPEN, db.getAbsolutePath()));
- }
- }
-
- @Override
- public void close() {
- mEventHandler.postMessage(
- Message.obtain(null, EventHandler.CLOSE));
- }
-
- @Override
- public void removeAllIcons() {
- mEventHandler.postMessage(
- Message.obtain(null, EventHandler.REMOVE_ALL));
- }
-
- /**
- * Request the Bitmap representing the icon for the given page
- * url. If the icon exists, the listener will be called with the result.
- * @param url The page's url.
- * @param listener An implementation on IconListener to receive the result.
- */
- public void requestIconForPageUrl(String url, IconListener listener) {
- if (listener == null || url == null) {
- return;
- }
- Message msg = Message.obtain(null, EventHandler.REQUEST_ICON, listener);
- msg.getData().putString("url", url);
- mEventHandler.postMessage(msg);
- }
-
- /** {@hide}
- */
- public void bulkRequestIconForPageUrl(ContentResolver cr, String where,
- IconListener listener) {
- if (listener == null) {
- return;
- }
-
- // Special case situation: we don't want to add this message to the
- // queue if there is no handler because we may never have a real
- // handler to service the messages and the cursor will never get
- // closed.
- if (mEventHandler.hasHandler()) {
- // Don't use Bundle as it is parcelable.
- HashMap<String, Object> map = new HashMap<String, Object>();
- map.put("contentResolver", cr);
- map.put("where", where);
- map.put("listener", listener);
- Message msg =
- Message.obtain(null, EventHandler.BULK_REQUEST_ICON, map);
- mEventHandler.postMessage(msg);
- }
- }
-
- @Override
- public void retainIconForPageUrl(String url) {
- if (url != null) {
- mEventHandler.postMessage(
- Message.obtain(null, EventHandler.RETAIN_ICON, url));
- }
- }
-
- @Override
- public void releaseIconForPageUrl(String url) {
- if (url != null) {
- mEventHandler.postMessage(
- Message.obtain(null, EventHandler.RELEASE_ICON, url));
- }
- }
-
- /**
- * Get the global instance of WebIconDatabase.
- * @return A single instance of WebIconDatabase. It will be the same
- * instance for the current process each time this method is
- * called.
- */
- public static WebIconDatabaseClassic getInstance() {
- // XXX: Must be created in the UI thread.
- if (sIconDatabase == null) {
- sIconDatabase = new WebIconDatabaseClassic();
- }
- return sIconDatabase;
- }
-
- /**
- * Create the internal handler and transfer all pending messages.
- * XXX: Called by WebCore thread only!
- */
- /*package*/ void createHandler() {
- mEventHandler.createHandler();
- }
-
- /**
- * Private constructor to avoid anyone else creating an instance.
- */
- private WebIconDatabaseClassic() {}
-
- // Native functions
- private static native void nativeOpen(String path);
- private static native void nativeClose();
- private static native void nativeRemoveAllIcons();
- private static native Bitmap nativeIconForPageUrl(String url);
- private static native void nativeRetainIconForPageUrl(String url);
- private static native void nativeReleaseIconForPageUrl(String url);
-}
+++ /dev/null
-/*
- * Copyright (C) 2012 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.content.Context;
-import android.content.SharedPreferences;
-import android.content.pm.PackageManager;
-import android.os.Build;
-import android.os.Handler;
-import android.os.Message;
-import android.provider.Settings;
-import android.provider.Settings.SettingNotFoundException;
-import android.util.EventLog;
-
-import java.util.Locale;
-
-/**
- * WebSettings implementation for the WebViewClassic implementation of WebView.
- * @hide
- */
-public class WebSettingsClassic extends WebSettings {
- // TODO: Keep this up to date
- private static final String PREVIOUS_VERSION = "4.1.1";
-
- // WebView associated with this WebSettings.
- private WebViewClassic mWebView;
- // BrowserFrame used to access the native frame pointer.
- private BrowserFrame mBrowserFrame;
- // Flag to prevent multiple SYNC messages at one time.
- private boolean mSyncPending = false;
- // Custom handler that queues messages until the WebCore thread is active.
- private final EventHandler mEventHandler;
-
- // Private settings so we don't have to go into native code to
- // retrieve the values. After setXXX, postSync() needs to be called.
- //
- // The default values need to match those in WebSettings.cpp
- // If the defaults change, please also update the JavaDocs so developers
- // know what they are.
- private LayoutAlgorithm mLayoutAlgorithm = LayoutAlgorithm.NARROW_COLUMNS;
- private Context mContext;
- private int mTextSize = 100;
- private String mStandardFontFamily = "sans-serif";
- private String mFixedFontFamily = "monospace";
- private String mSansSerifFontFamily = "sans-serif";
- private String mSerifFontFamily = "serif";
- private String mCursiveFontFamily = "cursive";
- private String mFantasyFontFamily = "fantasy";
- private String mDefaultTextEncoding;
- private String mUserAgent;
- private boolean mUseDefaultUserAgent;
- private String mAcceptLanguage;
- private int mMinimumFontSize = 8;
- private int mMinimumLogicalFontSize = 8;
- private int mDefaultFontSize = 16;
- private int mDefaultFixedFontSize = 13;
- private int mPageCacheCapacity = 0;
- private boolean mLoadsImagesAutomatically = true;
- private boolean mBlockNetworkImage = false;
- private boolean mBlockNetworkLoads;
- private boolean mJavaScriptEnabled = false;
- private boolean mAllowUniversalAccessFromFileURLs = false;
- private boolean mAllowFileAccessFromFileURLs = false;
- private boolean mHardwareAccelSkia = false;
- private boolean mShowVisualIndicator = false;
- private PluginState mPluginState = PluginState.OFF;
- private boolean mJavaScriptCanOpenWindowsAutomatically = false;
- private boolean mUseDoubleTree = false;
- private boolean mUseWideViewport = false;
- private boolean mSupportMultipleWindows = false;
- private boolean mShrinksStandaloneImagesToFit = false;
- private long mMaximumDecodedImageSize = 0; // 0 means default
- private boolean mPrivateBrowsingEnabled = false;
- private boolean mSyntheticLinksEnabled = true;
- // HTML5 API flags
- private boolean mAppCacheEnabled = false;
- private boolean mDatabaseEnabled = false;
- private boolean mDomStorageEnabled = false;
- private boolean mWorkersEnabled = false; // only affects V8.
- private boolean mGeolocationEnabled = true;
- private boolean mXSSAuditorEnabled = false;
- private boolean mLinkPrefetchEnabled = false;
- // HTML5 configuration parameters
- private long mAppCacheMaxSize = Long.MAX_VALUE;
- private String mAppCachePath = null;
- private String mDatabasePath = "";
- // The WebCore DatabaseTracker only allows the database path to be set
- // once. Keep track of when the path has been set.
- private boolean mDatabasePathHasBeenSet = false;
- private String mGeolocationDatabasePath = "";
- // Don't need to synchronize the get/set methods as they
- // are basic types, also none of these values are used in
- // native WebCore code.
- private ZoomDensity mDefaultZoom = ZoomDensity.MEDIUM;
- private RenderPriority mRenderPriority = RenderPriority.NORMAL;
- private int mOverrideCacheMode = LOAD_DEFAULT;
- private int mDoubleTapZoom = 100;
- private boolean mSaveFormData = true;
- private boolean mAutoFillEnabled = false;
- private boolean mSavePassword = true;
- private boolean mLightTouchEnabled = false;
- private boolean mNeedInitialFocus = true;
- private boolean mNavDump = false;
- private boolean mSupportZoom = true;
- private boolean mMediaPlaybackRequiresUserGesture = true;
- private boolean mBuiltInZoomControls = false;
- private boolean mDisplayZoomControls = true;
- private boolean mAllowFileAccess = true;
- private boolean mAllowContentAccess = true;
- private boolean mLoadWithOverviewMode = false;
- private boolean mEnableSmoothTransition = false;
- private boolean mForceUserScalable = false;
- private boolean mPasswordEchoEnabled = true;
-
- // AutoFill Profile data
- public static class AutoFillProfile {
- private int mUniqueId;
- private String mFullName;
- private String mEmailAddress;
- private String mCompanyName;
- private String mAddressLine1;
- private String mAddressLine2;
- private String mCity;
- private String mState;
- private String mZipCode;
- private String mCountry;
- private String mPhoneNumber;
-
- public AutoFillProfile(int uniqueId, String fullName, String email,
- String companyName, String addressLine1, String addressLine2,
- String city, String state, String zipCode, String country,
- String phoneNumber) {
- mUniqueId = uniqueId;
- mFullName = fullName;
- mEmailAddress = email;
- mCompanyName = companyName;
- mAddressLine1 = addressLine1;
- mAddressLine2 = addressLine2;
- mCity = city;
- mState = state;
- mZipCode = zipCode;
- mCountry = country;
- mPhoneNumber = phoneNumber;
- }
-
- public int getUniqueId() { return mUniqueId; }
- public String getFullName() { return mFullName; }
- public String getEmailAddress() { return mEmailAddress; }
- public String getCompanyName() { return mCompanyName; }
- public String getAddressLine1() { return mAddressLine1; }
- public String getAddressLine2() { return mAddressLine2; }
- public String getCity() { return mCity; }
- public String getState() { return mState; }
- public String getZipCode() { return mZipCode; }
- public String getCountry() { return mCountry; }
- public String getPhoneNumber() { return mPhoneNumber; }
- }
-
-
- private AutoFillProfile mAutoFillProfile;
-
- private boolean mUseWebViewBackgroundForOverscroll = true;
-
- // private WebSettings, not accessible by the host activity
- static private int mDoubleTapToastCount = 3;
-
- private static final String PREF_FILE = "WebViewSettings";
- private static final String DOUBLE_TAP_TOAST_COUNT = "double_tap_toast_count";
-
- // Class to handle messages before WebCore is ready.
- private class EventHandler {
- // Message id for syncing
- static final int SYNC = 0;
- // Message id for setting priority
- static final int PRIORITY = 1;
- // Message id for writing double-tap toast count
- static final int SET_DOUBLE_TAP_TOAST_COUNT = 2;
- // Actual WebCore thread handler
- private Handler mHandler;
-
- private synchronized void createHandler() {
- // as mRenderPriority can be set before thread is running, sync up
- setRenderPriority();
-
- // create a new handler
- mHandler = new Handler() {
- @Override
- public void handleMessage(Message msg) {
- switch (msg.what) {
- case SYNC:
- synchronized (WebSettingsClassic.this) {
- if (mBrowserFrame.mNativeFrame != 0) {
- nativeSync(mBrowserFrame.mNativeFrame);
- }
- mSyncPending = false;
- }
- break;
-
- case PRIORITY: {
- setRenderPriority();
- break;
- }
-
- case SET_DOUBLE_TAP_TOAST_COUNT: {
- SharedPreferences.Editor editor = mContext
- .getSharedPreferences(PREF_FILE,
- Context.MODE_PRIVATE).edit();
- editor.putInt(DOUBLE_TAP_TOAST_COUNT,
- mDoubleTapToastCount);
- editor.commit();
- break;
- }
- }
- }
- };
- }
-
- private void setRenderPriority() {
- synchronized (WebSettingsClassic.this) {
- if (mRenderPriority == RenderPriority.NORMAL) {
- android.os.Process.setThreadPriority(
- android.os.Process.THREAD_PRIORITY_DEFAULT);
- } else if (mRenderPriority == RenderPriority.HIGH) {
- android.os.Process.setThreadPriority(
- android.os.Process.THREAD_PRIORITY_FOREGROUND +
- android.os.Process.THREAD_PRIORITY_LESS_FAVORABLE);
- } else if (mRenderPriority == RenderPriority.LOW) {
- android.os.Process.setThreadPriority(
- android.os.Process.THREAD_PRIORITY_BACKGROUND);
- }
- }
- }
-
- /**
- * Send a message to the private queue or handler.
- */
- private synchronized boolean sendMessage(Message msg) {
- if (mHandler != null) {
- mHandler.sendMessage(msg);
- return true;
- } else {
- return false;
- }
- }
- }
-
- // User agent strings.
- private static final String DESKTOP_USERAGENT = "Mozilla/5.0 (X11; " +
- "Linux x86_64) AppleWebKit/534.24 (KHTML, like Gecko) " +
- "Chrome/11.0.696.34 Safari/534.24";
- private static final String IPHONE_USERAGENT =
- "Mozilla/5.0 (iPhone; U; CPU iPhone OS 3_0 like Mac OS X; en-us)"
- + " AppleWebKit/528.18 (KHTML, like Gecko) Version/4.0"
- + " Mobile/7A341 Safari/528.16";
- private static Locale sLocale;
- private static Object sLockForLocaleSettings;
-
- /**
- * Package constructor to prevent clients from creating a new settings
- * instance.
- */
- WebSettingsClassic(Context context, WebViewClassic webview) {
- mEventHandler = new EventHandler();
- mContext = context;
- mWebView = webview;
- mDefaultTextEncoding = context.getString(com.android.internal.
- R.string.default_text_encoding);
-
- if (sLockForLocaleSettings == null) {
- sLockForLocaleSettings = new Object();
- sLocale = Locale.getDefault();
- }
- mAcceptLanguage = getCurrentAcceptLanguage();
- mUserAgent = getCurrentUserAgent();
- mUseDefaultUserAgent = true;
-
- mBlockNetworkLoads = mContext.checkPermission(
- "android.permission.INTERNET", android.os.Process.myPid(),
- android.os.Process.myUid()) != PackageManager.PERMISSION_GRANTED;
-
- // SDK specific settings. See issue 6212665
- if (mContext.getApplicationInfo().targetSdkVersion <
- Build.VERSION_CODES.JELLY_BEAN) {
- mAllowUniversalAccessFromFileURLs = true;
- mAllowFileAccessFromFileURLs = true;
- }
- try {
- mPasswordEchoEnabled =
- Settings.System.getInt(context.getContentResolver(),
- Settings.System.TEXT_SHOW_PASSWORD) != 0;
- } catch (SettingNotFoundException e) {
- mPasswordEchoEnabled = true;
- }
- }
-
- private static final String ACCEPT_LANG_FOR_US_LOCALE = "en-US";
-
- /**
- * Looks at sLocale and returns current AcceptLanguage String.
- * @return Current AcceptLanguage String.
- */
- private String getCurrentAcceptLanguage() {
- Locale locale;
- synchronized(sLockForLocaleSettings) {
- locale = sLocale;
- }
- StringBuilder buffer = new StringBuilder();
- addLocaleToHttpAcceptLanguage(buffer, locale);
-
- if (!Locale.US.equals(locale)) {
- if (buffer.length() > 0) {
- buffer.append(", ");
- }
- buffer.append(ACCEPT_LANG_FOR_US_LOCALE);
- }
-
- return buffer.toString();
- }
-
- /**
- * Convert obsolete language codes, including Hebrew/Indonesian/Yiddish,
- * to new standard.
- */
- private static String convertObsoleteLanguageCodeToNew(String langCode) {
- if (langCode == null) {
- return null;
- }
- if ("iw".equals(langCode)) {
- // Hebrew
- return "he";
- } else if ("in".equals(langCode)) {
- // Indonesian
- return "id";
- } else if ("ji".equals(langCode)) {
- // Yiddish
- return "yi";
- }
- return langCode;
- }
-
- private static void addLocaleToHttpAcceptLanguage(StringBuilder builder,
- Locale locale) {
- String language = convertObsoleteLanguageCodeToNew(locale.getLanguage());
- if (language != null) {
- builder.append(language);
- String country = locale.getCountry();
- if (country != null) {
- builder.append("-");
- builder.append(country);
- }
- }
- }
-
- /**
- * Looks at sLocale and mContext and returns current UserAgent String.
- * @return Current UserAgent String.
- */
- private synchronized String getCurrentUserAgent() {
- Locale locale;
- synchronized(sLockForLocaleSettings) {
- locale = sLocale;
- }
- return getDefaultUserAgentForLocale(mContext, locale);
- }
-
- /**
- * Returns the default User-Agent used by a WebView.
- * An instance of WebView could use a different User-Agent if a call
- * is made to {@link WebSettings#setUserAgent(int)} or
- * {@link WebSettings#setUserAgentString(String)}.
- *
- * @param context a Context object used to access application assets
- * @param locale The Locale to use in the User-Agent string.
- * @see WebViewFactoryProvider#getDefaultUserAgent(Context)
- * @see WebView#getDefaultUserAgent(Context)
- */
- public static String getDefaultUserAgentForLocale(Context context, Locale locale) {
- StringBuffer buffer = new StringBuffer();
- // Add version
- final String version = Build.VERSION.RELEASE;
- if (version.length() > 0) {
- if (Character.isDigit(version.charAt(0))) {
- // Release is a version, eg "3.1"
- buffer.append(version);
- } else {
- // Release is a codename, eg "Honeycomb"
- // In this case, use the previous release's version
- buffer.append(PREVIOUS_VERSION);
- }
- } else {
- // default to "1.0"
- buffer.append("1.0");
- }
- buffer.append("; ");
- final String language = locale.getLanguage();
- if (language != null) {
- buffer.append(convertObsoleteLanguageCodeToNew(language));
- final String country = locale.getCountry();
- if (country != null) {
- buffer.append("-");
- buffer.append(country.toLowerCase());
- }
- } else {
- // default to "en"
- buffer.append("en");
- }
- buffer.append(";");
- // add the model for the release build
- if ("REL".equals(Build.VERSION.CODENAME)) {
- final String model = Build.MODEL;
- if (model.length() > 0) {
- buffer.append(" ");
- buffer.append(model);
- }
- }
- final String id = Build.ID;
- if (id.length() > 0) {
- buffer.append(" Build/");
- buffer.append(id);
- }
- String mobile = context.getResources().getText(
- com.android.internal.R.string.web_user_agent_target_content).toString();
- final String base = context.getResources().getText(
- com.android.internal.R.string.web_user_agent).toString();
- return String.format(base, buffer, mobile);
- }
-
- /**
- * @see android.webkit.WebSettings#setNavDump(boolean)
- */
- @Override
- @Deprecated
- public void setNavDump(boolean enabled) {
- mNavDump = enabled;
- }
-
- /**
- * @see android.webkit.WebSettings#getNavDump()
- */
- @Override
- @Deprecated
- public boolean getNavDump() {
- return mNavDump;
- }
-
- /**
- * @see android.webkit.WebSettings#setSupportZoom(boolean)
- */
- @Override
- public void setSupportZoom(boolean support) {
- mSupportZoom = support;
- mWebView.updateMultiTouchSupport(mContext);
- }
-
- /**
- * @see android.webkit.WebSettings#supportZoom()
- */
- @Override
- public boolean supportZoom() {
- return mSupportZoom;
- }
-
- /**
- * @see android.webkit.WebSettings#setMediaPlaybackRequiresUserGesture(boolean)
- */
- @Override
- public void setMediaPlaybackRequiresUserGesture(boolean support) {
- if (mMediaPlaybackRequiresUserGesture != support) {
- mMediaPlaybackRequiresUserGesture = support;
- postSync();
- }
- }
-
- /**
- * @see android.webkit.WebSettings#getMediaPlaybackRequiresUserGesture()
- */
- @Override
- public boolean getMediaPlaybackRequiresUserGesture() {
- return mMediaPlaybackRequiresUserGesture;
- }
-
- /**
- * @see android.webkit.WebSettings#setBuiltInZoomControls(boolean)
- */
- @Override
- public void setBuiltInZoomControls(boolean enabled) {
- mBuiltInZoomControls = enabled;
- mWebView.updateMultiTouchSupport(mContext);
- }
-
- /**
- * @see android.webkit.WebSettings#getBuiltInZoomControls()
- */
- @Override
- public boolean getBuiltInZoomControls() {
- return mBuiltInZoomControls;
- }
-
- /**
- * @see android.webkit.WebSettings#setDisplayZoomControls(boolean)
- */
- @Override
- public void setDisplayZoomControls(boolean enabled) {
- mDisplayZoomControls = enabled;
- mWebView.updateMultiTouchSupport(mContext);
- }
-
- /**
- * @see android.webkit.WebSettings#getDisplayZoomControls()
- */
- @Override
- public boolean getDisplayZoomControls() {
- return mDisplayZoomControls;
- }
-
- /**
- * @see android.webkit.WebSettings#setAllowFileAccess(boolean)
- */
- @Override
- public void setAllowFileAccess(boolean allow) {
- mAllowFileAccess = allow;
- }
-
- /**
- * @see android.webkit.WebSettings#getAllowFileAccess()
- */
- @Override
- public boolean getAllowFileAccess() {
- return mAllowFileAccess;
- }
-
- /**
- * @see android.webkit.WebSettings#setAllowContentAccess(boolean)
- */
- @Override
- public void setAllowContentAccess(boolean allow) {
- mAllowContentAccess = allow;
- }
-
- /**
- * @see android.webkit.WebSettings#getAllowContentAccess()
- */
- @Override
- public boolean getAllowContentAccess() {
- return mAllowContentAccess;
- }
-
- /**
- * @see android.webkit.WebSettings#setLoadWithOverviewMode(boolean)
- */
- @Override
- public void setLoadWithOverviewMode(boolean overview) {
- mLoadWithOverviewMode = overview;
- }
-
- /**
- * @see android.webkit.WebSettings#getLoadWithOverviewMode()
- */
- @Override
- public boolean getLoadWithOverviewMode() {
- return mLoadWithOverviewMode;
- }
-
- /**
- * @see android.webkit.WebSettings#setEnableSmoothTransition(boolean)
- */
- @Override
- public void setEnableSmoothTransition(boolean enable) {
- mEnableSmoothTransition = enable;
- }
-
- /**
- * @see android.webkit.WebSettings#enableSmoothTransition()
- */
- @Override
- public boolean enableSmoothTransition() {
- return mEnableSmoothTransition;
- }
-
- /**
- * @see android.webkit.WebSettings#setUseWebViewBackgroundForOverscrollBackground(boolean)
- */
- @Override
- @Deprecated
- public void setUseWebViewBackgroundForOverscrollBackground(boolean view) {
- mUseWebViewBackgroundForOverscroll = view;
- }
-
- /**
- * @see android.webkit.WebSettings#getUseWebViewBackgroundForOverscrollBackground()
- */
- @Override
- @Deprecated
- public boolean getUseWebViewBackgroundForOverscrollBackground() {
- return mUseWebViewBackgroundForOverscroll;
- }
-
- /**
- * @see android.webkit.WebSettings#setSaveFormData(boolean)
- */
- @Override
- public void setSaveFormData(boolean save) {
- mSaveFormData = save;
- }
-
- /**
- * @see android.webkit.WebSettings#getSaveFormData()
- */
- @Override
- public boolean getSaveFormData() {
- return mSaveFormData && !mPrivateBrowsingEnabled;
- }
-
- /**
- * @see android.webkit.WebSettings#setSavePassword(boolean)
- */
- @Override
- public void setSavePassword(boolean save) {
- mSavePassword = save;
- }
-
- /**
- * @see android.webkit.WebSettings#getSavePassword()
- */
- @Override
- public boolean getSavePassword() {
- return mSavePassword;
- }
-
- /**
- * @see android.webkit.WebSettings#setTextZoom(int)
- */
- @Override
- public synchronized void setTextZoom(int textZoom) {
- if (mTextSize != textZoom) {
- mTextSize = textZoom;
- postSync();
- }
- }
-
- /**
- * @see android.webkit.WebSettings#getTextZoom()
- */
- @Override
- public synchronized int getTextZoom() {
- return mTextSize;
- }
-
- /**
- * Set the double-tap zoom of the page in percent. Default is 100.
- * @param doubleTapZoom A percent value for increasing or decreasing the double-tap zoom.
- */
- public void setDoubleTapZoom(int doubleTapZoom) {
- if (mDoubleTapZoom != doubleTapZoom) {
- mDoubleTapZoom = doubleTapZoom;
- mWebView.updateDoubleTapZoom(doubleTapZoom);
- }
- }
-
- /**
- * Get the double-tap zoom of the page in percent.
- * @return A percent value describing the double-tap zoom.
- */
- public int getDoubleTapZoom() {
- return mDoubleTapZoom;
- }
-
- /**
- * @see android.webkit.WebSettings#setDefaultZoom(android.webkit.WebSettingsClassic.ZoomDensity)
- */
- @Override
- public void setDefaultZoom(ZoomDensity zoom) {
- if (mDefaultZoom != zoom) {
- mDefaultZoom = zoom;
- mWebView.adjustDefaultZoomDensity(zoom.value);
- }
- }
-
- /**
- * @see android.webkit.WebSettings#getDefaultZoom()
- */
- @Override
- public ZoomDensity getDefaultZoom() {
- return mDefaultZoom;
- }
-
- /**
- * @see android.webkit.WebSettings#setLightTouchEnabled(boolean)
- */
- @Override
- public void setLightTouchEnabled(boolean enabled) {
- mLightTouchEnabled = enabled;
- }
-
- /**
- * @see android.webkit.WebSettings#getLightTouchEnabled()
- */
- @Override
- public boolean getLightTouchEnabled() {
- return mLightTouchEnabled;
- }
-
- /**
- * @see android.webkit.WebSettings#setUseDoubleTree(boolean)
- */
- @Override
- @Deprecated
- public synchronized void setUseDoubleTree(boolean use) {
- return;
- }
-
- /**
- * @see android.webkit.WebSettings#getUseDoubleTree()
- */
- @Override
- @Deprecated
- public synchronized boolean getUseDoubleTree() {
- return false;
- }
-
- /**
- * @see android.webkit.WebSettings#setUserAgent(int)
- */
- @Override
- @Deprecated
- public synchronized void setUserAgent(int ua) {
- String uaString = null;
- if (ua == 1) {
- if (DESKTOP_USERAGENT.equals(mUserAgent)) {
- return; // do nothing
- } else {
- uaString = DESKTOP_USERAGENT;
- }
- } else if (ua == 2) {
- if (IPHONE_USERAGENT.equals(mUserAgent)) {
- return; // do nothing
- } else {
- uaString = IPHONE_USERAGENT;
- }
- } else if (ua != 0) {
- return; // do nothing
- }
- setUserAgentString(uaString);
- }
-
- /**
- * @see android.webkit.WebSettings#getUserAgent()
- */
- @Override
- @Deprecated
- public synchronized int getUserAgent() {
- if (DESKTOP_USERAGENT.equals(mUserAgent)) {
- return 1;
- } else if (IPHONE_USERAGENT.equals(mUserAgent)) {
- return 2;
- } else if (mUseDefaultUserAgent) {
- return 0;
- }
- return -1;
- }
-
- /**
- * @see android.webkit.WebSettings#setUseWideViewPort(boolean)
- */
- @Override
- public synchronized void setUseWideViewPort(boolean use) {
- if (mUseWideViewport != use) {
- mUseWideViewport = use;
- postSync();
- }
- }
-
- /**
- * @see android.webkit.WebSettings#getUseWideViewPort()
- */
- @Override
- public synchronized boolean getUseWideViewPort() {
- return mUseWideViewport;
- }
-
- /**
- * @see android.webkit.WebSettings#setSupportMultipleWindows(boolean)
- */
- @Override
- public synchronized void setSupportMultipleWindows(boolean support) {
- if (mSupportMultipleWindows != support) {
- mSupportMultipleWindows = support;
- postSync();
- }
- }
-
- /**
- * @see android.webkit.WebSettings#supportMultipleWindows()
- */
- @Override
- public synchronized boolean supportMultipleWindows() {
- return mSupportMultipleWindows;
- }
-
- /**
- * @see android.webkit.WebSettings#setLayoutAlgorithm(android.webkit.WebSettingsClassic.LayoutAlgorithm)
- */
- @Override
- public synchronized void setLayoutAlgorithm(LayoutAlgorithm l) {
- if (l == LayoutAlgorithm.TEXT_AUTOSIZING) {
- throw new IllegalArgumentException(
- "WebViewClassic does not support TEXT_AUTOSIZING layout mode");
- }
- // XXX: This will only be affective if libwebcore was built with
- // ANDROID_LAYOUT defined.
- if (mLayoutAlgorithm != l) {
- mLayoutAlgorithm = l;
- postSync();
- }
- }
-
- /**
- * @see android.webkit.WebSettings#getLayoutAlgorithm()
- */
- @Override
- public synchronized LayoutAlgorithm getLayoutAlgorithm() {
- return mLayoutAlgorithm;
- }
-
- /**
- * @see android.webkit.WebSettings#setStandardFontFamily(java.lang.String)
- */
- @Override
- public synchronized void setStandardFontFamily(String font) {
- if (font != null && !font.equals(mStandardFontFamily)) {
- mStandardFontFamily = font;
- postSync();
- }
- }
-
- /**
- * @see android.webkit.WebSettings#getStandardFontFamily()
- */
- @Override
- public synchronized String getStandardFontFamily() {
- return mStandardFontFamily;
- }
-
- /**
- * @see android.webkit.WebSettings#setFixedFontFamily(java.lang.String)
- */
- @Override
- public synchronized void setFixedFontFamily(String font) {
- if (font != null && !font.equals(mFixedFontFamily)) {
- mFixedFontFamily = font;
- postSync();
- }
- }
-
- /**
- * @see android.webkit.WebSettings#getFixedFontFamily()
- */
- @Override
- public synchronized String getFixedFontFamily() {
- return mFixedFontFamily;
- }
-
- /**
- * @see android.webkit.WebSettings#setSansSerifFontFamily(java.lang.String)
- */
- @Override
- public synchronized void setSansSerifFontFamily(String font) {
- if (font != null && !font.equals(mSansSerifFontFamily)) {
- mSansSerifFontFamily = font;
- postSync();
- }
- }
-
- /**
- * @see android.webkit.WebSettings#getSansSerifFontFamily()
- */
- @Override
- public synchronized String getSansSerifFontFamily() {
- return mSansSerifFontFamily;
- }
-
- /**
- * @see android.webkit.WebSettings#setSerifFontFamily(java.lang.String)
- */
- @Override
- public synchronized void setSerifFontFamily(String font) {
- if (font != null && !font.equals(mSerifFontFamily)) {
- mSerifFontFamily = font;
- postSync();
- }
- }
-
- /**
- * @see android.webkit.WebSettings#getSerifFontFamily()
- */
- @Override
- public synchronized String getSerifFontFamily() {
- return mSerifFontFamily;
- }
-
- /**
- * @see android.webkit.WebSettings#setCursiveFontFamily(java.lang.String)
- */
- @Override
- public synchronized void setCursiveFontFamily(String font) {
- if (font != null && !font.equals(mCursiveFontFamily)) {
- mCursiveFontFamily = font;
- postSync();
- }
- }
-
- /**
- * @see android.webkit.WebSettings#getCursiveFontFamily()
- */
- @Override
- public synchronized String getCursiveFontFamily() {
- return mCursiveFontFamily;
- }
-
- /**
- * @see android.webkit.WebSettings#setFantasyFontFamily(java.lang.String)
- */
- @Override
- public synchronized void setFantasyFontFamily(String font) {
- if (font != null && !font.equals(mFantasyFontFamily)) {
- mFantasyFontFamily = font;
- postSync();
- }
- }
-
- /**
- * @see android.webkit.WebSettings#getFantasyFontFamily()
- */
- @Override
- public synchronized String getFantasyFontFamily() {
- return mFantasyFontFamily;
- }
-
- /**
- * @see android.webkit.WebSettings#setMinimumFontSize(int)
- */
- @Override
- public synchronized void setMinimumFontSize(int size) {
- size = pin(size);
- if (mMinimumFontSize != size) {
- mMinimumFontSize = size;
- postSync();
- }
- }
-
- /**
- * @see android.webkit.WebSettings#getMinimumFontSize()
- */
- @Override
- public synchronized int getMinimumFontSize() {
- return mMinimumFontSize;
- }
-
- /**
- * @see android.webkit.WebSettings#setMinimumLogicalFontSize(int)
- */
- @Override
- public synchronized void setMinimumLogicalFontSize(int size) {
- size = pin(size);
- if (mMinimumLogicalFontSize != size) {
- mMinimumLogicalFontSize = size;
- postSync();
- }
- }
-
- /**
- * @see android.webkit.WebSettings#getMinimumLogicalFontSize()
- */
- @Override
- public synchronized int getMinimumLogicalFontSize() {
- return mMinimumLogicalFontSize;
- }
-
- /**
- * @see android.webkit.WebSettings#setDefaultFontSize(int)
- */
- @Override
- public synchronized void setDefaultFontSize(int size) {
- size = pin(size);
- if (mDefaultFontSize != size) {
- mDefaultFontSize = size;
- postSync();
- }
- }
-
- /**
- * @see android.webkit.WebSettings#getDefaultFontSize()
- */
- @Override
- public synchronized int getDefaultFontSize() {
- return mDefaultFontSize;
- }
-
- /**
- * @see android.webkit.WebSettings#setDefaultFixedFontSize(int)
- */
- @Override
- public synchronized void setDefaultFixedFontSize(int size) {
- size = pin(size);
- if (mDefaultFixedFontSize != size) {
- mDefaultFixedFontSize = size;
- postSync();
- }
- }
-
- /**
- * @see android.webkit.WebSettings#getDefaultFixedFontSize()
- */
- @Override
- public synchronized int getDefaultFixedFontSize() {
- return mDefaultFixedFontSize;
- }
-
- /**
- * Set the number of pages cached by the WebKit for the history navigation.
- * @param size A non-negative integer between 0 (no cache) and 20 (max).
- */
- public synchronized void setPageCacheCapacity(int size) {
- if (size < 0) size = 0;
- if (size > 20) size = 20;
- if (mPageCacheCapacity != size) {
- mPageCacheCapacity = size;
- postSync();
- }
- }
-
- /**
- * @see android.webkit.WebSettings#setLoadsImagesAutomatically(boolean)
- */
- @Override
- public synchronized void setLoadsImagesAutomatically(boolean flag) {
- if (mLoadsImagesAutomatically != flag) {
- mLoadsImagesAutomatically = flag;
- postSync();
- }
- }
-
- /**
- * @see android.webkit.WebSettings#getLoadsImagesAutomatically()
- */
- @Override
- public synchronized boolean getLoadsImagesAutomatically() {
- return mLoadsImagesAutomatically;
- }
-
- /**
- * @see android.webkit.WebSettings#setBlockNetworkImage(boolean)
- */
- @Override
- public synchronized void setBlockNetworkImage(boolean flag) {
- if (mBlockNetworkImage != flag) {
- mBlockNetworkImage = flag;
- postSync();
- }
- }
-
- /**
- * @see android.webkit.WebSettings#getBlockNetworkImage()
- */
- @Override
- public synchronized boolean getBlockNetworkImage() {
- return mBlockNetworkImage;
- }
-
- /**
- * @see android.webkit.WebSettings#setBlockNetworkLoads(boolean)
- */
- @Override
- public synchronized void setBlockNetworkLoads(boolean flag) {
- if (mBlockNetworkLoads != flag) {
- mBlockNetworkLoads = flag;
- verifyNetworkAccess();
- postSync();
- }
- }
-
- /**
- * @see android.webkit.WebSettings#getBlockNetworkLoads()
- */
- @Override
- public synchronized boolean getBlockNetworkLoads() {
- return mBlockNetworkLoads;
- }
-
-
- private void verifyNetworkAccess() {
- if (!mBlockNetworkLoads) {
- if (mContext.checkPermission("android.permission.INTERNET",
- android.os.Process.myPid(), android.os.Process.myUid()) !=
- PackageManager.PERMISSION_GRANTED) {
- throw new SecurityException
- ("Permission denied - " +
- "application missing INTERNET permission");
- }
- }
- }
-
- /**
- * @see android.webkit.WebSettings#setJavaScriptEnabled(boolean)
- */
- @Override
- public synchronized void setJavaScriptEnabled(boolean flag) {
- if (mJavaScriptEnabled != flag) {
- mJavaScriptEnabled = flag;
- postSync();
- mWebView.updateJavaScriptEnabled(flag);
- }
- }
-
- /**
- * @see android.webkit.WebSettings#setAllowUniversalAccessFromFileURLs
- */
- @Override
- public synchronized void setAllowUniversalAccessFromFileURLs(boolean flag) {
- if (mAllowUniversalAccessFromFileURLs != flag) {
- mAllowUniversalAccessFromFileURLs = flag;
- postSync();
- }
- }
-
- /**
- * @see android.webkit.WebSettings#setAllowFileAccessFromFileURLs
- */
- @Override
- public synchronized void setAllowFileAccessFromFileURLs(boolean flag) {
- if (mAllowFileAccessFromFileURLs != flag) {
- mAllowFileAccessFromFileURLs = flag;
- postSync();
- }
- }
-
- /**
- * Tell the WebView to use Skia's hardware accelerated rendering path
- * @param flag True if the WebView should use Skia's hw-accel path
- */
- public synchronized void setHardwareAccelSkiaEnabled(boolean flag) {
- if (mHardwareAccelSkia != flag) {
- mHardwareAccelSkia = flag;
- postSync();
- }
- }
-
- /**
- * @return True if the WebView is using hardware accelerated skia
- */
- public synchronized boolean getHardwareAccelSkiaEnabled() {
- return mHardwareAccelSkia;
- }
-
- /**
- * Tell the WebView to show the visual indicator
- * @param flag True if the WebView should show the visual indicator
- */
- public synchronized void setShowVisualIndicator(boolean flag) {
- if (mShowVisualIndicator != flag) {
- mShowVisualIndicator = flag;
- postSync();
- }
- }
-
- /**
- * @return True if the WebView is showing the visual indicator
- */
- public synchronized boolean getShowVisualIndicator() {
- return mShowVisualIndicator;
- }
-
- /**
- * @see android.webkit.WebSettings#setPluginsEnabled(boolean)
- */
- @Override
- @Deprecated
- public synchronized void setPluginsEnabled(boolean flag) {
- setPluginState(flag ? PluginState.ON : PluginState.OFF);
- }
-
- /**
- * @see android.webkit.WebSettings#setPluginState(android.webkit.WebSettingsClassic.PluginState)
- */
- @Override
- public synchronized void setPluginState(PluginState state) {
- if (mPluginState != state) {
- mPluginState = state;
- postSync();
- }
- }
-
- /**
- * @see android.webkit.WebSettings#setPluginsPath(java.lang.String)
- */
- @Override
- @Deprecated
- public synchronized void setPluginsPath(String pluginsPath) {
- }
-
- /**
- * @see android.webkit.WebSettings#setDatabasePath(java.lang.String)
- */
- @Override
- public synchronized void setDatabasePath(String databasePath) {
- if (databasePath != null && !mDatabasePathHasBeenSet) {
- mDatabasePath = databasePath;
- mDatabasePathHasBeenSet = true;
- postSync();
- }
- }
-
- /**
- * @see android.webkit.WebSettings#setGeolocationDatabasePath(java.lang.String)
- */
- @Override
- public synchronized void setGeolocationDatabasePath(String databasePath) {
- if (databasePath != null
- && !databasePath.equals(mGeolocationDatabasePath)) {
- mGeolocationDatabasePath = databasePath;
- postSync();
- }
- }
-
- /**
- * @see android.webkit.WebSettings#setAppCacheEnabled(boolean)
- */
- @Override
- public synchronized void setAppCacheEnabled(boolean flag) {
- if (mAppCacheEnabled != flag) {
- mAppCacheEnabled = flag;
- postSync();
- }
- }
-
- /**
- * @see android.webkit.WebSettings#setAppCachePath(java.lang.String)
- */
- @Override
- public synchronized void setAppCachePath(String path) {
- // We test for a valid path and for repeated setting on the native
- // side, but we can avoid syncing in some simple cases.
- if (mAppCachePath == null && path != null && !path.isEmpty()) {
- mAppCachePath = path;
- postSync();
- }
- }
-
- /**
- * @see android.webkit.WebSettings#setAppCacheMaxSize(long)
- */
- @Override
- public synchronized void setAppCacheMaxSize(long appCacheMaxSize) {
- if (appCacheMaxSize != mAppCacheMaxSize) {
- mAppCacheMaxSize = appCacheMaxSize;
- postSync();
- }
- }
-
- /**
- * @see android.webkit.WebSettings#setDatabaseEnabled(boolean)
- */
- @Override
- public synchronized void setDatabaseEnabled(boolean flag) {
- if (mDatabaseEnabled != flag) {
- mDatabaseEnabled = flag;
- postSync();
- }
- }
-
- /**
- * @see android.webkit.WebSettings#setDomStorageEnabled(boolean)
- */
- @Override
- public synchronized void setDomStorageEnabled(boolean flag) {
- if (mDomStorageEnabled != flag) {
- mDomStorageEnabled = flag;
- postSync();
- }
- }
-
- /**
- * @see android.webkit.WebSettings#getDomStorageEnabled()
- */
- @Override
- public synchronized boolean getDomStorageEnabled() {
- return mDomStorageEnabled;
- }
-
- /**
- * @see android.webkit.WebSettings#getDatabasePath()
- */
- @Override
- public synchronized String getDatabasePath() {
- return mDatabasePath;
- }
-
- /**
- * @see android.webkit.WebSettings#getDatabaseEnabled()
- */
- @Override
- public synchronized boolean getDatabaseEnabled() {
- return mDatabaseEnabled;
- }
-
- /**
- * Tell the WebView to enable WebWorkers API.
- * @param flag True if the WebView should enable WebWorkers.
- * Note that this flag only affects V8. JSC does not have
- * an equivalent setting.
- */
- public synchronized void setWorkersEnabled(boolean flag) {
- if (mWorkersEnabled != flag) {
- mWorkersEnabled = flag;
- postSync();
- }
- }
-
- /**
- * @see android.webkit.WebSettings#setGeolocationEnabled(boolean)
- */
- @Override
- public synchronized void setGeolocationEnabled(boolean flag) {
- if (mGeolocationEnabled != flag) {
- mGeolocationEnabled = flag;
- postSync();
- }
- }
-
- /**
- * Sets whether XSS Auditor is enabled.
- * Only used by LayoutTestController.
- * @param flag Whether XSS Auditor should be enabled.
- */
- public synchronized void setXSSAuditorEnabled(boolean flag) {
- if (mXSSAuditorEnabled != flag) {
- mXSSAuditorEnabled = flag;
- postSync();
- }
- }
-
- /**
- * Enables/disables HTML5 link "prefetch" parameter.
- */
- public synchronized void setLinkPrefetchEnabled(boolean flag) {
- if (mLinkPrefetchEnabled != flag) {
- mLinkPrefetchEnabled = flag;
- postSync();
- }
- }
-
- /**
- * @see android.webkit.WebSettings#getJavaScriptEnabled()
- */
- @Override
- public synchronized boolean getJavaScriptEnabled() {
- return mJavaScriptEnabled;
- }
-
- /**
- * @see android.webkit.WebSettings#getAllowUniversalFileAccessFromFileURLs
- */
- @Override
- public synchronized boolean getAllowUniversalAccessFromFileURLs() {
- return mAllowUniversalAccessFromFileURLs;
- }
-
- /**
- * @see android.webkit.WebSettings#getAllowFileAccessFromFileURLs
- */
- @Override
- public synchronized boolean getAllowFileAccessFromFileURLs() {
- return mAllowFileAccessFromFileURLs;
- }
-
- /**
- * @see android.webkit.WebSettings#getPluginsEnabled()
- */
- @Override
- @Deprecated
- public synchronized boolean getPluginsEnabled() {
- return mPluginState == PluginState.ON;
- }
-
- /**
- * @see android.webkit.WebSettings#getPluginState()
- */
- @Override
- public synchronized PluginState getPluginState() {
- return mPluginState;
- }
-
- /**
- * @see android.webkit.WebSettings#getPluginsPath()
- */
- @Override
- @Deprecated
- public synchronized String getPluginsPath() {
- return "";
- }
-
- /**
- * @see android.webkit.WebSettings#setJavaScriptCanOpenWindowsAutomatically(boolean)
- */
- @Override
- public synchronized void setJavaScriptCanOpenWindowsAutomatically(
- boolean flag) {
- if (mJavaScriptCanOpenWindowsAutomatically != flag) {
- mJavaScriptCanOpenWindowsAutomatically = flag;
- postSync();
- }
- }
-
- /**
- * @see android.webkit.WebSettings#getJavaScriptCanOpenWindowsAutomatically()
- */
- @Override
- public synchronized boolean getJavaScriptCanOpenWindowsAutomatically() {
- return mJavaScriptCanOpenWindowsAutomatically;
- }
-
- /**
- * @see android.webkit.WebSettings#setDefaultTextEncodingName(java.lang.String)
- */
- @Override
- public synchronized void setDefaultTextEncodingName(String encoding) {
- if (encoding != null && !encoding.equals(mDefaultTextEncoding)) {
- mDefaultTextEncoding = encoding;
- postSync();
- }
- }
-
- /**
- * @see android.webkit.WebSettings#getDefaultTextEncodingName()
- */
- @Override
- public synchronized String getDefaultTextEncodingName() {
- return mDefaultTextEncoding;
- }
-
- /**
- * @see android.webkit.WebSettings#setUserAgentString(java.lang.String)
- */
- @Override
- public synchronized void setUserAgentString(String ua) {
- if (ua == null || ua.length() == 0) {
- synchronized(sLockForLocaleSettings) {
- Locale currentLocale = Locale.getDefault();
- if (!sLocale.equals(currentLocale)) {
- sLocale = currentLocale;
- mAcceptLanguage = getCurrentAcceptLanguage();
- }
- }
- ua = getCurrentUserAgent();
- mUseDefaultUserAgent = true;
- } else {
- mUseDefaultUserAgent = false;
- }
-
- if (!ua.equals(mUserAgent)) {
- mUserAgent = ua;
- postSync();
- }
- }
-
- /**
- * @see android.webkit.WebSettings#getUserAgentString()
- */
- @Override
- public synchronized String getUserAgentString() {
- if (DESKTOP_USERAGENT.equals(mUserAgent) ||
- IPHONE_USERAGENT.equals(mUserAgent) ||
- !mUseDefaultUserAgent) {
- return mUserAgent;
- }
-
- boolean doPostSync = false;
- synchronized(sLockForLocaleSettings) {
- Locale currentLocale = Locale.getDefault();
- if (!sLocale.equals(currentLocale)) {
- sLocale = currentLocale;
- mUserAgent = getCurrentUserAgent();
- mAcceptLanguage = getCurrentAcceptLanguage();
- doPostSync = true;
- }
- }
- if (doPostSync) {
- postSync();
- }
- return mUserAgent;
- }
-
- /* package api to grab the Accept Language string. */
- /*package*/ synchronized String getAcceptLanguage() {
- synchronized(sLockForLocaleSettings) {
- Locale currentLocale = Locale.getDefault();
- if (!sLocale.equals(currentLocale)) {
- sLocale = currentLocale;
- mAcceptLanguage = getCurrentAcceptLanguage();
- }
- }
- return mAcceptLanguage;
- }
-
- /* package */ boolean isNarrowColumnLayout() {
- return getLayoutAlgorithm() == LayoutAlgorithm.NARROW_COLUMNS;
- }
-
- /**
- * @see android.webkit.WebSettings#setNeedInitialFocus(boolean)
- */
- @Override
- public void setNeedInitialFocus(boolean flag) {
- if (mNeedInitialFocus != flag) {
- mNeedInitialFocus = flag;
- }
- }
-
- /* Package api to get the choice whether it needs to set initial focus. */
- /* package */ boolean getNeedInitialFocus() {
- return mNeedInitialFocus;
- }
-
- /**
- * @see android.webkit.WebSettings#setRenderPriority(android.webkit.WebSettingsClassic.RenderPriority)
- */
- @Override
- public synchronized void setRenderPriority(RenderPriority priority) {
- if (mRenderPriority != priority) {
- mRenderPriority = priority;
- mEventHandler.sendMessage(Message.obtain(null,
- EventHandler.PRIORITY));
- }
- }
-
- /**
- * @see android.webkit.WebSettings#setCacheMode(int)
- */
- @Override
- public void setCacheMode(int mode) {
- if (mode != mOverrideCacheMode) {
- mOverrideCacheMode = mode;
- postSync();
- }
- }
-
- /**
- * @see android.webkit.WebSettings#getCacheMode()
- */
- @Override
- public int getCacheMode() {
- return mOverrideCacheMode;
- }
-
- /**
- * If set, webkit alternately shrinks and expands images viewed outside
- * of an HTML page to fit the screen. This conflicts with attempts by
- * the UI to zoom in and out of an image, so it is set false by default.
- * @param shrink Set true to let webkit shrink the standalone image to fit.
- */
- public void setShrinksStandaloneImagesToFit(boolean shrink) {
- if (mShrinksStandaloneImagesToFit != shrink) {
- mShrinksStandaloneImagesToFit = shrink;
- postSync();
- }
- }
-
- /**
- * Specify the maximum decoded image size. The default is
- * 2 megs for small memory devices and 8 megs for large memory devices.
- * @param size The maximum decoded size, or zero to set to the default.
- */
- public void setMaximumDecodedImageSize(long size) {
- if (mMaximumDecodedImageSize != size) {
- mMaximumDecodedImageSize = size;
- postSync();
- }
- }
-
- /**
- * Returns whether to use fixed viewport. Use fixed viewport
- * whenever wide viewport is on.
- */
- /* package */ boolean getUseFixedViewport() {
- return getUseWideViewPort();
- }
-
- /**
- * Returns whether private browsing is enabled.
- */
- /* package */ boolean isPrivateBrowsingEnabled() {
- return mPrivateBrowsingEnabled;
- }
-
- /**
- * Sets whether private browsing is enabled.
- * @param flag Whether private browsing should be enabled.
- */
- /* package */ synchronized void setPrivateBrowsingEnabled(boolean flag) {
- if (mPrivateBrowsingEnabled != flag) {
- mPrivateBrowsingEnabled = flag;
-
- // AutoFill is dependant on private browsing being enabled so
- // reset it to take account of the new value of mPrivateBrowsingEnabled.
- setAutoFillEnabled(mAutoFillEnabled);
-
- postSync();
- }
- }
-
- /**
- * Returns whether the viewport metatag can disable zooming
- */
- public boolean forceUserScalable() {
- return mForceUserScalable;
- }
-
- /**
- * Sets whether viewport metatag can disable zooming.
- * @param flag Whether or not to forceably enable user scalable.
- */
- public synchronized void setForceUserScalable(boolean flag) {
- mForceUserScalable = flag;
- }
-
- synchronized void setSyntheticLinksEnabled(boolean flag) {
- if (mSyntheticLinksEnabled != flag) {
- mSyntheticLinksEnabled = flag;
- postSync();
- }
- }
-
- public synchronized void setAutoFillEnabled(boolean enabled) {
- // AutoFill is always disabled in private browsing mode.
- boolean autoFillEnabled = enabled && !mPrivateBrowsingEnabled;
- if (mAutoFillEnabled != autoFillEnabled) {
- mAutoFillEnabled = autoFillEnabled;
- postSync();
- }
- }
-
- public synchronized boolean getAutoFillEnabled() {
- return mAutoFillEnabled;
- }
-
- public synchronized void setAutoFillProfile(AutoFillProfile profile) {
- if (mAutoFillProfile != profile) {
- mAutoFillProfile = profile;
- postSync();
- }
- }
-
- public synchronized AutoFillProfile getAutoFillProfile() {
- return mAutoFillProfile;
- }
-
- int getDoubleTapToastCount() {
- return mDoubleTapToastCount;
- }
-
- void setDoubleTapToastCount(int count) {
- if (mDoubleTapToastCount != count) {
- mDoubleTapToastCount = count;
- // write the settings in the non-UI thread
- mEventHandler.sendMessage(Message.obtain(null,
- EventHandler.SET_DOUBLE_TAP_TOAST_COUNT));
- }
- }
-
- public void setProperty(String key, String value) {
- if (mWebView.nativeSetProperty(key, value)) {
- mWebView.invalidate();
- }
- }
-
- public String getProperty(String key) {
- return mWebView.nativeGetProperty(key);
- }
-
- /**
- * Transfer messages from the queue to the new WebCoreThread. Called from
- * WebCore thread.
- */
- /*package*/
- synchronized void syncSettingsAndCreateHandler(BrowserFrame frame) {
- mBrowserFrame = frame;
- if (DebugFlags.WEB_SETTINGS) {
- junit.framework.Assert.assertTrue(frame.mNativeFrame != 0);
- }
-
- SharedPreferences sp = mContext.getSharedPreferences(PREF_FILE,
- Context.MODE_PRIVATE);
- if (mDoubleTapToastCount > 0) {
- mDoubleTapToastCount = sp.getInt(DOUBLE_TAP_TOAST_COUNT,
- mDoubleTapToastCount);
- }
- nativeSync(frame.mNativeFrame);
- mSyncPending = false;
- mEventHandler.createHandler();
- }
-
- /**
- * Let the Settings object know that our owner is being destroyed.
- */
- /*package*/
- synchronized void onDestroyed() {
- }
-
- private int pin(int size) {
- // FIXME: 72 is just an arbitrary max text size value.
- if (size < 1) {
- return 1;
- } else if (size > 72) {
- return 72;
- }
- return size;
- }
-
- /* Post a SYNC message to handle syncing the native settings. */
- private synchronized void postSync() {
- // Only post if a sync is not pending
- if (!mSyncPending) {
- mSyncPending = mEventHandler.sendMessage(
- Message.obtain(null, EventHandler.SYNC));
- }
- }
-
- // Synchronize the native and java settings.
- private native void nativeSync(int nativeFrame);
-}
+++ /dev/null
-/*
- * Copyright (C) 2012 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.os.Handler;
-import android.os.Message;
-
-import java.util.Collection;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Set;
-
-/** @hide */
-public class WebStorageClassic extends WebStorage {
- // Global instance of a WebStorage
- private static WebStorageClassic sWebStorage;
-
- // Message ids
- static final int UPDATE = 0;
- static final int SET_QUOTA_ORIGIN = 1;
- static final int DELETE_ORIGIN = 2;
- static final int DELETE_ALL = 3;
- static final int GET_ORIGINS = 4;
- static final int GET_USAGE_ORIGIN = 5;
- static final int GET_QUOTA_ORIGIN = 6;
-
- // Message ids on the UI thread
- static final int RETURN_ORIGINS = 0;
- static final int RETURN_USAGE_ORIGIN = 1;
- static final int RETURN_QUOTA_ORIGIN = 2;
-
- private static final String ORIGINS = "origins";
- private static final String ORIGIN = "origin";
- private static final String CALLBACK = "callback";
- private static final String USAGE = "usage";
- private static final String QUOTA = "quota";
-
- private Map <String, Origin> mOrigins;
-
- private Handler mHandler = null;
- private Handler mUIHandler = null;
-
- /**
- * @hide
- * Message handler, UI side
- * @hide
- */
- public void createUIHandler() {
- if (mUIHandler == null) {
- mUIHandler = new Handler() {
- @Override
- public void handleMessage(Message msg) {
- switch (msg.what) {
- case RETURN_ORIGINS: {
- Map values = (Map) msg.obj;
- Map origins = (Map) values.get(ORIGINS);
- ValueCallback<Map> callback = (ValueCallback<Map>) values.get(CALLBACK);
- callback.onReceiveValue(origins);
- } break;
-
- case RETURN_USAGE_ORIGIN: {
- Map values = (Map) msg.obj;
- ValueCallback<Long> callback = (ValueCallback<Long>) values.get(CALLBACK);
- callback.onReceiveValue((Long)values.get(USAGE));
- } break;
-
- case RETURN_QUOTA_ORIGIN: {
- Map values = (Map) msg.obj;
- ValueCallback<Long> callback = (ValueCallback<Long>) values.get(CALLBACK);
- callback.onReceiveValue((Long)values.get(QUOTA));
- } break;
- }
- }
- };
- }
- }
-
- /**
- * Message handler, WebCore side
- * @hide
- */
- public synchronized void createHandler() {
- if (mHandler == null) {
- mHandler = new Handler() {
- @Override
- public void handleMessage(Message msg) {
- switch (msg.what) {
- case SET_QUOTA_ORIGIN: {
- Origin website = (Origin) msg.obj;
- nativeSetQuotaForOrigin(website.getOrigin(),
- website.getQuota());
- } break;
-
- case DELETE_ORIGIN: {
- Origin website = (Origin) msg.obj;
- nativeDeleteOrigin(website.getOrigin());
- } break;
-
- case DELETE_ALL:
- nativeDeleteAllData();
- break;
-
- case GET_ORIGINS: {
- syncValues();
- ValueCallback callback = (ValueCallback) msg.obj;
- Map origins = new HashMap(mOrigins);
- Map values = new HashMap<String, Object>();
- values.put(CALLBACK, callback);
- values.put(ORIGINS, origins);
- postUIMessage(Message.obtain(null, RETURN_ORIGINS, values));
- } break;
-
- case GET_USAGE_ORIGIN: {
- syncValues();
- Map values = (Map) msg.obj;
- String origin = (String) values.get(ORIGIN);
- ValueCallback callback = (ValueCallback) values.get(CALLBACK);
- Origin website = mOrigins.get(origin);
- Map retValues = new HashMap<String, Object>();
- retValues.put(CALLBACK, callback);
- if (website != null) {
- long usage = website.getUsage();
- retValues.put(USAGE, new Long(usage));
- }
- postUIMessage(Message.obtain(null, RETURN_USAGE_ORIGIN, retValues));
- } break;
-
- case GET_QUOTA_ORIGIN: {
- syncValues();
- Map values = (Map) msg.obj;
- String origin = (String) values.get(ORIGIN);
- ValueCallback callback = (ValueCallback) values.get(CALLBACK);
- Origin website = mOrigins.get(origin);
- Map retValues = new HashMap<String, Object>();
- retValues.put(CALLBACK, callback);
- if (website != null) {
- long quota = website.getQuota();
- retValues.put(QUOTA, new Long(quota));
- }
- postUIMessage(Message.obtain(null, RETURN_QUOTA_ORIGIN, retValues));
- } break;
-
- case UPDATE:
- syncValues();
- break;
- }
- }
- };
- }
- }
-
- /*
- * When calling getOrigins(), getUsageForOrigin() and getQuotaForOrigin(),
- * we need to get the values from WebCore, but we cannot block while doing so
- * as we used to do, as this could result in a full deadlock (other WebCore
- * messages received while we are still blocked here, see http://b/2127737).
- *
- * We have to do everything asynchronously, by providing a callback function.
- * We post a message on the WebCore thread (mHandler) that will get the result
- * from WebCore, and we post it back on the UI thread (using mUIHandler).
- * We can then use the callback function to return the value.
- */
-
- @Override
- public void getOrigins(ValueCallback<Map> callback) {
- if (callback != null) {
- if (WebViewCore.THREAD_NAME.equals(Thread.currentThread().getName())) {
- syncValues();
- callback.onReceiveValue(mOrigins);
- } else {
- postMessage(Message.obtain(null, GET_ORIGINS, callback));
- }
- }
- }
-
- /**
- * Returns a list of origins having a database
- * should only be called from WebViewCore.
- */
- Collection<Origin> getOriginsSync() {
- if (WebViewCore.THREAD_NAME.equals(Thread.currentThread().getName())) {
- update();
- return mOrigins.values();
- }
- return null;
- }
-
- @Override
- public void getUsageForOrigin(String origin, ValueCallback<Long> callback) {
- if (callback == null) {
- return;
- }
- if (origin == null) {
- callback.onReceiveValue(null);
- return;
- }
- if (WebViewCore.THREAD_NAME.equals(Thread.currentThread().getName())) {
- syncValues();
- Origin website = mOrigins.get(origin);
- callback.onReceiveValue(new Long(website.getUsage()));
- } else {
- HashMap values = new HashMap<String, Object>();
- values.put(ORIGIN, origin);
- values.put(CALLBACK, callback);
- postMessage(Message.obtain(null, GET_USAGE_ORIGIN, values));
- }
- }
-
- @Override
- public void getQuotaForOrigin(String origin, ValueCallback<Long> callback) {
- if (callback == null) {
- return;
- }
- if (origin == null) {
- callback.onReceiveValue(null);
- return;
- }
- if (WebViewCore.THREAD_NAME.equals(Thread.currentThread().getName())) {
- syncValues();
- Origin website = mOrigins.get(origin);
- callback.onReceiveValue(new Long(website.getUsage()));
- } else {
- HashMap values = new HashMap<String, Object>();
- values.put(ORIGIN, origin);
- values.put(CALLBACK, callback);
- postMessage(Message.obtain(null, GET_QUOTA_ORIGIN, values));
- }
- }
-
- @Override
- public void setQuotaForOrigin(String origin, long quota) {
- if (origin != null) {
- if (WebViewCore.THREAD_NAME.equals(Thread.currentThread().getName())) {
- nativeSetQuotaForOrigin(origin, quota);
- } else {
- postMessage(Message.obtain(null, SET_QUOTA_ORIGIN,
- new Origin(origin, quota)));
- }
- }
- }
-
- @Override
- public void deleteOrigin(String origin) {
- if (origin != null) {
- if (WebViewCore.THREAD_NAME.equals(Thread.currentThread().getName())) {
- nativeDeleteOrigin(origin);
- } else {
- postMessage(Message.obtain(null, DELETE_ORIGIN,
- new Origin(origin)));
- }
- }
- }
-
- @Override
- public void deleteAllData() {
- if (WebViewCore.THREAD_NAME.equals(Thread.currentThread().getName())) {
- nativeDeleteAllData();
- } else {
- postMessage(Message.obtain(null, DELETE_ALL));
- }
- }
-
- /**
- * Sets the maximum size of the ApplicationCache.
- * This should only ever be called on the WebKit thread.
- * Not part of the base-class API: this is only used by dump render tree.
- */
- public void setAppCacheMaximumSize(long size) {
- nativeSetAppCacheMaximumSize(size);
- }
-
- /**
- * Utility function to send a message to our handler
- */
- private synchronized void postMessage(Message msg) {
- if (mHandler != null) {
- mHandler.sendMessage(msg);
- }
- }
-
- /**
- * Utility function to send a message to the handler on the UI thread
- */
- private void postUIMessage(Message msg) {
- if (mUIHandler != null) {
- mUIHandler.sendMessage(msg);
- }
- }
-
- /**
- * Get the singleton instance of this class.
- * @return The singleton {@link WebStorage} instance.
- */
- public static WebStorageClassic getInstance() {
- if (sWebStorage == null) {
- sWebStorage = new WebStorageClassic();
- }
- return sWebStorage;
- }
-
- /**
- * @hide
- * Post a Sync request
- */
- public void update() {
- if (WebViewCore.THREAD_NAME.equals(Thread.currentThread().getName())) {
- syncValues();
- } else {
- postMessage(Message.obtain(null, UPDATE));
- }
- }
-
- /**
- * Run on the WebCore thread
- * set the local values with the current ones
- */
- private void syncValues() {
- Set<String> tmp = nativeGetOrigins();
- mOrigins = new HashMap<String, Origin>();
- for (String origin : tmp) {
- Origin website = new Origin(origin,
- nativeGetQuotaForOrigin(origin),
- nativeGetUsageForOrigin(origin));
- mOrigins.put(origin, website);
- }
- }
-
- WebStorageClassic() {}
-
- // Native functions
- private static native Set nativeGetOrigins();
- private static native long nativeGetUsageForOrigin(String origin);
- private static native long nativeGetQuotaForOrigin(String origin);
- private static native void nativeSetQuotaForOrigin(String origin, long quota);
- private static native void nativeDeleteOrigin(String origin);
- private static native void nativeDeleteAllData();
- private static native void nativeSetAppCacheMaximumSize(long size);
-}
+++ /dev/null
-/*
- * Copyright (C) 2007 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 java.net.MalformedURLException;
-import java.net.URL;
-
-// TODO: Move these to a better place.
-/* package */ abstract class WebTextView {
-
- private static final String LOGTAG = "WebTextView";
-
- // Types used with setType. Keep in sync with CachedInput.h
- static final int NORMAL_TEXT_FIELD = 0;
- static final int TEXT_AREA = 1;
- static final int PASSWORD = 2;
- static final int SEARCH = 3;
- static final int EMAIL = 4;
- static final int NUMBER = 5;
- static final int TELEPHONE = 6;
- static final int URL = 7;
-
- static final int FORM_NOT_AUTOFILLABLE = -1;
-
- static String urlForAutoCompleteData(String urlString) {
- // Remove any fragment or query string.
- URL url = null;
- try {
- url = new URL(urlString);
- } catch (MalformedURLException e) {
- Log.e(LOGTAG, "Unable to parse URL "+url);
- }
-
- return url != null ? url.getProtocol() + "://" + url.getHost() + url.getPath() : null;
- }
-
-}
+++ /dev/null
-/*
- * Copyright (C) 2012 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.accessibilityservice.AccessibilityServiceInfo;
-import android.animation.ObjectAnimator;
-import android.annotation.Widget;
-import android.app.ActivityManager;
-import android.app.AlertDialog;
-import android.content.BroadcastReceiver;
-import android.content.ClipData;
-import android.content.ClipboardManager;
-import android.content.ComponentCallbacks2;
-import android.content.Context;
-import android.content.DialogInterface;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.pm.PackageManager;
-import android.content.res.Configuration;
-import android.database.DataSetObserver;
-import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
-import android.graphics.BitmapShader;
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.ColorFilter;
-import android.graphics.DrawFilter;
-import android.graphics.Paint;
-import android.graphics.PaintFlagsDrawFilter;
-import android.graphics.Picture;
-import android.graphics.Point;
-import android.graphics.PointF;
-import android.graphics.Rect;
-import android.graphics.RectF;
-import android.graphics.Region;
-import android.graphics.RegionIterator;
-import android.graphics.Shader;
-import android.graphics.drawable.Drawable;
-import android.net.Proxy;
-import android.net.ProxyProperties;
-import android.net.Uri;
-import android.net.http.SslCertificate;
-import android.os.AsyncTask;
-import android.os.Build;
-import android.os.Bundle;
-import android.os.CancellationSignal;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.Message;
-import android.os.SystemClock;
-import android.print.PrintDocumentAdapter;
-import android.security.KeyChain;
-import android.text.Editable;
-import android.text.InputType;
-import android.text.Selection;
-import android.text.TextUtils;
-import android.util.AndroidRuntimeException;
-import android.util.DisplayMetrics;
-import android.util.EventLog;
-import android.util.Log;
-import android.view.Gravity;
-import android.view.HapticFeedbackConstants;
-import android.view.HardwareCanvas;
-import android.view.InputDevice;
-import android.view.KeyCharacterMap;
-import android.view.KeyEvent;
-import android.view.LayoutInflater;
-import android.view.MotionEvent;
-import android.view.ScaleGestureDetector;
-import android.view.SoundEffectConstants;
-import android.view.VelocityTracker;
-import android.view.View;
-import android.view.View.MeasureSpec;
-import android.view.ViewConfiguration;
-import android.view.ViewGroup;
-import android.view.ViewParent;
-import android.view.ViewRootImpl;
-import android.view.accessibility.AccessibilityEvent;
-import android.view.accessibility.AccessibilityManager;
-import android.view.accessibility.AccessibilityNodeInfo;
-import android.view.accessibility.AccessibilityNodeProvider;
-import android.view.inputmethod.BaseInputConnection;
-import android.view.inputmethod.EditorInfo;
-import android.view.inputmethod.InputConnection;
-import android.view.inputmethod.InputMethodManager;
-import android.webkit.WebView.HitTestResult;
-import android.webkit.WebView.PictureListener;
-import android.webkit.WebViewCore.DrawData;
-import android.webkit.WebViewCore.EventHub;
-import android.webkit.WebViewCore.TextFieldInitData;
-import android.webkit.WebViewCore.TextSelectionData;
-import android.webkit.WebViewCore.WebKitHitTest;
-import android.widget.AbsoluteLayout;
-import android.widget.Adapter;
-import android.widget.AdapterView;
-import android.widget.AdapterView.OnItemClickListener;
-import android.widget.ArrayAdapter;
-import android.widget.CheckedTextView;
-import android.widget.LinearLayout;
-import android.widget.ListView;
-import android.widget.OverScroller;
-import android.widget.PopupWindow;
-import android.widget.Scroller;
-import android.widget.TextView;
-import android.widget.Toast;
-
-import junit.framework.Assert;
-
-import java.io.BufferedWriter;
-import java.io.ByteArrayOutputStream;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.net.URLDecoder;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Locale;
-import java.util.Map;
-import java.util.Set;
-import java.util.Vector;
-
-/**
- * Implements a backend provider for the {@link WebView} public API.
- * @hide
- */
-// TODO: Check if any WebView published API methods are called from within here, and if so
-// we should bounce the call out via the proxy to enable any sub-class to override it.
-@Widget
-@SuppressWarnings("deprecation")
-public final class WebViewClassic implements WebViewProvider, WebViewProvider.ScrollDelegate,
- WebViewProvider.ViewDelegate {
- /**
- * InputConnection used for ContentEditable. This captures changes
- * to the text and sends them either as key strokes or text changes.
- */
- class WebViewInputConnection extends BaseInputConnection {
- // Used for mapping characters to keys typed.
- private KeyCharacterMap mKeyCharacterMap;
- private boolean mIsKeySentByMe;
- private int mInputType;
- private int mImeOptions;
- private String mHint;
- private int mMaxLength;
- private boolean mIsAutoFillable;
- private boolean mIsAutoCompleteEnabled;
- private String mName;
- private int mBatchLevel;
-
- public WebViewInputConnection() {
- super(mWebView, true);
- }
-
- public void setAutoFillable(int queryId) {
- mIsAutoFillable = getSettings().getAutoFillEnabled()
- && (queryId != WebTextView.FORM_NOT_AUTOFILLABLE);
- int variation = mInputType & EditorInfo.TYPE_MASK_VARIATION;
- if (variation != EditorInfo.TYPE_TEXT_VARIATION_WEB_PASSWORD
- && (mIsAutoFillable || mIsAutoCompleteEnabled)) {
- if (mName != null && mName.length() > 0) {
- requestFormData(mName, mFieldPointer, mIsAutoFillable,
- mIsAutoCompleteEnabled);
- }
- }
- }
-
- @Override
- public boolean beginBatchEdit() {
- if (mBatchLevel == 0) {
- beginTextBatch();
- }
- mBatchLevel++;
- return false;
- }
-
- @Override
- public boolean endBatchEdit() {
- mBatchLevel--;
- if (mBatchLevel == 0) {
- commitTextBatch();
- }
- return false;
- }
-
- public boolean getIsAutoFillable() {
- return mIsAutoFillable;
- }
-
- @Override
- public boolean sendKeyEvent(KeyEvent event) {
- // Some IMEs send key events directly using sendKeyEvents.
- // WebViewInputConnection should treat these as text changes.
- if (!mIsKeySentByMe) {
- if (event.getAction() == KeyEvent.ACTION_UP) {
- if (event.getKeyCode() == KeyEvent.KEYCODE_DEL) {
- return deleteSurroundingText(1, 0);
- } else if (event.getKeyCode() == KeyEvent.KEYCODE_FORWARD_DEL) {
- return deleteSurroundingText(0, 1);
- } else if (event.getUnicodeChar() != 0){
- String newComposingText =
- Character.toString((char)event.getUnicodeChar());
- return commitText(newComposingText, 1);
- }
- } else if (event.getAction() == KeyEvent.ACTION_DOWN &&
- (event.getKeyCode() == KeyEvent.KEYCODE_DEL
- || event.getKeyCode() == KeyEvent.KEYCODE_FORWARD_DEL
- || event.getUnicodeChar() != 0)) {
- return true; // only act on action_down
- }
- }
- return super.sendKeyEvent(event);
- }
-
- public void setTextAndKeepSelection(CharSequence text) {
- Editable editable = getEditable();
- int selectionStart = Selection.getSelectionStart(editable);
- int selectionEnd = Selection.getSelectionEnd(editable);
- text = limitReplaceTextByMaxLength(text, editable.length());
- editable.replace(0, editable.length(), text);
- restartInput();
- // Keep the previous selection.
- selectionStart = Math.min(selectionStart, editable.length());
- selectionEnd = Math.min(selectionEnd, editable.length());
- setSelection(selectionStart, selectionEnd);
- finishComposingText();
- }
-
- public void replaceSelection(CharSequence text) {
- Editable editable = getEditable();
- int selectionStart = Selection.getSelectionStart(editable);
- int selectionEnd = Selection.getSelectionEnd(editable);
- text = limitReplaceTextByMaxLength(text, selectionEnd - selectionStart);
- setNewText(selectionStart, selectionEnd, text);
- editable.replace(selectionStart, selectionEnd, text);
- restartInput();
- // Move caret to the end of the new text
- int newCaret = selectionStart + text.length();
- setSelection(newCaret, newCaret);
- }
-
- @Override
- public boolean setComposingText(CharSequence text, int newCursorPosition) {
- Editable editable = getEditable();
- int start = getComposingSpanStart(editable);
- int end = getComposingSpanEnd(editable);
- if (start < 0 || end < 0) {
- start = Selection.getSelectionStart(editable);
- end = Selection.getSelectionEnd(editable);
- }
- if (end < start) {
- int temp = end;
- end = start;
- start = temp;
- }
- CharSequence limitedText = limitReplaceTextByMaxLength(text, end - start);
- setNewText(start, end, limitedText);
- if (limitedText != text) {
- newCursorPosition -= text.length() - limitedText.length();
- }
- super.setComposingText(limitedText, newCursorPosition);
- updateSelection();
- if (limitedText != text) {
- int lastCaret = start + limitedText.length();
- finishComposingText();
- setSelection(lastCaret, lastCaret);
- }
- return true;
- }
-
- @Override
- public boolean commitText(CharSequence text, int newCursorPosition) {
- setComposingText(text, newCursorPosition);
- finishComposingText();
- return true;
- }
-
- @Override
- public boolean deleteSurroundingText(int leftLength, int rightLength) {
- // This code is from BaseInputConnection#deleteSurroundText.
- // We have to delete the same text in webkit.
- Editable content = getEditable();
- int a = Selection.getSelectionStart(content);
- int b = Selection.getSelectionEnd(content);
-
- if (a > b) {
- int tmp = a;
- a = b;
- b = tmp;
- }
-
- int ca = getComposingSpanStart(content);
- int cb = getComposingSpanEnd(content);
- if (cb < ca) {
- int tmp = ca;
- ca = cb;
- cb = tmp;
- }
- if (ca != -1 && cb != -1) {
- if (ca < a) a = ca;
- if (cb > b) b = cb;
- }
-
- int endDelete = Math.min(content.length(), b + rightLength);
- if (endDelete > b) {
- setNewText(b, endDelete, "");
- }
- int startDelete = Math.max(0, a - leftLength);
- if (startDelete < a) {
- setNewText(startDelete, a, "");
- }
- return super.deleteSurroundingText(leftLength, rightLength);
- }
-
- @Override
- public boolean performEditorAction(int editorAction) {
-
- boolean handled = true;
- switch (editorAction) {
- case EditorInfo.IME_ACTION_NEXT:
- mWebView.requestFocus(View.FOCUS_FORWARD);
- break;
- case EditorInfo.IME_ACTION_PREVIOUS:
- mWebView.requestFocus(View.FOCUS_BACKWARD);
- break;
- case EditorInfo.IME_ACTION_DONE:
- WebViewClassic.this.hideSoftKeyboard();
- break;
- case EditorInfo.IME_ACTION_GO:
- case EditorInfo.IME_ACTION_SEARCH:
- WebViewClassic.this.hideSoftKeyboard();
- String text = getEditable().toString();
- passToJavaScript(text, new KeyEvent(KeyEvent.ACTION_DOWN,
- KeyEvent.KEYCODE_ENTER));
- passToJavaScript(text, new KeyEvent(KeyEvent.ACTION_UP,
- KeyEvent.KEYCODE_ENTER));
- break;
-
- default:
- handled = super.performEditorAction(editorAction);
- break;
- }
-
- return handled;
- }
-
- public void initEditorInfo(WebViewCore.TextFieldInitData initData) {
- int type = initData.mType;
- int inputType = InputType.TYPE_CLASS_TEXT
- | InputType.TYPE_TEXT_VARIATION_WEB_EDIT_TEXT;
- int imeOptions = EditorInfo.IME_FLAG_NO_EXTRACT_UI
- | EditorInfo.IME_FLAG_NO_FULLSCREEN;
- if (!initData.mIsSpellCheckEnabled) {
- inputType |= InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS;
- }
- if (WebTextView.TEXT_AREA != type) {
- if (initData.mIsTextFieldNext) {
- imeOptions |= EditorInfo.IME_FLAG_NAVIGATE_NEXT;
- }
- if (initData.mIsTextFieldPrev) {
- imeOptions |= EditorInfo.IME_FLAG_NAVIGATE_PREVIOUS;
- }
- }
- int action = EditorInfo.IME_ACTION_GO;
- switch (type) {
- case WebTextView.NORMAL_TEXT_FIELD:
- break;
- case WebTextView.TEXT_AREA:
- inputType |= InputType.TYPE_TEXT_FLAG_MULTI_LINE
- | InputType.TYPE_TEXT_FLAG_CAP_SENTENCES
- | InputType.TYPE_TEXT_FLAG_AUTO_CORRECT;
- action = EditorInfo.IME_ACTION_NONE;
- break;
- case WebTextView.PASSWORD:
- inputType |= EditorInfo.TYPE_TEXT_VARIATION_WEB_PASSWORD;
- break;
- case WebTextView.SEARCH:
- action = EditorInfo.IME_ACTION_SEARCH;
- break;
- case WebTextView.EMAIL:
- // inputType needs to be overwritten because of the different text variation.
- inputType = InputType.TYPE_CLASS_TEXT
- | InputType.TYPE_TEXT_VARIATION_WEB_EMAIL_ADDRESS;
- break;
- case WebTextView.NUMBER:
- // inputType needs to be overwritten because of the different class.
- inputType = InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_VARIATION_NORMAL
- | InputType.TYPE_NUMBER_FLAG_SIGNED | InputType.TYPE_NUMBER_FLAG_DECIMAL;
- // Number and telephone do not have both a Tab key and an
- // action, so set the action to NEXT
- break;
- case WebTextView.TELEPHONE:
- // inputType needs to be overwritten because of the different class.
- inputType = InputType.TYPE_CLASS_PHONE;
- break;
- case WebTextView.URL:
- // TYPE_TEXT_VARIATION_URI prevents Tab key from showing, so
- // exclude it for now.
- inputType |= InputType.TYPE_TEXT_VARIATION_URI;
- break;
- default:
- break;
- }
- imeOptions |= action;
- mHint = initData.mLabel;
- mInputType = inputType;
- mImeOptions = imeOptions;
- mMaxLength = initData.mMaxLength;
- mIsAutoCompleteEnabled = initData.mIsAutoCompleteEnabled;
- mName = initData.mName;
- mAutoCompletePopup.clearAdapter();
- }
-
- public void setupEditorInfo(EditorInfo outAttrs) {
- outAttrs.inputType = mInputType;
- outAttrs.imeOptions = mImeOptions;
- outAttrs.hintText = mHint;
- outAttrs.initialCapsMode = getCursorCapsMode(InputType.TYPE_CLASS_TEXT);
-
- Editable editable = getEditable();
- int selectionStart = Selection.getSelectionStart(editable);
- int selectionEnd = Selection.getSelectionEnd(editable);
- if (selectionStart < 0 || selectionEnd < 0) {
- selectionStart = editable.length();
- selectionEnd = selectionStart;
- }
- outAttrs.initialSelStart = selectionStart;
- outAttrs.initialSelEnd = selectionEnd;
- }
-
- @Override
- public boolean setSelection(int start, int end) {
- boolean result = super.setSelection(start, end);
- updateSelection();
- return result;
- }
-
- @Override
- public boolean setComposingRegion(int start, int end) {
- boolean result = super.setComposingRegion(start, end);
- updateSelection();
- return result;
- }
-
- /**
- * Send the selection and composing spans to the IME.
- */
- private void updateSelection() {
- Editable editable = getEditable();
- int selectionStart = Selection.getSelectionStart(editable);
- int selectionEnd = Selection.getSelectionEnd(editable);
- int composingStart = getComposingSpanStart(editable);
- int composingEnd = getComposingSpanEnd(editable);
- InputMethodManager imm = InputMethodManager.peekInstance();
- if (imm != null) {
- imm.updateSelection(mWebView, selectionStart, selectionEnd,
- composingStart, composingEnd);
- }
- }
-
- /**
- * Sends a text change to webkit indirectly. If it is a single-
- * character add or delete, it sends it as a key stroke. If it cannot
- * be represented as a key stroke, it sends it as a field change.
- * @param start The start offset (inclusive) of the text being changed.
- * @param end The end offset (exclusive) of the text being changed.
- * @param text The new text to replace the changed text.
- */
- private void setNewText(int start, int end, CharSequence text) {
- mIsKeySentByMe = true;
- Editable editable = getEditable();
- CharSequence original = editable.subSequence(start, end);
- boolean isCharacterAdd = false;
- boolean isCharacterDelete = false;
- int textLength = text.length();
- int originalLength = original.length();
- int selectionStart = Selection.getSelectionStart(editable);
- int selectionEnd = Selection.getSelectionEnd(editable);
- if (selectionStart == selectionEnd) {
- if (textLength > originalLength) {
- isCharacterAdd = (textLength == originalLength + 1)
- && TextUtils.regionMatches(text, 0, original, 0,
- originalLength);
- } else if (originalLength > textLength) {
- isCharacterDelete = (textLength == originalLength - 1)
- && TextUtils.regionMatches(text, 0, original, 0,
- textLength);
- }
- }
- if (isCharacterAdd) {
- sendCharacter(text.charAt(textLength - 1));
- } else if (isCharacterDelete) {
- sendKey(KeyEvent.KEYCODE_DEL);
- } else if ((textLength != originalLength) ||
- !TextUtils.regionMatches(text, 0, original, 0,
- textLength)) {
- // Send a message so that key strokes and text replacement
- // do not come out of order.
- Message replaceMessage = mPrivateHandler.obtainMessage(
- REPLACE_TEXT, start, end, text.toString());
- mPrivateHandler.sendMessage(replaceMessage);
- }
- if (mAutoCompletePopup != null) {
- StringBuilder newText = new StringBuilder();
- newText.append(editable.subSequence(0, start));
- newText.append(text);
- newText.append(editable.subSequence(end, editable.length()));
- mAutoCompletePopup.setText(newText.toString());
- }
- mIsKeySentByMe = false;
- }
-
- /**
- * Send a single character to the WebView as a key down and up event.
- * @param c The character to be sent.
- */
- private void sendCharacter(char c) {
- if (mKeyCharacterMap == null) {
- mKeyCharacterMap = KeyCharacterMap.load(KeyCharacterMap.VIRTUAL_KEYBOARD);
- }
- char[] chars = new char[1];
- chars[0] = c;
- KeyEvent[] events = mKeyCharacterMap.getEvents(chars);
- if (events != null) {
- for (KeyEvent event : events) {
- sendKeyEvent(event);
- }
- } else {
- Message msg = mPrivateHandler.obtainMessage(KEY_PRESS, (int) c, 0);
- mPrivateHandler.sendMessage(msg);
- }
- }
-
- /**
- * Send a key event for a specific key code, not a standard
- * unicode character.
- * @param keyCode The key code to send.
- */
- private void sendKey(int keyCode) {
- long eventTime = SystemClock.uptimeMillis();
- sendKeyEvent(new KeyEvent(eventTime, eventTime,
- KeyEvent.ACTION_DOWN, keyCode, 0, 0,
- KeyCharacterMap.VIRTUAL_KEYBOARD, 0,
- KeyEvent.FLAG_SOFT_KEYBOARD));
- sendKeyEvent(new KeyEvent(SystemClock.uptimeMillis(), eventTime,
- KeyEvent.ACTION_UP, keyCode, 0, 0,
- KeyCharacterMap.VIRTUAL_KEYBOARD, 0,
- KeyEvent.FLAG_SOFT_KEYBOARD));
- }
-
- private CharSequence limitReplaceTextByMaxLength(CharSequence text,
- int numReplaced) {
- if (mMaxLength > 0) {
- Editable editable = getEditable();
- int maxReplace = mMaxLength - editable.length() + numReplaced;
- if (maxReplace < text.length()) {
- maxReplace = Math.max(maxReplace, 0);
- // New length is greater than the maximum. trim it down.
- text = text.subSequence(0, maxReplace);
- }
- }
- return text;
- }
-
- private void restartInput() {
- InputMethodManager imm = InputMethodManager.peekInstance();
- if (imm != null) {
- // Since the text has changed, do not allow the IME to replace the
- // existing text as though it were a completion.
- imm.restartInput(mWebView);
- }
- }
- }
-
- private class PastePopupWindow extends PopupWindow implements View.OnClickListener {
- private ViewGroup mContentView;
- private TextView mPasteTextView;
-
- public PastePopupWindow() {
- super(mContext, null,
- com.android.internal.R.attr.textSelectHandleWindowStyle);
- setClippingEnabled(true);
- LinearLayout linearLayout = new LinearLayout(mContext);
- linearLayout.setOrientation(LinearLayout.HORIZONTAL);
- mContentView = linearLayout;
- mContentView.setBackgroundResource(
- com.android.internal.R.drawable.text_edit_paste_window);
-
- LayoutInflater inflater = (LayoutInflater)mContext.
- getSystemService(Context.LAYOUT_INFLATER_SERVICE);
-
- ViewGroup.LayoutParams wrapContent = new ViewGroup.LayoutParams(
- ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
-
- mPasteTextView = (TextView) inflater.inflate(
- com.android.internal.R.layout.text_edit_action_popup_text, null);
- mPasteTextView.setLayoutParams(wrapContent);
- mContentView.addView(mPasteTextView);
- mPasteTextView.setText(com.android.internal.R.string.paste);
- mPasteTextView.setOnClickListener(this);
- this.setContentView(mContentView);
- }
-
- public void show(Point cursorBottom, Point cursorTop,
- int windowLeft, int windowTop) {
- measureContent();
-
- int width = mContentView.getMeasuredWidth();
- int height = mContentView.getMeasuredHeight();
- int y = cursorTop.y - height;
- int x = cursorTop.x - (width / 2);
- if (y < windowTop) {
- // There's not enough room vertically, move it below the
- // handle.
- ensureSelectionHandles();
- y = cursorBottom.y + mSelectHandleCenter.getIntrinsicHeight();
- x = cursorBottom.x - (width / 2);
- }
- if (x < windowLeft) {
- x = windowLeft;
- }
- if (!isShowing()) {
- showAtLocation(mWebView, Gravity.NO_GRAVITY, x, y);
- }
- update(x, y, width, height);
- }
-
- public void hide() {
- dismiss();
- }
-
- @Override
- public void onClick(View view) {
- pasteFromClipboard();
- selectionDone();
- }
-
- protected void measureContent() {
- final DisplayMetrics displayMetrics = mContext.getResources().getDisplayMetrics();
- mContentView.measure(
- View.MeasureSpec.makeMeasureSpec(displayMetrics.widthPixels,
- View.MeasureSpec.AT_MOST),
- View.MeasureSpec.makeMeasureSpec(displayMetrics.heightPixels,
- View.MeasureSpec.AT_MOST));
- }
- }
-
- // if AUTO_REDRAW_HACK is true, then the CALL key will toggle redrawing
- // the screen all-the-time. Good for profiling our drawing code
- static private final boolean AUTO_REDRAW_HACK = false;
-
- // The rate at which edit text is scrolled in content pixels per millisecond
- static private final float TEXT_SCROLL_RATE = 0.01f;
-
- // The presumed scroll rate for the first scroll of edit text
- static private final long TEXT_SCROLL_FIRST_SCROLL_MS = 16;
-
- // Buffer pixels of the caret rectangle when moving edit text into view
- // after resize.
- static private final int EDIT_RECT_BUFFER = 10;
-
- static private final long SELECTION_HANDLE_ANIMATION_MS = 150;
-
- // true means redraw the screen all-the-time. Only with AUTO_REDRAW_HACK
- private boolean mAutoRedraw;
-
- // Reference to the AlertDialog displayed by InvokeListBox.
- // It's used to dismiss the dialog in destroy if not done before.
- private AlertDialog mListBoxDialog = null;
-
- // Reference to the save password dialog so it can be dimissed in
- // destroy if not done before.
- private AlertDialog mSavePasswordDialog = null;
-
- static final String LOGTAG = "webview";
-
- private ZoomManager mZoomManager;
-
- private final Rect mInvScreenRect = new Rect();
- private final Rect mScreenRect = new Rect();
- private final RectF mVisibleContentRect = new RectF();
- private boolean mIsWebViewVisible = true;
- WebViewInputConnection mInputConnection = null;
- private int mFieldPointer;
- private PastePopupWindow mPasteWindow;
- private AutoCompletePopup mAutoCompletePopup;
- Rect mEditTextContentBounds = new Rect();
- Rect mEditTextContent = new Rect();
- int mEditTextLayerId;
- boolean mIsEditingText = false;
- ArrayList<Message> mBatchedTextChanges = new ArrayList<Message>();
- boolean mIsBatchingTextChanges = false;
- private long mLastEditScroll = 0;
-
- private static class OnTrimMemoryListener implements ComponentCallbacks2 {
- private static OnTrimMemoryListener sInstance = null;
-
- static void init(Context c) {
- if (sInstance == null) {
- sInstance = new OnTrimMemoryListener(c.getApplicationContext());
- }
- }
-
- private OnTrimMemoryListener(Context c) {
- c.registerComponentCallbacks(this);
- }
-
- @Override
- public void onConfigurationChanged(Configuration newConfig) {
- // Ignore
- }
-
- @Override
- public void onLowMemory() {
- // Ignore
- }
-
- @Override
- public void onTrimMemory(int level) {
- if (DebugFlags.WEB_VIEW) {
- Log.d("WebView", "onTrimMemory: " + level);
- }
- // When framework reset EGL context during high memory pressure, all
- // the existing GL resources for the html5 video will be destroyed
- // at native side.
- // Here we just need to clean up the Surface Texture which is static.
- if (level > TRIM_MEMORY_UI_HIDDEN) {
- HTML5VideoInline.cleanupSurfaceTexture();
- HTML5VideoView.release();
- }
- WebViewClassic.nativeOnTrimMemory(level);
- }
- }
-
- // A final CallbackProxy shared by WebViewCore and BrowserFrame.
- private CallbackProxy mCallbackProxy;
-
- private WebViewDatabaseClassic mDatabase;
-
- // SSL certificate for the main top-level page (if secure)
- private SslCertificate mCertificate;
-
- // Native WebView pointer that is 0 until the native object has been
- // created.
- private int mNativeClass;
- // This would be final but it needs to be set to null when the WebView is
- // destroyed.
- private WebViewCore mWebViewCore;
- // Handler for dispatching UI messages.
- /* package */ final Handler mPrivateHandler = new PrivateHandler();
- // Used to ignore changes to webkit text that arrives to the UI side after
- // more key events.
- private int mTextGeneration;
-
- /* package */ void incrementTextGeneration() { mTextGeneration++; }
-
- // Used by WebViewCore to create child views.
- /* package */ ViewManager mViewManager;
-
- // Used to display in full screen mode
- PluginFullScreenHolder mFullScreenHolder;
-
- /**
- * Position of the last touch event in pixels.
- * Use integer to prevent loss of dragging delta calculation accuracy;
- * which was done in float and converted to integer, and resulted in gradual
- * and compounding touch position and view dragging mismatch.
- */
- private int mLastTouchX;
- private int mLastTouchY;
- private int mStartTouchX;
- private int mStartTouchY;
- private float mAverageAngle;
-
- /**
- * Time of the last touch event.
- */
- private long mLastTouchTime;
-
- /**
- * Time of the last time sending touch event to WebViewCore
- */
- private long mLastSentTouchTime;
-
- /**
- * The minimum elapsed time before sending another ACTION_MOVE event to
- * WebViewCore. This really should be tuned for each type of the devices.
- * For example in Google Map api test case, it takes Dream device at least
- * 150ms to do a full cycle in the WebViewCore by processing a touch event,
- * triggering the layout and drawing the picture. While the same process
- * takes 60+ms on the current high speed device. If we make
- * TOUCH_SENT_INTERVAL too small, there will be multiple touch events sent
- * to WebViewCore queue and the real layout and draw events will be pushed
- * to further, which slows down the refresh rate. Choose 50 to favor the
- * current high speed devices. For Dream like devices, 100 is a better
- * choice. Maybe make this in the buildspec later.
- * (Update 12/14/2010: changed to 0 since current device should be able to
- * handle the raw events and Map team voted to have the raw events too.
- */
- private static final int TOUCH_SENT_INTERVAL = 0;
- private int mCurrentTouchInterval = TOUCH_SENT_INTERVAL;
-
- /**
- * Helper class to get velocity for fling
- */
- VelocityTracker mVelocityTracker;
- private int mMaximumFling;
- private float mLastVelocity;
- private float mLastVelX;
- private float mLastVelY;
-
- // The id of the native layer being scrolled.
- private int mCurrentScrollingLayerId;
- private Rect mScrollingLayerRect = new Rect();
-
- // only trigger accelerated fling if the new velocity is at least
- // MINIMUM_VELOCITY_RATIO_FOR_ACCELERATION times of the previous velocity
- private static final float MINIMUM_VELOCITY_RATIO_FOR_ACCELERATION = 0.2f;
-
- /**
- * Touch mode
- * TODO: Some of this is now unnecessary as it is handled by
- * WebInputTouchDispatcher (such as click, long press, and double tap).
- */
- private int mTouchMode = TOUCH_DONE_MODE;
- private static final int TOUCH_INIT_MODE = 1;
- private static final int TOUCH_DRAG_START_MODE = 2;
- private static final int TOUCH_DRAG_MODE = 3;
- private static final int TOUCH_SHORTPRESS_START_MODE = 4;
- private static final int TOUCH_SHORTPRESS_MODE = 5;
- private static final int TOUCH_DOUBLE_TAP_MODE = 6;
- private static final int TOUCH_DONE_MODE = 7;
- private static final int TOUCH_PINCH_DRAG = 8;
- private static final int TOUCH_DRAG_LAYER_MODE = 9;
- private static final int TOUCH_DRAG_TEXT_MODE = 10;
-
- // true when the touch movement exceeds the slop
- private boolean mConfirmMove;
- private boolean mTouchInEditText;
-
- // Whether or not to draw the cursor ring.
- private boolean mDrawCursorRing = true;
-
- // true if onPause has been called (and not onResume)
- private boolean mIsPaused;
-
- private HitTestResult mInitialHitTestResult;
- private WebKitHitTest mFocusedNode;
-
- /**
- * Customizable constant
- */
- // pre-computed square of ViewConfiguration.getScaledTouchSlop()
- private int mTouchSlopSquare;
- // pre-computed square of ViewConfiguration.getScaledDoubleTapSlop()
- private int mDoubleTapSlopSquare;
- // pre-computed density adjusted navigation slop
- private int mNavSlop;
- // This should be ViewConfiguration.getTapTimeout()
- // But system time out is 100ms, which is too short for the browser.
- // In the browser, if it switches out of tap too soon, jump tap won't work.
- // In addition, a double tap on a trackpad will always have a duration of
- // 300ms, so this value must be at least that (otherwise we will timeout the
- // first tap and convert it to a long press).
- private static final int TAP_TIMEOUT = 300;
- // This should be ViewConfiguration.getLongPressTimeout()
- // But system time out is 500ms, which is too short for the browser.
- // With a short timeout, it's difficult to treat trigger a short press.
- private static final int LONG_PRESS_TIMEOUT = 1000;
- // needed to avoid flinging after a pause of no movement
- private static final int MIN_FLING_TIME = 250;
- // draw unfiltered after drag is held without movement
- private static final int MOTIONLESS_TIME = 100;
- // The amount of content to overlap between two screens when going through
- // pages with the space bar, in pixels.
- private static final int PAGE_SCROLL_OVERLAP = 24;
-
- /**
- * These prevent calling requestLayout if either dimension is fixed. This
- * depends on the layout parameters and the measure specs.
- */
- boolean mWidthCanMeasure;
- boolean mHeightCanMeasure;
-
- // Remember the last dimensions we sent to the native side so we can avoid
- // sending the same dimensions more than once.
- int mLastWidthSent;
- int mLastHeightSent;
- // Since view height sent to webkit could be fixed to avoid relayout, this
- // value records the last sent actual view height.
- int mLastActualHeightSent;
-
- private int mContentWidth; // cache of value from WebViewCore
- private int mContentHeight; // cache of value from WebViewCore
-
- // Need to have the separate control for horizontal and vertical scrollbar
- // style than the View's single scrollbar style
- private boolean mOverlayHorizontalScrollbar = true;
- private boolean mOverlayVerticalScrollbar = false;
-
- // our standard speed. this way small distances will be traversed in less
- // time than large distances, but we cap the duration, so that very large
- // distances won't take too long to get there.
- private static final int STD_SPEED = 480; // pixels per second
- // time for the longest scroll animation
- private static final int MAX_DURATION = 750; // milliseconds
-
- // Used by OverScrollGlow
- OverScroller mScroller;
- Scroller mEditTextScroller;
-
- private boolean mInOverScrollMode = false;
- private static Paint mOverScrollBackground;
- private static Paint mOverScrollBorder;
-
- private boolean mWrapContent;
- private static final int MOTIONLESS_FALSE = 0;
- private static final int MOTIONLESS_PENDING = 1;
- private static final int MOTIONLESS_TRUE = 2;
- private static final int MOTIONLESS_IGNORE = 3;
- private int mHeldMotionless;
-
- // Lazily-instantiated instance for injecting accessibility.
- private AccessibilityInjector mAccessibilityInjector;
-
- /**
- * How long the caret handle will last without being touched.
- */
- private static final long CARET_HANDLE_STAMINA_MS = 3000;
-
- private Drawable mSelectHandleLeft;
- private Drawable mSelectHandleRight;
- private Drawable mSelectHandleCenter;
- private Point mSelectOffset;
- private Point mSelectCursorBase = new Point();
- private Rect mSelectHandleBaseBounds = new Rect();
- private int mSelectCursorBaseLayerId;
- private QuadF mSelectCursorBaseTextQuad = new QuadF();
- private Point mSelectCursorExtent = new Point();
- private Rect mSelectHandleExtentBounds = new Rect();
- private int mSelectCursorExtentLayerId;
- private QuadF mSelectCursorExtentTextQuad = new QuadF();
- private Point mSelectDraggingCursor;
- private QuadF mSelectDraggingTextQuad;
- private boolean mIsCaretSelection;
- static final int HANDLE_ID_BASE = 0;
- static final int HANDLE_ID_EXTENT = 1;
-
- // the color used to highlight the touch rectangles
- static final int HIGHLIGHT_COLOR = 0x6633b5e5;
- // the region indicating where the user touched on the screen
- private Region mTouchHighlightRegion = new Region();
- // the paint for the touch highlight
- private Paint mTouchHightlightPaint = new Paint();
- // debug only
- private static final boolean DEBUG_TOUCH_HIGHLIGHT = true;
- private static final int TOUCH_HIGHLIGHT_ELAPSE_TIME = 2000;
- private Paint mTouchCrossHairColor;
- private int mTouchHighlightX;
- private int mTouchHighlightY;
- private boolean mShowTapHighlight;
-
- // Basically this proxy is used to tell the Video to update layer tree at
- // SetBaseLayer time and to pause when WebView paused.
- private HTML5VideoViewProxy mHTML5VideoViewProxy;
-
- // If we are using a set picture, don't send view updates to webkit
- private boolean mBlockWebkitViewMessages = false;
-
- // cached value used to determine if we need to switch drawing models
- private boolean mHardwareAccelSkia = false;
-
- /*
- * Private message ids
- */
- private static final int REMEMBER_PASSWORD = 1;
- private static final int NEVER_REMEMBER_PASSWORD = 2;
- private static final int SWITCH_TO_SHORTPRESS = 3;
- private static final int SWITCH_TO_LONGPRESS = 4;
- private static final int RELEASE_SINGLE_TAP = 5;
- private static final int REQUEST_FORM_DATA = 6;
- private static final int DRAG_HELD_MOTIONLESS = 8;
- private static final int PREVENT_DEFAULT_TIMEOUT = 10;
- private static final int SCROLL_SELECT_TEXT = 11;
-
-
- private static final int FIRST_PRIVATE_MSG_ID = REMEMBER_PASSWORD;
- private static final int LAST_PRIVATE_MSG_ID = SCROLL_SELECT_TEXT;
-
- /*
- * Package message ids
- */
- static final int SCROLL_TO_MSG_ID = 101;
- static final int NEW_PICTURE_MSG_ID = 105;
- static final int WEBCORE_INITIALIZED_MSG_ID = 107;
- static final int UPDATE_TEXTFIELD_TEXT_MSG_ID = 108;
- static final int UPDATE_ZOOM_RANGE = 109;
- static final int TAKE_FOCUS = 110;
- static final int CLEAR_TEXT_ENTRY = 111;
- static final int UPDATE_TEXT_SELECTION_MSG_ID = 112;
- static final int SHOW_RECT_MSG_ID = 113;
- static final int LONG_PRESS_CENTER = 114;
- static final int PREVENT_TOUCH_ID = 115;
- static final int WEBCORE_NEED_TOUCH_EVENTS = 116;
- // obj=Rect in doc coordinates
- static final int INVAL_RECT_MSG_ID = 117;
- static final int REQUEST_KEYBOARD = 118;
- static final int SHOW_FULLSCREEN = 120;
- static final int HIDE_FULLSCREEN = 121;
- static final int UPDATE_MATCH_COUNT = 126;
- static final int CENTER_FIT_RECT = 127;
- static final int SET_SCROLLBAR_MODES = 129;
- static final int HIT_TEST_RESULT = 130;
- static final int SAVE_WEBARCHIVE_FINISHED = 131;
- static final int SET_AUTOFILLABLE = 132;
- static final int AUTOFILL_COMPLETE = 133;
- static final int SCREEN_ON = 134;
- static final int UPDATE_ZOOM_DENSITY = 135;
- static final int EXIT_FULLSCREEN_VIDEO = 136;
- static final int COPY_TO_CLIPBOARD = 137;
- static final int INIT_EDIT_FIELD = 138;
- static final int REPLACE_TEXT = 139;
- static final int CLEAR_CARET_HANDLE = 140;
- static final int KEY_PRESS = 141;
- static final int RELOCATE_AUTO_COMPLETE_POPUP = 142;
- static final int FOCUS_NODE_CHANGED = 143;
- static final int AUTOFILL_FORM = 144;
- static final int SCROLL_EDIT_TEXT = 145;
- static final int EDIT_TEXT_SIZE_CHANGED = 146;
- static final int SHOW_CARET_HANDLE = 147;
- static final int UPDATE_CONTENT_BOUNDS = 148;
- static final int SCROLL_HANDLE_INTO_VIEW = 149;
-
- private static final int FIRST_PACKAGE_MSG_ID = SCROLL_TO_MSG_ID;
- private static final int LAST_PACKAGE_MSG_ID = HIT_TEST_RESULT;
-
- static final String[] HandlerPrivateDebugString = {
- "REMEMBER_PASSWORD", // = 1;
- "NEVER_REMEMBER_PASSWORD", // = 2;
- "SWITCH_TO_SHORTPRESS", // = 3;
- "SWITCH_TO_LONGPRESS", // = 4;
- "RELEASE_SINGLE_TAP", // = 5;
- "REQUEST_FORM_DATA", // = 6;
- "RESUME_WEBCORE_PRIORITY", // = 7;
- "DRAG_HELD_MOTIONLESS", // = 8;
- "", // = 9;
- "PREVENT_DEFAULT_TIMEOUT", // = 10;
- "SCROLL_SELECT_TEXT" // = 11;
- };
-
- static final String[] HandlerPackageDebugString = {
- "SCROLL_TO_MSG_ID", // = 101;
- "102", // = 102;
- "103", // = 103;
- "104", // = 104;
- "NEW_PICTURE_MSG_ID", // = 105;
- "UPDATE_TEXT_ENTRY_MSG_ID", // = 106;
- "WEBCORE_INITIALIZED_MSG_ID", // = 107;
- "UPDATE_TEXTFIELD_TEXT_MSG_ID", // = 108;
- "UPDATE_ZOOM_RANGE", // = 109;
- "UNHANDLED_NAV_KEY", // = 110;
- "CLEAR_TEXT_ENTRY", // = 111;
- "UPDATE_TEXT_SELECTION_MSG_ID", // = 112;
- "SHOW_RECT_MSG_ID", // = 113;
- "LONG_PRESS_CENTER", // = 114;
- "PREVENT_TOUCH_ID", // = 115;
- "WEBCORE_NEED_TOUCH_EVENTS", // = 116;
- "INVAL_RECT_MSG_ID", // = 117;
- "REQUEST_KEYBOARD", // = 118;
- "DO_MOTION_UP", // = 119;
- "SHOW_FULLSCREEN", // = 120;
- "HIDE_FULLSCREEN", // = 121;
- "DOM_FOCUS_CHANGED", // = 122;
- "REPLACE_BASE_CONTENT", // = 123;
- "RETURN_LABEL", // = 125;
- "UPDATE_MATCH_COUNT", // = 126;
- "CENTER_FIT_RECT", // = 127;
- "REQUEST_KEYBOARD_WITH_SELECTION_MSG_ID", // = 128;
- "SET_SCROLLBAR_MODES", // = 129;
- "SELECTION_STRING_CHANGED", // = 130;
- "SET_TOUCH_HIGHLIGHT_RECTS", // = 131;
- "SAVE_WEBARCHIVE_FINISHED", // = 132;
- "SET_AUTOFILLABLE", // = 133;
- "AUTOFILL_COMPLETE", // = 134;
- "SELECT_AT", // = 135;
- "SCREEN_ON", // = 136;
- "ENTER_FULLSCREEN_VIDEO", // = 137;
- "UPDATE_SELECTION", // = 138;
- "UPDATE_ZOOM_DENSITY" // = 139;
- };
-
- // If the site doesn't use the viewport meta tag to specify the viewport,
- // use DEFAULT_VIEWPORT_WIDTH as the default viewport width
- static final int DEFAULT_VIEWPORT_WIDTH = 980;
-
- // normally we try to fit the content to the minimum preferred width
- // calculated by the Webkit. To avoid the bad behavior when some site's
- // minimum preferred width keeps growing when changing the viewport width or
- // the minimum preferred width is huge, an upper limit is needed.
- static int sMaxViewportWidth = DEFAULT_VIEWPORT_WIDTH;
-
- // initial scale in percent. 0 means using default.
- private int mInitialScaleInPercent = 0;
-
- // Whether or not a scroll event should be sent to webkit. This is only set
- // to false when restoring the scroll position.
- private boolean mSendScrollEvent = true;
-
- private int mSnapScrollMode = SNAP_NONE;
- private static final int SNAP_NONE = 0;
- private static final int SNAP_LOCK = 1; // not a separate state
- private static final int SNAP_X = 2; // may be combined with SNAP_LOCK
- private static final int SNAP_Y = 4; // may be combined with SNAP_LOCK
- private boolean mSnapPositive;
-
- // keep these in sync with their counterparts in WebView.cpp
- private static final int DRAW_EXTRAS_NONE = 0;
- private static final int DRAW_EXTRAS_SELECTION = 1;
- private static final int DRAW_EXTRAS_CURSOR_RING = 2;
-
- // keep this in sync with WebCore:ScrollbarMode in WebKit
- private static final int SCROLLBAR_AUTO = 0;
- private static final int SCROLLBAR_ALWAYSOFF = 1;
- // as we auto fade scrollbar, this is ignored.
- private static final int SCROLLBAR_ALWAYSON = 2;
- private int mHorizontalScrollBarMode = SCROLLBAR_AUTO;
- private int mVerticalScrollBarMode = SCROLLBAR_AUTO;
-
- /**
- * Max distance to overscroll by in pixels.
- * This how far content can be pulled beyond its normal bounds by the user.
- */
- private int mOverscrollDistance;
-
- /**
- * Max distance to overfling by in pixels.
- * This is how far flinged content can move beyond the end of its normal bounds.
- */
- private int mOverflingDistance;
-
- private OverScrollGlow mOverScrollGlow;
-
- // Used to match key downs and key ups
- private Vector<Integer> mKeysPressed;
-
- /* package */ static boolean mLogEvent = true;
-
- // for event log
- private long mLastTouchUpTime = 0;
-
- private WebViewCore.AutoFillData mAutoFillData;
-
- private static boolean sNotificationsEnabled = true;
-
- /**
- * URI scheme for telephone number
- */
- public static final String SCHEME_TEL = "tel:";
- /**
- * URI scheme for email address
- */
- public static final String SCHEME_MAILTO = "mailto:";
- /**
- * URI scheme for map address
- */
- public static final String SCHEME_GEO = "geo:0,0?q=";
-
- private int mBackgroundColor = Color.WHITE;
-
- private static final long SELECT_SCROLL_INTERVAL = 1000 / 60; // 60 / second
- private int mAutoScrollX = 0;
- private int mAutoScrollY = 0;
- private int mMinAutoScrollX = 0;
- private int mMaxAutoScrollX = 0;
- private int mMinAutoScrollY = 0;
- private int mMaxAutoScrollY = 0;
- private Rect mScrollingLayerBounds = new Rect();
- private boolean mSentAutoScrollMessage = false;
-
- // used for serializing asynchronously handled touch events.
- private WebViewInputDispatcher mInputDispatcher;
-
- // Used to track whether picture updating was paused due to a window focus change.
- private boolean mPictureUpdatePausedForFocusChange = false;
-
- // Used to notify listeners of a new picture.
- private PictureListener mPictureListener;
-
- // Used to notify listeners about find-on-page results.
- private WebView.FindListener mFindListener;
-
- // Used to prevent resending save password message
- private Message mResumeMsg;
-
- /**
- * Refer to {@link WebView#requestFocusNodeHref(Message)} for more information
- */
- static class FocusNodeHref {
- static final String TITLE = "title";
- static final String URL = "url";
- static final String SRC = "src";
- }
-
- public WebViewClassic(WebView webView, WebView.PrivateAccess privateAccess) {
- mWebView = webView;
- mWebViewPrivate = privateAccess;
- mContext = webView.getContext();
- }
-
- /**
- * See {@link WebViewProvider#init(Map, boolean)}
- */
- @Override
- public void init(Map<String, Object> javaScriptInterfaces, boolean privateBrowsing) {
- Context context = mContext;
-
- // Used by the chrome stack to find application paths
- JniUtil.setContext(context);
-
- mCallbackProxy = new CallbackProxy(context, this);
- mViewManager = new ViewManager(this);
- L10nUtils.setApplicationContext(context.getApplicationContext());
- mWebViewCore = new WebViewCore(context, this, mCallbackProxy, javaScriptInterfaces);
- mDatabase = WebViewDatabaseClassic.getInstance(context);
- mScroller = new OverScroller(context, null, 0, 0, false); //TODO Use OverScroller's flywheel
- mZoomManager = new ZoomManager(this, mCallbackProxy);
-
- /* The init method must follow the creation of certain member variables,
- * such as the mZoomManager.
- */
- init();
- setupPackageListener(context);
- setupProxyListener(context);
- setupTrustStorageListener(context);
- updateMultiTouchSupport(context);
-
- if (privateBrowsing) {
- startPrivateBrowsing();
- }
-
- mAutoFillData = new WebViewCore.AutoFillData();
- mEditTextScroller = new Scroller(context);
-
- // Calculate channel distance
- calculateChannelDistance(context);
- }
-
- /**
- * Calculate sChannelDistance based on the screen information.
- * @param context A Context object used to access application assets.
- */
- private void calculateChannelDistance(Context context) {
- // The channel distance is adjusted for density and screen size
- final DisplayMetrics metrics = context.getResources().getDisplayMetrics();
- final double screenSize = Math.hypot((double)(metrics.widthPixels/metrics.densityDpi),
- (double)(metrics.heightPixels/metrics.densityDpi));
- if (screenSize < 3.0) {
- sChannelDistance = 16;
- } else if (screenSize < 5.0) {
- sChannelDistance = 22;
- } else if (screenSize < 7.0) {
- sChannelDistance = 28;
- } else {
- sChannelDistance = 34;
- }
- sChannelDistance = (int)(sChannelDistance * metrics.density);
- if (sChannelDistance < 16) sChannelDistance = 16;
-
- if (DebugFlags.WEB_VIEW) {
- Log.v(LOGTAG, "sChannelDistance : " + sChannelDistance
- + ", density : " + metrics.density
- + ", screenSize : " + screenSize
- + ", metrics.heightPixels : " + metrics.heightPixels
- + ", metrics.widthPixels : " + metrics.widthPixels
- + ", metrics.densityDpi : " + metrics.densityDpi);
- }
- }
-
- // WebViewProvider bindings
-
- static class Factory implements WebViewFactoryProvider, WebViewFactoryProvider.Statics {
- Factory() {
- // Touch JniUtil and WebViewCore in case this is being called from
- // WebViewFactory.Preloader, to ensure that the JNI libraries that they use are
- // preloaded in the zygote.
- try {
- Class.forName("android.webkit.JniUtil");
- Class.forName("android.webkit.WebViewCore");
- } catch (ClassNotFoundException e) {
- Log.e(LOGTAG, "failed to load JNI libraries");
- throw new AndroidRuntimeException(e);
- }
- }
-
- @Override
- public String findAddress(String addr) {
- return WebViewClassic.findAddress(addr);
- }
- @Override
- public void setPlatformNotificationsEnabled(boolean enable) {
- if (enable) {
- WebViewClassic.enablePlatformNotifications();
- } else {
- WebViewClassic.disablePlatformNotifications();
- }
- }
-
- @Override
- public Statics getStatics() { return this; }
-
- @Override
- public WebViewProvider createWebView(WebView webView, WebView.PrivateAccess privateAccess) {
- return new WebViewClassic(webView, privateAccess);
- }
-
- @Override
- public GeolocationPermissions getGeolocationPermissions() {
- return GeolocationPermissionsClassic.getInstance();
- }
-
- @Override
- public CookieManager getCookieManager() {
- return CookieManagerClassic.getInstance();
- }
-
- @Override
- public WebIconDatabase getWebIconDatabase() {
- return WebIconDatabaseClassic.getInstance();
- }
-
- @Override
- public WebStorage getWebStorage() {
- return WebStorageClassic.getInstance();
- }
-
- @Override
- public WebViewDatabase getWebViewDatabase(Context context) {
- return WebViewDatabaseClassic.getInstance(context);
- }
-
- @Override
- public String getDefaultUserAgent(Context context) {
- return WebSettingsClassic.getDefaultUserAgentForLocale(context,
- Locale.getDefault());
- }
-
- @Override
- public void setWebContentsDebuggingEnabled(boolean enable) {
- // no-op for WebViewClassic.
- }
- }
-
- private void onHandleUiEvent(MotionEvent event, int eventType, int flags) {
- switch (eventType) {
- case WebViewInputDispatcher.EVENT_TYPE_LONG_PRESS:
- HitTestResult hitTest = getHitTestResult();
- if (hitTest != null) {
- mWebView.performLongClick();
- }
- break;
- case WebViewInputDispatcher.EVENT_TYPE_DOUBLE_TAP:
- mZoomManager.handleDoubleTap(event.getX(), event.getY());
- break;
- case WebViewInputDispatcher.EVENT_TYPE_TOUCH:
- onHandleUiTouchEvent(event);
- break;
- case WebViewInputDispatcher.EVENT_TYPE_CLICK:
- if (mFocusedNode != null && mFocusedNode.mIntentUrl != null) {
- mWebView.playSoundEffect(SoundEffectConstants.CLICK);
- overrideLoading(mFocusedNode.mIntentUrl);
- }
- break;
- }
- }
-
- private void onHandleUiTouchEvent(MotionEvent ev) {
- final ScaleGestureDetector detector =
- mZoomManager.getScaleGestureDetector();
-
- int action = ev.getActionMasked();
- final boolean pointerUp = action == MotionEvent.ACTION_POINTER_UP;
- final boolean configChanged =
- action == MotionEvent.ACTION_POINTER_UP ||
- action == MotionEvent.ACTION_POINTER_DOWN;
- final int skipIndex = pointerUp ? ev.getActionIndex() : -1;
-
- // Determine focal point
- float sumX = 0, sumY = 0;
- final int count = ev.getPointerCount();
- for (int i = 0; i < count; i++) {
- if (skipIndex == i) continue;
- sumX += ev.getX(i);
- sumY += ev.getY(i);
- }
- final int div = pointerUp ? count - 1 : count;
- float x = sumX / div;
- float y = sumY / div;
-
- if (configChanged) {
- mLastTouchX = Math.round(x);
- mLastTouchY = Math.round(y);
- mLastTouchTime = ev.getEventTime();
- mWebView.cancelLongPress();
- mPrivateHandler.removeMessages(SWITCH_TO_LONGPRESS);
- }
-
- if (detector != null) {
- detector.onTouchEvent(ev);
- if (detector.isInProgress()) {
- mLastTouchTime = ev.getEventTime();
-
- if (!mZoomManager.supportsPanDuringZoom()) {
- return;
- }
- mTouchMode = TOUCH_DRAG_MODE;
- if (mVelocityTracker == null) {
- mVelocityTracker = VelocityTracker.obtain();
- }
- }
- }
-
- if (action == MotionEvent.ACTION_POINTER_DOWN) {
- cancelTouch();
- action = MotionEvent.ACTION_DOWN;
- } else if (action == MotionEvent.ACTION_MOVE) {
- // negative x or y indicate it is on the edge, skip it.
- if (x < 0 || y < 0) {
- return;
- }
- }
-
- handleTouchEventCommon(ev, action, Math.round(x), Math.round(y));
- }
-
- // The webview that is bound to this WebViewClassic instance. Primarily needed for supplying
- // as the first param in the WebViewClient and WebChromeClient callbacks.
- final private WebView mWebView;
- // Callback interface, provides priviledged access into the WebView instance.
- final private WebView.PrivateAccess mWebViewPrivate;
- // Cached reference to mWebView.getContext(), for convenience.
- final private Context mContext;
-
- /**
- * @return The webview proxy that this classic webview is bound to.
- */
- public WebView getWebView() {
- return mWebView;
- }
-
- @Override
- public ViewDelegate getViewDelegate() {
- return this;
- }
-
- @Override
- public ScrollDelegate getScrollDelegate() {
- return this;
- }
-
- public static WebViewClassic fromWebView(WebView webView) {
- return webView == null ? null : (WebViewClassic) webView.getWebViewProvider();
- }
-
- // Accessors, purely for convenience (and to reduce code churn during webview proxy migration).
- int getScrollX() {
- return mWebView.getScrollX();
- }
-
- int getScrollY() {
- return mWebView.getScrollY();
- }
-
- int getWidth() {
- return mWebView.getWidth();
- }
-
- int getHeight() {
- return mWebView.getHeight();
- }
-
- Context getContext() {
- return mContext;
- }
-
- void invalidate() {
- mWebView.invalidate();
- }
-
- // Setters for the Scroll X & Y, without invoking the onScrollChanged etc code paths.
- void setScrollXRaw(int mScrollX) {
- mWebViewPrivate.setScrollXRaw(mScrollX);
- }
-
- void setScrollYRaw(int mScrollY) {
- mWebViewPrivate.setScrollYRaw(mScrollY);
- }
-
- private static class TrustStorageListener extends BroadcastReceiver {
- @Override
- public void onReceive(Context context, Intent intent) {
- if (intent.getAction().equals(KeyChain.ACTION_STORAGE_CHANGED)) {
- handleCertTrustChanged();
- }
- }
- }
- private static TrustStorageListener sTrustStorageListener;
-
- /**
- * Handles update to the trust storage.
- */
- private static void handleCertTrustChanged() {
- // send a message for indicating trust storage change
- WebViewCore.sendStaticMessage(EventHub.TRUST_STORAGE_UPDATED, null);
- }
-
- /*
- * @param context This method expects this to be a valid context.
- */
- private static void setupTrustStorageListener(Context context) {
- if (sTrustStorageListener != null ) {
- return;
- }
- IntentFilter filter = new IntentFilter();
- filter.addAction(KeyChain.ACTION_STORAGE_CHANGED);
- sTrustStorageListener = new TrustStorageListener();
- Intent current =
- context.getApplicationContext().registerReceiver(sTrustStorageListener, filter);
- if (current != null) {
- handleCertTrustChanged();
- }
- }
-
- private static class ProxyReceiver extends BroadcastReceiver {
- @Override
- public void onReceive(Context context, Intent intent) {
- if (intent.getAction().equals(Proxy.PROXY_CHANGE_ACTION)) {
- handleProxyBroadcast(intent);
- }
- }
- }
-
- /*
- * Receiver for PROXY_CHANGE_ACTION, will be null when it is not added handling broadcasts.
- */
- private static ProxyReceiver sProxyReceiver;
-
- /*
- * @param context This method expects this to be a valid context
- */
- private static synchronized void setupProxyListener(Context context) {
- if (sProxyReceiver != null || sNotificationsEnabled == false) {
- return;
- }
- IntentFilter filter = new IntentFilter();
- filter.addAction(Proxy.PROXY_CHANGE_ACTION);
- sProxyReceiver = new ProxyReceiver();
- Intent currentProxy = context.getApplicationContext().registerReceiver(
- sProxyReceiver, filter);
- if (currentProxy != null) {
- handleProxyBroadcast(currentProxy);
- }
- }
-
- /*
- * @param context This method expects this to be a valid context
- */
- private static synchronized void disableProxyListener(Context context) {
- if (sProxyReceiver == null)
- return;
-
- context.getApplicationContext().unregisterReceiver(sProxyReceiver);
- sProxyReceiver = null;
- }
-
- private static void handleProxyBroadcast(Intent intent) {
- ProxyProperties proxyProperties = (ProxyProperties)intent.getExtra(Proxy.EXTRA_PROXY_INFO);
- if (proxyProperties == null || proxyProperties.getHost() == null) {
- WebViewCore.sendStaticMessage(EventHub.PROXY_CHANGED, null);
- return;
- }
- WebViewCore.sendStaticMessage(EventHub.PROXY_CHANGED, proxyProperties);
- }
-
- /*
- * A variable to track if there is a receiver added for ACTION_PACKAGE_ADDED
- * or ACTION_PACKAGE_REMOVED.
- */
- private static boolean sPackageInstallationReceiverAdded = false;
-
- /*
- * A set of Google packages we monitor for the
- * navigator.isApplicationInstalled() API. Add additional packages as
- * needed.
- */
- private static Set<String> sGoogleApps;
- static {
- sGoogleApps = new HashSet<String>();
- sGoogleApps.add("com.google.android.youtube");
- }
-
- private static class PackageListener extends BroadcastReceiver {
- @Override
- public void onReceive(Context context, Intent intent) {
- final String action = intent.getAction();
- final String packageName = intent.getData().getSchemeSpecificPart();
- final boolean replacing = intent.getBooleanExtra(Intent.EXTRA_REPLACING, false);
- if (Intent.ACTION_PACKAGE_REMOVED.equals(action) && replacing) {
- // if it is replacing, refreshPlugins() when adding
- return;
- }
-
- if (sGoogleApps.contains(packageName)) {
- if (Intent.ACTION_PACKAGE_ADDED.equals(action)) {
- WebViewCore.sendStaticMessage(EventHub.ADD_PACKAGE_NAME, packageName);
- } else {
- WebViewCore.sendStaticMessage(EventHub.REMOVE_PACKAGE_NAME, packageName);
- }
- }
-
- PluginManager pm = PluginManager.getInstance(context);
- if (pm.containsPluginPermissionAndSignatures(packageName)) {
- pm.refreshPlugins(Intent.ACTION_PACKAGE_ADDED.equals(action));
- }
- }
- }
-
- private void setupPackageListener(Context context) {
-
- /*
- * we must synchronize the instance check and the creation of the
- * receiver to ensure that only ONE receiver exists for all WebView
- * instances.
- */
- synchronized (WebViewClassic.class) {
-
- // if the receiver already exists then we do not need to register it
- // again
- if (sPackageInstallationReceiverAdded) {
- return;
- }
-
- IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED);
- filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
- filter.addDataScheme("package");
- BroadcastReceiver packageListener = new PackageListener();
- context.getApplicationContext().registerReceiver(packageListener, filter);
- sPackageInstallationReceiverAdded = true;
- }
-
- // check if any of the monitored apps are already installed
- AsyncTask<Void, Void, Set<String>> task = new AsyncTask<Void, Void, Set<String>>() {
-
- @Override
- protected Set<String> doInBackground(Void... unused) {
- Set<String> installedPackages = new HashSet<String>();
- PackageManager pm = mContext.getPackageManager();
- for (String name : sGoogleApps) {
- try {
- pm.getPackageInfo(name,
- PackageManager.GET_ACTIVITIES | PackageManager.GET_SERVICES);
- installedPackages.add(name);
- } catch (PackageManager.NameNotFoundException e) {
- // package not found
- }
- }
- return installedPackages;
- }
-
- // Executes on the UI thread
- @Override
- protected void onPostExecute(Set<String> installedPackages) {
- if (mWebViewCore != null) {
- mWebViewCore.sendMessage(EventHub.ADD_PACKAGE_NAMES, installedPackages);
- }
- }
- };
- task.execute();
- }
-
- void updateMultiTouchSupport(Context context) {
- mZoomManager.updateMultiTouchSupport(context);
- }
-
- void updateJavaScriptEnabled(boolean enabled) {
- if (isAccessibilityInjectionEnabled()) {
- getAccessibilityInjector().updateJavaScriptEnabled(enabled);
- }
- }
-
- private void init() {
- OnTrimMemoryListener.init(mContext);
- mWebView.setWillNotDraw(false);
- mWebView.setClickable(true);
- mWebView.setLongClickable(true);
-
- final ViewConfiguration configuration = ViewConfiguration.get(mContext);
- int slop = configuration.getScaledTouchSlop();
- mTouchSlopSquare = slop * slop;
- slop = configuration.getScaledDoubleTapSlop();
- mDoubleTapSlopSquare = slop * slop;
- final float density = WebViewCore.getFixedDisplayDensity(mContext);
- // use one line height, 16 based on our current default font, for how
- // far we allow a touch be away from the edge of a link
- mNavSlop = (int) (16 * density);
- mZoomManager.init(density);
- mMaximumFling = configuration.getScaledMaximumFlingVelocity();
-
- // Compute the inverse of the density squared.
- DRAG_LAYER_INVERSE_DENSITY_SQUARED = 1 / (density * density);
-
- mOverscrollDistance = configuration.getScaledOverscrollDistance();
- mOverflingDistance = configuration.getScaledOverflingDistance();
-
- setScrollBarStyle(mWebViewPrivate.super_getScrollBarStyle());
- // Initially use a size of two, since the user is likely to only hold
- // down two keys at a time (shift + another key)
- mKeysPressed = new Vector<Integer>(2);
- mHTML5VideoViewProxy = null ;
- }
-
- @Override
- public boolean shouldDelayChildPressedState() {
- return true;
- }
-
- @Override
- public boolean performAccessibilityAction(int action, Bundle arguments) {
- if (!mWebView.isEnabled()) {
- // Only default actions are supported while disabled.
- return mWebViewPrivate.super_performAccessibilityAction(action, arguments);
- }
-
- if (getAccessibilityInjector().supportsAccessibilityAction(action)) {
- return getAccessibilityInjector().performAccessibilityAction(action, arguments);
- }
-
- switch (action) {
- case AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD:
- case AccessibilityNodeInfo.ACTION_SCROLL_FORWARD: {
- final int convertedContentHeight = contentToViewY(getContentHeight());
- final int adjustedViewHeight = getHeight() - mWebView.getPaddingTop()
- - mWebView.getPaddingBottom();
- final int maxScrollY = Math.max(convertedContentHeight - adjustedViewHeight, 0);
- final boolean canScrollBackward = (getScrollY() > 0);
- final boolean canScrollForward = ((getScrollY() - maxScrollY) > 0);
- if ((action == AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD) && canScrollBackward) {
- mWebView.scrollBy(0, adjustedViewHeight);
- return true;
- }
- if ((action == AccessibilityNodeInfo.ACTION_SCROLL_FORWARD) && canScrollForward) {
- mWebView.scrollBy(0, -adjustedViewHeight);
- return true;
- }
- return false;
- }
- }
-
- return mWebViewPrivate.super_performAccessibilityAction(action, arguments);
- }
-
- @Override
- public AccessibilityNodeProvider getAccessibilityNodeProvider() {
- return null;
- }
-
- @Override
- public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
- if (!mWebView.isEnabled()) {
- // Only default actions are supported while disabled.
- return;
- }
-
- info.setScrollable(isScrollableForAccessibility());
-
- final int convertedContentHeight = contentToViewY(getContentHeight());
- final int adjustedViewHeight = getHeight() - mWebView.getPaddingTop()
- - mWebView.getPaddingBottom();
- final int maxScrollY = Math.max(convertedContentHeight - adjustedViewHeight, 0);
- final boolean canScrollBackward = (getScrollY() > 0);
- final boolean canScrollForward = ((getScrollY() - maxScrollY) > 0);
-
- if (canScrollForward) {
- info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD);
- }
-
- if (canScrollForward) {
- info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD);
- }
-
- getAccessibilityInjector().onInitializeAccessibilityNodeInfo(info);
- }
-
- @Override
- public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
- event.setScrollable(isScrollableForAccessibility());
- event.setScrollX(getScrollX());
- event.setScrollY(getScrollY());
- final int convertedContentWidth = contentToViewX(getContentWidth());
- final int adjustedViewWidth = getWidth() - mWebView.getPaddingLeft()
- - mWebView.getPaddingLeft();
- event.setMaxScrollX(Math.max(convertedContentWidth - adjustedViewWidth, 0));
- final int convertedContentHeight = contentToViewY(getContentHeight());
- final int adjustedViewHeight = getHeight() - mWebView.getPaddingTop()
- - mWebView.getPaddingBottom();
- event.setMaxScrollY(Math.max(convertedContentHeight - adjustedViewHeight, 0));
- }
-
- /* package */ void handleSelectionChangedWebCoreThread(String selection, int token) {
- if (isAccessibilityInjectionEnabled()) {
- getAccessibilityInjector().onSelectionStringChangedWebCoreThread(selection, token);
- }
- }
-
- private boolean isAccessibilityInjectionEnabled() {
- final AccessibilityManager manager = AccessibilityManager.getInstance(mContext);
- if (!manager.isEnabled()) {
- return false;
- }
-
- // Accessibility scripts should be injected only when a speaking service
- // is enabled. This may need to change later to accommodate Braille.
- final List<AccessibilityServiceInfo> services = manager.getEnabledAccessibilityServiceList(
- AccessibilityServiceInfo.FEEDBACK_SPOKEN);
- if (services.isEmpty()) {
- return false;
- }
-
- return true;
- }
-
- private AccessibilityInjector getAccessibilityInjector() {
- if (mAccessibilityInjector == null) {
- mAccessibilityInjector = new AccessibilityInjector(this);
- }
- return mAccessibilityInjector;
- }
-
- private boolean isScrollableForAccessibility() {
- return (contentToViewX(getContentWidth()) > getWidth() - mWebView.getPaddingLeft()
- - mWebView.getPaddingRight()
- || contentToViewY(getContentHeight()) > getHeight() - mWebView.getPaddingTop()
- - mWebView.getPaddingBottom());
- }
-
- @Override
- public void setOverScrollMode(int mode) {
- if (mode != View.OVER_SCROLL_NEVER) {
- if (mOverScrollGlow == null) {
- mOverScrollGlow = new OverScrollGlow(this);
- }
- } else {
- mOverScrollGlow = null;
- }
- }
-
- /* package */ void adjustDefaultZoomDensity(int zoomDensity) {
- final float density = WebViewCore.getFixedDisplayDensity(mContext)
- * 100 / zoomDensity;
- updateDefaultZoomDensity(density);
- }
-
- /* package */ void updateDefaultZoomDensity(float density) {
- mNavSlop = (int) (16 * density);
- mZoomManager.updateDefaultZoomDensity(density);
- }
-
- /* package */ int getScaledNavSlop() {
- return viewToContentDimension(mNavSlop);
- }
-
- /* package */ boolean onSavePassword(String schemePlusHost, String username,
- String password, final Message resumeMsg) {
- boolean rVal = false;
- if (resumeMsg == null) {
- // null resumeMsg implies saving password silently
- mDatabase.setUsernamePassword(schemePlusHost, username, password);
- } else {
- if (mResumeMsg != null) {
- Log.w(LOGTAG, "onSavePassword should not be called while dialog is up");
- resumeMsg.sendToTarget();
- return true;
- }
- mResumeMsg = resumeMsg;
- final Message remember = mPrivateHandler.obtainMessage(
- REMEMBER_PASSWORD);
- remember.getData().putString("host", schemePlusHost);
- remember.getData().putString("username", username);
- remember.getData().putString("password", password);
- remember.obj = resumeMsg;
-
- final Message neverRemember = mPrivateHandler.obtainMessage(
- NEVER_REMEMBER_PASSWORD);
- neverRemember.getData().putString("host", schemePlusHost);
- neverRemember.getData().putString("username", username);
- neverRemember.getData().putString("password", password);
- neverRemember.obj = resumeMsg;
-
- mSavePasswordDialog = new AlertDialog.Builder(mContext)
- .setTitle(com.android.internal.R.string.save_password_label)
- .setMessage(com.android.internal.R.string.save_password_message)
- .setPositiveButton(com.android.internal.R.string.save_password_notnow,
- new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int which) {
- if (mResumeMsg != null) {
- resumeMsg.sendToTarget();
- mResumeMsg = null;
- }
- mSavePasswordDialog = null;
- }
- })
- .setNeutralButton(com.android.internal.R.string.save_password_remember,
- new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int which) {
- if (mResumeMsg != null) {
- remember.sendToTarget();
- mResumeMsg = null;
- }
- mSavePasswordDialog = null;
- }
- })
- .setNegativeButton(com.android.internal.R.string.save_password_never,
- new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int which) {
- if (mResumeMsg != null) {
- neverRemember.sendToTarget();
- mResumeMsg = null;
- }
- mSavePasswordDialog = null;
- }
- })
- .setOnDismissListener(new DialogInterface.OnDismissListener() {
- @Override
- public void onDismiss(DialogInterface dialog) {
- if (mResumeMsg != null) {
- resumeMsg.sendToTarget();
- mResumeMsg = null;
- }
- mSavePasswordDialog = null;
- }
- }).show();
- // Return true so that WebViewCore will pause while the dialog is
- // up.
- rVal = true;
- }
- return rVal;
- }
-
- @Override
- public void setScrollBarStyle(int style) {
- if (style == View.SCROLLBARS_INSIDE_INSET
- || style == View.SCROLLBARS_OUTSIDE_INSET) {
- mOverlayHorizontalScrollbar = mOverlayVerticalScrollbar = false;
- } else {
- mOverlayHorizontalScrollbar = mOverlayVerticalScrollbar = true;
- }
- }
-
- /**
- * See {@link WebView#setHorizontalScrollbarOverlay(boolean)}
- */
- @Override
- public void setHorizontalScrollbarOverlay(boolean overlay) {
- mOverlayHorizontalScrollbar = overlay;
- }
-
- /**
- * See {@link WebView#setVerticalScrollbarOverlay(boolean)
- */
- @Override
- public void setVerticalScrollbarOverlay(boolean overlay) {
- mOverlayVerticalScrollbar = overlay;
- }
-
- /**
- * See {@link WebView#overlayHorizontalScrollbar()}
- */
- @Override
- public boolean overlayHorizontalScrollbar() {
- return mOverlayHorizontalScrollbar;
- }
-
- /**
- * See {@link WebView#overlayVerticalScrollbar()}
- */
- @Override
- public boolean overlayVerticalScrollbar() {
- return mOverlayVerticalScrollbar;
- }
-
- /*
- * Return the width of the view where the content of WebView should render
- * to.
- * Note: this can be called from WebCoreThread.
- */
- /* package */ int getViewWidth() {
- if (!mWebView.isVerticalScrollBarEnabled() || mOverlayVerticalScrollbar) {
- return getWidth();
- } else {
- return Math.max(0, getWidth() - mWebView.getVerticalScrollbarWidth());
- }
- }
-
- // Interface to enable the browser to override title bar handling.
- public interface TitleBarDelegate {
- int getTitleHeight();
- public void onSetEmbeddedTitleBar(final View title);
- }
-
- /**
- * Returns the height (in pixels) of the embedded title bar (if any). Does not care about
- * scrolling
- */
- protected int getTitleHeight() {
- if (mWebView instanceof TitleBarDelegate) {
- return ((TitleBarDelegate) mWebView).getTitleHeight();
- }
- return 0;
- }
-
- /**
- * See {@link WebView#getVisibleTitleHeight()}
- */
- @Override
- @Deprecated
- public int getVisibleTitleHeight() {
- // Actually, this method returns the height of the embedded title bar if one is set via the
- // hidden setEmbeddedTitleBar method.
- return getVisibleTitleHeightImpl();
- }
-
- private int getVisibleTitleHeightImpl() {
- // need to restrict mScrollY due to over scroll
- return Math.max(getTitleHeight() - Math.max(0, getScrollY()),
- getOverlappingActionModeHeight());
- }
-
- private int mCachedOverlappingActionModeHeight = -1;
-
- private int getOverlappingActionModeHeight() {
- if (mFindCallback == null) {
- return 0;
- }
- if (mCachedOverlappingActionModeHeight < 0) {
- mWebView.getGlobalVisibleRect(mGlobalVisibleRect, mGlobalVisibleOffset);
- mCachedOverlappingActionModeHeight = Math.max(0,
- mFindCallback.getActionModeGlobalBottom() - mGlobalVisibleRect.top);
- }
- return mCachedOverlappingActionModeHeight;
- }
-
- /*
- * Return the height of the view where the content of WebView should render
- * to. Note that this excludes mTitleBar, if there is one.
- * Note: this can be called from WebCoreThread.
- */
- /* package */ int getViewHeight() {
- return getViewHeightWithTitle() - getVisibleTitleHeightImpl();
- }
-
- int getViewHeightWithTitle() {
- int height = getHeight();
- if (mWebView.isHorizontalScrollBarEnabled() && !mOverlayHorizontalScrollbar) {
- height -= mWebViewPrivate.getHorizontalScrollbarHeight();
- }
- return height;
- }
-
- /**
- * See {@link WebView#getCertificate()}
- */
- @Override
- public SslCertificate getCertificate() {
- return mCertificate;
- }
-
- /**
- * See {@link WebView#setCertificate(SslCertificate)}
- */
- @Override
- public void setCertificate(SslCertificate certificate) {
- if (DebugFlags.WEB_VIEW) {
- Log.v(LOGTAG, "setCertificate=" + certificate);
- }
- // here, the certificate can be null (if the site is not secure)
- mCertificate = certificate;
- }
-
- //-------------------------------------------------------------------------
- // Methods called by activity
- //-------------------------------------------------------------------------
-
- /**
- * See {@link WebView#savePassword(String, String, String)}
- */
- @Override
- public void savePassword(String host, String username, String password) {
- mDatabase.setUsernamePassword(host, username, password);
- }
-
- /**
- * See {@link WebView#setHttpAuthUsernamePassword(String, String, String, String)}
- */
- @Override
- public void setHttpAuthUsernamePassword(String host, String realm,
- String username, String password) {
- mDatabase.setHttpAuthUsernamePassword(host, realm, username, password);
- }
-
- /**
- * See {@link WebView#getHttpAuthUsernamePassword(String, String)}
- */
- @Override
- public String[] getHttpAuthUsernamePassword(String host, String realm) {
- return mDatabase.getHttpAuthUsernamePassword(host, realm);
- }
-
- /**
- * Remove Find or Select ActionModes, if active.
- */
- private void clearActionModes() {
- if (mSelectCallback != null) {
- mSelectCallback.finish();
- }
- if (mFindCallback != null) {
- mFindCallback.finish();
- }
- }
-
- /**
- * Called to clear state when moving from one page to another, or changing
- * in some other way that makes elements associated with the current page
- * (such as ActionModes) no longer relevant.
- */
- private void clearHelpers() {
- hideSoftKeyboard();
- clearActionModes();
- dismissFullScreenMode();
- cancelDialogs();
- }
-
- private void cancelDialogs() {
- if (mListBoxDialog != null) {
- mListBoxDialog.cancel();
- mListBoxDialog = null;
- }
- if (mSavePasswordDialog != null) {
- mSavePasswordDialog.dismiss();
- mSavePasswordDialog = null;
- }
- }
-
- /**
- * See {@link WebView#destroy()}
- */
- @Override
- public void destroy() {
- if (mWebView.getViewRootImpl() != null) {
- Log.e(LOGTAG, Log.getStackTraceString(
- new Throwable("Error: WebView.destroy() called while still attached!")));
- }
- ensureFunctorDetached();
- destroyJava();
- destroyNative();
- }
-
- private void ensureFunctorDetached() {
- if (mWebView.isHardwareAccelerated()) {
- int drawGLFunction = nativeGetDrawGLFunction(mNativeClass);
- ViewRootImpl viewRoot = mWebView.getViewRootImpl();
- if (drawGLFunction != 0 && viewRoot != null) {
- viewRoot.detachFunctor(drawGLFunction);
- }
- }
- }
-
- private void destroyJava() {
- mCallbackProxy.blockMessages();
- if (mAccessibilityInjector != null) {
- mAccessibilityInjector.destroy();
- mAccessibilityInjector = null;
- }
- if (mWebViewCore != null) {
- // Tell WebViewCore to destroy itself
- synchronized (this) {
- WebViewCore webViewCore = mWebViewCore;
- mWebViewCore = null; // prevent using partial webViewCore
- webViewCore.destroy();
- }
- // Remove any pending messages that might not be serviced yet.
- mPrivateHandler.removeCallbacksAndMessages(null);
- }
- }
-
- private void destroyNative() {
- if (mNativeClass == 0) return;
- int nptr = mNativeClass;
- mNativeClass = 0;
- if (Thread.currentThread() == mPrivateHandler.getLooper().getThread()) {
- // We are on the main thread and can safely delete
- nativeDestroy(nptr);
- } else {
- mPrivateHandler.post(new DestroyNativeRunnable(nptr));
- }
- }
-
- private static class DestroyNativeRunnable implements Runnable {
-
- private int mNativePtr;
-
- public DestroyNativeRunnable(int nativePtr) {
- mNativePtr = nativePtr;
- }
-
- @Override
- public void run() {
- // nativeDestroy also does a stopGL()
- nativeDestroy(mNativePtr);
- }
-
- }
-
- /**
- * See {@link WebView#enablePlatformNotifications()}
- */
- @Deprecated
- public static void enablePlatformNotifications() {
- synchronized (WebViewClassic.class) {
- sNotificationsEnabled = true;
- Context context = JniUtil.getContext();
- if (context != null)
- setupProxyListener(context);
- }
- }
-
- /**
- * See {@link WebView#disablePlatformNotifications()}
- */
- @Deprecated
- public static void disablePlatformNotifications() {
- synchronized (WebViewClassic.class) {
- sNotificationsEnabled = false;
- Context context = JniUtil.getContext();
- if (context != null)
- disableProxyListener(context);
- }
- }
-
- /**
- * Sets JavaScript engine flags.
- *
- * @param flags JS engine flags in a String
- *
- * This is an implementation detail.
- */
- public void setJsFlags(String flags) {
- mWebViewCore.sendMessage(EventHub.SET_JS_FLAGS, flags);
- }
-
- /**
- * See {@link WebView#setNetworkAvailable(boolean)}
- */
- @Override
- public void setNetworkAvailable(boolean networkUp) {
- mWebViewCore.sendMessage(EventHub.SET_NETWORK_STATE,
- networkUp ? 1 : 0, 0);
- }
-
- /**
- * Inform WebView about the current network type.
- */
- public void setNetworkType(String type, String subtype) {
- Map<String, String> map = new HashMap<String, String>();
- map.put("type", type);
- map.put("subtype", subtype);
- mWebViewCore.sendMessage(EventHub.SET_NETWORK_TYPE, map);
- }
-
- /**
- * See {@link WebView#saveState(Bundle)}
- */
- @Override
- public WebBackForwardList saveState(Bundle outState) {
- if (outState == null) {
- return null;
- }
- // We grab a copy of the back/forward list because a client of WebView
- // may have invalidated the history list by calling clearHistory.
- WebBackForwardListClassic list = copyBackForwardList();
- final int currentIndex = list.getCurrentIndex();
- final int size = list.getSize();
- // We should fail saving the state if the list is empty or the index is
- // not in a valid range.
- if (currentIndex < 0 || currentIndex >= size || size == 0) {
- return null;
- }
- outState.putInt("index", currentIndex);
- // FIXME: This should just be a byte[][] instead of ArrayList but
- // Parcel.java does not have the code to handle multi-dimensional
- // arrays.
- ArrayList<byte[]> history = new ArrayList<byte[]>(size);
- for (int i = 0; i < size; i++) {
- WebHistoryItemClassic item = list.getItemAtIndex(i);
- if (null == item) {
- // FIXME: this shouldn't happen
- // need to determine how item got set to null
- Log.w(LOGTAG, "saveState: Unexpected null history item.");
- return null;
- }
- byte[] data = item.getFlattenedData();
- if (data == null) {
- // It would be very odd to not have any data for a given history
- // item. And we will fail to rebuild the history list without
- // flattened data.
- return null;
- }
- history.add(data);
- }
- outState.putSerializable("history", history);
- if (mCertificate != null) {
- outState.putBundle("certificate",
- SslCertificate.saveState(mCertificate));
- }
- outState.putBoolean("privateBrowsingEnabled", isPrivateBrowsingEnabled());
- mZoomManager.saveZoomState(outState);
- return list;
- }
-
- /**
- * See {@link WebView#savePicture(Bundle, File)}
- */
- @Override
- @Deprecated
- public boolean savePicture(Bundle b, final File dest) {
- if (dest == null || b == null) {
- return false;
- }
- final Picture p = capturePicture();
- // Use a temporary file while writing to ensure the destination file
- // contains valid data.
- final File temp = new File(dest.getPath() + ".writing");
- new Thread(new Runnable() {
- @Override
- public void run() {
- FileOutputStream out = null;
- try {
- out = new FileOutputStream(temp);
- p.writeToStream(out);
- // Writing the picture succeeded, rename the temporary file
- // to the destination.
- temp.renameTo(dest);
- } catch (Exception e) {
- // too late to do anything about it.
- } finally {
- if (out != null) {
- try {
- out.close();
- } catch (Exception e) {
- // Can't do anything about that
- }
- }
- temp.delete();
- }
- }
- }).start();
- // now update the bundle
- b.putInt("scrollX", getScrollX());
- b.putInt("scrollY", getScrollY());
- mZoomManager.saveZoomState(b);
- return true;
- }
-
- private void restoreHistoryPictureFields(Picture p, Bundle b) {
- int sx = b.getInt("scrollX", 0);
- int sy = b.getInt("scrollY", 0);
-
- mDrawHistory = true;
- mHistoryPicture = p;
-
- setScrollXRaw(sx);
- setScrollYRaw(sy);
- mZoomManager.restoreZoomState(b);
- final float scale = mZoomManager.getScale();
- mHistoryWidth = Math.round(p.getWidth() * scale);
- mHistoryHeight = Math.round(p.getHeight() * scale);
-
- invalidate();
- }
-
- /**
- * See {@link WebView#restorePicture(Bundle, File)};
- */
- @Override
- @Deprecated
- public boolean restorePicture(Bundle b, File src) {
- if (src == null || b == null) {
- return false;
- }
- if (!src.exists()) {
- return false;
- }
- try {
- final FileInputStream in = new FileInputStream(src);
- final Bundle copy = new Bundle(b);
- new Thread(new Runnable() {
- @Override
- public void run() {
- try {
- final Picture p = Picture.createFromStream(in);
- if (p != null) {
- // Post a runnable on the main thread to update the
- // history picture fields.
- mPrivateHandler.post(new Runnable() {
- @Override
- public void run() {
- restoreHistoryPictureFields(p, copy);
- }
- });
- }
- } finally {
- try {
- in.close();
- } catch (Exception e) {
- // Nothing we can do now.
- }
- }
- }
- }).start();
- } catch (FileNotFoundException e){
- e.printStackTrace();
- }
- return true;
- }
-
- /**
- * Saves the view data to the output stream. The output is highly
- * version specific, and may not be able to be loaded by newer versions
- * of WebView.
- * @param stream The {@link OutputStream} to save to
- * @param callback The {@link ValueCallback} to call with the result
- */
- public void saveViewState(OutputStream stream, ValueCallback<Boolean> callback) {
- if (mWebViewCore == null) {
- callback.onReceiveValue(false);
- return;
- }
- mWebViewCore.sendMessageAtFrontOfQueue(EventHub.SAVE_VIEW_STATE,
- new WebViewCore.SaveViewStateRequest(stream, callback));
- }
-
- /**
- * Loads the view data from the input stream. See
- * {@link #saveViewState(java.io.OutputStream, ValueCallback)} for more information.
- * @param stream The {@link InputStream} to load from
- */
- public void loadViewState(InputStream stream) {
- mBlockWebkitViewMessages = true;
- new AsyncTask<InputStream, Void, DrawData>() {
-
- @Override
- protected DrawData doInBackground(InputStream... params) {
- try {
- return ViewStateSerializer.deserializeViewState(params[0]);
- } catch (IOException e) {
- return null;
- }
- }
-
- @Override
- protected void onPostExecute(DrawData draw) {
- if (draw == null) {
- Log.e(LOGTAG, "Failed to load view state!");
- return;
- }
- int viewWidth = getViewWidth();
- int viewHeight = getViewHeightWithTitle() - getTitleHeight();
- draw.mViewSize = new Point(viewWidth, viewHeight);
- draw.mViewState.mDefaultScale = getDefaultZoomScale();
- mLoadedPicture = draw;
- setNewPicture(mLoadedPicture, true);
- mLoadedPicture.mViewState = null;
- }
-
- }.execute(stream);
- }
-
- /**
- * Clears the view state set with {@link #loadViewState(InputStream)}.
- * This WebView will then switch to showing the content from webkit
- */
- public void clearViewState() {
- mBlockWebkitViewMessages = false;
- mLoadedPicture = null;
- invalidate();
- }
-
- /**
- * See {@link WebView#restoreState(Bundle)}
- */
- @Override
- public WebBackForwardList restoreState(Bundle inState) {
- WebBackForwardListClassic returnList = null;
- if (inState == null) {
- return returnList;
- }
- if (inState.containsKey("index") && inState.containsKey("history")) {
- mCertificate = SslCertificate.restoreState(
- inState.getBundle("certificate"));
-
- final WebBackForwardListClassic list = mCallbackProxy.getBackForwardList();
- final int index = inState.getInt("index");
- // We can't use a clone of the list because we need to modify the
- // shared copy, so synchronize instead to prevent concurrent
- // modifications.
- synchronized (list) {
- final List<byte[]> history =
- (List<byte[]>) inState.getSerializable("history");
- final int size = history.size();
- // Check the index bounds so we don't crash in native code while
- // restoring the history index.
- if (index < 0 || index >= size) {
- return null;
- }
- for (int i = 0; i < size; i++) {
- byte[] data = history.remove(0);
- if (data == null) {
- // If we somehow have null data, we cannot reconstruct
- // the item and thus our history list cannot be rebuilt.
- return null;
- }
- WebHistoryItem item = new WebHistoryItemClassic(data);
- list.addHistoryItem(item);
- }
- // Grab the most recent copy to return to the caller.
- returnList = copyBackForwardList();
- // Update the copy to have the correct index.
- returnList.setCurrentIndex(index);
- }
- // Restore private browsing setting.
- if (inState.getBoolean("privateBrowsingEnabled")) {
- getSettings().setPrivateBrowsingEnabled(true);
- }
- mZoomManager.restoreZoomState(inState);
- // Remove all pending messages because we are restoring previous
- // state.
- mWebViewCore.removeMessages();
- if (isAccessibilityInjectionEnabled()) {
- getAccessibilityInjector().addAccessibilityApisIfNecessary();
- }
- // Send a restore state message.
- mWebViewCore.sendMessage(EventHub.RESTORE_STATE, index);
- }
- return returnList;
- }
-
- /**
- * See {@link WebView#loadUrl(String, Map)}
- */
- @Override
- public void loadUrl(String url, Map<String, String> additionalHttpHeaders) {
- loadUrlImpl(url, additionalHttpHeaders);
- }
-
- private void loadUrlImpl(String url, Map<String, String> extraHeaders) {
- switchOutDrawHistory();
- WebViewCore.GetUrlData arg = new WebViewCore.GetUrlData();
- arg.mUrl = url;
- arg.mExtraHeaders = extraHeaders;
- mWebViewCore.sendMessage(EventHub.LOAD_URL, arg);
- clearHelpers();
- }
-
- /**
- * See {@link WebView#loadUrl(String)}
- */
- @Override
- public void loadUrl(String url) {
- loadUrlImpl(url);
- }
-
- private void loadUrlImpl(String url) {
- if (url == null) {
- return;
- }
- loadUrlImpl(url, null);
- }
-
- /**
- * See {@link WebView#postUrl(String, byte[])}
- */
- @Override
- public void postUrl(String url, byte[] postData) {
- if (URLUtil.isNetworkUrl(url)) {
- switchOutDrawHistory();
- WebViewCore.PostUrlData arg = new WebViewCore.PostUrlData();
- arg.mUrl = url;
- arg.mPostData = postData;
- mWebViewCore.sendMessage(EventHub.POST_URL, arg);
- clearHelpers();
- } else {
- loadUrlImpl(url);
- }
- }
-
- /**
- * See {@link WebView#loadData(String, String, String)}
- */
- @Override
- public void loadData(String data, String mimeType, String encoding) {
- loadDataImpl(data, mimeType, encoding);
- }
-
- private void loadDataImpl(String data, String mimeType, String encoding) {
- StringBuilder dataUrl = new StringBuilder("data:");
- dataUrl.append(mimeType);
- if ("base64".equals(encoding)) {
- dataUrl.append(";base64");
- }
- dataUrl.append(",");
- dataUrl.append(data);
- loadUrlImpl(dataUrl.toString());
- }
-
- /**
- * See {@link WebView#loadDataWithBaseURL(String, String, String, String, String)}
- */
- @Override
- public void loadDataWithBaseURL(String baseUrl, String data,
- String mimeType, String encoding, String historyUrl) {
-
- if (baseUrl != null && baseUrl.toLowerCase().startsWith("data:")) {
- loadDataImpl(data, mimeType, encoding);
- return;
- }
- switchOutDrawHistory();
- WebViewCore.BaseUrlData arg = new WebViewCore.BaseUrlData();
- arg.mBaseUrl = baseUrl;
- arg.mData = data;
- arg.mMimeType = mimeType;
- arg.mEncoding = encoding;
- arg.mHistoryUrl = historyUrl;
- mWebViewCore.sendMessage(EventHub.LOAD_DATA, arg);
- clearHelpers();
- }
-
- @Override
- public void evaluateJavaScript(String script, ValueCallback<String> resultCallback) {
- // K-only API not implemented in WebViewClassic.
- throw new IllegalStateException("This API not supported on Android 4.3 and earlier");
- }
-
- /**
- * See {@link WebView#saveWebArchive(String)}
- */
- @Override
- public void saveWebArchive(String filename) {
- saveWebArchiveImpl(filename, false, null);
- }
-
- /* package */ static class SaveWebArchiveMessage {
- SaveWebArchiveMessage (String basename, boolean autoname, ValueCallback<String> callback) {
- mBasename = basename;
- mAutoname = autoname;
- mCallback = callback;
- }
-
- /* package */ final String mBasename;
- /* package */ final boolean mAutoname;
- /* package */ final ValueCallback<String> mCallback;
- /* package */ String mResultFile;
- }
-
- /**
- * See {@link WebView#saveWebArchive(String, boolean, ValueCallback)}
- */
- @Override
- public void saveWebArchive(String basename, boolean autoname, ValueCallback<String> callback) {
- saveWebArchiveImpl(basename, autoname, callback);
- }
-
- private void saveWebArchiveImpl(String basename, boolean autoname,
- ValueCallback<String> callback) {
- mWebViewCore.sendMessage(EventHub.SAVE_WEBARCHIVE,
- new SaveWebArchiveMessage(basename, autoname, callback));
- }
-
- /**
- * See {@link WebView#stopLoading()}
- */
- @Override
- public void stopLoading() {
- // TODO: should we clear all the messages in the queue before sending
- // STOP_LOADING?
- switchOutDrawHistory();
- mWebViewCore.sendMessage(EventHub.STOP_LOADING);
- }
-
- /**
- * See {@link WebView#reload()}
- */
- @Override
- public void reload() {
- clearHelpers();
- switchOutDrawHistory();
- mWebViewCore.sendMessage(EventHub.RELOAD);
- }
-
- /**
- * See {@link WebView#canGoBack()}
- */
- @Override
- public boolean canGoBack() {
- WebBackForwardListClassic l = mCallbackProxy.getBackForwardList();
- synchronized (l) {
- if (l.getClearPending()) {
- return false;
- } else {
- return l.getCurrentIndex() > 0;
- }
- }
- }
-
- /**
- * See {@link WebView#goBack()}
- */
- @Override
- public void goBack() {
- goBackOrForwardImpl(-1);
- }
-
- /**
- * See {@link WebView#canGoForward()}
- */
- @Override
- public boolean canGoForward() {
- WebBackForwardListClassic l = mCallbackProxy.getBackForwardList();
- synchronized (l) {
- if (l.getClearPending()) {
- return false;
- } else {
- return l.getCurrentIndex() < l.getSize() - 1;
- }
- }
- }
-
- /**
- * See {@link WebView#goForward()}
- */
- @Override
- public void goForward() {
- goBackOrForwardImpl(1);
- }
-
- /**
- * See {@link WebView#canGoBackOrForward(int)}
- */
- @Override
- public boolean canGoBackOrForward(int steps) {
- WebBackForwardListClassic l = mCallbackProxy.getBackForwardList();
- synchronized (l) {
- if (l.getClearPending()) {
- return false;
- } else {
- int newIndex = l.getCurrentIndex() + steps;
- return newIndex >= 0 && newIndex < l.getSize();
- }
- }
- }
-
- /**
- * See {@link WebView#goBackOrForward(int)}
- */
- @Override
- public void goBackOrForward(int steps) {
- goBackOrForwardImpl(steps);
- }
-
- private void goBackOrForwardImpl(int steps) {
- goBackOrForward(steps, false);
- }
-
- private void goBackOrForward(int steps, boolean ignoreSnapshot) {
- if (steps != 0) {
- clearHelpers();
- mWebViewCore.sendMessage(EventHub.GO_BACK_FORWARD, steps,
- ignoreSnapshot ? 1 : 0);
- }
- }
-
- /**
- * See {@link WebView#isPrivateBrowsingEnabled()}
- */
- @Override
- public boolean isPrivateBrowsingEnabled() {
- WebSettingsClassic settings = getSettings();
- return (settings != null) ? settings.isPrivateBrowsingEnabled() : false;
- }
-
- private void startPrivateBrowsing() {
- getSettings().setPrivateBrowsingEnabled(true);
- }
-
- private boolean extendScroll(int y) {
- int finalY = mScroller.getFinalY();
- int newY = pinLocY(finalY + y);
- if (newY == finalY) return false;
- mScroller.setFinalY(newY);
- mScroller.extendDuration(computeDuration(0, y));
- return true;
- }
-
- /**
- * See {@link WebView#pageUp(boolean)}
- */
- @Override
- public boolean pageUp(boolean top) {
- if (mNativeClass == 0) {
- return false;
- }
- if (top) {
- // go to the top of the document
- return pinScrollTo(getScrollX(), 0, true, 0);
- }
- // Page up
- int h = getHeight();
- int y;
- if (h > 2 * PAGE_SCROLL_OVERLAP) {
- y = -h + PAGE_SCROLL_OVERLAP;
- } else {
- y = -h / 2;
- }
- return mScroller.isFinished() ? pinScrollBy(0, y, true, 0)
- : extendScroll(y);
- }
-
- /**
- * See {@link WebView#pageDown(boolean)}
- */
- @Override
- public boolean pageDown(boolean bottom) {
- if (mNativeClass == 0) {
- return false;
- }
- if (bottom) {
- return pinScrollTo(getScrollX(), computeRealVerticalScrollRange(), true, 0);
- }
- // Page down.
- int h = getHeight();
- int y;
- if (h > 2 * PAGE_SCROLL_OVERLAP) {
- y = h - PAGE_SCROLL_OVERLAP;
- } else {
- y = h / 2;
- }
- return mScroller.isFinished() ? pinScrollBy(0, y, true, 0)
- : extendScroll(y);
- }
-
- /**
- * See {@link WebView#clearView()}
- */
- @Override
- public void clearView() {
- mContentWidth = 0;
- mContentHeight = 0;
- setBaseLayer(0, false, false);
- mWebViewCore.sendMessage(EventHub.CLEAR_CONTENT);
- }
-
- /**
- * See {@link WebView#capturePicture()}
- */
- @Override
- public Picture capturePicture() {
- if (mNativeClass == 0) return null;
- Picture result = new Picture();
- nativeCopyBaseContentToPicture(result);
- return result;
- }
-
- /**
- * See {@link WebView#createPrintDocumentAdapter()}
- */
- @Override
- public PrintDocumentAdapter createPrintDocumentAdapter() {
- // K-only API not implemented in WebViewClassic.
- throw new IllegalStateException("This API not supported on Android 4.3 and earlier");
- }
-
- /**
- * See {@link WebView#getScale()}
- */
- @Override
- public float getScale() {
- return mZoomManager.getScale();
- }
-
- /**
- * Compute the reading level scale of the WebView
- * @param scale The current scale.
- * @return The reading level scale.
- */
- /*package*/ float computeReadingLevelScale(float scale) {
- return mZoomManager.computeReadingLevelScale(scale);
- }
-
- /**
- * See {@link WebView#setInitialScale(int)}
- */
- @Override
- public void setInitialScale(int scaleInPercent) {
- mZoomManager.setInitialScaleInPercent(scaleInPercent);
- }
-
- /**
- * See {@link WebView#invokeZoomPicker()}
- */
- @Override
- public void invokeZoomPicker() {
- if (!getSettings().supportZoom()) {
- Log.w(LOGTAG, "This WebView doesn't support zoom.");
- return;
- }
- clearHelpers();
- mZoomManager.invokeZoomPicker();
- }
-
- /**
- * See {@link WebView#getHitTestResult()}
- */
- @Override
- public HitTestResult getHitTestResult() {
- return mInitialHitTestResult;
- }
-
- // No left edge for double-tap zoom alignment
- static final int NO_LEFTEDGE = -1;
-
- int getBlockLeftEdge(int x, int y, float readingScale) {
- float invReadingScale = 1.0f / readingScale;
- int readingWidth = (int) (getViewWidth() * invReadingScale);
- int left = NO_LEFTEDGE;
- if (mFocusedNode != null) {
- final int length = mFocusedNode.mEnclosingParentRects.length;
- for (int i = 0; i < length; i++) {
- Rect rect = mFocusedNode.mEnclosingParentRects[i];
- if (rect.width() < mFocusedNode.mHitTestSlop) {
- // ignore bounding boxes that are too small
- continue;
- } else if (rect.width() > readingWidth) {
- // stop when bounding box doesn't fit the screen width
- // at reading scale
- break;
- }
-
- left = rect.left;
- }
- }
-
- return left;
- }
-
- /**
- * See {@link WebView#requestFocusNodeHref(Message)}
- */
- @Override
- public void requestFocusNodeHref(Message hrefMsg) {
- if (hrefMsg == null) {
- return;
- }
- int contentX = viewToContentX(mLastTouchX + getScrollX());
- int contentY = viewToContentY(mLastTouchY + getScrollY());
- if (mFocusedNode != null && mFocusedNode.mHitTestX == contentX
- && mFocusedNode.mHitTestY == contentY) {
- hrefMsg.getData().putString(FocusNodeHref.URL, mFocusedNode.mLinkUrl);
- hrefMsg.getData().putString(FocusNodeHref.TITLE, mFocusedNode.mAnchorText);
- hrefMsg.getData().putString(FocusNodeHref.SRC, mFocusedNode.mImageUrl);
- hrefMsg.sendToTarget();
- return;
- }
- mWebViewCore.sendMessage(EventHub.REQUEST_CURSOR_HREF,
- contentX, contentY, hrefMsg);
- }
-
- /**
- * See {@link WebView#requestImageRef(Message)}
- */
- @Override
- public void requestImageRef(Message msg) {
- if (0 == mNativeClass) return; // client isn't initialized
- String url = mFocusedNode != null ? mFocusedNode.mImageUrl : null;
- Bundle data = msg.getData();
- data.putString("url", url);
- msg.setData(data);
- msg.sendToTarget();
- }
-
- static int pinLoc(int x, int viewMax, int docMax) {
-// Log.d(LOGTAG, "-- pinLoc " + x + " " + viewMax + " " + docMax);
- if (docMax < viewMax) { // the doc has room on the sides for "blank"
- // pin the short document to the top/left of the screen
- x = 0;
-// Log.d(LOGTAG, "--- center " + x);
- } else if (x < 0) {
- x = 0;
-// Log.d(LOGTAG, "--- zero");
- } else if (x + viewMax > docMax) {
- x = docMax - viewMax;
-// Log.d(LOGTAG, "--- pin " + x);
- }
- return x;
- }
-
- // Expects x in view coordinates
- int pinLocX(int x) {
- if (mInOverScrollMode) return x;
- return pinLoc(x, getViewWidth(), computeRealHorizontalScrollRange());
- }
-
- // Expects y in view coordinates
- int pinLocY(int y) {
- if (mInOverScrollMode) return y;
- return pinLoc(y, getViewHeightWithTitle(),
- computeRealVerticalScrollRange() + getTitleHeight());
- }
-
- /**
- * Given a distance in view space, convert it to content space. Note: this
- * does not reflect translation, just scaling, so this should not be called
- * with coordinates, but should be called for dimensions like width or
- * height.
- */
- private int viewToContentDimension(int d) {
- return Math.round(d * mZoomManager.getInvScale());
- }
-
- /**
- * Given an x coordinate in view space, convert it to content space. Also
- * may be used for absolute heights.
- */
- /*package*/ int viewToContentX(int x) {
- return viewToContentDimension(x);
- }
-
- /**
- * Given a y coordinate in view space, convert it to content space.
- * Takes into account the height of the title bar if there is one
- * embedded into the WebView.
- */
- /*package*/ int viewToContentY(int y) {
- return viewToContentDimension(y - getTitleHeight());
- }
-
- /**
- * Given a x coordinate in view space, convert it to content space.
- * Returns the result as a float.
- */
- private float viewToContentXf(int x) {
- return x * mZoomManager.getInvScale();
- }
-
- /**
- * Given a y coordinate in view space, convert it to content space.
- * Takes into account the height of the title bar if there is one
- * embedded into the WebView. Returns the result as a float.
- */
- private float viewToContentYf(int y) {
- return (y - getTitleHeight()) * mZoomManager.getInvScale();
- }
-
- /**
- * Given a distance in content space, convert it to view space. Note: this
- * does not reflect translation, just scaling, so this should not be called
- * with coordinates, but should be called for dimensions like width or
- * height.
- */
- /*package*/ int contentToViewDimension(int d) {
- return Math.round(d * mZoomManager.getScale());
- }
-
- /**
- * Given an x coordinate in content space, convert it to view
- * space.
- */
- /*package*/ int contentToViewX(int x) {
- return contentToViewDimension(x);
- }
-
- /**
- * Given a y coordinate in content space, convert it to view
- * space. Takes into account the height of the title bar.
- */
- /*package*/ int contentToViewY(int y) {
- return contentToViewDimension(y) + getTitleHeight();
- }
-
- private Rect contentToViewRect(Rect x) {
- return new Rect(contentToViewX(x.left), contentToViewY(x.top),
- contentToViewX(x.right), contentToViewY(x.bottom));
- }
-
- /* To invalidate a rectangle in content coordinates, we need to transform
- the rect into view coordinates, so we can then call invalidate(...).
-
- Normally, we would just call contentToView[XY](...), which eventually
- calls Math.round(coordinate * mActualScale). However, for invalidates,
- we need to account for the slop that occurs with antialiasing. To
- address that, we are a little more liberal in the size of the rect that
- we invalidate.
-
- This liberal calculation calls floor() for the top/left, and ceil() for
- the bottom/right coordinates. This catches the possible extra pixels of
- antialiasing that we might have missed with just round().
- */
-
- // Called by JNI to invalidate the View, given rectangle coordinates in
- // content space
- private void viewInvalidate(int l, int t, int r, int b) {
- final float scale = mZoomManager.getScale();
- final int dy = getTitleHeight();
- mWebView.invalidate((int)Math.floor(l * scale),
- (int)Math.floor(t * scale) + dy,
- (int)Math.ceil(r * scale),
- (int)Math.ceil(b * scale) + dy);
- }
-
- // Called by JNI to invalidate the View after a delay, given rectangle
- // coordinates in content space
- private void viewInvalidateDelayed(long delay, int l, int t, int r, int b) {
- final float scale = mZoomManager.getScale();
- final int dy = getTitleHeight();
- mWebView.postInvalidateDelayed(delay,
- (int)Math.floor(l * scale),
- (int)Math.floor(t * scale) + dy,
- (int)Math.ceil(r * scale),
- (int)Math.ceil(b * scale) + dy);
- }
-
- private void invalidateContentRect(Rect r) {
- viewInvalidate(r.left, r.top, r.right, r.bottom);
- }
-
- // stop the scroll animation, and don't let a subsequent fling add
- // to the existing velocity
- private void abortAnimation() {
- mScroller.abortAnimation();
- mLastVelocity = 0;
- }
-
- /* call from webcoreview.draw(), so we're still executing in the UI thread
- */
- private void recordNewContentSize(int w, int h, boolean updateLayout) {
-
- // premature data from webkit, ignore
- if ((w | h) == 0) {
- invalidate();
- return;
- }
-
- // don't abort a scroll animation if we didn't change anything
- if (mContentWidth != w || mContentHeight != h) {
- // record new dimensions
- mContentWidth = w;
- mContentHeight = h;
- // If history Picture is drawn, don't update scroll. They will be
- // updated when we get out of that mode.
- if (!mDrawHistory) {
- // repin our scroll, taking into account the new content size
- updateScrollCoordinates(pinLocX(getScrollX()), pinLocY(getScrollY()));
- if (!mScroller.isFinished()) {
- // We are in the middle of a scroll. Repin the final scroll
- // position.
- mScroller.setFinalX(pinLocX(mScroller.getFinalX()));
- mScroller.setFinalY(pinLocY(mScroller.getFinalY()));
- }
- }
- invalidate();
- }
- contentSizeChanged(updateLayout);
- }
-
- // Used to avoid sending many visible rect messages.
- private Rect mLastVisibleRectSent = new Rect();
- private Rect mLastGlobalRect = new Rect();
- private Rect mVisibleRect = new Rect();
- private Rect mGlobalVisibleRect = new Rect();
- private Point mScrollOffset = new Point();
-
- Rect sendOurVisibleRect() {
- if (mZoomManager.isPreventingWebkitUpdates()) return mLastVisibleRectSent;
- calcOurContentVisibleRect(mVisibleRect);
- // Rect.equals() checks for null input.
- if (!mVisibleRect.equals(mLastVisibleRectSent)) {
- if (!mBlockWebkitViewMessages) {
- mScrollOffset.set(mVisibleRect.left, mVisibleRect.top);
- mWebViewCore.removeMessages(EventHub.SET_SCROLL_OFFSET);
- mWebViewCore.sendMessage(EventHub.SET_SCROLL_OFFSET,
- mSendScrollEvent ? 1 : 0, mScrollOffset);
- }
- mLastVisibleRectSent.set(mVisibleRect);
- mPrivateHandler.removeMessages(SWITCH_TO_LONGPRESS);
- }
- if (mWebView.getGlobalVisibleRect(mGlobalVisibleRect)
- && !mGlobalVisibleRect.equals(mLastGlobalRect)) {
- if (DebugFlags.WEB_VIEW) {
- Log.v(LOGTAG, "sendOurVisibleRect=(" + mGlobalVisibleRect.left + ","
- + mGlobalVisibleRect.top + ",r=" + mGlobalVisibleRect.right + ",b="
- + mGlobalVisibleRect.bottom);
- }
- // TODO: the global offset is only used by windowRect()
- // in ChromeClientAndroid ; other clients such as touch
- // and mouse events could return view + screen relative points.
- if (!mBlockWebkitViewMessages) {
- mWebViewCore.sendMessage(EventHub.SET_GLOBAL_BOUNDS, mGlobalVisibleRect);
- }
- mLastGlobalRect.set(mGlobalVisibleRect);
- }
- return mVisibleRect;
- }
-
- private Point mGlobalVisibleOffset = new Point();
- // Sets r to be the visible rectangle of our webview in view coordinates
- private void calcOurVisibleRect(Rect r) {
- mWebView.getGlobalVisibleRect(r, mGlobalVisibleOffset);
- r.offset(-mGlobalVisibleOffset.x, -mGlobalVisibleOffset.y);
- }
-
- // Sets r to be our visible rectangle in content coordinates
- private void calcOurContentVisibleRect(Rect r) {
- calcOurVisibleRect(r);
- r.left = viewToContentX(r.left);
- // viewToContentY will remove the total height of the title bar. Add
- // the visible height back in to account for the fact that if the title
- // bar is partially visible, the part of the visible rect which is
- // displaying our content is displaced by that amount.
- r.top = viewToContentY(r.top + getVisibleTitleHeightImpl());
- r.right = viewToContentX(r.right);
- r.bottom = viewToContentY(r.bottom);
- }
-
- private final Rect mTempContentVisibleRect = new Rect();
- // Sets r to be our visible rectangle in content coordinates. We use this
- // method on the native side to compute the position of the fixed layers.
- // Uses floating coordinates (necessary to correctly place elements when
- // the scale factor is not 1)
- private void calcOurContentVisibleRectF(RectF r) {
- calcOurVisibleRect(mTempContentVisibleRect);
- viewToContentVisibleRect(r, mTempContentVisibleRect);
- }
-
- static class ViewSizeData {
- int mWidth;
- int mHeight;
- float mHeightWidthRatio;
- int mActualViewHeight;
- int mTextWrapWidth;
- int mAnchorX;
- int mAnchorY;
- float mScale;
- boolean mIgnoreHeight;
- }
-
- /**
- * Compute unzoomed width and height, and if they differ from the last
- * values we sent, send them to webkit (to be used as new viewport)
- *
- * @param force ensures that the message is sent to webkit even if the width
- * or height has not changed since the last message
- *
- * @return true if new values were sent
- */
- boolean sendViewSizeZoom(boolean force) {
- if (mBlockWebkitViewMessages) return false;
- if (mZoomManager.isPreventingWebkitUpdates()) return false;
-
- int viewWidth = getViewWidth();
- int newWidth = Math.round(viewWidth * mZoomManager.getInvScale());
- // This height could be fixed and be different from actual visible height.
- int viewHeight = getViewHeightWithTitle() - getTitleHeight();
- int newHeight = Math.round(viewHeight * mZoomManager.getInvScale());
- // Make the ratio more accurate than (newHeight / newWidth), since the
- // latter both are calculated and rounded.
- float heightWidthRatio = (float) viewHeight / viewWidth;
- /*
- * Because the native side may have already done a layout before the
- * View system was able to measure us, we have to send a height of 0 to
- * remove excess whitespace when we grow our width. This will trigger a
- * layout and a change in content size. This content size change will
- * mean that contentSizeChanged will either call this method directly or
- * indirectly from onSizeChanged.
- */
- if (newWidth > mLastWidthSent && mWrapContent) {
- newHeight = 0;
- heightWidthRatio = 0;
- }
- // Actual visible content height.
- int actualViewHeight = Math.round(getViewHeight() * mZoomManager.getInvScale());
- // Avoid sending another message if the dimensions have not changed.
- if (newWidth != mLastWidthSent || newHeight != mLastHeightSent || force ||
- actualViewHeight != mLastActualHeightSent) {
- ViewSizeData data = new ViewSizeData();
- data.mWidth = newWidth;
- data.mHeight = newHeight;
- data.mHeightWidthRatio = heightWidthRatio;
- data.mActualViewHeight = actualViewHeight;
- data.mTextWrapWidth = Math.round(viewWidth / mZoomManager.getTextWrapScale());
- data.mScale = mZoomManager.getScale();
- data.mIgnoreHeight = mZoomManager.isFixedLengthAnimationInProgress()
- && !mHeightCanMeasure;
- data.mAnchorX = mZoomManager.getDocumentAnchorX();
- data.mAnchorY = mZoomManager.getDocumentAnchorY();
- mWebViewCore.sendMessage(EventHub.VIEW_SIZE_CHANGED, data);
- mLastWidthSent = newWidth;
- mLastHeightSent = newHeight;
- mLastActualHeightSent = actualViewHeight;
- mZoomManager.clearDocumentAnchor();
- return true;
- }
- return false;
- }
-
- /**
- * Update the double-tap zoom.
- */
- /* package */ void updateDoubleTapZoom(int doubleTapZoom) {
- mZoomManager.updateDoubleTapZoom(doubleTapZoom);
- }
-
- private int computeRealHorizontalScrollRange() {
- if (mDrawHistory) {
- return mHistoryWidth;
- } else {
- // to avoid rounding error caused unnecessary scrollbar, use floor
- return (int) Math.floor(mContentWidth * mZoomManager.getScale());
- }
- }
-
- @Override
- public int computeHorizontalScrollRange() {
- int range = computeRealHorizontalScrollRange();
-
- // Adjust reported range if overscrolled to compress the scroll bars
- final int scrollX = getScrollX();
- final int overscrollRight = computeMaxScrollX();
- if (scrollX < 0) {
- range -= scrollX;
- } else if (scrollX > overscrollRight) {
- range += scrollX - overscrollRight;
- }
-
- return range;
- }
-
- @Override
- public int computeHorizontalScrollOffset() {
- return Math.max(getScrollX(), 0);
- }
-
- private int computeRealVerticalScrollRange() {
- if (mDrawHistory) {
- return mHistoryHeight;
- } else {
- // to avoid rounding error caused unnecessary scrollbar, use floor
- return (int) Math.floor(mContentHeight * mZoomManager.getScale());
- }
- }
-
- @Override
- public int computeVerticalScrollRange() {
- int range = computeRealVerticalScrollRange();
-
- // Adjust reported range if overscrolled to compress the scroll bars
- final int scrollY = getScrollY();
- final int overscrollBottom = computeMaxScrollY();
- if (scrollY < 0) {
- range -= scrollY;
- } else if (scrollY > overscrollBottom) {
- range += scrollY - overscrollBottom;
- }
-
- return range;
- }
-
- @Override
- public int computeVerticalScrollOffset() {
- return Math.max(getScrollY() - getTitleHeight(), 0);
- }
-
- @Override
- public int computeVerticalScrollExtent() {
- return getViewHeight();
- }
-
- @Override
- public void onDrawVerticalScrollBar(Canvas canvas,
- Drawable scrollBar,
- int l, int t, int r, int b) {
- if (getScrollY() < 0) {
- t -= getScrollY();
- }
- scrollBar.setBounds(l, t + getVisibleTitleHeightImpl(), r, b);
- scrollBar.draw(canvas);
- }
-
- @Override
- public void onOverScrolled(int scrollX, int scrollY, boolean clampedX,
- boolean clampedY) {
- // Special-case layer scrolling so that we do not trigger normal scroll
- // updating.
- if (mTouchMode == TOUCH_DRAG_TEXT_MODE) {
- scrollEditText(scrollX, scrollY);
- return;
- }
- if (mTouchMode == TOUCH_DRAG_LAYER_MODE) {
- scrollLayerTo(scrollX, scrollY);
- animateHandles();
- return;
- }
- mInOverScrollMode = false;
- int maxX = computeMaxScrollX();
- int maxY = computeMaxScrollY();
- if (maxX == 0) {
- // do not over scroll x if the page just fits the screen
- scrollX = pinLocX(scrollX);
- } else if (scrollX < 0 || scrollX > maxX) {
- mInOverScrollMode = true;
- }
- if (scrollY < 0 || scrollY > maxY) {
- mInOverScrollMode = true;
- }
-
- int oldX = getScrollX();
- int oldY = getScrollY();
-
- mWebViewPrivate.super_scrollTo(scrollX, scrollY);
-
- animateHandles();
-
- if (mOverScrollGlow != null) {
- mOverScrollGlow.pullGlow(getScrollX(), getScrollY(), oldX, oldY, maxX, maxY);
- }
- }
-
- /**
- * See {@link WebView#getUrl()}
- */
- @Override
- public String getUrl() {
- WebHistoryItem h = mCallbackProxy.getBackForwardList().getCurrentItem();
- return h != null ? h.getUrl() : null;
- }
-
- /**
- * See {@link WebView#getOriginalUrl()}
- */
- @Override
- public String getOriginalUrl() {
- WebHistoryItem h = mCallbackProxy.getBackForwardList().getCurrentItem();
- return h != null ? h.getOriginalUrl() : null;
- }
-
- /**
- * See {@link WebView#getTitle()}
- */
- @Override
- public String getTitle() {
- WebHistoryItem h = mCallbackProxy.getBackForwardList().getCurrentItem();
- return h != null ? h.getTitle() : null;
- }
-
- /**
- * See {@link WebView#getFavicon()}
- */
- @Override
- public Bitmap getFavicon() {
- WebHistoryItem h = mCallbackProxy.getBackForwardList().getCurrentItem();
- return h != null ? h.getFavicon() : null;
- }
-
- /**
- * See {@link WebView#getTouchIconUrl()}
- */
- @Override
- public String getTouchIconUrl() {
- WebHistoryItemClassic h = mCallbackProxy.getBackForwardList().getCurrentItem();
- return h != null ? h.getTouchIconUrl() : null;
- }
-
- /**
- * See {@link WebView#getProgress()}
- */
- @Override
- public int getProgress() {
- return mCallbackProxy.getProgress();
- }
-
- /**
- * See {@link WebView#getContentHeight()}
- */
- @Override
- public int getContentHeight() {
- return mContentHeight;
- }
-
- /**
- * See {@link WebView#getContentWidth()}
- */
- @Override
- public int getContentWidth() {
- return mContentWidth;
- }
-
- public int getPageBackgroundColor() {
- if (mNativeClass == 0) return Color.WHITE;
- return nativeGetBackgroundColor(mNativeClass);
- }
-
- /**
- * See {@link WebView#pauseTimers()}
- */
- @Override
- public void pauseTimers() {
- mWebViewCore.sendMessage(EventHub.PAUSE_TIMERS);
- }
-
- /**
- * See {@link WebView#resumeTimers()}
- */
- @Override
- public void resumeTimers() {
- mWebViewCore.sendMessage(EventHub.RESUME_TIMERS);
- }
-
- /**
- * See {@link WebView#onPause()}
- */
- @Override
- public void onPause() {
- if (!mIsPaused) {
- mIsPaused = true;
- mWebViewCore.sendMessage(EventHub.ON_PAUSE);
- // We want to pause the current playing video when switching out
- // from the current WebView/tab.
- if (mHTML5VideoViewProxy != null) {
- mHTML5VideoViewProxy.pauseAndDispatch();
- }
- if (mNativeClass != 0) {
- nativeSetPauseDrawing(mNativeClass, true);
- }
-
- cancelDialogs();
- WebCoreThreadWatchdog.pause();
- }
- }
-
- @Override
- public void onWindowVisibilityChanged(int visibility) {
- updateDrawingState();
- }
-
- void updateDrawingState() {
- if (mNativeClass == 0 || mIsPaused) return;
- if (mWebView.getWindowVisibility() != View.VISIBLE) {
- nativeSetPauseDrawing(mNativeClass, true);
- } else if (mWebView.getVisibility() != View.VISIBLE) {
- nativeSetPauseDrawing(mNativeClass, true);
- } else {
- nativeSetPauseDrawing(mNativeClass, false);
- }
- }
-
- /**
- * See {@link WebView#onResume()}
- */
- @Override
- public void onResume() {
- if (mIsPaused) {
- mIsPaused = false;
- mWebViewCore.sendMessage(EventHub.ON_RESUME);
- if (mNativeClass != 0) {
- nativeSetPauseDrawing(mNativeClass, false);
- }
- }
- // We get a call to onResume for new WebViews (i.e. mIsPaused will be false). We need
- // to ensure that the Watchdog thread is running for the new WebView, so call
- // it outside the if block above.
- WebCoreThreadWatchdog.resume();
- }
-
- /**
- * See {@link WebView#isPaused()}
- */
- @Override
- public boolean isPaused() {
- return mIsPaused;
- }
-
- /**
- * See {@link WebView#freeMemory()}
- */
- @Override
- public void freeMemory() {
- mWebViewCore.sendMessage(EventHub.FREE_MEMORY);
- }
-
- /**
- * See {@link WebView#clearCache(boolean)}
- */
- @Override
- public void clearCache(boolean includeDiskFiles) {
- // Note: this really needs to be a static method as it clears cache for all
- // WebView. But we need mWebViewCore to send message to WebCore thread, so
- // we can't make this static.
- mWebViewCore.sendMessage(EventHub.CLEAR_CACHE,
- includeDiskFiles ? 1 : 0, 0);
- }
-
- /**
- * See {@link WebView#clearFormData()}
- */
- @Override
- public void clearFormData() {
- if (mAutoCompletePopup != null) {
- mAutoCompletePopup.clearAdapter();
- }
- }
-
- /**
- * See {@link WebView#clearHistory()}
- */
- @Override
- public void clearHistory() {
- mCallbackProxy.getBackForwardList().setClearPending();
- mWebViewCore.sendMessage(EventHub.CLEAR_HISTORY);
- }
-
- /**
- * See {@link WebView#clearSslPreferences()}
- */
- @Override
- public void clearSslPreferences() {
- mWebViewCore.sendMessage(EventHub.CLEAR_SSL_PREF_TABLE);
- }
-
- /**
- * See {@link WebView#copyBackForwardList()}
- */
- @Override
- public WebBackForwardListClassic copyBackForwardList() {
- return mCallbackProxy.getBackForwardList().clone();
- }
-
- /**
- * See {@link WebView#setFindListener(WebView.FindListener)}.
- * @hide
- */
- @Override
- public void setFindListener(WebView.FindListener listener) {
- mFindListener = listener;
- }
-
- /**
- * See {@link WebView#findNext(boolean)}
- */
- @Override
- public void findNext(boolean forward) {
- if (0 == mNativeClass) return; // client isn't initialized
- if (mFindRequest != null) {
- mWebViewCore.sendMessage(EventHub.FIND_NEXT, forward ? 1 : 0, mFindRequest);
- }
- }
-
- /**
- * See {@link WebView#findAll(String)}
- */
- @Override
- public int findAll(String find) {
- return findAllBody(find, false);
- }
-
- @Override
- public void findAllAsync(String find) {
- findAllBody(find, true);
- }
-
- private int findAllBody(String find, boolean isAsync) {
- if (0 == mNativeClass) return 0; // client isn't initialized
- mFindRequest = null;
- if (find == null) return 0;
- mWebViewCore.removeMessages(EventHub.FIND_ALL);
- mFindRequest = new WebViewCore.FindAllRequest(find);
- if (isAsync) {
- mWebViewCore.sendMessage(EventHub.FIND_ALL, mFindRequest);
- return 0; // no need to wait for response
- }
- synchronized(mFindRequest) {
- try {
- mWebViewCore.sendMessageAtFrontOfQueue(EventHub.FIND_ALL, mFindRequest);
- while (mFindRequest.mMatchCount == -1) {
- mFindRequest.wait();
- }
- }
- catch (InterruptedException e) {
- return 0;
- }
- return mFindRequest.mMatchCount;
- }
- }
-
- /**
- * Start an ActionMode for finding text in this WebView. Only works if this
- * WebView is attached to the view system.
- * @param text If non-null, will be the initial text to search for.
- * Otherwise, the last String searched for in this WebView will
- * be used to start.
- * @param showIme If true, show the IME, assuming the user will begin typing.
- * If false and text is non-null, perform a find all.
- * @return boolean True if the find dialog is shown, false otherwise.
- */
- @Override
- public boolean showFindDialog(String text, boolean showIme) {
- FindActionModeCallback callback = new FindActionModeCallback(mContext);
- if (mWebView.getParent() == null || mWebView.startActionMode(callback) == null) {
- // Could not start the action mode, so end Find on page
- return false;
- }
- mCachedOverlappingActionModeHeight = -1;
- mFindCallback = callback;
- setFindIsUp(true);
- mFindCallback.setWebView(getWebView());
- if (showIme) {
- mFindCallback.showSoftInput();
- } else if (text != null) {
- mFindCallback.setText(text);
- mFindCallback.findAll();
- return true;
- }
- if (text == null) {
- text = mFindRequest == null ? null : mFindRequest.mSearchText;
- }
- if (text != null) {
- mFindCallback.setText(text);
- mFindCallback.findAll();
- }
- return true;
- }
-
- /**
- * Keep track of the find callback so that we can remove its titlebar if
- * necessary.
- */
- private FindActionModeCallback mFindCallback;
-
- /**
- * Toggle whether the find dialog is showing, for both native and Java.
- */
- private void setFindIsUp(boolean isUp) {
- mFindIsUp = isUp;
- }
-
- // Used to know whether the find dialog is open. Affects whether
- // or not we draw the highlights for matches.
- private boolean mFindIsUp;
-
- // Keep track of the last find request sent.
- private WebViewCore.FindAllRequest mFindRequest = null;
-
- /**
- * Return the first substring consisting of the address of a physical
- * location. Currently, only addresses in the United States are detected,
- * and consist of:
- * - a house number
- * - a street name
- * - a street type (Road, Circle, etc), either spelled out or abbreviated
- * - a city name
- * - a state or territory, either spelled out or two-letter abbr.
- * - an optional 5 digit or 9 digit zip code.
- *
- * All names must be correctly capitalized, and the zip code, if present,
- * must be valid for the state. The street type must be a standard USPS
- * spelling or abbreviation. The state or territory must also be spelled
- * or abbreviated using USPS standards. The house number may not exceed
- * five digits.
- * @param addr The string to search for addresses.
- *
- * @return the address, or if no address is found, return null.
- */
- public static String findAddress(String addr) {
- return findAddress(addr, false);
- }
-
- /**
- * Return the first substring consisting of the address of a physical
- * location. Currently, only addresses in the United States are detected,
- * and consist of:
- * - a house number
- * - a street name
- * - a street type (Road, Circle, etc), either spelled out or abbreviated
- * - a city name
- * - a state or territory, either spelled out or two-letter abbr.
- * - an optional 5 digit or 9 digit zip code.
- *
- * Names are optionally capitalized, and the zip code, if present,
- * must be valid for the state. The street type must be a standard USPS
- * spelling or abbreviation. The state or territory must also be spelled
- * or abbreviated using USPS standards. The house number may not exceed
- * five digits.
- * @param addr The string to search for addresses.
- * @param caseInsensitive addr Set to true to make search ignore case.
- *
- * @return the address, or if no address is found, return null.
- */
- public static String findAddress(String addr, boolean caseInsensitive) {
- return WebViewCore.nativeFindAddress(addr, caseInsensitive);
- }
-
- /**
- * See {@link WebView#clearMatches()}
- */
- @Override
- public void clearMatches() {
- if (mNativeClass == 0)
- return;
- mWebViewCore.removeMessages(EventHub.FIND_ALL);
- mWebViewCore.sendMessage(EventHub.FIND_ALL, null);
- }
-
-
- /**
- * Called when the find ActionMode ends.
- */
- @Override
- public void notifyFindDialogDismissed() {
- mFindCallback = null;
- mCachedOverlappingActionModeHeight = -1;
- if (mWebViewCore == null) {
- return;
- }
- clearMatches();
- setFindIsUp(false);
- // Now that the dialog has been removed, ensure that we scroll to a
- // location that is not beyond the end of the page.
- pinScrollTo(getScrollX(), getScrollY(), false, 0);
- invalidate();
- }
-
- /**
- * See {@link WebView#documentHasImages(Message)}
- */
- @Override
- public void documentHasImages(Message response) {
- if (response == null) {
- return;
- }
- mWebViewCore.sendMessage(EventHub.DOC_HAS_IMAGES, response);
- }
-
- /**
- * Request the scroller to abort any ongoing animation
- */
- public void stopScroll() {
- mScroller.forceFinished(true);
- mLastVelocity = 0;
- }
-
- @Override
- public void computeScroll() {
- if (mScroller.computeScrollOffset()) {
- int oldX = getScrollX();
- int oldY = getScrollY();
- int x = mScroller.getCurrX();
- int y = mScroller.getCurrY();
- invalidate(); // So we draw again
-
- if (!mScroller.isFinished()) {
- int rangeX = computeMaxScrollX();
- int rangeY = computeMaxScrollY();
- int overflingDistance = mOverflingDistance;
-
- // Use the layer's scroll data if needed.
- if (mTouchMode == TOUCH_DRAG_LAYER_MODE) {
- oldX = mScrollingLayerRect.left;
- oldY = mScrollingLayerRect.top;
- rangeX = mScrollingLayerRect.right;
- rangeY = mScrollingLayerRect.bottom;
- // No overscrolling for layers.
- overflingDistance = 0;
- } else if (mTouchMode == TOUCH_DRAG_TEXT_MODE) {
- oldX = getTextScrollX();
- oldY = getTextScrollY();
- rangeX = getMaxTextScrollX();
- rangeY = getMaxTextScrollY();
- overflingDistance = 0;
- }
-
- mWebViewPrivate.overScrollBy(x - oldX, y - oldY, oldX, oldY,
- rangeX, rangeY,
- overflingDistance, overflingDistance, false);
-
- if (mOverScrollGlow != null) {
- mOverScrollGlow.absorbGlow(x, y, oldX, oldY, rangeX, rangeY);
- }
- } else {
- if (mTouchMode == TOUCH_DRAG_LAYER_MODE) {
- // Update the layer position instead of WebView.
- scrollLayerTo(x, y);
- } else if (mTouchMode == TOUCH_DRAG_TEXT_MODE) {
- scrollEditText(x, y);
- } else {
- setScrollXRaw(x);
- setScrollYRaw(y);
- }
- abortAnimation();
- nativeSetIsScrolling(false);
- if (!mBlockWebkitViewMessages) {
- WebViewCore.resumePriority();
- if (!mSelectingText) {
- WebViewCore.resumeUpdatePicture(mWebViewCore);
- }
- }
- if (oldX != getScrollX() || oldY != getScrollY()) {
- sendOurVisibleRect();
- }
- }
- } else {
- mWebViewPrivate.super_computeScroll();
- }
- }
-
- private void scrollLayerTo(int x, int y) {
- int dx = mScrollingLayerRect.left - x;
- int dy = mScrollingLayerRect.top - y;
- if ((dx == 0 && dy == 0) || mNativeClass == 0) {
- return;
- }
- if (mSelectingText) {
- if (mSelectCursorBaseLayerId == mCurrentScrollingLayerId) {
- mSelectCursorBase.offset(dx, dy);
- mSelectCursorBaseTextQuad.offset(dx, dy);
- }
- if (mSelectCursorExtentLayerId == mCurrentScrollingLayerId) {
- mSelectCursorExtent.offset(dx, dy);
- mSelectCursorExtentTextQuad.offset(dx, dy);
- }
- }
- if (mAutoCompletePopup != null &&
- mCurrentScrollingLayerId == mEditTextLayerId) {
- mEditTextContentBounds.offset(dx, dy);
- mAutoCompletePopup.resetRect();
- }
- nativeScrollLayer(mNativeClass, mCurrentScrollingLayerId, x, y);
- mScrollingLayerRect.left = x;
- mScrollingLayerRect.top = y;
- mWebViewCore.sendMessage(WebViewCore.EventHub.SCROLL_LAYER, mCurrentScrollingLayerId,
- mScrollingLayerRect);
- mWebViewPrivate.onScrollChanged(getScrollX(), getScrollY(), getScrollX(), getScrollY());
- invalidate();
- }
-
- private static int computeDuration(int dx, int dy) {
- int distance = Math.max(Math.abs(dx), Math.abs(dy));
- int duration = distance * 1000 / STD_SPEED;
- return Math.min(duration, MAX_DURATION);
- }
-
- // helper to pin the scrollBy parameters (already in view coordinates)
- // returns true if the scroll was changed
- private boolean pinScrollBy(int dx, int dy, boolean animate, int animationDuration) {
- return pinScrollTo(getScrollX() + dx, getScrollY() + dy, animate, animationDuration);
- }
- // helper to pin the scrollTo parameters (already in view coordinates)
- // returns true if the scroll was changed
- private boolean pinScrollTo(int x, int y, boolean animate, int animationDuration) {
- abortAnimation();
- x = pinLocX(x);
- y = pinLocY(y);
- int dx = x - getScrollX();
- int dy = y - getScrollY();
-
- if ((dx | dy) == 0) {
- return false;
- }
- if (animate) {
- // Log.d(LOGTAG, "startScroll: " + dx + " " + dy);
- mScroller.startScroll(getScrollX(), getScrollY(), dx, dy,
- animationDuration > 0 ? animationDuration : computeDuration(dx, dy));
- invalidate();
- } else {
- mWebView.scrollTo(x, y);
- }
- return true;
- }
-
- // Scale from content to view coordinates, and pin.
- // Also called by jni webview.cpp
- private boolean setContentScrollBy(int cx, int cy, boolean animate) {
- if (mDrawHistory) {
- // disallow WebView to change the scroll position as History Picture
- // is used in the view system.
- // TODO: as we switchOutDrawHistory when trackball or navigation
- // keys are hit, this should be safe. Right?
- return false;
- }
- cx = contentToViewDimension(cx);
- cy = contentToViewDimension(cy);
- if (mHeightCanMeasure) {
- // move our visible rect according to scroll request
- if (cy != 0) {
- Rect tempRect = new Rect();
- calcOurVisibleRect(tempRect);
- tempRect.offset(cx, cy);
- mWebView.requestRectangleOnScreen(tempRect);
- }
- // FIXME: We scroll horizontally no matter what because currently
- // ScrollView and ListView will not scroll horizontally.
- // FIXME: Why do we only scroll horizontally if there is no
- // vertical scroll?
-// Log.d(LOGTAG, "setContentScrollBy cy=" + cy);
- return cy == 0 && cx != 0 && pinScrollBy(cx, 0, animate, 0);
- } else {
- return pinScrollBy(cx, cy, animate, 0);
- }
- }
-
- /**
- * Called by CallbackProxy when the page starts loading.
- * @param url The URL of the page which has started loading.
- */
- /* package */ void onPageStarted(String url) {
- // every time we start a new page, we want to reset the
- // WebView certificate: if the new site is secure, we
- // will reload it and get a new certificate set;
- // if the new site is not secure, the certificate must be
- // null, and that will be the case
- mWebView.setCertificate(null);
-
- if (isAccessibilityInjectionEnabled()) {
- getAccessibilityInjector().onPageStarted(url);
- }
-
- // Don't start out editing.
- mIsEditingText = false;
- }
-
- /**
- * Called by CallbackProxy when the page finishes loading.
- * @param url The URL of the page which has finished loading.
- */
- /* package */ void onPageFinished(String url) {
- mZoomManager.onPageFinished(url);
-
- if (isAccessibilityInjectionEnabled()) {
- getAccessibilityInjector().onPageFinished(url);
- }
- }
-
- // scale from content to view coordinates, and pin
- private void contentScrollTo(int cx, int cy, boolean animate) {
- if (mDrawHistory) {
- // disallow WebView to change the scroll position as History Picture
- // is used in the view system.
- return;
- }
- int vx = contentToViewX(cx);
- int vy = contentToViewY(cy);
- pinScrollTo(vx, vy, animate, 0);
- }
-
- /**
- * These are from webkit, and are in content coordinate system (unzoomed)
- */
- private void contentSizeChanged(boolean updateLayout) {
- // suppress 0,0 since we usually see real dimensions soon after
- // this avoids drawing the prev content in a funny place. If we find a
- // way to consolidate these notifications, this check may become
- // obsolete
- if ((mContentWidth | mContentHeight) == 0) {
- return;
- }
-
- if (mHeightCanMeasure) {
- if (mWebView.getMeasuredHeight() != contentToViewDimension(mContentHeight)
- || updateLayout) {
- mWebView.requestLayout();
- }
- } else if (mWidthCanMeasure) {
- if (mWebView.getMeasuredWidth() != contentToViewDimension(mContentWidth)
- || updateLayout) {
- mWebView.requestLayout();
- }
- } else {
- // If we don't request a layout, try to send our view size to the
- // native side to ensure that WebCore has the correct dimensions.
- sendViewSizeZoom(false);
- }
- }
-
- /**
- * See {@link WebView#setWebViewClient(WebViewClient)}
- */
- @Override
- public void setWebViewClient(WebViewClient client) {
- mCallbackProxy.setWebViewClient(client);
- }
-
- /**
- * Gets the WebViewClient
- * @return the current WebViewClient instance.
- *
- * This is an implementation detail.
- */
- public WebViewClient getWebViewClient() {
- return mCallbackProxy.getWebViewClient();
- }
-
- /**
- * See {@link WebView#setDownloadListener(DownloadListener)}
- */
- @Override
- public void setDownloadListener(DownloadListener listener) {
- mCallbackProxy.setDownloadListener(listener);
- }
-
- /**
- * See {@link WebView#setWebChromeClient(WebChromeClient)}
- */
- @Override
- public void setWebChromeClient(WebChromeClient client) {
- mCallbackProxy.setWebChromeClient(client);
- }
-
- /**
- * Gets the chrome handler.
- * @return the current WebChromeClient instance.
- *
- * This is an implementation detail.
- */
- public WebChromeClient getWebChromeClient() {
- return mCallbackProxy.getWebChromeClient();
- }
-
- /**
- * Set the back/forward list client. This is an implementation of
- * WebBackForwardListClient for handling new items and changes in the
- * history index.
- * @param client An implementation of WebBackForwardListClient.
- */
- public void setWebBackForwardListClient(WebBackForwardListClient client) {
- mCallbackProxy.setWebBackForwardListClient(client);
- }
-
- /**
- * Gets the WebBackForwardListClient.
- */
- public WebBackForwardListClient getWebBackForwardListClient() {
- return mCallbackProxy.getWebBackForwardListClient();
- }
-
- /**
- * See {@link WebView#setPictureListener(PictureListener)}
- */
- @Override
- @Deprecated
- public void setPictureListener(PictureListener listener) {
- mPictureListener = listener;
- }
-
- /* FIXME: Debug only! Remove for SDK! */
- public void externalRepresentation(Message callback) {
- mWebViewCore.sendMessage(EventHub.REQUEST_EXT_REPRESENTATION, callback);
- }
-
- /* FIXME: Debug only! Remove for SDK! */
- public void documentAsText(Message callback) {
- mWebViewCore.sendMessage(EventHub.REQUEST_DOC_AS_TEXT, callback);
- }
-
- /**
- * See {@link WebView#addJavascriptInterface(Object, String)}
- */
- @Override
- public void addJavascriptInterface(Object object, String name) {
-
- if (object == null) {
- return;
- }
- WebViewCore.JSInterfaceData arg = new WebViewCore.JSInterfaceData();
-
- arg.mObject = object;
- arg.mInterfaceName = name;
-
- // starting with JELLY_BEAN_MR1, annotations are mandatory for enabling access to
- // methods that are accessible from JS.
- if (mContext.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
- arg.mRequireAnnotation = true;
- } else {
- arg.mRequireAnnotation = false;
- }
- mWebViewCore.sendMessage(EventHub.ADD_JS_INTERFACE, arg);
- }
-
- /**
- * See {@link WebView#removeJavascriptInterface(String)}
- */
- @Override
- public void removeJavascriptInterface(String interfaceName) {
- if (mWebViewCore != null) {
- WebViewCore.JSInterfaceData arg = new WebViewCore.JSInterfaceData();
- arg.mInterfaceName = interfaceName;
- mWebViewCore.sendMessage(EventHub.REMOVE_JS_INTERFACE, arg);
- }
- }
-
- /**
- * See {@link WebView#getSettings()}
- * Note this returns WebSettingsClassic, a sub-class of WebSettings, which can be used
- * to access extension APIs.
- */
- @Override
- public WebSettingsClassic getSettings() {
- return (mWebViewCore != null) ? mWebViewCore.getSettings() : null;
- }
-
- /**
- * See {@link WebView#getPluginList()}
- */
- @Deprecated
- public static synchronized PluginList getPluginList() {
- return new PluginList();
- }
-
- /**
- * See {@link WebView#refreshPlugins(boolean)}
- */
- @Deprecated
- public void refreshPlugins(boolean reloadOpenPages) {
- }
-
- //-------------------------------------------------------------------------
- // Override View methods
- //-------------------------------------------------------------------------
-
- @Override
- protected void finalize() throws Throwable {
- try {
- destroy();
- } finally {
- super.finalize();
- }
- }
-
- private void drawContent(Canvas canvas) {
- if (mDrawHistory) {
- canvas.scale(mZoomManager.getScale(), mZoomManager.getScale());
- canvas.drawPicture(mHistoryPicture);
- return;
- }
- if (mNativeClass == 0) return;
-
- boolean animateZoom = mZoomManager.isFixedLengthAnimationInProgress();
- boolean animateScroll = ((!mScroller.isFinished()
- || mVelocityTracker != null)
- && (mTouchMode != TOUCH_DRAG_MODE ||
- mHeldMotionless != MOTIONLESS_TRUE));
- if (mTouchMode == TOUCH_DRAG_MODE) {
- if (mHeldMotionless == MOTIONLESS_PENDING) {
- mPrivateHandler.removeMessages(DRAG_HELD_MOTIONLESS);
- mHeldMotionless = MOTIONLESS_FALSE;
- }
- if (mHeldMotionless == MOTIONLESS_FALSE) {
- mPrivateHandler.sendMessageDelayed(mPrivateHandler
- .obtainMessage(DRAG_HELD_MOTIONLESS), MOTIONLESS_TIME);
- mHeldMotionless = MOTIONLESS_PENDING;
- }
- }
- int saveCount = canvas.save();
- if (animateZoom) {
- mZoomManager.animateZoom(canvas);
- } else if (!canvas.isHardwareAccelerated()) {
- canvas.scale(mZoomManager.getScale(), mZoomManager.getScale());
- }
-
- boolean UIAnimationsRunning = false;
- // Currently for each draw we compute the animation values;
- // We may in the future decide to do that independently.
- if (mNativeClass != 0 && !canvas.isHardwareAccelerated()
- && nativeEvaluateLayersAnimations(mNativeClass)) {
- UIAnimationsRunning = true;
- // If we have unfinished (or unstarted) animations,
- // we ask for a repaint. We only need to do this in software
- // rendering (with hardware rendering we already have a different
- // method of requesting a repaint)
- mWebViewCore.sendMessage(EventHub.NOTIFY_ANIMATION_STARTED);
- invalidate();
- }
-
- // decide which adornments to draw
- int extras = DRAW_EXTRAS_NONE;
- if (!mFindIsUp && mShowTextSelectionExtra) {
- extras = DRAW_EXTRAS_SELECTION;
- }
-
- calcOurContentVisibleRectF(mVisibleContentRect);
- if (canvas.isHardwareAccelerated()) {
- Rect invScreenRect = mIsWebViewVisible ? mInvScreenRect : null;
- Rect screenRect = mIsWebViewVisible ? mScreenRect : null;
-
- int functor = nativeCreateDrawGLFunction(mNativeClass, invScreenRect,
- screenRect, mVisibleContentRect, getScale(), extras);
- ((HardwareCanvas) canvas).callDrawGLFunction(functor);
- if (mHardwareAccelSkia != getSettings().getHardwareAccelSkiaEnabled()) {
- mHardwareAccelSkia = getSettings().getHardwareAccelSkiaEnabled();
- nativeUseHardwareAccelSkia(mHardwareAccelSkia);
- }
-
- } else {
- DrawFilter df = null;
- if (mZoomManager.isZoomAnimating() || UIAnimationsRunning) {
- df = mZoomFilter;
- } else if (animateScroll) {
- df = mScrollFilter;
- }
- canvas.setDrawFilter(df);
- nativeDraw(canvas, mVisibleContentRect, mBackgroundColor, extras);
- canvas.setDrawFilter(null);
- }
-
- canvas.restoreToCount(saveCount);
- drawTextSelectionHandles(canvas);
-
- if (extras == DRAW_EXTRAS_CURSOR_RING) {
- if (mTouchMode == TOUCH_SHORTPRESS_START_MODE) {
- mTouchMode = TOUCH_SHORTPRESS_MODE;
- }
- }
- }
-
- /**
- * Draw the background when beyond bounds
- * @param canvas Canvas to draw into
- */
- private void drawOverScrollBackground(Canvas canvas) {
- if (mOverScrollBackground == null) {
- mOverScrollBackground = new Paint();
- Bitmap bm = BitmapFactory.decodeResource(
- mContext.getResources(),
- com.android.internal.R.drawable.status_bar_background);
- mOverScrollBackground.setShader(new BitmapShader(bm,
- Shader.TileMode.REPEAT, Shader.TileMode.REPEAT));
- mOverScrollBorder = new Paint();
- mOverScrollBorder.setStyle(Paint.Style.STROKE);
- mOverScrollBorder.setStrokeWidth(0);
- mOverScrollBorder.setColor(0xffbbbbbb);
- }
-
- int top = 0;
- int right = computeRealHorizontalScrollRange();
- int bottom = top + computeRealVerticalScrollRange();
- // first draw the background and anchor to the top of the view
- canvas.save();
- canvas.translate(getScrollX(), getScrollY());
- canvas.clipRect(-getScrollX(), top - getScrollY(), right - getScrollX(), bottom
- - getScrollY(), Region.Op.DIFFERENCE);
- canvas.drawPaint(mOverScrollBackground);
- canvas.restore();
- // then draw the border
- canvas.drawRect(-1, top - 1, right, bottom, mOverScrollBorder);
- // next clip the region for the content
- canvas.clipRect(0, top, right, bottom);
- }
-
- @Override
- public void onDraw(Canvas canvas) {
- if (inFullScreenMode()) {
- return; // no need to draw anything if we aren't visible.
- }
- // if mNativeClass is 0, the WebView is either destroyed or not
- // initialized. In either case, just draw the background color and return
- if (mNativeClass == 0) {
- canvas.drawColor(mBackgroundColor);
- return;
- }
-
- // if both mContentWidth and mContentHeight are 0, it means there is no
- // valid Picture passed to WebView yet. This can happen when WebView
- // just starts. Draw the background and return.
- if ((mContentWidth | mContentHeight) == 0 && mHistoryPicture == null) {
- canvas.drawColor(mBackgroundColor);
- return;
- }
-
- if (canvas.isHardwareAccelerated()) {
- mZoomManager.setHardwareAccelerated();
- } else {
- mWebViewCore.resumeWebKitDraw();
- }
-
- int saveCount = canvas.save();
- if (mInOverScrollMode && !getSettings()
- .getUseWebViewBackgroundForOverscrollBackground()) {
- drawOverScrollBackground(canvas);
- }
-
- canvas.translate(0, getTitleHeight());
- drawContent(canvas);
- canvas.restoreToCount(saveCount);
-
- if (AUTO_REDRAW_HACK && mAutoRedraw) {
- invalidate();
- }
- mWebViewCore.signalRepaintDone();
-
- if (mOverScrollGlow != null && mOverScrollGlow.drawEdgeGlows(canvas)) {
- invalidate();
- }
-
- if (mFocusTransition != null) {
- mFocusTransition.draw(canvas);
- } else if (shouldDrawHighlightRect()) {
- RegionIterator iter = new RegionIterator(mTouchHighlightRegion);
- Rect r = new Rect();
- while (iter.next(r)) {
- canvas.drawRect(r, mTouchHightlightPaint);
- }
- }
- if (DEBUG_TOUCH_HIGHLIGHT) {
- if (getSettings().getNavDump()) {
- if ((mTouchHighlightX | mTouchHighlightY) != 0) {
- if (mTouchCrossHairColor == null) {
- mTouchCrossHairColor = new Paint();
- mTouchCrossHairColor.setColor(Color.RED);
- }
- canvas.drawLine(mTouchHighlightX - mNavSlop,
- mTouchHighlightY - mNavSlop, mTouchHighlightX
- + mNavSlop + 1, mTouchHighlightY + mNavSlop
- + 1, mTouchCrossHairColor);
- canvas.drawLine(mTouchHighlightX + mNavSlop + 1,
- mTouchHighlightY - mNavSlop, mTouchHighlightX
- - mNavSlop,
- mTouchHighlightY + mNavSlop + 1,
- mTouchCrossHairColor);
- }
- }
- }
- }
-
- private void removeTouchHighlight() {
- setTouchHighlightRects(null);
- }
-
- @Override
- public void setLayoutParams(ViewGroup.LayoutParams params) {
- if (params.height == AbsoluteLayout.LayoutParams.WRAP_CONTENT) {
- mWrapContent = true;
- }
- mWebViewPrivate.super_setLayoutParams(params);
- }
-
- @Override
- public boolean performLongClick() {
- // performLongClick() is the result of a delayed message. If we switch
- // to windows overview, the WebView will be temporarily removed from the
- // view system. In that case, do nothing.
- if (mWebView.getParent() == null) return false;
-
- // A multi-finger gesture can look like a long press; make sure we don't take
- // long press actions if we're scaling.
- final ScaleGestureDetector detector = mZoomManager.getScaleGestureDetector();
- if (detector != null && detector.isInProgress()) {
- return false;
- }
-
- if (mSelectingText) return false; // long click does nothing on selection
- /* if long click brings up a context menu, the super function
- * returns true and we're done. Otherwise, nothing happened when
- * the user clicked. */
- if (mWebViewPrivate.super_performLongClick()) {
- return true;
- }
- /* In the case where the application hasn't already handled the long
- * click action, look for a word under the click. If one is found,
- * animate the text selection into view.
- * FIXME: no animation code yet */
- final boolean isSelecting = selectText();
- if (isSelecting) {
- mWebView.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
- } else if (focusCandidateIsEditableText()) {
- mSelectCallback = new SelectActionModeCallback();
- mSelectCallback.setWebView(this);
- mSelectCallback.setTextSelected(false);
- mWebView.startActionMode(mSelectCallback);
- }
- return isSelecting;
- }
-
- /**
- * Select the word at the last click point.
- *
- * This is an implementation detail.
- */
- public boolean selectText() {
- int x = viewToContentX(mLastTouchX + getScrollX());
- int y = viewToContentY(mLastTouchY + getScrollY());
- return selectText(x, y);
- }
-
- /**
- * Select the word at the indicated content coordinates.
- */
- boolean selectText(int x, int y) {
- if (mWebViewCore == null) {
- return false;
- }
- mWebViewCore.sendMessage(EventHub.SELECT_WORD_AT, x, y);
- return true;
- }
-
- private int mOrientation = Configuration.ORIENTATION_UNDEFINED;
-
- @Override
- public void onConfigurationChanged(Configuration newConfig) {
- mCachedOverlappingActionModeHeight = -1;
- if (mSelectingText && mOrientation != newConfig.orientation) {
- selectionDone();
- }
- mOrientation = newConfig.orientation;
- if (mWebViewCore != null && !mBlockWebkitViewMessages) {
- mWebViewCore.sendMessage(EventHub.CLEAR_CONTENT);
- }
- }
-
- /**
- * Keep track of the Callback so we can end its ActionMode or remove its
- * titlebar.
- */
- private SelectActionModeCallback mSelectCallback;
-
- void setBaseLayer(int layer, boolean showVisualIndicator,
- boolean isPictureAfterFirstLayout) {
- if (mNativeClass == 0)
- return;
- boolean queueFull;
- final int scrollingLayer = (mTouchMode == TOUCH_DRAG_LAYER_MODE)
- ? mCurrentScrollingLayerId : 0;
- queueFull = nativeSetBaseLayer(mNativeClass, layer,
- showVisualIndicator, isPictureAfterFirstLayout,
- scrollingLayer);
-
- if (queueFull) {
- mWebViewCore.pauseWebKitDraw();
- } else {
- mWebViewCore.resumeWebKitDraw();
- }
-
- if (mHTML5VideoViewProxy != null) {
- mHTML5VideoViewProxy.setBaseLayer(layer);
- }
- }
-
- int getBaseLayer() {
- if (mNativeClass == 0) {
- return 0;
- }
- return nativeGetBaseLayer(mNativeClass);
- }
-
- private void onZoomAnimationStart() {
- }
-
- private void onZoomAnimationEnd() {
- mPrivateHandler.sendEmptyMessage(RELOCATE_AUTO_COMPLETE_POPUP);
- }
-
- void onFixedLengthZoomAnimationStart() {
- WebViewCore.pauseUpdatePicture(getWebViewCore());
- onZoomAnimationStart();
- }
-
- void onFixedLengthZoomAnimationEnd() {
- if (!mBlockWebkitViewMessages && !mSelectingText) {
- WebViewCore.resumeUpdatePicture(mWebViewCore);
- }
- onZoomAnimationEnd();
- }
-
- private static final int ZOOM_BITS = Paint.FILTER_BITMAP_FLAG |
- Paint.DITHER_FLAG |
- Paint.SUBPIXEL_TEXT_FLAG;
- private static final int SCROLL_BITS = Paint.FILTER_BITMAP_FLAG |
- Paint.DITHER_FLAG;
-
- private final DrawFilter mZoomFilter =
- new PaintFlagsDrawFilter(ZOOM_BITS, Paint.LINEAR_TEXT_FLAG);
- // If we need to trade better quality for speed, set mScrollFilter to null
- private final DrawFilter mScrollFilter =
- new PaintFlagsDrawFilter(SCROLL_BITS, 0);
-
- private class SelectionHandleAlpha {
- private int mAlpha = 0;
- private int mTargetAlpha = 0;
-
- public void setAlpha(int alpha) {
- mAlpha = alpha;
- // TODO: Use partial invalidate
- invalidate();
- }
-
- public int getAlpha() {
- return mAlpha;
- }
-
- public void setTargetAlpha(int alpha) {
- mTargetAlpha = alpha;
- }
-
- public int getTargetAlpha() {
- return mTargetAlpha;
- }
-
- }
-
- private void startSelectingText() {
- mSelectingText = true;
- mShowTextSelectionExtra = true;
- animateHandles();
- }
-
- private void animateHandle(boolean canShow, ObjectAnimator animator,
- Point selectionPoint, int selectionLayerId,
- SelectionHandleAlpha alpha) {
- boolean isVisible = canShow && mSelectingText
- && ((mSelectionStarted && mSelectDraggingCursor == selectionPoint)
- || isHandleVisible(selectionPoint, selectionLayerId));
- int targetValue = isVisible ? 255 : 0;
- if (targetValue != alpha.getTargetAlpha()) {
- alpha.setTargetAlpha(targetValue);
- animator.setIntValues(targetValue);
- animator.setDuration(SELECTION_HANDLE_ANIMATION_MS);
- animator.start();
- }
- }
-
- private void animateHandles() {
- boolean canShowBase = mSelectingText;
- boolean canShowExtent = mSelectingText && !mIsCaretSelection;
- animateHandle(canShowBase, mBaseHandleAlphaAnimator, mSelectCursorBase,
- mSelectCursorBaseLayerId, mBaseAlpha);
- animateHandle(canShowExtent, mExtentHandleAlphaAnimator,
- mSelectCursorExtent, mSelectCursorExtentLayerId,
- mExtentAlpha);
- }
-
- private void endSelectingText() {
- mSelectingText = false;
- mShowTextSelectionExtra = false;
- animateHandles();
- }
-
- private void ensureSelectionHandles() {
- if (mSelectHandleCenter == null) {
- mSelectHandleCenter = mContext.getResources().getDrawable(
- com.android.internal.R.drawable.text_select_handle_middle).mutate();
- mSelectHandleLeft = mContext.getResources().getDrawable(
- com.android.internal.R.drawable.text_select_handle_left).mutate();
- mSelectHandleRight = mContext.getResources().getDrawable(
- com.android.internal.R.drawable.text_select_handle_right).mutate();
- // All handles have the same height, so we can save effort with
- // this assumption.
- mSelectOffset = new Point(0,
- -mSelectHandleLeft.getIntrinsicHeight());
- }
- }
-
- private void drawHandle(Point point, int handleId, Rect bounds,
- int alpha, Canvas canvas) {
- int offset;
- int width;
- int height;
- Drawable drawable;
- boolean isLeft = nativeIsHandleLeft(mNativeClass, handleId);
- if (isLeft) {
- drawable = mSelectHandleLeft;
- width = mSelectHandleLeft.getIntrinsicWidth();
- height = mSelectHandleLeft.getIntrinsicHeight();
- // Magic formula copied from TextView
- offset = (width * 3) / 4;
- } else {
- drawable = mSelectHandleRight;
- width = mSelectHandleRight.getIntrinsicWidth();
- height = mSelectHandleRight.getIntrinsicHeight();
- // Magic formula copied from TextView
- offset = width / 4;
- }
- int x = contentToViewDimension(point.x);
- int y = contentToViewDimension(point.y);
- bounds.set(x - offset, y, x - offset + width, y + height);
- drawable.setBounds(bounds);
- drawable.setAlpha(alpha);
- drawable.draw(canvas);
- }
-
- private void drawTextSelectionHandles(Canvas canvas) {
- if (mBaseAlpha.getAlpha() == 0 && mExtentAlpha.getAlpha() == 0) {
- return;
- }
- ensureSelectionHandles();
- if (mIsCaretSelection) {
- // Caret handle is centered
- int x = contentToViewDimension(mSelectCursorBase.x) -
- (mSelectHandleCenter.getIntrinsicWidth() / 2);
- int y = contentToViewDimension(mSelectCursorBase.y);
- mSelectHandleBaseBounds.set(x, y,
- x + mSelectHandleCenter.getIntrinsicWidth(),
- y + mSelectHandleCenter.getIntrinsicHeight());
- mSelectHandleCenter.setBounds(mSelectHandleBaseBounds);
- mSelectHandleCenter.setAlpha(mBaseAlpha.getAlpha());
- mSelectHandleCenter.draw(canvas);
- } else {
- drawHandle(mSelectCursorBase, HANDLE_ID_BASE,
- mSelectHandleBaseBounds, mBaseAlpha.getAlpha(), canvas);
- drawHandle(mSelectCursorExtent, HANDLE_ID_EXTENT,
- mSelectHandleExtentBounds, mExtentAlpha.getAlpha(), canvas);
- }
- }
-
- private boolean isHandleVisible(Point selectionPoint, int layerId) {
- boolean isVisible = true;
- if (mIsEditingText) {
- isVisible = mEditTextContentBounds.contains(selectionPoint.x,
- selectionPoint.y);
- }
- if (isVisible) {
- isVisible = nativeIsPointVisible(mNativeClass, layerId,
- selectionPoint.x, selectionPoint.y);
- }
- return isVisible;
- }
-
- /**
- * Takes an int[4] array as an output param with the values being
- * startX, startY, endX, endY
- */
- private void getSelectionHandles(int[] handles) {
- handles[0] = mSelectCursorBase.x;
- handles[1] = mSelectCursorBase.y;
- handles[2] = mSelectCursorExtent.x;
- handles[3] = mSelectCursorExtent.y;
- }
-
- // draw history
- private boolean mDrawHistory = false;
- private Picture mHistoryPicture = null;
- private int mHistoryWidth = 0;
- private int mHistoryHeight = 0;
-
- // Only check the flag, can be called from WebCore thread
- boolean drawHistory() {
- return mDrawHistory;
- }
-
- int getHistoryPictureWidth() {
- return (mHistoryPicture != null) ? mHistoryPicture.getWidth() : 0;
- }
-
- // Should only be called in UI thread
- void switchOutDrawHistory() {
- if (null == mWebViewCore) return; // CallbackProxy may trigger this
- if (mDrawHistory && (getProgress() == 100 || nativeHasContent())) {
- mDrawHistory = false;
- mHistoryPicture = null;
- invalidate();
- int oldScrollX = getScrollX();
- int oldScrollY = getScrollY();
- setScrollXRaw(pinLocX(getScrollX()));
- setScrollYRaw(pinLocY(getScrollY()));
- if (oldScrollX != getScrollX() || oldScrollY != getScrollY()) {
- mWebViewPrivate.onScrollChanged(getScrollX(), getScrollY(), oldScrollX, oldScrollY);
- } else {
- sendOurVisibleRect();
- }
- }
- }
-
- /**
- * Delete text from start to end in the focused textfield. If there is no
- * focus, or if start == end, silently fail. If start and end are out of
- * order, swap them.
- * @param start Beginning of selection to delete.
- * @param end End of selection to delete.
- */
- /* package */ void deleteSelection(int start, int end) {
- mTextGeneration++;
- WebViewCore.TextSelectionData data
- = new WebViewCore.TextSelectionData(start, end, 0);
- mWebViewCore.sendMessage(EventHub.DELETE_SELECTION, mTextGeneration, 0,
- data);
- }
-
- /**
- * Set the selection to (start, end) in the focused textfield. If start and
- * end are out of order, swap them.
- * @param start Beginning of selection.
- * @param end End of selection.
- */
- /* package */ void setSelection(int start, int end) {
- if (mWebViewCore != null) {
- mWebViewCore.sendMessage(EventHub.SET_SELECTION, start, end);
- }
- }
-
- @Override
- public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
- if (mInputConnection == null) {
- mInputConnection = new WebViewInputConnection();
- mAutoCompletePopup = new AutoCompletePopup(this, mInputConnection);
- }
- mInputConnection.setupEditorInfo(outAttrs);
- return mInputConnection;
- }
-
- private void relocateAutoCompletePopup() {
- if (mAutoCompletePopup != null) {
- mAutoCompletePopup.resetRect();
- mAutoCompletePopup.setText(mInputConnection.getEditable());
- }
- }
-
- /**
- * Called in response to a message from webkit telling us that the soft
- * keyboard should be launched.
- */
- private void displaySoftKeyboard(boolean isTextView) {
- InputMethodManager imm = (InputMethodManager)
- mContext.getSystemService(Context.INPUT_METHOD_SERVICE);
-
- // bring it back to the default level scale so that user can enter text
- boolean zoom = mZoomManager.getScale() < mZoomManager.getDefaultScale();
- if (zoom) {
- mZoomManager.setZoomCenter(mLastTouchX, mLastTouchY);
- mZoomManager.setZoomScale(mZoomManager.getDefaultScale(), false);
- }
- // Used by plugins and contentEditable.
- // Also used if the navigation cache is out of date, and
- // does not recognize that a textfield is in focus. In that
- // case, use WebView as the targeted view.
- // see http://b/issue?id=2457459
- imm.showSoftInput(mWebView, 0);
- }
-
- // Called by WebKit to instruct the UI to hide the keyboard
- private void hideSoftKeyboard() {
- InputMethodManager imm = InputMethodManager.peekInstance();
- if (imm != null && (imm.isActive(mWebView))) {
- imm.hideSoftInputFromWindow(mWebView.getWindowToken(), 0);
- }
- }
-
- /**
- * Called by AutoCompletePopup to find saved form data associated with the
- * textfield
- * @param name Name of the textfield.
- * @param nodePointer Pointer to the node of the textfield, so it can be
- * compared to the currently focused textfield when the data is
- * retrieved.
- * @param autoFillable true if WebKit has determined this field is part of
- * a form that can be auto filled.
- * @param autoComplete true if the attribute "autocomplete" is set to true
- * on the textfield.
- */
- /* package */ void requestFormData(String name, int nodePointer,
- boolean autoFillable, boolean autoComplete) {
- if (mWebViewCore.getSettings().getSaveFormData()) {
- Message update = mPrivateHandler.obtainMessage(REQUEST_FORM_DATA);
- update.arg1 = nodePointer;
- RequestFormData updater = new RequestFormData(name, getUrl(),
- update, autoFillable, autoComplete);
- Thread t = new Thread(updater);
- t.start();
- }
- }
-
- /*
- * This class requests an Adapter for the AutoCompletePopup which shows past
- * entries stored in the database. It is a Runnable so that it can be done
- * in its own thread, without slowing down the UI.
- */
- private class RequestFormData implements Runnable {
- private String mName;
- private String mUrl;
- private Message mUpdateMessage;
- private boolean mAutoFillable;
- private boolean mAutoComplete;
- private WebSettingsClassic mWebSettings;
-
- public RequestFormData(String name, String url, Message msg,
- boolean autoFillable, boolean autoComplete) {
- mName = name;
- mUrl = WebTextView.urlForAutoCompleteData(url);
- mUpdateMessage = msg;
- mAutoFillable = autoFillable;
- mAutoComplete = autoComplete;
- mWebSettings = getSettings();
- }
-
- @Override
- public void run() {
- ArrayList<String> pastEntries = new ArrayList<String>();
-
- if (mAutoFillable) {
- // Note that code inside the adapter click handler in AutoCompletePopup depends
- // on the AutoFill item being at the top of the drop down list. If you change
- // the order, make sure to do it there too!
- if (mWebSettings != null && mWebSettings.getAutoFillProfile() != null) {
- pastEntries.add(mWebView.getResources().getText(
- com.android.internal.R.string.autofill_this_form).toString() +
- " " +
- mAutoFillData.getPreviewString());
- mAutoCompletePopup.setIsAutoFillProfileSet(true);
- } else {
- // There is no autofill profile set up yet, so add an option that
- // will invite the user to set their profile up.
- pastEntries.add(mWebView.getResources().getText(
- com.android.internal.R.string.setup_autofill).toString());
- mAutoCompletePopup.setIsAutoFillProfileSet(false);
- }
- }
-
- if (mAutoComplete) {
- pastEntries.addAll(mDatabase.getFormData(mUrl, mName));
- }
-
- if (pastEntries.size() > 0) {
- ArrayAdapter<String> adapter = new ArrayAdapter<String>(
- mContext,
- com.android.internal.R.layout.web_text_view_dropdown,
- pastEntries);
- mUpdateMessage.obj = adapter;
- mUpdateMessage.sendToTarget();
- }
- }
- }
-
- /**
- * Dump the display tree to "/sdcard/displayTree.txt"
- *
- * debug only
- */
- public void dumpDisplayTree() {
- nativeDumpDisplayTree(getUrl());
- }
-
- /**
- * Dump the dom tree to adb shell if "toFile" is False, otherwise dump it to
- * "/sdcard/domTree.txt"
- *
- * debug only
- */
- public void dumpDomTree(boolean toFile) {
- mWebViewCore.sendMessage(EventHub.DUMP_DOMTREE, toFile ? 1 : 0, 0);
- }
-
- /**
- * Dump the render tree to adb shell if "toFile" is False, otherwise dump it
- * to "/sdcard/renderTree.txt"
- *
- * debug only
- */
- public void dumpRenderTree(boolean toFile) {
- mWebViewCore.sendMessage(EventHub.DUMP_RENDERTREE, toFile ? 1 : 0, 0);
- }
-
- /**
- * Called by DRT on UI thread, need to proxy to WebCore thread.
- *
- * debug only
- */
- public void setUseMockDeviceOrientation() {
- mWebViewCore.sendMessage(EventHub.SET_USE_MOCK_DEVICE_ORIENTATION);
- }
-
- /**
- * Sets use of the Geolocation mock client. Also resets that client. Called
- * by DRT on UI thread, need to proxy to WebCore thread.
- *
- * debug only
- */
- public void setUseMockGeolocation() {
- mWebViewCore.sendMessage(EventHub.SET_USE_MOCK_GEOLOCATION);
- }
-
- /**
- * Called by DRT on WebCore thread.
- *
- * debug only
- */
- public void setMockGeolocationPosition(double latitude, double longitude, double accuracy) {
- mWebViewCore.setMockGeolocationPosition(latitude, longitude, accuracy);
- }
-
- /**
- * Called by DRT on WebCore thread.
- *
- * debug only
- */
- public void setMockGeolocationError(int code, String message) {
- mWebViewCore.setMockGeolocationError(code, message);
- }
-
- /**
- * Called by DRT on WebCore thread.
- *
- * debug only
- */
- public void setMockGeolocationPermission(boolean allow) {
- mWebViewCore.setMockGeolocationPermission(allow);
- }
-
- /**
- * Called by DRT on WebCore thread.
- *
- * debug only
- */
- public void setMockDeviceOrientation(boolean canProvideAlpha, double alpha,
- boolean canProvideBeta, double beta, boolean canProvideGamma, double gamma) {
- mWebViewCore.setMockDeviceOrientation(canProvideAlpha, alpha, canProvideBeta, beta,
- canProvideGamma, gamma);
- }
-
- // This is used to determine long press with the center key. Does not
- // affect long press with the trackball/touch.
- private boolean mGotCenterDown = false;
-
- @Override
- public boolean onKeyMultiple(int keyCode, int repeatCount, KeyEvent event) {
- if (mBlockWebkitViewMessages) {
- return false;
- }
- // send complex characters to webkit for use by JS and plugins
- if (keyCode == KeyEvent.KEYCODE_UNKNOWN && event.getCharacters() != null) {
- // pass the key to DOM
- sendBatchableInputMessage(EventHub.KEY_DOWN, 0, 0, event);
- sendBatchableInputMessage(EventHub.KEY_UP, 0, 0, event);
- // return true as DOM handles the key
- return true;
- }
- return false;
- }
-
- private boolean isEnterActionKey(int keyCode) {
- return keyCode == KeyEvent.KEYCODE_DPAD_CENTER
- || keyCode == KeyEvent.KEYCODE_ENTER
- || keyCode == KeyEvent.KEYCODE_NUMPAD_ENTER;
- }
-
- public boolean onKeyPreIme(int keyCode, KeyEvent event) {
- if (mAutoCompletePopup != null) {
- return mAutoCompletePopup.onKeyPreIme(keyCode, event);
- }
- return false;
- }
-
- @Override
- public boolean onKeyDown(int keyCode, KeyEvent event) {
- if (DebugFlags.WEB_VIEW) {
- Log.v(LOGTAG, "keyDown at " + System.currentTimeMillis()
- + "keyCode=" + keyCode
- + ", " + event + ", unicode=" + event.getUnicodeChar());
- }
- if (mIsCaretSelection) {
- selectionDone();
- }
- if (mBlockWebkitViewMessages) {
- return false;
- }
-
- // don't implement accelerator keys here; defer to host application
- if (event.isCtrlPressed()) {
- return false;
- }
-
- if (mNativeClass == 0) {
- return false;
- }
-
- // do this hack up front, so it always works, regardless of touch-mode
- if (AUTO_REDRAW_HACK && (keyCode == KeyEvent.KEYCODE_CALL)) {
- mAutoRedraw = !mAutoRedraw;
- if (mAutoRedraw) {
- invalidate();
- }
- return true;
- }
-
- // Bubble up the key event if
- // 1. it is a system key; or
- // 2. the host application wants to handle it;
- if (event.isSystem()
- || mCallbackProxy.uiOverrideKeyEvent(event)) {
- return false;
- }
-
- // See if the accessibility injector needs to handle this event.
- if (isAccessibilityInjectionEnabled()
- && getAccessibilityInjector().handleKeyEventIfNecessary(event)) {
- return true;
- }
-
- if (keyCode == KeyEvent.KEYCODE_PAGE_UP) {
- if (event.hasNoModifiers()) {
- pageUp(false);
- return true;
- } else if (event.hasModifiers(KeyEvent.META_ALT_ON)) {
- pageUp(true);
- return true;
- }
- }
-
- if (keyCode == KeyEvent.KEYCODE_PAGE_DOWN) {
- if (event.hasNoModifiers()) {
- pageDown(false);
- return true;
- } else if (event.hasModifiers(KeyEvent.META_ALT_ON)) {
- pageDown(true);
- return true;
- }
- }
-
- if (keyCode == KeyEvent.KEYCODE_MOVE_HOME && event.hasNoModifiers()) {
- pageUp(true);
- return true;
- }
-
- if (keyCode == KeyEvent.KEYCODE_MOVE_END && event.hasNoModifiers()) {
- pageDown(true);
- return true;
- }
-
- if (keyCode >= KeyEvent.KEYCODE_DPAD_UP
- && keyCode <= KeyEvent.KEYCODE_DPAD_RIGHT) {
- switchOutDrawHistory();
- }
-
- if (isEnterActionKey(keyCode)) {
- switchOutDrawHistory();
- if (event.getRepeatCount() == 0) {
- if (mSelectingText) {
- return true; // discard press if copy in progress
- }
- mGotCenterDown = true;
- mPrivateHandler.sendMessageDelayed(mPrivateHandler
- .obtainMessage(LONG_PRESS_CENTER), LONG_PRESS_TIMEOUT);
- }
- }
-
- if (getSettings().getNavDump()) {
- switch (keyCode) {
- case KeyEvent.KEYCODE_4:
- dumpDisplayTree();
- break;
- case KeyEvent.KEYCODE_5:
- case KeyEvent.KEYCODE_6:
- dumpDomTree(keyCode == KeyEvent.KEYCODE_5);
- break;
- case KeyEvent.KEYCODE_7:
- case KeyEvent.KEYCODE_8:
- dumpRenderTree(keyCode == KeyEvent.KEYCODE_7);
- break;
- }
- }
-
- // pass the key to DOM
- sendKeyEvent(event);
- // return true as DOM handles the key
- return true;
- }
-
- @Override
- public boolean onKeyUp(int keyCode, KeyEvent event) {
- if (DebugFlags.WEB_VIEW) {
- Log.v(LOGTAG, "keyUp at " + System.currentTimeMillis()
- + ", " + event + ", unicode=" + event.getUnicodeChar());
- }
- if (mBlockWebkitViewMessages) {
- return false;
- }
-
- if (mNativeClass == 0) {
- return false;
- }
-
- // special CALL handling when cursor node's href is "tel:XXX"
- if (keyCode == KeyEvent.KEYCODE_CALL
- && mInitialHitTestResult != null
- && mInitialHitTestResult.getType() == HitTestResult.PHONE_TYPE) {
- String text = mInitialHitTestResult.getExtra();
- Intent intent = new Intent(Intent.ACTION_DIAL, Uri.parse(text));
- mContext.startActivity(intent);
- return true;
- }
-
- // Bubble up the key event if
- // 1. it is a system key; or
- // 2. the host application wants to handle it;
- if (event.isSystem()
- || mCallbackProxy.uiOverrideKeyEvent(event)) {
- return false;
- }
-
- // See if the accessibility injector needs to handle this event.
- if (isAccessibilityInjectionEnabled()
- && getAccessibilityInjector().handleKeyEventIfNecessary(event)) {
- return true;
- }
-
- if (isEnterActionKey(keyCode)) {
- // remove the long press message first
- mPrivateHandler.removeMessages(LONG_PRESS_CENTER);
- mGotCenterDown = false;
-
- if (mSelectingText) {
- copySelection();
- selectionDone();
- return true; // discard press if copy in progress
- }
- }
-
- // pass the key to DOM
- sendKeyEvent(event);
- // return true as DOM handles the key
- return true;
- }
-
- private boolean startSelectActionMode() {
- mSelectCallback = new SelectActionModeCallback();
- mSelectCallback.setTextSelected(!mIsCaretSelection);
- mSelectCallback.setWebView(this);
- if (mWebView.startActionMode(mSelectCallback) == null) {
- // There is no ActionMode, so do not allow the user to modify a
- // selection.
- selectionDone();
- return false;
- }
- mWebView.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
- return true;
- }
-
- private void showPasteWindow() {
- ClipboardManager cm = (ClipboardManager)(mContext
- .getSystemService(Context.CLIPBOARD_SERVICE));
- if (cm.hasPrimaryClip()) {
- Point cursorPoint = new Point(contentToViewX(mSelectCursorBase.x),
- contentToViewY(mSelectCursorBase.y));
- Point cursorTop = calculateBaseCaretTop();
- cursorTop.set(contentToViewX(cursorTop.x),
- contentToViewY(cursorTop.y));
-
- int[] location = new int[2];
- mWebView.getLocationInWindow(location);
- int offsetX = location[0] - getScrollX();
- int offsetY = location[1] - getScrollY();
- cursorPoint.offset(offsetX, offsetY);
- cursorTop.offset(offsetX, offsetY);
- if (mPasteWindow == null) {
- mPasteWindow = new PastePopupWindow();
- }
- mPasteWindow.show(cursorPoint, cursorTop, location[0], location[1]);
- }
- }
-
- /**
- * Given segment AB, this finds the point C along AB that is closest to
- * point and then returns it scale along AB. The scale factor is AC/AB.
- *
- * @param x The x coordinate of the point near segment AB that determines
- * the scale factor.
- * @param y The y coordinate of the point near segment AB that determines
- * the scale factor.
- * @param a The first point of the line segment.
- * @param b The second point of the line segment.
- * @return The scale factor AC/AB, where C is the point on AB closest to
- * point.
- */
- private static float scaleAlongSegment(int x, int y, PointF a, PointF b) {
- // The bottom line of the text box is line AB
- float abX = b.x - a.x;
- float abY = b.y - a.y;
- float ab2 = (abX * abX) + (abY * abY);
-
- // The line from first point in text bounds to bottom is AP
- float apX = x - a.x;
- float apY = y - a.y;
- float abDotAP = (apX * abX) + (apY * abY);
- float scale = abDotAP / ab2;
- return scale;
- }
-
- private Point calculateBaseCaretTop() {
- return calculateCaretTop(mSelectCursorBase, mSelectCursorBaseTextQuad);
- }
-
- private Point calculateDraggingCaretTop() {
- return calculateCaretTop(mSelectDraggingCursor, mSelectDraggingTextQuad);
- }
-
- /**
- * Assuming arbitrary shape of a quadralateral forming text bounds, this
- * calculates the top of a caret.
- */
- private static Point calculateCaretTop(Point base, QuadF quad) {
- float scale = scaleAlongSegment(base.x, base.y, quad.p4, quad.p3);
- int x = Math.round(scaleCoordinate(scale, quad.p1.x, quad.p2.x));
- int y = Math.round(scaleCoordinate(scale, quad.p1.y, quad.p2.y));
- return new Point(x, y);
- }
-
- private void hidePasteButton() {
- if (mPasteWindow != null) {
- mPasteWindow.hide();
- }
- }
-
- private void syncSelectionCursors() {
- mSelectCursorBaseLayerId =
- nativeGetHandleLayerId(mNativeClass, HANDLE_ID_BASE,
- mSelectCursorBase, mSelectCursorBaseTextQuad);
- mSelectCursorExtentLayerId =
- nativeGetHandleLayerId(mNativeClass, HANDLE_ID_EXTENT,
- mSelectCursorExtent, mSelectCursorExtentTextQuad);
- }
-
- private boolean setupWebkitSelect() {
- syncSelectionCursors();
- if (!mIsCaretSelection && !startSelectActionMode()) {
- selectionDone();
- return false;
- }
- startSelectingText();
- mTouchMode = TOUCH_DRAG_MODE;
- return true;
- }
-
- private void updateWebkitSelection(boolean isSnapped) {
- int handleId = (mSelectDraggingCursor == mSelectCursorBase)
- ? HANDLE_ID_BASE : HANDLE_ID_EXTENT;
- int x = mSelectDraggingCursor.x;
- int y = mSelectDraggingCursor.y;
- if (isSnapped) {
- // "center" the cursor in the snapping quad
- Point top = calculateDraggingCaretTop();
- x = Math.round((top.x + x) / 2);
- y = Math.round((top.y + y) / 2);
- }
- mWebViewCore.removeMessages(EventHub.SELECT_TEXT);
- mWebViewCore.sendMessageAtFrontOfQueue(EventHub.SELECT_TEXT,
- x, y, (Integer)handleId);
- }
-
- private void resetCaretTimer() {
- mPrivateHandler.removeMessages(CLEAR_CARET_HANDLE);
- if (!mSelectionStarted) {
- mPrivateHandler.sendEmptyMessageDelayed(CLEAR_CARET_HANDLE,
- CARET_HANDLE_STAMINA_MS);
- }
- }
-
- /**
- * Select all of the text in this WebView.
- *
- * This is an implementation detail.
- */
- public void selectAll() {
- mWebViewCore.sendMessage(EventHub.SELECT_ALL);
- }
-
- /**
- * Called when the selection has been removed.
- */
- void selectionDone() {
- if (mSelectingText) {
- hidePasteButton();
- endSelectingText();
- // finish is idempotent, so this is fine even if selectionDone was
- // called by mSelectCallback.onDestroyActionMode
- if (mSelectCallback != null) {
- mSelectCallback.finish();
- mSelectCallback = null;
- }
- invalidate(); // redraw without selection
- mAutoScrollX = 0;
- mAutoScrollY = 0;
- mSentAutoScrollMessage = false;
- }
- }
-
- /**
- * Copy the selection to the clipboard
- *
- * This is an implementation detail.
- */
- public boolean copySelection() {
- boolean copiedSomething = false;
- String selection = getSelection();
- if (selection != null && selection != "") {
- if (DebugFlags.WEB_VIEW) {
- Log.v(LOGTAG, "copySelection \"" + selection + "\"");
- }
- Toast.makeText(mContext
- , com.android.internal.R.string.text_copied
- , Toast.LENGTH_SHORT).show();
- copiedSomething = true;
- ClipboardManager cm = (ClipboardManager)mContext
- .getSystemService(Context.CLIPBOARD_SERVICE);
- cm.setText(selection);
- int[] handles = new int[4];
- getSelectionHandles(handles);
- mWebViewCore.sendMessage(EventHub.COPY_TEXT, handles);
- }
- invalidate(); // remove selection region and pointer
- return copiedSomething;
- }
-
- /**
- * Cut the selected text into the clipboard
- *
- * This is an implementation detail
- */
- public void cutSelection() {
- copySelection();
- int[] handles = new int[4];
- getSelectionHandles(handles);
- mWebViewCore.sendMessage(EventHub.DELETE_TEXT, handles);
- }
-
- /**
- * Paste text from the clipboard to the cursor position.
- *
- * This is an implementation detail
- */
- public void pasteFromClipboard() {
- ClipboardManager cm = (ClipboardManager)mContext
- .getSystemService(Context.CLIPBOARD_SERVICE);
- ClipData clipData = cm.getPrimaryClip();
- if (clipData != null) {
- ClipData.Item clipItem = clipData.getItemAt(0);
- CharSequence pasteText = clipItem.coerceToText(mContext);
- if (mInputConnection != null) {
- mInputConnection.replaceSelection(pasteText);
- }
- }
- }
-
- /**
- * Returns the currently highlighted text as a string.
- */
- String getSelection() {
- if (mNativeClass == 0) return "";
- return nativeGetSelection();
- }
-
- @Override
- public void onAttachedToWindow() {
- if (mWebView.hasWindowFocus()) setActive(true);
-
- if (isAccessibilityInjectionEnabled()) {
- getAccessibilityInjector().toggleAccessibilityFeedback(true);
- }
-
- updateHwAccelerated();
- }
-
- @Override
- public void onDetachedFromWindow() {
- clearHelpers();
- mZoomManager.dismissZoomPicker();
- if (mWebView.hasWindowFocus()) setActive(false);
-
- if (isAccessibilityInjectionEnabled()) {
- getAccessibilityInjector().toggleAccessibilityFeedback(false);
- }
-
- updateHwAccelerated();
-
- ensureFunctorDetached();
- }
-
- @Override
- public void onVisibilityChanged(View changedView, int visibility) {
- // The zoomManager may be null if the webview is created from XML that
- // specifies the view's visibility param as not visible (see http://b/2794841)
- if (visibility != View.VISIBLE && mZoomManager != null) {
- mZoomManager.dismissZoomPicker();
- }
- updateDrawingState();
- }
-
- void setActive(boolean active) {
- if (active) {
- if (mWebView.hasFocus()) {
- // If our window regained focus, and we have focus, then begin
- // drawing the cursor ring
- mDrawCursorRing = true;
- setFocusControllerActive(true);
- } else {
- mDrawCursorRing = false;
- setFocusControllerActive(false);
- }
- } else {
- if (!mZoomManager.isZoomPickerVisible()) {
- /*
- * The external zoom controls come in their own window, so our
- * window loses focus. Our policy is to not draw the cursor ring
- * if our window is not focused, but this is an exception since
- * the user can still navigate the web page with the zoom
- * controls showing.
- */
- mDrawCursorRing = false;
- }
- mKeysPressed.clear();
- mPrivateHandler.removeMessages(SWITCH_TO_LONGPRESS);
- mTouchMode = TOUCH_DONE_MODE;
- setFocusControllerActive(false);
- }
- invalidate();
- }
-
- // To avoid drawing the cursor ring, and remove the TextView when our window
- // loses focus.
- @Override
- public void onWindowFocusChanged(boolean hasWindowFocus) {
- setActive(hasWindowFocus);
- if (hasWindowFocus) {
- JWebCoreJavaBridge.setActiveWebView(this);
- if (mPictureUpdatePausedForFocusChange) {
- WebViewCore.resumeUpdatePicture(mWebViewCore);
- mPictureUpdatePausedForFocusChange = false;
- }
- } else {
- JWebCoreJavaBridge.removeActiveWebView(this);
- final WebSettings settings = getSettings();
- if (settings != null && settings.enableSmoothTransition() &&
- mWebViewCore != null && !WebViewCore.isUpdatePicturePaused(mWebViewCore)) {
- WebViewCore.pauseUpdatePicture(mWebViewCore);
- mPictureUpdatePausedForFocusChange = true;
- }
- }
- }
-
- /*
- * Pass a message to WebCore Thread, telling the WebCore::Page's
- * FocusController to be "inactive" so that it will
- * not draw the blinking cursor. It gets set to "active" to draw the cursor
- * in WebViewCore.cpp, when the WebCore thread receives key events/clicks.
- */
- /* package */ void setFocusControllerActive(boolean active) {
- if (mWebViewCore == null) return;
- mWebViewCore.sendMessage(EventHub.SET_ACTIVE, active ? 1 : 0, 0);
- // Need to send this message after the document regains focus.
- if (active && mListBoxMessage != null) {
- mWebViewCore.sendMessage(mListBoxMessage);
- mListBoxMessage = null;
- }
- }
-
- @Override
- public void onFocusChanged(boolean focused, int direction,
- Rect previouslyFocusedRect) {
- if (DebugFlags.WEB_VIEW) {
- Log.v(LOGTAG, "MT focusChanged " + focused + ", " + direction);
- }
- if (focused) {
- mDrawCursorRing = true;
- setFocusControllerActive(true);
- } else {
- mDrawCursorRing = false;
- setFocusControllerActive(false);
- mKeysPressed.clear();
- }
- if (!mTouchHighlightRegion.isEmpty()) {
- mWebView.invalidate(mTouchHighlightRegion.getBounds());
- }
- }
-
- // updateRectsForGL() happens almost every draw call, in order to avoid creating
- // any object in this code path, we move the local variable out to be a private
- // final member, and we marked them as mTemp*.
- private final Point mTempVisibleRectOffset = new Point();
- private final Rect mTempVisibleRect = new Rect();
-
- void updateRectsForGL() {
- // Use the getGlobalVisibleRect() to get the intersection among the parents
- // visible == false means we're clipped - send a null rect down to indicate that
- // we should not draw
- boolean visible = mWebView.getGlobalVisibleRect(mTempVisibleRect, mTempVisibleRectOffset);
- mInvScreenRect.set(mTempVisibleRect);
- if (visible) {
- // Then need to invert the Y axis, just for GL
- View rootView = mWebView.getRootView();
- int rootViewHeight = rootView.getHeight();
- mScreenRect.set(mInvScreenRect);
- int savedWebViewBottom = mInvScreenRect.bottom;
- mInvScreenRect.bottom = rootViewHeight - mInvScreenRect.top - getVisibleTitleHeightImpl();
- mInvScreenRect.top = rootViewHeight - savedWebViewBottom;
- mIsWebViewVisible = true;
- } else {
- mIsWebViewVisible = false;
- }
-
- mTempVisibleRect.offset(-mTempVisibleRectOffset.x, -mTempVisibleRectOffset.y);
- viewToContentVisibleRect(mVisibleContentRect, mTempVisibleRect);
-
- nativeUpdateDrawGLFunction(mNativeClass, mIsWebViewVisible ? mInvScreenRect : null,
- mIsWebViewVisible ? mScreenRect : null,
- mVisibleContentRect, getScale());
- }
-
- // Input : viewRect, rect in view/screen coordinate.
- // Output: contentRect, rect in content/document coordinate.
- private void viewToContentVisibleRect(RectF contentRect, Rect viewRect) {
- contentRect.left = viewToContentXf(viewRect.left) / mWebView.getScaleX();
- // viewToContentY will remove the total height of the title bar. Add
- // the visible height back in to account for the fact that if the title
- // bar is partially visible, the part of the visible rect which is
- // displaying our content is displaced by that amount.
- contentRect.top = viewToContentYf(viewRect.top + getVisibleTitleHeightImpl())
- / mWebView.getScaleY();
- contentRect.right = viewToContentXf(viewRect.right) / mWebView.getScaleX();
- contentRect.bottom = viewToContentYf(viewRect.bottom) / mWebView.getScaleY();
- }
-
- @Override
- public boolean setFrame(int left, int top, int right, int bottom) {
- boolean changed = mWebViewPrivate.super_setFrame(left, top, right, bottom);
- if (!changed && mHeightCanMeasure) {
- // When mHeightCanMeasure is true, we will set mLastHeightSent to 0
- // in WebViewCore after we get the first layout. We do call
- // requestLayout() when we get contentSizeChanged(). But the View
- // system won't call onSizeChanged if the dimension is not changed.
- // In this case, we need to call sendViewSizeZoom() explicitly to
- // notify the WebKit about the new dimensions.
- sendViewSizeZoom(false);
- }
- updateRectsForGL();
- return changed;
- }
-
- @Override
- public void onSizeChanged(int w, int h, int ow, int oh) {
- // adjust the max viewport width depending on the view dimensions. This
- // is to ensure the scaling is not going insane. So do not shrink it if
- // the view size is temporarily smaller, e.g. when soft keyboard is up.
- int newMaxViewportWidth = (int) (Math.max(w, h) / mZoomManager.getDefaultMinZoomScale());
- if (newMaxViewportWidth > sMaxViewportWidth) {
- sMaxViewportWidth = newMaxViewportWidth;
- }
-
- mZoomManager.onSizeChanged(w, h, ow, oh);
-
- if (mLoadedPicture != null && mDelaySetPicture == null) {
- // Size changes normally result in a new picture
- // Re-set the loaded picture to simulate that
- // However, do not update the base layer as that hasn't changed
- setNewPicture(mLoadedPicture, false);
- }
- if (mIsEditingText) {
- scrollEditIntoView();
- }
- relocateAutoCompletePopup();
- }
-
- /**
- * Scrolls the edit field into view using the minimum scrolling necessary.
- * If the edit field is too large to fit in the visible window, the caret
- * dimensions are used so that at least the caret is visible.
- * A buffer of EDIT_RECT_BUFFER in view pixels is used to offset the
- * edit rectangle to ensure a margin with the edge of the screen.
- */
- private void scrollEditIntoView() {
- Rect visibleRect = new Rect(viewToContentX(getScrollX()),
- viewToContentY(getScrollY()),
- viewToContentX(getScrollX() + getWidth()),
- viewToContentY(getScrollY() + getViewHeightWithTitle()));
- if (visibleRect.contains(mEditTextContentBounds)) {
- return; // no need to scroll
- }
- syncSelectionCursors();
- nativeFindMaxVisibleRect(mNativeClass, mEditTextLayerId, visibleRect);
- final int buffer = Math.max(1, viewToContentDimension(EDIT_RECT_BUFFER));
- Rect showRect = new Rect(
- Math.max(0, mEditTextContentBounds.left - buffer),
- Math.max(0, mEditTextContentBounds.top - buffer),
- mEditTextContentBounds.right + buffer,
- mEditTextContentBounds.bottom + buffer);
- Point caretTop = calculateBaseCaretTop();
- if (visibleRect.width() < mEditTextContentBounds.width()) {
- // The whole edit won't fit in the width, so use the caret rect
- if (mSelectCursorBase.x < caretTop.x) {
- showRect.left = Math.max(0, mSelectCursorBase.x - buffer);
- showRect.right = caretTop.x + buffer;
- } else {
- showRect.left = Math.max(0, caretTop.x - buffer);
- showRect.right = mSelectCursorBase.x + buffer;
- }
- }
- if (visibleRect.height() < mEditTextContentBounds.height()) {
- // The whole edit won't fit in the height, so use the caret rect
- if (mSelectCursorBase.y > caretTop.y) {
- showRect.top = Math.max(0, caretTop.y - buffer);
- showRect.bottom = mSelectCursorBase.y + buffer;
- } else {
- showRect.top = Math.max(0, mSelectCursorBase.y - buffer);
- showRect.bottom = caretTop.y + buffer;
- }
- }
-
- if (visibleRect.contains(showRect)) {
- return; // no need to scroll
- }
-
- int scrollX = viewToContentX(getScrollX());
- if (visibleRect.left > showRect.left) {
- // We are scrolled too far
- scrollX += showRect.left - visibleRect.left;
- } else if (visibleRect.right < showRect.right) {
- // We aren't scrolled enough to include the right
- scrollX += showRect.right - visibleRect.right;
- }
- int scrollY = viewToContentY(getScrollY());
- if (visibleRect.top > showRect.top) {
- scrollY += showRect.top - visibleRect.top;
- } else if (visibleRect.bottom < showRect.bottom) {
- scrollY += showRect.bottom - visibleRect.bottom;
- }
-
- contentScrollTo(scrollX, scrollY, false);
- }
-
- @Override
- public void onScrollChanged(int l, int t, int oldl, int oldt) {
- if (!mInOverScrollMode) {
- sendOurVisibleRect();
- // update WebKit if visible title bar height changed. The logic is same
- // as getVisibleTitleHeightImpl.
- int titleHeight = getTitleHeight();
- if (Math.max(titleHeight - t, 0) != Math.max(titleHeight - oldt, 0)) {
- sendViewSizeZoom(false);
- }
- }
- }
-
- @Override
- public boolean dispatchKeyEvent(KeyEvent event) {
- switch (event.getAction()) {
- case KeyEvent.ACTION_DOWN:
- mKeysPressed.add(Integer.valueOf(event.getKeyCode()));
- break;
- case KeyEvent.ACTION_MULTIPLE:
- // Always accept the action.
- break;
- case KeyEvent.ACTION_UP:
- int location = mKeysPressed.indexOf(Integer.valueOf(event.getKeyCode()));
- if (location == -1) {
- // We did not receive the key down for this key, so do not
- // handle the key up.
- return false;
- } else {
- // We did receive the key down. Handle the key up, and
- // remove it from our pressed keys.
- mKeysPressed.remove(location);
- }
- break;
- default:
- // Accept the action. This should not happen, unless a new
- // action is added to KeyEvent.
- break;
- }
- return mWebViewPrivate.super_dispatchKeyEvent(event);
- }
-
- private static final int SNAP_BOUND = 16;
- private static int sChannelDistance = 16;
- private int mFirstTouchX = -1; // the first touched point
- private int mFirstTouchY = -1;
- private int mDistanceX = 0;
- private int mDistanceY = 0;
-
- private boolean inFullScreenMode() {
- return mFullScreenHolder != null;
- }
-
- private void dismissFullScreenMode() {
- if (inFullScreenMode()) {
- mFullScreenHolder.hide();
- mFullScreenHolder = null;
- invalidate();
- }
- }
-
- void onPinchToZoomAnimationStart() {
- // cancel the single touch handling
- cancelTouch();
- onZoomAnimationStart();
- }
-
- void onPinchToZoomAnimationEnd(ScaleGestureDetector detector) {
- onZoomAnimationEnd();
- // start a drag, TOUCH_PINCH_DRAG, can't use TOUCH_INIT_MODE as
- // it may trigger the unwanted click, can't use TOUCH_DRAG_MODE
- // as it may trigger the unwanted fling.
- mTouchMode = TOUCH_PINCH_DRAG;
- mConfirmMove = true;
- startTouch(detector.getFocusX(), detector.getFocusY(), mLastTouchTime);
- }
-
- // See if there is a layer at x, y and switch to TOUCH_DRAG_LAYER_MODE if a
- // layer is found.
- private void startScrollingLayer(float x, float y) {
- if (mNativeClass == 0)
- return;
-
- int contentX = viewToContentX((int) x + getScrollX());
- int contentY = viewToContentY((int) y + getScrollY());
- mCurrentScrollingLayerId = nativeScrollableLayer(mNativeClass,
- contentX, contentY, mScrollingLayerRect, mScrollingLayerBounds);
- if (mCurrentScrollingLayerId != 0) {
- mTouchMode = TOUCH_DRAG_LAYER_MODE;
- }
- }
-
- // 1/(density * density) used to compute the distance between points.
- // Computed in init().
- private float DRAG_LAYER_INVERSE_DENSITY_SQUARED;
-
- // The distance between two points reported in onTouchEvent scaled by the
- // density of the screen.
- private static final int DRAG_LAYER_FINGER_DISTANCE = 20000;
-
- @Override
- public boolean onHoverEvent(MotionEvent event) {
- if (mNativeClass == 0) {
- return false;
- }
- int x = viewToContentX((int) event.getX() + getScrollX());
- int y = viewToContentY((int) event.getY() + getScrollY());
- mWebViewCore.sendMessage(EventHub.SET_MOVE_MOUSE, x, y);
- mWebViewPrivate.super_onHoverEvent(event);
- return true;
- }
-
- @Override
- public boolean onTouchEvent(MotionEvent ev) {
- if (mNativeClass == 0 || (!mWebView.isClickable() && !mWebView.isLongClickable())) {
- return false;
- }
-
- if (mInputDispatcher == null) {
- return false;
- }
-
- if (mWebView.isFocusable() && mWebView.isFocusableInTouchMode()
- && !mWebView.isFocused()) {
- mWebView.requestFocus();
- }
-
- if (mInputDispatcher.postPointerEvent(ev, getScrollX(),
- getScrollY() - getTitleHeight(), mZoomManager.getInvScale())) {
- mInputDispatcher.dispatchUiEvents();
- return true;
- } else {
- Log.w(LOGTAG, "mInputDispatcher rejected the event!");
- return false;
- }
- }
-
- /*
- * Common code for single touch and multi-touch.
- * (x, y) denotes current focus point, which is the touch point for single touch
- * and the middle point for multi-touch.
- */
- private void handleTouchEventCommon(MotionEvent event, int action, int x, int y) {
- ScaleGestureDetector detector = mZoomManager.getScaleGestureDetector();
-
- long eventTime = event.getEventTime();
-
- // Due to the touch screen edge effect, a touch closer to the edge
- // always snapped to the edge. As getViewWidth() can be different from
- // getWidth() due to the scrollbar, adjusting the point to match
- // getViewWidth(). Same applied to the height.
- x = Math.min(x, getViewWidth() - 1);
- y = Math.min(y, getViewHeightWithTitle() - 1);
-
- int deltaX = mLastTouchX - x;
- int deltaY = mLastTouchY - y;
- int contentX = viewToContentX(x + getScrollX());
- int contentY = viewToContentY(y + getScrollY());
-
- switch (action) {
- case MotionEvent.ACTION_DOWN: {
- mConfirmMove = false;
-
- // Channel Scrolling
- mFirstTouchX = x;
- mFirstTouchY = y;
- mDistanceX = mDistanceY = 0;
-
- if (!mEditTextScroller.isFinished()) {
- mEditTextScroller.abortAnimation();
- }
- if (!mScroller.isFinished()) {
- // stop the current scroll animation, but if this is
- // the start of a fling, allow it to add to the current
- // fling's velocity
- mScroller.abortAnimation();
- mTouchMode = TOUCH_DRAG_START_MODE;
- mConfirmMove = true;
- nativeSetIsScrolling(false);
- } else if (mPrivateHandler.hasMessages(RELEASE_SINGLE_TAP)) {
- mPrivateHandler.removeMessages(RELEASE_SINGLE_TAP);
- removeTouchHighlight();
- if (deltaX * deltaX + deltaY * deltaY < mDoubleTapSlopSquare) {
- mTouchMode = TOUCH_DOUBLE_TAP_MODE;
- } else {
- mTouchMode = TOUCH_INIT_MODE;
- }
- } else { // the normal case
- mTouchMode = TOUCH_INIT_MODE;
- if (mLogEvent && eventTime - mLastTouchUpTime < 1000) {
- EventLog.writeEvent(EventLogTags.BROWSER_DOUBLE_TAP_DURATION,
- (eventTime - mLastTouchUpTime), eventTime);
- }
- mSelectionStarted = false;
- if (mSelectingText) {
- ensureSelectionHandles();
- int shiftedY = y - getTitleHeight() + getScrollY();
- int shiftedX = x + getScrollX();
- if (mSelectHandleBaseBounds.contains(shiftedX, shiftedY)) {
- mSelectionStarted = true;
- mSelectDraggingCursor = mSelectCursorBase;
- mSelectDraggingTextQuad = mSelectCursorBaseTextQuad;
- if (mIsCaretSelection) {
- mPrivateHandler.removeMessages(CLEAR_CARET_HANDLE);
- hidePasteButton();
- }
- } else if (mSelectHandleExtentBounds
- .contains(shiftedX, shiftedY)) {
- mSelectionStarted = true;
- mSelectDraggingCursor = mSelectCursorExtent;
- mSelectDraggingTextQuad = mSelectCursorExtentTextQuad;
- } else if (mIsCaretSelection) {
- selectionDone();
- }
- if (DebugFlags.WEB_VIEW) {
- Log.v(LOGTAG, "select=" + contentX + "," + contentY);
- }
- }
- }
- // Trigger the link
- if (!mSelectingText && (mTouchMode == TOUCH_INIT_MODE
- || mTouchMode == TOUCH_DOUBLE_TAP_MODE)) {
- mPrivateHandler.sendEmptyMessageDelayed(
- SWITCH_TO_SHORTPRESS, TAP_TIMEOUT);
- mPrivateHandler.sendEmptyMessageDelayed(
- SWITCH_TO_LONGPRESS, LONG_PRESS_TIMEOUT);
- }
- startTouch(x, y, eventTime);
- if (mIsEditingText) {
- mTouchInEditText = mEditTextContentBounds
- .contains(contentX, contentY);
- }
- break;
- }
- case MotionEvent.ACTION_MOVE: {
- if (!mConfirmMove && (deltaX * deltaX + deltaY * deltaY)
- >= mTouchSlopSquare) {
- mPrivateHandler.removeMessages(SWITCH_TO_SHORTPRESS);
- mPrivateHandler.removeMessages(SWITCH_TO_LONGPRESS);
- mConfirmMove = true;
- if (mTouchMode == TOUCH_DOUBLE_TAP_MODE) {
- mTouchMode = TOUCH_INIT_MODE;
- }
- removeTouchHighlight();
- }
- if (mSelectingText && mSelectionStarted) {
- if (DebugFlags.WEB_VIEW) {
- Log.v(LOGTAG, "extend=" + contentX + "," + contentY);
- }
- ViewParent parent = mWebView.getParent();
- if (parent != null) {
- parent.requestDisallowInterceptTouchEvent(true);
- }
- if (deltaX != 0 || deltaY != 0) {
- int handleX = contentX +
- viewToContentDimension(mSelectOffset.x);
- int handleY = contentY +
- viewToContentDimension(mSelectOffset.y);
- mSelectDraggingCursor.set(handleX, handleY);
- boolean inCursorText =
- mSelectDraggingTextQuad.containsPoint(handleX, handleY);
- boolean inEditBounds = mEditTextContentBounds
- .contains(handleX, handleY);
- if (mIsEditingText && !inEditBounds) {
- beginScrollEdit();
- } else {
- endScrollEdit();
- }
- boolean snapped = false;
- if (inCursorText || (mIsEditingText && !inEditBounds)) {
- snapDraggingCursor();
- snapped = true;
- }
- updateWebkitSelection(snapped);
- if (!inCursorText && mIsEditingText && inEditBounds) {
- // Visually snap even if we have moved the handle.
- snapDraggingCursor();
- }
- mLastTouchX = x;
- mLastTouchY = y;
- invalidate();
- }
- break;
- }
-
- if (mTouchMode == TOUCH_DONE_MODE) {
- // no dragging during scroll zoom animation, or when prevent
- // default is yes
- break;
- }
- if (mVelocityTracker == null) {
- Log.e(LOGTAG, "Got null mVelocityTracker when "
- + " mTouchMode = " + mTouchMode);
- } else {
- mVelocityTracker.addMovement(event);
- }
-
- if (mTouchMode != TOUCH_DRAG_MODE &&
- mTouchMode != TOUCH_DRAG_LAYER_MODE &&
- mTouchMode != TOUCH_DRAG_TEXT_MODE) {
-
- if (!mConfirmMove) {
- break;
- }
-
- if ((detector == null || !detector.isInProgress())
- && SNAP_NONE == mSnapScrollMode) {
- int ax = Math.abs(x - mFirstTouchX);
- int ay = Math.abs(y - mFirstTouchY);
- if (ax < SNAP_BOUND && ay < SNAP_BOUND) {
- break;
- } else if (ax < SNAP_BOUND) {
- mSnapScrollMode = SNAP_Y;
- } else if (ay < SNAP_BOUND) {
- mSnapScrollMode = SNAP_X;
- }
- }
-
- mTouchMode = TOUCH_DRAG_MODE;
- mLastTouchX = x;
- mLastTouchY = y;
- deltaX = 0;
- deltaY = 0;
-
- startScrollingLayer(x, y);
- startDrag();
- }
-
- // do pan
- boolean keepScrollBarsVisible = false;
- if (deltaX == 0 && deltaY == 0) {
- keepScrollBarsVisible = true;
- } else {
- if (mSnapScrollMode == SNAP_X || mSnapScrollMode == SNAP_Y) {
- mDistanceX += Math.abs(deltaX);
- mDistanceY += Math.abs(deltaY);
- if (mSnapScrollMode == SNAP_X) {
- if (mDistanceY > sChannelDistance) {
- mSnapScrollMode = SNAP_NONE;
- } else if (mDistanceX > sChannelDistance) {
- mDistanceX = mDistanceY = 0;
- }
- } else {
- if (mDistanceX > sChannelDistance) {
- mSnapScrollMode = SNAP_NONE;
- } else if (mDistanceY > sChannelDistance) {
- mDistanceX = mDistanceY = 0;
- }
- }
- }
- if (mSnapScrollMode != SNAP_NONE) {
- if ((mSnapScrollMode & SNAP_X) == SNAP_X) {
- deltaY = 0;
- } else {
- deltaX = 0;
- }
- }
- if (deltaX * deltaX + deltaY * deltaY > mTouchSlopSquare) {
- mHeldMotionless = MOTIONLESS_FALSE;
- } else {
- mHeldMotionless = MOTIONLESS_TRUE;
- keepScrollBarsVisible = true;
- }
-
- mLastTouchTime = eventTime;
- boolean allDrag = doDrag(deltaX, deltaY);
- if (allDrag) {
- mLastTouchX = x;
- mLastTouchY = y;
- } else {
- int contentDeltaX = (int)Math.floor(deltaX * mZoomManager.getInvScale());
- int roundedDeltaX = contentToViewDimension(contentDeltaX);
- int contentDeltaY = (int)Math.floor(deltaY * mZoomManager.getInvScale());
- int roundedDeltaY = contentToViewDimension(contentDeltaY);
- mLastTouchX -= roundedDeltaX;
- mLastTouchY -= roundedDeltaY;
- }
- }
-
- break;
- }
- case MotionEvent.ACTION_UP: {
- mFirstTouchX = mFirstTouchY = -1;
- if (mIsEditingText && mSelectionStarted) {
- endScrollEdit();
- mPrivateHandler.sendEmptyMessageDelayed(SCROLL_HANDLE_INTO_VIEW,
- TEXT_SCROLL_FIRST_SCROLL_MS);
- if (!mConfirmMove && mIsCaretSelection) {
- showPasteWindow();
- stopTouch();
- break;
- }
- }
- mLastTouchUpTime = eventTime;
- if (mSentAutoScrollMessage) {
- mAutoScrollX = mAutoScrollY = 0;
- }
- switch (mTouchMode) {
- case TOUCH_DOUBLE_TAP_MODE: // double tap
- mPrivateHandler.removeMessages(SWITCH_TO_SHORTPRESS);
- mPrivateHandler.removeMessages(SWITCH_TO_LONGPRESS);
- mTouchMode = TOUCH_DONE_MODE;
- break;
- case TOUCH_INIT_MODE: // tap
- case TOUCH_SHORTPRESS_START_MODE:
- case TOUCH_SHORTPRESS_MODE:
- mPrivateHandler.removeMessages(SWITCH_TO_SHORTPRESS);
- mPrivateHandler.removeMessages(SWITCH_TO_LONGPRESS);
- if (!mConfirmMove) {
- if (mSelectingText) {
- // tapping on selection or controls does nothing
- if (!mSelectionStarted) {
- selectionDone();
- }
- break;
- }
- // only trigger double tap if the WebView is
- // scalable
- if (mTouchMode == TOUCH_INIT_MODE
- && (canZoomIn() || canZoomOut())) {
- mPrivateHandler.sendEmptyMessageDelayed(
- RELEASE_SINGLE_TAP, ViewConfiguration
- .getDoubleTapTimeout());
- }
- break;
- }
- case TOUCH_DRAG_MODE:
- case TOUCH_DRAG_LAYER_MODE:
- case TOUCH_DRAG_TEXT_MODE:
- mPrivateHandler.removeMessages(DRAG_HELD_MOTIONLESS);
- // if the user waits a while w/o moving before the
- // up, we don't want to do a fling
- if (eventTime - mLastTouchTime <= MIN_FLING_TIME) {
- if (mVelocityTracker == null) {
- Log.e(LOGTAG, "Got null mVelocityTracker");
- } else {
- mVelocityTracker.addMovement(event);
- }
- // set to MOTIONLESS_IGNORE so that it won't keep
- // removing and sending message in
- // drawCoreAndCursorRing()
- mHeldMotionless = MOTIONLESS_IGNORE;
- doFling();
- break;
- } else {
- if (mScroller.springBack(getScrollX(), getScrollY(), 0,
- computeMaxScrollX(), 0,
- computeMaxScrollY())) {
- invalidate();
- }
- }
- // redraw in high-quality, as we're done dragging
- mHeldMotionless = MOTIONLESS_TRUE;
- invalidate();
- // fall through
- case TOUCH_DRAG_START_MODE:
- // TOUCH_DRAG_START_MODE should not happen for the real
- // device as we almost certain will get a MOVE. But this
- // is possible on emulator.
- mLastVelocity = 0;
- WebViewCore.resumePriority();
- if (!mSelectingText) {
- WebViewCore.resumeUpdatePicture(mWebViewCore);
- }
- break;
- }
- stopTouch();
- break;
- }
- case MotionEvent.ACTION_CANCEL: {
- if (mTouchMode == TOUCH_DRAG_MODE) {
- mScroller.springBack(getScrollX(), getScrollY(), 0,
- computeMaxScrollX(), 0, computeMaxScrollY());
- invalidate();
- }
- cancelTouch();
- break;
- }
- }
- }
-
- /**
- * Returns the text scroll speed in content pixels per millisecond based on
- * the touch location.
- * @param coordinate The x or y touch coordinate in content space
- * @param min The minimum coordinate (x or y) of the edit content bounds
- * @param max The maximum coordinate (x or y) of the edit content bounds
- */
- private static float getTextScrollSpeed(int coordinate, int min, int max) {
- if (coordinate < min) {
- return (coordinate - min) * TEXT_SCROLL_RATE;
- } else if (coordinate >= max) {
- return (coordinate - max + 1) * TEXT_SCROLL_RATE;
- } else {
- return 0.0f;
- }
- }
-
- private static int getSelectionCoordinate(int coordinate, int min, int max) {
- return Math.max(Math.min(coordinate, max), min);
- }
-
- private void beginScrollEdit() {
- if (mLastEditScroll == 0) {
- mLastEditScroll = SystemClock.uptimeMillis() -
- TEXT_SCROLL_FIRST_SCROLL_MS;
- scrollEditWithCursor();
- }
- }
-
- private void scrollDraggedSelectionHandleIntoView() {
- if (mSelectDraggingCursor == null) {
- return;
- }
- int x = mSelectDraggingCursor.x;
- int y = mSelectDraggingCursor.y;
- if (!mEditTextContentBounds.contains(x,y)) {
- int left = Math.min(0, x - mEditTextContentBounds.left - EDIT_RECT_BUFFER);
- int right = Math.max(0, x - mEditTextContentBounds.right + EDIT_RECT_BUFFER);
- int deltaX = left + right;
- int above = Math.min(0, y - mEditTextContentBounds.top - EDIT_RECT_BUFFER);
- int below = Math.max(0, y - mEditTextContentBounds.bottom + EDIT_RECT_BUFFER);
- int deltaY = above + below;
- if (deltaX != 0 || deltaY != 0) {
- int scrollX = getTextScrollX() + deltaX;
- int scrollY = getTextScrollY() + deltaY;
- scrollX = clampBetween(scrollX, 0, getMaxTextScrollX());
- scrollY = clampBetween(scrollY, 0, getMaxTextScrollY());
- scrollEditText(scrollX, scrollY);
- }
- }
- }
-
- private void endScrollEdit() {
- mLastEditScroll = 0;
- }
-
- private static int clampBetween(int value, int min, int max) {
- return Math.max(min, Math.min(value, max));
- }
-
- private static int getTextScrollDelta(float speed, long deltaT) {
- float distance = speed * deltaT;
- int intDistance = (int)Math.floor(distance);
- float probability = distance - intDistance;
- if (Math.random() < probability) {
- intDistance++;
- }
- return intDistance;
- }
- /**
- * Scrolls edit text a distance based on the last touch point,
- * the last scroll time, and the edit text content bounds.
- */
- private void scrollEditWithCursor() {
- if (mLastEditScroll != 0) {
- int x = viewToContentX(mLastTouchX + getScrollX() + mSelectOffset.x);
- float scrollSpeedX = getTextScrollSpeed(x, mEditTextContentBounds.left,
- mEditTextContentBounds.right);
- int y = viewToContentY(mLastTouchY + getScrollY() + mSelectOffset.y);
- float scrollSpeedY = getTextScrollSpeed(y, mEditTextContentBounds.top,
- mEditTextContentBounds.bottom);
- if (scrollSpeedX == 0.0f && scrollSpeedY == 0.0f) {
- endScrollEdit();
- } else {
- long currentTime = SystemClock.uptimeMillis();
- long timeSinceLastUpdate = currentTime - mLastEditScroll;
- int deltaX = getTextScrollDelta(scrollSpeedX, timeSinceLastUpdate);
- int deltaY = getTextScrollDelta(scrollSpeedY, timeSinceLastUpdate);
- int scrollX = getTextScrollX() + deltaX;
- scrollX = clampBetween(scrollX, 0, getMaxTextScrollX());
- int scrollY = getTextScrollY() + deltaY;
- scrollY = clampBetween(scrollY, 0, getMaxTextScrollY());
-
- mLastEditScroll = currentTime;
- if (scrollX == getTextScrollX() && scrollY == getTextScrollY()) {
- // By probability no text scroll this time. Try again later.
- mPrivateHandler.sendEmptyMessageDelayed(SCROLL_EDIT_TEXT,
- TEXT_SCROLL_FIRST_SCROLL_MS);
- } else {
- int selectionX = getSelectionCoordinate(x,
- mEditTextContentBounds.left, mEditTextContentBounds.right);
- int selectionY = getSelectionCoordinate(y,
- mEditTextContentBounds.top, mEditTextContentBounds.bottom);
- int oldX = mSelectDraggingCursor.x;
- int oldY = mSelectDraggingCursor.y;
- mSelectDraggingCursor.set(selectionX, selectionY);
- updateWebkitSelection(false);
- scrollEditText(scrollX, scrollY);
- mSelectDraggingCursor.set(oldX, oldY);
- }
- }
- }
- }
-
- private void startTouch(float x, float y, long eventTime) {
- // Remember where the motion event started
- mStartTouchX = mLastTouchX = Math.round(x);
- mStartTouchY = mLastTouchY = Math.round(y);
- mLastTouchTime = eventTime;
- mVelocityTracker = VelocityTracker.obtain();
- mSnapScrollMode = SNAP_NONE;
- }
-
- private void startDrag() {
- WebViewCore.reducePriority();
- // to get better performance, pause updating the picture
- WebViewCore.pauseUpdatePicture(mWebViewCore);
- nativeSetIsScrolling(true);
-
- if (mHorizontalScrollBarMode != SCROLLBAR_ALWAYSOFF
- || mVerticalScrollBarMode != SCROLLBAR_ALWAYSOFF) {
- mZoomManager.invokeZoomPicker();
- }
- }
-
- private boolean doDrag(int deltaX, int deltaY) {
- boolean allDrag = true;
- if ((deltaX | deltaY) != 0) {
- int oldX = getScrollX();
- int oldY = getScrollY();
- int rangeX = computeMaxScrollX();
- int rangeY = computeMaxScrollY();
- final int contentX = (int)Math.floor(deltaX * mZoomManager.getInvScale());
- final int contentY = (int)Math.floor(deltaY * mZoomManager.getInvScale());
-
- // Assume page scrolling and change below if we're wrong
- mTouchMode = TOUCH_DRAG_MODE;
-
- // Check special scrolling before going to main page scrolling.
- if (mIsEditingText && mTouchInEditText && canTextScroll(deltaX, deltaY)) {
- // Edit text scrolling
- oldX = getTextScrollX();
- rangeX = getMaxTextScrollX();
- deltaX = contentX;
- oldY = getTextScrollY();
- rangeY = getMaxTextScrollY();
- deltaY = contentY;
- mTouchMode = TOUCH_DRAG_TEXT_MODE;
- allDrag = false;
- } else if (mCurrentScrollingLayerId != 0) {
- // Check the scrolling bounds to see if we will actually do any
- // scrolling. The rectangle is in document coordinates.
- final int maxX = mScrollingLayerRect.right;
- final int maxY = mScrollingLayerRect.bottom;
- final int resultX = clampBetween(maxX, 0,
- mScrollingLayerRect.left + contentX);
- final int resultY = clampBetween(maxY, 0,
- mScrollingLayerRect.top + contentY);
-
- if (resultX != mScrollingLayerRect.left
- || resultY != mScrollingLayerRect.top
- || (contentX | contentY) == 0) {
- // In case we switched to dragging the page.
- mTouchMode = TOUCH_DRAG_LAYER_MODE;
- deltaX = contentX;
- deltaY = contentY;
- oldX = mScrollingLayerRect.left;
- oldY = mScrollingLayerRect.top;
- rangeX = maxX;
- rangeY = maxY;
- allDrag = false;
- }
- }
-
- if (mOverScrollGlow != null) {
- mOverScrollGlow.setOverScrollDeltas(deltaX, deltaY);
- }
-
- mWebViewPrivate.overScrollBy(deltaX, deltaY, oldX, oldY,
- rangeX, rangeY,
- mOverscrollDistance, mOverscrollDistance, true);
- if (mOverScrollGlow != null && mOverScrollGlow.isAnimating()) {
- invalidate();
- }
- }
- mZoomManager.keepZoomPickerVisible();
- return allDrag;
- }
-
- private void stopTouch() {
- if (mScroller.isFinished() && !mSelectingText
- && (mTouchMode == TOUCH_DRAG_MODE
- || mTouchMode == TOUCH_DRAG_LAYER_MODE)) {
- WebViewCore.resumePriority();
- WebViewCore.resumeUpdatePicture(mWebViewCore);
- nativeSetIsScrolling(false);
- }
-
- // we also use mVelocityTracker == null to tell us that we are
- // not "moving around", so we can take the slower/prettier
- // mode in the drawing code
- if (mVelocityTracker != null) {
- mVelocityTracker.recycle();
- mVelocityTracker = null;
- }
-
- // Release any pulled glows
- if (mOverScrollGlow != null) {
- mOverScrollGlow.releaseAll();
- }
-
- if (mSelectingText) {
- mSelectionStarted = false;
- syncSelectionCursors();
- if (mIsCaretSelection) {
- resetCaretTimer();
- }
- invalidate();
- }
- }
-
- private void cancelTouch() {
- // we also use mVelocityTracker == null to tell us that we are
- // not "moving around", so we can take the slower/prettier
- // mode in the drawing code
- if (mVelocityTracker != null) {
- mVelocityTracker.recycle();
- mVelocityTracker = null;
- }
-
- if ((mTouchMode == TOUCH_DRAG_MODE
- || mTouchMode == TOUCH_DRAG_LAYER_MODE) && !mSelectingText) {
- WebViewCore.resumePriority();
- WebViewCore.resumeUpdatePicture(mWebViewCore);
- nativeSetIsScrolling(false);
- }
- mPrivateHandler.removeMessages(SWITCH_TO_SHORTPRESS);
- mPrivateHandler.removeMessages(SWITCH_TO_LONGPRESS);
- mPrivateHandler.removeMessages(DRAG_HELD_MOTIONLESS);
- removeTouchHighlight();
- mHeldMotionless = MOTIONLESS_TRUE;
- mTouchMode = TOUCH_DONE_MODE;
- }
-
- private void snapDraggingCursor() {
- float scale = scaleAlongSegment(
- mSelectDraggingCursor.x, mSelectDraggingCursor.y,
- mSelectDraggingTextQuad.p4, mSelectDraggingTextQuad.p3);
- // clamp scale to ensure point is on the bottom segment
- scale = Math.max(0.0f, scale);
- scale = Math.min(scale, 1.0f);
- float newX = scaleCoordinate(scale,
- mSelectDraggingTextQuad.p4.x, mSelectDraggingTextQuad.p3.x);
- float newY = scaleCoordinate(scale,
- mSelectDraggingTextQuad.p4.y, mSelectDraggingTextQuad.p3.y);
- int x = Math.round(newX);
- int y = Math.round(newY);
- if (mIsEditingText) {
- x = clampBetween(x, mEditTextContentBounds.left,
- mEditTextContentBounds.right);
- y = clampBetween(y, mEditTextContentBounds.top,
- mEditTextContentBounds.bottom);
- }
- mSelectDraggingCursor.set(x, y);
- }
-
- private static float scaleCoordinate(float scale, float coord1, float coord2) {
- float diff = coord2 - coord1;
- return coord1 + (scale * diff);
- }
-
- @Override
- public boolean onGenericMotionEvent(MotionEvent event) {
- if ((event.getSource() & InputDevice.SOURCE_CLASS_POINTER) != 0) {
- switch (event.getAction()) {
- case MotionEvent.ACTION_SCROLL: {
- final float vscroll;
- final float hscroll;
- if ((event.getMetaState() & KeyEvent.META_SHIFT_ON) != 0) {
- vscroll = 0;
- hscroll = event.getAxisValue(MotionEvent.AXIS_VSCROLL);
- } else {
- vscroll = -event.getAxisValue(MotionEvent.AXIS_VSCROLL);
- hscroll = event.getAxisValue(MotionEvent.AXIS_HSCROLL);
- }
- if (hscroll != 0 || vscroll != 0) {
- final int vdelta = (int) (vscroll *
- mWebViewPrivate.getVerticalScrollFactor());
- final int hdelta = (int) (hscroll *
- mWebViewPrivate.getHorizontalScrollFactor());
-
- abortAnimation();
- int oldTouchMode = mTouchMode;
- startScrollingLayer(event.getX(), event.getY());
- doDrag(hdelta, vdelta);
- mTouchMode = oldTouchMode;
- return true;
- }
- }
- }
- }
- return mWebViewPrivate.super_onGenericMotionEvent(event);
- }
-
- private long mTrackballFirstTime = 0;
- private long mTrackballLastTime = 0;
- private float mTrackballRemainsX = 0.0f;
- private float mTrackballRemainsY = 0.0f;
- private int mTrackballXMove = 0;
- private int mTrackballYMove = 0;
- private boolean mSelectingText = false;
- private boolean mShowTextSelectionExtra = false;
- private boolean mSelectionStarted = false;
- private static final int TRACKBALL_KEY_TIMEOUT = 1000;
- private static final int TRACKBALL_TIMEOUT = 200;
- private static final int TRACKBALL_WAIT = 100;
- private static final int TRACKBALL_SCALE = 400;
- private static final int TRACKBALL_SCROLL_COUNT = 5;
- private static final int TRACKBALL_MOVE_COUNT = 10;
- private static final int TRACKBALL_MULTIPLIER = 3;
- private static final int SELECT_CURSOR_OFFSET = 16;
- private static final int SELECT_SCROLL = 5;
- private int mSelectX = 0;
- private int mSelectY = 0;
- private boolean mTrackballDown = false;
- private long mTrackballUpTime = 0;
- private long mLastCursorTime = 0;
- private Rect mLastCursorBounds;
- private SelectionHandleAlpha mBaseAlpha = new SelectionHandleAlpha();
- private SelectionHandleAlpha mExtentAlpha = new SelectionHandleAlpha();
- private ObjectAnimator mBaseHandleAlphaAnimator =
- ObjectAnimator.ofInt(mBaseAlpha, "alpha", 0);
- private ObjectAnimator mExtentHandleAlphaAnimator =
- ObjectAnimator.ofInt(mExtentAlpha, "alpha", 0);
-
- // Set by default; BrowserActivity clears to interpret trackball data
- // directly for movement. Currently, the framework only passes
- // arrow key events, not trackball events, from one child to the next
- private boolean mMapTrackballToArrowKeys = true;
-
- private DrawData mDelaySetPicture;
- private DrawData mLoadedPicture;
-
- @Override
- public void setMapTrackballToArrowKeys(boolean setMap) {
- mMapTrackballToArrowKeys = setMap;
- }
-
- void resetTrackballTime() {
- mTrackballLastTime = 0;
- }
-
- @Override
- public boolean onTrackballEvent(MotionEvent ev) {
- long time = ev.getEventTime();
- if ((ev.getMetaState() & KeyEvent.META_ALT_ON) != 0) {
- if (ev.getY() > 0) pageDown(true);
- if (ev.getY() < 0) pageUp(true);
- return true;
- }
- if (ev.getAction() == MotionEvent.ACTION_DOWN) {
- if (mSelectingText) {
- return true; // discard press if copy in progress
- }
- mTrackballDown = true;
- if (mNativeClass == 0) {
- return false;
- }
- if (DebugFlags.WEB_VIEW) {
- Log.v(LOGTAG, "onTrackballEvent down ev=" + ev
- + " time=" + time
- + " mLastCursorTime=" + mLastCursorTime);
- }
- if (mWebView.isInTouchMode()) mWebView.requestFocusFromTouch();
- return false; // let common code in onKeyDown at it
- }
- if (ev.getAction() == MotionEvent.ACTION_UP) {
- // LONG_PRESS_CENTER is set in common onKeyDown
- mPrivateHandler.removeMessages(LONG_PRESS_CENTER);
- mTrackballDown = false;
- mTrackballUpTime = time;
- if (mSelectingText) {
- copySelection();
- selectionDone();
- return true; // discard press if copy in progress
- }
- if (DebugFlags.WEB_VIEW) {
- Log.v(LOGTAG, "onTrackballEvent up ev=" + ev
- + " time=" + time
- );
- }
- return false; // let common code in onKeyUp at it
- }
- if ((mMapTrackballToArrowKeys && (ev.getMetaState() & KeyEvent.META_SHIFT_ON) == 0) ||
- AccessibilityManager.getInstance(mContext).isEnabled()) {
- if (DebugFlags.WEB_VIEW) Log.v(LOGTAG, "onTrackballEvent gmail quit");
- return false;
- }
- if (mTrackballDown) {
- if (DebugFlags.WEB_VIEW) Log.v(LOGTAG, "onTrackballEvent down quit");
- return true; // discard move if trackball is down
- }
- if (time - mTrackballUpTime < TRACKBALL_TIMEOUT) {
- if (DebugFlags.WEB_VIEW) Log.v(LOGTAG, "onTrackballEvent up timeout quit");
- return true;
- }
- // TODO: alternatively we can do panning as touch does
- switchOutDrawHistory();
- if (time - mTrackballLastTime > TRACKBALL_TIMEOUT) {
- if (DebugFlags.WEB_VIEW) {
- Log.v(LOGTAG, "onTrackballEvent time="
- + time + " last=" + mTrackballLastTime);
- }
- mTrackballFirstTime = time;
- mTrackballXMove = mTrackballYMove = 0;
- }
- mTrackballLastTime = time;
- if (DebugFlags.WEB_VIEW) {
- Log.v(LOGTAG, "onTrackballEvent ev=" + ev + " time=" + time);
- }
- mTrackballRemainsX += ev.getX();
- mTrackballRemainsY += ev.getY();
- doTrackball(time, ev.getMetaState());
- return true;
- }
-
- private int scaleTrackballX(float xRate, int width) {
- int xMove = (int) (xRate / TRACKBALL_SCALE * width);
- int nextXMove = xMove;
- if (xMove > 0) {
- if (xMove > mTrackballXMove) {
- xMove -= mTrackballXMove;
- }
- } else if (xMove < mTrackballXMove) {
- xMove -= mTrackballXMove;
- }
- mTrackballXMove = nextXMove;
- return xMove;
- }
-
- private int scaleTrackballY(float yRate, int height) {
- int yMove = (int) (yRate / TRACKBALL_SCALE * height);
- int nextYMove = yMove;
- if (yMove > 0) {
- if (yMove > mTrackballYMove) {
- yMove -= mTrackballYMove;
- }
- } else if (yMove < mTrackballYMove) {
- yMove -= mTrackballYMove;
- }
- mTrackballYMove = nextYMove;
- return yMove;
- }
-
- private int keyCodeToSoundsEffect(int keyCode) {
- switch(keyCode) {
- case KeyEvent.KEYCODE_DPAD_UP:
- return SoundEffectConstants.NAVIGATION_UP;
- case KeyEvent.KEYCODE_DPAD_RIGHT:
- return SoundEffectConstants.NAVIGATION_RIGHT;
- case KeyEvent.KEYCODE_DPAD_DOWN:
- return SoundEffectConstants.NAVIGATION_DOWN;
- case KeyEvent.KEYCODE_DPAD_LEFT:
- return SoundEffectConstants.NAVIGATION_LEFT;
- }
- return 0;
- }
-
- private void doTrackball(long time, int metaState) {
- int elapsed = (int) (mTrackballLastTime - mTrackballFirstTime);
- if (elapsed == 0) {
- elapsed = TRACKBALL_TIMEOUT;
- }
- float xRate = mTrackballRemainsX * 1000 / elapsed;
- float yRate = mTrackballRemainsY * 1000 / elapsed;
- int viewWidth = getViewWidth();
- int viewHeight = getViewHeight();
- float ax = Math.abs(xRate);
- float ay = Math.abs(yRate);
- float maxA = Math.max(ax, ay);
- if (DebugFlags.WEB_VIEW) {
- Log.v(LOGTAG, "doTrackball elapsed=" + elapsed
- + " xRate=" + xRate
- + " yRate=" + yRate
- + " mTrackballRemainsX=" + mTrackballRemainsX
- + " mTrackballRemainsY=" + mTrackballRemainsY);
- }
- int width = mContentWidth - viewWidth;
- int height = mContentHeight - viewHeight;
- if (width < 0) width = 0;
- if (height < 0) height = 0;
- ax = Math.abs(mTrackballRemainsX * TRACKBALL_MULTIPLIER);
- ay = Math.abs(mTrackballRemainsY * TRACKBALL_MULTIPLIER);
- maxA = Math.max(ax, ay);
- int count = Math.max(0, (int) maxA);
- int oldScrollX = getScrollX();
- int oldScrollY = getScrollY();
- if (count > 0) {
- int selectKeyCode = ax < ay ? mTrackballRemainsY < 0 ?
- KeyEvent.KEYCODE_DPAD_UP : KeyEvent.KEYCODE_DPAD_DOWN :
- mTrackballRemainsX < 0 ? KeyEvent.KEYCODE_DPAD_LEFT :
- KeyEvent.KEYCODE_DPAD_RIGHT;
- count = Math.min(count, TRACKBALL_MOVE_COUNT);
- if (DebugFlags.WEB_VIEW) {
- Log.v(LOGTAG, "doTrackball keyCode=" + selectKeyCode
- + " count=" + count
- + " mTrackballRemainsX=" + mTrackballRemainsX
- + " mTrackballRemainsY=" + mTrackballRemainsY);
- }
- if (mNativeClass != 0) {
- for (int i = 0; i < count; i++) {
- letPageHandleNavKey(selectKeyCode, time, true, metaState);
- }
- letPageHandleNavKey(selectKeyCode, time, false, metaState);
- }
- mTrackballRemainsX = mTrackballRemainsY = 0;
- }
- if (count >= TRACKBALL_SCROLL_COUNT) {
- int xMove = scaleTrackballX(xRate, width);
- int yMove = scaleTrackballY(yRate, height);
- if (DebugFlags.WEB_VIEW) {
- Log.v(LOGTAG, "doTrackball pinScrollBy"
- + " count=" + count
- + " xMove=" + xMove + " yMove=" + yMove
- + " mScrollX-oldScrollX=" + (getScrollX()-oldScrollX)
- + " mScrollY-oldScrollY=" + (getScrollY()-oldScrollY)
- );
- }
- if (Math.abs(getScrollX() - oldScrollX) > Math.abs(xMove)) {
- xMove = 0;
- }
- if (Math.abs(getScrollY() - oldScrollY) > Math.abs(yMove)) {
- yMove = 0;
- }
- if (xMove != 0 || yMove != 0) {
- pinScrollBy(xMove, yMove, true, 0);
- }
- }
- }
-
- /**
- * Compute the maximum horizontal scroll position. Used by {@link OverScrollGlow}.
- * @return Maximum horizontal scroll position within real content
- */
- int computeMaxScrollX() {
- return Math.max(computeRealHorizontalScrollRange() - getViewWidth(), 0);
- }
-
- /**
- * Compute the maximum vertical scroll position. Used by {@link OverScrollGlow}.
- * @return Maximum vertical scroll position within real content
- */
- int computeMaxScrollY() {
- return Math.max(computeRealVerticalScrollRange() + getTitleHeight()
- - getViewHeightWithTitle(), 0);
- }
-
- boolean updateScrollCoordinates(int x, int y) {
- int oldX = getScrollX();
- int oldY = getScrollY();
- setScrollXRaw(x);
- setScrollYRaw(y);
- if (oldX != getScrollX() || oldY != getScrollY()) {
- mWebViewPrivate.onScrollChanged(getScrollX(), getScrollY(), oldX, oldY);
- return true;
- } else {
- return false;
- }
- }
-
- @Override
- public void flingScroll(int vx, int vy) {
- mScroller.fling(getScrollX(), getScrollY(), vx, vy, 0, computeMaxScrollX(), 0,
- computeMaxScrollY(), mOverflingDistance, mOverflingDistance);
- invalidate();
- }
-
- private void doFling() {
- if (mVelocityTracker == null) {
- return;
- }
- int maxX = computeMaxScrollX();
- int maxY = computeMaxScrollY();
-
- mVelocityTracker.computeCurrentVelocity(1000, mMaximumFling);
- int vx = (int) mVelocityTracker.getXVelocity();
- int vy = (int) mVelocityTracker.getYVelocity();
-
- int scrollX = getScrollX();
- int scrollY = getScrollY();
- int overscrollDistance = mOverscrollDistance;
- int overflingDistance = mOverflingDistance;
-
- // Use the layer's scroll data if applicable.
- if (mTouchMode == TOUCH_DRAG_LAYER_MODE) {
- scrollX = mScrollingLayerRect.left;
- scrollY = mScrollingLayerRect.top;
- maxX = mScrollingLayerRect.right;
- maxY = mScrollingLayerRect.bottom;
- // No overscrolling for layers.
- overscrollDistance = overflingDistance = 0;
- } else if (mTouchMode == TOUCH_DRAG_TEXT_MODE) {
- scrollX = getTextScrollX();
- scrollY = getTextScrollY();
- maxX = getMaxTextScrollX();
- maxY = getMaxTextScrollY();
- // No overscrolling for edit text.
- overscrollDistance = overflingDistance = 0;
- }
-
- if (mSnapScrollMode != SNAP_NONE) {
- if ((mSnapScrollMode & SNAP_X) == SNAP_X) {
- vy = 0;
- } else {
- vx = 0;
- }
- }
- if ((maxX == 0 && vy == 0) || (maxY == 0 && vx == 0)) {
- WebViewCore.resumePriority();
- if (!mSelectingText) {
- WebViewCore.resumeUpdatePicture(mWebViewCore);
- }
- if (mScroller.springBack(scrollX, scrollY, 0, maxX, 0, maxY)) {
- invalidate();
- }
- return;
- }
- float currentVelocity = mScroller.getCurrVelocity();
- float velocity = (float) Math.hypot(vx, vy);
- if (mLastVelocity > 0 && currentVelocity > 0 && velocity
- > mLastVelocity * MINIMUM_VELOCITY_RATIO_FOR_ACCELERATION) {
- float deltaR = (float) (Math.abs(Math.atan2(mLastVelY, mLastVelX)
- - Math.atan2(vy, vx)));
- final float circle = (float) (Math.PI) * 2.0f;
- if (deltaR > circle * 0.9f || deltaR < circle * 0.1f) {
- vx += currentVelocity * mLastVelX / mLastVelocity;
- vy += currentVelocity * mLastVelY / mLastVelocity;
- velocity = (float) Math.hypot(vx, vy);
- if (DebugFlags.WEB_VIEW) {
- Log.v(LOGTAG, "doFling vx= " + vx + " vy=" + vy);
- }
- } else if (DebugFlags.WEB_VIEW) {
- Log.v(LOGTAG, "doFling missed " + deltaR / circle);
- }
- } else if (DebugFlags.WEB_VIEW) {
- Log.v(LOGTAG, "doFling start last=" + mLastVelocity
- + " current=" + currentVelocity
- + " vx=" + vx + " vy=" + vy
- + " maxX=" + maxX + " maxY=" + maxY
- + " scrollX=" + scrollX + " scrollY=" + scrollY
- + " layer=" + mCurrentScrollingLayerId);
- }
-
- // Allow sloppy flings without overscrolling at the edges.
- if ((scrollX == 0 || scrollX == maxX) && Math.abs(vx) < Math.abs(vy)) {
- vx = 0;
- }
- if ((scrollY == 0 || scrollY == maxY) && Math.abs(vy) < Math.abs(vx)) {
- vy = 0;
- }
-
- if (overscrollDistance < overflingDistance) {
- if ((vx > 0 && scrollX == -overscrollDistance) ||
- (vx < 0 && scrollX == maxX + overscrollDistance)) {
- vx = 0;
- }
- if ((vy > 0 && scrollY == -overscrollDistance) ||
- (vy < 0 && scrollY == maxY + overscrollDistance)) {
- vy = 0;
- }
- }
-
- mLastVelX = vx;
- mLastVelY = vy;
- mLastVelocity = velocity;
-
- // no horizontal overscroll if the content just fits
- mScroller.fling(scrollX, scrollY, -vx, -vy, 0, maxX, 0, maxY,
- maxX == 0 ? 0 : overflingDistance, overflingDistance);
-
- invalidate();
- }
-
- /**
- * See {@link WebView#getZoomControls()}
- */
- @Override
- @Deprecated
- public View getZoomControls() {
- if (!getSettings().supportZoom()) {
- Log.w(LOGTAG, "This WebView doesn't support zoom.");
- return null;
- }
- return mZoomManager.getExternalZoomPicker();
- }
-
- void dismissZoomControl() {
- mZoomManager.dismissZoomPicker();
- }
-
- float getDefaultZoomScale() {
- return mZoomManager.getDefaultScale();
- }
-
- /**
- * Return the overview scale of the WebView
- * @return The overview scale.
- */
- float getZoomOverviewScale() {
- return mZoomManager.getZoomOverviewScale();
- }
-
- /**
- * See {@link WebView#canZoomIn()}
- */
- @Override
- public boolean canZoomIn() {
- return mZoomManager.canZoomIn();
- }
-
- /**
- * See {@link WebView#canZoomOut()}
- */
- @Override
- public boolean canZoomOut() {
- return mZoomManager.canZoomOut();
- }
-
- /**
- * See {@link WebView#zoomIn()}
- */
- @Override
- public boolean zoomIn() {
- return mZoomManager.zoomIn();
- }
-
- /**
- * See {@link WebView#zoomOut()}
- */
- @Override
- public boolean zoomOut() {
- return mZoomManager.zoomOut();
- }
-
- /*
- * Return true if the rect (e.g. plugin) is fully visible and maximized
- * inside the WebView.
- */
- boolean isRectFitOnScreen(Rect rect) {
- final int rectWidth = rect.width();
- final int rectHeight = rect.height();
- final int viewWidth = getViewWidth();
- final int viewHeight = getViewHeightWithTitle();
- float scale = Math.min((float) viewWidth / rectWidth, (float) viewHeight / rectHeight);
- scale = mZoomManager.computeScaleWithLimits(scale);
- return !mZoomManager.willScaleTriggerZoom(scale)
- && contentToViewX(rect.left) >= getScrollX()
- && contentToViewX(rect.right) <= getScrollX() + viewWidth
- && contentToViewY(rect.top) >= getScrollY()
- && contentToViewY(rect.bottom) <= getScrollY() + viewHeight;
- }
-
- /*
- * Maximize and center the rectangle, specified in the document coordinate
- * space, inside the WebView. If the zoom doesn't need to be changed, do an
- * animated scroll to center it. If the zoom needs to be changed, find the
- * zoom center and do a smooth zoom transition. The rect is in document
- * coordinates
- */
- void centerFitRect(Rect rect) {
- final int rectWidth = rect.width();
- final int rectHeight = rect.height();
- final int viewWidth = getViewWidth();
- final int viewHeight = getViewHeightWithTitle();
- float scale = Math.min((float) viewWidth / rectWidth, (float) viewHeight
- / rectHeight);
- scale = mZoomManager.computeScaleWithLimits(scale);
- if (!mZoomManager.willScaleTriggerZoom(scale)) {
- pinScrollTo(contentToViewX(rect.left + rectWidth / 2) - viewWidth / 2,
- contentToViewY(rect.top + rectHeight / 2) - viewHeight / 2,
- true, 0);
- } else {
- float actualScale = mZoomManager.getScale();
- float oldScreenX = rect.left * actualScale - getScrollX();
- float rectViewX = rect.left * scale;
- float rectViewWidth = rectWidth * scale;
- float newMaxWidth = mContentWidth * scale;
- float newScreenX = (viewWidth - rectViewWidth) / 2;
- // pin the newX to the WebView
- if (newScreenX > rectViewX) {
- newScreenX = rectViewX;
- } else if (newScreenX > (newMaxWidth - rectViewX - rectViewWidth)) {
- newScreenX = viewWidth - (newMaxWidth - rectViewX);
- }
- float zoomCenterX = (oldScreenX * scale - newScreenX * actualScale)
- / (scale - actualScale);
- float oldScreenY = rect.top * actualScale + getTitleHeight()
- - getScrollY();
- float rectViewY = rect.top * scale + getTitleHeight();
- float rectViewHeight = rectHeight * scale;
- float newMaxHeight = mContentHeight * scale + getTitleHeight();
- float newScreenY = (viewHeight - rectViewHeight) / 2;
- // pin the newY to the WebView
- if (newScreenY > rectViewY) {
- newScreenY = rectViewY;
- } else if (newScreenY > (newMaxHeight - rectViewY - rectViewHeight)) {
- newScreenY = viewHeight - (newMaxHeight - rectViewY);
- }
- float zoomCenterY = (oldScreenY * scale - newScreenY * actualScale)
- / (scale - actualScale);
- mZoomManager.setZoomCenter(zoomCenterX, zoomCenterY);
- mZoomManager.startZoomAnimation(scale, false);
- }
- }
-
- // Called by JNI to handle a touch on a node representing an email address,
- // address, or phone number
- private void overrideLoading(String url) {
- mCallbackProxy.uiOverrideUrlLoading(url);
- }
-
- @Override
- public boolean requestFocus(int direction, Rect previouslyFocusedRect) {
- // Check if we are destroyed
- if (mWebViewCore == null) return false;
- // FIXME: If a subwindow is showing find, and the user touches the
- // background window, it can steal focus.
- if (mFindIsUp) return false;
- boolean result = false;
- result = mWebViewPrivate.super_requestFocus(direction, previouslyFocusedRect);
- if (mWebViewCore.getSettings().getNeedInitialFocus()
- && !mWebView.isInTouchMode()) {
- // For cases such as GMail, where we gain focus from a direction,
- // we want to move to the first available link.
- // FIXME: If there are no visible links, we may not want to
- int fakeKeyDirection = 0;
- switch(direction) {
- case View.FOCUS_UP:
- fakeKeyDirection = KeyEvent.KEYCODE_DPAD_UP;
- break;
- case View.FOCUS_DOWN:
- fakeKeyDirection = KeyEvent.KEYCODE_DPAD_DOWN;
- break;
- case View.FOCUS_LEFT:
- fakeKeyDirection = KeyEvent.KEYCODE_DPAD_LEFT;
- break;
- case View.FOCUS_RIGHT:
- fakeKeyDirection = KeyEvent.KEYCODE_DPAD_RIGHT;
- break;
- default:
- return result;
- }
- mWebViewCore.sendMessage(EventHub.SET_INITIAL_FOCUS, fakeKeyDirection);
- }
- return result;
- }
-
- @Override
- public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- int heightMode = MeasureSpec.getMode(heightMeasureSpec);
- int heightSize = MeasureSpec.getSize(heightMeasureSpec);
- int widthMode = MeasureSpec.getMode(widthMeasureSpec);
- int widthSize = MeasureSpec.getSize(widthMeasureSpec);
-
- int measuredHeight = heightSize;
- int measuredWidth = widthSize;
-
- // Grab the content size from WebViewCore.
- int contentHeight = contentToViewDimension(mContentHeight);
- int contentWidth = contentToViewDimension(mContentWidth);
-
-// Log.d(LOGTAG, "------- measure " + heightMode);
-
- if (heightMode != MeasureSpec.EXACTLY) {
- mHeightCanMeasure = true;
- measuredHeight = contentHeight;
- if (heightMode == MeasureSpec.AT_MOST) {
- // If we are larger than the AT_MOST height, then our height can
- // no longer be measured and we should scroll internally.
- if (measuredHeight > heightSize) {
- measuredHeight = heightSize;
- mHeightCanMeasure = false;
- measuredHeight |= View.MEASURED_STATE_TOO_SMALL;
- }
- }
- } else {
- mHeightCanMeasure = false;
- }
- if (mNativeClass != 0) {
- nativeSetHeightCanMeasure(mHeightCanMeasure);
- }
- // For the width, always use the given size unless unspecified.
- if (widthMode == MeasureSpec.UNSPECIFIED) {
- mWidthCanMeasure = true;
- measuredWidth = contentWidth;
- } else {
- if (measuredWidth < contentWidth) {
- measuredWidth |= View.MEASURED_STATE_TOO_SMALL;
- }
- mWidthCanMeasure = false;
- }
-
- synchronized (this) {
- mWebViewPrivate.setMeasuredDimension(measuredWidth, measuredHeight);
- }
- }
-
- @Override
- public boolean requestChildRectangleOnScreen(View child,
- Rect rect,
- boolean immediate) {
- if (mNativeClass == 0) {
- return false;
- }
- // don't scroll while in zoom animation. When it is done, we will adjust
- // the necessary components
- if (mZoomManager.isFixedLengthAnimationInProgress()) {
- return false;
- }
-
- rect.offset(child.getLeft() - child.getScrollX(),
- child.getTop() - child.getScrollY());
-
- Rect content = new Rect(viewToContentX(getScrollX()),
- viewToContentY(getScrollY()),
- viewToContentX(getScrollX() + getWidth()
- - mWebView.getVerticalScrollbarWidth()),
- viewToContentY(getScrollY() + getViewHeightWithTitle()));
- int screenTop = contentToViewY(content.top);
- int screenBottom = contentToViewY(content.bottom);
- int height = screenBottom - screenTop;
- int scrollYDelta = 0;
-
- if (rect.bottom > screenBottom) {
- int oneThirdOfScreenHeight = height / 3;
- if (rect.height() > 2 * oneThirdOfScreenHeight) {
- // If the rectangle is too tall to fit in the bottom two thirds
- // of the screen, place it at the top.
- scrollYDelta = rect.top - screenTop;
- } else {
- // If the rectangle will still fit on screen, we want its
- // top to be in the top third of the screen.
- scrollYDelta = rect.top - (screenTop + oneThirdOfScreenHeight);
- }
- } else if (rect.top < screenTop) {
- scrollYDelta = rect.top - screenTop;
- }
-
- int screenLeft = contentToViewX(content.left);
- int screenRight = contentToViewX(content.right);
- int width = screenRight - screenLeft;
- int scrollXDelta = 0;
-
- if (rect.right > screenRight && rect.left > screenLeft) {
- if (rect.width() > width) {
- scrollXDelta += (rect.left - screenLeft);
- } else {
- scrollXDelta += (rect.right - screenRight);
- }
- } else if (rect.left < screenLeft) {
- scrollXDelta -= (screenLeft - rect.left);
- }
-
- if ((scrollYDelta | scrollXDelta) != 0) {
- return pinScrollBy(scrollXDelta, scrollYDelta, !immediate, 0);
- }
-
- return false;
- }
-
- /* package */ void replaceTextfieldText(int oldStart, int oldEnd,
- String replace, int newStart, int newEnd) {
- WebViewCore.ReplaceTextData arg = new WebViewCore.ReplaceTextData();
- arg.mReplace = replace;
- arg.mNewStart = newStart;
- arg.mNewEnd = newEnd;
- mTextGeneration++;
- arg.mTextGeneration = mTextGeneration;
- sendBatchableInputMessage(EventHub.REPLACE_TEXT, oldStart, oldEnd, arg);
- }
-
- /* package */ void passToJavaScript(String currentText, KeyEvent event) {
- // check if mWebViewCore has been destroyed
- if (mWebViewCore == null) {
- return;
- }
- WebViewCore.JSKeyData arg = new WebViewCore.JSKeyData();
- arg.mEvent = event;
- arg.mCurrentText = currentText;
- // Increase our text generation number, and pass it to webcore thread
- mTextGeneration++;
- mWebViewCore.sendMessage(EventHub.PASS_TO_JS, mTextGeneration, 0, arg);
- // WebKit's document state is not saved until about to leave the page.
- // To make sure the host application, like Browser, has the up to date
- // document state when it goes to background, we force to save the
- // document state.
- mWebViewCore.removeMessages(EventHub.SAVE_DOCUMENT_STATE);
- mWebViewCore.sendMessageDelayed(EventHub.SAVE_DOCUMENT_STATE, null, 1000);
- }
-
- public synchronized WebViewCore getWebViewCore() {
- return mWebViewCore;
- }
-
- private boolean canTextScroll(int directionX, int directionY) {
- int scrollX = getTextScrollX();
- int scrollY = getTextScrollY();
- int maxScrollX = getMaxTextScrollX();
- int maxScrollY = getMaxTextScrollY();
- boolean canScrollX = (directionX > 0)
- ? (scrollX < maxScrollX)
- : (scrollX > 0);
- boolean canScrollY = (directionY > 0)
- ? (scrollY < maxScrollY)
- : (scrollY > 0);
- return canScrollX || canScrollY;
- }
-
- private int getTextScrollX() {
- return -mEditTextContent.left;
- }
-
- private int getTextScrollY() {
- return -mEditTextContent.top;
- }
-
- private int getMaxTextScrollX() {
- return Math.max(0, mEditTextContent.width() - mEditTextContentBounds.width());
- }
-
- private int getMaxTextScrollY() {
- return Math.max(0, mEditTextContent.height() - mEditTextContentBounds.height());
- }
-
- //-------------------------------------------------------------------------
- // Methods can be called from a separate thread, like WebViewCore
- // If it needs to call the View system, it has to send message.
- //-------------------------------------------------------------------------
-
- /**
- * General handler to receive message coming from webkit thread
- */
- class PrivateHandler extends Handler implements WebViewInputDispatcher.UiCallbacks {
- @Override
- public void handleMessage(Message msg) {
- // exclude INVAL_RECT_MSG_ID since it is frequently output
- if (DebugFlags.WEB_VIEW && msg.what != INVAL_RECT_MSG_ID) {
- if (msg.what >= FIRST_PRIVATE_MSG_ID
- && msg.what <= LAST_PRIVATE_MSG_ID) {
- Log.v(LOGTAG, HandlerPrivateDebugString[msg.what
- - FIRST_PRIVATE_MSG_ID]);
- } else if (msg.what >= FIRST_PACKAGE_MSG_ID
- && msg.what <= LAST_PACKAGE_MSG_ID) {
- Log.v(LOGTAG, HandlerPackageDebugString[msg.what
- - FIRST_PACKAGE_MSG_ID]);
- } else {
- Log.v(LOGTAG, Integer.toString(msg.what));
- }
- }
- if (mWebViewCore == null) {
- // after WebView's destroy() is called, skip handling messages.
- return;
- }
- if (mBlockWebkitViewMessages
- && msg.what != WEBCORE_INITIALIZED_MSG_ID) {
- // Blocking messages from webkit
- return;
- }
- switch (msg.what) {
- case REMEMBER_PASSWORD: {
- mDatabase.setUsernamePassword(
- msg.getData().getString("host"),
- msg.getData().getString("username"),
- msg.getData().getString("password"));
- ((Message) msg.obj).sendToTarget();
- break;
- }
- case NEVER_REMEMBER_PASSWORD: {
- mDatabase.setUsernamePassword(msg.getData().getString("host"), null, null);
- ((Message) msg.obj).sendToTarget();
- break;
- }
- case SCROLL_SELECT_TEXT: {
- if (mAutoScrollX == 0 && mAutoScrollY == 0) {
- mSentAutoScrollMessage = false;
- break;
- }
- if (mCurrentScrollingLayerId == 0) {
- pinScrollBy(mAutoScrollX, mAutoScrollY, true, 0);
- } else {
- scrollLayerTo(mScrollingLayerRect.left + mAutoScrollX,
- mScrollingLayerRect.top + mAutoScrollY);
- }
- sendEmptyMessageDelayed(
- SCROLL_SELECT_TEXT, SELECT_SCROLL_INTERVAL);
- break;
- }
- case SCROLL_TO_MSG_ID: {
- // arg1 = animate, arg2 = onlyIfImeIsShowing
- // obj = Point(x, y)
- if (msg.arg2 == 1) {
- // This scroll is intended to bring the textfield into
- // view, but is only necessary if the IME is showing
- InputMethodManager imm = InputMethodManager.peekInstance();
- if (imm == null || !imm.isAcceptingText()
- || !imm.isActive(mWebView)) {
- break;
- }
- }
- final Point p = (Point) msg.obj;
- contentScrollTo(p.x, p.y, msg.arg1 == 1);
- break;
- }
- case UPDATE_ZOOM_RANGE: {
- WebViewCore.ViewState viewState = (WebViewCore.ViewState) msg.obj;
- // mScrollX contains the new minPrefWidth
- mZoomManager.updateZoomRange(viewState, getViewWidth(), viewState.mScrollX);
- break;
- }
- case UPDATE_ZOOM_DENSITY: {
- final float density = (Float) msg.obj;
- mZoomManager.updateDefaultZoomDensity(density);
- break;
- }
- case NEW_PICTURE_MSG_ID: {
- // called for new content
- final WebViewCore.DrawData draw = (WebViewCore.DrawData) msg.obj;
- setNewPicture(draw, true);
- break;
- }
- case WEBCORE_INITIALIZED_MSG_ID:
- // nativeCreate sets mNativeClass to a non-zero value
- String drawableDir = BrowserFrame.getRawResFilename(
- BrowserFrame.DRAWABLEDIR, mContext);
- nativeCreate(msg.arg1, drawableDir, ActivityManager.isHighEndGfx());
- if (mDelaySetPicture != null) {
- setNewPicture(mDelaySetPicture, true);
- mDelaySetPicture = null;
- }
- if (mIsPaused) {
- nativeSetPauseDrawing(mNativeClass, true);
- }
- mInputDispatcher = new WebViewInputDispatcher(this,
- mWebViewCore.getInputDispatcherCallbacks());
- break;
- case UPDATE_TEXTFIELD_TEXT_MSG_ID:
- // Make sure that the textfield is currently focused
- // and representing the same node as the pointer.
- if (msg.arg2 == mTextGeneration) {
- String text = (String) msg.obj;
- if (null == text) {
- text = "";
- }
- if (mInputConnection != null &&
- mFieldPointer == msg.arg1) {
- mInputConnection.setTextAndKeepSelection(text);
- }
- }
- break;
- case UPDATE_TEXT_SELECTION_MSG_ID:
- updateTextSelectionFromMessage(msg.arg1, msg.arg2,
- (WebViewCore.TextSelectionData) msg.obj);
- break;
- case TAKE_FOCUS:
- int direction = msg.arg1;
- View focusSearch = mWebView.focusSearch(direction);
- if (focusSearch != null && focusSearch != mWebView) {
- focusSearch.requestFocus();
- }
- break;
- case CLEAR_TEXT_ENTRY:
- hideSoftKeyboard();
- break;
- case INVAL_RECT_MSG_ID: {
- Rect r = (Rect)msg.obj;
- if (r == null) {
- invalidate();
- } else {
- // we need to scale r from content into view coords,
- // which viewInvalidate() does for us
- viewInvalidate(r.left, r.top, r.right, r.bottom);
- }
- break;
- }
- case REQUEST_FORM_DATA:
- if (mFieldPointer == msg.arg1) {
- ArrayAdapter<String> adapter = (ArrayAdapter<String>)msg.obj;
- mAutoCompletePopup.setAdapter(adapter);
- }
- break;
-
- case LONG_PRESS_CENTER:
- // as this is shared by keydown and trackballdown, reset all
- // the states
- mGotCenterDown = false;
- mTrackballDown = false;
- mWebView.performLongClick();
- break;
-
- case WEBCORE_NEED_TOUCH_EVENTS:
- mInputDispatcher.setWebKitWantsTouchEvents(msg.arg1 != 0);
- break;
-
- case REQUEST_KEYBOARD:
- if (msg.arg1 == 0) {
- hideSoftKeyboard();
- } else {
- displaySoftKeyboard(false);
- }
- break;
-
- case DRAG_HELD_MOTIONLESS:
- mHeldMotionless = MOTIONLESS_TRUE;
- invalidate();
- break;
-
- case SCREEN_ON:
- mWebView.setKeepScreenOn(msg.arg1 == 1);
- break;
-
- case EXIT_FULLSCREEN_VIDEO:
- if (mHTML5VideoViewProxy != null) {
- mHTML5VideoViewProxy.exitFullScreenVideo();
- }
- break;
-
- case SHOW_FULLSCREEN: {
- View view = (View) msg.obj;
- int orientation = msg.arg1;
- int npp = msg.arg2;
-
- if (inFullScreenMode()) {
- Log.w(LOGTAG, "Should not have another full screen.");
- dismissFullScreenMode();
- }
- mFullScreenHolder = new PluginFullScreenHolder(WebViewClassic.this, orientation, npp);
- mFullScreenHolder.setContentView(view);
- mFullScreenHolder.show();
- invalidate();
-
- break;
- }
- case HIDE_FULLSCREEN:
- dismissFullScreenMode();
- break;
-
- case SHOW_RECT_MSG_ID: {
- WebViewCore.ShowRectData data = (WebViewCore.ShowRectData) msg.obj;
- int left = contentToViewX(data.mLeft);
- int width = contentToViewDimension(data.mWidth);
- int maxWidth = contentToViewDimension(data.mContentWidth);
- int viewWidth = getViewWidth();
- int x = (int) (left + data.mXPercentInDoc * width -
- data.mXPercentInView * viewWidth);
- if (DebugFlags.WEB_VIEW) {
- Log.v(LOGTAG, "showRectMsg=(left=" + left + ",width=" +
- width + ",maxWidth=" + maxWidth +
- ",viewWidth=" + viewWidth + ",x="
- + x + ",xPercentInDoc=" + data.mXPercentInDoc +
- ",xPercentInView=" + data.mXPercentInView+ ")");
- }
- // use the passing content width to cap x as the current
- // mContentWidth may not be updated yet
- x = Math.max(0,
- (Math.min(maxWidth, x + viewWidth)) - viewWidth);
- int top = contentToViewY(data.mTop);
- int height = contentToViewDimension(data.mHeight);
- int maxHeight = contentToViewDimension(data.mContentHeight);
- int viewHeight = getViewHeight();
- int y = (int) (top + data.mYPercentInDoc * height -
- data.mYPercentInView * viewHeight);
- if (DebugFlags.WEB_VIEW) {
- Log.v(LOGTAG, "showRectMsg=(top=" + top + ",height=" +
- height + ",maxHeight=" + maxHeight +
- ",viewHeight=" + viewHeight + ",y="
- + y + ",yPercentInDoc=" + data.mYPercentInDoc +
- ",yPercentInView=" + data.mYPercentInView+ ")");
- }
- // use the passing content height to cap y as the current
- // mContentHeight may not be updated yet
- y = Math.max(0,
- (Math.min(maxHeight, y + viewHeight) - viewHeight));
- // We need to take into account the visible title height
- // when scrolling since y is an absolute view position.
- y = Math.max(0, y - getVisibleTitleHeightImpl());
- mWebView.scrollTo(x, y);
- }
- break;
-
- case CENTER_FIT_RECT:
- centerFitRect((Rect)msg.obj);
- break;
-
- case SET_SCROLLBAR_MODES:
- mHorizontalScrollBarMode = msg.arg1;
- mVerticalScrollBarMode = msg.arg2;
- break;
-
- case FOCUS_NODE_CHANGED:
- mIsEditingText = (msg.arg1 == mFieldPointer);
- if (mAutoCompletePopup != null && !mIsEditingText) {
- mAutoCompletePopup.clearAdapter();
- }
- // fall through to HIT_TEST_RESULT
- case HIT_TEST_RESULT:
- WebKitHitTest hit = (WebKitHitTest) msg.obj;
- mFocusedNode = hit;
- setTouchHighlightRects(hit);
- setHitTestResult(hit);
- break;
-
- case SAVE_WEBARCHIVE_FINISHED:
- SaveWebArchiveMessage saveMessage = (SaveWebArchiveMessage)msg.obj;
- if (saveMessage.mCallback != null) {
- saveMessage.mCallback.onReceiveValue(saveMessage.mResultFile);
- }
- break;
-
- case SET_AUTOFILLABLE:
- mAutoFillData = (WebViewCore.AutoFillData) msg.obj;
- if (mInputConnection != null) {
- mInputConnection.setAutoFillable(mAutoFillData.getQueryId());
- mAutoCompletePopup.setAutoFillQueryId(mAutoFillData.getQueryId());
- }
- break;
-
- case AUTOFILL_COMPLETE:
- if (mAutoCompletePopup != null) {
- ArrayList<String> pastEntries = new ArrayList<String>();
- mAutoCompletePopup.setAdapter(new ArrayAdapter<String>(
- mContext,
- com.android.internal.R.layout.web_text_view_dropdown,
- pastEntries));
- }
- break;
-
- case COPY_TO_CLIPBOARD:
- copyToClipboard((String) msg.obj);
- break;
-
- case INIT_EDIT_FIELD:
- if (mInputConnection != null) {
- TextFieldInitData initData = (TextFieldInitData) msg.obj;
- mTextGeneration = 0;
- mFieldPointer = initData.mFieldPointer;
- mInputConnection.initEditorInfo(initData);
- mInputConnection.setTextAndKeepSelection(initData.mText);
- mEditTextContentBounds.set(initData.mContentBounds);
- mEditTextLayerId = initData.mNodeLayerId;
- nativeMapLayerRect(mNativeClass, mEditTextLayerId,
- mEditTextContentBounds);
- mEditTextContent.set(initData.mClientRect);
- relocateAutoCompletePopup();
- }
- break;
-
- case REPLACE_TEXT:{
- String text = (String)msg.obj;
- int start = msg.arg1;
- int end = msg.arg2;
- int cursorPosition = start + text.length();
- replaceTextfieldText(start, end, text,
- cursorPosition, cursorPosition);
- selectionDone();
- break;
- }
-
- case UPDATE_MATCH_COUNT: {
- WebViewCore.FindAllRequest request = (WebViewCore.FindAllRequest)msg.obj;
- if (request == null) {
- if (mFindCallback != null) {
- mFindCallback.updateMatchCount(0, 0, true);
- }
- } else if (request == mFindRequest) {
- int matchCount, matchIndex;
- synchronized (mFindRequest) {
- matchCount = request.mMatchCount;
- matchIndex = request.mMatchIndex;
- }
- if (mFindCallback != null) {
- mFindCallback.updateMatchCount(matchIndex, matchCount, false);
- }
- if (mFindListener != null) {
- mFindListener.onFindResultReceived(matchIndex, matchCount, true);
- }
- }
- break;
- }
-
- case CLEAR_CARET_HANDLE:
- if (mIsCaretSelection) {
- selectionDone();
- }
- break;
-
- case KEY_PRESS:
- sendBatchableInputMessage(EventHub.KEY_PRESS, msg.arg1, 0, null);
- break;
-
- case RELOCATE_AUTO_COMPLETE_POPUP:
- relocateAutoCompletePopup();
- break;
-
- case AUTOFILL_FORM:
- mWebViewCore.sendMessage(EventHub.AUTOFILL_FORM,
- msg.arg1, /* unused */0);
- break;
-
- case EDIT_TEXT_SIZE_CHANGED:
- if (msg.arg1 == mFieldPointer) {
- mEditTextContent.set((Rect)msg.obj);
- }
- break;
-
- case SHOW_CARET_HANDLE:
- if (!mSelectingText && mIsEditingText && mIsCaretSelection) {
- setupWebkitSelect();
- resetCaretTimer();
- showPasteWindow();
- }
- break;
-
- case UPDATE_CONTENT_BOUNDS:
- mEditTextContentBounds.set((Rect) msg.obj);
- nativeMapLayerRect(mNativeClass, mEditTextLayerId,
- mEditTextContentBounds);
- break;
-
- case SCROLL_EDIT_TEXT:
- scrollEditWithCursor();
- break;
-
- case SCROLL_HANDLE_INTO_VIEW:
- scrollDraggedSelectionHandleIntoView();
- break;
-
- default:
- super.handleMessage(msg);
- break;
- }
- }
-
- @Override
- public Looper getUiLooper() {
- return getLooper();
- }
-
- @Override
- public void dispatchUiEvent(MotionEvent event, int eventType, int flags) {
- onHandleUiEvent(event, eventType, flags);
- }
-
- @Override
- public Context getContext() {
- return WebViewClassic.this.getContext();
- }
-
- @Override
- public boolean shouldInterceptTouchEvent(MotionEvent event) {
- if (!mSelectingText) {
- return false;
- }
- ensureSelectionHandles();
- int y = Math.round(event.getY() - getTitleHeight() + getScrollY());
- int x = Math.round(event.getX() + getScrollX());
- boolean isPressingHandle;
- if (mIsCaretSelection) {
- isPressingHandle = mSelectHandleCenter.getBounds()
- .contains(x, y);
- } else {
- isPressingHandle =
- mSelectHandleBaseBounds.contains(x, y)
- || mSelectHandleExtentBounds.contains(x, y);
- }
- return isPressingHandle;
- }
-
- @Override
- public void showTapHighlight(boolean show) {
- if (mShowTapHighlight != show) {
- mShowTapHighlight = show;
- invalidate();
- }
- }
-
- @Override
- public void clearPreviousHitTest() {
- setHitTestResult(null);
- }
- }
-
- private void setHitTestTypeFromUrl(String url) {
- String substr = null;
- if (url.startsWith(SCHEME_GEO)) {
- mInitialHitTestResult.setType(HitTestResult.GEO_TYPE);
- substr = url.substring(SCHEME_GEO.length());
- } else if (url.startsWith(SCHEME_TEL)) {
- mInitialHitTestResult.setType(HitTestResult.PHONE_TYPE);
- substr = url.substring(SCHEME_TEL.length());
- } else if (url.startsWith(SCHEME_MAILTO)) {
- mInitialHitTestResult.setType(HitTestResult.EMAIL_TYPE);
- substr = url.substring(SCHEME_MAILTO.length());
- } else {
- mInitialHitTestResult.setType(HitTestResult.SRC_ANCHOR_TYPE);
- mInitialHitTestResult.setExtra(url);
- return;
- }
- try {
- mInitialHitTestResult.setExtra(URLDecoder.decode(substr, "UTF-8"));
- } catch (Throwable e) {
- Log.w(LOGTAG, "Failed to decode URL! " + substr, e);
- mInitialHitTestResult.setType(HitTestResult.UNKNOWN_TYPE);
- }
- }
-
- private void setHitTestResult(WebKitHitTest hit) {
- if (hit == null) {
- mInitialHitTestResult = null;
- return;
- }
- mInitialHitTestResult = new HitTestResult();
- if (hit.mLinkUrl != null) {
- setHitTestTypeFromUrl(hit.mLinkUrl);
- if (hit.mImageUrl != null
- && mInitialHitTestResult.getType() == HitTestResult.SRC_ANCHOR_TYPE) {
- mInitialHitTestResult.setType(HitTestResult.SRC_IMAGE_ANCHOR_TYPE);
- mInitialHitTestResult.setExtra(hit.mImageUrl);
- }
- } else if (hit.mImageUrl != null) {
- mInitialHitTestResult.setType(HitTestResult.IMAGE_TYPE);
- mInitialHitTestResult.setExtra(hit.mImageUrl);
- } else if (hit.mEditable) {
- mInitialHitTestResult.setType(HitTestResult.EDIT_TEXT_TYPE);
- } else if (hit.mIntentUrl != null) {
- setHitTestTypeFromUrl(hit.mIntentUrl);
- }
- }
-
- private boolean shouldDrawHighlightRect() {
- if (mFocusedNode == null || mInitialHitTestResult == null) {
- return false;
- }
- if (mTouchHighlightRegion.isEmpty()) {
- return false;
- }
- if (mFocusedNode.mHasFocus && !mWebView.isInTouchMode()) {
- return mDrawCursorRing && !mFocusedNode.mEditable;
- }
- if (mFocusedNode.mHasFocus && mFocusedNode.mEditable) {
- return false;
- }
- return mShowTapHighlight;
- }
-
-
- private FocusTransitionDrawable mFocusTransition = null;
- static class FocusTransitionDrawable extends Drawable {
- Region mPreviousRegion;
- Region mNewRegion;
- float mProgress = 0;
- WebViewClassic mWebView;
- Paint mPaint;
- int mMaxAlpha;
- Point mTranslate;
-
- public FocusTransitionDrawable(WebViewClassic view) {
- mWebView = view;
- mPaint = new Paint(mWebView.mTouchHightlightPaint);
- mMaxAlpha = mPaint.getAlpha();
- }
-
- @Override
- public void setColorFilter(ColorFilter cf) {
- }
-
- @Override
- public void setAlpha(int alpha) {
- }
-
- @Override
- public int getOpacity() {
- return 0;
- }
-
- public void setProgress(float p) {
- mProgress = p;
- if (mWebView.mFocusTransition == this) {
- if (mProgress == 1f)
- mWebView.mFocusTransition = null;
- mWebView.invalidate();
- }
- }
-
- public float getProgress() {
- return mProgress;
- }
-
- @Override
- public void draw(Canvas canvas) {
- if (mTranslate == null) {
- Rect bounds = mPreviousRegion.getBounds();
- Point from = new Point(bounds.centerX(), bounds.centerY());
- mNewRegion.getBounds(bounds);
- Point to = new Point(bounds.centerX(), bounds.centerY());
- mTranslate = new Point(from.x - to.x, from.y - to.y);
- }
- int alpha = (int) (mProgress * mMaxAlpha);
- RegionIterator iter = new RegionIterator(mPreviousRegion);
- Rect r = new Rect();
- mPaint.setAlpha(mMaxAlpha - alpha);
- float tx = mTranslate.x * mProgress;
- float ty = mTranslate.y * mProgress;
- int save = canvas.save(Canvas.MATRIX_SAVE_FLAG);
- canvas.translate(-tx, -ty);
- while (iter.next(r)) {
- canvas.drawRect(r, mPaint);
- }
- canvas.restoreToCount(save);
- iter = new RegionIterator(mNewRegion);
- r = new Rect();
- mPaint.setAlpha(alpha);
- save = canvas.save(Canvas.MATRIX_SAVE_FLAG);
- tx = mTranslate.x - tx;
- ty = mTranslate.y - ty;
- canvas.translate(tx, ty);
- while (iter.next(r)) {
- canvas.drawRect(r, mPaint);
- }
- canvas.restoreToCount(save);
- }
- };
-
- private boolean shouldAnimateTo(WebKitHitTest hit) {
- // TODO: Don't be annoying or throw out the animation entirely
- return false;
- }
-
- private void setTouchHighlightRects(WebKitHitTest hit) {
- FocusTransitionDrawable transition = null;
- if (shouldAnimateTo(hit)) {
- transition = new FocusTransitionDrawable(this);
- }
- Rect[] rects = hit != null ? hit.mTouchRects : null;
- if (!mTouchHighlightRegion.isEmpty()) {
- mWebView.invalidate(mTouchHighlightRegion.getBounds());
- if (transition != null) {
- transition.mPreviousRegion = new Region(mTouchHighlightRegion);
- }
- mTouchHighlightRegion.setEmpty();
- }
- if (rects != null) {
- mTouchHightlightPaint.setColor(hit.mTapHighlightColor);
- for (Rect rect : rects) {
- Rect viewRect = contentToViewRect(rect);
- // some sites, like stories in nytimes.com, set
- // mouse event handler in the top div. It is not
- // user friendly to highlight the div if it covers
- // more than half of the screen.
- if (viewRect.width() < getWidth() >> 1
- || viewRect.height() < getHeight() >> 1) {
- mTouchHighlightRegion.union(viewRect);
- } else if (DebugFlags.WEB_VIEW) {
- Log.d(LOGTAG, "Skip the huge selection rect:"
- + viewRect);
- }
- }
- mWebView.invalidate(mTouchHighlightRegion.getBounds());
- if (transition != null && transition.mPreviousRegion != null) {
- transition.mNewRegion = new Region(mTouchHighlightRegion);
- mFocusTransition = transition;
- ObjectAnimator animator = ObjectAnimator.ofFloat(
- mFocusTransition, "progress", 1f);
- animator.start();
- }
- }
- }
-
- // Interface to allow the profiled WebView to hook the page swap notifications.
- public interface PageSwapDelegate {
- void onPageSwapOccurred(boolean notifyAnimationStarted);
- }
-
- long mLastSwapTime;
- double mAverageSwapFps;
-
- /** Called by JNI when pages are swapped (only occurs with hardware
- * acceleration) */
- protected void pageSwapCallback(boolean notifyAnimationStarted) {
- if (DebugFlags.MEASURE_PAGE_SWAP_FPS) {
- long now = System.currentTimeMillis();
- long diff = now - mLastSwapTime;
- mAverageSwapFps = ((1000.0 / diff) + mAverageSwapFps) / 2;
- Log.d(LOGTAG, "page swap fps: " + mAverageSwapFps);
- mLastSwapTime = now;
- }
- mWebViewCore.resumeWebKitDraw();
- if (notifyAnimationStarted) {
- mWebViewCore.sendMessage(EventHub.NOTIFY_ANIMATION_STARTED);
- }
- if (mWebView instanceof PageSwapDelegate) {
- // This provides a hook for ProfiledWebView to observe the tile page swaps.
- ((PageSwapDelegate) mWebView).onPageSwapOccurred(notifyAnimationStarted);
- }
-
- if (mPictureListener != null) {
- // trigger picture listener for hardware layers. Software layers are
- // triggered in setNewPicture
- Picture picture = mContext.getApplicationInfo().targetSdkVersion <
- Build.VERSION_CODES.JELLY_BEAN_MR2 ? capturePicture() : null;
- if (DebugFlags.TRACE_CALLBACK) Log.d(CallbackProxy.LOGTAG, "onNewPicture");
- mPictureListener.onNewPicture(getWebView(), picture);
- }
- }
-
- void setNewPicture(final WebViewCore.DrawData draw, boolean updateBaseLayer) {
- if (mNativeClass == 0) {
- if (mDelaySetPicture != null) {
- throw new IllegalStateException("Tried to setNewPicture with"
- + " a delay picture already set! (memory leak)");
- }
- // Not initialized yet, delay set
- mDelaySetPicture = draw;
- return;
- }
- WebViewCore.ViewState viewState = draw.mViewState;
- boolean isPictureAfterFirstLayout = viewState != null;
-
- if (updateBaseLayer) {
- setBaseLayer(draw.mBaseLayer,
- getSettings().getShowVisualIndicator(),
- isPictureAfterFirstLayout);
- }
- final Point viewSize = draw.mViewSize;
- // We update the layout (i.e. request a layout from the
- // view system) if the last view size that we sent to
- // WebCore matches the view size of the picture we just
- // received in the fixed dimension.
- final boolean updateLayout = viewSize.x == mLastWidthSent
- && viewSize.y == mLastHeightSent;
- // Don't send scroll event for picture coming from webkit,
- // since the new picture may cause a scroll event to override
- // the saved history scroll position.
- mSendScrollEvent = false;
- recordNewContentSize(draw.mContentSize.x,
- draw.mContentSize.y, updateLayout);
- if (isPictureAfterFirstLayout) {
- // Reset the last sent data here since dealing with new page.
- mLastWidthSent = 0;
- mZoomManager.onFirstLayout(draw);
- int scrollX = viewState.mShouldStartScrolledRight
- ? getContentWidth() : viewState.mScrollX;
- int scrollY = viewState.mScrollY;
- contentScrollTo(scrollX, scrollY, false);
- if (!mDrawHistory) {
- // As we are on a new page, hide the keyboard
- hideSoftKeyboard();
- }
- }
- mSendScrollEvent = true;
-
- int functor = 0;
- boolean forceInval = isPictureAfterFirstLayout;
- ViewRootImpl viewRoot = mWebView.getViewRootImpl();
- if (mWebView.isHardwareAccelerated()
- && mWebView.getLayerType() != View.LAYER_TYPE_SOFTWARE
- && viewRoot != null) {
- functor = nativeGetDrawGLFunction(mNativeClass);
- if (functor != 0) {
- // force an invalidate if functor attach not successful
- forceInval |= !viewRoot.attachFunctor(functor);
- }
- }
-
- if (functor == 0
- || forceInval
- || mWebView.getLayerType() != View.LAYER_TYPE_NONE) {
- // invalidate the screen so that the next repaint will show new content
- // TODO: partial invalidate
- mWebView.invalidate();
- }
-
- // update the zoom information based on the new picture
- if (mZoomManager.onNewPicture(draw))
- invalidate();
-
- if (isPictureAfterFirstLayout) {
- mViewManager.postReadyToDrawAll();
- }
- scrollEditWithCursor();
-
- if (mPictureListener != null) {
- if (!mWebView.isHardwareAccelerated()
- || mWebView.getLayerType() == View.LAYER_TYPE_SOFTWARE) {
- // trigger picture listener for software layers. Hardware layers are
- // triggered in pageSwapCallback
- Picture picture = mContext.getApplicationInfo().targetSdkVersion <
- Build.VERSION_CODES.JELLY_BEAN_MR2 ? capturePicture() : null;
- if (DebugFlags.TRACE_CALLBACK) Log.d(CallbackProxy.LOGTAG, "onNewPicture");
- mPictureListener.onNewPicture(getWebView(), picture);
- }
- }
- }
-
- /**
- * Used when receiving messages for REQUEST_KEYBOARD_WITH_SELECTION_MSG_ID
- * and UPDATE_TEXT_SELECTION_MSG_ID.
- */
- private void updateTextSelectionFromMessage(int nodePointer,
- int textGeneration, WebViewCore.TextSelectionData data) {
- if (textGeneration == mTextGeneration) {
- if (mInputConnection != null && mFieldPointer == nodePointer) {
- mInputConnection.setSelection(data.mStart, data.mEnd);
- }
- }
- nativeSetTextSelection(mNativeClass, data.mSelectTextPtr);
-
- if ((data.mSelectionReason == TextSelectionData.REASON_ACCESSIBILITY_INJECTOR)
- || (!mSelectingText && data.mStart != data.mEnd
- && data.mSelectionReason != TextSelectionData.REASON_SELECT_WORD)) {
- selectionDone();
- mShowTextSelectionExtra = true;
- invalidate();
- return;
- }
-
- if (data.mSelectTextPtr != 0 &&
- (data.mStart != data.mEnd ||
- (mFieldPointer == nodePointer && mFieldPointer != 0) ||
- (nodePointer == 0 && data.mStart == 0 && data.mEnd == 0))) {
- mIsEditingText = (mFieldPointer == nodePointer) && nodePointer != 0;
- mIsCaretSelection = (data.mStart == data.mEnd && nodePointer != 0);
- if (mIsCaretSelection &&
- (mInputConnection == null ||
- mInputConnection.getEditable().length() == 0)) {
- // There's no text, don't show caret handle.
- selectionDone();
- } else {
- if (!mSelectingText) {
- setupWebkitSelect();
- } else {
- syncSelectionCursors();
- }
- animateHandles();
- if (mIsCaretSelection) {
- resetCaretTimer();
- }
- }
- } else {
- selectionDone();
- }
- invalidate();
- }
-
- private void scrollEditText(int scrollX, int scrollY) {
- // Scrollable edit text. Scroll it.
- float maxScrollX = getMaxTextScrollX();
- float scrollPercentX = ((float)scrollX)/maxScrollX;
- mEditTextContent.offsetTo(-scrollX, -scrollY);
- mWebViewCore.removeMessages(EventHub.SCROLL_TEXT_INPUT);
- mWebViewCore.sendMessage(EventHub.SCROLL_TEXT_INPUT, 0,
- scrollY, (Float)scrollPercentX);
- animateHandles();
- }
-
- private void beginTextBatch() {
- mIsBatchingTextChanges = true;
- }
-
- private void commitTextBatch() {
- if (mWebViewCore != null) {
- mWebViewCore.sendMessages(mBatchedTextChanges);
- }
- mBatchedTextChanges.clear();
- mIsBatchingTextChanges = false;
- }
-
- void sendBatchableInputMessage(int what, int arg1, int arg2,
- Object obj) {
- if (mWebViewCore == null) {
- return;
- }
- Message message = Message.obtain(null, what, arg1, arg2, obj);
- if (mIsBatchingTextChanges) {
- mBatchedTextChanges.add(message);
- } else {
- mWebViewCore.sendMessage(message);
- }
- }
-
- // Class used to use a dropdown for a <select> element
- private class InvokeListBox implements Runnable {
- // Whether the listbox allows multiple selection.
- private boolean mMultiple;
- // Passed in to a list with multiple selection to tell
- // which items are selected.
- private int[] mSelectedArray;
- // Passed in to a list with single selection to tell
- // where the initial selection is.
- private int mSelection;
-
- private Container[] mContainers;
-
- // Need these to provide stable ids to my ArrayAdapter,
- // which normally does not have stable ids. (Bug 1250098)
- private class Container extends Object {
- /**
- * Possible values for mEnabled. Keep in sync with OptionStatus in
- * WebViewCore.cpp
- */
- final static int OPTGROUP = -1;
- final static int OPTION_DISABLED = 0;
- final static int OPTION_ENABLED = 1;
-
- String mString;
- int mEnabled;
- int mId;
-
- @Override
- public String toString() {
- return mString;
- }
- }
-
- /**
- * Subclass ArrayAdapter so we can disable OptionGroupLabels,
- * and allow filtering.
- */
- private class MyArrayListAdapter extends ArrayAdapter<Container> {
- public MyArrayListAdapter() {
- super(WebViewClassic.this.mContext,
- mMultiple ? com.android.internal.R.layout.select_dialog_multichoice :
- com.android.internal.R.layout.webview_select_singlechoice,
- mContainers);
- }
-
- @Override
- public View getView(int position, View convertView,
- ViewGroup parent) {
- // Always pass in null so that we will get a new CheckedTextView
- // Otherwise, an item which was previously used as an <optgroup>
- // element (i.e. has no check), could get used as an <option>
- // element, which needs a checkbox/radio, but it would not have
- // one.
- convertView = super.getView(position, null, parent);
- Container c = item(position);
- if (c != null && Container.OPTION_ENABLED != c.mEnabled) {
- // ListView does not draw dividers between disabled and
- // enabled elements. Use a LinearLayout to provide dividers
- LinearLayout layout = new LinearLayout(mContext);
- layout.setOrientation(LinearLayout.VERTICAL);
- if (position > 0) {
- View dividerTop = new View(mContext);
- dividerTop.setBackgroundResource(
- android.R.drawable.divider_horizontal_bright);
- layout.addView(dividerTop);
- }
-
- if (Container.OPTGROUP == c.mEnabled) {
- // Currently select_dialog_multichoice uses CheckedTextViews.
- // If that changes, the class cast will no longer be valid.
- if (mMultiple) {
- Assert.assertTrue(convertView instanceof CheckedTextView);
- ((CheckedTextView) convertView).setCheckMarkDrawable(null);
- }
- } else {
- // c.mEnabled == Container.OPTION_DISABLED
- // Draw the disabled element in a disabled state.
- convertView.setEnabled(false);
- }
-
- layout.addView(convertView);
- if (position < getCount() - 1) {
- View dividerBottom = new View(mContext);
- dividerBottom.setBackgroundResource(
- android.R.drawable.divider_horizontal_bright);
- layout.addView(dividerBottom);
- }
- return layout;
- }
- return convertView;
- }
-
- @Override
- public boolean hasStableIds() {
- // AdapterView's onChanged method uses this to determine whether
- // to restore the old state. Return false so that the old (out
- // of date) state does not replace the new, valid state.
- return false;
- }
-
- private Container item(int position) {
- if (position < 0 || position >= getCount()) {
- return null;
- }
- return getItem(position);
- }
-
- @Override
- public long getItemId(int position) {
- Container item = item(position);
- if (item == null) {
- return -1;
- }
- return item.mId;
- }
-
- @Override
- public boolean areAllItemsEnabled() {
- return false;
- }
-
- @Override
- public boolean isEnabled(int position) {
- Container item = item(position);
- if (item == null) {
- return false;
- }
- return Container.OPTION_ENABLED == item.mEnabled;
- }
- }
-
- private InvokeListBox(String[] array, int[] enabled, int[] selected) {
- mMultiple = true;
- mSelectedArray = selected;
-
- int length = array.length;
- mContainers = new Container[length];
- for (int i = 0; i < length; i++) {
- mContainers[i] = new Container();
- mContainers[i].mString = array[i];
- mContainers[i].mEnabled = enabled[i];
- mContainers[i].mId = i;
- }
- }
-
- private InvokeListBox(String[] array, int[] enabled, int selection) {
- mSelection = selection;
- mMultiple = false;
-
- int length = array.length;
- mContainers = new Container[length];
- for (int i = 0; i < length; i++) {
- mContainers[i] = new Container();
- mContainers[i].mString = array[i];
- mContainers[i].mEnabled = enabled[i];
- mContainers[i].mId = i;
- }
- }
-
- /*
- * Whenever the data set changes due to filtering, this class ensures
- * that the checked item remains checked.
- */
- private class SingleDataSetObserver extends DataSetObserver {
- private long mCheckedId;
- private ListView mListView;
- private Adapter mAdapter;
-
- /*
- * Create a new observer.
- * @param id The ID of the item to keep checked.
- * @param l ListView for getting and clearing the checked states
- * @param a Adapter for getting the IDs
- */
- public SingleDataSetObserver(long id, ListView l, Adapter a) {
- mCheckedId = id;
- mListView = l;
- mAdapter = a;
- }
-
- @Override
- public void onChanged() {
- // The filter may have changed which item is checked. Find the
- // item that the ListView thinks is checked.
- int position = mListView.getCheckedItemPosition();
- long id = mAdapter.getItemId(position);
- if (mCheckedId != id) {
- // Clear the ListView's idea of the checked item, since
- // it is incorrect
- mListView.clearChoices();
- // Search for mCheckedId. If it is in the filtered list,
- // mark it as checked
- int count = mAdapter.getCount();
- for (int i = 0; i < count; i++) {
- if (mAdapter.getItemId(i) == mCheckedId) {
- mListView.setItemChecked(i, true);
- break;
- }
- }
- }
- }
- }
-
- @Override
- public void run() {
- if (mWebViewCore == null
- || getWebView().getWindowToken() == null
- || getWebView().getViewRootImpl() == null) {
- // We've been detached and/or destroyed since this was posted
- return;
- }
- final ListView listView = (ListView) LayoutInflater.from(mContext)
- .inflate(com.android.internal.R.layout.select_dialog, null);
- final MyArrayListAdapter adapter = new MyArrayListAdapter();
- AlertDialog.Builder b = new AlertDialog.Builder(mContext)
- .setView(listView).setCancelable(true)
- .setInverseBackgroundForced(true);
-
- if (mMultiple) {
- b.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int which) {
- mWebViewCore.sendMessage(
- EventHub.LISTBOX_CHOICES,
- adapter.getCount(), 0,
- listView.getCheckedItemPositions());
- }});
- b.setNegativeButton(android.R.string.cancel,
- new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int which) {
- mWebViewCore.sendMessage(
- EventHub.SINGLE_LISTBOX_CHOICE, -2, 0);
- }});
- }
- mListBoxDialog = b.create();
- listView.setAdapter(adapter);
- listView.setFocusableInTouchMode(true);
- // There is a bug (1250103) where the checks in a ListView with
- // multiple items selected are associated with the positions, not
- // the ids, so the items do not properly retain their checks when
- // filtered. Do not allow filtering on multiple lists until
- // that bug is fixed.
-
- listView.setTextFilterEnabled(!mMultiple);
- if (mMultiple) {
- listView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE);
- int length = mSelectedArray.length;
- for (int i = 0; i < length; i++) {
- listView.setItemChecked(mSelectedArray[i], true);
- }
- } else {
- listView.setOnItemClickListener(new OnItemClickListener() {
- @Override
- public void onItemClick(AdapterView<?> parent, View v,
- int position, long id) {
- // Rather than sending the message right away, send it
- // after the page regains focus.
- mListBoxMessage = Message.obtain(null,
- EventHub.SINGLE_LISTBOX_CHOICE, (int) id, 0);
- if (mListBoxDialog != null) {
- mListBoxDialog.dismiss();
- mListBoxDialog = null;
- }
- }
- });
- if (mSelection != -1) {
- listView.setSelection(mSelection);
- listView.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
- listView.setItemChecked(mSelection, true);
- DataSetObserver observer = new SingleDataSetObserver(
- adapter.getItemId(mSelection), listView, adapter);
- adapter.registerDataSetObserver(observer);
- }
- }
- mListBoxDialog.setOnCancelListener(new DialogInterface.OnCancelListener() {
- @Override
- public void onCancel(DialogInterface dialog) {
- if (mWebViewCore != null) {
- mWebViewCore.sendMessage(
- EventHub.SINGLE_LISTBOX_CHOICE, -2, 0);
- }
- mListBoxDialog = null;
- }
- });
- mListBoxDialog.show();
- }
- }
-
- private Message mListBoxMessage;
-
- /*
- * Request a dropdown menu for a listbox with multiple selection.
- *
- * @param array Labels for the listbox.
- * @param enabledArray State for each element in the list. See static
- * integers in Container class.
- * @param selectedArray Which positions are initally selected.
- */
- void requestListBox(String[] array, int[] enabledArray, int[]
- selectedArray) {
- mPrivateHandler.post(
- new InvokeListBox(array, enabledArray, selectedArray));
- }
-
- /*
- * Request a dropdown menu for a listbox with single selection or a single
- * <select> element.
- *
- * @param array Labels for the listbox.
- * @param enabledArray State for each element in the list. See static
- * integers in Container class.
- * @param selection Which position is initally selected.
- */
- void requestListBox(String[] array, int[] enabledArray, int selection) {
- mPrivateHandler.post(
- new InvokeListBox(array, enabledArray, selection));
- }
-
- private int getScaledMaxXScroll() {
- int width;
- if (mHeightCanMeasure == false) {
- width = getViewWidth() / 4;
- } else {
- Rect visRect = new Rect();
- calcOurVisibleRect(visRect);
- width = visRect.width() / 2;
- }
- // FIXME the divisor should be retrieved from somewhere
- return viewToContentX(width);
- }
-
- private int getScaledMaxYScroll() {
- int height;
- if (mHeightCanMeasure == false) {
- height = getViewHeight() / 4;
- } else {
- Rect visRect = new Rect();
- calcOurVisibleRect(visRect);
- height = visRect.height() / 2;
- }
- // FIXME the divisor should be retrieved from somewhere
- // the closest thing today is hard-coded into ScrollView.java
- // (from ScrollView.java, line 363) int maxJump = height/2;
- return Math.round(height * mZoomManager.getInvScale());
- }
-
- /**
- * Called by JNI to invalidate view
- */
- private void viewInvalidate() {
- invalidate();
- }
-
- /**
- * Pass the key directly to the page. This assumes that
- * nativePageShouldHandleShiftAndArrows() returned true.
- */
- private void letPageHandleNavKey(int keyCode, long time, boolean down, int metaState) {
- int keyEventAction;
- if (down) {
- keyEventAction = KeyEvent.ACTION_DOWN;
- } else {
- keyEventAction = KeyEvent.ACTION_UP;
- }
-
- KeyEvent event = new KeyEvent(time, time, keyEventAction, keyCode,
- 1, (metaState & KeyEvent.META_SHIFT_ON)
- | (metaState & KeyEvent.META_ALT_ON)
- | (metaState & KeyEvent.META_SYM_ON)
- , KeyCharacterMap.VIRTUAL_KEYBOARD, 0, 0);
- sendKeyEvent(event);
- }
-
- private void sendKeyEvent(KeyEvent event) {
- int direction = 0;
- switch (event.getKeyCode()) {
- case KeyEvent.KEYCODE_DPAD_DOWN:
- direction = View.FOCUS_DOWN;
- break;
- case KeyEvent.KEYCODE_DPAD_UP:
- direction = View.FOCUS_UP;
- break;
- case KeyEvent.KEYCODE_DPAD_LEFT:
- direction = View.FOCUS_LEFT;
- break;
- case KeyEvent.KEYCODE_DPAD_RIGHT:
- direction = View.FOCUS_RIGHT;
- break;
- case KeyEvent.KEYCODE_TAB:
- direction = event.isShiftPressed() ? View.FOCUS_BACKWARD : View.FOCUS_FORWARD;
- break;
- }
- if (direction != 0 && mWebView.focusSearch(direction) == null) {
- // Can't take focus in that direction
- direction = 0;
- }
- int eventHubAction = EventHub.KEY_UP;
- if (event.getAction() == KeyEvent.ACTION_DOWN) {
- eventHubAction = EventHub.KEY_DOWN;
- int sound = keyCodeToSoundsEffect(event.getKeyCode());
- if (sound != 0) {
- mWebView.playSoundEffect(sound);
- }
- }
- sendBatchableInputMessage(eventHubAction, direction, 0, event);
- }
-
- /**
- * See {@link WebView#setBackgroundColor(int)}
- */
- @Override
- public void setBackgroundColor(int color) {
- mBackgroundColor = color;
- mWebViewCore.sendMessage(EventHub.SET_BACKGROUND_COLOR, color);
- }
-
- /**
- * Enable the communication b/t the webView and VideoViewProxy
- *
- * only used by the Browser
- */
- public void setHTML5VideoViewProxy(HTML5VideoViewProxy proxy) {
- mHTML5VideoViewProxy = proxy;
- }
-
- /**
- * Set the time to wait between passing touches to WebCore. See also the
- * TOUCH_SENT_INTERVAL member for further discussion.
- *
- * This is only used by the DRT test application.
- */
- public void setTouchInterval(int interval) {
- mCurrentTouchInterval = interval;
- }
-
- /**
- * Copy text into the clipboard. This is called indirectly from
- * WebViewCore.
- * @param text The text to put into the clipboard.
- */
- private void copyToClipboard(String text) {
- ClipboardManager cm = (ClipboardManager)mContext
- .getSystemService(Context.CLIPBOARD_SERVICE);
- ClipData clip = ClipData.newPlainText(getTitle(), text);
- cm.setPrimaryClip(clip);
- }
-
- /*package*/ void autoFillForm(int autoFillQueryId) {
- mPrivateHandler.obtainMessage(AUTOFILL_FORM, autoFillQueryId, 0)
- .sendToTarget();
- }
-
- /* package */ ViewManager getViewManager() {
- return mViewManager;
- }
-
- /** send content invalidate */
- protected void contentInvalidateAll() {
- if (mWebViewCore != null && !mBlockWebkitViewMessages) {
- mWebViewCore.sendMessage(EventHub.CONTENT_INVALIDATE_ALL);
- }
- }
-
- /** discard all textures from tiles. Used in Profiled WebView */
- public void discardAllTextures() {
- nativeDiscardAllTextures();
- }
-
- @Override
- public void setLayerType(int layerType, Paint paint) {
- updateHwAccelerated();
- }
-
- @Override
- public void preDispatchDraw(Canvas canvas) {
- // no-op for WebViewClassic.
- }
-
- private void updateHwAccelerated() {
- if (mNativeClass == 0) {
- return;
- }
- boolean hwAccelerated = false;
- if (mWebView.isHardwareAccelerated()
- && mWebView.getLayerType() != View.LAYER_TYPE_SOFTWARE) {
- hwAccelerated = true;
- }
-
- // result is of type LayerAndroid::InvalidateFlags, non zero means invalidate/redraw
- int result = nativeSetHwAccelerated(mNativeClass, hwAccelerated);
- if (mWebViewCore != null && !mBlockWebkitViewMessages && result != 0) {
- mWebViewCore.contentDraw();
- }
- }
-
- /**
- * Begin collecting per-tile profiling data
- *
- * only used by profiling tests
- */
- public void tileProfilingStart() {
- nativeTileProfilingStart();
- }
- /**
- * Return per-tile profiling data
- *
- * only used by profiling tests
- */
- public float tileProfilingStop() {
- return nativeTileProfilingStop();
- }
-
- /** only used by profiling tests */
- public void tileProfilingClear() {
- nativeTileProfilingClear();
- }
- /** only used by profiling tests */
- public int tileProfilingNumFrames() {
- return nativeTileProfilingNumFrames();
- }
- /** only used by profiling tests */
- public int tileProfilingNumTilesInFrame(int frame) {
- return nativeTileProfilingNumTilesInFrame(frame);
- }
- /** only used by profiling tests */
- public int tileProfilingGetInt(int frame, int tile, String key) {
- return nativeTileProfilingGetInt(frame, tile, key);
- }
- /** only used by profiling tests */
- public float tileProfilingGetFloat(int frame, int tile, String key) {
- return nativeTileProfilingGetFloat(frame, tile, key);
- }
-
- /**
- * Checks the focused content for an editable text field. This can be
- * text input or ContentEditable.
- * @return true if the focused item is an editable text field.
- */
- boolean focusCandidateIsEditableText() {
- if (mFocusedNode != null) {
- return mFocusedNode.mEditable;
- }
- return false;
- }
-
- // Called via JNI
- private void postInvalidate() {
- mWebView.postInvalidate();
- }
-
- // Note: must be called before first WebViewClassic is created.
- public static void setShouldMonitorWebCoreThread() {
- WebViewCore.setShouldMonitorWebCoreThread();
- }
-
- @Override
- public void dumpViewHierarchyWithProperties(BufferedWriter out, int level) {
- int layer = getBaseLayer();
- if (layer != 0) {
- try {
- ByteArrayOutputStream stream = new ByteArrayOutputStream();
- ViewStateSerializer.dumpLayerHierarchy(layer, stream, level);
- stream.close();
- byte[] buf = stream.toByteArray();
- out.write(new String(buf, "ascii"));
- } catch (IOException e) {}
- }
- }
-
- @Override
- public View findHierarchyView(String className, int hashCode) {
- if (mNativeClass == 0) return null;
- Picture pic = new Picture();
- if (!nativeDumpLayerContentToPicture(mNativeClass, className, hashCode, pic)) {
- return null;
- }
- return new PictureWrapperView(getContext(), pic, mWebView);
- }
-
- private static class PictureWrapperView extends View {
- Picture mPicture;
- WebView mWebView;
-
- public PictureWrapperView(Context context, Picture picture, WebView parent) {
- super(context);
- mPicture = picture;
- mWebView = parent;
- setWillNotDraw(false);
- setRight(mPicture.getWidth());
- setBottom(mPicture.getHeight());
- }
-
- @Override
- protected void onDraw(Canvas canvas) {
- canvas.drawPicture(mPicture);
- }
-
- @Override
- public boolean post(Runnable action) {
- return mWebView.post(action);
- }
- }
-
- private native void nativeCreate(int ptr, String drawableDir, boolean isHighEndGfx);
- private native void nativeDebugDump();
- private static native void nativeDestroy(int ptr);
-
- private native void nativeDraw(Canvas canvas, RectF visibleRect,
- int color, int extra);
- private native void nativeDumpDisplayTree(String urlOrNull);
- private native boolean nativeEvaluateLayersAnimations(int nativeInstance);
- private native int nativeCreateDrawGLFunction(int nativeInstance, Rect invScreenRect,
- Rect screenRect, RectF visibleContentRect, float scale, int extras);
- private native int nativeGetDrawGLFunction(int nativeInstance);
- private native void nativeUpdateDrawGLFunction(int nativeInstance, Rect invScreenRect,
- Rect screenRect, RectF visibleContentRect, float scale);
- private native String nativeGetSelection();
- private native void nativeSetHeightCanMeasure(boolean measure);
- private native boolean nativeSetBaseLayer(int nativeInstance,
- int layer, boolean showVisualIndicator, boolean isPictureAfterFirstLayout,
- int scrollingLayer);
- private native int nativeGetBaseLayer(int nativeInstance);
- private native void nativeCopyBaseContentToPicture(Picture pict);
- private native boolean nativeDumpLayerContentToPicture(int nativeInstance,
- String className, int layerId, Picture pict);
- private native boolean nativeHasContent();
- private native void nativeStopGL(int ptr);
- private native void nativeDiscardAllTextures();
- private native void nativeTileProfilingStart();
- private native float nativeTileProfilingStop();
- private native void nativeTileProfilingClear();
- private native int nativeTileProfilingNumFrames();
- private native int nativeTileProfilingNumTilesInFrame(int frame);
- private native int nativeTileProfilingGetInt(int frame, int tile, String key);
- private native float nativeTileProfilingGetFloat(int frame, int tile, String key);
-
- private native void nativeUseHardwareAccelSkia(boolean enabled);
-
- // Returns a pointer to the scrollable LayerAndroid at the given point.
- private native int nativeScrollableLayer(int nativeInstance, int x, int y, Rect scrollRect,
- Rect scrollBounds);
- /**
- * Scroll the specified layer.
- * @param nativeInstance Native WebView instance
- * @param layer Id of the layer to scroll, as determined by nativeScrollableLayer.
- * @param newX Destination x position to which to scroll.
- * @param newY Destination y position to which to scroll.
- * @return True if the layer is successfully scrolled.
- */
- private native boolean nativeScrollLayer(int nativeInstance, int layer, int newX, int newY);
- private native void nativeSetIsScrolling(boolean isScrolling);
- private native int nativeGetBackgroundColor(int nativeInstance);
- native boolean nativeSetProperty(String key, String value);
- native String nativeGetProperty(String key);
- /**
- * See {@link ComponentCallbacks2} for the trim levels and descriptions
- */
- private static native void nativeOnTrimMemory(int level);
- private static native void nativeSetPauseDrawing(int instance, boolean pause);
- private static native void nativeSetTextSelection(int instance, int selection);
- private static native int nativeGetHandleLayerId(int instance, int handle,
- Point cursorLocation, QuadF textQuad);
- private static native void nativeMapLayerRect(int instance, int layerId,
- Rect rect);
- // Returns 1 if a layer sync is needed, else 0
- private static native int nativeSetHwAccelerated(int instance, boolean hwAccelerated);
- private static native void nativeFindMaxVisibleRect(int instance, int layerId,
- Rect visibleContentRect);
- private static native boolean nativeIsHandleLeft(int instance, int handleId);
- private static native boolean nativeIsPointVisible(int instance,
- int layerId, int contentX, int contentY);
-}
+++ /dev/null
-/*
- * Copyright (C) 2012 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.net.http.SslError;
-
-/**
- * Adds WebViewClassic specific extension methods to the WebViewClient callback class.
- * These are not part of the public WebView API, so the class is hidden.
- * @hide
- */
-public class WebViewClientClassicExt extends WebViewClient {
-
- /**
- * Notify the host application that an SSL error occurred while loading a
- * resource, but the WebView chose to proceed anyway based on a
- * decision retained from a previous response to onReceivedSslError().
- */
- public void onProceededAfterSslError(WebView view, SslError error) {
- }
-
- /**
- * Notify the host application to handle a SSL client certificate
- * request (display the request to the user and ask whether to
- * proceed with a client certificate or not). The host application
- * has to call either handler.cancel() or handler.proceed() as the
- * connection is suspended and waiting for the response. The
- * default behavior is to cancel, returning no client certificate.
- *
- * @param view The WebView that is initiating the callback.
- * @param handler A ClientCertRequestHandler object that will
- * handle the user's response.
- * @param host_and_port The host and port of the requesting server.
- */
- public void onReceivedClientCertRequest(WebView view,
- ClientCertRequestHandler handler, String host_and_port) {
- handler.cancel();
- }
-}
+++ /dev/null
-/*
- * Copyright (C) 2007 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.app.ActivityManager;
-import android.content.Context;
-import android.content.pm.PackageManager.NameNotFoundException;
-import android.database.Cursor;
-import android.graphics.Point;
-import android.graphics.Rect;
-import android.media.MediaFile;
-import android.net.ProxyProperties;
-import android.net.Uri;
-import android.net.http.CertificateChainValidator;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.Message;
-import android.os.Process;
-import android.provider.MediaStore;
-import android.util.Log;
-import android.util.SparseBooleanArray;
-import android.view.KeyEvent;
-import android.view.MotionEvent;
-import android.view.SurfaceView;
-import android.view.View;
-import android.webkit.WebViewClassic.FocusNodeHref;
-import android.webkit.WebViewInputDispatcher.WebKitCallbacks;
-
-import com.android.internal.os.SomeArgs;
-
-import junit.framework.Assert;
-
-import java.io.OutputStream;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Iterator;
-import java.util.LinkedList;
-import java.util.Map;
-import java.util.Set;
-
-/**
- * @hide
- */
-public final class WebViewCore {
-
- private static final String LOGTAG = "webcore";
-
- static {
- // Load libwebcore and libchromium_net during static initialization.
- // This happens in the zygote process so they will be shared read-only
- // across all app processes.
- try {
- System.loadLibrary("webcore");
- System.loadLibrary("chromium_net");
- } catch (UnsatisfiedLinkError e) {
- Log.e(LOGTAG, "Unable to load native support libraries.");
- }
- }
-
- /*
- * WebViewCore always executes in the same thread as the native webkit.
- */
-
- // The WebViewClassic that corresponds to this WebViewCore.
- private WebViewClassic mWebViewClassic;
- // Proxy for handling callbacks from native code
- private final CallbackProxy mCallbackProxy;
- // Settings object for maintaining all settings
- private final WebSettingsClassic mSettings;
- // Context for initializing the BrowserFrame with the proper assets.
- private final Context mContext;
- // The pointer to a native view object.
- private int mNativeClass;
- // The BrowserFrame is an interface to the native Frame component.
- private BrowserFrame mBrowserFrame;
- // Custom JS interfaces to add during the initialization.
- private Map<String, Object> mJavascriptInterfaces;
- /*
- * range is from 200 to 10,000. 0 is a special value means device-width. -1
- * means undefined.
- */
- private int mViewportWidth = -1;
-
- /*
- * range is from 200 to 10,000. 0 is a special value means device-height. -1
- * means undefined.
- */
- private int mViewportHeight = -1;
-
- /*
- * scale in percent, range is from 1 to 1000. 0 means undefined.
- */
- private int mViewportInitialScale = 0;
-
- /*
- * scale in percent, range is from 1 to 1000. 0 means undefined.
- */
- private int mViewportMinimumScale = 0;
-
- /*
- * scale in percent, range is from 1 to 1000. 0 means undefined.
- */
- private int mViewportMaximumScale = 0;
-
- private boolean mViewportUserScalable = true;
-
- /*
- * range is from 70 to 400.
- * 0 is a special value means device-dpi. The default scale factor will be
- * always 100.
- * -1 means undefined. The default scale factor will be
- * WebView.DEFAULT_SCALE_PERCENT.
- */
- private int mViewportDensityDpi = -1;
-
- private boolean mIsRestored = false;
- private float mRestoredScale = 0;
- private float mRestoredTextWrapScale = 0;
- private int mRestoredX = 0;
- private int mRestoredY = 0;
-
- private MockGeolocation mMockGeolocation = new MockGeolocation(this);
-
- private DeviceMotionAndOrientationManager mDeviceMotionAndOrientationManager =
- new DeviceMotionAndOrientationManager(this);
- private DeviceMotionService mDeviceMotionService;
- private DeviceOrientationService mDeviceOrientationService;
-
- private int mLowMemoryUsageThresholdMb;
- private int mHighMemoryUsageThresholdMb;
- private int mHighUsageDeltaMb;
-
- private int mChromeCanFocusDirection;
- private int mTextSelectionChangeReason = TextSelectionData.REASON_UNKNOWN;
-
- // Used to determine if we should monitor the WebCore thread for responsiveness.
- // If it "hangs", for example a web page enters a while(true) loop, we will
- // prompt the user with a dialog allowing them to terminate the process.
- private static boolean sShouldMonitorWebCoreThread;
-
- // The thread name used to identify the WebCore thread and for use in
- // debugging other classes that require operation within the WebCore thread.
- /* package */ static final String THREAD_NAME = "WebViewCoreThread";
-
- public WebViewCore(Context context, WebViewClassic w, CallbackProxy proxy,
- Map<String, Object> javascriptInterfaces) {
- // No need to assign this in the WebCore thread.
- mCallbackProxy = proxy;
- mWebViewClassic = w;
- mJavascriptInterfaces = javascriptInterfaces;
- // This context object is used to initialize the WebViewCore during
- // subwindow creation.
- mContext = context;
-
- // We need to wait for the initial thread creation before sending
- // a message to the WebCore thread.
- // XXX: This is the only time the UI thread will wait for the WebCore
- // thread!
- synchronized (WebViewCore.class) {
- if (sWebCoreHandler == null) {
- // Create a global thread and start it.
- Thread t = new Thread(new WebCoreThread());
- t.setName(THREAD_NAME);
- t.start();
- try {
- WebViewCore.class.wait();
- } catch (InterruptedException e) {
- Log.e(LOGTAG, "Caught exception while waiting for thread " +
- "creation.");
- Log.e(LOGTAG, Log.getStackTraceString(e));
- }
-
- if (sShouldMonitorWebCoreThread) {
- // Start the singleton watchdog which will monitor the WebCore thread
- // to verify it's still processing messages. Note that this is the only
- // time we need to check the value as all the other public methods on
- // the WebCoreThreadWatchdog are no-ops if start() is not called.
- WebCoreThreadWatchdog.start(sWebCoreHandler);
- }
- }
- // Make sure the Watchdog is aware of this new WebView.
- WebCoreThreadWatchdog.registerWebView(w);
- }
- // Create an EventHub to handle messages before and after the thread is
- // ready.
- mEventHub = new EventHub();
- // Create a WebSettings object for maintaining all settings
- mSettings = new WebSettingsClassic(mContext, mWebViewClassic);
- // The WebIconDatabase needs to be initialized within the UI thread so
- // just request the instance here.
- WebIconDatabase.getInstance();
- // Create the WebStorageClassic singleton and the UI handler
- WebStorageClassic.getInstance().createUIHandler();
- // Create the UI handler for GeolocationPermissions
- GeolocationPermissionsClassic.getInstance().createUIHandler();
-
- // Get the memory class of the current device. V8 will use these values
- // to GC more effectively.
- ActivityManager manager = (ActivityManager) mContext.getSystemService(
- Context.ACTIVITY_SERVICE);
- ActivityManager.MemoryInfo memInfo = new ActivityManager.MemoryInfo();
- manager.getMemoryInfo(memInfo);
-
- // Allow us to use up to our memory class value before V8's GC kicks in.
- // These values have been determined by experimentation.
- mLowMemoryUsageThresholdMb = manager.getLargeMemoryClass();
- mHighMemoryUsageThresholdMb = (int) (mLowMemoryUsageThresholdMb * 1.5);
- // Avoid constant V8 GC when memory usage equals to working set estimate.
- mHighUsageDeltaMb = mLowMemoryUsageThresholdMb / 32;
-
- // Send a message to initialize the WebViewCore.
- Message init = sWebCoreHandler.obtainMessage(
- WebCoreThread.INITIALIZE, this);
- sWebCoreHandler.sendMessage(init);
- }
-
- /* Initialize private data within the WebCore thread.
- */
- private void initialize() {
- /* Initialize our private BrowserFrame class to handle all
- * frame-related functions. We need to create a new view which
- * in turn creates a C level FrameView and attaches it to the frame.
- */
- mBrowserFrame = new BrowserFrame(mContext, this, mCallbackProxy,
- mSettings, mJavascriptInterfaces);
- mJavascriptInterfaces = null;
- // Sync the native settings and also create the WebCore thread handler.
- mSettings.syncSettingsAndCreateHandler(mBrowserFrame);
- // Create the handler and transfer messages for the IconDatabase
- WebIconDatabaseClassic.getInstance().createHandler();
- // Create the handler for WebStorageClassic
- WebStorageClassic.getInstance().createHandler();
- // Create the handler for GeolocationPermissions.
- GeolocationPermissionsClassic.getInstance().createHandler();
- // The transferMessages call will transfer all pending messages to the
- // WebCore thread handler.
- mEventHub.transferMessages();
-
- // Send a message back to WebView to tell it that we have set up the
- // WebCore thread.
- if (mWebViewClassic != null) {
- Message.obtain(mWebViewClassic.mPrivateHandler,
- WebViewClassic.WEBCORE_INITIALIZED_MSG_ID,
- mNativeClass, 0).sendToTarget();
- }
-
- }
-
- /* Handle the initialization of WebViewCore during subwindow creation. This
- * method is called from the WebCore thread but it is called before the
- * INITIALIZE message can be handled.
- */
- /* package */ void initializeSubwindow() {
- // Go ahead and initialize the core components.
- initialize();
- // Remove the INITIALIZE method so we don't try to initialize twice.
- sWebCoreHandler.removeMessages(WebCoreThread.INITIALIZE, this);
- }
-
- /* Get the BrowserFrame component. This is used for subwindow creation and
- * is called only from BrowserFrame in the WebCore thread. */
- /* package */ synchronized BrowserFrame getBrowserFrame() {
- return mBrowserFrame;
- }
-
- public WebKitCallbacks getInputDispatcherCallbacks() {
- return mEventHub;
- }
-
- //-------------------------------------------------------------------------
- // Common methods
- //-------------------------------------------------------------------------
-
- /**
- * Causes all timers to pause. This applies to all WebViews in the current
- * app process.
- */
- public static void pauseTimers() {
- if (BrowserFrame.sJavaBridge == null) {
- throw new IllegalStateException(
- "No WebView has been created in this process!");
- }
- BrowserFrame.sJavaBridge.pause();
- }
-
- /**
- * Resume all timers. This applies to all WebViews in the current process.
- */
- public static void resumeTimers() {
- if (BrowserFrame.sJavaBridge == null) {
- throw new IllegalStateException(
- "No WebView has been created in this process!");
- }
- BrowserFrame.sJavaBridge.resume();
- }
-
- public WebSettingsClassic getSettings() {
- return mSettings;
- }
-
- /*
- * Given mimeType, check whether it's supported in Android media framework.
- * mimeType could be such as "audio/ogg" and "video/mp4".
- */
- /* package */ static boolean isSupportedMediaMimeType(String mimeType) {
- int fileType = MediaFile.getFileTypeForMimeType(mimeType);
- return MediaFile.isAudioFileType(fileType)
- || MediaFile.isVideoFileType(fileType)
- || MediaFile.isPlayListFileType(fileType)
- // The following is not in Media framework, but it's supported.
- || (mimeType != null && mimeType.startsWith("video/m4v"));
- }
-
- /**
- * Add an error message to the client's console.
- * @param message The message to add
- * @param lineNumber the line on which the error occurred
- * @param sourceID the filename of the source that caused the error.
- * @param msgLevel the log level of this message. This is a value casted to int
- * from WebCore::MessageLevel in WebCore/page/Console.h.
- */
- protected void addMessageToConsole(String message, int lineNumber, String sourceID,
- int msgLevel) {
- mCallbackProxy.addMessageToConsole(message, lineNumber, sourceID, msgLevel);
- }
-
- /**
- * Invoke a javascript alert.
- * @param message The message displayed in the alert.
- */
- protected void jsAlert(String url, String message) {
- mCallbackProxy.onJsAlert(url, message);
- }
-
- /**
- * Called by JNI when the focus node changed.
- */
- private void focusNodeChanged(int nodePointer, WebKitHitTest hitTest) {
- if (mWebViewClassic == null) return;
- mWebViewClassic.mPrivateHandler.obtainMessage(WebViewClassic.FOCUS_NODE_CHANGED,
- nodePointer, 0, hitTest).sendToTarget();
- }
-
- /**
- * Called by JNI to advance focus to the next view.
- */
- private void chromeTakeFocus(int webkitDirection) {
- if (mWebViewClassic == null) return;
- Message m = mWebViewClassic.mPrivateHandler.obtainMessage(
- WebViewClassic.TAKE_FOCUS);
- m.arg1 = mapDirection(webkitDirection);
- m.sendToTarget();
- }
-
- /**
- * Called by JNI to see if we can take focus in the given direction.
- */
- private boolean chromeCanTakeFocus(int webkitDirection) {
- int direction = mapDirection(webkitDirection);
- return direction == mChromeCanFocusDirection && direction != 0;
- }
-
- /**
- * Maps a Webkit focus direction to a framework one
- */
- private int mapDirection(int webkitDirection) {
- /*
- * This is WebKit's FocusDirection enum (from FocusDirection.h)
- enum FocusDirection {
- FocusDirectionNone = 0,
- FocusDirectionForward,
- FocusDirectionBackward,
- FocusDirectionUp,
- FocusDirectionDown,
- FocusDirectionLeft,
- FocusDirectionRight
- };
- */
- switch (webkitDirection) {
- case 1:
- return View.FOCUS_FORWARD;
- case 2:
- return View.FOCUS_BACKWARD;
- case 3:
- return View.FOCUS_UP;
- case 4:
- return View.FOCUS_DOWN;
- case 5:
- return View.FOCUS_LEFT;
- case 6:
- return View.FOCUS_RIGHT;
- }
- return 0;
- }
-
- /**
- * Called by JNI. Open a file chooser to upload a file.
- * @param acceptType The value of the 'accept' attribute of the
- * input tag associated with this file picker.
- * @param capture The value of the 'capture' attribute of the
- * input tag associated with this file picker.
- * @return String version of the URI.
- */
- private String openFileChooser(String acceptType, String capture) {
- Uri uri = mCallbackProxy.openFileChooser(acceptType, capture);
- if (uri != null) {
- String filePath = "";
- // Note - querying for MediaStore.Images.Media.DATA
- // seems to work for all content URIs, not just images
- Cursor cursor = mContext.getContentResolver().query(
- uri,
- new String[] { MediaStore.Images.Media.DATA },
- null, null, null);
- if (cursor != null) {
- try {
- if (cursor.moveToNext()) {
- filePath = cursor.getString(0);
- }
- } finally {
- cursor.close();
- }
- } else {
- filePath = uri.getLastPathSegment();
- }
- String uriString = uri.toString();
- BrowserFrame.sJavaBridge.storeFilePathForContentUri(filePath, uriString);
- return uriString;
- }
- return "";
- }
-
- /**
- * Notify the embedding application that the origin has exceeded it's database quota.
- * @param url The URL that caused the overflow.
- * @param databaseIdentifier The identifier of the database.
- * @param quota The current quota for the origin.
- * @param estimatedDatabaseSize The estimated size of the database.
- */
- protected void exceededDatabaseQuota(String url,
- String databaseIdentifier,
- long quota,
- long estimatedDatabaseSize) {
- // Inform the callback proxy of the quota overflow. Send an object
- // that encapsulates a call to the nativeSetDatabaseQuota method to
- // awaken the sleeping webcore thread when a decision from the
- // client to allow or deny quota is available.
- mCallbackProxy.onExceededDatabaseQuota(url, databaseIdentifier,
- quota, estimatedDatabaseSize, getUsedQuota(),
- new WebStorage.QuotaUpdater() {
- @Override
- public void updateQuota(long newQuota) {
- nativeSetNewStorageLimit(mNativeClass, newQuota);
- }
- });
- }
-
- /**
- * Notify the embedding application that the appcache has reached or exceeded its maximum
- * allowed storage size.
- *
- * @param requiredStorage is the amount of storage, in bytes, that would be
- * needed in order for the last appcache operation to succeed.
- * @param maxSize maximum allowed Application Cache database size, in bytes.
- */
- protected void reachedMaxAppCacheSize(long requiredStorage, long maxSize) {
- mCallbackProxy.onReachedMaxAppCacheSize(requiredStorage, maxSize,
- new WebStorage.QuotaUpdater() {
- @Override
- public void updateQuota(long newQuota) {
- nativeSetNewStorageLimit(mNativeClass, newQuota);
- }
- });
- }
-
- protected void populateVisitedLinks() {
- ValueCallback callback = new ValueCallback<String[]>() {
- @Override
- public void onReceiveValue(String[] value) {
- sendMessage(EventHub.POPULATE_VISITED_LINKS, (Object)value);
- }
- };
- mCallbackProxy.getVisitedHistory(callback);
- }
-
- /**
- * Shows a prompt to ask the user to set the Geolocation permission state
- * for the given origin.
- * @param origin The origin for which Geolocation permissions are
- * requested.
- */
- protected void geolocationPermissionsShowPrompt(String origin) {
- mCallbackProxy.onGeolocationPermissionsShowPrompt(origin,
- new GeolocationPermissions.Callback() {
- @Override
- public void invoke(String origin, boolean allow, boolean remember) {
- GeolocationPermissionsData data = new GeolocationPermissionsData();
- data.mOrigin = origin;
- data.mAllow = allow;
- data.mRemember = remember;
- // Marshall to WebCore thread.
- sendMessage(EventHub.GEOLOCATION_PERMISSIONS_PROVIDE, data);
- }
- });
- }
-
- /**
- * Hides the Geolocation permissions prompt.
- */
- protected void geolocationPermissionsHidePrompt() {
- mCallbackProxy.onGeolocationPermissionsHidePrompt();
- }
-
- /**
- * Invoke a javascript confirm dialog.
- * @param message The message displayed in the dialog.
- * @return True if the user confirmed or false if the user cancelled.
- */
- protected boolean jsConfirm(String url, String message) {
- return mCallbackProxy.onJsConfirm(url, message);
- }
-
- /**
- * Invoke a javascript prompt dialog.
- * @param message The message to be displayed in the dialog.
- * @param defaultValue The default value in the prompt input.
- * @return The input from the user or null to indicate the user cancelled
- * the dialog.
- */
- protected String jsPrompt(String url, String message, String defaultValue) {
- return mCallbackProxy.onJsPrompt(url, message, defaultValue);
- }
-
- /**
- * Invoke a javascript before unload dialog.
- * @param url The url that is requesting the dialog.
- * @param message The message displayed in the dialog.
- * @return True if the user confirmed or false if the user cancelled. False
- * will cancel the navigation.
- */
- protected boolean jsUnload(String url, String message) {
- return mCallbackProxy.onJsBeforeUnload(url, message);
- }
-
- /**
- *
- * Callback to notify that a JavaScript execution timeout has occured.
- * @return True if the JavaScript execution should be interrupted. False
- * will continue the execution.
- */
- protected boolean jsInterrupt() {
- return mCallbackProxy.onJsTimeout();
- }
-
- /**
- * Notify the webview that we want to exit the video fullscreen.
- * This is called through JNI by webcore.
- */
- protected void exitFullscreenVideo() {
- if (mWebViewClassic == null) return;
- Message message = Message.obtain(mWebViewClassic.mPrivateHandler,
- WebViewClassic.EXIT_FULLSCREEN_VIDEO);
- message.sendToTarget();
- }
-
- /**
- * Clear the picture set. To be called only on the WebCore thread.
- */
- /* package */ void clearContent() {
- nativeClearContent(mNativeClass);
- }
-
- //-------------------------------------------------------------------------
- // JNI methods
- //-------------------------------------------------------------------------
-
- static native String nativeFindAddress(String addr, boolean caseInsensitive);
-
- /**
- * Empty the picture set.
- */
- private native void nativeClearContent(int nativeClass);
-
- private native void nativeContentInvalidateAll(int nativeClass);
-
- /**
- * Redraw a portion of the picture set. The Point wh returns the
- * width and height of the overall picture.
- */
- private native int nativeRecordContent(int nativeClass, Point wh);
-
- /**
- * Notify webkit that animations have begun (on the hardware accelerated content)
- */
- private native void nativeNotifyAnimationStarted(int nativeClass);
-
- private native boolean nativeKey(int nativeClass, int keyCode,
- int unichar, int repeatCount, boolean isShift, boolean isAlt,
- boolean isSym, boolean isDown);
-
- private native void nativeSendListBoxChoices(int nativeClass,
- boolean[] choices, int size);
-
- private native void nativeSendListBoxChoice(int nativeClass, int choice);
-
- private native void nativeCloseIdleConnections(int nativeClass);
-
- /* Tell webkit what its width and height are, for the purposes
- of layout/line-breaking. These coordinates are in document space,
- which is the same as View coords unless we have zoomed the document
- (see nativeSetZoom).
- textWrapWidth is used by layout to wrap column around. If viewport uses
- fixed size, textWrapWidth can be different from width with zooming.
- should this be called nativeSetViewPortSize?
- */
- private native void nativeSetSize(int nativeClass, int width, int height,
- int textWrapWidth, float scale, int screenWidth, int screenHeight,
- int anchorX, int anchorY, boolean ignoreHeight);
-
- private native int nativeGetContentMinPrefWidth(int nativeClass);
-
- // Start: functions that deal with text editing
- private native void nativeReplaceTextfieldText(
- int nativeClass, int oldStart, int oldEnd, String replace,
- int newStart, int newEnd, int textGeneration);
-
- private native void passToJs(int nativeClass,
- int gen, String currentText, int keyCode, int keyValue,
- boolean down, boolean cap, boolean fn, boolean sym);
-
- private native void nativeSetFocusControllerActive(int nativeClass,
- boolean active);
-
- private native void nativeSaveDocumentState(int nativeClass);
-
- private native void nativeMoveMouse(int nativeClass, int x, int y);
-
- private native String nativeRetrieveHref(int nativeClass, int x, int y);
- private native String nativeRetrieveAnchorText(int nativeClass,
- int x, int y);
- private native String nativeRetrieveImageSource(int nativeClass,
- int x, int y);
- private native boolean nativeMouseClick(int nativeClass);
-
- private native int nativeHandleTouchEvent(int nativeClass, int action,
- int[] idArray, int[] xArray, int[] yArray, int count,
- int actionIndex, int metaState);
-
- private native void nativeSetBackgroundColor(int nativeClass, int color);
-
- private native void nativeDumpDomTree(int nativeClass, boolean useFile);
-
- private native void nativeDumpRenderTree(int nativeClass, boolean useFile);
-
- private native void nativeSetJsFlags(int nativeClass, String flags);
-
- /**
- * Delete text from start to end in the focused textfield. If there is no
- * focus, or if start == end, silently fail. If start and end are out of
- * order, swap them.
- * @param nativeClass Pointer to the C++ WebViewCore object mNativeClass
- * @param start Beginning of selection to delete.
- * @param end End of selection to delete.
- * @param textGeneration Text generation number when delete was pressed.
- */
- private native void nativeDeleteSelection(int nativeClass, int start,
- int end, int textGeneration);
-
- /**
- * Set the selection to (start, end) in the focused textfield. If start and
- * end are out of order, swap them.
- * @param nativeClass Pointer to the C++ WebViewCore object mNativeClass
- * @param start Beginning of selection.
- * @param end End of selection.
- */
- private native void nativeSetSelection(int nativeClass, int start, int end);
-
- // Register a scheme to be treated as local scheme so that it can access
- // local asset files for resources
- private native void nativeRegisterURLSchemeAsLocal(int nativeClass,
- String scheme);
-
- /*
- * Inform webcore that the user has decided whether to allow or deny new
- * quota for the current origin or more space for the app cache, and that
- * the main thread should wake up now.
- * @param limit Is the new quota for an origin or new app cache max size.
- */
- private native void nativeSetNewStorageLimit(int nativeClass, long limit);
-
- /**
- * Provide WebCore with a Geolocation permission state for the specified
- * origin.
- * @param nativeClass Pointer to the C++ WebViewCore object mNativeClass
- * @param origin The origin for which Geolocation permissions are provided.
- * @param allow Whether Geolocation permissions are allowed.
- * @param remember Whether this decision should be remembered beyond the
- * life of the current page.
- */
- private native void nativeGeolocationPermissionsProvide(int nativeClass,
- String origin, boolean allow, boolean remember);
-
- /**
- * Provide WebCore with the previously visted links from the history database
- * @param nativeClass TODO
- */
- private native void nativeProvideVisitedHistory(int nativeClass,
- String[] history);
-
- /**
- * Modifies the current selection.
- *
- * Note: Accessibility support.
- * @param nativeClass Pointer to the C++ WebViewCore object mNativeClass
- * @param direction The direction in which to alter the selection.
- * @param granularity The granularity of the selection modification.
- *
- * @return The selection string.
- */
- private native String nativeModifySelection(int nativeClass, int direction,
- int granularity);
-
- // EventHub for processing messages
- private final EventHub mEventHub;
- // WebCore thread handler
- private static Handler sWebCoreHandler;
- // Class for providing Handler creation inside the WebCore thread.
- private static class WebCoreThread implements Runnable {
- // Message id for initializing a new WebViewCore.
- private static final int INITIALIZE = 0;
- private static final int REDUCE_PRIORITY = 1;
- private static final int RESUME_PRIORITY = 2;
-
- @Override
- public void run() {
- Looper.prepare();
- Assert.assertNull(sWebCoreHandler);
- synchronized (WebViewCore.class) {
- sWebCoreHandler = new Handler() {
- @Override
- public void handleMessage(Message msg) {
- switch (msg.what) {
- case INITIALIZE:
- WebViewCore core = (WebViewCore) msg.obj;
- core.initialize();
- break;
-
- case REDUCE_PRIORITY:
- // 3 is an adjustable number.
- Process.setThreadPriority(
- Process.THREAD_PRIORITY_DEFAULT + 3 *
- Process.THREAD_PRIORITY_LESS_FAVORABLE);
- break;
-
- case RESUME_PRIORITY:
- Process.setThreadPriority(
- Process.THREAD_PRIORITY_DEFAULT);
- break;
-
- case EventHub.ADD_PACKAGE_NAME:
- if (BrowserFrame.sJavaBridge == null) {
- throw new IllegalStateException(
- "No WebView has been created in this process!");
- }
- BrowserFrame.sJavaBridge.addPackageName((String) msg.obj);
- break;
-
- case EventHub.REMOVE_PACKAGE_NAME:
- if (BrowserFrame.sJavaBridge == null) {
- throw new IllegalStateException(
- "No WebView has been created in this process!");
- }
- BrowserFrame.sJavaBridge.removePackageName((String) msg.obj);
- break;
-
- case EventHub.PROXY_CHANGED:
- if (BrowserFrame.sJavaBridge == null) {
- throw new IllegalStateException(
- "No WebView has been created in this process!");
- }
- BrowserFrame.sJavaBridge.updateProxy((ProxyProperties)msg.obj);
- break;
-
- case EventHub.HEARTBEAT:
- // Ping back the watchdog to let it know we're still processing
- // messages.
- Message m = (Message)msg.obj;
- m.sendToTarget();
- break;
- case EventHub.TRUST_STORAGE_UPDATED:
- // post a task to network thread for updating trust manager
- nativeCertTrustChanged();
- CertificateChainValidator.handleTrustStorageUpdate();
- break;
- }
- }
- };
- WebViewCore.class.notify();
- }
- Looper.loop();
- }
- }
-
- static class BaseUrlData {
- String mBaseUrl;
- String mData;
- String mMimeType;
- String mEncoding;
- String mHistoryUrl;
- }
-
- static class JSInterfaceData {
- Object mObject;
- String mInterfaceName;
- boolean mRequireAnnotation;
- }
-
- static class JSKeyData {
- String mCurrentText;
- KeyEvent mEvent;
- }
-
- static class MotionUpData {
- int mFrame;
- int mNode;
- Rect mBounds;
- int mX;
- int mY;
- }
-
- static class GetUrlData {
- String mUrl;
- Map<String, String> mExtraHeaders;
- }
-
- static class PostUrlData {
- String mUrl;
- byte[] mPostData;
- }
-
- static class ReplaceTextData {
- String mReplace;
- int mNewStart;
- int mNewEnd;
- int mTextGeneration;
- }
-
- static class TextSelectionData {
- static final int REASON_UNKNOWN = 0;
- static final int REASON_ACCESSIBILITY_INJECTOR = 1;
- static final int REASON_SELECT_WORD = 2;
- public TextSelectionData(int start, int end, int selectTextPtr) {
- mStart = start;
- mEnd = end;
- mSelectTextPtr = selectTextPtr;
- }
- int mStart;
- int mEnd;
- int mSelectTextPtr;
- int mSelectionReason = TextSelectionData.REASON_UNKNOWN;
- }
-
- static class TouchUpData {
- int mMoveGeneration;
- int mFrame;
- int mNode;
- int mX;
- int mY;
- int mNativeLayer;
- Rect mNativeLayerRect = new Rect();
- }
-
- static class TouchHighlightData {
- int mX;
- int mY;
- int mSlop;
- int mNativeLayer;
- Rect mNativeLayerRect;
- }
-
- static class WebKitHitTest {
- String mLinkUrl;
- String mIntentUrl;
- String mAnchorText;
- String mImageUrl;
- String mAltDisplayString;
- String mTitle;
- Rect[] mTouchRects;
- boolean mEditable;
- int mTapHighlightColor = WebViewClassic.HIGHLIGHT_COLOR;
- Rect[] mEnclosingParentRects;
- boolean mHasFocus;
-
- // These are the input values that produced this hit test
- int mHitTestX;
- int mHitTestY;
- int mHitTestSlop;
- boolean mHitTestMovedMouse;
- }
-
- static class AutoFillData {
- public AutoFillData() {
- mQueryId = WebTextView.FORM_NOT_AUTOFILLABLE;
- mPreview = "";
- }
-
- public AutoFillData(int queryId, String preview) {
- mQueryId = queryId;
- mPreview = preview;
- }
-
- public int getQueryId() {
- return mQueryId;
- }
-
- public String getPreviewString() {
- return mPreview;
- }
-
- private int mQueryId;
- private String mPreview;
- }
-
- static class TextFieldInitData {
- public int mFieldPointer;
- public String mText;
- public int mType;
- public boolean mIsSpellCheckEnabled;
- public boolean mIsTextFieldNext;
- public boolean mIsTextFieldPrev;
- public boolean mIsAutoCompleteEnabled;
- public String mName;
- public String mLabel;
- public int mMaxLength;
- public Rect mContentBounds;
- public int mNodeLayerId;
- public Rect mClientRect;
- }
-
- // mAction of TouchEventData can be MotionEvent.getAction() which uses the
- // last two bytes or one of the following values
- static final int ACTION_LONGPRESS = 0x100;
- static final int ACTION_DOUBLETAP = 0x200;
-
- private static final int TOUCH_FLAG_HIT_HANDLER = 0x1;
- private static final int TOUCH_FLAG_PREVENT_DEFAULT = 0x2;
-
- static class TouchEventData {
- int mAction;
- int[] mIds; // Ids of the touch points
- Point[] mPoints;
- Point[] mPointsInView; // the point coordinates in view axis.
- int mActionIndex; // Associated pointer index for ACTION_POINTER_DOWN/UP
- int mMetaState;
- boolean mReprocess;
- MotionEvent mMotionEvent;
- int mNativeLayer;
- Rect mNativeLayerRect = new Rect();
- long mSequence;
- boolean mNativeResult;
- }
-
- static class GeolocationPermissionsData {
- String mOrigin;
- boolean mAllow;
- boolean mRemember;
- }
-
- static final String[] HandlerDebugString = {
- "REVEAL_SELECTION", // 96
- "", // 97
- "", // = 98
- "SCROLL_TEXT_INPUT", // = 99
- "LOAD_URL", // = 100;
- "STOP_LOADING", // = 101;
- "RELOAD", // = 102;
- "KEY_DOWN", // = 103;
- "KEY_UP", // = 104;
- "VIEW_SIZE_CHANGED", // = 105;
- "GO_BACK_FORWARD", // = 106;
- "SET_SCROLL_OFFSET", // = 107;
- "RESTORE_STATE", // = 108;
- "PAUSE_TIMERS", // = 109;
- "RESUME_TIMERS", // = 110;
- "CLEAR_CACHE", // = 111;
- "CLEAR_HISTORY", // = 112;
- "SET_SELECTION", // = 113;
- "REPLACE_TEXT", // = 114;
- "PASS_TO_JS", // = 115;
- "SET_GLOBAL_BOUNDS", // = 116;
- "", // = 117;
- "CLICK", // = 118;
- "SET_NETWORK_STATE", // = 119;
- "DOC_HAS_IMAGES", // = 120;
- "FAKE_CLICK", // = 121;
- "DELETE_SELECTION", // = 122;
- "LISTBOX_CHOICES", // = 123;
- "SINGLE_LISTBOX_CHOICE", // = 124;
- "MESSAGE_RELAY", // = 125;
- "SET_BACKGROUND_COLOR", // = 126;
- "SET_MOVE_FOCUS", // = 127
- "SAVE_DOCUMENT_STATE", // = 128;
- "129", // = 129;
- "WEBKIT_DRAW", // = 130;
- "131", // = 131;
- "POST_URL", // = 132;
- "", // = 133;
- "CLEAR_CONTENT", // = 134;
- "", // = 135;
- "", // = 136;
- "REQUEST_CURSOR_HREF", // = 137;
- "ADD_JS_INTERFACE", // = 138;
- "LOAD_DATA", // = 139;
- "", // = 140;
- "", // = 141;
- "SET_ACTIVE", // = 142;
- "ON_PAUSE", // = 143
- "ON_RESUME", // = 144
- "FREE_MEMORY", // = 145
- "VALID_NODE_BOUNDS", // = 146
- "SAVE_WEBARCHIVE", // = 147
- "WEBKIT_DRAW_LAYERS", // = 148;
- "REMOVE_JS_INTERFACE", // = 149;
- };
-
- static class FindAllRequest {
- public FindAllRequest(String text) {
- mSearchText = text;
- mMatchCount = -1;
- mMatchIndex = -1;
- }
- public final String mSearchText;
- public int mMatchCount;
- public int mMatchIndex;
- }
-
- static class SaveViewStateRequest {
- SaveViewStateRequest(OutputStream s, ValueCallback<Boolean> cb) {
- mStream = s;
- mCallback = cb;
- }
- public OutputStream mStream;
- public ValueCallback<Boolean> mCallback;
- }
-
- /**
- * @hide
- */
- public class EventHub implements WebViewInputDispatcher.WebKitCallbacks {
- // Message Ids
- static final int REVEAL_SELECTION = 96;
- static final int SCROLL_TEXT_INPUT = 99;
- static final int LOAD_URL = 100;
- static final int STOP_LOADING = 101;
- static final int RELOAD = 102;
- static final int KEY_DOWN = 103;
- static final int KEY_UP = 104;
- static final int VIEW_SIZE_CHANGED = 105;
- static final int GO_BACK_FORWARD = 106;
- static final int SET_SCROLL_OFFSET = 107;
- static final int RESTORE_STATE = 108;
- static final int PAUSE_TIMERS = 109;
- static final int RESUME_TIMERS = 110;
- static final int CLEAR_CACHE = 111;
- static final int CLEAR_HISTORY = 112;
- static final int SET_SELECTION = 113;
- static final int REPLACE_TEXT = 114;
- static final int PASS_TO_JS = 115;
- static final int SET_GLOBAL_BOUNDS = 116;
- static final int SET_NETWORK_STATE = 119;
- static final int DOC_HAS_IMAGES = 120;
- static final int DELETE_SELECTION = 122;
- static final int LISTBOX_CHOICES = 123;
- static final int SINGLE_LISTBOX_CHOICE = 124;
- public static final int MESSAGE_RELAY = 125;
- static final int SET_BACKGROUND_COLOR = 126;
- static final int SAVE_DOCUMENT_STATE = 128;
- static final int DELETE_SURROUNDING_TEXT = 129;
-
-
- static final int WEBKIT_DRAW = 130;
- static final int POST_URL = 132;
- static final int CLEAR_CONTENT = 134;
-
- // UI nav messages
- static final int SET_MOVE_MOUSE = 135;
- static final int REQUEST_CURSOR_HREF = 137;
- static final int ADD_JS_INTERFACE = 138;
- static final int LOAD_DATA = 139;
-
- // Used to tell the focus controller not to draw the blinking cursor,
- // based on whether the WebView has focus and whether the WebView's
- // cursor matches the webpage's focus.
- static final int SET_ACTIVE = 142;
-
- // lifecycle activities for just this DOM (unlike pauseTimers, which
- // is global)
- static final int ON_PAUSE = 143;
- static final int ON_RESUME = 144;
- static final int FREE_MEMORY = 145;
-
- // Load and save web archives
- static final int SAVE_WEBARCHIVE = 147;
-
- static final int REMOVE_JS_INTERFACE = 149;
-
- // Network-based messaging
- static final int CLEAR_SSL_PREF_TABLE = 150;
-
- // Test harness messages
- static final int REQUEST_EXT_REPRESENTATION = 160;
- static final int REQUEST_DOC_AS_TEXT = 161;
-
- // debugging
- static final int DUMP_DOMTREE = 170;
- static final int DUMP_RENDERTREE = 171;
-
- static final int SET_JS_FLAGS = 174;
- static final int CONTENT_INVALIDATE_ALL = 175;
- // Geolocation
- static final int GEOLOCATION_PERMISSIONS_PROVIDE = 180;
-
- static final int POPULATE_VISITED_LINKS = 181;
-
- static final int HIDE_FULLSCREEN = 182;
-
- static final int SET_NETWORK_TYPE = 183;
-
- // navigator.isApplicationInstalled()
- static final int ADD_PACKAGE_NAMES = 184;
- static final int ADD_PACKAGE_NAME = 185;
- static final int REMOVE_PACKAGE_NAME = 186;
-
- // accessibility support
- static final int MODIFY_SELECTION = 190;
-
- static final int SET_USE_MOCK_DEVICE_ORIENTATION = 191;
-
- static final int AUTOFILL_FORM = 192;
-
- static final int PROXY_CHANGED = 193;
-
- static final int EXECUTE_JS = 194;
-
- static final int PLUGIN_SURFACE_READY = 195;
-
- static final int NOTIFY_ANIMATION_STARTED = 196;
-
- static final int HEARTBEAT = 197;
-
- static final int SCROLL_LAYER = 198;
-
- // private message ids
- private static final int DESTROY = 200;
-
- // for cut & paste
- static final int COPY_TEXT = 210;
- static final int DELETE_TEXT = 211;
- static final int INSERT_TEXT = 212;
- static final int SELECT_TEXT = 213;
- static final int SELECT_WORD_AT = 214;
- static final int SELECT_ALL = 215;
-
- // for updating state on trust storage change
- static final int TRUST_STORAGE_UPDATED = 220;
-
- // find-on-page controls
- static final int FIND_ALL = 221;
- static final int FIND_NEXT = 222;
-
- // key was pressed (down and up)
- static final int KEY_PRESS = 223;
- static final int SET_INITIAL_FOCUS = 224;
-
- static final int SAVE_VIEW_STATE = 225;
- static final int SET_USE_MOCK_GEOLOCATION = 226;
-
- // Private handler for WebCore messages.
- private Handler mHandler;
- // Message queue for containing messages before the WebCore thread is
- // ready.
- private LinkedList<Message> mMessages = new LinkedList<Message>();
- // Flag for blocking messages. This is used during DESTROY to avoid
- // posting more messages to the EventHub or to WebView's event handler.
- private boolean mBlockMessages;
- private boolean mDestroying;
-
- private int mTid;
- private int mSavedPriority;
-
- /**
- * Prevent other classes from creating an EventHub.
- */
- private EventHub() {}
-
- private static final int FIRST_PACKAGE_MSG_ID = REVEAL_SELECTION;
- private static final int LAST_PACKAGE_MSG_ID = REMOVE_JS_INTERFACE;
-
- /**
- * Transfer all messages to the newly created webcore thread handler.
- */
- private void transferMessages() {
- mTid = Process.myTid();
- mSavedPriority = Process.getThreadPriority(mTid);
-
- mHandler = new Handler() {
- @Override
- public void handleMessage(Message msg) {
- if (DebugFlags.WEB_VIEW_CORE) {
- Log.v(LOGTAG, (msg.what < FIRST_PACKAGE_MSG_ID
- || msg.what > LAST_PACKAGE_MSG_ID
- ? Integer.toString(msg.what)
- : HandlerDebugString[msg.what
- - FIRST_PACKAGE_MSG_ID])
- + " arg1=" + msg.arg1 + " arg2=" + msg.arg2
- + " obj=" + msg.obj);
- }
- switch (msg.what) {
- case PAUSE_TIMERS:
- mSavedPriority = Process.getThreadPriority(mTid);
- Process.setThreadPriority(mTid,
- Process.THREAD_PRIORITY_BACKGROUND);
- pauseTimers();
- if (mNativeClass != 0) {
- nativeCloseIdleConnections(mNativeClass);
- }
- return;
-
- case RESUME_TIMERS:
- Process.setThreadPriority(mTid, mSavedPriority);
- resumeTimers();
- return;
- }
-
- if (mWebViewClassic == null || mNativeClass == 0) {
- if (DebugFlags.WEB_VIEW_CORE) {
- Log.w(LOGTAG, "Rejecting message " + msg.what
- + " because we are destroyed");
- }
- return;
- }
- if (mDestroying == true
- && msg.what != EventHub.DESTROY) {
- if (DebugFlags.WEB_VIEW_CORE) {
- Log.v(LOGTAG, "Rejecting message " + msg.what
- + " because we are being destroyed");
- }
- return;
- }
- switch (msg.what) {
- case WEBKIT_DRAW:
- webkitDraw();
- break;
-
- case DESTROY:
- // Time to take down the world. Cancel all pending
- // loads and destroy the native view and frame.
- synchronized (WebViewCore.this) {
- mCallbackProxy.shutdown();
- // Wake up the WebCore thread just in case it is waiting for a
- // JavaScript dialog.
- synchronized (mCallbackProxy) {
- mCallbackProxy.notify();
- }
- mBrowserFrame.destroy();
- mBrowserFrame = null;
- mSettings.onDestroyed();
- mNativeClass = 0;
- WebCoreThreadWatchdog.unregisterWebView(mWebViewClassic);
- mWebViewClassic = null;
- }
- break;
-
- case REVEAL_SELECTION:
- nativeRevealSelection(mNativeClass);
- break;
-
- case SCROLL_TEXT_INPUT:
- float xPercent;
- if (msg.obj == null) {
- xPercent = 0f;
- } else {
- xPercent = ((Float) msg.obj).floatValue();
- }
- nativeScrollFocusedTextInput(mNativeClass, xPercent,
- msg.arg2);
- break;
-
- case LOAD_URL: {
- CookieManagerClassic.getInstance().waitForCookieOperationsToComplete();
- GetUrlData param = (GetUrlData) msg.obj;
- loadUrl(param.mUrl, param.mExtraHeaders);
- break;
- }
-
- case POST_URL: {
- CookieManagerClassic.getInstance().waitForCookieOperationsToComplete();
- PostUrlData param = (PostUrlData) msg.obj;
- mBrowserFrame.postUrl(param.mUrl, param.mPostData);
- break;
- }
- case LOAD_DATA:
- CookieManagerClassic.getInstance().waitForCookieOperationsToComplete();
- BaseUrlData loadParams = (BaseUrlData) msg.obj;
- String baseUrl = loadParams.mBaseUrl;
- if (baseUrl != null) {
- int i = baseUrl.indexOf(':');
- if (i > 0) {
- // In 1.0, WebView.loadDataWithBaseURL() could access local
- // asset files using 'file' scheme URLs as long as the data is
- // valid. Later versions of WebKit have tightened the
- // restriction around when pages can access such local URLs.
- // To maintain compatibility with 1.0, we register the scheme of
- // the baseUrl to be considered local, as long as it is not
- // http(s)/ftp(s)/about/javascript.
- String scheme = baseUrl.substring(0, i);
- if (!scheme.startsWith("http") &&
- !scheme.startsWith("ftp") &&
- !scheme.startsWith("about") &&
- !scheme.startsWith("javascript")) {
- nativeRegisterURLSchemeAsLocal(mNativeClass,
- scheme);
- }
- }
- }
- mBrowserFrame.loadData(baseUrl,
- loadParams.mData,
- loadParams.mMimeType,
- loadParams.mEncoding,
- loadParams.mHistoryUrl);
- nativeContentInvalidateAll(mNativeClass);
- break;
-
- case STOP_LOADING:
- // If the WebCore has committed the load, but not
- // finished the first layout yet, we need to set
- // first layout done to trigger the interpreted side sync
- // up with native side
- if (mBrowserFrame.committed()
- && !mBrowserFrame.firstLayoutDone()) {
- mBrowserFrame.didFirstLayout();
- }
- // Do this after syncing up the layout state.
- stopLoading();
- break;
-
- case RELOAD:
- mBrowserFrame.reload(false);
- break;
-
- case KEY_DOWN:
- key((KeyEvent) msg.obj, msg.arg1, true);
- break;
-
- case KEY_UP:
- key((KeyEvent) msg.obj, msg.arg1, false);
- break;
-
- case KEY_PRESS:
- keyPress(msg.arg1);
- break;
-
- case VIEW_SIZE_CHANGED: {
- viewSizeChanged((WebViewClassic.ViewSizeData) msg.obj);
- break;
- }
- case SET_SCROLL_OFFSET:
- // note: these are in document coordinates
- // (inv-zoom)
- Point pt = (Point) msg.obj;
- nativeSetScrollOffset(mNativeClass,
- msg.arg1 == 1, pt.x, pt.y);
- break;
-
- case SET_GLOBAL_BOUNDS:
- Rect r = (Rect) msg.obj;
- nativeSetGlobalBounds(mNativeClass, r.left, r.top,
- r.width(), r.height());
- break;
-
- case GO_BACK_FORWARD:
- // If it is a standard load and the load is not
- // committed yet, we interpret BACK as RELOAD
- if (!mBrowserFrame.committed() && msg.arg1 == -1 &&
- (mBrowserFrame.loadType() ==
- BrowserFrame.FRAME_LOADTYPE_STANDARD)) {
- mBrowserFrame.reload(true);
- } else {
- mBrowserFrame.goBackOrForward(msg.arg1);
- }
- break;
-
- case RESTORE_STATE:
- stopLoading();
- restoreState(msg.arg1);
- break;
-
-
- case ON_PAUSE:
- nativePause(mNativeClass);
- break;
-
- case ON_RESUME:
- nativeResume(mNativeClass);
- break;
-
- case FREE_MEMORY:
- clearCache(false);
- nativeFreeMemory(mNativeClass);
- break;
-
- case SET_NETWORK_STATE:
- if (BrowserFrame.sJavaBridge == null) {
- throw new IllegalStateException("No WebView " +
- "has been created in this process!");
- }
- BrowserFrame.sJavaBridge
- .setNetworkOnLine(msg.arg1 == 1);
- break;
-
- case SET_NETWORK_TYPE:
- if (BrowserFrame.sJavaBridge == null) {
- throw new IllegalStateException("No WebView " +
- "has been created in this process!");
- }
- Map<String, String> map = (Map<String, String>) msg.obj;
- BrowserFrame.sJavaBridge
- .setNetworkType(map.get("type"), map.get("subtype"));
- break;
-
- case CLEAR_CACHE:
- clearCache(msg.arg1 == 1);
- break;
-
- case CLEAR_HISTORY:
- mCallbackProxy.getBackForwardList().
- close(mBrowserFrame.mNativeFrame);
- break;
-
- case REPLACE_TEXT:
- ReplaceTextData rep = (ReplaceTextData) msg.obj;
- nativeReplaceTextfieldText(mNativeClass, msg.arg1,
- msg.arg2, rep.mReplace, rep.mNewStart,
- rep.mNewEnd, rep.mTextGeneration);
- break;
-
- case PASS_TO_JS: {
- JSKeyData jsData = (JSKeyData) msg.obj;
- KeyEvent evt = jsData.mEvent;
- int keyCode = evt.getKeyCode();
- int keyValue = evt.getUnicodeChar();
- int generation = msg.arg1;
- passToJs(mNativeClass,
- generation,
- jsData.mCurrentText,
- keyCode,
- keyValue,
- evt.isDown(), evt.isShiftPressed(),
- evt.isAltPressed(), evt.isSymPressed());
- break;
- }
-
- case SAVE_DOCUMENT_STATE: {
- nativeSaveDocumentState(mNativeClass);
- break;
- }
-
- case CLEAR_SSL_PREF_TABLE:
- // FIXME: This will not work for connections currently in use, as
- // they cache the certificate responses. See http://b/5324235.
- SslCertLookupTable.getInstance().clear();
- nativeCloseIdleConnections(mNativeClass);
- break;
-
- case SET_ACTIVE:
- nativeSetFocusControllerActive(mNativeClass, msg.arg1 == 1);
- break;
-
- case ADD_JS_INTERFACE:
- JSInterfaceData jsData = (JSInterfaceData) msg.obj;
- mBrowserFrame.addJavascriptInterface(jsData.mObject,
- jsData.mInterfaceName, jsData.mRequireAnnotation);
- break;
-
- case REMOVE_JS_INTERFACE:
- jsData = (JSInterfaceData) msg.obj;
- mBrowserFrame.removeJavascriptInterface(
- jsData.mInterfaceName);
- break;
-
- case REQUEST_EXT_REPRESENTATION:
- mBrowserFrame.externalRepresentation(
- (Message) msg.obj);
- break;
-
- case REQUEST_DOC_AS_TEXT:
- mBrowserFrame.documentAsText((Message) msg.obj);
- break;
-
- case SET_MOVE_MOUSE:
- nativeMoveMouse(mNativeClass, msg.arg1, msg.arg2);
- break;
-
- case REQUEST_CURSOR_HREF: {
- WebKitHitTest hit = performHitTest(msg.arg1, msg.arg2, 1, false);
- Message hrefMsg = (Message) msg.obj;
- Bundle data = hrefMsg.getData();
- data.putString(FocusNodeHref.URL,hit.mLinkUrl);
- data.putString(FocusNodeHref.TITLE, hit.mAnchorText);
- data.putString(FocusNodeHref.SRC, hit.mImageUrl);
- hrefMsg.sendToTarget();
- break;
- }
-
- case DOC_HAS_IMAGES:
- Message imageResult = (Message) msg.obj;
- imageResult.arg1 =
- mBrowserFrame.documentHasImages() ? 1 : 0;
- imageResult.sendToTarget();
- break;
-
- case DELETE_SELECTION:
- TextSelectionData deleteSelectionData
- = (TextSelectionData) msg.obj;
- nativeDeleteSelection(mNativeClass,
- deleteSelectionData.mStart, deleteSelectionData.mEnd, msg.arg1);
- break;
-
- case SET_SELECTION:
- nativeSetSelection(mNativeClass, msg.arg1, msg.arg2);
- break;
-
- case MODIFY_SELECTION:
- mTextSelectionChangeReason
- = TextSelectionData.REASON_ACCESSIBILITY_INJECTOR;
- final SomeArgs args = (SomeArgs) msg.obj;
- final String modifiedSelectionString = nativeModifySelection(
- mNativeClass, args.argi1, args.argi2);
- // If accessibility is on, the main thread may be
- // waiting for a response. Send on webcore thread.
- mWebViewClassic.handleSelectionChangedWebCoreThread(
- modifiedSelectionString, args.argi3);
- args.recycle();
- mTextSelectionChangeReason
- = TextSelectionData.REASON_UNKNOWN;
- break;
-
- case LISTBOX_CHOICES:
- SparseBooleanArray choices = (SparseBooleanArray)
- msg.obj;
- int choicesSize = msg.arg1;
- boolean[] choicesArray = new boolean[choicesSize];
- for (int c = 0; c < choicesSize; c++) {
- choicesArray[c] = choices.get(c);
- }
- nativeSendListBoxChoices(mNativeClass,
- choicesArray, choicesSize);
- break;
-
- case SINGLE_LISTBOX_CHOICE:
- nativeSendListBoxChoice(mNativeClass, msg.arg1);
- break;
-
- case SET_BACKGROUND_COLOR:
- nativeSetBackgroundColor(mNativeClass, msg.arg1);
- break;
-
- case DUMP_DOMTREE:
- nativeDumpDomTree(mNativeClass, msg.arg1 == 1);
- break;
-
- case DUMP_RENDERTREE:
- nativeDumpRenderTree(mNativeClass, msg.arg1 == 1);
- break;
-
- case SET_JS_FLAGS:
- nativeSetJsFlags(mNativeClass, (String)msg.obj);
- break;
-
- case CONTENT_INVALIDATE_ALL:
- nativeContentInvalidateAll(mNativeClass);
- break;
-
- case SAVE_WEBARCHIVE:
- WebViewClassic.SaveWebArchiveMessage saveMessage =
- (WebViewClassic.SaveWebArchiveMessage)msg.obj;
- saveMessage.mResultFile =
- saveWebArchive(saveMessage.mBasename, saveMessage.mAutoname);
- mWebViewClassic.mPrivateHandler.obtainMessage(
- WebViewClassic.SAVE_WEBARCHIVE_FINISHED, saveMessage).sendToTarget();
- break;
-
- case GEOLOCATION_PERMISSIONS_PROVIDE:
- GeolocationPermissionsData data =
- (GeolocationPermissionsData) msg.obj;
- nativeGeolocationPermissionsProvide(mNativeClass,
- data.mOrigin, data.mAllow, data.mRemember);
- break;
-
- case CLEAR_CONTENT:
- // Clear the view so that onDraw() will draw nothing
- // but white background
- // (See public method WebView.clearView)
- clearContent();
- break;
-
- case MESSAGE_RELAY:
- ((Message) msg.obj).sendToTarget();
- break;
-
- case POPULATE_VISITED_LINKS:
- nativeProvideVisitedHistory(mNativeClass, (String[])msg.obj);
- break;
-
- case HIDE_FULLSCREEN:
- nativeFullScreenPluginHidden(mNativeClass, msg.arg1);
- break;
-
- case PLUGIN_SURFACE_READY:
- nativePluginSurfaceReady(mNativeClass);
- break;
-
- case NOTIFY_ANIMATION_STARTED:
- nativeNotifyAnimationStarted(mNativeClass);
- break;
-
- case ADD_PACKAGE_NAMES:
- if (BrowserFrame.sJavaBridge == null) {
- throw new IllegalStateException("No WebView " +
- "has been created in this process!");
- }
- BrowserFrame.sJavaBridge.addPackageNames(
- (Set<String>) msg.obj);
- break;
-
- case SET_USE_MOCK_GEOLOCATION:
- setUseMockGeolocation();
- break;
-
- case SET_USE_MOCK_DEVICE_ORIENTATION:
- setUseMockDeviceOrientation();
- break;
-
- case AUTOFILL_FORM:
- nativeAutoFillForm(mNativeClass, msg.arg1);
- mWebViewClassic.mPrivateHandler.obtainMessage(
- WebViewClassic.AUTOFILL_COMPLETE, null).sendToTarget();
- break;
-
- case EXECUTE_JS:
- if (msg.obj instanceof String) {
- if (DebugFlags.WEB_VIEW_CORE) {
- Log.d(LOGTAG, "Executing JS : " + msg.obj);
- }
- mBrowserFrame.stringByEvaluatingJavaScriptFromString(
- (String) msg.obj);
- }
- break;
- case SCROLL_LAYER:
- int nativeLayer = msg.arg1;
- Rect rect = (Rect) msg.obj;
- nativeScrollLayer(mNativeClass, nativeLayer,
- rect);
- break;
-
- case DELETE_TEXT: {
- int[] handles = (int[]) msg.obj;
- nativeDeleteText(mNativeClass, handles[0],
- handles[1], handles[2], handles[3]);
- break;
- }
- case COPY_TEXT: {
- int[] handles = (int[]) msg.obj;
- String copiedText = nativeGetText(mNativeClass,
- handles[0], handles[1], handles[2],
- handles[3]);
- if (copiedText != null) {
- mWebViewClassic.mPrivateHandler.obtainMessage(
- WebViewClassic.COPY_TO_CLIPBOARD, copiedText)
- .sendToTarget();
- }
- break;
- }
- case INSERT_TEXT:
- nativeInsertText(mNativeClass, (String) msg.obj);
- break;
- case SELECT_TEXT: {
- int handleId = (Integer) msg.obj;
- nativeSelectText(mNativeClass, handleId,
- msg.arg1, msg.arg2);
- break;
- }
- case SELECT_WORD_AT: {
- mTextSelectionChangeReason
- = TextSelectionData.REASON_SELECT_WORD;
- int x = msg.arg1;
- int y = msg.arg2;
- if (!nativeSelectWordAt(mNativeClass, x, y)) {
- mWebViewClassic.mPrivateHandler.obtainMessage(WebViewClassic.SHOW_CARET_HANDLE)
- .sendToTarget();
- }
- mTextSelectionChangeReason
- = TextSelectionData.REASON_UNKNOWN;
- break;
- }
- case SELECT_ALL:
- nativeSelectAll(mNativeClass);
- break;
- case FIND_ALL: {
- FindAllRequest request = (FindAllRequest)msg.obj;
- if (request != null) {
- int matchCount = nativeFindAll(mNativeClass, request.mSearchText);
- int matchIndex = nativeFindNext(mNativeClass, true);
- synchronized (request) {
- request.mMatchCount = matchCount;
- request.mMatchIndex = matchIndex;
- request.notify();
- }
- } else {
- nativeFindAll(mNativeClass, null);
- }
- Message.obtain(mWebViewClassic.mPrivateHandler,
- WebViewClassic.UPDATE_MATCH_COUNT, request).sendToTarget();
- break;
- }
- case FIND_NEXT: {
- FindAllRequest request = (FindAllRequest)msg.obj;
- int matchIndex = nativeFindNext(mNativeClass, msg.arg1 != 0);
- synchronized (request) {
- request.mMatchIndex = matchIndex;
- }
- Message.obtain(mWebViewClassic.mPrivateHandler,
- WebViewClassic.UPDATE_MATCH_COUNT, request).sendToTarget();
- break;
- }
- case SET_INITIAL_FOCUS:
- nativeSetInitialFocus(mNativeClass, msg.arg1);
- break;
- case SAVE_VIEW_STATE:
- SaveViewStateRequest request = (SaveViewStateRequest) msg.obj;
- saveViewState(request.mStream, request.mCallback);
- break;
- }
- }
-
- };
- // Take all queued messages and resend them to the new handler.
- synchronized (this) {
- int size = mMessages.size();
- for (int i = 0; i < size; i++) {
- mHandler.sendMessage(mMessages.get(i));
- }
- mMessages = null;
- }
- }
-
- @Override
- public Looper getWebKitLooper() {
- return mHandler.getLooper();
- }
-
- @Override
- public boolean dispatchWebKitEvent(WebViewInputDispatcher dispatcher,
- MotionEvent event, int eventType, int flags) {
- if (mNativeClass == 0) {
- return false;
- }
- switch (eventType) {
- case WebViewInputDispatcher.EVENT_TYPE_HIT_TEST:
- int x = Math.round(event.getX());
- int y = Math.round(event.getY());
- WebKitHitTest hit = performHitTest(x, y,
- mWebViewClassic.getScaledNavSlop(), true);
- mWebViewClassic.mPrivateHandler.obtainMessage(
- WebViewClassic.HIT_TEST_RESULT, hit).sendToTarget();
- return false;
-
- case WebViewInputDispatcher.EVENT_TYPE_CLICK:
- return nativeMouseClick(mNativeClass);
-
- case WebViewInputDispatcher.EVENT_TYPE_TOUCH: {
- int count = event.getPointerCount();
- int[] idArray = new int[count];
- int[] xArray = new int[count];
- int[] yArray = new int[count];
- for (int i = 0; i < count; i++) {
- idArray[i] = event.getPointerId(i);
- xArray[i] = (int) event.getX(i);
- yArray[i] = (int) event.getY(i);
- }
- int touchFlags = nativeHandleTouchEvent(mNativeClass,
- event.getActionMasked(),
- idArray, xArray, yArray, count,
- event.getActionIndex(), event.getMetaState());
- if (touchFlags == 0
- && event.getActionMasked() != MotionEvent.ACTION_CANCEL
- && (flags & WebViewInputDispatcher.FLAG_PRIVATE) == 0) {
- dispatcher.skipWebkitForRemainingTouchStream();
- }
- return (touchFlags & TOUCH_FLAG_PREVENT_DEFAULT) > 0;
- }
-
- default:
- return false;
- }
- }
-
- /**
- * Send a message internally to the queue or to the handler
- */
- private synchronized void sendMessage(Message msg) {
- if (mBlockMessages) {
- return;
- }
- if (mMessages != null) {
- mMessages.add(msg);
- } else {
- mHandler.sendMessage(msg);
- }
- }
-
- private synchronized void removeMessages(int what) {
- if (mBlockMessages) {
- return;
- }
- if (what == EventHub.WEBKIT_DRAW) {
- mDrawIsScheduled = false;
- }
- if (mMessages != null) {
- Iterator<Message> iter = mMessages.iterator();
- while (iter.hasNext()) {
- Message m = iter.next();
- if (m.what == what) {
- iter.remove();
- }
- }
- } else {
- mHandler.removeMessages(what);
- }
- }
-
- private synchronized void sendMessageDelayed(Message msg, long delay) {
- if (mBlockMessages) {
- return;
- }
- mHandler.sendMessageDelayed(msg, delay);
- }
-
- /**
- * Send a message internally to the front of the queue.
- */
- private synchronized void sendMessageAtFrontOfQueue(Message msg) {
- if (mBlockMessages) {
- return;
- }
- if (mMessages != null) {
- mMessages.add(0, msg);
- } else {
- mHandler.sendMessageAtFrontOfQueue(msg);
- }
- }
-
- /**
- * Remove all the messages.
- */
- private synchronized void removeMessages() {
- // reset mDrawIsScheduled flag as WEBKIT_DRAW may be removed
- mDrawIsScheduled = false;
- if (mMessages != null) {
- mMessages.clear();
- } else {
- mHandler.removeCallbacksAndMessages(null);
- }
- }
-
- /**
- * Block sending messages to the EventHub.
- */
- private synchronized void blockMessages() {
- mBlockMessages = true;
- }
- }
-
- //-------------------------------------------------------------------------
- // Methods called by host activity (in the same thread)
- //-------------------------------------------------------------------------
-
- void stopLoading() {
- if (DebugFlags.WEB_VIEW_CORE) Log.v(LOGTAG, "CORE stopLoading");
- if (mBrowserFrame != null) {
- mBrowserFrame.stopLoading();
- }
- }
-
- //-------------------------------------------------------------------------
- // Methods called by WebView
- // If it refers to local variable, it needs synchronized().
- // If it needs WebCore, it has to send message.
- //-------------------------------------------------------------------------
-
- /**
- * @hide
- */
- public void sendMessage(Message msg) {
- mEventHub.sendMessage(msg);
- }
-
- void sendMessages(ArrayList<Message> messages) {
- synchronized (mEventHub) {
- for (int i = 0; i < messages.size(); i++) {
- mEventHub.sendMessage(messages.get(i));
- }
- }
- }
-
- void sendMessage(int what) {
- mEventHub.sendMessage(Message.obtain(null, what));
- }
-
- void sendMessageAtFrontOfQueue(int what, int arg1, int arg2, Object obj) {
- mEventHub.sendMessageAtFrontOfQueue(Message.obtain(
- null, what, arg1, arg2, obj));
- }
-
- void sendMessage(int what, Object obj) {
- mEventHub.sendMessage(Message.obtain(null, what, obj));
- }
-
- void sendMessage(int what, int arg1) {
- // just ignore the second argument (make it 0)
- mEventHub.sendMessage(Message.obtain(null, what, arg1, 0));
- }
-
- void sendMessage(int what, int arg1, int arg2) {
- mEventHub.sendMessage(Message.obtain(null, what, arg1, arg2));
- }
-
- void sendMessage(int what, int arg1, Object obj) {
- // just ignore the second argument (make it 0)
- mEventHub.sendMessage(Message.obtain(null, what, arg1, 0, obj));
- }
-
- void sendMessage(int what, int arg1, int arg2, Object obj) {
- mEventHub.sendMessage(Message.obtain(null, what, arg1, arg2, obj));
- }
-
- void sendMessageAtFrontOfQueue(int what, Object obj) {
- mEventHub.sendMessageAtFrontOfQueue(Message.obtain(
- null, what, obj));
- }
-
- void sendMessageDelayed(int what, Object obj, long delay) {
- mEventHub.sendMessageDelayed(Message.obtain(null, what, obj), delay);
- }
-
- void removeMessages(int what) {
- mEventHub.removeMessages(what);
- }
-
- void removeMessages() {
- mEventHub.removeMessages();
- }
-
- /**
- * Sends a DESTROY message to WebCore.
- * Called from UI thread.
- */
- void destroy() {
- synchronized (mEventHub) {
- // send DESTROY to front of queue
- // PAUSE/RESUME timers will still be processed even if they get handled later
- mEventHub.mDestroying = true;
- mEventHub.sendMessageAtFrontOfQueue(
- Message.obtain(null, EventHub.DESTROY));
- mEventHub.blockMessages();
- }
- }
-
- //-------------------------------------------------------------------------
- // WebViewCore private methods
- //-------------------------------------------------------------------------
-
- private WebKitHitTest performHitTest(int x, int y, int slop, boolean moveMouse) {
- WebKitHitTest hit = nativeHitTest(mNativeClass, x, y, slop, moveMouse);
- hit.mHitTestX = x;
- hit.mHitTestY = y;
- hit.mHitTestSlop = slop;
- hit.mHitTestMovedMouse = moveMouse;
- return hit;
- }
-
- private void clearCache(boolean includeDiskFiles) {
- mBrowserFrame.clearCache();
- }
-
- private void loadUrl(String url, Map<String, String> extraHeaders) {
- if (DebugFlags.WEB_VIEW_CORE) Log.v(LOGTAG, " CORE loadUrl " + url);
- mBrowserFrame.loadUrl(url, extraHeaders);
- }
-
- private String saveWebArchive(String filename, boolean autoname) {
- if (DebugFlags.WEB_VIEW_CORE) {
- Log.v(LOGTAG, " CORE saveWebArchive " + filename + " " + autoname);
- }
- return mBrowserFrame.saveWebArchive(filename, autoname);
- }
-
- private void key(KeyEvent evt, int canTakeFocusDirection, boolean isDown) {
- if (DebugFlags.WEB_VIEW_CORE) {
- Log.v(LOGTAG, "CORE key at " + System.currentTimeMillis() + ", "
- + evt);
- }
- mChromeCanFocusDirection = canTakeFocusDirection;
- int keyCode = evt.getKeyCode();
- int unicodeChar = evt.getUnicodeChar();
-
- if (keyCode == KeyEvent.KEYCODE_UNKNOWN && evt.getCharacters() != null
- && evt.getCharacters().length() > 0) {
- // we should only receive individual complex characters
- unicodeChar = evt.getCharacters().codePointAt(0);
- }
-
- boolean handled = nativeKey(mNativeClass, keyCode, unicodeChar, evt.getRepeatCount(),
- evt.isShiftPressed(), evt.isAltPressed(),
- evt.isSymPressed(), isDown);
- mChromeCanFocusDirection = 0;
- if (!handled && keyCode != KeyEvent.KEYCODE_ENTER) {
- if (keyCode >= KeyEvent.KEYCODE_DPAD_UP
- && keyCode <= KeyEvent.KEYCODE_DPAD_RIGHT) {
- if (canTakeFocusDirection != 0 && isDown) {
- Message m = mWebViewClassic.mPrivateHandler.obtainMessage(
- WebViewClassic.TAKE_FOCUS);
- m.arg1 = canTakeFocusDirection;
- m.sendToTarget();
- }
- return;
- }
- // bubble up the event handling
- // but do not bubble up the ENTER key, which would open the search
- // bar without any text.
- mCallbackProxy.onUnhandledKeyEvent(evt);
- }
- }
-
- private void keyPress(int unicodeChar) {
- nativeKey(mNativeClass, 0, unicodeChar, 0, false, false, false, true);
- nativeKey(mNativeClass, 0, unicodeChar, 0, false, false, false, false);
- }
-
- // These values are used to avoid requesting a layout based on old values
- private int mCurrentViewWidth = 0;
- private int mCurrentViewHeight = 0;
- private float mCurrentViewScale = 1.0f;
-
- // notify webkit that our virtual view size changed size (after inv-zoom)
- private void viewSizeChanged(WebViewClassic.ViewSizeData data) {
- int w = data.mWidth;
- int h = data.mHeight;
- int textwrapWidth = data.mTextWrapWidth;
- float scale = data.mScale;
- if (DebugFlags.WEB_VIEW_CORE) {
- Log.v(LOGTAG, "viewSizeChanged w=" + w + "; h=" + h
- + "; textwrapWidth=" + textwrapWidth + "; scale=" + scale);
- }
- if (w == 0) {
- Log.w(LOGTAG, "skip viewSizeChanged as w is 0");
- return;
- }
- int width = calculateWindowWidth(w);
- int height = h;
- if (width != w) {
- float heightWidthRatio = data.mHeightWidthRatio;
- float ratio = (heightWidthRatio > 0) ? heightWidthRatio : (float) h / w;
- height = Math.round(ratio * width);
- }
- int screenHeight = data.mActualViewHeight > 0 ? data.mActualViewHeight : h;
- nativeSetSize(mNativeClass, width, height, textwrapWidth, scale,
- w, screenHeight, data.mAnchorX, data.mAnchorY, data.mIgnoreHeight);
- // Remember the current width and height
- boolean needInvalidate = (mCurrentViewWidth == 0);
- mCurrentViewWidth = w;
- mCurrentViewHeight = h;
- mCurrentViewScale = scale;
- if (needInvalidate) {
- // ensure {@link #webkitDraw} is called as we were blocking in
- // {@link #contentDraw} when mCurrentViewWidth is 0
- if (DebugFlags.WEB_VIEW_CORE) Log.v(LOGTAG, "viewSizeChanged");
- contentDraw();
- }
- }
-
- // Calculate width to be used in webkit window.
- private int calculateWindowWidth(int viewWidth) {
- int width = viewWidth;
- if (mSettings.getUseWideViewPort()) {
- if (mViewportWidth == -1) {
- // Fixed viewport width.
- width = WebViewClassic.DEFAULT_VIEWPORT_WIDTH;
- } else if (mViewportWidth > 0) {
- // Use website specified or desired fixed viewport width.
- width = mViewportWidth;
- } else {
- // For mobile web site.
- width = Math.round(mWebViewClassic.getViewWidth() /
- mWebViewClassic.getDefaultZoomScale());
- }
- }
- return width;
- }
-
- // Utility method for exceededDatabaseQuota callback. Computes the sum
- // of WebSQL database quota for all origins.
- private long getUsedQuota() {
- WebStorageClassic webStorage = WebStorageClassic.getInstance();
- Collection<WebStorage.Origin> origins = webStorage.getOriginsSync();
-
- if (origins == null) {
- return 0;
- }
- long usedQuota = 0;
- for (WebStorage.Origin website : origins) {
- usedQuota += website.getQuota();
- }
- return usedQuota;
- }
-
- // Used to avoid posting more than one draw message.
- private boolean mDrawIsScheduled;
-
- // Used to suspend drawing.
- private boolean mDrawIsPaused;
-
- // mInitialViewState is set by didFirstLayout() and then reset in the
- // next webkitDraw after passing the state to the UI thread.
- private ViewState mInitialViewState = null;
- private boolean mFirstLayoutForNonStandardLoad;
-
- static class ViewState {
- float mMinScale;
- float mMaxScale;
- float mViewScale;
- float mTextWrapScale;
- float mDefaultScale;
- int mScrollX;
- int mScrollY;
- boolean mMobileSite;
- boolean mIsRestored;
- boolean mShouldStartScrolledRight;
- }
-
- static class DrawData {
- DrawData() {
- mBaseLayer = 0;
- mContentSize = new Point();
- }
- int mBaseLayer;
- // view size that was used by webkit during the most recent layout
- Point mViewSize;
- Point mContentSize;
- int mMinPrefWidth;
- // only non-null if it is for the first picture set after the first layout
- ViewState mViewState;
- boolean mFirstLayoutForNonStandardLoad;
- }
-
- DrawData mLastDrawData = null;
-
- private Object m_skipDrawFlagLock = new Object();
- private boolean m_skipDrawFlag = false;
- private boolean m_drawWasSkipped = false;
-
- void pauseWebKitDraw() {
- synchronized (m_skipDrawFlagLock) {
- if (!m_skipDrawFlag) {
- m_skipDrawFlag = true;
- }
- }
- }
-
- void resumeWebKitDraw() {
- synchronized (m_skipDrawFlagLock) {
- if (m_skipDrawFlag && m_drawWasSkipped) {
- // a draw was dropped, send a retry
- m_drawWasSkipped = false;
- mEventHub.sendMessage(Message.obtain(null, EventHub.WEBKIT_DRAW));
- }
- m_skipDrawFlag = false;
- }
- }
-
- private void webkitDraw() {
- synchronized (m_skipDrawFlagLock) {
- if (m_skipDrawFlag) {
- m_drawWasSkipped = true;
- return;
- }
- }
-
- mDrawIsScheduled = false;
- DrawData draw = new DrawData();
- if (DebugFlags.WEB_VIEW_CORE) Log.v(LOGTAG, "webkitDraw start");
- draw.mBaseLayer = nativeRecordContent(mNativeClass, draw.mContentSize);
- if (draw.mBaseLayer == 0) {
- if (mWebViewClassic != null && !mWebViewClassic.isPaused()) {
- if (DebugFlags.WEB_VIEW_CORE) Log.v(LOGTAG, "webkitDraw abort, resending draw message");
- mEventHub.sendMessageDelayed(Message.obtain(null, EventHub.WEBKIT_DRAW), 10);
- } else {
- if (DebugFlags.WEB_VIEW_CORE) Log.v(LOGTAG, "webkitDraw abort, webview paused");
- }
- return;
- }
- mLastDrawData = draw;
- webkitDraw(draw);
- }
-
- private void webkitDraw(DrawData draw) {
- if (mWebViewClassic != null) {
- draw.mViewSize = new Point(mCurrentViewWidth, mCurrentViewHeight);
- if (mSettings.getUseWideViewPort()) {
- draw.mMinPrefWidth = Math.max(
- mViewportWidth == -1 ? WebViewClassic.DEFAULT_VIEWPORT_WIDTH
- : (mViewportWidth == 0 ? mCurrentViewWidth
- : mViewportWidth),
- nativeGetContentMinPrefWidth(mNativeClass));
- }
- if (mInitialViewState != null) {
- draw.mViewState = mInitialViewState;
- mInitialViewState = null;
- }
- if (mFirstLayoutForNonStandardLoad) {
- draw.mFirstLayoutForNonStandardLoad = true;
- mFirstLayoutForNonStandardLoad = false;
- }
- if (DebugFlags.WEB_VIEW_CORE) Log.v(LOGTAG, "webkitDraw NEW_PICTURE_MSG_ID");
- pauseWebKitDraw();
- Message.obtain(mWebViewClassic.mPrivateHandler,
- WebViewClassic.NEW_PICTURE_MSG_ID, draw).sendToTarget();
- }
- }
-
- private void saveViewState(OutputStream stream,
- ValueCallback<Boolean> callback) {
- // TODO: Create a native method to do this better without overloading
- // the draw path (and fix saving <canvas>)
- DrawData draw = new DrawData();
- if (DebugFlags.WEB_VIEW_CORE) Log.v(LOGTAG, "saveViewState start");
- draw.mBaseLayer = nativeRecordContent(mNativeClass, draw.mContentSize);
- boolean result = false;
- try {
- result = ViewStateSerializer.serializeViewState(stream, draw);
- } catch (Throwable t) {
- Log.w(LOGTAG, "Failed to save view state", t);
- }
- callback.onReceiveValue(result);
- if (draw.mBaseLayer != 0) {
- if (mDrawIsScheduled) {
- mDrawIsScheduled = false;
- mEventHub.removeMessages(EventHub.WEBKIT_DRAW);
- }
- mLastDrawData = draw;
- webkitDraw(draw);
- }
- }
-
- static void reducePriority() {
- // remove the pending REDUCE_PRIORITY and RESUME_PRIORITY messages
- sWebCoreHandler.removeMessages(WebCoreThread.REDUCE_PRIORITY);
- sWebCoreHandler.removeMessages(WebCoreThread.RESUME_PRIORITY);
- sWebCoreHandler.sendMessageAtFrontOfQueue(sWebCoreHandler
- .obtainMessage(WebCoreThread.REDUCE_PRIORITY));
- }
-
- static void resumePriority() {
- // remove the pending REDUCE_PRIORITY and RESUME_PRIORITY messages
- sWebCoreHandler.removeMessages(WebCoreThread.REDUCE_PRIORITY);
- sWebCoreHandler.removeMessages(WebCoreThread.RESUME_PRIORITY);
- sWebCoreHandler.sendMessageAtFrontOfQueue(sWebCoreHandler
- .obtainMessage(WebCoreThread.RESUME_PRIORITY));
- }
-
- static void sendStaticMessage(int messageType, Object argument) {
- if (sWebCoreHandler == null)
- return;
-
- sWebCoreHandler.sendMessage(sWebCoreHandler.obtainMessage(messageType, argument));
- }
-
- static void pauseUpdatePicture(WebViewCore core) {
- // Note: there is one possible failure mode. If pauseUpdatePicture() is
- // called from UI thread while WEBKIT_DRAW is just pulled out of the
- // queue in WebCore thread to be executed. Then update won't be blocked.
- if (core != null) {
- if (!core.getSettings().enableSmoothTransition()) return;
-
- synchronized (core) {
- if (core.mNativeClass == 0) {
- Log.w(LOGTAG, "Cannot pauseUpdatePicture, core destroyed or not initialized!");
- return;
- }
- core.mDrawIsPaused = true;
- }
- }
-
- }
-
- static void resumeUpdatePicture(WebViewCore core) {
- if (core != null) {
- // if mDrawIsPaused is true, ignore the setting, continue to resume
- if (!core.mDrawIsPaused)
- return;
-
- synchronized (core) {
- if (core.mNativeClass == 0) {
- Log.w(LOGTAG, "Cannot resumeUpdatePicture, core destroyed!");
- return;
- }
- core.mDrawIsPaused = false;
- // always redraw on resume to reenable gif animations
- core.mDrawIsScheduled = false;
- }
- }
- }
-
- static boolean isUpdatePicturePaused(WebViewCore core) {
- return core != null ? core.mDrawIsPaused : false;
- }
-
- //////////////////////////////////////////////////////////////////////////
-
- private void restoreState(int index) {
- WebBackForwardListClassic list = mCallbackProxy.getBackForwardList();
- int size = list.getSize();
- for (int i = 0; i < size; i++) {
- list.getItemAtIndex(i).inflate(mBrowserFrame.mNativeFrame);
- }
- mBrowserFrame.mLoadInitFromJava = true;
- WebBackForwardListClassic.restoreIndex(mBrowserFrame.mNativeFrame, index);
- mBrowserFrame.mLoadInitFromJava = false;
- }
-
- //-------------------------------------------------------------------------
- // Implement abstract methods in WebViewCore, native WebKit callback part
- //-------------------------------------------------------------------------
-
- // called from JNI or WebView thread
- /* package */ void contentDraw() {
- synchronized (this) {
- if (mWebViewClassic == null || mBrowserFrame == null) {
- // We were destroyed
- return;
- }
- // don't update the Picture until we have an initial width and finish
- // the first layout
- if (mCurrentViewWidth == 0 || !mBrowserFrame.firstLayoutDone()) {
- return;
- }
- // only fire an event if this is our first request
- if (mDrawIsScheduled) return;
- mDrawIsScheduled = true;
- mEventHub.sendMessage(Message.obtain(null, EventHub.WEBKIT_DRAW));
- }
- }
-
- // called by JNI
- private void contentScrollTo(int x, int y, boolean animate,
- boolean onlyIfImeIsShowing) {
- if (!mBrowserFrame.firstLayoutDone()) {
- /*
- * WebKit restore state will be called before didFirstLayout(),
- * remember the position as it has to be applied after restoring
- * zoom factor which is controlled by screenWidth.
- */
- mRestoredX = x;
- mRestoredY = y;
- return;
- }
- if (mWebViewClassic != null) {
- Message msg = Message.obtain(mWebViewClassic.mPrivateHandler,
- WebViewClassic.SCROLL_TO_MSG_ID, animate ? 1 : 0,
- onlyIfImeIsShowing ? 1 : 0, new Point(x, y));
- if (mDrawIsScheduled) {
- mEventHub.sendMessage(Message.obtain(null,
- EventHub.MESSAGE_RELAY, msg));
- } else {
- msg.sendToTarget();
- }
- }
- }
-
- // called by JNI
- private void sendNotifyProgressFinished() {
- contentDraw();
- }
-
- /* Called by JNI. The coordinates are in doc coordinates, so they need to
- be scaled before they can be used by the view system, which happens
- in WebView since it (and its thread) know the current scale factor.
- */
- private void sendViewInvalidate(int left, int top, int right, int bottom) {
- if (mWebViewClassic != null) {
- Message.obtain(mWebViewClassic.mPrivateHandler,
- WebViewClassic.INVAL_RECT_MSG_ID,
- new Rect(left, top, right, bottom)).sendToTarget();
- }
- }
-
- private static boolean mRepaintScheduled = false;
-
- /*
- * Called by the WebView thread
- */
- /* package */ void signalRepaintDone() {
- mRepaintScheduled = false;
- }
-
- // Gets the WebViewClassic corresponding to this WebViewCore. Note that the
- // WebViewClassic object must only be used on the UI thread.
- /* package */ WebViewClassic getWebViewClassic() {
- return mWebViewClassic;
- }
-
- // Called by JNI
- private WebView getWebView() {
- return mWebViewClassic.getWebView();
- }
-
- // Called by JNI
- private void sendPluginDrawMsg() {
- sendMessage(EventHub.PLUGIN_SURFACE_READY);
- }
-
- private native void setViewportSettingsFromNative(int nativeClass);
-
- // called by JNI
- private void didFirstLayout(boolean standardLoad) {
- if (DebugFlags.WEB_VIEW_CORE) {
- Log.v(LOGTAG, "didFirstLayout standardLoad =" + standardLoad);
- }
-
- mBrowserFrame.didFirstLayout();
-
- if (mWebViewClassic == null) return;
-
- boolean updateViewState = standardLoad || mIsRestored;
- setupViewport(updateViewState);
- // if updateRestoreState is true, ViewManager.postReadyToDrawAll() will
- // be called after the WebView updates its state. If updateRestoreState
- // is false, start to draw now as it is ready.
- if (!updateViewState) {
- mWebViewClassic.mViewManager.postReadyToDrawAll();
- }
-
- // remove the touch highlight when moving to a new page
- mWebViewClassic.mPrivateHandler.sendEmptyMessage(
- WebViewClassic.HIT_TEST_RESULT);
-
- // reset the scroll position, the restored offset and scales
- mRestoredX = mRestoredY = 0;
- mIsRestored = false;
- mRestoredScale = mRestoredTextWrapScale = 0;
- }
-
- // called by JNI
- private void updateViewport() {
- // Update viewport asap to make sure we get correct one.
- setupViewport(true);
- }
-
- static float getFixedDisplayDensity(Context context) {
- // We make bad assumptions about multiplying and dividing density by 100,
- // force them to be true with this hack
- float density = context.getResources().getDisplayMetrics().density;
- return ((int) (density * 100)) / 100.0f;
- }
-
- private void setupViewport(boolean updateViewState) {
- if (mWebViewClassic == null || mSettings == null) {
- // We've been destroyed or are being destroyed, return early
- return;
- }
- // set the viewport settings from WebKit
- setViewportSettingsFromNative(mNativeClass);
-
- // clamp initial scale
- if (mViewportInitialScale > 0) {
- if (mViewportMinimumScale > 0) {
- mViewportInitialScale = Math.max(mViewportInitialScale,
- mViewportMinimumScale);
- }
- if (mViewportMaximumScale > 0) {
- mViewportInitialScale = Math.min(mViewportInitialScale,
- mViewportMaximumScale);
- }
- }
-
- if (mSettings.forceUserScalable()) {
- mViewportUserScalable = true;
- if (mViewportInitialScale > 0) {
- if (mViewportMinimumScale > 0) {
- mViewportMinimumScale = Math.min(mViewportMinimumScale,
- mViewportInitialScale / 2);
- }
- if (mViewportMaximumScale > 0) {
- mViewportMaximumScale = Math.max(mViewportMaximumScale,
- mViewportInitialScale * 2);
- }
- } else {
- if (mViewportMinimumScale > 0) {
- mViewportMinimumScale = Math.min(mViewportMinimumScale, 50);
- }
- if (mViewportMaximumScale > 0) {
- mViewportMaximumScale = Math.max(mViewportMaximumScale, 200);
- }
- }
- }
-
- // adjust the default scale to match the densityDpi
- float adjust = 1.0f;
- if (mViewportDensityDpi == -1) {
- adjust = getFixedDisplayDensity(mContext);
- } else if (mViewportDensityDpi > 0) {
- adjust = (float) mContext.getResources().getDisplayMetrics().densityDpi
- / mViewportDensityDpi;
- adjust = ((int) (adjust * 100)) / 100.0f;
- }
-
- // Remove any update density messages in flight.
- // If the density is indeed different from WebView's default scale,
- // a new message will be queued.
- mWebViewClassic.mPrivateHandler.removeMessages(
- WebViewClassic.UPDATE_ZOOM_DENSITY);
- if (adjust != mWebViewClassic.getDefaultZoomScale()) {
- Message.obtain(mWebViewClassic.mPrivateHandler,
- WebViewClassic.UPDATE_ZOOM_DENSITY, adjust).sendToTarget();
- }
- int defaultScale = (int) (adjust * 100);
-
- if (mViewportInitialScale > 0) {
- mViewportInitialScale *= adjust;
- }
- if (mViewportMinimumScale > 0) {
- mViewportMinimumScale *= adjust;
- }
- if (mViewportMaximumScale > 0) {
- mViewportMaximumScale *= adjust;
- }
-
- // infer the values if they are not defined.
- if (mViewportWidth == 0) {
- if (mViewportInitialScale == 0) {
- mViewportInitialScale = defaultScale;
- }
- }
- if (mViewportUserScalable == false) {
- mViewportInitialScale = defaultScale;
- mViewportMinimumScale = defaultScale;
- mViewportMaximumScale = defaultScale;
- }
- if (mViewportMinimumScale > mViewportInitialScale
- && mViewportInitialScale != 0) {
- mViewportMinimumScale = mViewportInitialScale;
- }
- if (mViewportMaximumScale > 0
- && mViewportMaximumScale < mViewportInitialScale) {
- mViewportMaximumScale = mViewportInitialScale;
- }
- if (mViewportWidth < 0 && mViewportInitialScale == defaultScale) {
- mViewportWidth = 0;
- }
-
- // if mViewportWidth is 0, it means device-width, always update.
- if (mViewportWidth != 0 && !updateViewState) {
- // For non standard load, since updateViewState will be false.
- mFirstLayoutForNonStandardLoad = true;
- ViewState viewState = new ViewState();
- viewState.mMinScale = mViewportMinimumScale / 100.0f;
- viewState.mMaxScale = mViewportMaximumScale / 100.0f;
- viewState.mDefaultScale = adjust;
- // as mViewportWidth is not 0, it is not mobile site.
- viewState.mMobileSite = false;
- // for non-mobile site, we don't need minPrefWidth, set it as 0
- viewState.mScrollX = 0;
- viewState.mShouldStartScrolledRight = false;
- Message.obtain(mWebViewClassic.mPrivateHandler,
- WebViewClassic.UPDATE_ZOOM_RANGE, viewState).sendToTarget();
- return;
- }
-
- // now notify webview
- // webViewWidth refers to the width in the view system
- int webViewWidth;
- // viewportWidth refers to the width in the document system
- int viewportWidth = mCurrentViewWidth;
- if (viewportWidth == 0) {
- // this may happen when WebView just starts. This is not perfect as
- // we call WebView method from WebCore thread. But not perfect
- // reference is better than no reference.
- webViewWidth = mWebViewClassic.getViewWidth();
- viewportWidth = (int) (webViewWidth / adjust);
- if (viewportWidth == 0) {
- if (DebugFlags.WEB_VIEW_CORE) {
- Log.v(LOGTAG, "Can't get the viewWidth yet");
- }
- }
- } else {
- webViewWidth = Math.round(viewportWidth * mCurrentViewScale);
- }
- mInitialViewState = new ViewState();
- mInitialViewState.mMinScale = mViewportMinimumScale / 100.0f;
- mInitialViewState.mMaxScale = mViewportMaximumScale / 100.0f;
- mInitialViewState.mDefaultScale = adjust;
- mInitialViewState.mScrollX = mRestoredX;
- mInitialViewState.mScrollY = mRestoredY;
- mInitialViewState.mShouldStartScrolledRight = (mRestoredX == 0)
- && (mRestoredY == 0)
- && (mBrowserFrame != null)
- && mBrowserFrame.getShouldStartScrolledRight();
-
- mInitialViewState.mMobileSite = (0 == mViewportWidth);
- if (mIsRestored) {
- mInitialViewState.mIsRestored = true;
- mInitialViewState.mViewScale = mRestoredScale;
- if (mRestoredTextWrapScale > 0) {
- mInitialViewState.mTextWrapScale = mRestoredTextWrapScale;
- } else {
- mInitialViewState.mTextWrapScale = mInitialViewState.mViewScale;
- }
- } else {
- if (mViewportInitialScale > 0) {
- mInitialViewState.mViewScale = mInitialViewState.mTextWrapScale =
- mViewportInitialScale / 100.0f;
- } else if (mViewportWidth > 0 && mViewportWidth < webViewWidth &&
- !getSettings().getUseFixedViewport()) {
- mInitialViewState.mViewScale = mInitialViewState.mTextWrapScale =
- (float) webViewWidth / mViewportWidth;
- } else {
- mInitialViewState.mTextWrapScale = adjust;
- if (mSettings.getUseWideViewPort()) {
- // 0 will trigger WebView to turn on zoom overview mode
- mInitialViewState.mViewScale = 0;
- } else {
- mInitialViewState.mViewScale = adjust;
- }
- }
- }
-
- if (mWebViewClassic.mHeightCanMeasure) {
- // Trick to ensure that the Picture has the exact height for the
- // content by forcing to layout with 0 height after the page is
- // ready, which is indicated by didFirstLayout. This is essential to
- // get rid of the white space in the GMail which uses WebView for
- // message view.
- mWebViewClassic.mLastHeightSent = 0;
- // Send a negative scale to indicate that WebCore should reuse
- // the current scale
- WebViewClassic.ViewSizeData data = new WebViewClassic.ViewSizeData();
- data.mWidth = mWebViewClassic.mLastWidthSent;
- data.mHeight = 0;
- // if mHeightCanMeasure is true, getUseWideViewPort() can't be
- // true. It is safe to use mWidth for mTextWrapWidth.
- data.mTextWrapWidth = data.mWidth;
- data.mScale = -1.0f;
- data.mIgnoreHeight = false;
- data.mAnchorX = data.mAnchorY = 0;
- // send VIEW_SIZE_CHANGED to the front of the queue so that we can
- // avoid pushing the wrong picture to the WebView side. If there is
- // a VIEW_SIZE_CHANGED in the queue, probably from WebView side,
- // ignore it as we have a new size. If we leave VIEW_SIZE_CHANGED
- // in the queue, as mLastHeightSent has been updated here, we may
- // miss the requestLayout in WebView side after the new picture.
- mEventHub.removeMessages(EventHub.VIEW_SIZE_CHANGED);
- mEventHub.sendMessageAtFrontOfQueue(Message.obtain(null,
- EventHub.VIEW_SIZE_CHANGED, data));
- } else {
- if (viewportWidth == 0) {
- // Trick to ensure VIEW_SIZE_CHANGED will be sent from WebView
- // to WebViewCore
- mWebViewClassic.mLastWidthSent = 0;
- } else {
- WebViewClassic.ViewSizeData data = new WebViewClassic.ViewSizeData();
- // mViewScale as 0 means it is in zoom overview mode. So we don't
- // know the exact scale. If mRestoredScale is non-zero, use it;
- // otherwise just use mTextWrapScale as the initial scale.
- float tentativeScale = mInitialViewState.mViewScale;
- if (tentativeScale == 0) {
- // The following tries to figure out more correct view scale
- // and text wrap scale to be sent to webkit, by using some
- // knowledge from web settings and zoom manager.
-
- // Calculated window width will be used to guess the scale
- // in zoom overview mode.
- tentativeScale = mInitialViewState.mTextWrapScale;
- int tentativeViewWidth = Math.round(webViewWidth / tentativeScale);
- int windowWidth = calculateWindowWidth(tentativeViewWidth);
- // In viewport setup time, since no content width is known, we assume
- // the windowWidth will be the content width, to get a more likely
- // zoom overview scale.
- data.mScale = (float) webViewWidth / windowWidth;
- if (!mSettings.getLoadWithOverviewMode()) {
- // If user choose non-overview mode.
- data.mScale = Math.max(data.mScale, tentativeScale);
- }
- if (mSettings.isNarrowColumnLayout()) {
- // In case of automatic text reflow in fixed view port mode.
- mInitialViewState.mTextWrapScale =
- mWebViewClassic.computeReadingLevelScale(data.mScale);
- }
- } else {
- // Scale is given such as when page is restored, use it.
- data.mScale = tentativeScale;
- }
- if (DebugFlags.WEB_VIEW_CORE) {
- Log.v(LOGTAG, "setupViewport"
- + " mRestoredScale=" + mRestoredScale
- + " mViewScale=" + mInitialViewState.mViewScale
- + " mTextWrapScale=" + mInitialViewState.mTextWrapScale
- + " data.mScale= " + data.mScale
- );
- }
- data.mWidth = Math.round(webViewWidth / data.mScale);
- // We may get a call here when mCurrentViewHeight == 0 if webcore completes the
- // first layout before we sync our webview dimensions to it. In that case, we
- // request the real height of the webview. This is not a perfect solution as we
- // are calling a WebView method from the WebCore thread. But this is preferable
- // to syncing an incorrect height.
- data.mHeight = mCurrentViewHeight == 0 ?
- Math.round(mWebViewClassic.getViewHeight() / data.mScale)
- : Math.round((float) mCurrentViewHeight * data.mWidth / viewportWidth);
- data.mTextWrapWidth = Math.round(webViewWidth
- / mInitialViewState.mTextWrapScale);
- data.mIgnoreHeight = false;
- data.mAnchorX = data.mAnchorY = 0;
- // send VIEW_SIZE_CHANGED to the front of the queue so that we
- // can avoid pushing the wrong picture to the WebView side.
- mEventHub.removeMessages(EventHub.VIEW_SIZE_CHANGED);
- // Let webkit know the scale and inner width/height immediately
- // in viewport setup time to avoid wrong information.
- viewSizeChanged(data);
- }
- }
- }
-
- // called by JNI
- private void restoreScale(float scale, float textWrapScale) {
- if (mBrowserFrame.firstLayoutDone() == false) {
- mIsRestored = true;
- mRestoredScale = scale;
- if (mSettings.getUseWideViewPort()) {
- mRestoredTextWrapScale = textWrapScale;
- }
- }
- }
-
- // called by JNI
- private void needTouchEvents(boolean need) {
- if (mWebViewClassic != null) {
- Message.obtain(mWebViewClassic.mPrivateHandler,
- WebViewClassic.WEBCORE_NEED_TOUCH_EVENTS, need ? 1 : 0, 0)
- .sendToTarget();
- }
- }
-
- // called by JNI
- private void updateTextfield(int ptr, String text, int textGeneration) {
- if (mWebViewClassic != null) {
- Message.obtain(mWebViewClassic.mPrivateHandler,
- WebViewClassic.UPDATE_TEXTFIELD_TEXT_MSG_ID, ptr,
- textGeneration, text).sendToTarget();
- }
- }
-
- private TextSelectionData createTextSelection(int start, int end, int selPtr) {
- TextSelectionData data = new TextSelectionData(start, end, selPtr);
- data.mSelectionReason = mTextSelectionChangeReason;
- return data;
- }
-
- // called by JNI
- private void updateTextSelection(int pointer, int start, int end,
- int textGeneration, int selectionPtr) {
- if (mWebViewClassic != null) {
- Message.obtain(mWebViewClassic.mPrivateHandler,
- WebViewClassic.UPDATE_TEXT_SELECTION_MSG_ID, pointer, textGeneration,
- createTextSelection(start, end, selectionPtr)).sendToTarget();
- }
- }
-
- // called by JNI
- private void updateTextSizeAndScroll(int pointer, int width, int height,
- int scrollX, int scrollY) {
- if (mWebViewClassic != null) {
- Rect rect = new Rect(-scrollX, -scrollY, width - scrollX,
- height - scrollY);
- Message.obtain(mWebViewClassic.mPrivateHandler,
- WebViewClassic.EDIT_TEXT_SIZE_CHANGED, pointer, 0, rect)
- .sendToTarget();
- }
- }
-
- // called by JNI
- private void clearTextEntry() {
- if (mWebViewClassic == null) return;
- Message.obtain(mWebViewClassic.mPrivateHandler,
- WebViewClassic.CLEAR_TEXT_ENTRY).sendToTarget();
- }
-
- // called by JNI
- private void initEditField(int start, int end, int selectionPtr,
- TextFieldInitData initData) {
- if (mWebViewClassic == null) {
- return;
- }
- Message.obtain(mWebViewClassic.mPrivateHandler,
- WebViewClassic.INIT_EDIT_FIELD, initData).sendToTarget();
- Message.obtain(mWebViewClassic.mPrivateHandler,
- WebViewClassic.UPDATE_TEXT_SELECTION_MSG_ID,
- initData.mFieldPointer, 0,
- createTextSelection(start, end, selectionPtr))
- .sendToTarget();
- }
-
- private native void nativeRevealSelection(int nativeClass);
- private native String nativeRequestLabel(int nativeClass, int framePtr,
- int nodePtr);
- /**
- * Scroll the focused textfield to (xPercent, y) in document space
- */
- private native void nativeScrollFocusedTextInput(int nativeClass,
- float xPercent, int y);
-
- // these must be in document space (i.e. not scaled/zoomed).
- private native void nativeSetScrollOffset(int nativeClass,
- boolean sendScrollEvent, int dx, int dy);
-
- private native void nativeSetGlobalBounds(int nativeClass, int x, int y,
- int w, int h);
-
- // called by JNI
- private void requestListBox(String[] array, int[] enabledArray,
- int[] selectedArray) {
- if (mWebViewClassic != null) {
- mWebViewClassic.requestListBox(array, enabledArray, selectedArray);
- }
- }
-
- // called by JNI
- private void requestListBox(String[] array, int[] enabledArray,
- int selection) {
- if (mWebViewClassic != null) {
- mWebViewClassic.requestListBox(array, enabledArray, selection);
- }
-
- }
-
- // called by JNI
- private void requestKeyboard(boolean showKeyboard) {
- if (mWebViewClassic != null) {
- Message.obtain(mWebViewClassic.mPrivateHandler,
- WebViewClassic.REQUEST_KEYBOARD, showKeyboard ? 1 : 0, 0)
- .sendToTarget();
- }
- }
-
- private void setWebTextViewAutoFillable(int queryId, String preview) {
- if (mWebViewClassic != null) {
- Message.obtain(mWebViewClassic.mPrivateHandler, WebViewClassic.SET_AUTOFILLABLE,
- new AutoFillData(queryId, preview))
- .sendToTarget();
- }
- }
-
- Context getContext() {
- return mContext;
- }
-
- // called by JNI
- private void keepScreenOn(boolean screenOn) {
- if (mWebViewClassic != null) {
- Message message = mWebViewClassic.mPrivateHandler.obtainMessage(
- WebViewClassic.SCREEN_ON);
- message.arg1 = screenOn ? 1 : 0;
- message.sendToTarget();
- }
- }
-
- // called by JNI
- private Class<?> getPluginClass(String libName, String clsName) {
-
- if (mWebViewClassic == null) {
- return null;
- }
-
- PluginManager pluginManager = PluginManager.getInstance(null);
-
- String pkgName = pluginManager.getPluginsAPKName(libName);
- if (pkgName == null) {
- Log.w(LOGTAG, "Unable to resolve " + libName + " to a plugin APK");
- return null;
- }
-
- try {
- return pluginManager.getPluginClass(pkgName, clsName);
- } catch (NameNotFoundException e) {
- Log.e(LOGTAG, "Unable to find plugin classloader for the apk (" + pkgName + ")");
- } catch (ClassNotFoundException e) {
- Log.e(LOGTAG, "Unable to find plugin class (" + clsName +
- ") in the apk (" + pkgName + ")");
- }
-
- return null;
- }
-
- // called by JNI. PluginWidget function to launch a full-screen view using a
- // View object provided by the plugin class.
- private void showFullScreenPlugin(ViewManager.ChildView childView, int orientation, int npp) {
- if (mWebViewClassic == null) {
- return;
- }
-
- Message message = mWebViewClassic.mPrivateHandler.obtainMessage(
- WebViewClassic.SHOW_FULLSCREEN);
- message.obj = childView.mView;
- message.arg1 = orientation;
- message.arg2 = npp;
- message.sendToTarget();
- }
-
- // called by JNI
- private void hideFullScreenPlugin() {
- if (mWebViewClassic == null) {
- return;
- }
- mWebViewClassic.mPrivateHandler.obtainMessage(WebViewClassic.HIDE_FULLSCREEN)
- .sendToTarget();
- }
-
- private ViewManager.ChildView createSurface(View pluginView) {
- if (mWebViewClassic == null) {
- return null;
- }
-
- if (pluginView == null) {
- Log.e(LOGTAG, "Attempted to add an empty plugin view to the view hierarchy");
- return null;
- }
-
- // ensures the view system knows the view can redraw itself
- pluginView.setWillNotDraw(false);
-
- if(pluginView instanceof SurfaceView)
- ((SurfaceView)pluginView).setZOrderOnTop(true);
-
- ViewManager.ChildView view = mWebViewClassic.mViewManager.createView();
- view.mView = pluginView;
- return view;
- }
-
- // called by JNI. PluginWidget functions for creating an embedded View for
- // the surface drawing model.
- private ViewManager.ChildView addSurface(View pluginView, int x, int y,
- int width, int height) {
- ViewManager.ChildView view = createSurface(pluginView);
- view.attachView(x, y, width, height);
- return view;
- }
-
- private void updateSurface(ViewManager.ChildView childView, int x, int y,
- int width, int height) {
- childView.attachView(x, y, width, height);
- }
-
- private void destroySurface(ViewManager.ChildView childView) {
- childView.removeView();
- }
-
- // called by JNI
- static class ShowRectData {
- int mLeft;
- int mTop;
- int mWidth;
- int mHeight;
- int mContentWidth;
- int mContentHeight;
- float mXPercentInDoc;
- float mXPercentInView;
- float mYPercentInDoc;
- float mYPercentInView;
- }
-
- private void showRect(int left, int top, int width, int height,
- int contentWidth, int contentHeight, float xPercentInDoc,
- float xPercentInView, float yPercentInDoc, float yPercentInView) {
- if (mWebViewClassic != null) {
- ShowRectData data = new ShowRectData();
- data.mLeft = left;
- data.mTop = top;
- data.mWidth = width;
- data.mHeight = height;
- data.mContentWidth = contentWidth;
- data.mContentHeight = contentHeight;
- data.mXPercentInDoc = xPercentInDoc;
- data.mXPercentInView = xPercentInView;
- data.mYPercentInDoc = yPercentInDoc;
- data.mYPercentInView = yPercentInView;
- Message.obtain(mWebViewClassic.mPrivateHandler, WebViewClassic.SHOW_RECT_MSG_ID,
- data).sendToTarget();
- }
- }
-
- // called by JNI
- private void centerFitRect(int x, int y, int width, int height) {
- if (mWebViewClassic == null) {
- return;
- }
- mWebViewClassic.mPrivateHandler.obtainMessage(WebViewClassic.CENTER_FIT_RECT,
- new Rect(x, y, x + width, y + height)).sendToTarget();
- }
-
- // called by JNI
- private void setScrollbarModes(int hMode, int vMode) {
- if (mWebViewClassic == null) {
- return;
- }
- mWebViewClassic.mPrivateHandler.obtainMessage(WebViewClassic.SET_SCROLLBAR_MODES,
- hMode, vMode).sendToTarget();
- }
-
- // called by JNI
- private void selectAt(int x, int y) {
- // TODO: Figure out what to do with this (b/6111818)
- }
-
- private void setUseMockDeviceOrientation() {
- mDeviceMotionAndOrientationManager.setUseMock();
- }
-
- private void setUseMockGeolocation() {
- mMockGeolocation.setUseMock();
- }
-
- public void setMockGeolocationPosition(double latitude, double longitude, double accuracy) {
- mMockGeolocation.setPosition(latitude, longitude, accuracy);
- }
-
- public void setMockGeolocationError(int code, String message) {
- mMockGeolocation.setError(code, message);
- }
-
- public void setMockGeolocationPermission(boolean allow) {
- mMockGeolocation.setPermission(allow);
- }
-
- public void setMockDeviceOrientation(boolean canProvideAlpha, double alpha,
- boolean canProvideBeta, double beta, boolean canProvideGamma, double gamma) {
- mDeviceMotionAndOrientationManager.setMockOrientation(canProvideAlpha, alpha,
- canProvideBeta, beta, canProvideGamma, gamma);
- }
-
- protected DeviceMotionService getDeviceMotionService() {
- if (mDeviceMotionService == null) {
- mDeviceMotionService =
- new DeviceMotionService(mDeviceMotionAndOrientationManager, mContext);
- }
- return mDeviceMotionService;
- }
-
- protected DeviceOrientationService getDeviceOrientationService() {
- if (mDeviceOrientationService == null) {
- mDeviceOrientationService =
- new DeviceOrientationService(mDeviceMotionAndOrientationManager, mContext);
- }
- return mDeviceOrientationService;
- }
-
- static void setShouldMonitorWebCoreThread() {
- sShouldMonitorWebCoreThread = true;
- }
-
- private native void nativePause(int nativeClass);
- private native void nativeResume(int nativeClass);
- private native void nativeFreeMemory(int nativeClass);
- private native void nativeFullScreenPluginHidden(int nativeClass, int npp);
- private native void nativePluginSurfaceReady(int nativeClass);
-
- private native WebKitHitTest nativeHitTest(int nativeClass, int x, int y,
- int slop, boolean moveMouse);
-
- private native void nativeAutoFillForm(int nativeClass, int queryId);
- private native void nativeScrollLayer(int nativeClass, int layer, Rect rect);
- private native int nativeFindAll(int nativeClass, String text);
- private native int nativeFindNext(int nativeClass, boolean forward);
-
- /**
- * Deletes editable text between two points. Note that the selection may
- * differ from the WebView's selection because the algorithms for selecting
- * text differs for non-LTR text. Any text that isn't editable will be
- * left unchanged.
- * @param nativeClass The pointer to the native class (mNativeClass)
- * @param startX The X position of the top-left selection point.
- * @param startY The Y position of the top-left selection point.
- * @param endX The X position of the bottom-right selection point.
- * @param endY The Y position of the bottom-right selection point.
- */
- private native void nativeDeleteText(int nativeClass,
- int startX, int startY, int endX, int endY);
- /**
- * Inserts text at the current cursor position. If the currently-focused
- * node does not have a cursor position then this function does nothing.
- */
- private native void nativeInsertText(int nativeClass, String text);
- /**
- * Gets the text between two selection points. Note that the selection
- * may differ from the WebView's selection because the algorithms for
- * selecting text differs for non-LTR text.
- * @param nativeClass The pointer to the native class (mNativeClass)
- * @param startX The X position of the top-left selection point.
- * @param startY The Y position of the top-left selection point.
- * @param endX The X position of the bottom-right selection point.
- * @param endY The Y position of the bottom-right selection point.
- */
- private native String nativeGetText(int nativeClass,
- int startX, int startY, int endX, int endY);
- private native void nativeSelectText(int nativeClass,
- int handleId, int x, int y);
- private native void nativeClearTextSelection(int nativeClass);
- private native boolean nativeSelectWordAt(int nativeClass, int x, int y);
- private native void nativeSelectAll(int nativeClass);
- private native void nativeSetInitialFocus(int nativeClass, int keyDirection);
-
- private static native void nativeCertTrustChanged();
-}
+++ /dev/null
-/*
- * Copyright (C) 2012 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.ArrayList;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Set;
-import java.util.Map.Entry;
-
-import android.content.ContentValues;
-import android.content.Context;
-import android.database.Cursor;
-import android.database.DatabaseUtils;
-import android.database.sqlite.SQLiteDatabase;
-import android.database.sqlite.SQLiteException;
-import android.database.sqlite.SQLiteStatement;
-import android.util.Log;
-
-final class WebViewDatabaseClassic extends WebViewDatabase {
- private static final String LOGTAG = "WebViewDatabaseClassic";
- private static final String DATABASE_FILE = "webview.db";
- private static final String CACHE_DATABASE_FILE = "webviewCache.db";
-
- private static final int DATABASE_VERSION = 11;
- // 2 -> 3 Modified Cache table to allow cache of redirects
- // 3 -> 4 Added Oma-Downloads table
- // 4 -> 5 Modified Cache table to support persistent contentLength
- // 5 -> 4 Removed Oma-Downoads table
- // 5 -> 6 Add INDEX for cache table
- // 6 -> 7 Change cache localPath from int to String
- // 7 -> 8 Move cache to its own db
- // 8 -> 9 Store both scheme and host when storing passwords
- // 9 -> 10 Update httpauth table UNIQUE
- // 10 -> 11 Drop cookies and cache now managed by the chromium stack,
- // and update the form data table to use the new format
- // implemented for b/5265606.
-
- private static WebViewDatabaseClassic sInstance = null;
- private static final Object sInstanceLock = new Object();
-
- private static SQLiteDatabase sDatabase = null;
-
- // synchronize locks
- private final Object mPasswordLock = new Object();
- private final Object mFormLock = new Object();
- private final Object mHttpAuthLock = new Object();
-
- private static final String mTableNames[] = {
- "password", "formurl", "formdata", "httpauth"
- };
-
- // Table ids (they are index to mTableNames)
- private static final int TABLE_PASSWORD_ID = 0;
- private static final int TABLE_FORMURL_ID = 1;
- private static final int TABLE_FORMDATA_ID = 2;
- private static final int TABLE_HTTPAUTH_ID = 3;
-
- // column id strings for "_id" which can be used by any table
- private static final String ID_COL = "_id";
-
- private static final String[] ID_PROJECTION = new String[] {
- "_id"
- };
-
- // column id strings for "password" table
- private static final String PASSWORD_HOST_COL = "host";
- private static final String PASSWORD_USERNAME_COL = "username";
- private static final String PASSWORD_PASSWORD_COL = "password";
-
- // column id strings for "formurl" table
- private static final String FORMURL_URL_COL = "url";
-
- // column id strings for "formdata" table
- private static final String FORMDATA_URLID_COL = "urlid";
- private static final String FORMDATA_NAME_COL = "name";
- private static final String FORMDATA_VALUE_COL = "value";
-
- // column id strings for "httpauth" table
- private static final String HTTPAUTH_HOST_COL = "host";
- private static final String HTTPAUTH_REALM_COL = "realm";
- private static final String HTTPAUTH_USERNAME_COL = "username";
- private static final String HTTPAUTH_PASSWORD_COL = "password";
-
- // Initially true until the background thread completes.
- private boolean mInitialized = false;
-
- private WebViewDatabaseClassic(final Context context) {
- JniUtil.setContext(context);
- new Thread() {
- @Override
- public void run() {
- init(context);
- }
- }.start();
-
- // Singleton only, use getInstance()
- }
-
- public static WebViewDatabaseClassic getInstance(Context context) {
- synchronized (sInstanceLock) {
- if (sInstance == null) {
- sInstance = new WebViewDatabaseClassic(context);
- }
- return sInstance;
- }
- }
-
- private synchronized void init(Context context) {
- if (mInitialized) {
- return;
- }
-
- initDatabase(context);
- // Before using the Chromium HTTP stack, we stored the WebKit cache in
- // our own DB. Clean up the DB file if it's still around.
- context.deleteDatabase(CACHE_DATABASE_FILE);
-
- // Thread done, notify.
- mInitialized = true;
- notify();
- }
-
- private void initDatabase(Context context) {
- try {
- sDatabase = context.openOrCreateDatabase(DATABASE_FILE, 0, null);
- } catch (SQLiteException e) {
- // try again by deleting the old db and create a new one
- if (context.deleteDatabase(DATABASE_FILE)) {
- sDatabase = context.openOrCreateDatabase(DATABASE_FILE, 0,
- null);
- }
- }
-
- // sDatabase should not be null,
- // the only case is RequestAPI test has problem to create db
- if (sDatabase == null) {
- mInitialized = true;
- notify();
- return;
- }
-
- if (sDatabase.getVersion() != DATABASE_VERSION) {
- sDatabase.beginTransactionNonExclusive();
- try {
- upgradeDatabase();
- sDatabase.setTransactionSuccessful();
- } finally {
- sDatabase.endTransaction();
- }
- }
- }
-
- private static void upgradeDatabase() {
- upgradeDatabaseToV10();
- upgradeDatabaseFromV10ToV11();
- // Add future database upgrade functions here, one version at a
- // time.
- sDatabase.setVersion(DATABASE_VERSION);
- }
-
- private static void upgradeDatabaseFromV10ToV11() {
- int oldVersion = sDatabase.getVersion();
-
- if (oldVersion >= 11) {
- // Nothing to do.
- return;
- }
-
- // Clear out old java stack cookies - this data is now stored in
- // a separate database managed by the Chrome stack.
- sDatabase.execSQL("DROP TABLE IF EXISTS cookies");
-
- // Likewise for the old cache table.
- sDatabase.execSQL("DROP TABLE IF EXISTS cache");
-
- // Update form autocomplete URLs to match new ICS formatting.
- Cursor c = sDatabase.query(mTableNames[TABLE_FORMURL_ID], null, null,
- null, null, null, null);
- while (c.moveToNext()) {
- String urlId = Long.toString(c.getLong(c.getColumnIndex(ID_COL)));
- String url = c.getString(c.getColumnIndex(FORMURL_URL_COL));
- ContentValues cv = new ContentValues(1);
- cv.put(FORMURL_URL_COL, WebTextView.urlForAutoCompleteData(url));
- sDatabase.update(mTableNames[TABLE_FORMURL_ID], cv, ID_COL + "=?",
- new String[] { urlId });
- }
- c.close();
- }
-
- private static void upgradeDatabaseToV10() {
- int oldVersion = sDatabase.getVersion();
-
- if (oldVersion >= 10) {
- // Nothing to do.
- return;
- }
-
- if (oldVersion != 0) {
- Log.i(LOGTAG, "Upgrading database from version "
- + oldVersion + " to "
- + DATABASE_VERSION + ", which will destroy old data");
- }
-
- if (9 == oldVersion) {
- sDatabase.execSQL("DROP TABLE IF EXISTS "
- + mTableNames[TABLE_HTTPAUTH_ID]);
- sDatabase.execSQL("CREATE TABLE " + mTableNames[TABLE_HTTPAUTH_ID]
- + " (" + ID_COL + " INTEGER PRIMARY KEY, "
- + HTTPAUTH_HOST_COL + " TEXT, " + HTTPAUTH_REALM_COL
- + " TEXT, " + HTTPAUTH_USERNAME_COL + " TEXT, "
- + HTTPAUTH_PASSWORD_COL + " TEXT," + " UNIQUE ("
- + HTTPAUTH_HOST_COL + ", " + HTTPAUTH_REALM_COL
- + ") ON CONFLICT REPLACE);");
- return;
- }
-
- sDatabase.execSQL("DROP TABLE IF EXISTS cookies");
- sDatabase.execSQL("DROP TABLE IF EXISTS cache");
- sDatabase.execSQL("DROP TABLE IF EXISTS "
- + mTableNames[TABLE_FORMURL_ID]);
- sDatabase.execSQL("DROP TABLE IF EXISTS "
- + mTableNames[TABLE_FORMDATA_ID]);
- sDatabase.execSQL("DROP TABLE IF EXISTS "
- + mTableNames[TABLE_HTTPAUTH_ID]);
- sDatabase.execSQL("DROP TABLE IF EXISTS "
- + mTableNames[TABLE_PASSWORD_ID]);
-
- // formurl
- sDatabase.execSQL("CREATE TABLE " + mTableNames[TABLE_FORMURL_ID]
- + " (" + ID_COL + " INTEGER PRIMARY KEY, " + FORMURL_URL_COL
- + " TEXT" + ");");
-
- // formdata
- sDatabase.execSQL("CREATE TABLE " + mTableNames[TABLE_FORMDATA_ID]
- + " (" + ID_COL + " INTEGER PRIMARY KEY, "
- + FORMDATA_URLID_COL + " INTEGER, " + FORMDATA_NAME_COL
- + " TEXT, " + FORMDATA_VALUE_COL + " TEXT," + " UNIQUE ("
- + FORMDATA_URLID_COL + ", " + FORMDATA_NAME_COL + ", "
- + FORMDATA_VALUE_COL + ") ON CONFLICT IGNORE);");
-
- // httpauth
- sDatabase.execSQL("CREATE TABLE " + mTableNames[TABLE_HTTPAUTH_ID]
- + " (" + ID_COL + " INTEGER PRIMARY KEY, "
- + HTTPAUTH_HOST_COL + " TEXT, " + HTTPAUTH_REALM_COL
- + " TEXT, " + HTTPAUTH_USERNAME_COL + " TEXT, "
- + HTTPAUTH_PASSWORD_COL + " TEXT," + " UNIQUE ("
- + HTTPAUTH_HOST_COL + ", " + HTTPAUTH_REALM_COL
- + ") ON CONFLICT REPLACE);");
- // passwords
- sDatabase.execSQL("CREATE TABLE " + mTableNames[TABLE_PASSWORD_ID]
- + " (" + ID_COL + " INTEGER PRIMARY KEY, "
- + PASSWORD_HOST_COL + " TEXT, " + PASSWORD_USERNAME_COL
- + " TEXT, " + PASSWORD_PASSWORD_COL + " TEXT," + " UNIQUE ("
- + PASSWORD_HOST_COL + ", " + PASSWORD_USERNAME_COL
- + ") ON CONFLICT REPLACE);");
- }
-
- // Wait for the background initialization thread to complete and check the
- // database creation status.
- private boolean checkInitialized() {
- synchronized (this) {
- while (!mInitialized) {
- try {
- wait();
- } catch (InterruptedException e) {
- Log.e(LOGTAG, "Caught exception while checking " +
- "initialization");
- Log.e(LOGTAG, Log.getStackTraceString(e));
- }
- }
- }
- return sDatabase != null;
- }
-
- private boolean hasEntries(int tableId) {
- if (!checkInitialized()) {
- return false;
- }
-
- Cursor cursor = null;
- boolean ret = false;
- try {
- cursor = sDatabase.query(mTableNames[tableId], ID_PROJECTION,
- null, null, null, null, null);
- ret = cursor.moveToFirst() == true;
- } catch (IllegalStateException e) {
- Log.e(LOGTAG, "hasEntries", e);
- } finally {
- if (cursor != null) cursor.close();
- }
- return ret;
- }
-
- //
- // password functions
- //
-
- /**
- * Set password. Tuple (PASSWORD_HOST_COL, PASSWORD_USERNAME_COL) is unique.
- *
- * @param schemePlusHost The scheme and host for the password
- * @param username The username for the password. If it is null, it means
- * password can't be saved.
- * @param password The password
- */
- void setUsernamePassword(String schemePlusHost, String username,
- String password) {
- if (schemePlusHost == null || !checkInitialized()) {
- return;
- }
-
- synchronized (mPasswordLock) {
- final ContentValues c = new ContentValues();
- c.put(PASSWORD_HOST_COL, schemePlusHost);
- c.put(PASSWORD_USERNAME_COL, username);
- c.put(PASSWORD_PASSWORD_COL, password);
- sDatabase.insert(mTableNames[TABLE_PASSWORD_ID], PASSWORD_HOST_COL,
- c);
- }
- }
-
- /**
- * Retrieve the username and password for a given host
- *
- * @param schemePlusHost The scheme and host which passwords applies to
- * @return String[] if found, String[0] is username, which can be null and
- * String[1] is password. Return null if it can't find anything.
- */
- String[] getUsernamePassword(String schemePlusHost) {
- if (schemePlusHost == null || !checkInitialized()) {
- return null;
- }
-
- final String[] columns = new String[] {
- PASSWORD_USERNAME_COL, PASSWORD_PASSWORD_COL
- };
- final String selection = "(" + PASSWORD_HOST_COL + " == ?)";
- synchronized (mPasswordLock) {
- String[] ret = null;
- Cursor cursor = null;
- try {
- cursor = sDatabase.query(mTableNames[TABLE_PASSWORD_ID],
- columns, selection, new String[] { schemePlusHost }, null,
- null, null);
- if (cursor.moveToFirst()) {
- ret = new String[2];
- ret[0] = cursor.getString(
- cursor.getColumnIndex(PASSWORD_USERNAME_COL));
- ret[1] = cursor.getString(
- cursor.getColumnIndex(PASSWORD_PASSWORD_COL));
- }
- } catch (IllegalStateException e) {
- Log.e(LOGTAG, "getUsernamePassword", e);
- } finally {
- if (cursor != null) cursor.close();
- }
- return ret;
- }
- }
-
- /**
- * @see WebViewDatabase#hasUsernamePassword
- */
- @Override
- public boolean hasUsernamePassword() {
- synchronized (mPasswordLock) {
- return hasEntries(TABLE_PASSWORD_ID);
- }
- }
-
- /**
- * @see WebViewDatabase#clearUsernamePassword
- */
- @Override
- public void clearUsernamePassword() {
- if (!checkInitialized()) {
- return;
- }
-
- synchronized (mPasswordLock) {
- sDatabase.delete(mTableNames[TABLE_PASSWORD_ID], null, null);
- }
- }
-
- //
- // http authentication password functions
- //
-
- /**
- * Set HTTP authentication password. Tuple (HTTPAUTH_HOST_COL,
- * HTTPAUTH_REALM_COL, HTTPAUTH_USERNAME_COL) is unique.
- *
- * @param host The host for the password
- * @param realm The realm for the password
- * @param username The username for the password. If it is null, it means
- * password can't be saved.
- * @param password The password
- */
- void setHttpAuthUsernamePassword(String host, String realm, String username,
- String password) {
- if (host == null || realm == null || !checkInitialized()) {
- return;
- }
-
- synchronized (mHttpAuthLock) {
- final ContentValues c = new ContentValues();
- c.put(HTTPAUTH_HOST_COL, host);
- c.put(HTTPAUTH_REALM_COL, realm);
- c.put(HTTPAUTH_USERNAME_COL, username);
- c.put(HTTPAUTH_PASSWORD_COL, password);
- sDatabase.insert(mTableNames[TABLE_HTTPAUTH_ID], HTTPAUTH_HOST_COL,
- c);
- }
- }
-
- /**
- * Retrieve the HTTP authentication username and password for a given
- * host+realm pair
- *
- * @param host The host the password applies to
- * @param realm The realm the password applies to
- * @return String[] if found, String[0] is username, which can be null and
- * String[1] is password. Return null if it can't find anything.
- */
- String[] getHttpAuthUsernamePassword(String host, String realm) {
- if (host == null || realm == null || !checkInitialized()){
- return null;
- }
-
- final String[] columns = new String[] {
- HTTPAUTH_USERNAME_COL, HTTPAUTH_PASSWORD_COL
- };
- final String selection = "(" + HTTPAUTH_HOST_COL + " == ?) AND ("
- + HTTPAUTH_REALM_COL + " == ?)";
- synchronized (mHttpAuthLock) {
- String[] ret = null;
- Cursor cursor = null;
- try {
- cursor = sDatabase.query(mTableNames[TABLE_HTTPAUTH_ID],
- columns, selection, new String[] { host, realm }, null,
- null, null);
- if (cursor.moveToFirst()) {
- ret = new String[2];
- ret[0] = cursor.getString(
- cursor.getColumnIndex(HTTPAUTH_USERNAME_COL));
- ret[1] = cursor.getString(
- cursor.getColumnIndex(HTTPAUTH_PASSWORD_COL));
- }
- } catch (IllegalStateException e) {
- Log.e(LOGTAG, "getHttpAuthUsernamePassword", e);
- } finally {
- if (cursor != null) cursor.close();
- }
- return ret;
- }
- }
-
- /**
- * @see WebViewDatabase#hasHttpAuthUsernamePassword
- */
- @Override
- public boolean hasHttpAuthUsernamePassword() {
- synchronized (mHttpAuthLock) {
- return hasEntries(TABLE_HTTPAUTH_ID);
- }
- }
-
- /**
- * @see WebViewDatabase#clearHttpAuthUsernamePassword
- */
- @Override
- public void clearHttpAuthUsernamePassword() {
- if (!checkInitialized()) {
- return;
- }
-
- synchronized (mHttpAuthLock) {
- sDatabase.delete(mTableNames[TABLE_HTTPAUTH_ID], null, null);
- }
- }
-
- //
- // form data functions
- //
-
- /**
- * Set form data for a site. Tuple (FORMDATA_URLID_COL, FORMDATA_NAME_COL,
- * FORMDATA_VALUE_COL) is unique
- *
- * @param url The url of the site
- * @param formdata The form data in HashMap
- */
- void setFormData(String url, HashMap<String, String> formdata) {
- if (url == null || formdata == null || !checkInitialized()) {
- return;
- }
-
- final String selection = "(" + FORMURL_URL_COL + " == ?)";
- synchronized (mFormLock) {
- long urlid = -1;
- Cursor cursor = null;
- try {
- cursor = sDatabase.query(mTableNames[TABLE_FORMURL_ID],
- ID_PROJECTION, selection, new String[] { url }, null, null,
- null);
- if (cursor.moveToFirst()) {
- urlid = cursor.getLong(cursor.getColumnIndex(ID_COL));
- } else {
- ContentValues c = new ContentValues();
- c.put(FORMURL_URL_COL, url);
- urlid = sDatabase.insert(
- mTableNames[TABLE_FORMURL_ID], null, c);
- }
- } catch (IllegalStateException e) {
- Log.e(LOGTAG, "setFormData", e);
- } finally {
- if (cursor != null) cursor.close();
- }
- if (urlid >= 0) {
- Set<Entry<String, String>> set = formdata.entrySet();
- Iterator<Entry<String, String>> iter = set.iterator();
- ContentValues map = new ContentValues();
- map.put(FORMDATA_URLID_COL, urlid);
- while (iter.hasNext()) {
- Entry<String, String> entry = iter.next();
- map.put(FORMDATA_NAME_COL, entry.getKey());
- map.put(FORMDATA_VALUE_COL, entry.getValue());
- sDatabase.insert(mTableNames[TABLE_FORMDATA_ID], null, map);
- }
- }
- }
- }
-
- /**
- * Get all the values for a form entry with "name" in a given site
- *
- * @param url The url of the site
- * @param name The name of the form entry
- * @return A list of values. Return empty list if nothing is found.
- */
- ArrayList<String> getFormData(String url, String name) {
- ArrayList<String> values = new ArrayList<String>();
- if (url == null || name == null || !checkInitialized()) {
- return values;
- }
-
- final String urlSelection = "(" + FORMURL_URL_COL + " == ?)";
- final String dataSelection = "(" + FORMDATA_URLID_COL + " == ?) AND ("
- + FORMDATA_NAME_COL + " == ?)";
- synchronized (mFormLock) {
- Cursor cursor = null;
- try {
- cursor = sDatabase.query(mTableNames[TABLE_FORMURL_ID],
- ID_PROJECTION, urlSelection, new String[] { url }, null,
- null, null);
- while (cursor.moveToNext()) {
- long urlid = cursor.getLong(cursor.getColumnIndex(ID_COL));
- Cursor dataCursor = null;
- try {
- dataCursor = sDatabase.query(
- mTableNames[TABLE_FORMDATA_ID],
- new String[] { ID_COL, FORMDATA_VALUE_COL },
- dataSelection,
- new String[] { Long.toString(urlid), name },
- null, null, null);
- if (dataCursor.moveToFirst()) {
- int valueCol = dataCursor.getColumnIndex(
- FORMDATA_VALUE_COL);
- do {
- values.add(dataCursor.getString(valueCol));
- } while (dataCursor.moveToNext());
- }
- } catch (IllegalStateException e) {
- Log.e(LOGTAG, "getFormData dataCursor", e);
- } finally {
- if (dataCursor != null) dataCursor.close();
- }
- }
- } catch (IllegalStateException e) {
- Log.e(LOGTAG, "getFormData cursor", e);
- } finally {
- if (cursor != null) cursor.close();
- }
- return values;
- }
- }
-
- /**
- * @see WebViewDatabase#hasFormData
- */
- @Override
- public boolean hasFormData() {
- synchronized (mFormLock) {
- return hasEntries(TABLE_FORMURL_ID);
- }
- }
-
- /**
- * @see WebViewDatabase#clearFormData
- */
- @Override
- public void clearFormData() {
- if (!checkInitialized()) {
- return;
- }
-
- synchronized (mFormLock) {
- sDatabase.delete(mTableNames[TABLE_FORMURL_ID], null, null);
- sDatabase.delete(mTableNames[TABLE_FORMDATA_ID], null, null);
- }
- }
-}
+++ /dev/null
-/*
- * Copyright (C) 2012 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.content.Context;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.Message;
-import android.os.SystemClock;
-import android.util.Log;
-import android.view.MotionEvent;
-import android.view.ViewConfiguration;
-
-/**
- * Perform asynchronous dispatch of input events in a {@link WebView}.
- *
- * This dispatcher is shared by the UI thread ({@link WebViewClassic}) and web kit
- * thread ({@link WebViewCore}). The UI thread enqueues events for
- * processing, waits for the web kit thread to handle them, and then performs
- * additional processing depending on the outcome.
- *
- * How it works:
- *
- * 1. The web view thread receives an input event from the input system on the UI
- * thread in its {@link WebViewClassic#onTouchEvent} handler. It sends the input event
- * to the dispatcher, then immediately returns true to the input system to indicate that
- * it will handle the event.
- *
- * 2. The web kit thread is notified that an event has been enqueued. Meanwhile additional
- * events may be enqueued from the UI thread. In some cases, the dispatcher may decide to
- * coalesce motion events into larger batches or to cancel events that have been
- * sitting in the queue for too long.
- *
- * 3. The web kit thread wakes up and handles all input events that are waiting for it.
- * After processing each input event, it informs the dispatcher whether the web application
- * has decided to handle the event itself and to prevent default event handling.
- *
- * 4. If web kit indicates that it wants to prevent default event handling, then web kit
- * consumes the remainder of the gesture and web view receives a cancel event if
- * needed. Otherwise, the web view handles the gesture on the UI thread normally.
- *
- * 5. If the web kit thread takes too long to handle an input event, then it loses the
- * right to handle it. The dispatcher synthesizes a cancellation event for web kit and
- * then tells the web view on the UI thread to handle the event that timed out along
- * with the rest of the gesture.
- *
- * One thing to keep in mind about the dispatcher is that what goes into the dispatcher
- * is not necessarily what the web kit or UI thread will see. As mentioned above, the
- * dispatcher may tweak the input event stream to improve responsiveness. Both web view and
- * web kit are guaranteed to perceive a consistent stream of input events but
- * they might not always see the same events (especially if one decides
- * to prevent the other from handling a particular gesture).
- *
- * This implementation very deliberately does not refer to the {@link WebViewClassic}
- * or {@link WebViewCore} classes, preferring to communicate with them only via
- * interfaces to avoid unintentional coupling to their implementation details.
- *
- * Currently, the input dispatcher only handles pointer events (includes touch,
- * hover and scroll events). In principle, it could be extended to handle trackball
- * and key events if needed.
- *
- * @hide
- */
-final class WebViewInputDispatcher {
- private static final String TAG = "WebViewInputDispatcher";
- private static final boolean DEBUG = false;
- // This enables batching of MotionEvents. It will combine multiple MotionEvents
- // together into a single MotionEvent if more events come in while we are
- // still waiting on the processing of a previous event.
- // If this is set to false, we will instead opt to drop ACTION_MOVE
- // events we cannot keep up with.
- // TODO: If batching proves to be working well, remove this
- private static final boolean ENABLE_EVENT_BATCHING = true;
-
- private final Object mLock = new Object();
-
- // Pool of queued input events. (guarded by mLock)
- private static final int MAX_DISPATCH_EVENT_POOL_SIZE = 10;
- private DispatchEvent mDispatchEventPool;
- private int mDispatchEventPoolSize;
-
- // Posted state, tracks events posted to the dispatcher. (guarded by mLock)
- private final TouchStream mPostTouchStream = new TouchStream();
- private boolean mPostSendTouchEventsToWebKit;
- private boolean mPostDoNotSendTouchEventsToWebKitUntilNextGesture;
- private boolean mPostLongPressScheduled;
- private boolean mPostClickScheduled;
- private boolean mPostShowTapHighlightScheduled;
- private boolean mPostHideTapHighlightScheduled;
- private int mPostLastWebKitXOffset;
- private int mPostLastWebKitYOffset;
- private float mPostLastWebKitScale;
-
- // State for event tracking (click, longpress, double tap, etc..)
- private boolean mIsDoubleTapCandidate;
- private boolean mIsTapCandidate;
- private float mInitialDownX;
- private float mInitialDownY;
- private float mTouchSlopSquared;
- private float mDoubleTapSlopSquared;
-
- // Web kit state, tracks events observed by web kit. (guarded by mLock)
- private final DispatchEventQueue mWebKitDispatchEventQueue = new DispatchEventQueue();
- private final TouchStream mWebKitTouchStream = new TouchStream();
- private final WebKitCallbacks mWebKitCallbacks;
- private final WebKitHandler mWebKitHandler;
- private boolean mWebKitDispatchScheduled;
- private boolean mWebKitTimeoutScheduled;
- private long mWebKitTimeoutTime;
-
- // UI state, tracks events observed by the UI. (guarded by mLock)
- private final DispatchEventQueue mUiDispatchEventQueue = new DispatchEventQueue();
- private final TouchStream mUiTouchStream = new TouchStream();
- private final UiCallbacks mUiCallbacks;
- private final UiHandler mUiHandler;
- private boolean mUiDispatchScheduled;
-
- // Give up on web kit handling of input events when this timeout expires.
- private static final long WEBKIT_TIMEOUT_MILLIS = 200;
- private static final int TAP_TIMEOUT = ViewConfiguration.getTapTimeout();
- private static final int LONG_PRESS_TIMEOUT =
- ViewConfiguration.getLongPressTimeout() + TAP_TIMEOUT;
- private static final int DOUBLE_TAP_TIMEOUT = ViewConfiguration.getDoubleTapTimeout();
- private static final int PRESSED_STATE_DURATION = ViewConfiguration.getPressedStateDuration();
-
- /**
- * Event type: Indicates a touch event type.
- *
- * This event is delivered together with a {@link MotionEvent} with one of the
- * following actions: {@link MotionEvent#ACTION_DOWN}, {@link MotionEvent#ACTION_MOVE},
- * {@link MotionEvent#ACTION_UP}, {@link MotionEvent#ACTION_POINTER_DOWN},
- * {@link MotionEvent#ACTION_POINTER_UP}, {@link MotionEvent#ACTION_CANCEL}.
- */
- public static final int EVENT_TYPE_TOUCH = 0;
-
- /**
- * Event type: Indicates a hover event type.
- *
- * This event is delivered together with a {@link MotionEvent} with one of the
- * following actions: {@link MotionEvent#ACTION_HOVER_ENTER},
- * {@link MotionEvent#ACTION_HOVER_MOVE}, {@link MotionEvent#ACTION_HOVER_MOVE}.
- */
- public static final int EVENT_TYPE_HOVER = 1;
-
- /**
- * Event type: Indicates a scroll event type.
- *
- * This event is delivered together with a {@link MotionEvent} with action
- * {@link MotionEvent#ACTION_SCROLL}.
- */
- public static final int EVENT_TYPE_SCROLL = 2;
-
- /**
- * Event type: Indicates a long-press event type.
- *
- * This event is delivered in the middle of a sequence of {@link #EVENT_TYPE_TOUCH} events.
- * It includes a {@link MotionEvent} with action {@link MotionEvent#ACTION_MOVE}
- * that indicates the current touch coordinates of the long-press.
- *
- * This event is sent when the current touch gesture has been held longer than
- * the long-press interval.
- */
- public static final int EVENT_TYPE_LONG_PRESS = 3;
-
- /**
- * Event type: Indicates a click event type.
- *
- * This event is delivered after a sequence of {@link #EVENT_TYPE_TOUCH} events that
- * comprise a complete gesture ending with {@link MotionEvent#ACTION_UP}.
- * It includes a {@link MotionEvent} with action {@link MotionEvent#ACTION_UP}
- * that indicates the location of the click.
- *
- * This event is sent shortly after the end of a touch after the double-tap
- * interval has expired to indicate a click.
- */
- public static final int EVENT_TYPE_CLICK = 4;
-
- /**
- * Event type: Indicates a double-tap event type.
- *
- * This event is delivered after a sequence of {@link #EVENT_TYPE_TOUCH} events that
- * comprise a complete gesture ending with {@link MotionEvent#ACTION_UP}.
- * It includes a {@link MotionEvent} with action {@link MotionEvent#ACTION_UP}
- * that indicates the location of the double-tap.
- *
- * This event is sent immediately after a sequence of two touches separated
- * in time by no more than the double-tap interval and separated in space
- * by no more than the double-tap slop.
- */
- public static final int EVENT_TYPE_DOUBLE_TAP = 5;
-
- /**
- * Event type: Indicates that a hit test should be performed
- */
- public static final int EVENT_TYPE_HIT_TEST = 6;
-
- /**
- * Flag: This event is private to this queue. Do not forward it.
- */
- public static final int FLAG_PRIVATE = 1 << 0;
-
- /**
- * Flag: This event is currently being processed by web kit.
- * If a timeout occurs, make a copy of it before forwarding the event to another queue.
- */
- public static final int FLAG_WEBKIT_IN_PROGRESS = 1 << 1;
-
- /**
- * Flag: A timeout occurred while waiting for web kit to process this input event.
- */
- public static final int FLAG_WEBKIT_TIMEOUT = 1 << 2;
-
- /**
- * Flag: Indicates that the event was transformed for delivery to web kit.
- * The event must be transformed back before being delivered to the UI.
- */
- public static final int FLAG_WEBKIT_TRANSFORMED_EVENT = 1 << 3;
-
- public WebViewInputDispatcher(UiCallbacks uiCallbacks, WebKitCallbacks webKitCallbacks) {
- this.mUiCallbacks = uiCallbacks;
- mUiHandler = new UiHandler(uiCallbacks.getUiLooper());
-
- this.mWebKitCallbacks = webKitCallbacks;
- mWebKitHandler = new WebKitHandler(webKitCallbacks.getWebKitLooper());
-
- ViewConfiguration config = ViewConfiguration.get(mUiCallbacks.getContext());
- mDoubleTapSlopSquared = config.getScaledDoubleTapSlop();
- mDoubleTapSlopSquared = (mDoubleTapSlopSquared * mDoubleTapSlopSquared);
- mTouchSlopSquared = config.getScaledTouchSlop();
- mTouchSlopSquared = (mTouchSlopSquared * mTouchSlopSquared);
- }
-
- /**
- * Sets whether web kit wants to receive touch events.
- *
- * @param enable True to enable dispatching of touch events to web kit, otherwise
- * web kit will be skipped.
- */
- public void setWebKitWantsTouchEvents(boolean enable) {
- if (DEBUG) {
- Log.d(TAG, "webkitWantsTouchEvents: " + enable);
- }
- synchronized (mLock) {
- if (mPostSendTouchEventsToWebKit != enable) {
- if (!enable) {
- enqueueWebKitCancelTouchEventIfNeededLocked();
- }
- mPostSendTouchEventsToWebKit = enable;
- }
- }
- }
-
- /**
- * Posts a pointer event to the dispatch queue.
- *
- * @param event The event to post.
- * @param webKitXOffset X offset to apply to events before dispatching them to web kit.
- * @param webKitYOffset Y offset to apply to events before dispatching them to web kit.
- * @param webKitScale The scale factor to apply to translated events before dispatching
- * them to web kit.
- * @return True if the dispatcher will handle the event, false if the event is unsupported.
- */
- public boolean postPointerEvent(MotionEvent event,
- int webKitXOffset, int webKitYOffset, float webKitScale) {
- if (event == null) {
- throw new IllegalArgumentException("event cannot be null");
- }
-
- if (DEBUG) {
- Log.d(TAG, "postPointerEvent: " + event);
- }
-
- final int action = event.getActionMasked();
- final int eventType;
- switch (action) {
- case MotionEvent.ACTION_DOWN:
- case MotionEvent.ACTION_MOVE:
- case MotionEvent.ACTION_UP:
- case MotionEvent.ACTION_POINTER_DOWN:
- case MotionEvent.ACTION_POINTER_UP:
- case MotionEvent.ACTION_CANCEL:
- eventType = EVENT_TYPE_TOUCH;
- break;
- case MotionEvent.ACTION_SCROLL:
- eventType = EVENT_TYPE_SCROLL;
- break;
- case MotionEvent.ACTION_HOVER_ENTER:
- case MotionEvent.ACTION_HOVER_MOVE:
- case MotionEvent.ACTION_HOVER_EXIT:
- eventType = EVENT_TYPE_HOVER;
- break;
- default:
- return false; // currently unsupported event type
- }
-
- synchronized (mLock) {
- // Ensure that the event is consistent and should be delivered.
- MotionEvent eventToEnqueue = event;
- if (eventType == EVENT_TYPE_TOUCH) {
- eventToEnqueue = mPostTouchStream.update(event);
- if (eventToEnqueue == null) {
- if (DEBUG) {
- Log.d(TAG, "postPointerEvent: dropped event " + event);
- }
- unscheduleLongPressLocked();
- unscheduleClickLocked();
- hideTapCandidateLocked();
- return false;
- }
-
- if (action == MotionEvent.ACTION_DOWN && mPostSendTouchEventsToWebKit) {
- if (mUiCallbacks.shouldInterceptTouchEvent(eventToEnqueue)) {
- mPostDoNotSendTouchEventsToWebKitUntilNextGesture = true;
- } else if (mPostDoNotSendTouchEventsToWebKitUntilNextGesture) {
- // Recover from a previous web kit timeout.
- mPostDoNotSendTouchEventsToWebKitUntilNextGesture = false;
- }
- }
- }
-
- // Copy the event because we need to retain ownership.
- if (eventToEnqueue == event) {
- eventToEnqueue = event.copy();
- }
-
- DispatchEvent d = obtainDispatchEventLocked(eventToEnqueue, eventType, 0,
- webKitXOffset, webKitYOffset, webKitScale);
- updateStateTrackersLocked(d, event);
- enqueueEventLocked(d);
- }
- return true;
- }
-
- private void scheduleLongPressLocked() {
- unscheduleLongPressLocked();
- mPostLongPressScheduled = true;
- mUiHandler.sendEmptyMessageDelayed(UiHandler.MSG_LONG_PRESS,
- LONG_PRESS_TIMEOUT);
- }
-
- private void unscheduleLongPressLocked() {
- if (mPostLongPressScheduled) {
- mPostLongPressScheduled = false;
- mUiHandler.removeMessages(UiHandler.MSG_LONG_PRESS);
- }
- }
-
- private void postLongPress() {
- synchronized (mLock) {
- if (!mPostLongPressScheduled) {
- return;
- }
- mPostLongPressScheduled = false;
-
- MotionEvent event = mPostTouchStream.getLastEvent();
- if (event == null) {
- return;
- }
-
- switch (event.getActionMasked()) {
- case MotionEvent.ACTION_DOWN:
- case MotionEvent.ACTION_MOVE:
- case MotionEvent.ACTION_POINTER_DOWN:
- case MotionEvent.ACTION_POINTER_UP:
- break;
- default:
- return;
- }
-
- MotionEvent eventToEnqueue = MotionEvent.obtainNoHistory(event);
- eventToEnqueue.setAction(MotionEvent.ACTION_MOVE);
- DispatchEvent d = obtainDispatchEventLocked(eventToEnqueue, EVENT_TYPE_LONG_PRESS, 0,
- mPostLastWebKitXOffset, mPostLastWebKitYOffset, mPostLastWebKitScale);
- enqueueEventLocked(d);
- }
- }
-
- private void hideTapCandidateLocked() {
- unscheduleHideTapHighlightLocked();
- unscheduleShowTapHighlightLocked();
- mUiCallbacks.showTapHighlight(false);
- }
-
- private void showTapCandidateLocked() {
- unscheduleHideTapHighlightLocked();
- unscheduleShowTapHighlightLocked();
- mUiCallbacks.showTapHighlight(true);
- }
-
- private void scheduleShowTapHighlightLocked() {
- unscheduleShowTapHighlightLocked();
- mPostShowTapHighlightScheduled = true;
- mUiHandler.sendEmptyMessageDelayed(UiHandler.MSG_SHOW_TAP_HIGHLIGHT,
- TAP_TIMEOUT);
- }
-
- private void unscheduleShowTapHighlightLocked() {
- if (mPostShowTapHighlightScheduled) {
- mPostShowTapHighlightScheduled = false;
- mUiHandler.removeMessages(UiHandler.MSG_SHOW_TAP_HIGHLIGHT);
- }
- }
-
- private void scheduleHideTapHighlightLocked() {
- unscheduleHideTapHighlightLocked();
- mPostHideTapHighlightScheduled = true;
- mUiHandler.sendEmptyMessageDelayed(UiHandler.MSG_HIDE_TAP_HIGHLIGHT,
- PRESSED_STATE_DURATION);
- }
-
- private void unscheduleHideTapHighlightLocked() {
- if (mPostHideTapHighlightScheduled) {
- mPostHideTapHighlightScheduled = false;
- mUiHandler.removeMessages(UiHandler.MSG_HIDE_TAP_HIGHLIGHT);
- }
- }
-
- private void postShowTapHighlight(boolean show) {
- synchronized (mLock) {
- if (show) {
- if (!mPostShowTapHighlightScheduled) {
- return;
- }
- mPostShowTapHighlightScheduled = false;
- } else {
- if (!mPostHideTapHighlightScheduled) {
- return;
- }
- mPostHideTapHighlightScheduled = false;
- }
- mUiCallbacks.showTapHighlight(show);
- }
- }
-
- private void scheduleClickLocked() {
- unscheduleClickLocked();
- mPostClickScheduled = true;
- mUiHandler.sendEmptyMessageDelayed(UiHandler.MSG_CLICK, DOUBLE_TAP_TIMEOUT);
- }
-
- private void unscheduleClickLocked() {
- if (mPostClickScheduled) {
- mPostClickScheduled = false;
- mUiHandler.removeMessages(UiHandler.MSG_CLICK);
- }
- }
-
- private void postClick() {
- synchronized (mLock) {
- if (!mPostClickScheduled) {
- return;
- }
- mPostClickScheduled = false;
-
- MotionEvent event = mPostTouchStream.getLastEvent();
- if (event == null || event.getAction() != MotionEvent.ACTION_UP) {
- return;
- }
-
- showTapCandidateLocked();
- MotionEvent eventToEnqueue = MotionEvent.obtainNoHistory(event);
- DispatchEvent d = obtainDispatchEventLocked(eventToEnqueue, EVENT_TYPE_CLICK, 0,
- mPostLastWebKitXOffset, mPostLastWebKitYOffset, mPostLastWebKitScale);
- enqueueEventLocked(d);
- }
- }
-
- private void checkForDoubleTapOnDownLocked(MotionEvent event) {
- mIsDoubleTapCandidate = false;
- if (!mPostClickScheduled) {
- return;
- }
- int deltaX = (int) mInitialDownX - (int) event.getX();
- int deltaY = (int) mInitialDownY - (int) event.getY();
- if ((deltaX * deltaX + deltaY * deltaY) < mDoubleTapSlopSquared) {
- unscheduleClickLocked();
- mIsDoubleTapCandidate = true;
- }
- }
-
- private boolean isClickCandidateLocked(MotionEvent event) {
- if (event == null
- || event.getActionMasked() != MotionEvent.ACTION_UP
- || !mIsTapCandidate) {
- return false;
- }
- long downDuration = event.getEventTime() - event.getDownTime();
- return downDuration < LONG_PRESS_TIMEOUT;
- }
-
- private void enqueueDoubleTapLocked(MotionEvent event) {
- MotionEvent eventToEnqueue = MotionEvent.obtainNoHistory(event);
- DispatchEvent d = obtainDispatchEventLocked(eventToEnqueue, EVENT_TYPE_DOUBLE_TAP, 0,
- mPostLastWebKitXOffset, mPostLastWebKitYOffset, mPostLastWebKitScale);
- enqueueEventLocked(d);
- }
-
- private void enqueueHitTestLocked(MotionEvent event) {
- mUiCallbacks.clearPreviousHitTest();
- MotionEvent eventToEnqueue = MotionEvent.obtainNoHistory(event);
- DispatchEvent d = obtainDispatchEventLocked(eventToEnqueue, EVENT_TYPE_HIT_TEST, 0,
- mPostLastWebKitXOffset, mPostLastWebKitYOffset, mPostLastWebKitScale);
- enqueueEventLocked(d);
- }
-
- private void checkForSlopLocked(MotionEvent event) {
- if (!mIsTapCandidate) {
- return;
- }
- int deltaX = (int) mInitialDownX - (int) event.getX();
- int deltaY = (int) mInitialDownY - (int) event.getY();
- if ((deltaX * deltaX + deltaY * deltaY) > mTouchSlopSquared) {
- unscheduleLongPressLocked();
- mIsTapCandidate = false;
- hideTapCandidateLocked();
- }
- }
-
- private void updateStateTrackersLocked(DispatchEvent d, MotionEvent event) {
- mPostLastWebKitXOffset = d.mWebKitXOffset;
- mPostLastWebKitYOffset = d.mWebKitYOffset;
- mPostLastWebKitScale = d.mWebKitScale;
- int action = event != null ? event.getAction() : MotionEvent.ACTION_CANCEL;
- if (d.mEventType != EVENT_TYPE_TOUCH) {
- return;
- }
-
- if (action == MotionEvent.ACTION_CANCEL
- || event.getPointerCount() > 1) {
- unscheduleLongPressLocked();
- unscheduleClickLocked();
- hideTapCandidateLocked();
- mIsDoubleTapCandidate = false;
- mIsTapCandidate = false;
- hideTapCandidateLocked();
- } else if (action == MotionEvent.ACTION_DOWN) {
- checkForDoubleTapOnDownLocked(event);
- scheduleLongPressLocked();
- mIsTapCandidate = true;
- mInitialDownX = event.getX();
- mInitialDownY = event.getY();
- enqueueHitTestLocked(event);
- if (mIsDoubleTapCandidate) {
- hideTapCandidateLocked();
- } else {
- scheduleShowTapHighlightLocked();
- }
- } else if (action == MotionEvent.ACTION_UP) {
- unscheduleLongPressLocked();
- if (isClickCandidateLocked(event)) {
- if (mIsDoubleTapCandidate) {
- hideTapCandidateLocked();
- enqueueDoubleTapLocked(event);
- } else {
- scheduleClickLocked();
- }
- } else {
- hideTapCandidateLocked();
- }
- } else if (action == MotionEvent.ACTION_MOVE) {
- checkForSlopLocked(event);
- }
- }
-
- /**
- * Dispatches pending web kit events.
- * Must only be called from the web kit thread.
- *
- * This method may be used to flush the queue of pending input events
- * immediately. This method may help to reduce input dispatch latency
- * if called before certain expensive operations such as drawing.
- */
- public void dispatchWebKitEvents() {
- dispatchWebKitEvents(false);
- }
-
- private void dispatchWebKitEvents(boolean calledFromHandler) {
- for (;;) {
- // Get the next event, but leave it in the queue so we can move it to the UI
- // queue if a timeout occurs.
- DispatchEvent d;
- MotionEvent event;
- final int eventType;
- int flags;
- synchronized (mLock) {
- if (!ENABLE_EVENT_BATCHING) {
- drainStaleWebKitEventsLocked();
- }
- d = mWebKitDispatchEventQueue.mHead;
- if (d == null) {
- if (mWebKitDispatchScheduled) {
- mWebKitDispatchScheduled = false;
- if (!calledFromHandler) {
- mWebKitHandler.removeMessages(
- WebKitHandler.MSG_DISPATCH_WEBKIT_EVENTS);
- }
- }
- return;
- }
-
- event = d.mEvent;
- if (event != null) {
- event.offsetLocation(d.mWebKitXOffset, d.mWebKitYOffset);
- event.scale(d.mWebKitScale);
- d.mFlags |= FLAG_WEBKIT_TRANSFORMED_EVENT;
- }
-
- eventType = d.mEventType;
- if (eventType == EVENT_TYPE_TOUCH) {
- event = mWebKitTouchStream.update(event);
- if (DEBUG && event == null && d.mEvent != null) {
- Log.d(TAG, "dispatchWebKitEvents: dropped event " + d.mEvent);
- }
- }
-
- d.mFlags |= FLAG_WEBKIT_IN_PROGRESS;
- flags = d.mFlags;
- }
-
- // Handle the event.
- final boolean preventDefault;
- if (event == null) {
- preventDefault = false;
- } else {
- preventDefault = dispatchWebKitEvent(event, eventType, flags);
- }
-
- synchronized (mLock) {
- flags = d.mFlags;
- d.mFlags = flags & ~FLAG_WEBKIT_IN_PROGRESS;
- boolean recycleEvent = event != d.mEvent;
-
- if ((flags & FLAG_WEBKIT_TIMEOUT) != 0) {
- // A timeout occurred!
- recycleDispatchEventLocked(d);
- } else {
- // Web kit finished in a timely manner. Dequeue the event.
- assert mWebKitDispatchEventQueue.mHead == d;
- mWebKitDispatchEventQueue.dequeue();
-
- updateWebKitTimeoutLocked();
-
- if ((flags & FLAG_PRIVATE) != 0) {
- // Event was intended for web kit only. All done.
- recycleDispatchEventLocked(d);
- } else if (preventDefault) {
- // Web kit has decided to consume the event!
- if (d.mEventType == EVENT_TYPE_TOUCH) {
- enqueueUiCancelTouchEventIfNeededLocked();
- unscheduleLongPressLocked();
- }
- } else {
- // Web kit is being friendly. Pass the event to the UI.
- enqueueUiEventUnbatchedLocked(d);
- }
- }
-
- if (event != null && recycleEvent) {
- event.recycle();
- }
-
- if (eventType == EVENT_TYPE_CLICK) {
- scheduleHideTapHighlightLocked();
- }
- }
- }
- }
-
- // Runs on web kit thread.
- private boolean dispatchWebKitEvent(MotionEvent event, int eventType, int flags) {
- if (DEBUG) {
- Log.d(TAG, "dispatchWebKitEvent: event=" + event
- + ", eventType=" + eventType + ", flags=" + flags);
- }
- boolean preventDefault = mWebKitCallbacks.dispatchWebKitEvent(
- this, event, eventType, flags);
- if (DEBUG) {
- Log.d(TAG, "dispatchWebKitEvent: preventDefault=" + preventDefault);
- }
- return preventDefault;
- }
-
- private boolean isMoveEventLocked(DispatchEvent d) {
- return d.mEvent != null
- && d.mEvent.getActionMasked() == MotionEvent.ACTION_MOVE;
- }
-
- private void drainStaleWebKitEventsLocked() {
- DispatchEvent d = mWebKitDispatchEventQueue.mHead;
- while (d != null && d.mNext != null
- && isMoveEventLocked(d)
- && isMoveEventLocked(d.mNext)) {
- DispatchEvent next = d.mNext;
- skipWebKitEventLocked(d);
- d = next;
- }
- mWebKitDispatchEventQueue.mHead = d;
- }
-
- // Called by WebKit when it doesn't care about the rest of the touch stream
- public void skipWebkitForRemainingTouchStream() {
- // Just treat this like a timeout
- handleWebKitTimeout();
- }
-
- // Runs on UI thread in response to the web kit thread appearing to be unresponsive.
- private void handleWebKitTimeout() {
- synchronized (mLock) {
- if (!mWebKitTimeoutScheduled) {
- return;
- }
- mWebKitTimeoutScheduled = false;
-
- if (DEBUG) {
- Log.d(TAG, "handleWebKitTimeout: timeout occurred!");
- }
-
- // Drain the web kit event queue.
- DispatchEvent d = mWebKitDispatchEventQueue.dequeueList();
-
- // If web kit was processing an event (must be at the head of the list because
- // it can only do one at a time), then clone it or ignore it.
- if ((d.mFlags & FLAG_WEBKIT_IN_PROGRESS) != 0) {
- d.mFlags |= FLAG_WEBKIT_TIMEOUT;
- if ((d.mFlags & FLAG_PRIVATE) != 0) {
- d = d.mNext; // the event is private to web kit, ignore it
- } else {
- d = copyDispatchEventLocked(d);
- d.mFlags &= ~FLAG_WEBKIT_IN_PROGRESS;
- }
- }
-
- // Enqueue all non-private events for handling by the UI thread.
- while (d != null) {
- DispatchEvent next = d.mNext;
- skipWebKitEventLocked(d);
- d = next;
- }
-
- // Tell web kit to cancel all pending touches.
- // This also prevents us from sending web kit any more touches until the
- // next gesture begins. (As required to ensure touch event stream consistency.)
- enqueueWebKitCancelTouchEventIfNeededLocked();
- }
- }
-
- private void skipWebKitEventLocked(DispatchEvent d) {
- d.mNext = null;
- if ((d.mFlags & FLAG_PRIVATE) != 0) {
- recycleDispatchEventLocked(d);
- } else {
- d.mFlags |= FLAG_WEBKIT_TIMEOUT;
- enqueueUiEventUnbatchedLocked(d);
- }
- }
-
- /**
- * Dispatches pending UI events.
- * Must only be called from the UI thread.
- *
- * This method may be used to flush the queue of pending input events
- * immediately. This method may help to reduce input dispatch latency
- * if called before certain expensive operations such as drawing.
- */
- public void dispatchUiEvents() {
- dispatchUiEvents(false);
- }
-
- private void dispatchUiEvents(boolean calledFromHandler) {
- for (;;) {
- MotionEvent event;
- final int eventType;
- final int flags;
- synchronized (mLock) {
- DispatchEvent d = mUiDispatchEventQueue.dequeue();
- if (d == null) {
- if (mUiDispatchScheduled) {
- mUiDispatchScheduled = false;
- if (!calledFromHandler) {
- mUiHandler.removeMessages(UiHandler.MSG_DISPATCH_UI_EVENTS);
- }
- }
- return;
- }
-
- event = d.mEvent;
- if (event != null && (d.mFlags & FLAG_WEBKIT_TRANSFORMED_EVENT) != 0) {
- event.scale(1.0f / d.mWebKitScale);
- event.offsetLocation(-d.mWebKitXOffset, -d.mWebKitYOffset);
- d.mFlags &= ~FLAG_WEBKIT_TRANSFORMED_EVENT;
- }
-
- eventType = d.mEventType;
- if (eventType == EVENT_TYPE_TOUCH) {
- event = mUiTouchStream.update(event);
- if (DEBUG && event == null && d.mEvent != null) {
- Log.d(TAG, "dispatchUiEvents: dropped event " + d.mEvent);
- }
- }
-
- flags = d.mFlags;
-
- if (event == d.mEvent) {
- d.mEvent = null; // retain ownership of event, don't recycle it yet
- }
- recycleDispatchEventLocked(d);
-
- if (eventType == EVENT_TYPE_CLICK) {
- scheduleHideTapHighlightLocked();
- }
- }
-
- // Handle the event.
- if (event != null) {
- dispatchUiEvent(event, eventType, flags);
- event.recycle();
- }
- }
- }
-
- // Runs on UI thread.
- private void dispatchUiEvent(MotionEvent event, int eventType, int flags) {
- if (DEBUG) {
- Log.d(TAG, "dispatchUiEvent: event=" + event
- + ", eventType=" + eventType + ", flags=" + flags);
- }
- mUiCallbacks.dispatchUiEvent(event, eventType, flags);
- }
-
- private void enqueueEventLocked(DispatchEvent d) {
- if (!shouldSkipWebKit(d)) {
- enqueueWebKitEventLocked(d);
- } else {
- enqueueUiEventLocked(d);
- }
- }
-
- private boolean shouldSkipWebKit(DispatchEvent d) {
- switch (d.mEventType) {
- case EVENT_TYPE_CLICK:
- case EVENT_TYPE_HOVER:
- case EVENT_TYPE_SCROLL:
- case EVENT_TYPE_HIT_TEST:
- return false;
- case EVENT_TYPE_TOUCH:
- // TODO: This should be cleaned up. We now have WebViewInputDispatcher
- // and WebViewClassic both checking for slop and doing their own
- // thing - they should be consolidated. And by consolidated, I mean
- // WebViewClassic's version should just be deleted.
- // The reason this is done is because webpages seem to expect
- // that they only get an ontouchmove if the slop has been exceeded.
- if (mIsTapCandidate && d.mEvent != null
- && d.mEvent.getActionMasked() == MotionEvent.ACTION_MOVE) {
- return true;
- }
- return !mPostSendTouchEventsToWebKit
- || mPostDoNotSendTouchEventsToWebKitUntilNextGesture;
- }
- return true;
- }
-
- private void enqueueWebKitCancelTouchEventIfNeededLocked() {
- // We want to cancel touch events that were delivered to web kit.
- // Enqueue a null event at the end of the queue if needed.
- if (mWebKitTouchStream.isCancelNeeded() || !mWebKitDispatchEventQueue.isEmpty()) {
- DispatchEvent d = obtainDispatchEventLocked(null, EVENT_TYPE_TOUCH, FLAG_PRIVATE,
- 0, 0, 1.0f);
- enqueueWebKitEventUnbatchedLocked(d);
- mPostDoNotSendTouchEventsToWebKitUntilNextGesture = true;
- }
- }
-
- private void enqueueWebKitEventLocked(DispatchEvent d) {
- if (batchEventLocked(d, mWebKitDispatchEventQueue.mTail)) {
- if (DEBUG) {
- Log.d(TAG, "enqueueWebKitEventLocked: batched event " + d.mEvent);
- }
- recycleDispatchEventLocked(d);
- } else {
- enqueueWebKitEventUnbatchedLocked(d);
- }
- }
-
- private void enqueueWebKitEventUnbatchedLocked(DispatchEvent d) {
- if (DEBUG) {
- Log.d(TAG, "enqueueWebKitEventUnbatchedLocked: enqueued event " + d.mEvent);
- }
- mWebKitDispatchEventQueue.enqueue(d);
- scheduleWebKitDispatchLocked();
- updateWebKitTimeoutLocked();
- }
-
- private void scheduleWebKitDispatchLocked() {
- if (!mWebKitDispatchScheduled) {
- mWebKitHandler.sendEmptyMessage(WebKitHandler.MSG_DISPATCH_WEBKIT_EVENTS);
- mWebKitDispatchScheduled = true;
- }
- }
-
- private void updateWebKitTimeoutLocked() {
- DispatchEvent d = mWebKitDispatchEventQueue.mHead;
- if (d != null && mWebKitTimeoutScheduled && mWebKitTimeoutTime == d.mTimeoutTime) {
- return;
- }
- if (mWebKitTimeoutScheduled) {
- mUiHandler.removeMessages(UiHandler.MSG_WEBKIT_TIMEOUT);
- mWebKitTimeoutScheduled = false;
- }
- if (d != null) {
- mUiHandler.sendEmptyMessageAtTime(UiHandler.MSG_WEBKIT_TIMEOUT, d.mTimeoutTime);
- mWebKitTimeoutScheduled = true;
- mWebKitTimeoutTime = d.mTimeoutTime;
- }
- }
-
- private void enqueueUiCancelTouchEventIfNeededLocked() {
- // We want to cancel touch events that were delivered to the UI.
- // Enqueue a null event at the end of the queue if needed.
- if (mUiTouchStream.isCancelNeeded() || !mUiDispatchEventQueue.isEmpty()) {
- DispatchEvent d = obtainDispatchEventLocked(null, EVENT_TYPE_TOUCH, FLAG_PRIVATE,
- 0, 0, 1.0f);
- enqueueUiEventUnbatchedLocked(d);
- }
- }
-
- private void enqueueUiEventLocked(DispatchEvent d) {
- if (batchEventLocked(d, mUiDispatchEventQueue.mTail)) {
- if (DEBUG) {
- Log.d(TAG, "enqueueUiEventLocked: batched event " + d.mEvent);
- }
- recycleDispatchEventLocked(d);
- } else {
- enqueueUiEventUnbatchedLocked(d);
- }
- }
-
- private void enqueueUiEventUnbatchedLocked(DispatchEvent d) {
- if (DEBUG) {
- Log.d(TAG, "enqueueUiEventUnbatchedLocked: enqueued event " + d.mEvent);
- }
- mUiDispatchEventQueue.enqueue(d);
- scheduleUiDispatchLocked();
- }
-
- private void scheduleUiDispatchLocked() {
- if (!mUiDispatchScheduled) {
- mUiHandler.sendEmptyMessage(UiHandler.MSG_DISPATCH_UI_EVENTS);
- mUiDispatchScheduled = true;
- }
- }
-
- private boolean batchEventLocked(DispatchEvent in, DispatchEvent tail) {
- if (!ENABLE_EVENT_BATCHING) {
- return false;
- }
- if (tail != null && tail.mEvent != null && in.mEvent != null
- && in.mEventType == tail.mEventType
- && in.mFlags == tail.mFlags
- && in.mWebKitXOffset == tail.mWebKitXOffset
- && in.mWebKitYOffset == tail.mWebKitYOffset
- && in.mWebKitScale == tail.mWebKitScale) {
- return tail.mEvent.addBatch(in.mEvent);
- }
- return false;
- }
-
- private DispatchEvent obtainDispatchEventLocked(MotionEvent event,
- int eventType, int flags, int webKitXOffset, int webKitYOffset, float webKitScale) {
- DispatchEvent d = obtainUninitializedDispatchEventLocked();
- d.mEvent = event;
- d.mEventType = eventType;
- d.mFlags = flags;
- d.mTimeoutTime = SystemClock.uptimeMillis() + WEBKIT_TIMEOUT_MILLIS;
- d.mWebKitXOffset = webKitXOffset;
- d.mWebKitYOffset = webKitYOffset;
- d.mWebKitScale = webKitScale;
- if (DEBUG) {
- Log.d(TAG, "Timeout time: " + (d.mTimeoutTime - SystemClock.uptimeMillis()));
- }
- return d;
- }
-
- private DispatchEvent copyDispatchEventLocked(DispatchEvent d) {
- DispatchEvent copy = obtainUninitializedDispatchEventLocked();
- if (d.mEvent != null) {
- copy.mEvent = d.mEvent.copy();
- }
- copy.mEventType = d.mEventType;
- copy.mFlags = d.mFlags;
- copy.mTimeoutTime = d.mTimeoutTime;
- copy.mWebKitXOffset = d.mWebKitXOffset;
- copy.mWebKitYOffset = d.mWebKitYOffset;
- copy.mWebKitScale = d.mWebKitScale;
- copy.mNext = d.mNext;
- return copy;
- }
-
- private DispatchEvent obtainUninitializedDispatchEventLocked() {
- DispatchEvent d = mDispatchEventPool;
- if (d != null) {
- mDispatchEventPoolSize -= 1;
- mDispatchEventPool = d.mNext;
- d.mNext = null;
- } else {
- d = new DispatchEvent();
- }
- return d;
- }
-
- private void recycleDispatchEventLocked(DispatchEvent d) {
- if (d.mEvent != null) {
- d.mEvent.recycle();
- d.mEvent = null;
- }
-
- if (mDispatchEventPoolSize < MAX_DISPATCH_EVENT_POOL_SIZE) {
- mDispatchEventPoolSize += 1;
- d.mNext = mDispatchEventPool;
- mDispatchEventPool = d;
- }
- }
-
- /* Implemented by {@link WebViewClassic} to perform operations on the UI thread. */
- public static interface UiCallbacks {
- /**
- * Gets the UI thread's looper.
- * @return The looper.
- */
- public Looper getUiLooper();
-
- /**
- * Gets the UI's context
- * @return The context
- */
- public Context getContext();
-
- /**
- * Dispatches an event to the UI.
- * @param event The event.
- * @param eventType The event type.
- * @param flags The event's dispatch flags.
- */
- public void dispatchUiEvent(MotionEvent event, int eventType, int flags);
-
- /**
- * Asks the UI thread whether this touch event stream should be
- * intercepted based on the touch down event.
- * @param event The touch down event.
- * @return true if the UI stream wants the touch stream without going
- * through webkit or false otherwise.
- */
- public boolean shouldInterceptTouchEvent(MotionEvent event);
-
- /**
- * Inform's the UI that it should show the tap highlight
- * @param show True if it should show the highlight, false if it should hide it
- */
- public void showTapHighlight(boolean show);
-
- /**
- * Called when we are sending a new EVENT_TYPE_HIT_TEST to WebKit, so
- * previous hit tests should be cleared as they are obsolete.
- */
- public void clearPreviousHitTest();
- }
-
- /* Implemented by {@link WebViewCore} to perform operations on the web kit thread. */
- public static interface WebKitCallbacks {
- /**
- * Gets the web kit thread's looper.
- * @return The looper.
- */
- public Looper getWebKitLooper();
-
- /**
- * Dispatches an event to web kit.
- * @param dispatcher The WebViewInputDispatcher sending the event
- * @param event The event.
- * @param eventType The event type.
- * @param flags The event's dispatch flags.
- * @return True if web kit wants to prevent default event handling.
- */
- public boolean dispatchWebKitEvent(WebViewInputDispatcher dispatcher,
- MotionEvent event, int eventType, int flags);
- }
-
- // Runs on UI thread.
- private final class UiHandler extends Handler {
- public static final int MSG_DISPATCH_UI_EVENTS = 1;
- public static final int MSG_WEBKIT_TIMEOUT = 2;
- public static final int MSG_LONG_PRESS = 3;
- public static final int MSG_CLICK = 4;
- public static final int MSG_SHOW_TAP_HIGHLIGHT = 5;
- public static final int MSG_HIDE_TAP_HIGHLIGHT = 6;
-
- public UiHandler(Looper looper) {
- super(looper);
- }
-
- @Override
- public void handleMessage(Message msg) {
- switch (msg.what) {
- case MSG_DISPATCH_UI_EVENTS:
- dispatchUiEvents(true);
- break;
- case MSG_WEBKIT_TIMEOUT:
- handleWebKitTimeout();
- break;
- case MSG_LONG_PRESS:
- postLongPress();
- break;
- case MSG_CLICK:
- postClick();
- break;
- case MSG_SHOW_TAP_HIGHLIGHT:
- postShowTapHighlight(true);
- break;
- case MSG_HIDE_TAP_HIGHLIGHT:
- postShowTapHighlight(false);
- break;
- default:
- throw new IllegalStateException("Unknown message type: " + msg.what);
- }
- }
- }
-
- // Runs on web kit thread.
- private final class WebKitHandler extends Handler {
- public static final int MSG_DISPATCH_WEBKIT_EVENTS = 1;
-
- public WebKitHandler(Looper looper) {
- super(looper);
- }
-
- @Override
- public void handleMessage(Message msg) {
- switch (msg.what) {
- case MSG_DISPATCH_WEBKIT_EVENTS:
- dispatchWebKitEvents(true);
- break;
- default:
- throw new IllegalStateException("Unknown message type: " + msg.what);
- }
- }
- }
-
- private static final class DispatchEvent {
- public DispatchEvent mNext;
-
- public MotionEvent mEvent;
- public int mEventType;
- public int mFlags;
- public long mTimeoutTime;
- public int mWebKitXOffset;
- public int mWebKitYOffset;
- public float mWebKitScale;
- }
-
- private static final class DispatchEventQueue {
- public DispatchEvent mHead;
- public DispatchEvent mTail;
-
- public boolean isEmpty() {
- return mHead != null;
- }
-
- public void enqueue(DispatchEvent d) {
- if (mHead == null) {
- mHead = d;
- mTail = d;
- } else {
- mTail.mNext = d;
- mTail = d;
- }
- }
-
- public DispatchEvent dequeue() {
- DispatchEvent d = mHead;
- if (d != null) {
- DispatchEvent next = d.mNext;
- if (next == null) {
- mHead = null;
- mTail = null;
- } else {
- mHead = next;
- d.mNext = null;
- }
- }
- return d;
- }
-
- public DispatchEvent dequeueList() {
- DispatchEvent d = mHead;
- if (d != null) {
- mHead = null;
- mTail = null;
- }
- return d;
- }
- }
-
- /**
- * Keeps track of a stream of touch events so that we can discard touch
- * events that would make the stream inconsistent.
- */
- private static final class TouchStream {
- private MotionEvent mLastEvent;
-
- /**
- * Gets the last touch event that was delivered.
- * @return The last touch event, or null if none.
- */
- public MotionEvent getLastEvent() {
- return mLastEvent;
- }
-
- /**
- * Updates the touch event stream.
- * @param event The event that we intend to send, or null to cancel the
- * touch event stream.
- * @return The event that we should actually send, or null if no event should
- * be sent because the proposed event would make the stream inconsistent.
- */
- public MotionEvent update(MotionEvent event) {
- if (event == null) {
- if (isCancelNeeded()) {
- event = mLastEvent;
- if (event != null) {
- event.setAction(MotionEvent.ACTION_CANCEL);
- mLastEvent = null;
- }
- }
- return event;
- }
-
- switch (event.getActionMasked()) {
- case MotionEvent.ACTION_MOVE:
- case MotionEvent.ACTION_UP:
- case MotionEvent.ACTION_POINTER_DOWN:
- case MotionEvent.ACTION_POINTER_UP:
- if (mLastEvent == null
- || mLastEvent.getAction() == MotionEvent.ACTION_UP) {
- return null;
- }
- updateLastEvent(event);
- return event;
-
- case MotionEvent.ACTION_DOWN:
- updateLastEvent(event);
- return event;
-
- case MotionEvent.ACTION_CANCEL:
- if (mLastEvent == null) {
- return null;
- }
- updateLastEvent(null);
- return event;
-
- default:
- return null;
- }
- }
-
- /**
- * Returns true if there is a gesture in progress that may need to be canceled.
- * @return True if cancel is needed.
- */
- public boolean isCancelNeeded() {
- return mLastEvent != null && mLastEvent.getAction() != MotionEvent.ACTION_UP;
- }
-
- private void updateLastEvent(MotionEvent event) {
- if (mLastEvent != null) {
- mLastEvent.recycle();
- }
- mLastEvent = event != null ? MotionEvent.obtainNoHistory(event) : null;
- }
- }
-}
\ No newline at end of file
+++ /dev/null
-/*
- * Copyright (C) 2010 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;
-
-interface ZoomControlBase {
-
- /**
- * Causes the on-screen zoom control to be made visible
- */
- public void show();
-
- /**
- * Causes the on-screen zoom control to disappear
- */
- public void hide();
-
- /**
- * Enables the control to update its state if necessary in response to a
- * change in the pages zoom level. For example, if the max zoom level is
- * reached then the control can disable the button for zooming in.
- */
- public void update();
-
- /**
- * Checks to see if the control is currently visible to the user.
- */
- public boolean isVisible();
-}
+++ /dev/null
-/*
- * Copyright (C) 2010 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.view.Gravity;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.FrameLayout;
-import android.widget.Toast;
-import android.widget.ZoomButtonsController;
-
-class ZoomControlEmbedded implements ZoomControlBase {
-
- private final ZoomManager mZoomManager;
- private final WebViewClassic mWebView;
-
- // The controller is lazily initialized in getControls() for performance.
- private ZoomButtonsController mZoomButtonsController;
-
- public ZoomControlEmbedded(ZoomManager zoomManager, WebViewClassic webView) {
- mZoomManager = zoomManager;
- mWebView = webView;
- }
-
- public void show() {
- if (!getControls().isVisible() && !mZoomManager.isZoomScaleFixed()) {
-
- mZoomButtonsController.setVisible(true);
-
- if (mZoomManager.isDoubleTapEnabled()) {
- WebSettingsClassic settings = mWebView.getSettings();
- int count = settings.getDoubleTapToastCount();
- if (mZoomManager.isInZoomOverview() && count > 0) {
- settings.setDoubleTapToastCount(--count);
- Toast.makeText(mWebView.getContext(),
- com.android.internal.R.string.double_tap_toast,
- Toast.LENGTH_LONG).show();
- }
- }
- }
- }
-
- public void hide() {
- if (mZoomButtonsController != null) {
- mZoomButtonsController.setVisible(false);
- }
- }
-
- public boolean isVisible() {
- return mZoomButtonsController != null && mZoomButtonsController.isVisible();
- }
-
- public void update() {
- if (mZoomButtonsController == null) {
- return;
- }
-
- boolean canZoomIn = mZoomManager.canZoomIn();
- boolean canZoomOut = mZoomManager.canZoomOut() && !mZoomManager.isInZoomOverview();
- if (!canZoomIn && !canZoomOut) {
- // Hide the zoom in and out buttons if the page cannot zoom
- mZoomButtonsController.getZoomControls().setVisibility(View.GONE);
- } else {
- // Set each one individually, as a page may be able to zoom in or out
- mZoomButtonsController.setZoomInEnabled(canZoomIn);
- mZoomButtonsController.setZoomOutEnabled(canZoomOut);
- }
- }
-
- private ZoomButtonsController getControls() {
- if (mZoomButtonsController == null) {
- mZoomButtonsController = new ZoomButtonsController(mWebView.getWebView());
- mZoomButtonsController.setOnZoomListener(new ZoomListener());
- // ZoomButtonsController positions the buttons at the bottom, but in
- // the middle. Change their layout parameters so they appear on the
- // right.
- View controls = mZoomButtonsController.getZoomControls();
- ViewGroup.LayoutParams params = controls.getLayoutParams();
- if (params instanceof FrameLayout.LayoutParams) {
- ((FrameLayout.LayoutParams) params).gravity = Gravity.END;
- }
- }
- return mZoomButtonsController;
- }
-
- private class ZoomListener implements ZoomButtonsController.OnZoomListener {
-
- public void onVisibilityChanged(boolean visible) {
- if (visible) {
- mWebView.switchOutDrawHistory();
- // Bring back the hidden zoom controls.
- mZoomButtonsController.getZoomControls().setVisibility(View.VISIBLE);
- update();
- }
- }
-
- public void onZoom(boolean zoomIn) {
- if (zoomIn) {
- mWebView.zoomIn();
- } else {
- mWebView.zoomOut();
- }
- update();
- }
- }
-}
+++ /dev/null
-/*
- * Copyright (C) 2010 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.content.Context;
-import android.os.Handler;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewConfiguration;
-import android.view.View.OnClickListener;
-import android.view.animation.AlphaAnimation;
-import android.widget.FrameLayout;
-
-@Deprecated
-class ZoomControlExternal implements ZoomControlBase {
-
- // The time that the external controls are visible before fading away
- private static final long ZOOM_CONTROLS_TIMEOUT =
- ViewConfiguration.getZoomControlsTimeout();
- // The view containing the external zoom controls
- private ExtendedZoomControls mZoomControls;
- private Runnable mZoomControlRunnable;
- private final Handler mPrivateHandler = new Handler();
-
- private final WebViewClassic mWebView;
-
- public ZoomControlExternal(WebViewClassic webView) {
- mWebView = webView;
- }
-
- public void show() {
- if(mZoomControlRunnable != null) {
- mPrivateHandler.removeCallbacks(mZoomControlRunnable);
- }
- getControls().show(true);
- mPrivateHandler.postDelayed(mZoomControlRunnable, ZOOM_CONTROLS_TIMEOUT);
- }
-
- public void hide() {
- if (mZoomControlRunnable != null) {
- mPrivateHandler.removeCallbacks(mZoomControlRunnable);
- }
- if (mZoomControls != null) {
- mZoomControls.hide();
- }
- }
-
- public boolean isVisible() {
- return mZoomControls != null && mZoomControls.isShown();
- }
-
- public void update() { }
-
- public ExtendedZoomControls getControls() {
- if (mZoomControls == null) {
- mZoomControls = createZoomControls();
-
- /*
- * need to be set to VISIBLE first so that getMeasuredHeight() in
- * {@link #onSizeChanged()} can return the measured value for proper
- * layout.
- */
- mZoomControls.setVisibility(View.VISIBLE);
- mZoomControlRunnable = new Runnable() {
- public void run() {
- /* Don't dismiss the controls if the user has
- * focus on them. Wait and check again later.
- */
- if (!mZoomControls.hasFocus()) {
- mZoomControls.hide();
- } else {
- mPrivateHandler.removeCallbacks(mZoomControlRunnable);
- mPrivateHandler.postDelayed(mZoomControlRunnable,
- ZOOM_CONTROLS_TIMEOUT);
- }
- }
- };
- }
- return mZoomControls;
- }
-
- private ExtendedZoomControls createZoomControls() {
- ExtendedZoomControls zoomControls = new ExtendedZoomControls(mWebView.getContext());
- zoomControls.setOnZoomInClickListener(new OnClickListener() {
- public void onClick(View v) {
- // reset time out
- mPrivateHandler.removeCallbacks(mZoomControlRunnable);
- mPrivateHandler.postDelayed(mZoomControlRunnable, ZOOM_CONTROLS_TIMEOUT);
- mWebView.zoomIn();
- }
- });
- zoomControls.setOnZoomOutClickListener(new OnClickListener() {
- public void onClick(View v) {
- // reset time out
- mPrivateHandler.removeCallbacks(mZoomControlRunnable);
- mPrivateHandler.postDelayed(mZoomControlRunnable, ZOOM_CONTROLS_TIMEOUT);
- mWebView.zoomOut();
- }
- });
- return zoomControls;
- }
-
- private static class ExtendedZoomControls extends FrameLayout {
-
- private android.widget.ZoomControls mPlusMinusZoomControls;
-
- public ExtendedZoomControls(Context context) {
- super(context, null);
- LayoutInflater inflater = (LayoutInflater)
- context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
- inflater.inflate(com.android.internal.R.layout.zoom_magnify, this, true);
- mPlusMinusZoomControls = (android.widget.ZoomControls) findViewById(
- com.android.internal.R.id.zoomControls);
- findViewById(com.android.internal.R.id.zoomMagnify).setVisibility(
- View.GONE);
- }
-
- public void show(boolean showZoom) {
- mPlusMinusZoomControls.setVisibility(showZoom ? View.VISIBLE : View.GONE);
- fade(View.VISIBLE, 0.0f, 1.0f);
- }
-
- public void hide() {
- fade(View.GONE, 1.0f, 0.0f);
- }
-
- private void fade(int visibility, float startAlpha, float endAlpha) {
- AlphaAnimation anim = new AlphaAnimation(startAlpha, endAlpha);
- anim.setDuration(500);
- startAnimation(anim);
- setVisibility(visibility);
- }
-
- public boolean hasFocus() {
- return mPlusMinusZoomControls.hasFocus();
- }
-
- public void setOnZoomInClickListener(OnClickListener listener) {
- mPlusMinusZoomControls.setOnZoomInClickListener(listener);
- }
-
- public void setOnZoomOutClickListener(OnClickListener listener) {
- mPlusMinusZoomControls.setOnZoomOutClickListener(listener);
- }
- }
-}
+++ /dev/null
-/*
- * Copyright (C) 2010 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.content.Context;
-import android.content.pm.PackageManager;
-import android.graphics.Canvas;
-import android.graphics.Point;
-import android.graphics.Rect;
-import android.os.Bundle;
-import android.os.SystemClock;
-import android.util.FloatMath;
-import android.util.Log;
-import android.view.ScaleGestureDetector;
-import android.view.View;
-
-/**
- * The ZoomManager is responsible for maintaining the WebView's current zoom
- * level state. It is also responsible for managing the on-screen zoom controls
- * as well as any animation of the WebView due to zooming.
- *
- * Currently, there are two methods for animating the zoom of a WebView.
- *
- * (1) The first method is triggered by startZoomAnimation(...) and is a fixed
- * length animation where the final zoom scale is known at startup. This type of
- * animation notifies webkit of the final scale BEFORE it animates. The animation
- * is then done by scaling the CANVAS incrementally based on a stepping function.
- *
- * (2) The second method is triggered by a multi-touch pinch and the new scale
- * is determined dynamically based on the user's gesture. This type of animation
- * only notifies webkit of new scale AFTER the gesture is complete. The animation
- * effect is achieved by scaling the VIEWS (both WebView and ViewManager.ChildView)
- * to the new scale in response to events related to the user's gesture.
- */
-class ZoomManager {
-
- static final String LOGTAG = "webviewZoom";
-
- private final WebViewClassic mWebView;
- private final CallbackProxy mCallbackProxy;
-
- // Widgets responsible for the on-screen zoom functions of the WebView.
- private ZoomControlEmbedded mEmbeddedZoomControl;
- private ZoomControlExternal mExternalZoomControl;
-
- /*
- * The scale factors that determine the upper and lower bounds for the
- * default zoom scale.
- */
- protected static final float DEFAULT_MAX_ZOOM_SCALE_FACTOR = 4.00f;
- protected static final float DEFAULT_MIN_ZOOM_SCALE_FACTOR = 0.25f;
-
- // The default scale limits, which are dependent on the display density.
- private float mDefaultMaxZoomScale;
- private float mDefaultMinZoomScale;
-
- // The actual scale limits, which can be set through a webpage's viewport
- // meta-tag.
- private float mMaxZoomScale;
- private float mMinZoomScale;
-
- // Locks the minimum ZoomScale to the value currently set in mMinZoomScale.
- private boolean mMinZoomScaleFixed = true;
-
- /*
- * When loading a new page the WebView does not initially know the final
- * width of the page. Therefore, when a new page is loaded in overview mode
- * the overview scale is initialized to a default value. This flag is then
- * set and used to notify the ZoomManager to take the width of the next
- * picture from webkit and use that width to enter into zoom overview mode.
- */
- private boolean mInitialZoomOverview = false;
-
- /*
- * When in the zoom overview mode, the page's width is fully fit to the
- * current window. Additionally while the page is in this state it is
- * active, in other words, you can click to follow the links. We cache a
- * boolean to enable us to quickly check whether or not we are in overview
- * mode, but this value should only be modified by changes to the zoom
- * scale.
- */
- private boolean mInZoomOverview = false;
- private int mZoomOverviewWidth;
- private float mInvZoomOverviewWidth;
-
- /*
- * These variables track the center point of the zoom and they are used to
- * determine the point around which we should zoom. They are stored in view
- * coordinates.
- */
- private float mZoomCenterX;
- private float mZoomCenterY;
-
- /*
- * Similar to mZoomCenterX(Y), these track the focus point of the scale
- * gesture. The difference is these get updated every time when onScale is
- * invoked no matter if a zooming really happens.
- */
- private float mFocusX;
- private float mFocusY;
-
- /*
- * mFocusMovementQueue keeps track of the previous focus point movement
- * has been through. Comparing to the difference of the gesture's previous
- * span and current span, it determines if the gesture is for panning or
- * zooming or both.
- */
- private FocusMovementQueue mFocusMovementQueue;
-
- /*
- * These values represent the point around which the screen should be
- * centered after zooming. In other words it is used to determine the center
- * point of the visible document after the page has finished zooming. This
- * is important because the zoom may have potentially reflowed the text and
- * we need to ensure the proper portion of the document remains on the
- * screen.
- */
- private int mAnchorX;
- private int mAnchorY;
-
- // The scale factor that is used to determine the column width for text
- private float mTextWrapScale;
-
- /*
- * The default zoom scale is the scale factor used when the user triggers a
- * zoom in by double tapping on the WebView. The value is initially set
- * based on the display density, but can be changed at any time via the
- * WebSettings.
- */
- private float mDefaultScale;
- private float mInvDefaultScale;
-
- /*
- * The logical density of the display. This is a scaling factor for the
- * Density Independent Pixel unit, where one DIP is one pixel on an
- * approximately 160 dpi screen (see android.util.DisplayMetrics.density)
- */
- private float mDisplayDensity;
-
- /*
- * The factor that is used to tweak the zoom scale on a double-tap,
- * and can be changed via WebSettings. Range is from 0.75f to 1.25f.
- */
- private float mDoubleTapZoomFactor = 1.0f;
-
- /*
- * The scale factor that is used as the minimum increment when going from
- * overview to reading level on a double tap.
- */
- private static float MIN_DOUBLE_TAP_SCALE_INCREMENT = 0.5f;
-
- // the current computed zoom scale and its inverse.
- private float mActualScale;
- private float mInvActualScale;
-
- /*
- * The initial scale for the WebView. 0 means default. If initial scale is
- * greater than 0, the WebView starts with this value as its initial scale.
- */
- private float mInitialScale;
-
- private static float MINIMUM_SCALE_INCREMENT = 0.007f;
-
- /*
- * The touch points could be changed even the fingers stop moving.
- * We use the following to filter out the zooming jitters.
- */
- private static float MINIMUM_SCALE_WITHOUT_JITTER = 0.007f;
-
- /*
- * The following member variables are only to be used for animating zoom. If
- * mZoomScale is non-zero then we are in the middle of a zoom animation. The
- * other variables are used as a cache (e.g. inverse) or as a way to store
- * the state of the view prior to animating (e.g. initial scroll coords).
- */
- private float mZoomScale;
- private float mInvInitialZoomScale;
- private float mInvFinalZoomScale;
- private int mInitialScrollX;
- private int mInitialScrollY;
- private long mZoomStart;
-
- private static final int ZOOM_ANIMATION_LENGTH = 175;
-
- // whether support multi-touch
- private boolean mSupportMultiTouch;
-
- /**
- * True if we have a touch panel capable of detecting smooth pan/scale at the same time
- */
- private boolean mAllowPanAndScale;
-
- // use the framework's ScaleGestureDetector to handle scaling gestures
- private ScaleGestureDetector mScaleDetector;
- private boolean mPinchToZoomAnimating = false;
-
- private boolean mHardwareAccelerated = false;
- private boolean mInHWAcceleratedZoom = false;
-
- public ZoomManager(WebViewClassic webView, CallbackProxy callbackProxy) {
- mWebView = webView;
- mCallbackProxy = callbackProxy;
-
- /*
- * Ideally mZoomOverviewWidth should be mContentWidth. But sites like
- * ESPN and Engadget always have wider mContentWidth no matter what the
- * viewport size is.
- */
- setZoomOverviewWidth(WebViewClassic.DEFAULT_VIEWPORT_WIDTH);
-
- mFocusMovementQueue = new FocusMovementQueue();
- }
-
- /**
- * Initialize both the default and actual zoom scale to the given density.
- *
- * @param density The logical density of the display. This is a scaling factor
- * for the Density Independent Pixel unit, where one DIP is one pixel on an
- * approximately 160 dpi screen (see android.util.DisplayMetrics.density).
- */
- public void init(float density) {
- assert density > 0;
-
- mDisplayDensity = density;
- setDefaultZoomScale(density);
- mActualScale = density;
- mInvActualScale = 1 / density;
- mTextWrapScale = getReadingLevelScale();
- }
-
- /**
- * Update the default zoom scale using the given density. It will also reset
- * the current min and max zoom scales to the default boundaries as well as
- * ensure that the actual scale falls within those boundaries.
- *
- * @param density The logical density of the display. This is a scaling factor
- * for the Density Independent Pixel unit, where one DIP is one pixel on an
- * approximately 160 dpi screen (see android.util.DisplayMetrics.density).
- */
- public void updateDefaultZoomDensity(float density) {
- assert density > 0;
-
- if (Math.abs(density - mDefaultScale) > MINIMUM_SCALE_INCREMENT) {
- // Remember the current zoom density before it gets changed.
- final float originalDefault = mDefaultScale;
- // set the new default density
- mDisplayDensity = density;
- setDefaultZoomScale(density);
- float scaleChange = (originalDefault > 0.0) ? density / originalDefault: 1.0f;
- // adjust the scale if it falls outside the new zoom bounds
- setZoomScale(mActualScale * scaleChange, true);
- }
- }
-
- private void setDefaultZoomScale(float defaultScale) {
- final float originalDefault = mDefaultScale;
- mDefaultScale = defaultScale;
- mInvDefaultScale = 1 / defaultScale;
- mDefaultMaxZoomScale = defaultScale * DEFAULT_MAX_ZOOM_SCALE_FACTOR;
- mDefaultMinZoomScale = defaultScale * DEFAULT_MIN_ZOOM_SCALE_FACTOR;
- if (originalDefault > 0.0 && mMaxZoomScale > 0.0) {
- // Keeps max zoom scale when zoom density changes.
- mMaxZoomScale = defaultScale / originalDefault * mMaxZoomScale;
- } else {
- mMaxZoomScale = mDefaultMaxZoomScale;
- }
- if (originalDefault > 0.0 && mMinZoomScale > 0.0) {
- // Keeps min zoom scale when zoom density changes.
- mMinZoomScale = defaultScale / originalDefault * mMinZoomScale;
- } else {
- mMinZoomScale = mDefaultMinZoomScale;
- }
- if (!exceedsMinScaleIncrement(mMinZoomScale, mMaxZoomScale)) {
- mMaxZoomScale = mMinZoomScale;
- }
- sanitizeMinMaxScales();
- }
-
- public final float getScale() {
- return mActualScale;
- }
-
- public final float getInvScale() {
- return mInvActualScale;
- }
-
- public final float getTextWrapScale() {
- return mTextWrapScale;
- }
-
- public final float getMaxZoomScale() {
- return mMaxZoomScale;
- }
-
- public final float getMinZoomScale() {
- return mMinZoomScale;
- }
-
- public final float getDefaultScale() {
- return mDefaultScale;
- }
-
- /**
- * Returns the zoom scale used for reading text on a double-tap.
- */
- public final float getReadingLevelScale() {
- return computeScaleWithLimits(computeReadingLevelScale(getZoomOverviewScale()));
- }
-
- /* package */ final float computeReadingLevelScale(float scale) {
- return Math.max(mDisplayDensity * mDoubleTapZoomFactor,
- scale + MIN_DOUBLE_TAP_SCALE_INCREMENT);
- }
-
- public final float getInvDefaultScale() {
- return mInvDefaultScale;
- }
-
- public final float getDefaultMaxZoomScale() {
- return mDefaultMaxZoomScale;
- }
-
- public final float getDefaultMinZoomScale() {
- return mDefaultMinZoomScale;
- }
-
- public final int getDocumentAnchorX() {
- return mAnchorX;
- }
-
- public final int getDocumentAnchorY() {
- return mAnchorY;
- }
-
- public final void clearDocumentAnchor() {
- mAnchorX = mAnchorY = 0;
- }
-
- public final void setZoomCenter(float x, float y) {
- mZoomCenterX = x;
- mZoomCenterY = y;
- }
-
- public final void setInitialScaleInPercent(int scaleInPercent) {
- mInitialScale = scaleInPercent * 0.01f;
- }
-
- public final float computeScaleWithLimits(float scale) {
- if (scale < mMinZoomScale) {
- scale = mMinZoomScale;
- } else if (scale > mMaxZoomScale) {
- scale = mMaxZoomScale;
- }
- return scale;
- }
-
- public final boolean isScaleOverLimits(float scale) {
- return scale <= mMinZoomScale || scale >= mMaxZoomScale;
- }
-
- public final boolean isZoomScaleFixed() {
- return mMinZoomScale >= mMaxZoomScale;
- }
-
- public static final boolean exceedsMinScaleIncrement(float scaleA, float scaleB) {
- return Math.abs(scaleA - scaleB) >= MINIMUM_SCALE_INCREMENT;
- }
-
- public boolean willScaleTriggerZoom(float scale) {
- return exceedsMinScaleIncrement(scale, mActualScale);
- }
-
- public final boolean canZoomIn() {
- return mMaxZoomScale - mActualScale > MINIMUM_SCALE_INCREMENT;
- }
-
- public final boolean canZoomOut() {
- return mActualScale - mMinZoomScale > MINIMUM_SCALE_INCREMENT;
- }
-
- public boolean zoomIn() {
- return zoom(1.25f);
- }
-
- public boolean zoomOut() {
- return zoom(0.8f);
- }
-
- // returns TRUE if zoom out succeeds and FALSE if no zoom changes.
- private boolean zoom(float zoomMultiplier) {
- mInitialZoomOverview = false;
- // TODO: alternatively we can disallow this during draw history mode
- mWebView.switchOutDrawHistory();
- // Center zooming to the center of the screen.
- mZoomCenterX = mWebView.getViewWidth() * .5f;
- mZoomCenterY = mWebView.getViewHeight() * .5f;
- mAnchorX = mWebView.viewToContentX((int) mZoomCenterX + mWebView.getScrollX());
- mAnchorY = mWebView.viewToContentY((int) mZoomCenterY + mWebView.getScrollY());
- return startZoomAnimation(mActualScale * zoomMultiplier,
- !mWebView.getSettings().getUseFixedViewport());
- }
-
- /**
- * Initiates an animated zoom of the WebView.
- *
- * @return true if the new scale triggered an animation and false otherwise.
- */
- public boolean startZoomAnimation(float scale, boolean reflowText) {
- mInitialZoomOverview = false;
- float oldScale = mActualScale;
- mInitialScrollX = mWebView.getScrollX();
- mInitialScrollY = mWebView.getScrollY();
-
- // snap to reading level scale if it is close
- if (!exceedsMinScaleIncrement(scale, getReadingLevelScale())) {
- scale = getReadingLevelScale();
- }
-
- setZoomScale(scale, reflowText);
-
- if (oldScale != mActualScale) {
- if (mHardwareAccelerated) {
- mInHWAcceleratedZoom = true;
- }
- // use mZoomPickerScale to see zoom preview first
- mZoomStart = SystemClock.uptimeMillis();
- mInvInitialZoomScale = 1.0f / oldScale;
- mInvFinalZoomScale = 1.0f / mActualScale;
- mZoomScale = mActualScale;
- mWebView.onFixedLengthZoomAnimationStart();
- mWebView.invalidate();
- return true;
- } else {
- return false;
- }
- }
-
- /**
- * This method is called by the WebView's drawing code when a fixed length zoom
- * animation is occurring. Its purpose is to animate the zooming of the canvas
- * to the desired scale which was specified in startZoomAnimation(...).
- *
- * A fixed length animation begins when startZoomAnimation(...) is called and
- * continues until the ZOOM_ANIMATION_LENGTH time has elapsed. During that
- * interval each time the WebView draws it calls this function which is
- * responsible for generating the animation.
- *
- * Additionally, the WebView can check to see if such an animation is currently
- * in progress by calling isFixedLengthAnimationInProgress().
- */
- public void animateZoom(Canvas canvas) {
- mInitialZoomOverview = false;
- if (mZoomScale == 0) {
- Log.w(LOGTAG, "A WebView is attempting to perform a fixed length "
- + "zoom animation when no zoom is in progress");
- // Now that we've logged about it, go ahead and just recover
- mInHWAcceleratedZoom = false;
- return;
- }
-
- float zoomScale;
- int interval = (int) (SystemClock.uptimeMillis() - mZoomStart);
- if (interval < ZOOM_ANIMATION_LENGTH) {
- float ratio = (float) interval / ZOOM_ANIMATION_LENGTH;
- zoomScale = 1.0f / (mInvInitialZoomScale
- + (mInvFinalZoomScale - mInvInitialZoomScale) * ratio);
- mWebView.invalidate();
- } else {
- zoomScale = mZoomScale;
- // set mZoomScale to be 0 as we have finished animating
- mZoomScale = 0;
- mWebView.onFixedLengthZoomAnimationEnd();
- }
- // calculate the intermediate scroll position. Since we need to use
- // zoomScale, we can't use the WebView's pinLocX/Y functions directly.
- float scale = zoomScale * mInvInitialZoomScale;
- int tx = Math.round(scale * (mInitialScrollX + mZoomCenterX) - mZoomCenterX);
- tx = -WebViewClassic.pinLoc(tx, mWebView.getViewWidth(), Math.round(mWebView.getContentWidth()
- * zoomScale)) + mWebView.getScrollX();
- int titleHeight = mWebView.getTitleHeight();
- int ty = Math.round(scale
- * (mInitialScrollY + mZoomCenterY - titleHeight)
- - (mZoomCenterY - titleHeight));
- ty = -(ty <= titleHeight ? Math.max(ty, 0) : WebViewClassic.pinLoc(ty
- - titleHeight, mWebView.getViewHeight(), Math.round(mWebView.getContentHeight()
- * zoomScale)) + titleHeight) + mWebView.getScrollY();
-
- if (mHardwareAccelerated) {
- mWebView.updateScrollCoordinates(mWebView.getScrollX() - tx, mWebView.getScrollY() - ty);
- // By adding webView matrix, we need to offset the canvas a bit
- // to make the animation smooth.
- canvas.translate(tx, ty);
- setZoomScale(zoomScale, false);
-
- if (mZoomScale == 0) {
- // We've reached the end of the zoom animation.
- mInHWAcceleratedZoom = false;
-
- // Ensure that the zoom level is pushed to WebCore. This has not
- // yet occurred because we prevent it from happening while
- // mInHWAcceleratedZoom is true.
- mWebView.sendViewSizeZoom(false);
- }
- } else {
- canvas.translate(tx, ty);
- canvas.scale(zoomScale, zoomScale);
- }
- }
-
- public boolean isZoomAnimating() {
- return isFixedLengthAnimationInProgress() || mPinchToZoomAnimating;
- }
-
- public boolean isFixedLengthAnimationInProgress() {
- return mZoomScale != 0 || mInHWAcceleratedZoom;
- }
-
- public void updateDoubleTapZoom(int doubleTapZoom) {
- boolean zoomIn = (mTextWrapScale - mActualScale) < .1f;
- mDoubleTapZoomFactor = doubleTapZoom / 100.0f;
- mTextWrapScale = getReadingLevelScale();
- float newScale = zoomIn ? mTextWrapScale
- : Math.min(mTextWrapScale, mActualScale);
- setZoomScale(newScale, true, true);
- }
-
- public void refreshZoomScale(boolean reflowText) {
- setZoomScale(mActualScale, reflowText, true);
- }
-
- public void setZoomScale(float scale, boolean reflowText) {
- setZoomScale(scale, reflowText, false);
- }
-
- private void setZoomScale(float scale, boolean reflowText, boolean force) {
- final boolean isScaleLessThanMinZoom = scale < mMinZoomScale;
- scale = computeScaleWithLimits(scale);
-
- // determine whether or not we are in the zoom overview mode
- if (isScaleLessThanMinZoom && mMinZoomScale < mDefaultScale) {
- mInZoomOverview = true;
- } else {
- mInZoomOverview = !exceedsMinScaleIncrement(scale, getZoomOverviewScale());
- }
-
- if (reflowText && !mWebView.getSettings().getUseFixedViewport()) {
- mTextWrapScale = scale;
- }
-
- if (scale != mActualScale || force) {
- float oldScale = mActualScale;
- float oldInvScale = mInvActualScale;
-
- if (scale != mActualScale && !mPinchToZoomAnimating) {
- mCallbackProxy.onScaleChanged(mActualScale, scale);
- }
-
- mActualScale = scale;
- mInvActualScale = 1 / scale;
-
- if (!mWebView.drawHistory() && !mInHWAcceleratedZoom) {
-
- // If history Picture is drawn, don't update scroll. They will
- // be updated when we get out of that mode.
- // update our scroll so we don't appear to jump
- // i.e. keep the center of the doc in the center of the view
- // If this is part of a zoom on a HW accelerated canvas, we
- // have already updated the scroll so don't do it again.
- int oldX = mWebView.getScrollX();
- int oldY = mWebView.getScrollY();
- float ratio = scale * oldInvScale;
- float sx = ratio * oldX + (ratio - 1) * mZoomCenterX;
- float sy = ratio * oldY + (ratio - 1)
- * (mZoomCenterY - mWebView.getTitleHeight());
-
- // Scale all the child views
- mWebView.mViewManager.scaleAll();
-
- // as we don't have animation for scaling, don't do animation
- // for scrolling, as it causes weird intermediate state
- int scrollX = mWebView.pinLocX(Math.round(sx));
- int scrollY = mWebView.pinLocY(Math.round(sy));
- if(!mWebView.updateScrollCoordinates(scrollX, scrollY)) {
- // the scroll position is adjusted at the beginning of the
- // zoom animation. But we want to update the WebKit at the
- // end of the zoom animation. See comments in onScaleEnd().
- mWebView.sendOurVisibleRect();
- }
- }
-
- // if the we need to reflow the text then force the VIEW_SIZE_CHANGED
- // event to be sent to WebKit
- mWebView.sendViewSizeZoom(reflowText);
- }
- }
-
- public boolean isDoubleTapEnabled() {
- WebSettings settings = mWebView.getSettings();
- return settings != null && settings.getUseWideViewPort();
- }
-
- /**
- * The double tap gesture can result in different behaviors depending on the
- * content that is tapped.
- *
- * (1) PLUGINS: If the taps occur on a plugin then we maximize the plugin on
- * the screen. If the plugin is already maximized then zoom the user into
- * overview mode.
- *
- * (2) HTML/OTHER: If the taps occur outside a plugin then the following
- * heuristic is used.
- * A. If the current text wrap scale differs from newly calculated and the
- * layout algorithm specifies the use of NARROW_COLUMNS, then fit to
- * column by reflowing the text.
- * B. If the page is not in overview mode then change to overview mode.
- * C. If the page is in overmode then change to the default scale.
- */
- public void handleDoubleTap(float lastTouchX, float lastTouchY) {
- // User takes action, set initial zoom overview to false.
- mInitialZoomOverview = false;
- WebSettingsClassic settings = mWebView.getSettings();
- if (!isDoubleTapEnabled()) {
- return;
- }
-
- setZoomCenter(lastTouchX, lastTouchY);
- mAnchorX = mWebView.viewToContentX((int) lastTouchX + mWebView.getScrollX());
- mAnchorY = mWebView.viewToContentY((int) lastTouchY + mWebView.getScrollY());
- settings.setDoubleTapToastCount(0);
-
- // remove the zoom control after double tap
- dismissZoomPicker();
-
- final float newTextWrapScale;
- if (settings.getUseFixedViewport()) {
- newTextWrapScale = Math.max(mActualScale, getReadingLevelScale());
- } else {
- newTextWrapScale = mActualScale;
- }
- final boolean firstTimeReflow = !exceedsMinScaleIncrement(mActualScale, mTextWrapScale);
- if (firstTimeReflow || mInZoomOverview) {
- // In case first time reflow or in zoom overview mode, let reflow and zoom
- // happen at the same time.
- mTextWrapScale = newTextWrapScale;
- }
- if (settings.isNarrowColumnLayout()
- && exceedsMinScaleIncrement(mTextWrapScale, newTextWrapScale)
- && !firstTimeReflow
- && !mInZoomOverview) {
- // Reflow only.
- mTextWrapScale = newTextWrapScale;
- refreshZoomScale(true);
- } else if (!mInZoomOverview && willScaleTriggerZoom(getZoomOverviewScale())) {
- // Reflow, if necessary.
- if (mTextWrapScale > getReadingLevelScale()) {
- mTextWrapScale = getReadingLevelScale();
- refreshZoomScale(true);
- }
- zoomToOverview();
- } else {
- zoomToReadingLevel();
- }
- }
-
- private void setZoomOverviewWidth(int width) {
- if (width == 0) {
- mZoomOverviewWidth = WebViewClassic.DEFAULT_VIEWPORT_WIDTH;
- } else {
- mZoomOverviewWidth = width;
- }
- mInvZoomOverviewWidth = 1.0f / width;
- }
-
- /* package */ float getZoomOverviewScale() {
- return mWebView.getViewWidth() * mInvZoomOverviewWidth;
- }
-
- public boolean isInZoomOverview() {
- return mInZoomOverview;
- }
-
- private void zoomToOverview() {
- // Force the titlebar fully reveal in overview mode
- int scrollY = mWebView.getScrollY();
- if (scrollY < mWebView.getTitleHeight()) {
- mWebView.updateScrollCoordinates(mWebView.getScrollX(), 0);
- }
- startZoomAnimation(getZoomOverviewScale(),
- !mWebView.getSettings().getUseFixedViewport());
- }
-
- private void zoomToReadingLevel() {
- final float readingScale = getReadingLevelScale();
-
- int left = mWebView.getBlockLeftEdge(mAnchorX, mAnchorY, readingScale);
- if (left != WebViewClassic.NO_LEFTEDGE) {
- // add a 5pt padding to the left edge.
- int viewLeft = mWebView.contentToViewX(left < 5 ? 0 : (left - 5))
- - mWebView.getScrollX();
- // Re-calculate the zoom center so that the new scroll x will be
- // on the left edge.
- if (viewLeft > 0) {
- mZoomCenterX = viewLeft * readingScale / (readingScale - mActualScale);
- } else {
- mWebView.getWebView().scrollBy(viewLeft, 0);
- mZoomCenterX = 0;
- }
- }
- startZoomAnimation(readingScale,
- !mWebView.getSettings().getUseFixedViewport());
- }
-
- public void updateMultiTouchSupport(Context context) {
- // check the preconditions
- assert mWebView.getSettings() != null;
-
- final WebSettings settings = mWebView.getSettings();
- final PackageManager pm = context.getPackageManager();
- mSupportMultiTouch =
- (pm.hasSystemFeature(PackageManager.FEATURE_TOUCHSCREEN_MULTITOUCH)
- || pm.hasSystemFeature(PackageManager.FEATURE_FAKETOUCH_MULTITOUCH_DISTINCT))
- && settings.supportZoom() && settings.getBuiltInZoomControls();
- mAllowPanAndScale =
- pm.hasSystemFeature(PackageManager.FEATURE_TOUCHSCREEN_MULTITOUCH_DISTINCT)
- || pm.hasSystemFeature(PackageManager.FEATURE_FAKETOUCH_MULTITOUCH_DISTINCT);
-
- if (mSupportMultiTouch && (mScaleDetector == null)) {
- mScaleDetector = new ScaleGestureDetector(context, new ScaleDetectorListener());
- } else if (!mSupportMultiTouch && (mScaleDetector != null)) {
- mScaleDetector = null;
- }
- }
-
- public boolean supportsMultiTouchZoom() {
- return mSupportMultiTouch;
- }
-
- public boolean supportsPanDuringZoom() {
- return mAllowPanAndScale;
- }
-
- /**
- * Notifies the caller that the ZoomManager is requesting that scale related
- * updates should not be sent to webkit. This can occur in cases where the
- * ZoomManager is performing an animation and does not want webkit to update
- * until the animation is complete.
- *
- * @return true if scale related updates should not be sent to webkit and
- * false otherwise.
- */
- public boolean isPreventingWebkitUpdates() {
- // currently only animating a multi-touch zoom and fixed length
- // animations prevent updates, but others can add their own conditions
- // to this method if necessary.
- return isZoomAnimating();
- }
-
- public ScaleGestureDetector getScaleGestureDetector() {
- return mScaleDetector;
- }
-
- private class FocusMovementQueue {
- private static final int QUEUE_CAPACITY = 5;
- private float[] mQueue;
- private float mSum;
- private int mSize;
- private int mIndex;
-
- FocusMovementQueue() {
- mQueue = new float[QUEUE_CAPACITY];
- mSize = 0;
- mSum = 0;
- mIndex = 0;
- }
-
- private void clear() {
- mSize = 0;
- mSum = 0;
- mIndex = 0;
- for (int i = 0; i < QUEUE_CAPACITY; ++i) {
- mQueue[i] = 0;
- }
- }
-
- private void add(float focusDelta) {
- mSum += focusDelta;
- if (mSize < QUEUE_CAPACITY) { // fill up the queue.
- mSize++;
- } else { // circulate the queue.
- mSum -= mQueue[mIndex];
- }
- mQueue[mIndex] = focusDelta;
- mIndex = (mIndex + 1) % QUEUE_CAPACITY;
- }
-
- private float getSum() {
- return mSum;
- }
- }
-
- private class ScaleDetectorListener implements ScaleGestureDetector.OnScaleGestureListener {
- private float mAccumulatedSpan;
-
- public boolean onScaleBegin(ScaleGestureDetector detector) {
- mInitialZoomOverview = false;
- dismissZoomPicker();
- mFocusMovementQueue.clear();
- mFocusX = detector.getFocusX();
- mFocusY = detector.getFocusY();
- mWebView.mViewManager.startZoom();
- mWebView.onPinchToZoomAnimationStart();
- mAccumulatedSpan = 0;
- return true;
- }
-
- // If the user moves the fingers but keeps the same distance between them,
- // we should do panning only.
- public boolean isPanningOnly(ScaleGestureDetector detector) {
- float prevFocusX = mFocusX;
- float prevFocusY = mFocusY;
- mFocusX = detector.getFocusX();
- mFocusY = detector.getFocusY();
- float focusDelta = (prevFocusX == 0 && prevFocusY == 0) ? 0 :
- FloatMath.sqrt((mFocusX - prevFocusX) * (mFocusX - prevFocusX)
- + (mFocusY - prevFocusY) * (mFocusY - prevFocusY));
- mFocusMovementQueue.add(focusDelta);
- float deltaSpan = detector.getCurrentSpan() - detector.getPreviousSpan() +
- mAccumulatedSpan;
- final boolean result = mFocusMovementQueue.getSum() > Math.abs(deltaSpan);
- if (result) {
- mAccumulatedSpan += deltaSpan;
- } else {
- mAccumulatedSpan = 0;
- }
- return result;
- }
-
- public boolean handleScale(ScaleGestureDetector detector) {
- float scale = detector.getScaleFactor() * mActualScale;
-
- // if scale is limited by any reason, don't zoom but do ask
- // the detector to update the event.
- boolean isScaleLimited =
- isScaleOverLimits(scale) || scale < getZoomOverviewScale();
-
- // Prevent scaling beyond overview scale.
- scale = Math.max(computeScaleWithLimits(scale), getZoomOverviewScale());
-
- if (mPinchToZoomAnimating || willScaleTriggerZoom(scale)) {
- mPinchToZoomAnimating = true;
- // limit the scale change per step
- if (scale > mActualScale) {
- scale = Math.min(scale, mActualScale * 1.25f);
- } else {
- scale = Math.max(scale, mActualScale * 0.8f);
- }
- scale = computeScaleWithLimits(scale);
- // if the scale change is too small, regard it as jitter and skip it.
- if (Math.abs(scale - mActualScale) < MINIMUM_SCALE_WITHOUT_JITTER) {
- return isScaleLimited;
- }
- setZoomCenter(detector.getFocusX(), detector.getFocusY());
- setZoomScale(scale, false);
- mWebView.invalidate();
- return true;
- }
- return isScaleLimited;
- }
-
- public boolean onScale(ScaleGestureDetector detector) {
- if (isPanningOnly(detector) || handleScale(detector)) {
- mFocusMovementQueue.clear();
- return true;
- }
- return false;
- }
-
- public void onScaleEnd(ScaleGestureDetector detector) {
- if (mPinchToZoomAnimating) {
- mPinchToZoomAnimating = false;
- mAnchorX = mWebView.viewToContentX((int) mZoomCenterX + mWebView.getScrollX());
- mAnchorY = mWebView.viewToContentY((int) mZoomCenterY + mWebView.getScrollY());
- // don't reflow when zoom in; when zoom out, do reflow if the
- // new scale is almost minimum scale.
- boolean reflowNow = !canZoomOut() || (mActualScale <= 0.8 * mTextWrapScale);
- // force zoom after mPreviewZoomOnly is set to false so that the
- // new view size will be passed to the WebKit
- refreshZoomScale(reflowNow &&
- !mWebView.getSettings().getUseFixedViewport());
- // call invalidate() to draw without zoom filter
- mWebView.invalidate();
- }
-
- mWebView.mViewManager.endZoom();
- mWebView.onPinchToZoomAnimationEnd(detector);
- }
- }
-
- private void sanitizeMinMaxScales() {
- if (mMinZoomScale > mMaxZoomScale) {
- Log.w(LOGTAG, "mMinZoom > mMaxZoom!!! " + mMinZoomScale + " > " + mMaxZoomScale,
- new Exception());
- mMaxZoomScale = mMinZoomScale;
- }
- }
-
- public void onSizeChanged(int w, int h, int ow, int oh) {
- // reset zoom and anchor to the top left corner of the screen
- // unless we are already zooming
- if (!isFixedLengthAnimationInProgress()) {
- int visibleTitleHeight = mWebView.getVisibleTitleHeight();
- mZoomCenterX = 0;
- mZoomCenterY = visibleTitleHeight;
- mAnchorX = mWebView.viewToContentX(mWebView.getScrollX());
- mAnchorY = mWebView.viewToContentY(visibleTitleHeight + mWebView.getScrollY());
- }
-
- // update mMinZoomScale if the minimum zoom scale is not fixed
- if (!mMinZoomScaleFixed) {
- // when change from narrow screen to wide screen, the new viewWidth
- // can be wider than the old content width. We limit the minimum
- // scale to 1.0f. The proper minimum scale will be calculated when
- // the new picture shows up.
- mMinZoomScale = Math.min(1.0f, (float) mWebView.getViewWidth()
- / (mWebView.drawHistory() ? mWebView.getHistoryPictureWidth()
- : mZoomOverviewWidth));
- // limit the minZoomScale to the initialScale if it is set
- if (mInitialScale > 0 && mInitialScale < mMinZoomScale) {
- mMinZoomScale = mInitialScale;
- }
- sanitizeMinMaxScales();
- }
-
- dismissZoomPicker();
-
- // onSizeChanged() is called during WebView layout. And any
- // requestLayout() is blocked during layout. As refreshZoomScale() will
- // cause its child View to reposition itself through ViewManager's
- // scaleAll(), we need to post a Runnable to ensure requestLayout().
- // Additionally, only update the text wrap scale if the width changed.
- mWebView.getWebView().post(new PostScale(w != ow &&
- !mWebView.getSettings().getUseFixedViewport(), mInZoomOverview, w < ow));
- }
-
- private class PostScale implements Runnable {
- final boolean mUpdateTextWrap;
- // Remember the zoom overview state right after rotation since
- // it could be changed between the time this callback is initiated and
- // the time it's actually run.
- final boolean mInZoomOverviewBeforeSizeChange;
- final boolean mInPortraitMode;
-
- public PostScale(boolean updateTextWrap,
- boolean inZoomOverview,
- boolean inPortraitMode) {
- mUpdateTextWrap = updateTextWrap;
- mInZoomOverviewBeforeSizeChange = inZoomOverview;
- mInPortraitMode = inPortraitMode;
- }
-
- public void run() {
- if (mWebView.getWebViewCore() != null) {
- // we always force, in case our height changed, in which case we
- // still want to send the notification over to webkit.
- // Keep overview mode unchanged when rotating.
- float newScale = mActualScale;
- if (mWebView.getSettings().getUseWideViewPort() &&
- mInPortraitMode &&
- mInZoomOverviewBeforeSizeChange) {
- newScale = getZoomOverviewScale();
- }
- setZoomScale(newScale, mUpdateTextWrap, true);
- // update the zoom buttons as the scale can be changed
- updateZoomPicker();
- }
- }
- }
-
- public void updateZoomRange(WebViewCore.ViewState viewState,
- int viewWidth, int minPrefWidth) {
- if (viewState.mMinScale == 0) {
- if (viewState.mMobileSite) {
- if (minPrefWidth > Math.max(0, viewWidth)) {
- mMinZoomScale = (float) viewWidth / minPrefWidth;
- mMinZoomScaleFixed = false;
- } else {
- mMinZoomScale = viewState.mDefaultScale;
- mMinZoomScaleFixed = true;
- }
- } else {
- mMinZoomScale = mDefaultMinZoomScale;
- mMinZoomScaleFixed = false;
- }
- } else {
- mMinZoomScale = viewState.mMinScale;
- mMinZoomScaleFixed = true;
- }
- if (viewState.mMaxScale == 0) {
- mMaxZoomScale = mDefaultMaxZoomScale;
- } else {
- mMaxZoomScale = viewState.mMaxScale;
- }
- sanitizeMinMaxScales();
- }
-
- /**
- * Updates zoom values when Webkit produces a new picture. This method
- * should only be called from the UI thread's message handler.
- *
- * @return True if zoom value has changed
- */
- public boolean onNewPicture(WebViewCore.DrawData drawData) {
- final int viewWidth = mWebView.getViewWidth();
- final boolean zoomOverviewWidthChanged = setupZoomOverviewWidth(drawData, viewWidth);
- final float newZoomOverviewScale = getZoomOverviewScale();
- WebSettingsClassic settings = mWebView.getSettings();
- if (zoomOverviewWidthChanged && settings.isNarrowColumnLayout() &&
- settings.getUseFixedViewport() &&
- (mInitialZoomOverview || mInZoomOverview)) {
- // Keep mobile site's text wrap scale unchanged. For mobile sites,
- // the text wrap scale is the same as zoom overview scale.
- if (exceedsMinScaleIncrement(mTextWrapScale, mDefaultScale) ||
- exceedsMinScaleIncrement(newZoomOverviewScale, mDefaultScale)) {
- mTextWrapScale = getReadingLevelScale();
- } else {
- mTextWrapScale = newZoomOverviewScale;
- }
- }
-
- if (!mMinZoomScaleFixed || settings.getUseWideViewPort()) {
- mMinZoomScale = newZoomOverviewScale;
- mMaxZoomScale = Math.max(mMaxZoomScale, mMinZoomScale);
- sanitizeMinMaxScales();
- }
- // fit the content width to the current view for the first new picture
- // after first layout.
- boolean scaleHasDiff = exceedsMinScaleIncrement(newZoomOverviewScale, mActualScale);
- // Make sure the actual scale is no less than zoom overview scale.
- boolean scaleLessThanOverview =
- (newZoomOverviewScale - mActualScale) >= MINIMUM_SCALE_INCREMENT;
- // Make sure mobile sites are correctly handled since mobile site will
- // change content width after rotating.
- boolean mobileSiteInOverview = mInZoomOverview &&
- !exceedsMinScaleIncrement(newZoomOverviewScale, mDefaultScale);
- if (!mWebView.drawHistory() &&
- ((scaleLessThanOverview && settings.getUseWideViewPort())||
- ((mInitialZoomOverview || mobileSiteInOverview) &&
- scaleHasDiff && zoomOverviewWidthChanged))) {
- mInitialZoomOverview = false;
- setZoomScale(newZoomOverviewScale, !willScaleTriggerZoom(mTextWrapScale) &&
- !mWebView.getSettings().getUseFixedViewport());
- } else {
- mInZoomOverview = !scaleHasDiff;
- }
- if (drawData.mFirstLayoutForNonStandardLoad && settings.getLoadWithOverviewMode()) {
- // Set mInitialZoomOverview in case this is the first picture for non standard load,
- // so next new picture could be forced into overview mode if it's true.
- mInitialZoomOverview = mInZoomOverview;
- }
-
- return scaleHasDiff;
- }
-
- /**
- * Set up correct zoom overview width based on different settings.
- *
- * @param drawData webviewcore draw data
- * @param viewWidth current view width
- */
- private boolean setupZoomOverviewWidth(WebViewCore.DrawData drawData, final int viewWidth) {
- WebSettings settings = mWebView.getSettings();
- int newZoomOverviewWidth = mZoomOverviewWidth;
- if (settings.getUseWideViewPort()) {
- if (drawData.mContentSize.x > 0) {
- // The webkitDraw for layers will not populate contentSize, and it'll be
- // ignored for zoom overview width update.
- newZoomOverviewWidth = Math.min(WebViewClassic.sMaxViewportWidth,
- drawData.mContentSize.x);
- }
- } else {
- // If not use wide viewport, use view width as the zoom overview width.
- newZoomOverviewWidth = Math.round(viewWidth / mDefaultScale);
- }
- if (newZoomOverviewWidth != mZoomOverviewWidth) {
- setZoomOverviewWidth(newZoomOverviewWidth);
- return true;
- }
- return false;
- }
-
- /**
- * Updates zoom values after Webkit completes the initial page layout. It
- * is called when visiting a page for the first time as well as when the
- * user navigates back to a page (in which case we may need to restore the
- * zoom levels to the state they were when you left the page). This method
- * should only be called from the UI thread's message handler.
- */
- public void onFirstLayout(WebViewCore.DrawData drawData) {
- // precondition check
- assert drawData != null;
- assert drawData.mViewState != null;
- assert mWebView.getSettings() != null;
-
- WebViewCore.ViewState viewState = drawData.mViewState;
- final Point viewSize = drawData.mViewSize;
- updateZoomRange(viewState, viewSize.x, drawData.mMinPrefWidth);
- setupZoomOverviewWidth(drawData, mWebView.getViewWidth());
- final float overviewScale = getZoomOverviewScale();
- WebSettingsClassic settings = mWebView.getSettings();
- if (!mMinZoomScaleFixed || settings.getUseWideViewPort()) {
- mMinZoomScale = (mInitialScale > 0) ?
- Math.min(mInitialScale, overviewScale) : overviewScale;
- mMaxZoomScale = Math.max(mMaxZoomScale, mMinZoomScale);
- sanitizeMinMaxScales();
- }
-
- if (!mWebView.drawHistory()) {
- float scale;
- if (mInitialScale > 0) {
- scale = mInitialScale;
- } else if (viewState.mIsRestored || viewState.mViewScale > 0) {
- scale = (viewState.mViewScale > 0)
- ? viewState.mViewScale : overviewScale;
- mTextWrapScale = (viewState.mTextWrapScale > 0)
- ? viewState.mTextWrapScale : getReadingLevelScale();
- } else {
- scale = overviewScale;
- if (!settings.getUseWideViewPort()
- || !settings.getLoadWithOverviewMode()) {
- scale = Math.max(mDefaultScale, scale);
- }
- if (settings.isNarrowColumnLayout() &&
- settings.getUseFixedViewport()) {
- // When first layout, reflow using the reading level scale to avoid
- // reflow when double tapped.
- mTextWrapScale = getReadingLevelScale();
- }
- }
- boolean reflowText = false;
- if (!viewState.mIsRestored) {
- if (settings.getUseFixedViewport()) {
- // Override the scale only in case of fixed viewport.
- scale = Math.max(scale, overviewScale);
- mTextWrapScale = Math.max(mTextWrapScale, overviewScale);
- }
- reflowText = exceedsMinScaleIncrement(mTextWrapScale, scale);
- }
- mInitialZoomOverview = settings.getLoadWithOverviewMode() &&
- !exceedsMinScaleIncrement(scale, overviewScale);
- setZoomScale(scale, reflowText);
-
- // update the zoom buttons as the scale can be changed
- updateZoomPicker();
- }
- }
-
- public void saveZoomState(Bundle b) {
- b.putFloat("scale", mActualScale);
- b.putFloat("textwrapScale", mTextWrapScale);
- b.putBoolean("overview", mInZoomOverview);
- }
-
- public void restoreZoomState(Bundle b) {
- // as getWidth() / getHeight() of the view are not available yet, set up
- // mActualScale, so that when onSizeChanged() is called, the rest will
- // be set correctly
- mActualScale = b.getFloat("scale", 1.0f);
- mInvActualScale = 1 / mActualScale;
- mTextWrapScale = b.getFloat("textwrapScale", mActualScale);
- mInZoomOverview = b.getBoolean("overview");
- }
-
- private ZoomControlBase getCurrentZoomControl() {
- if (mWebView.getSettings() != null && mWebView.getSettings().supportZoom()) {
- if (mWebView.getSettings().getBuiltInZoomControls()) {
- if ((mEmbeddedZoomControl == null)
- && mWebView.getSettings().getDisplayZoomControls()) {
- mEmbeddedZoomControl = new ZoomControlEmbedded(this, mWebView);
- }
- return mEmbeddedZoomControl;
- } else {
- if (mExternalZoomControl == null) {
- mExternalZoomControl = new ZoomControlExternal(mWebView);
- }
- return mExternalZoomControl;
- }
- }
- return null;
- }
-
- public void invokeZoomPicker() {
- ZoomControlBase control = getCurrentZoomControl();
- if (control != null) {
- control.show();
- }
- }
-
- public void dismissZoomPicker() {
- ZoomControlBase control = getCurrentZoomControl();
- if (control != null) {
- control.hide();
- }
- }
-
- public boolean isZoomPickerVisible() {
- ZoomControlBase control = getCurrentZoomControl();
- return (control != null) ? control.isVisible() : false;
- }
-
- public void updateZoomPicker() {
- ZoomControlBase control = getCurrentZoomControl();
- if (control != null) {
- control.update();
- }
- }
-
- /**
- * The embedded zoom control intercepts touch events and automatically stays
- * visible. The external control needs to constantly refresh its internal
- * timer to stay visible.
- */
- public void keepZoomPickerVisible() {
- ZoomControlBase control = getCurrentZoomControl();
- if (control != null && control == mExternalZoomControl) {
- control.show();
- }
- }
-
- public View getExternalZoomPicker() {
- ZoomControlBase control = getCurrentZoomControl();
- if (control != null && control == mExternalZoomControl) {
- return mExternalZoomControl.getControls();
- } else {
- return null;
- }
- }
-
- public void setHardwareAccelerated() {
- mHardwareAccelerated = true;
- }
-
- /**
- * OnPageFinished called by webview when a page is fully loaded.
- */
- /* package*/ void onPageFinished(String url) {
- // Turn off initial zoom overview flag when a page is fully loaded.
- mInitialZoomOverview = false;
- }
-}