Commit f3c2f99d authored by Anastasia Helfinstein's avatar Anastasia Helfinstein Committed by Commit Bot

[Switch Access] Improve interactions with Virtual Keyboard.

Previously, Switch Access would get stuck in the virtual keyboard when
it was hidden, and would not jump to it when it was activated by any
method other than the Switch Access menu.

This change listens to events corresponding to the keyboard being shown
and hidden, and reacts appropriately.

Bug: 996899
Change-Id: Ic6c58c8f66afdbf7422b74a8dc86b0d64c700607
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2062584
Commit-Queue: Anastasia Helfinstein <anastasi@google.com>
Reviewed-by: default avatarDavid Tseng <dtseng@chromium.org>
Cr-Commit-Position: refs/heads/master@{#744039}
parent d98590d5
...@@ -571,11 +571,6 @@ class MenuManager { ...@@ -571,11 +571,6 @@ class MenuManager {
this.exit(); this.exit();
return; return;
} }
if (action === SAConstants.MenuAction.OPEN_KEYBOARD) {
this.navigationManager_.enterKeyboard();
this.exit();
return;
}
// Handle global actions. // Handle global actions.
if (action === SAConstants.MenuAction.SETTINGS) { if (action === SAConstants.MenuAction.SETTINGS) {
......
...@@ -41,6 +41,18 @@ class NavigationManager { ...@@ -41,6 +41,18 @@ class NavigationManager {
// =============== Static Methods ============== // =============== Static Methods ==============
/**
* Puts focus on the virtual keyboard, if the current node is a text input.
* TODO(crbug/946190): Handle the case where the user has not enabled the
* onscreen keyboard.
*/
static enterKeyboard() {
const navigator = NavigationManager.instance;
const keyboard = KeyboardRootNode.buildTree();
navigator.jumpTo_(keyboard);
navigator.node_.automationNode.focus();
}
/** /**
* Open the Switch Access menu for the currently highlighted node. If there * Open the Switch Access menu for the currently highlighted node. If there
* are not enough actions available to trigger the menu, the current element * are not enough actions available to trigger the menu, the current element
...@@ -56,6 +68,28 @@ class NavigationManager { ...@@ -56,6 +68,28 @@ class NavigationManager {
} }
} }
static exitKeyboard() {
const navigator = NavigationManager.instance;
let foundKeyboard = navigator.group_ instanceof KeyboardRootNode;
for (const group of navigator.groupStack_) {
foundKeyboard |= group instanceof KeyboardRootNode;
}
// If we are not in the keyboard, do nothing.
if (!foundKeyboard) {
return;
}
while (navigator.groupStack_.length > 0) {
if (navigator.group_ instanceof KeyboardRootNode) {
break;
}
navigator.exitGroup_();
}
navigator.exitGroup_();
NavigationManager.moveToValidNode();
}
/** /**
* Forces the current node to be |node|. * Forces the current node to be |node|.
* Should only be called by subclasses of SARootNode and * Should only be called by subclasses of SARootNode and
...@@ -180,6 +214,14 @@ class NavigationManager { ...@@ -180,6 +214,14 @@ class NavigationManager {
// =============== Instance Methods ============== // =============== Instance Methods ==============
/**
* Returns the desktop automation node object.
* @return {!chrome.automation.AutomationNode}
*/
get desktopNode() {
return this.desktop_;
}
/** /**
* Enters |this.node_|. * Enters |this.node_|.
*/ */
...@@ -197,17 +239,6 @@ class NavigationManager { ...@@ -197,17 +239,6 @@ class NavigationManager {
} }
} }
/**
* Puts focus on the virtual keyboard, if the current node is a text input.
* TODO(cbug/946190): Handle the case where the user has not enabled the
* onscreen keyboard.
*/
enterKeyboard() {
const keyboard = KeyboardRootNode.buildTree(this.desktop_);
this.node_.performAction(SAConstants.MenuAction.OPEN_KEYBOARD);
this.jumpTo_(keyboard);
}
/** /**
* Selects the current node. * Selects the current node.
*/ */
...@@ -227,7 +258,6 @@ class NavigationManager { ...@@ -227,7 +258,6 @@ class NavigationManager {
SwitchAccessMetrics.recordMenuAction( SwitchAccessMetrics.recordMenuAction(
SAConstants.MenuAction.OPEN_KEYBOARD); SAConstants.MenuAction.OPEN_KEYBOARD);
this.node_.performAction(SAConstants.MenuAction.OPEN_KEYBOARD); this.node_.performAction(SAConstants.MenuAction.OPEN_KEYBOARD);
this.enterKeyboard();
return; return;
} }
......
...@@ -39,7 +39,7 @@ class EditableTextNode extends NodeWrapper { ...@@ -39,7 +39,7 @@ class EditableTextNode extends NodeWrapper {
performAction(action) { performAction(action) {
switch (action) { switch (action) {
case SAConstants.MenuAction.OPEN_KEYBOARD: case SAConstants.MenuAction.OPEN_KEYBOARD:
this.automationNode.focus(); NavigationManager.enterKeyboard();
return SAConstants.ActionResponse.CLOSE_MENU; return SAConstants.ActionResponse.CLOSE_MENU;
case SAConstants.MenuAction.DICTATION: case SAConstants.MenuAction.DICTATION:
chrome.accessibilityPrivate.toggleDictation(); chrome.accessibilityPrivate.toggleDictation();
......
...@@ -90,18 +90,32 @@ class KeyboardNode extends NodeWrapper { ...@@ -90,18 +90,32 @@ class KeyboardNode extends NodeWrapper {
*/ */
class KeyboardRootNode extends RootNodeWrapper { class KeyboardRootNode extends RootNodeWrapper {
/** /**
* @param {!chrome.automation.AutomationNode} keyboard * @param {!chrome.automation.AutomationNode} groupNode
* @private * @private
*/ */
constructor(keyboard) { constructor(groupNode) {
super(keyboard); super(groupNode);
} }
// ================= General methods ================= // ================= General methods =================
/** @override */
isValidGroup() {
// To ensure we can find the keyboard root node to appropriately respond to
// visibility changes, never mark it as invalid.
return true;
}
/** @override */ /** @override */
onExit() { onExit() {
chrome.accessibilityPrivate.setVirtualKeyboardVisible(false); // If the keyboard is currently visible, ignore the corresponding
// state change.
if (KeyboardRootNode.isVisible_) {
KeyboardRootNode.explicitStateChange_ = true;
chrome.accessibilityPrivate.setVirtualKeyboardVisible(false);
}
AutoScanManager.setInKeyboard(false); AutoScanManager.setInKeyboard(false);
} }
...@@ -109,20 +123,23 @@ class KeyboardRootNode extends RootNodeWrapper { ...@@ -109,20 +123,23 @@ class KeyboardRootNode extends RootNodeWrapper {
/** /**
* Creates the tree structure for the system menu. * Creates the tree structure for the system menu.
* @param {!chrome.automation.AutomationNode} desktop
* @return {!KeyboardRootNode} * @return {!KeyboardRootNode}
*/ */
static buildTree(desktop) { static buildTree() {
KeyboardRootNode.loadKeyboard_(); KeyboardRootNode.loadKeyboard_();
AutoScanManager.setInKeyboard(true); AutoScanManager.setInKeyboard(true);
const keyboardContainer = if (!KeyboardRootNode.keyboardObject_) {
desktop.find({role: chrome.automation.RoleType.KEYBOARD}); throw SwitchAccess.error(
SAConstants.ErrorType.MISSING_KEYBOARD,
'Could not find keyboard in the automation tree');
}
const keyboard = const keyboard =
new AutomationTreeWalker(keyboardContainer, constants.Dir.FORWARD, { new AutomationTreeWalker(
visit: (node) => SwitchAccessPredicate.isGroup(node, null), KeyboardRootNode.keyboardObject_, constants.Dir.FORWARD, {
root: (node) => node === keyboardContainer visit: (node) => SwitchAccessPredicate.isGroup(node, null),
}) root: (node) => node === KeyboardRootNode.keyboardObject_
})
.next() .next()
.node; .node;
...@@ -131,11 +148,69 @@ class KeyboardRootNode extends RootNodeWrapper { ...@@ -131,11 +148,69 @@ class KeyboardRootNode extends RootNodeWrapper {
return root; return root;
} }
/**
* Start listening for keyboard open/closed.
*/
static startWatchingVisibility() {
KeyboardRootNode.isVisible_ =
SwitchAccessPredicate.isVisible(KeyboardRootNode.keyboardObject_);
KeyboardRootNode.keyboardObject_.addEventListener(
chrome.automation.EventType.ARIA_ATTRIBUTE_CHANGED,
KeyboardRootNode.checkVisibilityChanged_, false /* capture */);
}
// ================= Private static methods =================
/**
* @param {chrome.automation.AutomationEvent} event
* @private
*/
static checkVisibilityChanged_(event) {
const currentlyVisible =
SwitchAccessPredicate.isVisible(KeyboardRootNode.keyboardObject_);
if (currentlyVisible === KeyboardRootNode.isVisible_) {
return;
}
KeyboardRootNode.isVisible_ = currentlyVisible;
if (KeyboardRootNode.explicitStateChange_) {
// When the user has explicitly shown / hidden the keyboard, do not
// enter / exit the keyboard again to avoid looping / double-calls.
KeyboardRootNode.explicitStateChange_ = false;
return;
}
if (KeyboardRootNode.isVisible_) {
NavigationManager.enterKeyboard();
} else {
NavigationManager.exitKeyboard();
}
}
/**
* @return {chrome.automation.AutomationNode}
* @private
*/
static get keyboardObject_() {
if (!this.object_ || !this.object_.role) {
this.object_ = NavigationManager.instance.desktopNode.find(
{role: chrome.automation.RoleType.KEYBOARD});
}
return this.object_;
}
/** /**
* Loads the keyboard. * Loads the keyboard.
* @private * @private
*/ */
static loadKeyboard_() { static loadKeyboard_() {
if (KeyboardRootNode.isVisible_) {
return;
}
KeyboardRootNode.explicitStateChange_ = true;
chrome.accessibilityPrivate.setVirtualKeyboardVisible(true); chrome.accessibilityPrivate.setVirtualKeyboardVisible(true);
} }
} }
...@@ -138,6 +138,7 @@ class NodeWrapper extends SAChildNode { ...@@ -138,6 +138,7 @@ class NodeWrapper extends SAChildNode {
switch (action) { switch (action) {
case SAConstants.MenuAction.SELECT: case SAConstants.MenuAction.SELECT:
this.baseNode_.doDefault(); this.baseNode_.doDefault();
return SAConstants.ActionResponse.CLOSE_MENU;
case SAConstants.MenuAction.SCROLL_DOWN: case SAConstants.MenuAction.SCROLL_DOWN:
ancestor = this.getScrollableAncestor_(); ancestor = this.getScrollableAncestor_();
if (ancestor.scrollable) { if (ancestor.scrollable) {
......
...@@ -17,6 +17,7 @@ class SwitchAccess { ...@@ -17,6 +17,7 @@ class SwitchAccess {
BackButtonNode.findAutomationNode(desktop); BackButtonNode.findAutomationNode(desktop);
Commands.initialize(); Commands.initialize();
KeyboardRootNode.startWatchingVisibility();
SwitchAccessPreferences.initialize(); SwitchAccessPreferences.initialize();
}); });
} }
......
...@@ -69,6 +69,7 @@ const SAConstants = { ...@@ -69,6 +69,7 @@ const SAConstants = {
NO_CHILDREN: 7, NO_CHILDREN: 7,
MALFORMED_DESKTOP: 8, MALFORMED_DESKTOP: 8,
MISSING_LOCATION: 9, MISSING_LOCATION: 9,
MISSING_KEYBOARD: 10,
}, },
/** /**
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment