mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
Bug 876475 - Make braille output less verbose. r=eeejay r=kats r=ted
--HG-- rename : accessible/src/jsat/UtteranceGenerator.jsm => accessible/src/jsat/OutputGenerator.jsm rename : accessible/tests/mochitest/jsat/utterance.js => accessible/tests/mochitest/jsat/output.js
This commit is contained in:
parent
88f5678f8a
commit
978da0f3ad
@ -468,6 +468,10 @@ var Output = {
|
||||
Utils.win.navigator.vibrate(aDetails.pattern);
|
||||
},
|
||||
|
||||
Braille: function Braille(aDetails, aBrowser) {
|
||||
Logger.debug('Braille output: ' + aDetails.text);
|
||||
},
|
||||
|
||||
_adjustBounds: function(aJsonBounds, aBrowser) {
|
||||
let bounds = new Rect(aJsonBounds.left, aJsonBounds.top,
|
||||
aJsonBounds.right - aJsonBounds.left,
|
||||
|
@ -16,11 +16,11 @@ ACCESSFU_FILES := \
|
||||
EventManager.jsm \
|
||||
jar.mn \
|
||||
Makefile.in \
|
||||
OutputGenerator.jsm \
|
||||
Presentation.jsm \
|
||||
TouchAdapter.jsm \
|
||||
TraversalRules.jsm \
|
||||
Utils.jsm \
|
||||
UtteranceGenerator.jsm \
|
||||
$(NULL)
|
||||
|
||||
ACCESSFU_DEST = $(FINAL_TARGET)/modules/accessibility
|
||||
|
@ -14,9 +14,14 @@ const INCLUDE_NAME = 0x02;
|
||||
const INCLUDE_CUSTOM = 0x04;
|
||||
const NAME_FROM_SUBTREE_RULE = 0x08;
|
||||
|
||||
const UTTERANCE_DESC_FIRST = 0;
|
||||
const OUTPUT_DESC_FIRST = 0;
|
||||
const OUTPUT_DESC_LAST = 1;
|
||||
|
||||
Cu.import('resource://gre/modules/accessibility/Utils.jsm');
|
||||
Cu.import('resource://gre/modules/XPCOMUtils.jsm');
|
||||
XPCOMUtils.defineLazyModuleGetter(this, 'Utils',
|
||||
'resource://gre/modules/accessibility/Utils.jsm');
|
||||
XPCOMUtils.defineLazyModuleGetter(this, 'PrefCache',
|
||||
'resource://gre/modules/accessibility/Utils.jsm');
|
||||
|
||||
let gUtteranceOrder = new PrefCache('accessibility.accessfu.utterance');
|
||||
|
||||
@ -24,44 +29,12 @@ var gStringBundle = Cc['@mozilla.org/intl/stringbundle;1'].
|
||||
getService(Ci.nsIStringBundleService).
|
||||
createBundle('chrome://global/locale/AccessFu.properties');
|
||||
|
||||
this.EXPORTED_SYMBOLS = ['UtteranceGenerator'];
|
||||
this.EXPORTED_SYMBOLS = ['UtteranceGenerator', 'BrailleGenerator'];
|
||||
|
||||
|
||||
/**
|
||||
* Generates speech utterances from objects, actions and state changes.
|
||||
* An utterance is an array of strings.
|
||||
*
|
||||
* It should not be assumed that flattening an utterance array would create a
|
||||
* gramatically correct sentence. For example, {@link genForObject} might
|
||||
* return: ['graphic', 'Welcome to my home page'].
|
||||
* Each string element in an utterance should be gramatically correct in itself.
|
||||
* Another example from {@link genForObject}: ['list item 2 of 5', 'Alabama'].
|
||||
*
|
||||
* An utterance is ordered from the least to the most important. Speaking the
|
||||
* last string usually makes sense, but speaking the first often won't.
|
||||
* For example {@link genForAction} might return ['button', 'clicked'] for a
|
||||
* clicked event. Speaking only 'clicked' makes sense. Speaking 'button' does
|
||||
* not.
|
||||
*/
|
||||
this.UtteranceGenerator = {
|
||||
gActionMap: {
|
||||
jump: 'jumpAction',
|
||||
press: 'pressAction',
|
||||
check: 'checkAction',
|
||||
uncheck: 'uncheckAction',
|
||||
select: 'selectAction',
|
||||
open: 'openAction',
|
||||
close: 'closeAction',
|
||||
switch: 'switchAction',
|
||||
click: 'clickAction',
|
||||
collapse: 'collapseAction',
|
||||
expand: 'expandAction',
|
||||
activate: 'activateAction',
|
||||
cycle: 'cycleAction'
|
||||
},
|
||||
this.OutputGenerator = {
|
||||
|
||||
/**
|
||||
* Generates an utterance for a PivotContext.
|
||||
* Generates output for a PivotContext.
|
||||
* @param {PivotContext} aContext object that generates and caches
|
||||
* context information for a given accessible and its relationship with
|
||||
* another accessible.
|
||||
@ -70,43 +43,44 @@ this.UtteranceGenerator = {
|
||||
* starting from the accessible's ancestry or accessible's subtree.
|
||||
*/
|
||||
genForContext: function genForContext(aContext) {
|
||||
let utterance = [];
|
||||
let addUtterance = function addUtterance(aAccessible) {
|
||||
utterance.push.apply(utterance,
|
||||
UtteranceGenerator.genForObject(aAccessible));
|
||||
let output = [];
|
||||
let self = this;
|
||||
let addOutput = function addOutput(aAccessible) {
|
||||
output.push.apply(output, self.genForObject(aAccessible));
|
||||
};
|
||||
let ignoreSubtree = function ignoreSubtree(aAccessible) {
|
||||
let roleString = Utils.AccRetrieval.getStringRole(aAccessible.role);
|
||||
let nameRule = UtteranceGenerator.roleRuleMap[roleString] || 0;
|
||||
let nameRule = self.roleRuleMap[roleString] || 0;
|
||||
// Ignore subtree if the name is explicit and the role's name rule is the
|
||||
// NAME_FROM_SUBTREE_RULE.
|
||||
return (nameRule & NAME_FROM_SUBTREE_RULE) &&
|
||||
(Utils.getAttributes(aAccessible)['explicit-name'] === 'true');
|
||||
};
|
||||
let utteranceOrder = gUtteranceOrder.value || UTTERANCE_DESC_FIRST;
|
||||
let outputOrder = typeof gUtteranceOrder.value == 'number' ?
|
||||
gUtteranceOrder.value : this.defaultOutputOrder;
|
||||
let contextStart = this._getContextStart(aContext);
|
||||
|
||||
if (utteranceOrder === UTTERANCE_DESC_FIRST) {
|
||||
aContext.newAncestry.forEach(addUtterance);
|
||||
addUtterance(aContext.accessible);
|
||||
[addUtterance(node) for
|
||||
if (outputOrder === OUTPUT_DESC_FIRST) {
|
||||
contextStart.forEach(addOutput);
|
||||
addOutput(aContext.accessible);
|
||||
[addOutput(node) for
|
||||
(node of aContext.subtreeGenerator(true, ignoreSubtree))];
|
||||
} else {
|
||||
[addUtterance(node) for
|
||||
[addOutput(node) for
|
||||
(node of aContext.subtreeGenerator(false, ignoreSubtree))];
|
||||
addUtterance(aContext.accessible);
|
||||
aContext.newAncestry.reverse().forEach(addUtterance);
|
||||
addOutput(aContext.accessible);
|
||||
contextStart.reverse().forEach(addOutput);
|
||||
}
|
||||
|
||||
// Clean up the white space.
|
||||
let trimmed;
|
||||
utterance = [trimmed for (word of utterance) if (trimmed = word.trim())];
|
||||
|
||||
return utterance;
|
||||
output = [trimmed for (word of output) if (trimmed = word.trim())];
|
||||
return output;
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* Generates an utterance for an object.
|
||||
* Generates output for an object.
|
||||
* @param {nsIAccessible} aAccessible accessible object to generate utterance
|
||||
* for.
|
||||
* @return {Array} Two string array. The first string describes the object
|
||||
@ -116,9 +90,8 @@ this.UtteranceGenerator = {
|
||||
*/
|
||||
genForObject: function genForObject(aAccessible) {
|
||||
let roleString = Utils.AccRetrieval.getStringRole(aAccessible.role);
|
||||
|
||||
let func = this.objectUtteranceFunctions[roleString] ||
|
||||
this.objectUtteranceFunctions.defaultFunc;
|
||||
let func = this.objectOutputFunctions[roleString.replace(' ', '')] ||
|
||||
this.objectOutputFunctions.defaultFunc;
|
||||
|
||||
let flags = this.roleRuleMap[roleString] || 0;
|
||||
|
||||
@ -134,68 +107,61 @@ this.UtteranceGenerator = {
|
||||
},
|
||||
|
||||
/**
|
||||
* Generates an utterance for an action performed.
|
||||
* TODO: May become more verbose in the future.
|
||||
* Generates output for an action performed.
|
||||
* @param {nsIAccessible} aAccessible accessible object that the action was
|
||||
* invoked in.
|
||||
* @param {string} aActionName the name of the action, one of the keys in
|
||||
* {@link gActionMap}.
|
||||
* @return {Array} A one string array with the action.
|
||||
*/
|
||||
genForAction: function genForAction(aObject, aActionName) {
|
||||
return [gStringBundle.GetStringFromName(this.gActionMap[aActionName])];
|
||||
},
|
||||
genForAction: function genForAction(aObject, aActionName) {},
|
||||
|
||||
/**
|
||||
* Generates an utterance for an announcement. Basically attempts to localize
|
||||
* Generates output for an announcement. Basically attempts to localize
|
||||
* the announcement string.
|
||||
* @param {string} aAnnouncement unlocalized announcement.
|
||||
* @return {Array} A one string array with the announcement.
|
||||
*/
|
||||
genForAnnouncement: function genForAnnouncement(aAnnouncement) {
|
||||
try {
|
||||
return [gStringBundle.GetStringFromName(aAnnouncement)];
|
||||
} catch (x) {
|
||||
return [aAnnouncement];
|
||||
}
|
||||
},
|
||||
genForAnnouncement: function genForAnnouncement(aAnnouncement) {},
|
||||
|
||||
/**
|
||||
* Generates an utterance for a tab state change.
|
||||
* Generates output for a tab state change.
|
||||
* @param {nsIAccessible} aAccessible accessible object of the tab's attached
|
||||
* document.
|
||||
* @param {string} aTabState the tab state name, see
|
||||
* {@link Presenter.tabStateChanged}.
|
||||
* @return {Array} The tab state utterace.
|
||||
*/
|
||||
genForTabStateChange: function genForTabStateChange(aObject, aTabState) {
|
||||
switch (aTabState) {
|
||||
case 'newtab':
|
||||
return [gStringBundle.GetStringFromName('tabNew')];
|
||||
case 'loading':
|
||||
return [gStringBundle.GetStringFromName('tabLoading')];
|
||||
case 'loaded':
|
||||
return [aObject.name || '',
|
||||
gStringBundle.GetStringFromName('tabLoaded')];
|
||||
case 'loadstopped':
|
||||
return [gStringBundle.GetStringFromName('tabLoadStopped')];
|
||||
case 'reload':
|
||||
return [gStringBundle.GetStringFromName('tabReload')];
|
||||
default:
|
||||
return [];
|
||||
}
|
||||
},
|
||||
genForTabStateChange: function genForTabStateChange(aObject, aTabState) {},
|
||||
|
||||
/**
|
||||
* Generates an utterance for announcing entering and leaving editing mode.
|
||||
* Generates output for announcing entering and leaving editing mode.
|
||||
* @param {aIsEditing} boolean true if we are in editing mode
|
||||
* @return {Array} The mode utterance
|
||||
*/
|
||||
genForEditingMode: function genForEditingMode(aIsEditing) {
|
||||
return [gStringBundle.GetStringFromName(
|
||||
aIsEditing ? 'editingMode' : 'navigationMode')];
|
||||
genForEditingMode: function genForEditingMode(aIsEditing) {},
|
||||
|
||||
_getContextStart: function getContextStart(aContext) {},
|
||||
|
||||
_addName: function _addName(aOutput, aAccessible, aFlags) {
|
||||
let name;
|
||||
if (Utils.getAttributes(aAccessible)['explicit-name'] === 'true' ||
|
||||
(aFlags & INCLUDE_NAME)) {
|
||||
name = aAccessible.name;
|
||||
}
|
||||
|
||||
if (name) {
|
||||
let outputOrder = typeof gUtteranceOrder.value == 'number' ?
|
||||
gUtteranceOrder.value : this.defaultOutputOrder;
|
||||
aOutput[outputOrder === OUTPUT_DESC_FIRST ?
|
||||
'push' : 'unshift'](name);
|
||||
}
|
||||
},
|
||||
|
||||
_getLocalizedRole: function _getLocalizedRole(aRoleStr) {},
|
||||
|
||||
_getLocalizedStates: function _getLocalizedStates(aStates) {},
|
||||
|
||||
roleRuleMap: {
|
||||
'menubar': INCLUDE_DESC,
|
||||
'scrollbar': INCLUDE_DESC,
|
||||
@ -268,35 +234,119 @@ this.UtteranceGenerator = {
|
||||
'listbox': INCLUDE_DESC,
|
||||
'definitionlist': INCLUDE_DESC | INCLUDE_NAME},
|
||||
|
||||
objectUtteranceFunctions: {
|
||||
defaultFunc: function defaultFunc(aAccessible, aRoleStr, aStates, aFlags) {
|
||||
let utterance = [];
|
||||
objectOutputFunctions: {
|
||||
_generateBaseOutput: function _generateBaseOutput(aAccessible, aRoleStr, aStates, aFlags) {
|
||||
let output = [];
|
||||
|
||||
if (aFlags & INCLUDE_DESC) {
|
||||
let desc = this._getLocalizedStates(aStates);
|
||||
let roleStr = this._getLocalizedRole(aRoleStr);
|
||||
if (roleStr)
|
||||
desc.push(roleStr);
|
||||
utterance.push(desc.join(' '));
|
||||
output.push(desc.join(' '));
|
||||
}
|
||||
|
||||
this._addName(utterance, aAccessible, aFlags);
|
||||
this._addName(output, aAccessible, aFlags);
|
||||
|
||||
return utterance;
|
||||
return output;
|
||||
},
|
||||
|
||||
entry: function entry(aAccessible, aRoleStr, aStates, aFlags) {
|
||||
let utterance = [];
|
||||
let output = [];
|
||||
let desc = this._getLocalizedStates(aStates);
|
||||
desc.push(this._getLocalizedRole(
|
||||
(aStates.ext & Ci.nsIAccessibleStates.EXT_STATE_MULTI_LINE) ?
|
||||
'textarea' : 'entry'));
|
||||
|
||||
utterance.push(desc.join(' '));
|
||||
output.push(desc.join(' '));
|
||||
|
||||
this._addName(utterance, aAccessible, aFlags);
|
||||
this._addName(output, aAccessible, aFlags);
|
||||
|
||||
return utterance;
|
||||
return output;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Generates speech utterances from objects, actions and state changes.
|
||||
* An utterance is an array of strings.
|
||||
*
|
||||
* It should not be assumed that flattening an utterance array would create a
|
||||
* gramatically correct sentence. For example, {@link genForObject} might
|
||||
* return: ['graphic', 'Welcome to my home page'].
|
||||
* Each string element in an utterance should be gramatically correct in itself.
|
||||
* Another example from {@link genForObject}: ['list item 2 of 5', 'Alabama'].
|
||||
*
|
||||
* An utterance is ordered from the least to the most important. Speaking the
|
||||
* last string usually makes sense, but speaking the first often won't.
|
||||
* For example {@link genForAction} might return ['button', 'clicked'] for a
|
||||
* clicked event. Speaking only 'clicked' makes sense. Speaking 'button' does
|
||||
* not.
|
||||
*/
|
||||
this.UtteranceGenerator = {
|
||||
__proto__: OutputGenerator,
|
||||
|
||||
defaultOutputOrder: OUTPUT_DESC_FIRST,
|
||||
|
||||
gActionMap: {
|
||||
jump: 'jumpAction',
|
||||
press: 'pressAction',
|
||||
check: 'checkAction',
|
||||
uncheck: 'uncheckAction',
|
||||
select: 'selectAction',
|
||||
open: 'openAction',
|
||||
close: 'closeAction',
|
||||
switch: 'switchAction',
|
||||
click: 'clickAction',
|
||||
collapse: 'collapseAction',
|
||||
expand: 'expandAction',
|
||||
activate: 'activateAction',
|
||||
cycle: 'cycleAction'
|
||||
},
|
||||
|
||||
//TODO: May become more verbose in the future.
|
||||
genForAction: function genForAction(aObject, aActionName) {
|
||||
return [gStringBundle.GetStringFromName(this.gActionMap[aActionName])];
|
||||
},
|
||||
|
||||
genForAnnouncement: function genForAnnouncement(aAnnouncement) {
|
||||
try {
|
||||
return [gStringBundle.GetStringFromName(aAnnouncement)];
|
||||
} catch (x) {
|
||||
return [aAnnouncement];
|
||||
}
|
||||
},
|
||||
|
||||
genForTabStateChange: function genForTabStateChange(aObject, aTabState) {
|
||||
switch (aTabState) {
|
||||
case 'newtab':
|
||||
return [gStringBundle.GetStringFromName('tabNew')];
|
||||
case 'loading':
|
||||
return [gStringBundle.GetStringFromName('tabLoading')];
|
||||
case 'loaded':
|
||||
return [aObject.name || '',
|
||||
gStringBundle.GetStringFromName('tabLoaded')];
|
||||
case 'loadstopped':
|
||||
return [gStringBundle.GetStringFromName('tabLoadStopped')];
|
||||
case 'reload':
|
||||
return [gStringBundle.GetStringFromName('tabReload')];
|
||||
default:
|
||||
return [];
|
||||
}
|
||||
},
|
||||
|
||||
genForEditingMode: function genForEditingMode(aIsEditing) {
|
||||
return [gStringBundle.GetStringFromName(
|
||||
aIsEditing ? 'editingMode' : 'navigationMode')];
|
||||
},
|
||||
|
||||
objectOutputFunctions: {
|
||||
defaultFunc: function defaultFunc(aAccessible, aRoleStr, aStates, aFlags) {
|
||||
return OutputGenerator.objectOutputFunctions._generateBaseOutput.apply(this, arguments);
|
||||
},
|
||||
|
||||
entry: function entry(aAccessible, aRoleStr, aStates, aFlags) {
|
||||
return OutputGenerator.objectOutputFunctions.entry.apply(this, arguments);
|
||||
},
|
||||
|
||||
heading: function heading(aAccessible, aRoleStr, aStates, aFlags) {
|
||||
@ -338,25 +388,15 @@ this.UtteranceGenerator = {
|
||||
application: function application(aAccessible, aRoleStr, aStates, aFlags) {
|
||||
// Don't utter location of applications, it gets tiring.
|
||||
if (aAccessible.name != aAccessible.DOMNode.location)
|
||||
return this.objectUtteranceFunctions.defaultFunc.apply(this,
|
||||
return this.objectOutputFunctions.defaultFunc.apply(this,
|
||||
[aAccessible, aRoleStr, aStates, aFlags]);
|
||||
|
||||
return [];
|
||||
}
|
||||
},
|
||||
|
||||
_addName: function _addName(utterance, aAccessible, aFlags) {
|
||||
let name;
|
||||
if (Utils.getAttributes(aAccessible)['explicit-name'] === 'true' ||
|
||||
(aFlags & INCLUDE_NAME)) {
|
||||
name = aAccessible.name;
|
||||
}
|
||||
|
||||
if (name) {
|
||||
let utteranceOrder = gUtteranceOrder.value || UTTERANCE_DESC_FIRST;
|
||||
utterance[utteranceOrder === UTTERANCE_DESC_FIRST ?
|
||||
'push' : 'unshift'](name);
|
||||
}
|
||||
_getContextStart: function _getContextStart(aContext) {
|
||||
return aContext.newAncestry;
|
||||
},
|
||||
|
||||
_getLocalizedRole: function _getLocalizedRole(aRoleStr) {
|
||||
@ -419,3 +459,118 @@ this.UtteranceGenerator = {
|
||||
return utterance;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
this.BrailleGenerator = {
|
||||
__proto__: OutputGenerator,
|
||||
|
||||
defaultOutputOrder: OUTPUT_DESC_LAST,
|
||||
|
||||
objectOutputFunctions: {
|
||||
defaultFunc: function defaultFunc(aAccessible, aRoleStr, aStates, aFlags) {
|
||||
let braille = OutputGenerator.objectOutputFunctions._generateBaseOutput.apply(this, arguments);
|
||||
|
||||
if (aAccessible.indexInParent === 1 &&
|
||||
aAccessible.parent.role == Ci.nsIAccessibleRole.ROLE_LISTITEM &&
|
||||
aAccessible.previousSibling.role == Ci.nsIAccessibleRole.ROLE_STATICTEXT) {
|
||||
if (aAccessible.parent.parent && aAccessible.parent.parent.DOMNode &&
|
||||
aAccessible.parent.parent.DOMNode.nodeName == 'UL') {
|
||||
braille.unshift('*');
|
||||
} else {
|
||||
braille.unshift(aAccessible.previousSibling.name);
|
||||
}
|
||||
}
|
||||
|
||||
return braille;
|
||||
},
|
||||
|
||||
listitem: function listitem(aAccessible, aRoleStr, aStates, aFlags) {
|
||||
let braille = [];
|
||||
|
||||
this._addName(braille, aAccessible, aFlags);
|
||||
|
||||
return braille;
|
||||
},
|
||||
|
||||
statictext: function statictext(aAccessible, aRoleStr, aStates, aFlags) {
|
||||
// Since we customize the list bullet's output, we add the static
|
||||
// text from the first node in each listitem, so skip it here.
|
||||
if (aAccessible.parent.role == Ci.nsIAccessibleRole.ROLE_LISTITEM) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return this.objectOutputFunctions._useStateNotRole.apply(this, arguments);
|
||||
},
|
||||
|
||||
_useStateNotRole: function _useStateNotRole(aAccessible, aRoleStr, aStates, aFlags) {
|
||||
let braille = [];
|
||||
|
||||
let desc = this._getLocalizedStates(aStates);
|
||||
braille.push(desc.join(' '));
|
||||
|
||||
this._addName(braille, aAccessible, aFlags);
|
||||
|
||||
return braille;
|
||||
},
|
||||
|
||||
checkbutton: function checkbutton(aAccessible, aRoleStr, aStates, aFlags) {
|
||||
return this.objectOutputFunctions._useStateNotRole.apply(this, arguments);
|
||||
},
|
||||
|
||||
radiobutton: function radiobutton(aAccessible, aRoleStr, aStates, aFlags) {
|
||||
return this.objectOutputFunctions._useStateNotRole.apply(this, arguments);
|
||||
},
|
||||
|
||||
togglebutton: function radiobutton(aAccessible, aRoleStr, aStates, aFlags) {
|
||||
return this.objectOutputFunctions._useStateNotRole.apply(this, arguments);
|
||||
},
|
||||
|
||||
entry: function entry(aAccessible, aRoleStr, aStates, aFlags) {
|
||||
return OutputGenerator.objectOutputFunctions.entry.apply(this, arguments);
|
||||
}
|
||||
},
|
||||
|
||||
_getContextStart: function _getContextStart(aContext) {
|
||||
if (aContext.accessible.parent.role == Ci.nsIAccessibleRole.ROLE_LINK) {
|
||||
return [aContext.accessible.parent];
|
||||
}
|
||||
|
||||
return [];
|
||||
},
|
||||
|
||||
_getLocalizedRole: function _getLocalizedRole(aRoleStr) {
|
||||
try {
|
||||
return gStringBundle.GetStringFromName(aRoleStr.replace(' ', '') + 'Abbr');
|
||||
} catch (x) {
|
||||
try {
|
||||
return gStringBundle.GetStringFromName(aRoleStr.replace(' ', ''));
|
||||
} catch (y) {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
_getLocalizedStates: function _getLocalizedStates(aStates) {
|
||||
let stateBraille = [];
|
||||
|
||||
let getCheckedState = function getCheckedState() {
|
||||
let resultMarker = [];
|
||||
let state = aStates.base;
|
||||
let fill = !!(state & Ci.nsIAccessibleStates.STATE_CHECKED) ||
|
||||
!!(state & Ci.nsIAccessibleStates.STATE_PRESSED);
|
||||
|
||||
resultMarker.push('(');
|
||||
resultMarker.push(fill ? 'x' : ' ');
|
||||
resultMarker.push(')');
|
||||
|
||||
return resultMarker.join('');
|
||||
};
|
||||
|
||||
if (aStates.base & Ci.nsIAccessibleStates.STATE_CHECKABLE) {
|
||||
stateBraille.push(getCheckedState());
|
||||
}
|
||||
|
||||
return stateBraille;
|
||||
}
|
||||
|
||||
};
|
@ -9,8 +9,17 @@ const Ci = Components.interfaces;
|
||||
const Cu = Components.utils;
|
||||
const Cr = Components.results;
|
||||
|
||||
Cu.import('resource://gre/modules/accessibility/Utils.jsm');
|
||||
Cu.import('resource://gre/modules/accessibility/UtteranceGenerator.jsm');
|
||||
Cu.import('resource://gre/modules/XPCOMUtils.jsm');
|
||||
XPCOMUtils.defineLazyModuleGetter(this, 'Utils',
|
||||
'resource://gre/modules/accessibility/Utils.jsm');
|
||||
XPCOMUtils.defineLazyModuleGetter(this, 'Logger',
|
||||
'resource://gre/modules/accessibility/Utils.jsm');
|
||||
XPCOMUtils.defineLazyModuleGetter(this, 'PivotContext',
|
||||
'resource://gre/modules/accessibility/Utils.jsm');
|
||||
XPCOMUtils.defineLazyModuleGetter(this, 'UtteranceGenerator',
|
||||
'resource://gre/modules/accessibility/OutputGenerator.jsm');
|
||||
XPCOMUtils.defineLazyModuleGetter(this, 'BrailleGenerator',
|
||||
'resource://gre/modules/accessibility/OutputGenerator.jsm');
|
||||
|
||||
this.EXPORTED_SYMBOLS = ['Presentation'];
|
||||
|
||||
@ -219,6 +228,15 @@ AndroidPresenter.prototype = {
|
||||
|
||||
let state = Utils.getStates(aContext.accessible)[0];
|
||||
|
||||
let brailleText = '';
|
||||
if (Utils.AndroidSdkVersion >= 16) {
|
||||
if (!this._braillePresenter) {
|
||||
this._braillePresenter = new BraillePresenter();
|
||||
}
|
||||
brailleText = this._braillePresenter.pivotChanged(aContext, aReason).
|
||||
details.text;
|
||||
}
|
||||
|
||||
androidEvents.push({eventType: (isExploreByTouch) ?
|
||||
this.ANDROID_VIEW_HOVER_ENTER : focusEventType,
|
||||
text: UtteranceGenerator.genForContext(aContext),
|
||||
@ -227,7 +245,8 @@ AndroidPresenter.prototype = {
|
||||
checkable: !!(state &
|
||||
Ci.nsIAccessibleStates.STATE_CHECKABLE),
|
||||
checked: !!(state &
|
||||
Ci.nsIAccessibleStates.STATE_CHECKED)});
|
||||
Ci.nsIAccessibleStates.STATE_CHECKED),
|
||||
brailleText: brailleText});
|
||||
|
||||
|
||||
return {
|
||||
@ -367,6 +386,29 @@ HapticPresenter.prototype = {
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* A braille presenter
|
||||
*/
|
||||
|
||||
this.BraillePresenter = function BraillePresenter() {};
|
||||
|
||||
BraillePresenter.prototype = {
|
||||
__proto__: Presenter.prototype,
|
||||
|
||||
type: 'Braille',
|
||||
|
||||
pivotChanged: function BraillePresenter_pivotChanged(aContext, aReason) {
|
||||
if (!aContext.accessible) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let text = BrailleGenerator.genForContext(aContext);
|
||||
|
||||
return { type: this.type, details: {text: text.join(' ')} };
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
this.Presentation = {
|
||||
get presenters() {
|
||||
delete this.presenters;
|
||||
|
@ -13,10 +13,11 @@ include $(DEPTH)/config/autoconf.mk
|
||||
|
||||
MOCHITEST_A11Y_FILES =\
|
||||
jsatcommon.js \
|
||||
utterance.js \
|
||||
output.js \
|
||||
test_alive.html \
|
||||
test_explicit_names.html \
|
||||
test_utterance_order.html \
|
||||
test_braille.html \
|
||||
$(NULL)
|
||||
|
||||
include $(topsrcdir)/config/rules.mk
|
||||
|
80
accessible/tests/mochitest/jsat/output.js
Normal file
80
accessible/tests/mochitest/jsat/output.js
Normal file
@ -0,0 +1,80 @@
|
||||
const Cu = Components.utils;
|
||||
const PREF_UTTERANCE_ORDER = "accessibility.accessfu.utterance";
|
||||
|
||||
Cu.import('resource://gre/modules/accessibility/Utils.jsm');
|
||||
Cu.import("resource://gre/modules/accessibility/OutputGenerator.jsm", this);
|
||||
|
||||
/**
|
||||
* Test context output generation.
|
||||
*
|
||||
* @param expected {Array} expected output.
|
||||
* @param aAccOrElmOrID identifier to get an accessible to test.
|
||||
* @param aOldAccOrElmOrID optional identifier to get an accessible relative to
|
||||
* the |aAccOrElmOrID|.
|
||||
* @param aGenerator the output generator to use when generating accessible
|
||||
* output
|
||||
*
|
||||
* Note: if |aOldAccOrElmOrID| is not provided, the |aAccOrElmOrID| must be
|
||||
* scoped to the "root" element in markup.
|
||||
*/
|
||||
function testContextOutput(expected, aAccOrElmOrID, aOldAccOrElmOrID, aGenerator) {
|
||||
aOldAccOrElmOrID = aOldAccOrElmOrID || "root";
|
||||
var accessible = getAccessible(aAccOrElmOrID);
|
||||
var oldAccessible = getAccessible(aOldAccOrElmOrID);
|
||||
var context = new PivotContext(accessible, oldAccessible);
|
||||
var output = aGenerator.genForContext(context);
|
||||
isDeeply(output, expected,
|
||||
"Context output is correct for " + aAccOrElmOrID);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test object output generated array that includes names.
|
||||
* Note: test ignores outputs without the name.
|
||||
*
|
||||
* @param aAccOrElmOrID identifier to get an accessible to test.
|
||||
* @param aGenerator the output generator to use when generating accessible
|
||||
* output
|
||||
*/
|
||||
function testObjectOutput(aAccOrElmOrID, aGenerator) {
|
||||
var accessible = getAccessible(aAccOrElmOrID);
|
||||
var output = aGenerator.genForObject(accessible);
|
||||
var outputOrder;
|
||||
try {
|
||||
outputOrder = SpecialPowers.getIntPref(PREF_UTTERANCE_ORDER);
|
||||
} catch (ex) {
|
||||
// PREF_UTTERANCE_ORDER not set.
|
||||
outputOrder = 0;
|
||||
}
|
||||
var expectedNameIndex = outputOrder === 0 ? output.length - 1 : 0;
|
||||
var nameIndex = output.indexOf(accessible.name);
|
||||
|
||||
if (nameIndex > -1) {
|
||||
ok(output.indexOf(accessible.name) === expectedNameIndex,
|
||||
"Object output is correct for " + aAccOrElmOrID);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test object and context output for an accessible.
|
||||
*
|
||||
* @param expected {Array} expected output.
|
||||
* @param aAccOrElmOrID identifier to get an accessible to test.
|
||||
* @param aOldAccOrElmOrID optional identifier to get an accessible relative to
|
||||
* the |aAccOrElmOrID|.
|
||||
* @param aOutputKind the type of output
|
||||
*/
|
||||
function testOutput(expected, aAccOrElmOrID, aOldAccOrElmOrID, aOutputKind) {
|
||||
var generator;
|
||||
if (aOutputKind === 1) {
|
||||
generator = UtteranceGenerator;
|
||||
} else {
|
||||
generator = BrailleGenerator;
|
||||
}
|
||||
testContextOutput(expected, aAccOrElmOrID, aOldAccOrElmOrID, generator);
|
||||
// Just need to test object output for individual
|
||||
// accOrElmOrID.
|
||||
if (aOldAccOrElmOrID) {
|
||||
return;
|
||||
}
|
||||
testObjectOutput(aAccOrElmOrID, generator);
|
||||
}
|
114
accessible/tests/mochitest/jsat/test_braille.html
Normal file
114
accessible/tests/mochitest/jsat/test_braille.html
Normal file
@ -0,0 +1,114 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<!--
|
||||
https://bugzilla.mozilla.org/show_bug.cgi?id=876475
|
||||
-->
|
||||
<head>
|
||||
<title>[AccessFu] braille generation test</title>
|
||||
<meta charset="utf-8">
|
||||
<link rel="stylesheet" type="text/css"
|
||||
href="chrome://mochikit/content/tests/SimpleTest/test.css" />
|
||||
<script type="application/javascript"
|
||||
src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<script type="application/javascript"
|
||||
src="../common.js"></script>
|
||||
<script type="application/javascript"
|
||||
src="./output.js"></script>
|
||||
<script type="application/javascript">
|
||||
|
||||
function doTest() {
|
||||
// Test the following accOrElmOrID (with optional old accOrElmOrID).
|
||||
// Note: each accOrElmOrID entry maps to a unique object braille
|
||||
// generator function within the BrailleGenerator.
|
||||
var tests = [{
|
||||
accOrElmOrID: "link",
|
||||
expected: [["lnk", "Link"], ["Link", "lnk"]]
|
||||
},{
|
||||
accOrElmOrID: "button",
|
||||
expected: [["btn", "I am a button"], ["I am a button", "btn"]]
|
||||
},{
|
||||
accOrElmOrID: "password_input",
|
||||
expected: [["passwdtxt", "Secret Password"], ["Secret Password", "passwdtxt"]]
|
||||
},{
|
||||
accOrElmOrID: "checkbox_unchecked",
|
||||
expected: [["( )", "checkboxtext"], ["checkboxtext", "( )"]]
|
||||
},{
|
||||
accOrElmOrID: "checkbox_checked",
|
||||
expected: [["(x)", "some more checkbox text"], ["some more checkbox text", "(x)"]]
|
||||
},{
|
||||
accOrElmOrID: "radio_unselected",
|
||||
expected: [["( )", "any old radio button"], ["any old radio button", "( )"]]
|
||||
},{
|
||||
accOrElmOrID: "radio_selected",
|
||||
expected: [["(x)", "a unique radio button"], ["a unique radio button", "(x)"]]
|
||||
},{
|
||||
accOrElmOrID: "togglebutton_notpressed",
|
||||
expected: [["( )", "I ain't pressed"], ["I ain't pressed", "( )"]]
|
||||
},{
|
||||
accOrElmOrID: "togglebutton_pressed",
|
||||
expected: [["(x)", "I am pressed!"], ["I am pressed!", "(x)"]]
|
||||
},{
|
||||
accOrElmOrID: "ul_li_one",
|
||||
expected: [["*", "ul item 1"], ["*", "ul item 1"]]
|
||||
},{
|
||||
accOrElmOrID: "ol_li_one",
|
||||
expected: [["1.", "ol item 1"], ["1.", "ol item 1"]]
|
||||
},{
|
||||
accOrElmOrID: "textarea",
|
||||
expected: [["txtarea", "Here lies treasure."], ["Here lies treasure.", "txtarea"]]
|
||||
}];
|
||||
|
||||
// Test all possible braille order preference values.
|
||||
tests.forEach(function run(test) {
|
||||
var brailleOrderValues = [0, 1];
|
||||
brailleOrderValues.forEach(
|
||||
function testBrailleOrder(brailleOrder) {
|
||||
SpecialPowers.setIntPref(PREF_UTTERANCE_ORDER, brailleOrder);
|
||||
var expected = test.expected[brailleOrder];
|
||||
testOutput(expected, test.accOrElmOrID, test.oldAccOrElmOrID, 2);
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
// If there was an original utterance order preference, revert to it.
|
||||
SpecialPowers.clearUserPref(PREF_UTTERANCE_ORDER);
|
||||
SimpleTest.finish();
|
||||
}
|
||||
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
addA11yLoadEvent(doTest);
|
||||
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root">
|
||||
<p id="display"></p>
|
||||
<div id="content" style="display: none"></div>
|
||||
<pre id="test"></pre>
|
||||
<a href="example.com" id="link">Link</a>
|
||||
<button id="button">I am a button</button>
|
||||
<label for="password_input">Secret Password</label><input id="password_input" type="password"></input>
|
||||
<label for="checkbox_unchecked">checkboxtext</label><input id="checkbox_unchecked" type="checkbox"></input>
|
||||
<label for="checkbox_checked">some more checkbox text</label><input id="checkbox_checked" type="checkbox" checked></input>
|
||||
<label for="radio_unselected">any old radio button</label><input id="radio_unselected" type="radio"></input>
|
||||
<label for="radio_selected">a unique radio button</label><input id="radio_selected" type="radio" checked></input>
|
||||
<div id="togglebutton_notpressed" aria-pressed="false" role="button" tabindex="-1">I ain't pressed</div>
|
||||
<div id="togglebutton_pressed" aria-pressed="true" role="button" tabindex="-1">I am pressed!</div>
|
||||
<ol id="ordered_list">
|
||||
<li id="ol_li_one">ol item 1</li>
|
||||
<li id="ol_li_two">ol item 2</li>
|
||||
<li id="ol_li_three">ol item 3</li>
|
||||
<li id="ol_li_three">ol item 4</li>
|
||||
</ol>
|
||||
<ul id="unordered_list">
|
||||
<li id="ul_li_one">ul item 1</li>
|
||||
<li id="ul_li_two">ul item 2</li>
|
||||
<li id="ul_li_three">ul item 3</li>
|
||||
<li id="ul_li_three">ul item 4</li>
|
||||
</ul>
|
||||
<textarea id="textarea" cols="80" rows="5">
|
||||
Here lies treasure.
|
||||
</textarea>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
@ -9,7 +9,7 @@
|
||||
<script type="application/javascript"
|
||||
src="../common.js"></script>
|
||||
<script type="application/javascript"
|
||||
src="utterance.js"></script>
|
||||
src="output.js"></script>
|
||||
<script type="application/javascript">
|
||||
|
||||
function doTest() {
|
||||
@ -87,11 +87,14 @@
|
||||
"Plums"]
|
||||
}];
|
||||
|
||||
SpecialPowers.setIntPref(PREF_UTTERANCE_ORDER, 0);
|
||||
|
||||
// Test various explicit names vs the utterance generated from subtrees.
|
||||
tests.forEach(function run(test) {
|
||||
testUtterance(test.expected, test.accOrElmOrID, test.oldAccOrElmOrID);
|
||||
testOutput(test.expected, test.accOrElmOrID, test.oldAccOrElmOrID, 1);
|
||||
});
|
||||
|
||||
SpecialPowers.clearUserPref(PREF_UTTERANCE_ORDER);
|
||||
SimpleTest.finish();
|
||||
}
|
||||
|
||||
@ -163,4 +166,4 @@
|
||||
</ul>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
|
@ -13,7 +13,7 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=753984
|
||||
<script type="application/javascript"
|
||||
src="../common.js"></script>
|
||||
<script type="application/javascript"
|
||||
src="./utterance.js"></script>
|
||||
src="./output.js"></script>
|
||||
<script type="application/javascript">
|
||||
|
||||
function doTest() {
|
||||
@ -120,7 +120,7 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=753984
|
||||
function testUtteranceOrder(utteranceOrder) {
|
||||
SpecialPowers.setIntPref(PREF_UTTERANCE_ORDER, utteranceOrder);
|
||||
var expected = test.expected[utteranceOrder];
|
||||
testUtterance(expected, test.accOrElmOrID, test.oldAccOrElmOrID);
|
||||
testOutput(expected, test.accOrElmOrID, test.oldAccOrElmOrID, 1);
|
||||
}
|
||||
);
|
||||
});
|
||||
|
@ -1,70 +0,0 @@
|
||||
const Cu = Components.utils;
|
||||
const PREF_UTTERANCE_ORDER = "accessibility.accessfu.utterance";
|
||||
|
||||
Cu.import('resource://gre/modules/accessibility/Utils.jsm');
|
||||
Cu.import("resource://gre/modules/accessibility/UtteranceGenerator.jsm",
|
||||
this);
|
||||
|
||||
/**
|
||||
* Test context utterance generation.
|
||||
*
|
||||
* @param expected {Array} expected utterance.
|
||||
* @param aAccOrElmOrID identifier to get an accessible to test.
|
||||
* @param aOldAccOrElmOrID optional identifier to get an accessible relative to
|
||||
* the |aAccOrElmOrID|.
|
||||
*
|
||||
* Note: if |aOldAccOrElmOrID| is not provided, the |aAccOrElmOrID| must be
|
||||
* scoped to the "root" element in markup.
|
||||
*/
|
||||
function testContextUtterance(expected, aAccOrElmOrID, aOldAccOrElmOrID) {
|
||||
aOldAccOrElmOrID = aOldAccOrElmOrID || "root";
|
||||
var accessible = getAccessible(aAccOrElmOrID);
|
||||
var oldAccessible = getAccessible(aOldAccOrElmOrID);
|
||||
var context = new PivotContext(accessible, oldAccessible);
|
||||
var utterance = UtteranceGenerator.genForContext(context);
|
||||
isDeeply(utterance, expected,
|
||||
"Context utterance is correct for " + aAccOrElmOrID);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test object utterance generated array that includes names.
|
||||
* Note: test ignores utterances without the name.
|
||||
*
|
||||
* @param aAccOrElmOrID identifier to get an accessible to test.
|
||||
*/
|
||||
function testObjectUtterance(aAccOrElmOrID) {
|
||||
var accessible = getAccessible(aAccOrElmOrID);
|
||||
var utterance = UtteranceGenerator.genForObject(accessible);
|
||||
var utteranceOrder;
|
||||
try {
|
||||
utteranceOrder = SpecialPowers.getIntPref(PREF_UTTERANCE_ORDER);
|
||||
} catch (ex) {
|
||||
// PREF_UTTERANCE_ORDER not set.
|
||||
utteranceOrder = 0;
|
||||
}
|
||||
var expectedNameIndex = utteranceOrder === 0 ? utterance.length - 1 : 0;
|
||||
var nameIndex = utterance.indexOf(accessible.name);
|
||||
|
||||
if (nameIndex > -1) {
|
||||
ok(utterance.indexOf(accessible.name) === expectedNameIndex,
|
||||
"Object utterance is correct for " + aAccOrElmOrID);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test object and context utterance for an accessible.
|
||||
*
|
||||
* @param expected {Array} expected utterance.
|
||||
* @param aAccOrElmOrID identifier to get an accessible to test.
|
||||
* @param aOldAccOrElmOrID optional identifier to get an accessible relative to
|
||||
* the |aAccOrElmOrID|.
|
||||
*/
|
||||
function testUtterance(expected, aAccOrElmOrID, aOldAccOrElmOrID) {
|
||||
testContextUtterance(expected, aAccOrElmOrID, aOldAccOrElmOrID);
|
||||
// Just need to test object utterance for individual
|
||||
// accOrElmOrID.
|
||||
if (aOldAccOrElmOrID) {
|
||||
return;
|
||||
}
|
||||
testObjectUtterance(aAccOrElmOrID);
|
||||
}
|
@ -132,4 +132,12 @@ quicknav_PageTab = Page tabs
|
||||
quicknav_RadioButton = Radio buttons
|
||||
quicknav_Separator = Separators
|
||||
quicknav_Table = Tables
|
||||
quicknav_Checkbox = Check boxes
|
||||
quicknav_Checkbox = Check boxes
|
||||
|
||||
# Shortened role names for braille
|
||||
linkAbbr = lnk
|
||||
pushbuttonAbbr = btn
|
||||
passwordtextAbbr = passwdtxt
|
||||
imagemapAbbr = imgmap
|
||||
figureAbbr = fig
|
||||
textareaAbbr = txtarea
|
||||
|
@ -25,6 +25,9 @@ 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;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
@ -40,6 +43,8 @@ public class GeckoAccessibility {
|
||||
private static JSONObject sEventMessage = null;
|
||||
private static AccessibilityNodeInfo sVirtualCursorNode = null;
|
||||
|
||||
private static SelfBrailleClient sSelfBrailleClient = null;
|
||||
|
||||
private static final HashSet<String> sServiceWhitelist =
|
||||
new HashSet<String>(Arrays.asList(new String[] {
|
||||
"com.google.android.marvin.talkback.TalkBackService", // Google Talkback screen reader
|
||||
@ -66,6 +71,10 @@ public class GeckoAccessibility {
|
||||
if (sEnabled)
|
||||
break;
|
||||
}
|
||||
if (sEnabled && sSelfBrailleClient == null &&
|
||||
Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
|
||||
sSelfBrailleClient = new SelfBrailleClient(GeckoAppShell.getContext(), false);
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
@ -188,6 +197,11 @@ public class GeckoAccessibility {
|
||||
sVirtualCursorNode.setBoundsInScreen(screenBounds);
|
||||
}
|
||||
|
||||
final String brailleText = message.optString("brailleText");
|
||||
if (!brailleText.isEmpty()) {
|
||||
sendBrailleText(view, brailleText);
|
||||
}
|
||||
|
||||
ThreadUtils.postToUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
@ -217,6 +231,16 @@ public class GeckoAccessibility {
|
||||
}
|
||||
}
|
||||
|
||||
private static void sendBrailleText(final View view, final String text) {
|
||||
AccessibilityNodeInfo info = AccessibilityNodeInfo.obtain(view, VIRTUAL_CURSOR_POSITION);
|
||||
WriteData data = WriteData.forInfo(info);
|
||||
data.setText(text);
|
||||
// Set the focus blink
|
||||
data.setSelectionStart(0);
|
||||
data.setSelectionEnd(0);
|
||||
sSelfBrailleClient.write(data);
|
||||
}
|
||||
|
||||
public static void setDelegate(LayerView layerview) {
|
||||
// Only use this delegate in Jelly Bean.
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
|
||||
@ -282,7 +306,6 @@ public class GeckoAccessibility {
|
||||
info.addAction(AccessibilityNodeInfo.ACTION_CLICK);
|
||||
break;
|
||||
}
|
||||
|
||||
return info;
|
||||
}
|
||||
|
||||
@ -309,6 +332,11 @@ public class GeckoAccessibility {
|
||||
GeckoAppShell.
|
||||
sendEventToGecko(GeckoEvent.createBroadcastEvent("Accessibility:ActivateObject", null));
|
||||
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
|
||||
GeckoAppShell.
|
||||
sendEventToGecko(GeckoEvent.createBroadcastEvent("Accessibility:ActivateObject", null));
|
||||
return true;
|
||||
}
|
||||
return host.performAccessibilityAction(action, arguments);
|
||||
}
|
||||
|
@ -44,6 +44,10 @@ UTIL_JAVA_FILES := \
|
||||
util/UiAsyncTask.java \
|
||||
$(NULL)
|
||||
|
||||
AIDL_AUTOGEN_FILES = \
|
||||
braille/com/googlecode/eyesfree/braille/selfbraille/ISelfBrailleService.java \
|
||||
$(NULL)
|
||||
|
||||
FENNEC_JAVA_FILES = \
|
||||
ANRReporter.java \
|
||||
ActivityHandlerHelper.java \
|
||||
@ -173,6 +177,8 @@ FENNEC_JAVA_FILES = \
|
||||
WebAppAllocator.java \
|
||||
WebAppImpl.java \
|
||||
ZoomConstraints.java \
|
||||
braille/com/googlecode/eyesfree/braille/selfbraille/SelfBrailleClient.java \
|
||||
braille/com/googlecode/eyesfree/braille/selfbraille/WriteData.java \
|
||||
db/BrowserContract.java \
|
||||
db/BrowserProvider.java \
|
||||
db/FormHistoryProvider.java \
|
||||
@ -1172,10 +1178,10 @@ classes.dex: $(ALL_JARS)
|
||||
@echo "DX classes.dex"
|
||||
$(DX) --dex --output=classes.dex jars $(ANDROID_COMPAT_LIB)
|
||||
|
||||
jars/gecko-browser.jar: jars/gecko-mozglue.jar jars/gecko-util.jar jars/sync-thirdparty.jar jars/websockets.jar $(addprefix $(srcdir)/,$(FENNEC_JAVA_FILES)) $(FENNEC_PP_JAVA_FILES) $(FENNEC_PP_JAVA_VIEW_FILES) $(addprefix $(srcdir)/,$(SYNC_JAVA_FILES)) $(SYNC_PP_JAVA_FILES) R.java
|
||||
jars/gecko-browser.jar: jars/gecko-mozglue.jar jars/gecko-util.jar jars/sync-thirdparty.jar jars/websockets.jar $(addprefix $(srcdir)/,$(FENNEC_JAVA_FILES)) $(FENNEC_PP_JAVA_FILES) $(FENNEC_PP_JAVA_VIEW_FILES) $(addprefix $(srcdir)/,$(SYNC_JAVA_FILES)) $(SYNC_PP_JAVA_FILES) $(AIDL_AUTOGEN_FILES) R.java
|
||||
@echo "JAR gecko-browser.jar"
|
||||
$(NSINSTALL) -D classes/gecko-browser
|
||||
$(JAVAC) $(JAVAC_FLAGS) -Xlint:all,-deprecation,-fallthrough -d classes/gecko-browser -classpath "jars/gecko-mozglue.jar:jars/gecko-util.jar:jars/sync-thirdparty.jar:jars/websockets.jar" $(addprefix $(srcdir)/,$(FENNEC_JAVA_FILES)) $(FENNEC_PP_JAVA_FILES) $(FENNEC_PP_JAVA_VIEW_FILES) $(addprefix $(srcdir)/,$(SYNC_JAVA_FILES)) $(SYNC_PP_JAVA_FILES) R.java
|
||||
$(JAVAC) $(JAVAC_FLAGS) -Xlint:all,-deprecation,-fallthrough -d classes/gecko-browser -classpath "jars/gecko-mozglue.jar:jars/gecko-util.jar:jars/sync-thirdparty.jar:jars/websockets.jar" $(addprefix $(srcdir)/,$(FENNEC_JAVA_FILES)) $(FENNEC_PP_JAVA_FILES) $(FENNEC_PP_JAVA_VIEW_FILES) $(addprefix $(srcdir)/,$(SYNC_JAVA_FILES)) $(SYNC_PP_JAVA_FILES) $(addprefix $(srcdir)/,$(AIDL_AUTOGEN_FILES)) R.java
|
||||
$(JAR) cMf jars/gecko-browser.jar -C classes/gecko-browser .
|
||||
|
||||
jars/gecko-mozglue.jar: $(addprefix $(srcdir)/,$(MOZGLUE_JAVA_FILES)) $(MOZGLUE_PP_JAVA_FILES) jars
|
||||
@ -1214,6 +1220,9 @@ jars:
|
||||
@echo "MKDIR jars"
|
||||
$(NSINSTALL) -D jars
|
||||
|
||||
$(AIDL_AUTOGEN_FILES): %.java: %.aidl
|
||||
$(ANDROID_PLATFORM_TOOLS)/aidl -I$(srcdir)/braille $<
|
||||
|
||||
CLASSES_WITH_JNI= \
|
||||
org.mozilla.gecko.GeckoAppShell \
|
||||
org.mozilla.gecko.GeckoJavaSampler \
|
||||
|
@ -0,0 +1,28 @@
|
||||
/*
|
||||
* 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 com.googlecode.eyesfree.braille.selfbraille.WriteData;
|
||||
|
||||
/**
|
||||
* Interface for a client to control braille output for a part of the
|
||||
* accessibility node tree.
|
||||
*/
|
||||
interface ISelfBrailleService {
|
||||
void write(IBinder clientToken, in WriteData writeData);
|
||||
oneway void disconnect(IBinder clientToken);
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
parcelable WriteData;
|
@ -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