am 94c0057d: Delete the old WebView.
* commit '94c0057d67c2e0a4b88a4f735388639210260d0e': Delete the old WebView.
This commit is contained in:
committed by
Android Git Automerger
commit
c5f742d4de
@@ -1,976 +0,0 @@
|
||||
/*
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,636 +0,0 @@
|
||||
/*
|
||||
* 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,251 +0,0 @@
|
||||
/*
|
||||
* 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,154 +0,0 @@
|
||||
/*
|
||||
* 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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,341 +0,0 @@
|
||||
/*
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,80 +0,0 @@
|
||||
/*
|
||||
* 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() {}
|
||||
}
|
||||
@@ -1,119 +0,0 @@
|
||||
/*
|
||||
* 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);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,184 +0,0 @@
|
||||
/*
|
||||
* 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);
|
||||
}
|
||||
@@ -89,10 +89,6 @@ public final class CookieSyncManager extends WebSyncManager {
|
||||
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();
|
||||
|
||||
@@ -35,22 +35,4 @@ public class DebugFlags {
|
||||
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;
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -1,78 +0,0 @@
|
||||
/*
|
||||
* 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);
|
||||
}
|
||||
@@ -1,178 +0,0 @@
|
||||
/*
|
||||
* 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());
|
||||
}
|
||||
}
|
||||
@@ -1,225 +0,0 @@
|
||||
/*
|
||||
* 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());
|
||||
}
|
||||
}
|
||||
@@ -1,234 +0,0 @@
|
||||
/*
|
||||
* 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();
|
||||
}
|
||||
@@ -1,201 +0,0 @@
|
||||
/*
|
||||
* 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);
|
||||
}
|
||||
@@ -1,358 +0,0 @@
|
||||
/*
|
||||
* 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);
|
||||
|
||||
}
|
||||
@@ -1,421 +0,0 @@
|
||||
/*
|
||||
* 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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,132 +0,0 @@
|
||||
/*
|
||||
* 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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,383 +0,0 @@
|
||||
/*
|
||||
* 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() {
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,825 +0,0 @@
|
||||
/*
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
@@ -1,321 +0,0 @@
|
||||
/*
|
||||
* 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);
|
||||
}
|
||||
@@ -1,189 +0,0 @@
|
||||
/*
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
@@ -1,77 +0,0 @@
|
||||
/*
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,120 +0,0 @@
|
||||
/*
|
||||
* 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();
|
||||
}
|
||||
}
|
||||
@@ -1,64 +0,0 @@
|
||||
/*
|
||||
* 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);
|
||||
}
|
||||
@@ -1,222 +0,0 @@
|
||||
/*
|
||||
* 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();
|
||||
}
|
||||
}
|
||||
@@ -1,141 +0,0 @@
|
||||
/*
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
@@ -1,150 +0,0 @@
|
||||
/*
|
||||
* 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();
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -1,300 +0,0 @@
|
||||
/*
|
||||
* 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);
|
||||
}
|
||||
}
|
||||
@@ -1,85 +0,0 @@
|
||||
/*
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
@@ -1,149 +0,0 @@
|
||||
/*
|
||||
* 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,72 +0,0 @@
|
||||
/*
|
||||
* 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();
|
||||
}
|
||||
}
|
||||
@@ -1,74 +0,0 @@
|
||||
/*
|
||||
* 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);
|
||||
}
|
||||
}
|
||||
@@ -1,60 +0,0 @@
|
||||
/*
|
||||
* 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);
|
||||
}
|
||||
@@ -1,167 +0,0 @@
|
||||
/*
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
@@ -1,296 +0,0 @@
|
||||
/*
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
@@ -1,88 +0,0 @@
|
||||
/*
|
||||
* 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() {}
|
||||
}
|
||||
@@ -1,166 +0,0 @@
|
||||
/*
|
||||
* 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);
|
||||
}
|
||||
@@ -1,277 +0,0 @@
|
||||
/*
|
||||
* 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,221 +0,0 @@
|
||||
/*
|
||||
* 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);
|
||||
|
||||
}
|
||||
@@ -1,289 +0,0 @@
|
||||
/*
|
||||
* 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);
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,352 +0,0 @@
|
||||
/*
|
||||
* 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);
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
/*
|
||||
* 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;
|
||||
}
|
||||
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,53 +0,0 @@
|
||||
/*
|
||||
* 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();
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,628 +0,0 @@
|
||||
/*
|
||||
* 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,41 +0,0 @@
|
||||
/*
|
||||
* 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();
|
||||
}
|
||||
@@ -1,119 +0,0 @@
|
||||
/*
|
||||
* 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,159 +0,0 @@
|
||||
/*
|
||||
* 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user