mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-12-01 08:42:13 +00:00
Backed out 2 changesets (bug 1449364, bug 1453278) for rc4 and 42 perma failures in layout/base/tests/test_bug332655-2.html and testInputConnection on a CLOSED TREE
Backed out changeset 7149139c60d9 (bug 1449364) Backed out changeset bcfee006ebaa (bug 1453278)
This commit is contained in:
parent
a589ef9901
commit
d4e35cf8f7
@ -13,32 +13,32 @@ if (Utils.MozBuildApp === "mobile/android") {
|
||||
ChromeUtils.import("resource://gre/modules/Messaging.jsm");
|
||||
}
|
||||
|
||||
// const ACCESSFU_DISABLE = 0;
|
||||
const ACCESSFU_ENABLE = 1;
|
||||
const ACCESSFU_AUTO = 2;
|
||||
|
||||
const SCREENREADER_SETTING = "accessibility.screenreader";
|
||||
const QUICKNAV_MODES_PREF = "accessibility.accessfu.quicknav_modes";
|
||||
const QUICKNAV_INDEX_PREF = "accessibility.accessfu.quicknav_index";
|
||||
|
||||
const GECKOVIEW_MESSAGE = {
|
||||
ACTIVATE: "GeckoView:AccessibilityActivate",
|
||||
VIEW_FOCUSED: "GeckoView:AccessibilityViewFocused",
|
||||
LONG_PRESS: "GeckoView:AccessibilityLongPress",
|
||||
BY_GRANULARITY: "GeckoView:AccessibilityByGranularity",
|
||||
NEXT: "GeckoView:AccessibilityNext",
|
||||
PREVIOUS: "GeckoView:AccessibilityPrevious",
|
||||
SCROLL_BACKWARD: "GeckoView:AccessibilityScrollBackward",
|
||||
SCROLL_FORWARD: "GeckoView:AccessibilityScrollForward",
|
||||
};
|
||||
|
||||
var AccessFu = {
|
||||
/**
|
||||
* Initialize chrome-layer accessibility functionality.
|
||||
* If accessibility is enabled on the platform, then a special accessibility
|
||||
* mode is started.
|
||||
*/
|
||||
attach: function attach(aWindow, aInTest = false) {
|
||||
attach: function attach(aWindow) {
|
||||
Utils.init(aWindow);
|
||||
|
||||
if (!aInTest) {
|
||||
this._enable();
|
||||
if (Utils.MozBuildApp === "mobile/android") {
|
||||
EventDispatcher.instance.dispatch("Accessibility:Ready");
|
||||
EventDispatcher.instance.registerListener(this, "Accessibility:Settings");
|
||||
}
|
||||
|
||||
this._activatePref = new PrefCache(
|
||||
"accessibility.accessfu.activate", this._enableOrDisable.bind(this));
|
||||
|
||||
this._enableOrDisable();
|
||||
},
|
||||
|
||||
/**
|
||||
@ -49,7 +49,10 @@ var AccessFu = {
|
||||
if (this._enabled) {
|
||||
this._disable();
|
||||
}
|
||||
|
||||
if (Utils.MozBuildApp === "mobile/android") {
|
||||
EventDispatcher.instance.unregisterListener(this, "Accessibility:Settings");
|
||||
}
|
||||
delete this._activatePref;
|
||||
Utils.uninit();
|
||||
},
|
||||
|
||||
@ -114,8 +117,16 @@ var AccessFu = {
|
||||
PointerAdapter.start();
|
||||
|
||||
if (Utils.MozBuildApp === "mobile/android") {
|
||||
Utils.win.WindowEventDispatcher.registerListener(this,
|
||||
Object.values(GECKOVIEW_MESSAGE));
|
||||
EventDispatcher.instance.registerListener(this, [
|
||||
"Accessibility:ActivateObject",
|
||||
"Accessibility:Focus",
|
||||
"Accessibility:LongPress",
|
||||
"Accessibility:MoveByGranularity",
|
||||
"Accessibility:NextObject",
|
||||
"Accessibility:PreviousObject",
|
||||
"Accessibility:ScrollBackward",
|
||||
"Accessibility:ScrollForward",
|
||||
]);
|
||||
}
|
||||
|
||||
Services.obs.addObserver(this, "remote-browser-shown");
|
||||
@ -161,8 +172,16 @@ var AccessFu = {
|
||||
Services.obs.removeObserver(this, "inprocess-browser-shown");
|
||||
|
||||
if (Utils.MozBuildApp === "mobile/android") {
|
||||
Utils.win.WindowEventDispatcher.unregisterListener(this,
|
||||
Object.values(GECKOVIEW_MESSAGE));
|
||||
EventDispatcher.instance.unregisterListener(this, [
|
||||
"Accessibility:ActivateObject",
|
||||
"Accessibility:Focus",
|
||||
"Accessibility:LongPress",
|
||||
"Accessibility:MoveByGranularity",
|
||||
"Accessibility:NextObject",
|
||||
"Accessibility:PreviousObject",
|
||||
"Accessibility:ScrollBackward",
|
||||
"Accessibility:ScrollForward",
|
||||
]);
|
||||
}
|
||||
|
||||
delete this._quicknavModesPref;
|
||||
@ -176,6 +195,23 @@ var AccessFu = {
|
||||
Logger.info("AccessFu:Disabled");
|
||||
},
|
||||
|
||||
_enableOrDisable: function _enableOrDisable() {
|
||||
try {
|
||||
if (!this._activatePref) {
|
||||
return;
|
||||
}
|
||||
let activatePref = this._activatePref.value;
|
||||
if (activatePref == ACCESSFU_ENABLE ||
|
||||
this._systemPref && activatePref == ACCESSFU_AUTO) {
|
||||
this._enable();
|
||||
} else {
|
||||
this._disable();
|
||||
}
|
||||
} catch (x) {
|
||||
dump("Error " + x.message + " " + x.fileName + ":" + x.lineNumber);
|
||||
}
|
||||
},
|
||||
|
||||
receiveMessage: function receiveMessage(aMessage) {
|
||||
Logger.debug(() => {
|
||||
return ["Recieved", aMessage.name, JSON.stringify(aMessage.json)];
|
||||
@ -260,43 +296,40 @@ var AccessFu = {
|
||||
|
||||
onEvent(event, data, callback) {
|
||||
switch (event) {
|
||||
case GECKOVIEW_MESSAGE.SETTINGS:
|
||||
if (data.enabled) {
|
||||
this._enable();
|
||||
} else {
|
||||
this._disable();
|
||||
}
|
||||
case "Accessibility:Settings":
|
||||
this._systemPref = data.enabled;
|
||||
this._enableOrDisable();
|
||||
break;
|
||||
case GECKOVIEW_MESSAGE.NEXT:
|
||||
case GECKOVIEW_MESSAGE.PREVIOUS: {
|
||||
case "Accessibility:NextObject":
|
||||
case "Accessibility:PreviousObject": {
|
||||
let rule = "Simple";
|
||||
if (data && data.rule && data.rule.length) {
|
||||
rule = data.rule.substr(0, 1).toUpperCase() +
|
||||
data.rule.substr(1).toLowerCase();
|
||||
}
|
||||
let method = event.replace(/GeckoView:Accessibility(\w+)/, "move$1");
|
||||
let method = event.replace(/Accessibility:(\w+)Object/, "move$1");
|
||||
this.Input.moveCursor(method, rule, "gesture");
|
||||
break;
|
||||
}
|
||||
case GECKOVIEW_MESSAGE.ACTIVATE:
|
||||
case "Accessibility:ActivateObject":
|
||||
this.Input.activateCurrent(data);
|
||||
break;
|
||||
case GECKOVIEW_MESSAGE.LONG_PRESS:
|
||||
case "Accessibility:LongPress":
|
||||
this.Input.sendContextMenuMessage();
|
||||
break;
|
||||
case GECKOVIEW_MESSAGE.SCROL_LFORWARD:
|
||||
case "Accessibility:ScrollForward":
|
||||
this.Input.androidScroll("forward");
|
||||
break;
|
||||
case GECKOVIEW_MESSAGE.SCROLL_BACKWARD:
|
||||
case "Accessibility:ScrollBackward":
|
||||
this.Input.androidScroll("backward");
|
||||
break;
|
||||
case GECKOVIEW_MESSAGE.VIEW_FOCUSED:
|
||||
case "Accessibility:Focus":
|
||||
this._focused = data.gainFocus;
|
||||
if (this._focused) {
|
||||
this.autoMove({ forcePresent: true, noOpIfOnScreen: true });
|
||||
}
|
||||
break;
|
||||
case GECKOVIEW_MESSAGE.BY_GRANULARITY:
|
||||
case "Accessibility:MoveByGranularity":
|
||||
this.Input.moveByGranularity(data);
|
||||
break;
|
||||
}
|
||||
@ -351,7 +384,14 @@ var AccessFu = {
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
// A settings change, it does not have an event type
|
||||
if (aEvent.settingName == SCREENREADER_SETTING) {
|
||||
this._systemPref = aEvent.settingValue;
|
||||
this._enableOrDisable();
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@ -545,7 +585,7 @@ var Output = {
|
||||
const ANDROID_VIEW_TEXT_SELECTION_CHANGED = 0x2000;
|
||||
|
||||
for (let androidEvent of aDetails) {
|
||||
androidEvent.type = "GeckoView:AccessibilityEvent";
|
||||
androidEvent.type = "Accessibility:Event";
|
||||
if (androidEvent.bounds) {
|
||||
androidEvent.bounds = AccessFu.adjustContentBounds(
|
||||
androidEvent.bounds, aBrowser);
|
||||
|
@ -141,8 +141,7 @@ var Utils = { // jshint ignore:line
|
||||
|
||||
get CurrentBrowser() {
|
||||
if (!this.BrowserApp) {
|
||||
// Get the first content browser element when no 'BrowserApp' exists.
|
||||
return this.win.document.querySelector("browser[type=content]");
|
||||
return null;
|
||||
}
|
||||
if (this.MozBuildApp == "b2g") {
|
||||
return this.BrowserApp.contentBrowser;
|
||||
|
@ -141,11 +141,7 @@ var AccessFuTest = {
|
||||
// Start AccessFu and put it in stand-by.
|
||||
ChromeUtils.import("resource://gre/modules/accessibility/AccessFu.jsm");
|
||||
|
||||
let chromeWin = getMainChromeWindow(window);
|
||||
chromeWin.WindowEventDispatcher = {
|
||||
dispatch: () => {},
|
||||
sendRequest: () => {}
|
||||
};
|
||||
AccessFu.attach(getMainChromeWindow(window));
|
||||
|
||||
AccessFu.readyCallback = function readyCallback() {
|
||||
// Enable logging to the console service.
|
||||
@ -153,8 +149,6 @@ var AccessFuTest = {
|
||||
Logger.logLevel = Logger.DEBUG;
|
||||
};
|
||||
|
||||
AccessFu.attach(chromeWin, true);
|
||||
|
||||
var prefs = [["accessibility.accessfu.notify_output", 1]];
|
||||
prefs.push.apply(prefs, aAdditionalPrefs);
|
||||
|
||||
|
@ -13,6 +13,14 @@
|
||||
src="./jsatcommon.js"></script>
|
||||
<script type="application/javascript">
|
||||
|
||||
function prefStart() {
|
||||
AccessFuTest.once_log("AccessFu:Enabled", () =>
|
||||
ok(AccessFu._enabled, "AccessFu was enabled again."));
|
||||
AccessFuTest.once_log("EventManager.start", AccessFuTest.nextTest);
|
||||
// Start AccessFu via pref.
|
||||
SpecialPowers.pushPrefEnv({"set": [["accessibility.accessfu.activate", 1]]});
|
||||
}
|
||||
|
||||
// Listen for 'EventManager.stop' and enable AccessFu again.
|
||||
function settingsStart() {
|
||||
isnot(AccessFu._enabled, true, "AccessFu was disabled.");
|
||||
@ -39,7 +47,19 @@
|
||||
AccessFu._disable();
|
||||
}
|
||||
|
||||
// Listen for initial 'EventManager.start' and disable AccessFu.
|
||||
function prefStop() {
|
||||
ok(AccessFu._enabled, "AccessFu was started via preference.");
|
||||
AccessFuTest.once_log("AccessFu:Disabled", () =>
|
||||
isnot(AccessFu._enabled, true, "AccessFu was disabled."));
|
||||
AccessFuTest.once_log("EventManager.stop", AccessFuTest.nextTest);
|
||||
|
||||
SpecialPowers.pushPrefEnv({"set": [["accessibility.accessfu.activate", 0]]});
|
||||
}
|
||||
|
||||
function doTest() {
|
||||
AccessFuTest.addFunc(prefStart);
|
||||
AccessFuTest.addFunc(prefStop);
|
||||
AccessFuTest.addFunc(settingsStart);
|
||||
AccessFuTest.addFunc(settingsStop);
|
||||
AccessFuTest.waitForExplicitFinish();
|
||||
|
@ -14,13 +14,13 @@
|
||||
<script type="application/javascript">
|
||||
|
||||
function startAccessFu() {
|
||||
SpecialPowers.pushPrefEnv({"set": [["accessibility.accessfu.activate", 1]]});
|
||||
AccessFuTest.once_log("EventManager.start", AccessFuTest.nextTest);
|
||||
AccessFu._enable();
|
||||
}
|
||||
|
||||
function stopAccessFu() {
|
||||
SpecialPowers.pushPrefEnv({"set": [["accessibility.accessfu.activate", 0]]});
|
||||
AccessFuTest.once_log("EventManager.stop", () => AccessFuTest.finish());
|
||||
AccessFu._disable();
|
||||
}
|
||||
|
||||
function hide(id) {
|
||||
|
@ -13,9 +13,10 @@
|
||||
src="./jsatcommon.js"></script>
|
||||
<script type="application/javascript">
|
||||
|
||||
function startAccessFu() {
|
||||
function prefStart() {
|
||||
// Start AccessFu via pref.
|
||||
SpecialPowers.pushPrefEnv({"set": [["accessibility.accessfu.activate", 1]]});
|
||||
AccessFuTest.once_log("EventManager.start", AccessFuTest.nextTest);
|
||||
AccessFu._enable();
|
||||
}
|
||||
|
||||
function nextMode(aCurrentMode, aNextMode) {
|
||||
@ -68,14 +69,15 @@
|
||||
}
|
||||
}
|
||||
|
||||
function stopAccessFu() {
|
||||
ok(AccessFu._enabled, "AccessFu is enabled.");
|
||||
// Listen for initial 'EventManager.start' and disable AccessFu.
|
||||
function prefStop() {
|
||||
ok(AccessFu._enabled, "AccessFu was started via preference.");
|
||||
AccessFuTest.once_log("EventManager.stop", () => AccessFuTest.finish());
|
||||
AccessFu._disable();
|
||||
SpecialPowers.pushPrefEnv({"set": [["accessibility.accessfu.activate", 0]]});
|
||||
}
|
||||
|
||||
function doTest() {
|
||||
AccessFuTest.addFunc(startAccessFu);
|
||||
AccessFuTest.addFunc(prefStart);
|
||||
AccessFuTest.addFunc(nextMode("Link", "Heading"));
|
||||
AccessFuTest.addFunc(nextMode("Heading", "FormElement"));
|
||||
AccessFuTest.addFunc(nextMode("FormElement", "Link"));
|
||||
@ -84,7 +86,7 @@
|
||||
AccessFuTest.addFunc(prevMode("Link", "FormElement"));
|
||||
AccessFuTest.addFunc(setMode(1, "Heading"));
|
||||
AccessFuTest.addFunc(reconfigureModes);
|
||||
AccessFuTest.addFunc(stopAccessFu);
|
||||
AccessFuTest.addFunc(prefStop);
|
||||
AccessFuTest.waitForExplicitFinish();
|
||||
AccessFuTest.runTests([ // Will call SimpleTest.finish();
|
||||
["accessibility.accessfu.quicknav_modes", "Link,Heading,FormElement"]]);
|
||||
|
@ -1255,9 +1255,6 @@ Simulator::Simulator()
|
||||
break_count_ = 0;
|
||||
break_pc_ = nullptr;
|
||||
break_instr_ = 0;
|
||||
single_stepping_ = false;
|
||||
single_step_callback_ = nullptr;
|
||||
single_step_callback_arg_ = nullptr;
|
||||
|
||||
// Set up architecture state.
|
||||
// All registers are initialized to zero to start with.
|
||||
@ -2071,9 +2068,6 @@ Simulator::softwareInterrupt(SimInstruction* instr)
|
||||
MOZ_CRASH();
|
||||
}
|
||||
|
||||
if (single_stepping_)
|
||||
single_step_callback_(single_step_callback_arg_, this, nullptr);
|
||||
|
||||
switch (redirection->type()) {
|
||||
case Args_General0: {
|
||||
Prototype_General0 target = reinterpret_cast<Prototype_General0>(external);
|
||||
@ -2291,9 +2285,6 @@ Simulator::softwareInterrupt(SimInstruction* instr)
|
||||
MOZ_CRASH("call");
|
||||
}
|
||||
|
||||
if (single_stepping_)
|
||||
single_step_callback_(single_step_callback_arg_, this, nullptr);
|
||||
|
||||
setRegister(ra, saved_ra);
|
||||
set_pc(getRegister(ra));
|
||||
#endif
|
||||
@ -3638,33 +3629,10 @@ Simulator::branchDelayInstructionDecode(SimInstruction* instr)
|
||||
instructionDecode(instr);
|
||||
}
|
||||
|
||||
void
|
||||
Simulator::enable_single_stepping(SingleStepCallback cb, void* arg)
|
||||
{
|
||||
single_stepping_ = true;
|
||||
single_step_callback_ = cb;
|
||||
single_step_callback_arg_ = arg;
|
||||
single_step_callback_(single_step_callback_arg_, this, (void*)get_pc());
|
||||
}
|
||||
|
||||
void
|
||||
Simulator::disable_single_stepping()
|
||||
{
|
||||
if (!single_stepping_)
|
||||
return;
|
||||
single_step_callback_(single_step_callback_arg_, this, (void*)get_pc());
|
||||
single_stepping_ = false;
|
||||
single_step_callback_ = nullptr;
|
||||
single_step_callback_arg_ = nullptr;
|
||||
}
|
||||
|
||||
template<bool enableStopSimAt>
|
||||
void
|
||||
Simulator::execute()
|
||||
{
|
||||
if (single_stepping_)
|
||||
single_step_callback_(single_step_callback_arg_, this, nullptr);
|
||||
|
||||
// Get the PC to simulate. Cannot use the accessor here as we need the
|
||||
// raw PC value and not the one used as input to arithmetic instructions.
|
||||
int program_counter = get_pc();
|
||||
@ -3674,17 +3642,12 @@ Simulator::execute()
|
||||
MipsDebugger dbg(this);
|
||||
dbg.debug();
|
||||
} else {
|
||||
if (single_stepping_)
|
||||
single_step_callback_(single_step_callback_arg_, this, (void*)program_counter);
|
||||
SimInstruction* instr = reinterpret_cast<SimInstruction*>(program_counter);
|
||||
instructionDecode(instr);
|
||||
icount_++;
|
||||
}
|
||||
program_counter = get_pc();
|
||||
}
|
||||
|
||||
if (single_stepping_)
|
||||
single_step_callback_(single_step_callback_arg_, this, nullptr);
|
||||
}
|
||||
|
||||
void
|
||||
|
@ -50,11 +50,6 @@ class Redirection;
|
||||
class CachePage;
|
||||
class AutoLockSimulator;
|
||||
|
||||
// When the SingleStepCallback is called, the simulator is about to execute
|
||||
// sim->get_pc() and the current machine state represents the completed
|
||||
// execution of the previous pc.
|
||||
typedef void (*SingleStepCallback)(void* arg, Simulator* sim, void* pc);
|
||||
|
||||
const intptr_t kPointerAlignment = 4;
|
||||
const intptr_t kPointerAlignmentMask = kPointerAlignment - 1;
|
||||
|
||||
@ -207,9 +202,6 @@ class Simulator {
|
||||
template <typename T>
|
||||
T get_pc_as() const { return reinterpret_cast<T>(get_pc()); }
|
||||
|
||||
void enable_single_stepping(SingleStepCallback cb, void* arg);
|
||||
void disable_single_stepping();
|
||||
|
||||
// Accessor to the internal simulator stack area.
|
||||
uintptr_t stackLimit() const;
|
||||
bool overRecursed(uintptr_t newsp = 0) const;
|
||||
@ -371,11 +363,6 @@ class Simulator {
|
||||
SimInstruction* break_pc_;
|
||||
Instr break_instr_;
|
||||
|
||||
// Single-stepping support
|
||||
bool single_stepping_;
|
||||
SingleStepCallback single_step_callback_;
|
||||
void* single_step_callback_arg_;
|
||||
|
||||
// A stop is watched if its code is less than kNumOfWatchedStops.
|
||||
// Only watched stops support enabling/disabling and the counter feature.
|
||||
static const uint32_t kNumOfWatchedStops = 256;
|
||||
|
@ -5327,7 +5327,7 @@ SingleStepCallback(void* arg, jit::Simulator* sim, void* pc)
|
||||
state.sp = (void*)sim->get_register(jit::Simulator::sp);
|
||||
state.lr = (void*)sim->get_register(jit::Simulator::lr);
|
||||
state.fp = (void*)sim->get_register(jit::Simulator::fp);
|
||||
#elif defined(JS_SIMULATOR_MIPS64) || defined(JS_SIMULATOR_MIPS32)
|
||||
#elif defined(JS_SIMULATOR_MIPS64)
|
||||
state.sp = (void*)sim->getRegister(jit::Simulator::sp);
|
||||
state.lr = (void*)sim->getRegister(jit::Simulator::ra);
|
||||
state.fp = (void*)sim->getRegister(jit::Simulator::fp);
|
||||
|
@ -23,7 +23,7 @@
|
||||
#include "vm/Monitor.h"
|
||||
|
||||
// Some platform hooks must be implemented for single-step profiling.
|
||||
#if defined(JS_SIMULATOR_ARM) || defined(JS_SIMULATOR_MIPS64) || defined(JS_SIMULATOR_MIPS32)
|
||||
#if defined(JS_SIMULATOR_ARM) || defined(JS_SIMULATOR_MIPS64)
|
||||
# define SINGLESTEP_PROFILING
|
||||
#endif
|
||||
|
||||
|
@ -19,7 +19,6 @@
|
||||
#ifndef wasm_module_h
|
||||
#define wasm_module_h
|
||||
|
||||
#include "jit/shared/Assembler-shared.h"
|
||||
#include "js/TypeDecls.h"
|
||||
#include "threading/ConditionVariable.h"
|
||||
#include "threading/Mutex.h"
|
||||
|
@ -883,7 +883,7 @@ public class BrowserApp extends GeckoApp
|
||||
null);
|
||||
|
||||
EventDispatcher.getInstance().registerUiThreadListener(this,
|
||||
"GeckoView:AccessibilityEnabled",
|
||||
"Accessibility:Enabled",
|
||||
"Menu:Open",
|
||||
"Menu:Update",
|
||||
"Menu:Add",
|
||||
@ -1715,7 +1715,7 @@ public class BrowserApp extends GeckoApp
|
||||
null);
|
||||
|
||||
EventDispatcher.getInstance().unregisterUiThreadListener(this,
|
||||
"GeckoView:AccessibilityEnabled",
|
||||
"Accessibility:Enabled",
|
||||
"Menu:Open",
|
||||
"Menu:Update",
|
||||
"Menu:Add",
|
||||
@ -1968,7 +1968,7 @@ public class BrowserApp extends GeckoApp
|
||||
|
||||
break;
|
||||
|
||||
case "GeckoView:AccessibilityEnabled":
|
||||
case "Accessibility:Enabled":
|
||||
mDynamicToolbar.setAccessibilityEnabled(message.getBoolean("enabled"));
|
||||
break;
|
||||
|
||||
|
@ -0,0 +1,401 @@
|
||||
/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
package org.mozilla.gecko;
|
||||
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
import org.mozilla.gecko.util.GeckoBundle;
|
||||
import org.mozilla.gecko.util.ThreadUtils;
|
||||
import org.mozilla.geckoview.GeckoView;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.content.Context;
|
||||
import android.graphics.Matrix;
|
||||
import android.graphics.Rect;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.view.ViewParent;
|
||||
import android.view.accessibility.AccessibilityEvent;
|
||||
import android.view.accessibility.AccessibilityManager;
|
||||
import android.view.accessibility.AccessibilityNodeInfo;
|
||||
import android.view.accessibility.AccessibilityNodeProvider;
|
||||
|
||||
import com.googlecode.eyesfree.braille.selfbraille.SelfBrailleClient;
|
||||
import com.googlecode.eyesfree.braille.selfbraille.WriteData;
|
||||
|
||||
public class GeckoAccessibility {
|
||||
private static final String LOGTAG = "GeckoAccessibility";
|
||||
private static final int VIRTUAL_ENTRY_POINT_BEFORE = 1;
|
||||
private static final int VIRTUAL_CURSOR_POSITION = 2;
|
||||
private static final int VIRTUAL_ENTRY_POINT_AFTER = 3;
|
||||
|
||||
private static boolean sEnabled;
|
||||
// Used to store the JSON message and populate the event later in the code path.
|
||||
private static GeckoBundle sHoverEnter;
|
||||
private static AccessibilityNodeInfo sVirtualCursorNode;
|
||||
private static int sCurrentNode;
|
||||
|
||||
// This is the number Brailleback uses to start indexing routing keys.
|
||||
private static final int BRAILLE_CLICK_BASE_INDEX = -275000000;
|
||||
private static SelfBrailleClient sSelfBrailleClient;
|
||||
|
||||
public static void updateAccessibilitySettings (final Context context) {
|
||||
ThreadUtils.postToBackgroundThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
final AccessibilityManager accessibilityManager = (AccessibilityManager)
|
||||
context.getSystemService(Context.ACCESSIBILITY_SERVICE);
|
||||
sEnabled = accessibilityManager.isEnabled() &&
|
||||
accessibilityManager.isTouchExplorationEnabled();
|
||||
|
||||
if (Build.VERSION.SDK_INT >= 16 && sEnabled && sSelfBrailleClient == null) {
|
||||
sSelfBrailleClient = new SelfBrailleClient(context, false);
|
||||
}
|
||||
|
||||
final GeckoBundle ret = new GeckoBundle(1);
|
||||
ret.putBoolean("enabled", sEnabled);
|
||||
// "Accessibility:Settings" is dispatched to the Gecko thread.
|
||||
EventDispatcher.getInstance().dispatch("Accessibility:Settings", ret);
|
||||
// "Accessibility:Enabled" is dispatched to the UI thread.
|
||||
EventDispatcher.getInstance().dispatch("Accessibility:Enabled", ret);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static void populateEventFromJSON (AccessibilityEvent event, GeckoBundle message) {
|
||||
final String[] textArray = message.getStringArray("text");
|
||||
if (textArray != null) {
|
||||
for (int i = 0; i < textArray.length; i++)
|
||||
event.getText().add(textArray[i] != null ? textArray[i] : "");
|
||||
}
|
||||
|
||||
event.setContentDescription(message.getString("description", ""));
|
||||
event.setEnabled(message.getBoolean("enabled", true));
|
||||
event.setChecked(message.getBoolean("checked"));
|
||||
event.setPassword(message.getBoolean("password"));
|
||||
event.setAddedCount(message.getInt("addedCount", -1));
|
||||
event.setRemovedCount(message.getInt("removedCount", -1));
|
||||
event.setFromIndex(message.getInt("fromIndex", -1));
|
||||
event.setItemCount(message.getInt("itemCount", -1));
|
||||
event.setCurrentItemIndex(message.getInt("currentItemIndex", -1));
|
||||
event.setBeforeText(message.getString("beforeText", ""));
|
||||
event.setToIndex(message.getInt("toIndex", -1));
|
||||
event.setScrollable(message.getBoolean("scrollable"));
|
||||
event.setScrollX(message.getInt("scrollX", -1));
|
||||
event.setScrollY(message.getInt("scrollY", -1));
|
||||
event.setMaxScrollX(message.getInt("maxScrollX", -1));
|
||||
event.setMaxScrollY(message.getInt("maxScrollY", -1));
|
||||
}
|
||||
|
||||
private static void sendDirectAccessibilityEvent(int eventType, GeckoBundle message) {
|
||||
final Context context = GeckoAppShell.getApplicationContext();
|
||||
final AccessibilityEvent accEvent = AccessibilityEvent.obtain(eventType);
|
||||
accEvent.setClassName(GeckoAccessibility.class.getName());
|
||||
accEvent.setPackageName(context.getPackageName());
|
||||
populateEventFromJSON(accEvent, message);
|
||||
AccessibilityManager accessibilityManager =
|
||||
(AccessibilityManager) context.getSystemService(Context.ACCESSIBILITY_SERVICE);
|
||||
try {
|
||||
accessibilityManager.sendAccessibilityEvent(accEvent);
|
||||
} catch (IllegalStateException e) {
|
||||
// Accessibility is off.
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean isEnabled() {
|
||||
return sEnabled;
|
||||
}
|
||||
|
||||
public static void sendAccessibilityEvent(final GeckoView view,
|
||||
final GeckoBundle message) {
|
||||
if (!sEnabled)
|
||||
return;
|
||||
|
||||
final int eventType = message.getInt("eventType", -1);
|
||||
if (eventType < 0) {
|
||||
Log.e(LOGTAG, "No accessibility event type provided");
|
||||
return;
|
||||
}
|
||||
|
||||
sendAccessibilityEvent(view, message, eventType);
|
||||
}
|
||||
|
||||
public static void sendAccessibilityEvent(final GeckoView view, final GeckoBundle message,
|
||||
final int eventType) {
|
||||
if (!sEnabled)
|
||||
return;
|
||||
|
||||
final String exitView = message.getString("exitView", "");
|
||||
if (exitView.equals("moveNext")) {
|
||||
sCurrentNode = VIRTUAL_ENTRY_POINT_AFTER;
|
||||
} else if (exitView.equals("movePrevious")) {
|
||||
sCurrentNode = VIRTUAL_ENTRY_POINT_BEFORE;
|
||||
} else {
|
||||
sCurrentNode = VIRTUAL_CURSOR_POSITION;
|
||||
}
|
||||
|
||||
if (Build.VERSION.SDK_INT < 16) {
|
||||
// Before Jelly Bean we send events directly from here while spoofing the source by setting
|
||||
// the package and class name manually.
|
||||
ThreadUtils.postToBackgroundThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
sendDirectAccessibilityEvent(eventType, message);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// In Jelly Bean we populate an AccessibilityNodeInfo with the minimal amount of data to have
|
||||
// it work with TalkBack.
|
||||
if (sVirtualCursorNode == null) {
|
||||
sVirtualCursorNode = AccessibilityNodeInfo.obtain(view, VIRTUAL_CURSOR_POSITION);
|
||||
}
|
||||
sVirtualCursorNode.setEnabled(message.getBoolean("enabled", true));
|
||||
sVirtualCursorNode.setClickable(message.getBoolean("clickable"));
|
||||
sVirtualCursorNode.setCheckable(message.getBoolean("checkable"));
|
||||
sVirtualCursorNode.setChecked(message.getBoolean("checked"));
|
||||
sVirtualCursorNode.setPassword(message.getBoolean("password"));
|
||||
|
||||
final String[] textArray = message.getStringArray("text");
|
||||
StringBuilder sb = new StringBuilder();
|
||||
if (textArray != null && textArray.length > 0) {
|
||||
sb.append(textArray[0] != null ? textArray[0] : "");
|
||||
for (int i = 1; i < textArray.length; i++) {
|
||||
sb.append(' ').append(textArray[i] != null ? textArray[i] : "");
|
||||
}
|
||||
sVirtualCursorNode.setText(sb.toString());
|
||||
}
|
||||
sVirtualCursorNode.setContentDescription(message.getString("description", ""));
|
||||
|
||||
final GeckoBundle bounds = message.getBundle("bounds");
|
||||
if (bounds != null) {
|
||||
Rect relativeBounds = new Rect(bounds.getInt("left"), bounds.getInt("top"),
|
||||
bounds.getInt("right"), bounds.getInt("bottom"));
|
||||
sVirtualCursorNode.setBoundsInParent(relativeBounds);
|
||||
|
||||
final Matrix matrix = new Matrix();
|
||||
final float[] origin = new float[2];
|
||||
view.getSession().getClientToScreenMatrix(matrix);
|
||||
matrix.mapPoints(origin);
|
||||
|
||||
relativeBounds.offset((int) origin[0], (int) origin[1]);
|
||||
sVirtualCursorNode.setBoundsInScreen(relativeBounds);
|
||||
}
|
||||
|
||||
final GeckoBundle braille = message.getBundle("brailleOutput");
|
||||
if (braille != null) {
|
||||
sendBrailleText(view, braille.getString("text", ""),
|
||||
braille.getInt("selectionStart"), braille.getInt("selectionEnd"));
|
||||
}
|
||||
|
||||
if (eventType == AccessibilityEvent.TYPE_VIEW_HOVER_ENTER) {
|
||||
sHoverEnter = message;
|
||||
}
|
||||
|
||||
final AccessibilityEvent event = AccessibilityEvent.obtain(eventType);
|
||||
event.setPackageName(GeckoAppShell.getApplicationContext().getPackageName());
|
||||
event.setClassName(GeckoAccessibility.class.getName());
|
||||
if (eventType == AccessibilityEvent.TYPE_ANNOUNCEMENT ||
|
||||
eventType == AccessibilityEvent.TYPE_VIEW_SCROLLED) {
|
||||
event.setSource(view, View.NO_ID);
|
||||
} else {
|
||||
event.setSource(view, VIRTUAL_CURSOR_POSITION);
|
||||
}
|
||||
populateEventFromJSON(event, message);
|
||||
((ViewParent) view).requestSendAccessibilityEvent(view, event);
|
||||
}
|
||||
}
|
||||
|
||||
private static void sendBrailleText(final View view, final String text, final int selectionStart, final int selectionEnd) {
|
||||
AccessibilityNodeInfo info = AccessibilityNodeInfo.obtain(view, VIRTUAL_CURSOR_POSITION);
|
||||
WriteData data = WriteData.forInfo(info);
|
||||
data.setText(text);
|
||||
// Set either the focus blink or the current caret position/selection
|
||||
data.setSelectionStart(selectionStart);
|
||||
data.setSelectionEnd(selectionEnd);
|
||||
sSelfBrailleClient.write(data);
|
||||
}
|
||||
|
||||
public static void setDelegate(View view) {
|
||||
// Only use this delegate in Jelly Bean.
|
||||
if (Build.VERSION.SDK_INT >= 16) {
|
||||
view.setAccessibilityDelegate(new GeckoAccessibilityDelegate());
|
||||
view.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES);
|
||||
}
|
||||
|
||||
view.setOnFocusChangeListener(new View.OnFocusChangeListener() {
|
||||
@Override
|
||||
public void onFocusChange(final View v, final boolean hasFocus) {
|
||||
onLayerViewFocusChanged(hasFocus);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@TargetApi(19)
|
||||
public static void setAccessibilityManagerListeners(final Context context) {
|
||||
AccessibilityManager accessibilityManager =
|
||||
(AccessibilityManager) context.getSystemService(Context.ACCESSIBILITY_SERVICE);
|
||||
|
||||
accessibilityManager.addAccessibilityStateChangeListener(new AccessibilityManager.AccessibilityStateChangeListener() {
|
||||
@Override
|
||||
public void onAccessibilityStateChanged(boolean enabled) {
|
||||
updateAccessibilitySettings(context);
|
||||
}
|
||||
});
|
||||
|
||||
if (Build.VERSION.SDK_INT >= 19) {
|
||||
accessibilityManager.addTouchExplorationStateChangeListener(new AccessibilityManager.TouchExplorationStateChangeListener() {
|
||||
@Override
|
||||
public void onTouchExplorationStateChanged(boolean enabled) {
|
||||
updateAccessibilitySettings(context);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public static void onLayerViewFocusChanged(boolean gainFocus) {
|
||||
if (sEnabled) {
|
||||
final GeckoBundle data = new GeckoBundle(1);
|
||||
data.putBoolean("gainFocus", gainFocus);
|
||||
EventDispatcher.getInstance().dispatch("Accessibility:Focus", data);
|
||||
}
|
||||
}
|
||||
|
||||
public static class GeckoAccessibilityDelegate extends View.AccessibilityDelegate {
|
||||
AccessibilityNodeProvider mAccessibilityNodeProvider;
|
||||
|
||||
@Override
|
||||
public AccessibilityNodeProvider getAccessibilityNodeProvider(final View hostView) {
|
||||
if (!(hostView instanceof GeckoView)) {
|
||||
return super.getAccessibilityNodeProvider(hostView);
|
||||
}
|
||||
final GeckoView host = (GeckoView) hostView;
|
||||
|
||||
if (mAccessibilityNodeProvider == null)
|
||||
// The accessibility node structure for web content consists of 3 LayerView child nodes:
|
||||
// 1. VIRTUAL_ENTRY_POINT_BEFORE: Represents the entry point before the LayerView.
|
||||
// 2. VIRTUAL_CURSOR_POSITION: Represents the current position of the virtual cursor.
|
||||
// 3. VIRTUAL_ENTRY_POINT_AFTER: Represents the entry point after the LayerView.
|
||||
mAccessibilityNodeProvider = new AccessibilityNodeProvider() {
|
||||
@Override
|
||||
public AccessibilityNodeInfo createAccessibilityNodeInfo(int virtualDescendantId) {
|
||||
AccessibilityNodeInfo info = (virtualDescendantId == VIRTUAL_CURSOR_POSITION && sVirtualCursorNode != null) ?
|
||||
AccessibilityNodeInfo.obtain(sVirtualCursorNode) :
|
||||
AccessibilityNodeInfo.obtain(host, virtualDescendantId);
|
||||
|
||||
switch (virtualDescendantId) {
|
||||
case View.NO_ID:
|
||||
// This is the parent LayerView node, populate it with children.
|
||||
onInitializeAccessibilityNodeInfo(host, info);
|
||||
info.addChild(host, VIRTUAL_ENTRY_POINT_BEFORE);
|
||||
info.addChild(host, VIRTUAL_CURSOR_POSITION);
|
||||
info.addChild(host, VIRTUAL_ENTRY_POINT_AFTER);
|
||||
break;
|
||||
default:
|
||||
info.setParent(host);
|
||||
info.setSource(host, virtualDescendantId);
|
||||
info.setVisibleToUser(host.isShown());
|
||||
info.setPackageName(GeckoAppShell.getApplicationContext().getPackageName());
|
||||
info.setClassName(host.getClass().getName());
|
||||
info.setEnabled(true);
|
||||
info.addAction(AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS);
|
||||
info.addAction(AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS);
|
||||
info.addAction(AccessibilityNodeInfo.ACTION_CLICK);
|
||||
info.addAction(AccessibilityNodeInfo.ACTION_LONG_CLICK);
|
||||
info.addAction(AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY);
|
||||
info.addAction(AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY);
|
||||
info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD);
|
||||
info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD);
|
||||
info.addAction(AccessibilityNodeInfo.ACTION_NEXT_HTML_ELEMENT);
|
||||
info.addAction(AccessibilityNodeInfo.ACTION_PREVIOUS_HTML_ELEMENT);
|
||||
info.setMovementGranularities(AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER |
|
||||
AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD |
|
||||
AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE |
|
||||
AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PARAGRAPH);
|
||||
break;
|
||||
}
|
||||
return info;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean performAction (int virtualViewId, int action, Bundle arguments) {
|
||||
if (action == AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS) {
|
||||
// The accessibility focus is permanently on the middle node, VIRTUAL_CURSOR_POSITION.
|
||||
// When we enter the view forward or backward we just ask Gecko to get focus, keeping the current position.
|
||||
if (virtualViewId == VIRTUAL_CURSOR_POSITION && sHoverEnter != null) {
|
||||
GeckoAccessibility.sendAccessibilityEvent(host, sHoverEnter, AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED);
|
||||
} else {
|
||||
final GeckoBundle data = new GeckoBundle(1);
|
||||
data.putBoolean("gainFocus", true);
|
||||
EventDispatcher.getInstance().dispatch("Accessibility:Focus", data);
|
||||
}
|
||||
return true;
|
||||
} else if (action == AccessibilityNodeInfo.ACTION_CLICK && virtualViewId == VIRTUAL_CURSOR_POSITION) {
|
||||
EventDispatcher.getInstance().dispatch("Accessibility:ActivateObject", null);
|
||||
return true;
|
||||
} else if (action == AccessibilityNodeInfo.ACTION_LONG_CLICK && virtualViewId == VIRTUAL_CURSOR_POSITION) {
|
||||
EventDispatcher.getInstance().dispatch("Accessibility:LongPress", null);
|
||||
return true;
|
||||
} else if (action == AccessibilityNodeInfo.ACTION_SCROLL_FORWARD && virtualViewId == VIRTUAL_CURSOR_POSITION) {
|
||||
EventDispatcher.getInstance().dispatch("Accessibility:ScrollForward", null);
|
||||
return true;
|
||||
} else if (action == AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD && virtualViewId == VIRTUAL_CURSOR_POSITION) {
|
||||
EventDispatcher.getInstance().dispatch("Accessibility:ScrollBackward", null);
|
||||
return true;
|
||||
} else if ((action == AccessibilityNodeInfo.ACTION_NEXT_HTML_ELEMENT ||
|
||||
action == AccessibilityNodeInfo.ACTION_PREVIOUS_HTML_ELEMENT) && virtualViewId == VIRTUAL_CURSOR_POSITION) {
|
||||
final GeckoBundle data;
|
||||
if (arguments != null) {
|
||||
data = new GeckoBundle(1);
|
||||
data.putString("rule", arguments.getString(AccessibilityNodeInfo.ACTION_ARGUMENT_HTML_ELEMENT_STRING));
|
||||
} else {
|
||||
data = null;
|
||||
}
|
||||
EventDispatcher.getInstance().dispatch(action == AccessibilityNodeInfo.ACTION_NEXT_HTML_ELEMENT ?
|
||||
"Accessibility:NextObject" : "Accessibility:PreviousObject", data);
|
||||
return true;
|
||||
} else if (action == AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY &&
|
||||
virtualViewId == VIRTUAL_CURSOR_POSITION) {
|
||||
// XXX: Self brailling gives this action with a bogus argument instead of an actual click action;
|
||||
// the argument value is the BRAILLE_CLICK_BASE_INDEX - the index of the routing key that was hit.
|
||||
// Other negative values are used by ChromeVox, but we don't support them.
|
||||
// FAKE_GRANULARITY_READ_CURRENT = -1
|
||||
// FAKE_GRANULARITY_READ_TITLE = -2
|
||||
// FAKE_GRANULARITY_STOP_SPEECH = -3
|
||||
// FAKE_GRANULARITY_CHANGE_SHIFTER = -4
|
||||
int granularity = arguments.getInt(AccessibilityNodeInfo.ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT);
|
||||
if (granularity <= BRAILLE_CLICK_BASE_INDEX) {
|
||||
int keyIndex = BRAILLE_CLICK_BASE_INDEX - granularity;
|
||||
final GeckoBundle data = new GeckoBundle(1);
|
||||
data.putInt("keyIndex", keyIndex);
|
||||
EventDispatcher.getInstance().dispatch("Accessibility:ActivateObject", data);
|
||||
} else if (granularity > 0) {
|
||||
final GeckoBundle data = new GeckoBundle(2);
|
||||
data.putString("direction", "Next");
|
||||
data.putInt("granularity", granularity);
|
||||
EventDispatcher.getInstance().dispatch("Accessibility:MoveByGranularity", data);
|
||||
}
|
||||
return true;
|
||||
} else if (action == AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY &&
|
||||
virtualViewId == VIRTUAL_CURSOR_POSITION) {
|
||||
int granularity = arguments.getInt(AccessibilityNodeInfo.ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT);
|
||||
final GeckoBundle data = new GeckoBundle(2);
|
||||
data.putString("direction", "Previous");
|
||||
data.putInt("granularity", granularity);
|
||||
if (granularity > 0) {
|
||||
EventDispatcher.getInstance().dispatch("Accessibility:MoveByGranularity", data);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return host.performAccessibilityAction(action, arguments);
|
||||
}
|
||||
};
|
||||
|
||||
return mAccessibilityNodeProvider;
|
||||
}
|
||||
}
|
||||
}
|
@ -687,6 +687,12 @@ public abstract class GeckoApp extends GeckoActivity
|
||||
finish();
|
||||
}
|
||||
|
||||
} else if ("Accessibility:Ready".equals(event)) {
|
||||
GeckoAccessibility.updateAccessibilitySettings(this);
|
||||
|
||||
} else if ("Accessibility:Event".equals(event)) {
|
||||
GeckoAccessibility.sendAccessibilityEvent(mLayerView, message);
|
||||
|
||||
} else if ("Contact:Add".equals(event)) {
|
||||
final String email = message.getString("email");
|
||||
final String phone = message.getString("phone");
|
||||
@ -1032,6 +1038,7 @@ public abstract class GeckoApp extends GeckoActivity
|
||||
|
||||
// To prevent races, register startup events before launching the Gecko thread.
|
||||
EventDispatcher.getInstance().registerGeckoThreadListener(this,
|
||||
"Accessibility:Ready",
|
||||
"Gecko:Ready",
|
||||
null);
|
||||
|
||||
@ -1084,12 +1091,15 @@ public abstract class GeckoApp extends GeckoActivity
|
||||
mLayerView.setSession(session, GeckoRuntime.getDefault(this));
|
||||
mLayerView.setOverScrollMode(View.OVER_SCROLL_NEVER);
|
||||
|
||||
GeckoAccessibility.setDelegate(mLayerView);
|
||||
|
||||
getAppEventDispatcher().registerGeckoThreadListener(this,
|
||||
"Locale:Set",
|
||||
"PrivateBrowsing:Data",
|
||||
null);
|
||||
|
||||
getAppEventDispatcher().registerUiThreadListener(this,
|
||||
"Accessibility:Event",
|
||||
"Contact:Add",
|
||||
"DevToolsAuth:Scan",
|
||||
"DOMFullScreen:Start",
|
||||
@ -2079,6 +2089,7 @@ public abstract class GeckoApp extends GeckoActivity
|
||||
}
|
||||
|
||||
EventDispatcher.getInstance().unregisterGeckoThreadListener(this,
|
||||
"Accessibility:Ready",
|
||||
"Gecko:Ready",
|
||||
null);
|
||||
|
||||
@ -2095,6 +2106,7 @@ public abstract class GeckoApp extends GeckoActivity
|
||||
null);
|
||||
|
||||
getAppEventDispatcher().unregisterUiThreadListener(this,
|
||||
"Accessibility:Event",
|
||||
"Contact:Add",
|
||||
"DevToolsAuth:Scan",
|
||||
"DOMFullScreen:Start",
|
||||
|
@ -367,6 +367,8 @@ public class GeckoApplication extends Application
|
||||
});
|
||||
}
|
||||
|
||||
GeckoAccessibility.setAccessibilityManagerListeners(this);
|
||||
|
||||
AudioFocusAgent.getInstance().attachToContext(this);
|
||||
}
|
||||
|
||||
|
@ -37,6 +37,7 @@ import org.mozilla.gecko.Clipboard;
|
||||
import org.mozilla.gecko.DoorHangerPopup;
|
||||
import org.mozilla.gecko.EventDispatcher;
|
||||
import org.mozilla.gecko.FormAssistPopup;
|
||||
import org.mozilla.gecko.GeckoAccessibility;
|
||||
import org.mozilla.gecko.GeckoSharedPrefs;
|
||||
import org.mozilla.gecko.preferences.GeckoPreferences;
|
||||
import org.mozilla.gecko.R;
|
||||
@ -123,6 +124,8 @@ public class CustomTabsActivity extends AppCompatActivity
|
||||
|
||||
mGeckoView = (GeckoView) findViewById(R.id.gecko_view);
|
||||
|
||||
GeckoAccessibility.setDelegate(mGeckoView);
|
||||
|
||||
final GeckoSessionSettings settings = new GeckoSessionSettings();
|
||||
settings.setBoolean(GeckoSessionSettings.USE_MULTIPROCESS, false);
|
||||
settings.setBoolean(
|
||||
|
@ -26,6 +26,7 @@ import org.mozilla.gecko.AppConstants;
|
||||
import org.mozilla.gecko.BrowserApp;
|
||||
import org.mozilla.gecko.DoorHangerPopup;
|
||||
import org.mozilla.gecko.FormAssistPopup;
|
||||
import org.mozilla.gecko.GeckoAccessibility;
|
||||
import org.mozilla.gecko.GeckoScreenOrientation;
|
||||
import org.mozilla.gecko.GeckoSharedPrefs;
|
||||
import org.mozilla.gecko.preferences.GeckoPreferences;
|
||||
@ -142,6 +143,8 @@ public class WebAppActivity extends AppCompatActivity
|
||||
}
|
||||
});
|
||||
|
||||
GeckoAccessibility.setDelegate(mGeckoView);
|
||||
|
||||
mPromptService = new PromptService(this, mGeckoView.getEventDispatcher());
|
||||
mDoorHangerPopup = new DoorHangerPopup(this, mGeckoView.getEventDispatcher());
|
||||
|
||||
|
@ -86,8 +86,6 @@ function startup() {
|
||||
"GeckoViewTrackingProtection");
|
||||
ModuleManager.add("resource://gre/modules/GeckoViewSelectionAction.jsm",
|
||||
"GeckoViewSelectionAction");
|
||||
ModuleManager.add("resource://gre/modules/GeckoViewAccessibility.jsm",
|
||||
"GeckoViewAccessibility");
|
||||
|
||||
// Move focus to the content window at the end of startup,
|
||||
// so things like text selection can work properly.
|
||||
|
@ -92,8 +92,6 @@ public class GeckoSession extends LayerSession
|
||||
|
||||
private final SessionTextInput mTextInput = new SessionTextInput(this, mNativeQueue);
|
||||
|
||||
private SessionAccessibility mSessionAccessibility;
|
||||
|
||||
private String mId = UUID.randomUUID().toString().replace("-", "");
|
||||
/* package */ String getId() { return mId; }
|
||||
|
||||
@ -827,18 +825,6 @@ public class GeckoSession extends LayerSession
|
||||
return mTextInput;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the SessionAccessibility instance for this session.
|
||||
*
|
||||
* @return SessionAccessibility instance.
|
||||
*/
|
||||
public @NonNull SessionAccessibility getAccessibility() {
|
||||
if (mSessionAccessibility == null) {
|
||||
mSessionAccessibility = new SessionAccessibility(this);
|
||||
}
|
||||
return mSessionAccessibility;
|
||||
}
|
||||
|
||||
@IntDef(flag = true,
|
||||
value = { LOAD_FLAGS_NONE, LOAD_FLAGS_BYPASS_CACHE, LOAD_FLAGS_BYPASS_PROXY,
|
||||
LOAD_FLAGS_EXTERNAL, LOAD_FLAGS_ALLOW_POPUPS })
|
||||
|
@ -34,8 +34,8 @@ import android.view.KeyEvent;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.SurfaceHolder;
|
||||
import android.view.SurfaceView;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.accessibility.AccessibilityManager;
|
||||
import android.view.inputmethod.EditorInfo;
|
||||
import android.view.inputmethod.InputConnection;
|
||||
import android.view.inputmethod.InputMethodManager;
|
||||
@ -45,6 +45,8 @@ public class GeckoView extends FrameLayout {
|
||||
private static final String LOGTAG = "GeckoView";
|
||||
private static final boolean DEBUG = false;
|
||||
|
||||
private static AccessibilityManager sAccessibilityManager;
|
||||
|
||||
protected final Display mDisplay = new Display();
|
||||
protected GeckoSession mSession;
|
||||
protected GeckoRuntime mRuntime;
|
||||
@ -165,7 +167,6 @@ public class GeckoView extends FrameLayout {
|
||||
private void init() {
|
||||
setFocusable(true);
|
||||
setFocusableInTouchMode(true);
|
||||
setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES);
|
||||
|
||||
// We are adding descendants to this LayerView, but we don't want the
|
||||
// descendants to affect the way LayerView retains its focus.
|
||||
@ -324,8 +325,6 @@ public class GeckoView extends FrameLayout {
|
||||
|
||||
mSession.getTextInput().setView(this);
|
||||
|
||||
mSession.getAccessibility().setView(this);
|
||||
|
||||
super.onAttachedToWindow();
|
||||
}
|
||||
|
||||
@ -334,8 +333,7 @@ public class GeckoView extends FrameLayout {
|
||||
super.onDetachedFromWindow();
|
||||
|
||||
if (mSession != null) {
|
||||
mSession.getTextInput().setView(null);
|
||||
mSession.getAccessibility().setView(null);
|
||||
mSession.getTextInput().setView(null);
|
||||
}
|
||||
|
||||
if (mStateSaved) {
|
||||
@ -509,12 +507,21 @@ public class GeckoView extends FrameLayout {
|
||||
mSession.getPanZoomController().onTouchEvent(event);
|
||||
}
|
||||
|
||||
protected static boolean isAccessibilityEnabled(final Context context) {
|
||||
if (sAccessibilityManager == null) {
|
||||
sAccessibilityManager = (AccessibilityManager)
|
||||
context.getSystemService(Context.ACCESSIBILITY_SERVICE);
|
||||
}
|
||||
return sAccessibilityManager.isEnabled() &&
|
||||
sAccessibilityManager.isTouchExplorationEnabled();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onHoverEvent(final MotionEvent event) {
|
||||
// If we get a touchscreen hover event, and accessibility is not enabled, don't
|
||||
// send it to Gecko.
|
||||
if (event.getSource() == InputDevice.SOURCE_TOUCHSCREEN &&
|
||||
!SessionAccessibility.Settings.isEnabled()) {
|
||||
!isAccessibilityEnabled(getContext())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -1,416 +0,0 @@
|
||||
/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
package org.mozilla.geckoview;
|
||||
|
||||
import org.mozilla.gecko.EventDispatcher;
|
||||
import org.mozilla.gecko.GeckoAppShell;
|
||||
import org.mozilla.gecko.util.BundleEventListener;
|
||||
import org.mozilla.gecko.util.EventCallback;
|
||||
import org.mozilla.gecko.util.GeckoBundle;
|
||||
import org.mozilla.gecko.util.ThreadUtils;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.content.Context;
|
||||
import android.graphics.Matrix;
|
||||
import android.graphics.Rect;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.view.ViewParent;
|
||||
import android.view.accessibility.AccessibilityEvent;
|
||||
import android.view.accessibility.AccessibilityManager;
|
||||
import android.view.accessibility.AccessibilityNodeInfo;
|
||||
import android.view.accessibility.AccessibilityNodeProvider;
|
||||
|
||||
public class SessionAccessibility {
|
||||
private static final String LOGTAG = "GeckoAccessibility";
|
||||
// This is a special ID we use for nodes that are eent sources.
|
||||
// We expose it as a fragment and not an actual child of the View node.
|
||||
private static final int VIRTUAL_CONTENT_ID = -2;
|
||||
|
||||
// This is the number BrailleBack uses to start indexing routing keys.
|
||||
private static final int BRAILLE_CLICK_BASE_INDEX = -275000000;
|
||||
|
||||
// Gecko session we are proxying
|
||||
/* package */ final GeckoSession mSession;
|
||||
// This is the view that delegates accessibility to us. We also sends event through it.
|
||||
private View mView;
|
||||
// Aave we reached the last item in content?
|
||||
private boolean mLastItem;
|
||||
// Used to store the JSON message and populate the event later in the code path.
|
||||
private AccessibilityNodeInfo mVirtualContentNode;
|
||||
|
||||
/* package */ SessionAccessibility(final GeckoSession session) {
|
||||
mSession = session;
|
||||
|
||||
Settings.getInstance().dispatch();
|
||||
|
||||
session.getEventDispatcher().registerUiThreadListener(new BundleEventListener() {
|
||||
@Override
|
||||
public void handleMessage(final String event, final GeckoBundle message,
|
||||
final EventCallback callback) {
|
||||
sendAccessibilityEvent(message);
|
||||
}
|
||||
}, "GeckoView:AccessibilityEvent", null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the GeckoView instance that delegates accessibility to this session.
|
||||
*
|
||||
* @return GeckoView instance.
|
||||
*/
|
||||
public View getView() {
|
||||
return mView;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the GeckoView instance that should delegate accessibility to this session.
|
||||
*/
|
||||
public void setView(final View view) {
|
||||
if (mView != null) {
|
||||
mView.setAccessibilityDelegate(null);
|
||||
}
|
||||
|
||||
mView = view;
|
||||
mLastItem = false;
|
||||
|
||||
if (mView == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
mView.setAccessibilityDelegate(new View.AccessibilityDelegate() {
|
||||
private AccessibilityNodeProvider mAccessibilityNodeProvider;
|
||||
|
||||
@Override
|
||||
public AccessibilityNodeProvider getAccessibilityNodeProvider(final View hostView) {
|
||||
|
||||
if (mAccessibilityNodeProvider == null)
|
||||
mAccessibilityNodeProvider = new AccessibilityNodeProvider() {
|
||||
@Override
|
||||
public AccessibilityNodeInfo createAccessibilityNodeInfo(int virtualDescendantId) {
|
||||
assertAttachedView(hostView);
|
||||
|
||||
AccessibilityNodeInfo info = (virtualDescendantId == VIRTUAL_CONTENT_ID && mVirtualContentNode != null) ?
|
||||
AccessibilityNodeInfo.obtain(mVirtualContentNode) :
|
||||
AccessibilityNodeInfo.obtain(mView, virtualDescendantId);
|
||||
|
||||
switch (virtualDescendantId) {
|
||||
case View.NO_ID:
|
||||
// This is the parent View node.
|
||||
// We intentionally don't add VIRTUAL_CONTENT_ID
|
||||
// as a child. It is a source for events,
|
||||
// but not a member of the tree you
|
||||
// can get to by traversing down.
|
||||
onInitializeAccessibilityNodeInfo(mView, info);
|
||||
info.setClassName("android.webkit.WebView"); // TODO: WTF
|
||||
Bundle bundle = info.getExtras();
|
||||
bundle.putCharSequence(
|
||||
"ACTION_ARGUMENT_HTML_ELEMENT_STRING_VALUES",
|
||||
"ARTICLE,BUTTON,CHECKBOX,COMBOBOX,CONTROL,FOCUSABLE,FRAME,GRAPHIC,H1,H2,H3,H4,H5,H6,HEADING,LANDMARK,LINK,LIST,LIST_ITEM,MAIN,MEDIA,RADIO,SECTION,TABLE,TEXT_FIELD,UNVISITED_LINK,VISITED_LINK");
|
||||
info.addAction(AccessibilityNodeInfo.ACTION_NEXT_HTML_ELEMENT);
|
||||
info.addChild(hostView, VIRTUAL_CONTENT_ID);
|
||||
break;
|
||||
default:
|
||||
info.setParent(mView);
|
||||
info.setSource(mView, virtualDescendantId);
|
||||
info.setVisibleToUser(mView.isShown());
|
||||
info.setPackageName(GeckoAppShell.getApplicationContext().getPackageName());
|
||||
info.setClassName(mView.getClass().getName());
|
||||
info.setEnabled(true);
|
||||
info.addAction(AccessibilityNodeInfo.ACTION_CLICK);
|
||||
info.addAction(AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY);
|
||||
info.addAction(AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY);
|
||||
info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD);
|
||||
info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD);
|
||||
info.addAction(AccessibilityNodeInfo.ACTION_NEXT_HTML_ELEMENT);
|
||||
info.addAction(AccessibilityNodeInfo.ACTION_PREVIOUS_HTML_ELEMENT);
|
||||
info.setMovementGranularities(AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER |
|
||||
AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD |
|
||||
AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE |
|
||||
AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PARAGRAPH);
|
||||
break;
|
||||
}
|
||||
return info;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean performAction(int virtualViewId, int action, Bundle arguments) {
|
||||
assertAttachedView(hostView);
|
||||
|
||||
if (virtualViewId == View.NO_ID) {
|
||||
return performRootAction(action, arguments);
|
||||
}
|
||||
return performContentAction(action, arguments);
|
||||
}
|
||||
|
||||
private boolean performRootAction(int action, Bundle arguments) {
|
||||
switch (action) {
|
||||
case AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS:
|
||||
case AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS:
|
||||
final GeckoBundle data = new GeckoBundle(1);
|
||||
data.putBoolean("gainFocus", action == AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS);
|
||||
mSession.getEventDispatcher().dispatch("GeckoView:AccessibilityViewFocused", data);
|
||||
return true;
|
||||
}
|
||||
|
||||
return mView.performAccessibilityAction(action, arguments);
|
||||
}
|
||||
|
||||
private boolean performContentAction(int action, Bundle arguments) {
|
||||
final GeckoBundle data;
|
||||
switch (action) {
|
||||
case AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS:
|
||||
final AccessibilityEvent event = obtainEvent(AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED, VIRTUAL_CONTENT_ID);
|
||||
((ViewParent) mView).requestSendAccessibilityEvent(mView, event);
|
||||
return true;
|
||||
case AccessibilityNodeInfo.ACTION_CLICK:
|
||||
mSession.getEventDispatcher().dispatch("GeckoView:AccessibilityActivate", null);
|
||||
return true;
|
||||
case AccessibilityNodeInfo.ACTION_LONG_CLICK:
|
||||
mSession.getEventDispatcher().dispatch("GeckoView:AccessibilityLongPress", null);
|
||||
return true;
|
||||
case AccessibilityNodeInfo.ACTION_SCROLL_FORWARD:
|
||||
mSession.getEventDispatcher().dispatch("GeckoView:AccessibilityScrollForward", null);
|
||||
return true;
|
||||
case AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD:
|
||||
mSession.getEventDispatcher().dispatch("GeckoView:AccessibilityScrollBackward", null);
|
||||
return true;
|
||||
case AccessibilityNodeInfo.ACTION_NEXT_HTML_ELEMENT:
|
||||
if (mLastItem) {
|
||||
return false;
|
||||
}
|
||||
case AccessibilityNodeInfo.ACTION_PREVIOUS_HTML_ELEMENT:
|
||||
if (arguments != null) {
|
||||
data = new GeckoBundle(1);
|
||||
data.putString("rule", arguments.getString(AccessibilityNodeInfo.ACTION_ARGUMENT_HTML_ELEMENT_STRING));
|
||||
} else {
|
||||
data = null;
|
||||
}
|
||||
mSession.getEventDispatcher().dispatch(action == AccessibilityNodeInfo.ACTION_NEXT_HTML_ELEMENT ?
|
||||
"GeckoView:AccessibilityNext" : "GeckoView:AccessibilityPrevious", data);
|
||||
return true;
|
||||
case AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY:
|
||||
case AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY:
|
||||
// XXX: Self brailling gives this action with a bogus argument instead of an actual click action;
|
||||
// the argument value is the BRAILLE_CLICK_BASE_INDEX - the index of the routing key that was hit.
|
||||
// Other negative values are used by ChromeVox, but we don't support them.
|
||||
// FAKE_GRANULARITY_READ_CURRENT = -1
|
||||
// FAKE_GRANULARITY_READ_TITLE = -2
|
||||
// FAKE_GRANULARITY_STOP_SPEECH = -3
|
||||
// FAKE_GRANULARITY_CHANGE_SHIFTER = -4
|
||||
int granularity = arguments.getInt(AccessibilityNodeInfo.ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT);
|
||||
if (granularity <= BRAILLE_CLICK_BASE_INDEX) {
|
||||
int keyIndex = BRAILLE_CLICK_BASE_INDEX - granularity;
|
||||
data = new GeckoBundle(1);
|
||||
data.putInt("keyIndex", keyIndex);
|
||||
mSession.getEventDispatcher().dispatch("GeckoView:AccessibilityActivate", data);
|
||||
} else if (granularity > 0) {
|
||||
data = new GeckoBundle(2);
|
||||
data.putString("direction", action == AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY ? "Next" : "Previous");
|
||||
data.putInt("granularity", granularity);
|
||||
mSession.getEventDispatcher().dispatch("GeckoView:AccessibilityByGranularity", data);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
return mView.performAccessibilityAction(action, arguments);
|
||||
}
|
||||
|
||||
private void assertAttachedView(final View view) {
|
||||
if (view != mView) {
|
||||
throw new AssertionError("delegate used with wrong view.");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return mAccessibilityNodeProvider;
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
public static class Settings {
|
||||
private static final Settings INSTANCE = new Settings();
|
||||
private boolean mEnabled;
|
||||
|
||||
public Settings() {
|
||||
EventDispatcher.getInstance().registerUiThreadListener(new BundleEventListener() {
|
||||
@Override
|
||||
public void handleMessage(final String event, final GeckoBundle message,
|
||||
final EventCallback callback) {
|
||||
updateAccessibilitySettings();
|
||||
}
|
||||
}, "GeckoView:AccessibilityReady", null);
|
||||
|
||||
final Context context = GeckoAppShell.getApplicationContext();
|
||||
AccessibilityManager accessibilityManager =
|
||||
(AccessibilityManager) context.getSystemService(Context.ACCESSIBILITY_SERVICE);
|
||||
|
||||
mEnabled = accessibilityManager.isEnabled() &&
|
||||
accessibilityManager.isTouchExplorationEnabled();
|
||||
|
||||
accessibilityManager.addAccessibilityStateChangeListener(
|
||||
new AccessibilityManager.AccessibilityStateChangeListener() {
|
||||
@Override
|
||||
public void onAccessibilityStateChanged(boolean enabled) {
|
||||
updateAccessibilitySettings();
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
if (Build.VERSION.SDK_INT >= 19) {
|
||||
accessibilityManager.addTouchExplorationStateChangeListener(
|
||||
new AccessibilityManager.TouchExplorationStateChangeListener() {
|
||||
@Override
|
||||
public void onTouchExplorationStateChanged(boolean enabled) {
|
||||
updateAccessibilitySettings();
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public static Settings getInstance() {
|
||||
return INSTANCE;
|
||||
}
|
||||
|
||||
public static boolean isEnabled() {
|
||||
return INSTANCE.mEnabled;
|
||||
}
|
||||
|
||||
private void updateAccessibilitySettings() {
|
||||
ThreadUtils.postToBackgroundThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
final AccessibilityManager accessibilityManager = (AccessibilityManager)
|
||||
GeckoAppShell.getApplicationContext().getSystemService(Context.ACCESSIBILITY_SERVICE);
|
||||
mEnabled = accessibilityManager.isEnabled() &&
|
||||
accessibilityManager.isTouchExplorationEnabled();
|
||||
|
||||
dispatch();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void dispatch() {
|
||||
final GeckoBundle ret = new GeckoBundle(1);
|
||||
ret.putBoolean("enabled", mEnabled);
|
||||
// "GeckoView:AccessibilitySettings" is dispatched to the Gecko thread.
|
||||
EventDispatcher.getInstance().dispatch("GeckoView:AccessibilitySettings", ret);
|
||||
// "GeckoView:AccessibilityEnabled" is dispatched to the UI thread.
|
||||
EventDispatcher.getInstance().dispatch("GeckoView:AccessibilityEnabled", ret);
|
||||
}
|
||||
}
|
||||
|
||||
private AccessibilityEvent obtainEvent(final int eventType, final int sourceId) {
|
||||
AccessibilityEvent event = AccessibilityEvent.obtain(eventType);
|
||||
event.setPackageName(GeckoAppShell.getApplicationContext().getPackageName());
|
||||
event.setClassName(SessionAccessibility.class.getName());
|
||||
event.setSource(mView, sourceId);
|
||||
|
||||
return event;
|
||||
}
|
||||
|
||||
private static void populateEventFromJSON(AccessibilityEvent event, final GeckoBundle message) {
|
||||
final String[] textArray = message.getStringArray("text");
|
||||
if (textArray != null) {
|
||||
for (int i = 0; i < textArray.length; i++)
|
||||
event.getText().add(textArray[i] != null ? textArray[i] : "");
|
||||
}
|
||||
|
||||
event.setContentDescription(message.getString("description", ""));
|
||||
event.setEnabled(message.getBoolean("enabled", true));
|
||||
event.setChecked(message.getBoolean("checked"));
|
||||
event.setPassword(message.getBoolean("password"));
|
||||
event.setAddedCount(message.getInt("addedCount", -1));
|
||||
event.setRemovedCount(message.getInt("removedCount", -1));
|
||||
event.setFromIndex(message.getInt("fromIndex", -1));
|
||||
event.setItemCount(message.getInt("itemCount", -1));
|
||||
event.setCurrentItemIndex(message.getInt("currentItemIndex", -1));
|
||||
event.setBeforeText(message.getString("beforeText", ""));
|
||||
event.setToIndex(message.getInt("toIndex", -1));
|
||||
event.setScrollable(message.getBoolean("scrollable"));
|
||||
event.setScrollX(message.getInt("scrollX", -1));
|
||||
event.setScrollY(message.getInt("scrollY", -1));
|
||||
event.setMaxScrollX(message.getInt("maxScrollX", -1));
|
||||
event.setMaxScrollY(message.getInt("maxScrollY", -1));
|
||||
}
|
||||
|
||||
private void populateNodeInfoFromJSON(AccessibilityNodeInfo node, final GeckoBundle message) {
|
||||
node.setEnabled(message.getBoolean("enabled", true));
|
||||
node.setClickable(message.getBoolean("clickable"));
|
||||
node.setCheckable(message.getBoolean("checkable"));
|
||||
node.setChecked(message.getBoolean("checked"));
|
||||
node.setPassword(message.getBoolean("password"));
|
||||
|
||||
final String[] textArray = message.getStringArray("text");
|
||||
StringBuilder sb = new StringBuilder();
|
||||
if (textArray != null && textArray.length > 0) {
|
||||
sb.append(textArray[0] != null ? textArray[0] : "");
|
||||
for (int i = 1; i < textArray.length; i++) {
|
||||
sb.append(' ').append(textArray[i] != null ? textArray[i] : "");
|
||||
}
|
||||
node.setText(sb.toString());
|
||||
}
|
||||
node.setContentDescription(message.getString("description", ""));
|
||||
|
||||
final GeckoBundle bounds = message.getBundle("bounds");
|
||||
if (bounds != null) {
|
||||
Rect relativeBounds = new Rect(bounds.getInt("left"), bounds.getInt("top"),
|
||||
bounds.getInt("right"), bounds.getInt("bottom"));
|
||||
node.setBoundsInParent(relativeBounds);
|
||||
|
||||
final Matrix matrix = new Matrix();
|
||||
final float[] origin = new float[2];
|
||||
mSession.getClientToScreenMatrix(matrix);
|
||||
matrix.mapPoints(origin);
|
||||
|
||||
relativeBounds.offset((int) origin[0], (int) origin[1]);
|
||||
node.setBoundsInScreen(relativeBounds);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void sendAccessibilityEvent(final GeckoBundle message) {
|
||||
if (mView == null || !Settings.isEnabled())
|
||||
return;
|
||||
|
||||
final int eventType = message.getInt("eventType", -1);
|
||||
if (eventType < 0) {
|
||||
Log.e(LOGTAG, "No accessibility event type provided");
|
||||
return;
|
||||
}
|
||||
|
||||
int eventSource = VIRTUAL_CONTENT_ID;
|
||||
|
||||
if (eventType == AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED) {
|
||||
final String exitView = message.getString("exitView", "");
|
||||
|
||||
mLastItem = exitView.equals("moveNext");
|
||||
if (mLastItem) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (exitView.equals("movePrevious")) {
|
||||
eventSource = View.NO_ID;
|
||||
}
|
||||
}
|
||||
|
||||
if (eventSource != View.NO_ID) {
|
||||
// In Jelly Bean we populate an AccessibilityNodeInfo with the minimal amount of data to have
|
||||
// it work with TalkBack.
|
||||
if (mVirtualContentNode == null) {
|
||||
mVirtualContentNode = AccessibilityNodeInfo.obtain(mView, eventSource);
|
||||
}
|
||||
populateNodeInfoFromJSON(mVirtualContentNode, message);
|
||||
}
|
||||
|
||||
final AccessibilityEvent accessibilityEvent = obtainEvent(eventType, eventSource);
|
||||
populateEventFromJSON(accessibilityEvent, message);
|
||||
((ViewParent) mView).requestSendAccessibilityEvent(mView, accessibilityEvent);
|
||||
}
|
||||
}
|
@ -1,32 +0,0 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
var EXPORTED_SYMBOLS = ["GeckoViewAccessibility"];
|
||||
|
||||
ChromeUtils.import("resource://gre/modules/GeckoViewModule.jsm");
|
||||
ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetters(this, {
|
||||
EventDispatcher: "resource://gre/modules/Messaging.jsm",
|
||||
AccessFu: "resource://gre/modules/accessibility/AccessFu.jsm"
|
||||
});
|
||||
|
||||
XPCOMUtils.defineLazyGetter(this, "dump", () =>
|
||||
ChromeUtils.import("resource://gre/modules/AndroidLog.jsm",
|
||||
{}).AndroidLog.d.bind(null, "GeckoAccessibility"));
|
||||
|
||||
class GeckoViewAccessibility extends GeckoViewModule {
|
||||
init() {
|
||||
EventDispatcher.instance.dispatch("GeckoView:AccessibilityReady");
|
||||
EventDispatcher.instance.registerListener((aEvent, aData, aCallback) => {
|
||||
if (aData.enabled) {
|
||||
AccessFu.attach(this.window);
|
||||
} else {
|
||||
AccessFu.detach();
|
||||
}
|
||||
}, "GeckoView:AccessibilitySettings");
|
||||
}
|
||||
}
|
@ -6,7 +6,6 @@
|
||||
|
||||
EXTRA_JS_MODULES += [
|
||||
'AndroidLog.jsm',
|
||||
'GeckoViewAccessibility.jsm',
|
||||
'GeckoViewContent.jsm',
|
||||
'GeckoViewContentModule.jsm',
|
||||
'GeckoViewModule.jsm',
|
||||
|
147
mobile/android/thirdparty/com/googlecode/eyesfree/braille/selfbraille/ISelfBrailleService.java
vendored
Normal file
147
mobile/android/thirdparty/com/googlecode/eyesfree/braille/selfbraille/ISelfBrailleService.java
vendored
Normal file
@ -0,0 +1,147 @@
|
||||
/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
package com.googlecode.eyesfree.braille.selfbraille;
|
||||
|
||||
/**
|
||||
* Interface for a client to control braille output for a part of the
|
||||
* accessibility node tree.
|
||||
*/
|
||||
public interface ISelfBrailleService extends android.os.IInterface {
|
||||
/** Local-side IPC implementation stub class. */
|
||||
public static abstract class Stub extends android.os.Binder implements
|
||||
com.googlecode.eyesfree.braille.selfbraille.ISelfBrailleService {
|
||||
private static final java.lang.String DESCRIPTOR = "com.googlecode.eyesfree.braille.selfbraille.ISelfBrailleService";
|
||||
|
||||
/** Construct the stub at attach it to the interface. */
|
||||
public Stub() {
|
||||
this.attachInterface(this, DESCRIPTOR);
|
||||
}
|
||||
|
||||
/**
|
||||
* Cast an IBinder object into an
|
||||
* com.googlecode.eyesfree.braille.selfbraille.ISelfBrailleService
|
||||
* interface, generating a proxy if needed.
|
||||
*/
|
||||
public static com.googlecode.eyesfree.braille.selfbraille.ISelfBrailleService asInterface(
|
||||
android.os.IBinder obj) {
|
||||
if ((obj == null)) {
|
||||
return null;
|
||||
}
|
||||
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
|
||||
if (((iin != null) && (iin instanceof com.googlecode.eyesfree.braille.selfbraille.ISelfBrailleService))) {
|
||||
return ((com.googlecode.eyesfree.braille.selfbraille.ISelfBrailleService) iin);
|
||||
}
|
||||
return new com.googlecode.eyesfree.braille.selfbraille.ISelfBrailleService.Stub.Proxy(
|
||||
obj);
|
||||
}
|
||||
|
||||
@Override
|
||||
public android.os.IBinder asBinder() {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onTransact(int code, android.os.Parcel data,
|
||||
android.os.Parcel reply, int flags)
|
||||
throws android.os.RemoteException {
|
||||
switch (code) {
|
||||
case INTERFACE_TRANSACTION: {
|
||||
reply.writeString(DESCRIPTOR);
|
||||
return true;
|
||||
}
|
||||
case TRANSACTION_write: {
|
||||
data.enforceInterface(DESCRIPTOR);
|
||||
android.os.IBinder _arg0;
|
||||
_arg0 = data.readStrongBinder();
|
||||
com.googlecode.eyesfree.braille.selfbraille.WriteData _arg1;
|
||||
if ((0 != data.readInt())) {
|
||||
_arg1 = com.googlecode.eyesfree.braille.selfbraille.WriteData.CREATOR
|
||||
.createFromParcel(data);
|
||||
} else {
|
||||
_arg1 = null;
|
||||
}
|
||||
this.write(_arg0, _arg1);
|
||||
reply.writeNoException();
|
||||
return true;
|
||||
}
|
||||
case TRANSACTION_disconnect: {
|
||||
data.enforceInterface(DESCRIPTOR);
|
||||
android.os.IBinder _arg0;
|
||||
_arg0 = data.readStrongBinder();
|
||||
this.disconnect(_arg0);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return super.onTransact(code, data, reply, flags);
|
||||
}
|
||||
|
||||
private static class Proxy implements
|
||||
com.googlecode.eyesfree.braille.selfbraille.ISelfBrailleService {
|
||||
private android.os.IBinder mRemote;
|
||||
|
||||
Proxy(android.os.IBinder remote) {
|
||||
mRemote = remote;
|
||||
}
|
||||
|
||||
@Override
|
||||
public android.os.IBinder asBinder() {
|
||||
return mRemote;
|
||||
}
|
||||
|
||||
public java.lang.String getInterfaceDescriptor() {
|
||||
return DESCRIPTOR;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(
|
||||
android.os.IBinder clientToken,
|
||||
com.googlecode.eyesfree.braille.selfbraille.WriteData writeData)
|
||||
throws android.os.RemoteException {
|
||||
android.os.Parcel _data = android.os.Parcel.obtain();
|
||||
android.os.Parcel _reply = android.os.Parcel.obtain();
|
||||
try {
|
||||
_data.writeInterfaceToken(DESCRIPTOR);
|
||||
_data.writeStrongBinder(clientToken);
|
||||
if ((writeData != null)) {
|
||||
_data.writeInt(1);
|
||||
writeData.writeToParcel(_data, 0);
|
||||
} else {
|
||||
_data.writeInt(0);
|
||||
}
|
||||
mRemote.transact(Stub.TRANSACTION_write, _data, _reply, 0);
|
||||
_reply.readException();
|
||||
} finally {
|
||||
_reply.recycle();
|
||||
_data.recycle();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void disconnect(android.os.IBinder clientToken)
|
||||
throws android.os.RemoteException {
|
||||
android.os.Parcel _data = android.os.Parcel.obtain();
|
||||
try {
|
||||
_data.writeInterfaceToken(DESCRIPTOR);
|
||||
_data.writeStrongBinder(clientToken);
|
||||
mRemote.transact(Stub.TRANSACTION_disconnect, _data, null,
|
||||
android.os.IBinder.FLAG_ONEWAY);
|
||||
} finally {
|
||||
_data.recycle();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static final int TRANSACTION_write = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
|
||||
static final int TRANSACTION_disconnect = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
|
||||
}
|
||||
|
||||
public void write(android.os.IBinder clientToken,
|
||||
com.googlecode.eyesfree.braille.selfbraille.WriteData writeData)
|
||||
throws android.os.RemoteException;
|
||||
|
||||
public void disconnect(android.os.IBinder clientToken)
|
||||
throws android.os.RemoteException;
|
||||
}
|
267
mobile/android/thirdparty/com/googlecode/eyesfree/braille/selfbraille/SelfBrailleClient.java
vendored
Normal file
267
mobile/android/thirdparty/com/googlecode/eyesfree/braille/selfbraille/SelfBrailleClient.java
vendored
Normal file
@ -0,0 +1,267 @@
|
||||
/*
|
||||
* Copyright (C) 2012 Google Inc.
|
||||
*
|
||||
* 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 com.googlecode.eyesfree.braille.selfbraille;
|
||||
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.ServiceConnection;
|
||||
import android.content.pm.PackageInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.Signature;
|
||||
import android.os.Binder;
|
||||
import android.os.Handler;
|
||||
import android.os.IBinder;
|
||||
import android.os.Message;
|
||||
import android.os.RemoteException;
|
||||
import android.util.Log;
|
||||
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
|
||||
/**
|
||||
* Client-side interface to the self brailling interface.
|
||||
*
|
||||
* Threading: Instances of this object should be created and shut down
|
||||
* in a thread with a {@link Looper} associated with it. Other methods may
|
||||
* be called on any thread.
|
||||
*/
|
||||
public class SelfBrailleClient {
|
||||
private static final String LOG_TAG =
|
||||
SelfBrailleClient.class.getSimpleName();
|
||||
private static final String ACTION_SELF_BRAILLE_SERVICE =
|
||||
"com.googlecode.eyesfree.braille.service.ACTION_SELF_BRAILLE_SERVICE";
|
||||
private static final String BRAILLE_BACK_PACKAGE =
|
||||
"com.googlecode.eyesfree.brailleback";
|
||||
private static final Intent mServiceIntent =
|
||||
new Intent(ACTION_SELF_BRAILLE_SERVICE)
|
||||
.setPackage(BRAILLE_BACK_PACKAGE);
|
||||
/**
|
||||
* SHA-1 hash value of the Eyes-Free release key certificate, used to sign
|
||||
* BrailleBack. It was generated from the keystore with:
|
||||
* $ keytool -exportcert -keystore <keystorefile> -alias android.keystore \
|
||||
* > cert
|
||||
* $ keytool -printcert -file cert
|
||||
*/
|
||||
// The typecasts are to silence a compiler warning about loss of precision
|
||||
private static final byte[] EYES_FREE_CERT_SHA1 = new byte[] {
|
||||
(byte) 0x9B, (byte) 0x42, (byte) 0x4C, (byte) 0x2D,
|
||||
(byte) 0x27, (byte) 0xAD, (byte) 0x51, (byte) 0xA4,
|
||||
(byte) 0x2A, (byte) 0x33, (byte) 0x7E, (byte) 0x0B,
|
||||
(byte) 0xB6, (byte) 0x99, (byte) 0x1C, (byte) 0x76,
|
||||
(byte) 0xEC, (byte) 0xA4, (byte) 0x44, (byte) 0x61
|
||||
};
|
||||
/**
|
||||
* Delay before the first rebind attempt on bind error or service
|
||||
* disconnect.
|
||||
*/
|
||||
private static final int REBIND_DELAY_MILLIS = 500;
|
||||
private static final int MAX_REBIND_ATTEMPTS = 5;
|
||||
|
||||
private final Binder mIdentity = new Binder();
|
||||
private final Context mContext;
|
||||
private final boolean mAllowDebugService;
|
||||
private final SelfBrailleHandler mHandler = new SelfBrailleHandler();
|
||||
private boolean mShutdown = false;
|
||||
|
||||
/**
|
||||
* Written in handler thread, read in any thread calling methods on the
|
||||
* object.
|
||||
*/
|
||||
private volatile Connection mConnection;
|
||||
/** Protected by synchronizing on mHandler. */
|
||||
private int mNumFailedBinds = 0;
|
||||
|
||||
/**
|
||||
* Constructs an instance of this class. {@code context} is used to bind
|
||||
* to the self braille service. The current thread must have a Looper
|
||||
* associated with it. If {@code allowDebugService} is true, this instance
|
||||
* will connect to a BrailleBack service without requiring it to be signed
|
||||
* by the release key used to sign BrailleBack.
|
||||
*/
|
||||
public SelfBrailleClient(Context context, boolean allowDebugService) {
|
||||
mContext = context;
|
||||
mAllowDebugService = allowDebugService;
|
||||
doBindService();
|
||||
}
|
||||
|
||||
/**
|
||||
* Shuts this instance down, deallocating any global resources it is using.
|
||||
* This method must be called on the same thread that created this object.
|
||||
*/
|
||||
public void shutdown() {
|
||||
mShutdown = true;
|
||||
doUnbindService();
|
||||
}
|
||||
|
||||
public void write(WriteData writeData) {
|
||||
writeData.validate();
|
||||
ISelfBrailleService localService = getSelfBrailleService();
|
||||
if (localService != null) {
|
||||
try {
|
||||
localService.write(mIdentity, writeData);
|
||||
} catch (RemoteException ex) {
|
||||
Log.e(LOG_TAG, "Self braille write failed", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void doBindService() {
|
||||
Connection localConnection = new Connection();
|
||||
if (!mContext.bindService(mServiceIntent, localConnection,
|
||||
Context.BIND_AUTO_CREATE)) {
|
||||
Log.e(LOG_TAG, "Failed to bind to service");
|
||||
mHandler.scheduleRebind();
|
||||
return;
|
||||
}
|
||||
mConnection = localConnection;
|
||||
Log.i(LOG_TAG, "Bound to self braille service");
|
||||
}
|
||||
|
||||
private void doUnbindService() {
|
||||
if (mConnection != null) {
|
||||
ISelfBrailleService localService = getSelfBrailleService();
|
||||
if (localService != null) {
|
||||
try {
|
||||
localService.disconnect(mIdentity);
|
||||
} catch (RemoteException ex) {
|
||||
// Nothing to do.
|
||||
}
|
||||
}
|
||||
mContext.unbindService(mConnection);
|
||||
mConnection = null;
|
||||
}
|
||||
}
|
||||
|
||||
private ISelfBrailleService getSelfBrailleService() {
|
||||
Connection localConnection = mConnection;
|
||||
if (localConnection != null) {
|
||||
return localConnection.mService;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private boolean verifyPackage() {
|
||||
PackageManager pm = mContext.getPackageManager();
|
||||
PackageInfo pi;
|
||||
try {
|
||||
pi = pm.getPackageInfo(BRAILLE_BACK_PACKAGE,
|
||||
PackageManager.GET_SIGNATURES);
|
||||
} catch (PackageManager.NameNotFoundException ex) {
|
||||
Log.w(LOG_TAG, "Can't verify package " + BRAILLE_BACK_PACKAGE,
|
||||
ex);
|
||||
return false;
|
||||
}
|
||||
MessageDigest digest;
|
||||
try {
|
||||
digest = MessageDigest.getInstance("SHA-1");
|
||||
} catch (NoSuchAlgorithmException ex) {
|
||||
Log.e(LOG_TAG, "SHA-1 not supported", ex);
|
||||
return false;
|
||||
}
|
||||
// Check if any of the certificates match our hash.
|
||||
for (Signature signature : pi.signatures) {
|
||||
digest.update(signature.toByteArray());
|
||||
if (MessageDigest.isEqual(EYES_FREE_CERT_SHA1, digest.digest())) {
|
||||
return true;
|
||||
}
|
||||
digest.reset();
|
||||
}
|
||||
if (mAllowDebugService) {
|
||||
Log.w(LOG_TAG, String.format(
|
||||
"*** %s connected to BrailleBack with invalid (debug?) "
|
||||
+ "signature ***",
|
||||
mContext.getPackageName()));
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
private class Connection implements ServiceConnection {
|
||||
// Read in application threads, written in main thread.
|
||||
private volatile ISelfBrailleService mService;
|
||||
|
||||
@Override
|
||||
public void onServiceConnected(ComponentName className,
|
||||
IBinder binder) {
|
||||
if (!verifyPackage()) {
|
||||
Log.w(LOG_TAG, String.format("Service certificate mismatch "
|
||||
+ "for %s, dropping connection",
|
||||
BRAILLE_BACK_PACKAGE));
|
||||
mHandler.unbindService();
|
||||
return;
|
||||
}
|
||||
Log.i(LOG_TAG, "Connected to self braille service");
|
||||
mService = ISelfBrailleService.Stub.asInterface(binder);
|
||||
synchronized (mHandler) {
|
||||
mNumFailedBinds = 0;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onServiceDisconnected(ComponentName className) {
|
||||
Log.e(LOG_TAG, "Disconnected from self braille service");
|
||||
mService = null;
|
||||
// Retry by rebinding.
|
||||
mHandler.scheduleRebind();
|
||||
}
|
||||
}
|
||||
|
||||
private class SelfBrailleHandler extends Handler {
|
||||
private static final int MSG_REBIND_SERVICE = 1;
|
||||
private static final int MSG_UNBIND_SERVICE = 2;
|
||||
|
||||
public void scheduleRebind() {
|
||||
synchronized (this) {
|
||||
if (mNumFailedBinds < MAX_REBIND_ATTEMPTS) {
|
||||
int delay = REBIND_DELAY_MILLIS << mNumFailedBinds;
|
||||
sendEmptyMessageDelayed(MSG_REBIND_SERVICE, delay);
|
||||
++mNumFailedBinds;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void unbindService() {
|
||||
sendEmptyMessage(MSG_UNBIND_SERVICE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleMessage(Message msg) {
|
||||
switch (msg.what) {
|
||||
case MSG_REBIND_SERVICE:
|
||||
handleRebindService();
|
||||
break;
|
||||
case MSG_UNBIND_SERVICE:
|
||||
handleUnbindService();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void handleRebindService() {
|
||||
if (mShutdown) {
|
||||
return;
|
||||
}
|
||||
if (mConnection != null) {
|
||||
doUnbindService();
|
||||
}
|
||||
doBindService();
|
||||
}
|
||||
|
||||
private void handleUnbindService() {
|
||||
doUnbindService();
|
||||
}
|
||||
}
|
||||
}
|
192
mobile/android/thirdparty/com/googlecode/eyesfree/braille/selfbraille/WriteData.java
vendored
Normal file
192
mobile/android/thirdparty/com/googlecode/eyesfree/braille/selfbraille/WriteData.java
vendored
Normal file
@ -0,0 +1,192 @@
|
||||
/*
|
||||
* Copyright (C) 2012 Google Inc.
|
||||
*
|
||||
* 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 com.googlecode.eyesfree.braille.selfbraille;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
import android.view.View;
|
||||
import android.view.accessibility.AccessibilityNodeInfo;
|
||||
|
||||
/**
|
||||
* Represents what should be shown on the braille display for a
|
||||
* part of the accessibility node tree.
|
||||
*/
|
||||
public class WriteData implements Parcelable {
|
||||
|
||||
private static final String PROP_SELECTION_START = "selectionStart";
|
||||
private static final String PROP_SELECTION_END = "selectionEnd";
|
||||
|
||||
private AccessibilityNodeInfo mAccessibilityNodeInfo;
|
||||
private CharSequence mText;
|
||||
private Bundle mProperties = Bundle.EMPTY;
|
||||
|
||||
/**
|
||||
* Returns a new {@link WriteData} instance for the given {@code view}.
|
||||
*/
|
||||
public static WriteData forView(View view) {
|
||||
AccessibilityNodeInfo node = AccessibilityNodeInfo.obtain(view);
|
||||
WriteData writeData = new WriteData();
|
||||
writeData.mAccessibilityNodeInfo = node;
|
||||
return writeData;
|
||||
}
|
||||
|
||||
public static WriteData forInfo(AccessibilityNodeInfo info){
|
||||
WriteData writeData = new WriteData();
|
||||
writeData.mAccessibilityNodeInfo = info;
|
||||
return writeData;
|
||||
}
|
||||
|
||||
|
||||
public AccessibilityNodeInfo getAccessibilityNodeInfo() {
|
||||
return mAccessibilityNodeInfo;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the text to be displayed when the accessibility node associated
|
||||
* with this instance has focus. If this method is not called (or
|
||||
* {@code text} is {@code null}), this client relinquishes control over
|
||||
* this node.
|
||||
*/
|
||||
public WriteData setText(CharSequence text) {
|
||||
mText = text;
|
||||
return this;
|
||||
}
|
||||
|
||||
public CharSequence getText() {
|
||||
return mText;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the start position in the text of a text selection or cursor that
|
||||
* should be marked on the display. A negative value (the default) means
|
||||
* no selection will be added.
|
||||
*/
|
||||
public WriteData setSelectionStart(int v) {
|
||||
writableProperties().putInt(PROP_SELECTION_START, v);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see {@link #setSelectionStart}.
|
||||
*/
|
||||
public int getSelectionStart() {
|
||||
return mProperties.getInt(PROP_SELECTION_START, -1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the end of the text selection to be marked on the display. This
|
||||
* value should only be non-negative if the selection start is
|
||||
* non-negative. If this value is <= the selection start, the selection
|
||||
* is a cursor. Otherwise, the selection covers the range from
|
||||
* start(inclusive) to end (exclusive).
|
||||
*
|
||||
* @see {@link android.text.Selection}.
|
||||
*/
|
||||
public WriteData setSelectionEnd(int v) {
|
||||
writableProperties().putInt(PROP_SELECTION_END, v);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see {@link #setSelectionEnd}.
|
||||
*/
|
||||
public int getSelectionEnd() {
|
||||
return mProperties.getInt(PROP_SELECTION_END, -1);
|
||||
}
|
||||
|
||||
private Bundle writableProperties() {
|
||||
if (mProperties == Bundle.EMPTY) {
|
||||
mProperties = new Bundle();
|
||||
}
|
||||
return mProperties;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks constraints on the fields that must be satisfied before sending
|
||||
* this instance to the self braille service.
|
||||
* @throws IllegalStateException
|
||||
*/
|
||||
public void validate() throws IllegalStateException {
|
||||
if (mAccessibilityNodeInfo == null) {
|
||||
throw new IllegalStateException(
|
||||
"Accessibility node info can't be null");
|
||||
}
|
||||
int selectionStart = getSelectionStart();
|
||||
int selectionEnd = getSelectionEnd();
|
||||
if (mText == null) {
|
||||
if (selectionStart > 0 || selectionEnd > 0) {
|
||||
throw new IllegalStateException(
|
||||
"Selection can't be set without text");
|
||||
}
|
||||
} else {
|
||||
if (selectionStart < 0 && selectionEnd >= 0) {
|
||||
throw new IllegalStateException(
|
||||
"Selection end without start");
|
||||
}
|
||||
int textLength = mText.length();
|
||||
if (selectionStart > textLength || selectionEnd > textLength) {
|
||||
throw new IllegalStateException("Selection out of bounds");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// For Parcelable support.
|
||||
|
||||
public static final Parcelable.Creator<WriteData> CREATOR =
|
||||
new Parcelable.Creator<WriteData>() {
|
||||
@Override
|
||||
public WriteData createFromParcel(Parcel in) {
|
||||
return new WriteData(in);
|
||||
}
|
||||
|
||||
@Override
|
||||
public WriteData[] newArray(int size) {
|
||||
return new WriteData[size];
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
* <strong>Note:</strong> The {@link AccessibilityNodeInfo} will be
|
||||
* recycled by this method, don't try to use this more than once.
|
||||
*/
|
||||
@Override
|
||||
public void writeToParcel(Parcel out, int flags) {
|
||||
mAccessibilityNodeInfo.writeToParcel(out, flags);
|
||||
// The above call recycles the node, so make sure we don't use it
|
||||
// anymore.
|
||||
mAccessibilityNodeInfo = null;
|
||||
out.writeString(mText.toString());
|
||||
out.writeBundle(mProperties);
|
||||
}
|
||||
|
||||
private WriteData() {
|
||||
}
|
||||
|
||||
private WriteData(Parcel in) {
|
||||
mAccessibilityNodeInfo =
|
||||
AccessibilityNodeInfo.CREATOR.createFromParcel(in);
|
||||
mText = in.readString();
|
||||
mProperties = in.readBundle();
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user