Commit f6250339 authored by dtrainor@chromium.org's avatar dtrainor@chromium.org

Start upstreaming accessibility.


BUG=http://crbug.com/138218


Review URL: https://chromiumcodereview.appspot.com/10832104

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@149667 0039d316-1c4b-4281-b951-d872f2087c98
parent cfeb8ee8
...@@ -7,13 +7,18 @@ package org.chromium.content.browser; ...@@ -7,13 +7,18 @@ package org.chromium.content.browser;
import android.content.Context; import android.content.Context;
import android.content.res.Configuration; import android.content.res.Configuration;
import android.graphics.Canvas; import android.graphics.Canvas;
import android.os.Build;
import android.util.AttributeSet; import android.util.AttributeSet;
import android.view.KeyEvent; import android.view.KeyEvent;
import android.view.MotionEvent; import android.view.MotionEvent;
import android.view.View; import android.view.View;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityNodeInfo;
import android.webkit.DownloadListener; import android.webkit.DownloadListener;
import android.widget.FrameLayout; import android.widget.FrameLayout;
import org.chromium.content.browser.ContentViewCore;
/** /**
* The containing view for {@link ContentViewCore} that exists in the Android UI hierarchy and * The containing view for {@link ContentViewCore} that exists in the Android UI hierarchy and
* exposes the various {@link View} functionality to it. * exposes the various {@link View} functionality to it.
...@@ -94,21 +99,55 @@ public class ContentView extends FrameLayout implements ContentViewCore.Internal ...@@ -94,21 +99,55 @@ public class ContentView extends FrameLayout implements ContentViewCore.Internal
private ContentViewCore mContentViewCore; private ContentViewCore mContentViewCore;
public ContentView(Context context, int nativeWebContents, int personality) { /**
this(context, nativeWebContents, null, android.R.attr.webViewStyle, personality); * Creates an instance of a ContentView.
* @param context The Context the view is running in, through which it can
* access the current theme, resources, etc.
* @param nativeWebContents A pointer to the native web contents.
* @param personality One of {@link #PERSONALITY_CHROME} or {@link #PERSONALITY_VIEW}.
* @return A ContentView instance.
*/
public static ContentView newInstance(Context context, int nativeWebContents, int personality) {
return newInstance(context, nativeWebContents, null, android.R.attr.webViewStyle,
personality);
} }
public ContentView(Context context, int nativeWebContents, AttributeSet attrs) { /**
* Creates an instance of a ContentView.
* @param context The Context the view is running in, through which it can
* access the current theme, resources, etc.
* @param nativeWebContents A pointer to the native web contents.
* @param attrs The attributes of the XML tag that is inflating the view.
* @return A ContentView instance.
*/
public static ContentView newInstance(Context context, int nativeWebContents,
AttributeSet attrs) {
// TODO(klobag): use the WebViewStyle as the default style for now. It enables scrollbar. // TODO(klobag): use the WebViewStyle as the default style for now. It enables scrollbar.
// When ContentView is moved to framework, we can define its own style in the res. // When ContentView is moved to framework, we can define its own style in the res.
this(context, nativeWebContents, attrs, android.R.attr.webViewStyle); return newInstance(context, nativeWebContents, attrs, android.R.attr.webViewStyle);
}
/**
* Creates an instance of a ContentView.
* @param context The Context the view is running in, through which it can
* access the current theme, resources, etc.
* @param nativeWebContents A pointer to the native web contents.
* @param attrs The attributes of the XML tag that is inflating the view.
* @param defStyle The default style to apply to this view.
* @return A ContentView instance.
*/
public static ContentView newInstance(Context context, int nativeWebContents,
AttributeSet attrs, int defStyle) {
return newInstance(context, nativeWebContents, attrs, defStyle, PERSONALITY_VIEW);
} }
public ContentView(Context context, int nativeWebContents, AttributeSet attrs, int defStyle) { private static ContentView newInstance(Context context, int nativeWebContents,
this(context, nativeWebContents, attrs, defStyle, PERSONALITY_VIEW); AttributeSet attrs, int defStyle, int personality) {
// TODO(dtrainor): Upstream JellyBean version of AccessibilityInjector when SDK is 16.
return new ContentView(context, nativeWebContents, attrs, defStyle, personality);
} }
private ContentView(Context context, int nativeWebContents, AttributeSet attrs, int defStyle, protected ContentView(Context context, int nativeWebContents, AttributeSet attrs, int defStyle,
int personality) { int personality) {
super(context, attrs, defStyle); super(context, attrs, defStyle);
...@@ -319,6 +358,34 @@ public class ContentView extends FrameLayout implements ContentViewCore.Internal ...@@ -319,6 +358,34 @@ public class ContentView extends FrameLayout implements ContentViewCore.Internal
mContentViewCore.getContentViewGestureHandler().pinchBy(timeMs, anchorX, anchorY, delta); mContentViewCore.getContentViewGestureHandler().pinchBy(timeMs, anchorX, anchorY, delta);
} }
/**
* This method should be called when the containing activity is paused.
**/
public void onActivityPause() {
mContentViewCore.onActivityPause();
}
/**
* This method should be called when the containing activity is resumed.
**/
public void onActivityResume() {
mContentViewCore.onActivityResume();
}
/**
* To be called when the ContentView is shown.
**/
public void onShow() {
mContentViewCore.onShow();
}
/**
* To be called when the ContentView is hidden.
**/
public void onHide() {
mContentViewCore.onHide();
}
/** /**
* Return the ContentSettings object used to control the settings for this * Return the ContentSettings object used to control the settings for this
* WebView. * WebView.
...@@ -363,6 +430,30 @@ public class ContentView extends FrameLayout implements ContentViewCore.Internal ...@@ -363,6 +430,30 @@ public class ContentView extends FrameLayout implements ContentViewCore.Internal
return super.awakenScrollBars(); return super.awakenScrollBars();
} }
@Override
public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
super.onInitializeAccessibilityNodeInfo(info);
mContentViewCore.onInitializeAccessibilityNodeInfo(info);
}
@Override
public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
super.onInitializeAccessibilityEvent(event);
mContentViewCore.onInitializeAccessibilityEvent(event);
}
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
mContentViewCore.onAttachedToWindow();
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
mContentViewCore.onDetachedFromWindow();
}
void updateMultiTouchZoomSupport() { void updateMultiTouchZoomSupport() {
mContentViewCore.updateMultiTouchZoomSupport(); mContentViewCore.updateMultiTouchZoomSupport();
} }
......
...@@ -7,6 +7,7 @@ package org.chromium.content.browser; ...@@ -7,6 +7,7 @@ package org.chromium.content.browser;
import android.content.Context; import android.content.Context;
import android.content.res.Configuration; import android.content.res.Configuration;
import android.graphics.Canvas; import android.graphics.Canvas;
import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.util.Log; import android.util.Log;
import android.view.ActionMode; import android.view.ActionMode;
...@@ -14,6 +15,8 @@ import android.view.KeyEvent; ...@@ -14,6 +15,8 @@ import android.view.KeyEvent;
import android.view.MotionEvent; import android.view.MotionEvent;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityNodeInfo;
import android.webkit.DownloadListener; import android.webkit.DownloadListener;
import org.chromium.base.CalledByNative; import org.chromium.base.CalledByNative;
...@@ -25,6 +28,7 @@ import org.chromium.content.browser.ZoomManager; ...@@ -25,6 +28,7 @@ import org.chromium.content.browser.ZoomManager;
import org.chromium.content.common.CleanupReference; import org.chromium.content.common.CleanupReference;
import org.chromium.content.common.TraceEvent; import org.chromium.content.common.TraceEvent;
import org.chromium.content.browser.accessibility.AccessibilityInjector;
import org.chromium.content.browser.ContentViewGestureHandler.MotionEventDelegate; import org.chromium.content.browser.ContentViewGestureHandler.MotionEventDelegate;
/** /**
...@@ -178,6 +182,9 @@ public class ContentViewCore implements MotionEventDelegate { ...@@ -178,6 +182,9 @@ public class ContentViewCore implements MotionEventDelegate {
// over DownloadListener. // over DownloadListener.
private ContentViewDownloadDelegate mDownloadDelegate; private ContentViewDownloadDelegate mDownloadDelegate;
// The AccessibilityInjector that handles loading Accessibility scripts into the web page.
private final AccessibilityInjector mAccessibilityInjector;
/** /**
* Enable multi-process ContentView. This should be called by the application before * Enable multi-process ContentView. This should be called by the application before
* constructing any ContentView instances. If enabled, ContentView will run renderers in * constructing any ContentView instances. If enabled, ContentView will run renderers in
...@@ -235,6 +242,9 @@ public class ContentViewCore implements MotionEventDelegate { ...@@ -235,6 +242,9 @@ public class ContentViewCore implements MotionEventDelegate {
AndroidBrowserProcess.initContentViewProcess( AndroidBrowserProcess.initContentViewProcess(
context, AndroidBrowserProcess.MAX_RENDERERS_SINGLE_PROCESS); context, AndroidBrowserProcess.MAX_RENDERERS_SINGLE_PROCESS);
mAccessibilityInjector = AccessibilityInjector.newInstance(this);
mAccessibilityInjector.addOrRemoveAccessibilityApisIfNecessary();
initialize(context, nativeWebContents, personality); initialize(context, nativeWebContents, personality);
} }
...@@ -368,6 +378,7 @@ public class ContentViewCore implements MotionEventDelegate { ...@@ -368,6 +378,7 @@ public class ContentViewCore implements MotionEventDelegate {
* omnibox can report suggestions correctly. * omnibox can report suggestions correctly.
*/ */
public void loadUrlWithoutUrlSanitization(String url, int pageTransition) { public void loadUrlWithoutUrlSanitization(String url, int pageTransition) {
mAccessibilityInjector.addOrRemoveAccessibilityApisIfNecessary();
if (mNativeContentViewCore != 0) { if (mNativeContentViewCore != 0) {
if (isPersonalityView()) { if (isPersonalityView()) {
nativeLoadUrlWithoutUrlSanitizationWithUserAgentOverride( nativeLoadUrlWithoutUrlSanitizationWithUserAgentOverride(
...@@ -484,6 +495,7 @@ public class ContentViewCore implements MotionEventDelegate { ...@@ -484,6 +495,7 @@ public class ContentViewCore implements MotionEventDelegate {
* Reload the current page. * Reload the current page.
*/ */
public void reload() { public void reload() {
mAccessibilityInjector.addOrRemoveAccessibilityApisIfNecessary();
if (mNativeContentViewCore != 0) nativeReload(mNativeContentViewCore); if (mNativeContentViewCore != 0) nativeReload(mNativeContentViewCore);
} }
...@@ -605,19 +617,35 @@ public class ContentViewCore implements MotionEventDelegate { ...@@ -605,19 +617,35 @@ public class ContentViewCore implements MotionEventDelegate {
} }
/** /**
* This method should be called when the containing activity is paused * This method should be called when the containing activity is paused.
*/ */
public void onActivityPause() { public void onActivityPause() {
TraceEvent.begin(); TraceEvent.begin();
hidePopupDialog(); hidePopupDialog();
setAccessibilityState(false);
TraceEvent.end(); TraceEvent.end();
} }
/** /**
* Called when the ContentView is hidden. * This method should be called when the containing activity is resumed.
*/
public void onActivityResume() {
setAccessibilityState(true);
}
/**
* To be called when the ContentView is shown.
*/
public void onShow() {
setAccessibilityState(true);
}
/**
* To be called when the ContentView is hidden.
*/ */
public void onHide() { public void onHide() {
hidePopupDialog(); hidePopupDialog();
setAccessibilityState(false);
} }
/** /**
...@@ -645,6 +673,22 @@ public class ContentViewCore implements MotionEventDelegate { ...@@ -645,6 +673,22 @@ public class ContentViewCore implements MotionEventDelegate {
SelectPopupDialog.hide(this); SelectPopupDialog.hide(this);
} }
/**
* @see View#onAttachedToWindow()
*/
@SuppressWarnings("javadoc")
protected void onAttachedToWindow() {
setAccessibilityState(true);
}
/**
* @see View#onDetachedFromWindow()
*/
@SuppressWarnings("javadoc")
protected void onDetachedFromWindow() {
setAccessibilityState(false);
}
// End FrameLayout overrides. // End FrameLayout overrides.
/** /**
...@@ -1012,6 +1056,57 @@ public class ContentViewCore implements MotionEventDelegate { ...@@ -1012,6 +1056,57 @@ public class ContentViewCore implements MotionEventDelegate {
getContentViewClient().onStartContentIntent(getContext(), contentUrl); getContentViewClient().onStartContentIntent(getContext(), contentUrl);
} }
/**
* Determines whether or not this ContentViewCore can handle this accessibility action.
* @param action The action to perform.
* @return Whether or not this action is supported.
*/
public boolean supportsAccessibilityAction(int action) {
return mAccessibilityInjector.supportsAccessibilityAction(action);
}
/**
* Attempts to perform an accessibility action on the web content. If the accessibility action
* cannot be processed, it returns {@code null}, allowing the caller to know to call the
* super {@link View#performAccessibilityAction(int, Bundle)} method and use that return value.
* Otherwise the return value from this method should be used.
* @param action The action to perform.
* @param arguments Optional action arguments.
* @return Whether the action was performed or {@code null} if the call should be delegated to
* the super {@link View} class.
*/
public boolean performAccessibilityAction(int action, Bundle arguments) {
if (mAccessibilityInjector.supportsAccessibilityAction(action)) {
return mAccessibilityInjector.performAccessibilityAction(action, arguments);
}
return false;
}
/**
* @see View#onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo)
*/
public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
mAccessibilityInjector.onInitializeAccessibilityNodeInfo(info);
// TODO(dtrainor): Upstream accessibility scrolling event information once that data is
// available in ContentViewCore. Currently internal scrolling variables aren't upstreamed.
}
/**
* @see View#onInitializeAccessibilityEvent(AccessibilityEvent)
*/
public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
event.setClassName(this.getClass().getName());
}
/**
* Enable or disable accessibility features.
*/
public void setAccessibilityState(boolean state) {
mAccessibilityInjector.setScriptEnabled(state);
}
// The following methods are implemented at native side. // The following methods are implemented at native side.
/** /**
......
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.content.browser.accessibility;
import android.content.Context;
import android.os.Build;
import android.os.Bundle;
import android.os.SystemClock;
import android.os.Vibrator;
import android.provider.Settings;
import android.speech.tts.TextToSpeech;
import android.view.View;
import android.view.accessibility.AccessibilityManager;
import android.view.accessibility.AccessibilityNodeInfo;
import org.apache.http.NameValuePair;
import org.apache.http.client.utils.URLEncodedUtils;
import org.chromium.content.browser.ContentViewCore;
import org.json.JSONException;
import org.json.JSONObject;
import java.lang.reflect.Field;
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;
/**
* Responsible for accessibility injection and management of a {@link ContentViewCore}.
*/
public class AccessibilityInjector {
// The ContentView this injector is responsible for managing.
protected ContentViewCore mContentViewCore;
// The Java objects that are exposed to JavaScript
private TextToSpeechWrapper mTextToSpeech;
private VibratorWrapper mVibrator;
// Lazily loaded helper objects.
private AccessibilityManager mAccessibilityManager;
// Whether or not we should be injecting the script.
protected boolean mInjectedScriptEnabled;
protected boolean mScriptInjected;
// 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;
private static final int ACCESSIBILITY_SCRIPT_INJECTION_PROVIDED = 1;
private static final String ALIAS_ACCESSIBILITY_JS_INTERFACE = "accessibility";
private static final String ALIAS_ACCESSIBILITY_JS_INTERFACE_2 = "accessibility2";
// Template for JavaScript that injects a screen-reader.
private static final String ACCESSIBILITY_SCREEN_READER_URL =
"https://ssl.gstatic.com/accessibility/javascript/android/chromeandroidvox.js";
private static final String ACCESSIBILITY_SCREEN_READER_JAVASCRIPT_TEMPLATE =
"(function() {" +
" var chooser = document.createElement('script');" +
" chooser.type = 'text/javascript';" +
" chooser.src = '%1s';" +
" document.getElementsByTagName('head')[0].appendChild(chooser);" +
" })();";
// JavaScript call to turn ChromeVox on or off.
private static final String TOGGLE_CHROME_VOX_JAVASCRIPT =
"(function() {" +
" if (typeof cvox !== 'undefined') {" +
" cvox.ChromeVox.host.activateOrDeactivateChromeVox(%1s);" +
" }" +
" })();";
/**
* Returns an instance of the {@link AccessibilityInjector} based on the SDK version.
* @param view The ContentViewCore that this AccessibilityInjector manages.
* @return An instance of a {@link AccessibilityInjector}.
*/
public static AccessibilityInjector newInstance(ContentViewCore view) {
// TODO(dtrainor): Upstream JellyBean version of AccessibilityInjector when SDK is 16.
return new AccessibilityInjector(view);
}
/**
* Creates an instance of the IceCreamSandwichAccessibilityInjector.
* @param view The ContentViewCore that this AccessibilityInjector manages.
*/
protected AccessibilityInjector(ContentViewCore view) {
mContentViewCore = view;
}
/**
* Injects a <script> tag into the current web site that pulls in the ChromeVox script for
* accessibility support. Only injects if accessibility is turned on by
* {@link AccessibilityManager#isEnabled()}, accessibility script injection is turned on, and
* javascript is enabled on this page.
*
* @see AccessibilityManager#isEnabled()
*/
public void injectAccessibilityScriptIntoPage() {
if (!accessibilityIsAvailable()) return;
int axsParameterValue = getAxsUrlParameterValue();
if (axsParameterValue == ACCESSIBILITY_SCRIPT_INJECTION_UNDEFINED) {
try {
Field field = Settings.Secure.class.getField("ACCESSIBILITY_SCRIPT_INJECTION");
field.setAccessible(true);
String ACCESSIBILITY_SCRIPT_INJECTION = (String) field.get(null);
boolean onDeviceScriptInjectionEnabled = (Settings.Secure.getInt(
mContentViewCore.getContext().getContentResolver(),
ACCESSIBILITY_SCRIPT_INJECTION, 0) == 1);
String js = getScreenReaderInjectingJs();
if (onDeviceScriptInjectionEnabled && js != null && mContentViewCore.isAlive()) {
addOrRemoveAccessibilityApisIfNecessary();
mContentViewCore.evaluateJavaScript(js);
mInjectedScriptEnabled = true;
mScriptInjected = true;
}
} catch (NoSuchFieldException ex) {
} catch (IllegalArgumentException ex) {
} catch (IllegalAccessException ex) {
}
}
}
/**
* Handles adding or removing accessibility related Java objects ({@link TextToSpeech} and
* {@link Vibrator}) interfaces from Javascript. This method should be called at a time when it
* is safe to add or remove these interfaces, specifically when the {@link ContentView} is first
* initialized or right before the {@link ContentView} is about to navigate to a URL or reload.
* <p>
* If this method is called at other times, the interfaces might not be correctly removed,
* meaning that Javascript can still access these Java objects that may have been already
* shut down.
*/
public void addOrRemoveAccessibilityApisIfNecessary() {
if (accessibilityIsAvailable()) {
addAccessibilityApis();
} else {
removeAccessibilityApis();
}
}
/**
* Checks whether or not touch to explore is enabled on the system.
*/
public boolean accessibilityIsAvailable() {
return getAccessibilityManager().isEnabled() &&
mContentViewCore.getContentSettings() != null &&
mContentViewCore.getContentSettings().getJavaScriptEnabled();
}
/**
* Sets whether or not the script is enabled. If the script is disabled, we also stop any
* we output that is occurring.
* @param enabled Whether or not to enable the script.
*/
public void setScriptEnabled(boolean enabled) {
if (!accessibilityIsAvailable() || mInjectedScriptEnabled == enabled) return;
mInjectedScriptEnabled = enabled;
if (mContentViewCore.isAlive()) {
String js = String.format(TOGGLE_CHROME_VOX_JAVASCRIPT, Boolean.toString(
mInjectedScriptEnabled));
mContentViewCore.evaluateJavaScript(js);
if (!mInjectedScriptEnabled) {
// Stop any TTS/Vibration right now.
onPageLostFocus();
}
}
}
/**
* Notifies this handler that a page load has started, which means we should mark the
* accessibility script as not being injected. This way we can properly ignore incoming
* accessibility gesture events.
*/
public void onPageLoadStarted() {
mScriptInjected = false;
}
/**
* Stop any notifications that are currently going on (e.g. Text-to-Speech).
*/
public void onPageLostFocus() {
if (mContentViewCore.isAlive()) {
if (mTextToSpeech != null) mTextToSpeech.stop();
if (mVibrator != null) mVibrator.cancel();
}
}
/**
* 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) { }
/**
* 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) {
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) {
return false;
}
protected void addAccessibilityApis() {
Context context = mContentViewCore.getContext();
if (context != null) {
// Enabled, we should try to add if we have to.
if (mTextToSpeech == null) {
mTextToSpeech = new TextToSpeechWrapper(context);
mContentViewCore.addJavascriptInterface(mTextToSpeech,
ALIAS_ACCESSIBILITY_JS_INTERFACE, false);
}
if (mVibrator == null) {
mVibrator = new VibratorWrapper(context);
mContentViewCore.addJavascriptInterface(mVibrator,
ALIAS_ACCESSIBILITY_JS_INTERFACE_2, false);
}
}
}
protected void removeAccessibilityApis() {
if (mTextToSpeech != null) {
mContentViewCore.removeJavascriptInterface(ALIAS_ACCESSIBILITY_JS_INTERFACE);
mTextToSpeech.stop();
mTextToSpeech.shutdownInternal();
mTextToSpeech = null;
}
if (mVibrator != null) {
mContentViewCore.removeJavascriptInterface(ALIAS_ACCESSIBILITY_JS_INTERFACE_2);
mVibrator.cancel();
mVibrator = null;
}
}
private int getAxsUrlParameterValue() {
if (mContentViewCore.getUrl() == null) return ACCESSIBILITY_SCRIPT_INJECTION_UNDEFINED;
try {
List<NameValuePair> params = URLEncodedUtils.parse(new URI(mContentViewCore.getUrl()),
null);
for (NameValuePair param : params) {
if ("axs".equals(param.getName())) {
return Integer.parseInt(param.getValue());
}
}
} catch (URISyntaxException ex) {
} catch (NumberFormatException ex) {
} catch (IllegalArgumentException ex) {
}
return ACCESSIBILITY_SCRIPT_INJECTION_UNDEFINED;
}
private String getScreenReaderInjectingJs() {
return String.format(ACCESSIBILITY_SCREEN_READER_JAVASCRIPT_TEMPLATE,
ACCESSIBILITY_SCREEN_READER_URL);
}
private AccessibilityManager getAccessibilityManager() {
if (mAccessibilityManager == null) {
mAccessibilityManager = (AccessibilityManager) mContentViewCore.getContext().
getSystemService(Context.ACCESSIBILITY_SERVICE);
}
return mAccessibilityManager;
}
/**
* Used to protect how long JavaScript can vibrate for. This isn't a good comprehensive
* protection, just used to cover mistakes and protect against long vibrate durations/repeats.
*
* Also only exposes methods we *want* to expose, no others for the class.
*/
private static class VibratorWrapper {
private static final long MAX_VIBRATE_DURATION_MS = 5000;
private Vibrator mVibrator;
public VibratorWrapper(Context context) {
mVibrator = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE);
}
@SuppressWarnings("unused")
public boolean hasVibrator() {
return mVibrator.hasVibrator();
}
@SuppressWarnings("unused")
public void vibrate(long milliseconds) {
milliseconds = Math.min(milliseconds, MAX_VIBRATE_DURATION_MS);
mVibrator.vibrate(milliseconds);
}
@SuppressWarnings("unused")
public void vibrate(long[] pattern, int repeat) {
for (int i = 0; i < pattern.length; ++i) {
pattern[i] = Math.min(pattern[i], MAX_VIBRATE_DURATION_MS);
}
repeat = -1;
mVibrator.vibrate(pattern, repeat);
}
@SuppressWarnings("unused")
public void cancel() {
mVibrator.cancel();
}
}
/**
* Used to protect the TextToSpeech class, only exposing the methods we want to expose.
*/
private static class TextToSpeechWrapper {
private TextToSpeech mTextToSpeech;
public TextToSpeechWrapper(Context context) {
mTextToSpeech = new TextToSpeech(context, null, null);
}
@SuppressWarnings("unused")
public boolean isSpeaking() {
return mTextToSpeech.isSpeaking();
}
@SuppressWarnings("unused")
public int speak(String text, int queueMode, HashMap<String, String> params) {
return mTextToSpeech.speak(text, queueMode, params);
}
@SuppressWarnings("unused")
public int stop() {
return mTextToSpeech.stop();
}
@SuppressWarnings("unused")
protected void shutdownInternal() {
mTextToSpeech.shutdown();
}
}
}
...@@ -56,5 +56,6 @@ ...@@ -56,5 +56,6 @@
<uses-sdk android:minSdkVersion="14" android:targetSdkVersion="14" /> <uses-sdk android:minSdkVersion="14" android:targetSdkVersion="14" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="android.permission.INTERNET"/> <uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.VIBRATE"/>
<uses-permission android:name="android.permission.WAKE_LOCK"/> <uses-permission android:name="android.permission.WAKE_LOCK"/>
</manifest> </manifest>
...@@ -99,6 +99,22 @@ public class ContentShellActivity extends Activity { ...@@ -99,6 +99,22 @@ public class ContentShellActivity extends Activity {
} }
} }
@Override
protected void onPause() {
ContentView view = getActiveContentView();
if (view != null) view.onActivityPause();
super.onPause();
}
@Override
protected void onResume() {
super.onResume();
ContentView view = getActiveContentView();
if (view != null) view.onActivityResume();
}
private static String getUrlFromIntent(Intent intent) { private static String getUrlFromIntent(Intent intent) {
return intent != null ? intent.getDataString() : null; return intent != null ? intent.getDataString() : null;
} }
...@@ -118,6 +134,15 @@ public class ContentShellActivity extends Activity { ...@@ -118,6 +134,15 @@ public class ContentShellActivity extends Activity {
return mShellManager != null ? mShellManager.getActiveShell() : null; return mShellManager != null ? mShellManager.getActiveShell() : null;
} }
/**
* @return The {@link ContentView} owned by the currently visible {@link Shell} or null if one
* is not showing.
*/
public ContentView getActiveContentView() {
Shell shell = getActiveShell();
return shell != null ? shell.getContentView() : null;
}
private void initializeContentViewResources() { private void initializeContentViewResources() {
AppResource.DIMENSION_LINK_PREVIEW_OVERLAY_RADIUS = R.dimen.link_preview_overlay_radius; AppResource.DIMENSION_LINK_PREVIEW_OVERLAY_RADIUS = R.dimen.link_preview_overlay_radius;
AppResource.DRAWABLE_LINK_PREVIEW_POPUP_OVERLAY = R.drawable.popup_zoomer_overlay; AppResource.DRAWABLE_LINK_PREVIEW_POPUP_OVERLAY = R.drawable.popup_zoomer_overlay;
......
...@@ -171,7 +171,7 @@ public class Shell extends LinearLayout { ...@@ -171,7 +171,7 @@ public class Shell extends LinearLayout {
@SuppressWarnings("unused") @SuppressWarnings("unused")
@CalledByNative @CalledByNative
private void initFromNativeTabContents(int nativeTabContents) { private void initFromNativeTabContents(int nativeTabContents) {
mContentView = new ContentView( mContentView = ContentView.newInstance(
getContext(), nativeTabContents, ContentView.PERSONALITY_CHROME); getContext(), nativeTabContents, ContentView.PERSONALITY_CHROME);
if (mContentView.getUrl() != null) mUrlTextView.setText(mContentView.getUrl()); if (mContentView.getUrl() != null) mUrlTextView.setText(mContentView.getUrl());
((FrameLayout) findViewById(R.id.contentview_holder)).addView(mContentView, ((FrameLayout) findViewById(R.id.contentview_holder)).addView(mContentView,
......
...@@ -86,9 +86,14 @@ public class ShellManager extends FrameLayout { ...@@ -86,9 +86,14 @@ public class ShellManager extends FrameLayout {
shellView.setSurfaceView(mSurfaceView); shellView.setSurfaceView(mSurfaceView);
removeAllViews(); removeAllViews();
if (mActiveShell != null && mActiveShell.getContentView() != null) {
mActiveShell.getContentView().onHide();
}
addView(shellView, new FrameLayout.LayoutParams( addView(shellView, new FrameLayout.LayoutParams(
FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT)); FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT));
mActiveShell = shellView; mActiveShell = shellView;
if (mActiveShell.getContentView() != null) mActiveShell.getContentView().onShow();
return shellView; return shellView;
} }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment