am 94c0057d: Delete the old WebView.

* commit '94c0057d67c2e0a4b88a4f735388639210260d0e':
  Delete the old WebView.
This commit is contained in:
Torne (Richard Coles)
2013-10-15 03:39:48 -07:00
committed by Android Git Automerger
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