Delete the old WebView.

Delete all the Java classes used only by the old WebView implementation,
and also sections of common classes that were only needed for the old
WebView.

Bug: 10427705
Change-Id: I02549a71104b35d86d99058c71f43e054730ec7d
This commit is contained in:
Torne (Richard Coles)
2013-10-14 16:28:11 +01:00
parent d892afc88d
commit 94c0057d67
55 changed files with 0 additions and 29789 deletions

View File

@@ -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;
}
}
}
}

View File

@@ -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();
}
}
}

View File

@@ -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

View File

@@ -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();
}
}
}
}

View File

@@ -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

View File

@@ -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() {}
}

View File

@@ -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);
}
});
}
}

View File

@@ -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);
}

View File

@@ -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();

View File

@@ -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;
}

View File

@@ -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);
}

View File

@@ -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());
}
}

View File

@@ -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());
}
}

View File

@@ -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();
}

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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();
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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() {
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}
}

View File

@@ -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();
}
}

View File

@@ -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);
}

View File

@@ -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();
}
}

View File

@@ -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;
}
}

View File

@@ -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();
}
};
}

View File

@@ -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);
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}
}

View File

@@ -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();
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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() {}
}

View File

@@ -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);
}

View File

@@ -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();
}
}
}

View File

@@ -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);
}

View File

@@ -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

View File

@@ -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);
}

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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();
}

View File

@@ -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();
}
}
}

View File

@@ -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