/*
* Copyright (C) 2015 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.android.talkback;
import static org.junit.Assert.assertNotEquals;
import android.annotation.TargetApi;
import android.app.Activity;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Build;
import android.os.Bundle;
import android.preference.DialogPreference;
import android.preference.PreferenceFragment;
import android.preference.PreferenceManager;
import android.test.suitebuilder.annotation.MediumTest;
import android.util.LayoutDirection;
import android.view.KeyEvent;
import android.widget.TextView;
import com.android.talkback.keyboard.DefaultKeyComboModel;
import com.android.talkback.keyboard.KeyComboModel;
import com.android.talkback.keyboard.KeyComboModelApp;
import com.android.talkback.keyboard.KeyComboPersister;
import com.android.utils.SharedPreferencesUtils;
import com.google.android.marvin.talkback.TalkBackService;
import com.googlecode.eyesfree.testing.TalkBackInstrumentationTestCase;
// TODO: Make this test not to run below this API level.
@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2)
public class KeyboardShortcutDialogPreferenceTest extends TalkBackInstrumentationTestCase {
private Activity mPreferencesActivity = null;
private class KeyAcceptedTestData {
public boolean escape;
public boolean enter;
public boolean backspace;
public boolean A;
}
@Override
protected void tearDown() throws Exception {
if (mPreferencesActivity != null) {
mPreferencesActivity.finish();
mPreferencesActivity = null;
}
SharedPreferences pref = SharedPreferencesUtils.getSharedPreferences(getActivity());
pref.edit().clear().commit();
super.tearDown();
}
@MediumTest
public void testKeyAccepted() throws Throwable {
setContentView(R.layout.keyboard_shortcut_dialog);
final KeyboardShortcutDialogPreference pref = new KeyboardShortcutDialogPreference(
getService(), null);
final KeyAcceptedTestData data = new KeyAcceptedTestData();
getInstrumentation().runOnMainSync(new Runnable() {
@Override
public void run() {
pref.onBindDialogView(getViewForId(android.R.id.content));
KeyEvent escape = new KeyEvent(0, 0, KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_ESCAPE,
0);
KeyEvent enter = new KeyEvent(0, 0, KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_ENTER,
0);
KeyEvent backspace = new KeyEvent(0, 0, KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DEL,
0);
KeyEvent A = new KeyEvent(0, 0, KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_A,
0);
synchronized (data) {
data.escape = pref.onKey(null, KeyEvent.KEYCODE_ESCAPE, escape);
data.enter = pref.onKey(null, KeyEvent.KEYCODE_ENTER, enter);
data.backspace = pref.onKey(null, KeyEvent.KEYCODE_DEL, backspace);
data.A = pref.onKey(null, KeyEvent.KEYCODE_A, A);
}
}
});
getInstrumentation().waitForIdleSync();
synchronized (data) {
assertFalse(data.escape); // Escape should be trapped and used to close the dialog.
assertFalse(data.enter); // Enter should be trapped and used to close the dialog.
assertTrue(data.backspace); // Backspace should be accepted but clears the shortcut.
assertTrue(data.A); // A should be accepted.
}
}
private class BackspaceUnassignsShortcutTestData {
public String textAfterA;
public String textAfterBackspace;
}
@MediumTest
public void testBackspaceUnassignsShortcut() {
setContentView(R.layout.keyboard_shortcut_dialog);
final KeyboardShortcutDialogPreference pref = new KeyboardShortcutDialogPreference(
getService(), null);
final BackspaceUnassignsShortcutTestData data = new BackspaceUnassignsShortcutTestData();
getInstrumentation().runOnMainSync(new Runnable() {
@Override
public void run() {
pref.onBindDialogView(getViewForId(android.R.id.content));
KeyEvent backspace = new KeyEvent(0, 0, KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DEL,
0);
KeyEvent A = new KeyEvent(0, 0, KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_A,
0);
TextView assignedCombo = (TextView) getViewForId(R.id.assigned_combination);
synchronized (data) {
pref.onKey(null, KeyEvent.KEYCODE_A, A);
data.textAfterA = assignedCombo.getText().toString();
pref.onKey(null, KeyEvent.KEYCODE_DEL, backspace);
data.textAfterBackspace = assignedCombo.getText().toString();
}
}
});
getInstrumentation().waitForIdleSync();
final String unassigned = KeyComboManager.create(getService())
.getKeyComboStringRepresentation(KeyComboModel.KEY_COMBO_CODE_UNASSIGNED);
synchronized (data) {
assertNotEquals(unassigned, data.textAfterA); // Label shouldn't be "unassigned".
assertEquals(unassigned, data.textAfterBackspace); // Label should be "unassigned".
}
}
public void testAssignKeyComboWithClassicKeymap() {
setKeymapToClassicKeymap();
assertAssignKeyCombo(
KeyComboManager.getKeyComboCode(KeyEvent.META_ALT_ON | KeyEvent.META_SHIFT_ON,
KeyEvent.KEYCODE_DPAD_RIGHT),
KeyComboManager.getKeyComboCode(KeyEvent.META_ALT_ON | KeyEvent.META_SHIFT_ON,
KeyEvent.KEYCODE_X),
KeyComboManager.getKeyComboCode(KeyEvent.META_ALT_ON | KeyEvent.META_SHIFT_ON,
KeyEvent.KEYCODE_X),
new KeyComboPersister(getActivity(), null /* no prefix */));
}
public void testAssignKeyComboWithDefaultKeymap() {
setKeymapToDefaultKeymap();
assertAssignKeyCombo(
KeyComboManager.getKeyComboCode(KeyEvent.META_ALT_ON, KeyEvent.KEYCODE_DPAD_RIGHT),
KeyComboManager.getKeyComboCode(KeyEvent.META_ALT_ON, KeyEvent.KEYCODE_A),
KeyComboManager.getKeyComboCode(KeyComboModel.NO_MODIFIER, KeyEvent.KEYCODE_A),
new KeyComboPersister(getActivity(), DefaultKeyComboModel.PREF_KEY_PREFIX));
}
public void testErrorKeyComboWithoutTriggerModifierWithDefaultKeymap() {
setKeymapToDefaultKeymap();
startTalkBackKeyboardShortcutPreferencesActivity();
assertNotNull(mPreferencesActivity);
KeyboardShortcutDialogPreference dialogPreference =
openNavigateNextDialogPreference(mPreferencesActivity);
KeyComboManager keyComboManager = TalkBackService.getInstance().getKeyComboManager();
// Try to set x.
assertAssignedKeyComboText(KeyEvent.META_ALT_ON, KeyEvent.KEYCODE_DPAD_RIGHT,
keyComboManager, dialogPreference);
sendKeyEventDownAndUp(KeyComboModel.NO_MODIFIER, KeyEvent.KEYCODE_X, keyComboManager);
assertAssignedKeyComboText(KeyComboModel.NO_MODIFIER, KeyEvent.KEYCODE_X, keyComboManager,
dialogPreference);
// Try to set it and confirm that error message is announced.
startRecordingUtterances();
sendKeyEventDownAndUp(KeyComboModel.NO_MODIFIER, KeyEvent.KEYCODE_ENTER, keyComboManager);
stopRecordingAndAssertUtterance(
keyComboManager.getKeyComboModel().getDescriptionOfEligibleKeyCombo());
// Confirm that key combo is not changed.
assertEquals(KeyComboManager.getKeyComboCode(
KeyComboModel.NO_MODIFIER, KeyEvent.KEYCODE_DPAD_RIGHT),
keyComboManager.getKeyComboModel().getKeyComboCodeForKey(
getActivity().getString(R.string.keycombo_shortcut_navigate_next)));
}
@MediumTest
public void testUnassignKeyComboWithDefaultKeyMap() {
setKeymapToDefaultKeymap();
startTalkBackKeyboardShortcutPreferencesActivity();
assertNotNull(mPreferencesActivity);
KeyboardShortcutDialogPreference dialogPreference =
openNavigateNextDialogPreference(mPreferencesActivity);
KeyComboManager keyComboManager = TalkBackService.getInstance().getKeyComboManager();
// Assert that current key combo is ALT + DPAD RIGHT.
assertAssignedKeyComboText(KeyEvent.META_ALT_ON, KeyEvent.KEYCODE_DPAD_RIGHT,
keyComboManager, dialogPreference);
// Press backspace.
sendKeyEventDownAndUp(KeyComboModel.NO_MODIFIER, KeyEvent.KEYCODE_DEL, keyComboManager);
// Assert that key combo has been changed to unassigned.
int modifier = KeyComboManager.getModifier(KeyComboModel.KEY_COMBO_CODE_UNASSIGNED);
int keyCode = KeyComboManager.getKeyCode(KeyComboModel.KEY_COMBO_CODE_UNASSIGNED);
assertAssignedKeyComboText(modifier, keyCode, keyComboManager, dialogPreference);
// Press enter.
sendKeyEventDownAndUp(KeyComboModel.NO_MODIFIER, KeyEvent.KEYCODE_ENTER, keyComboManager);
// Confirm that new key combo has been made persistent.
KeyComboPersister persister = new KeyComboPersister(getActivity(),
DefaultKeyComboModel.PREF_KEY_PREFIX);
assertEquals(KeyComboModel.KEY_COMBO_CODE_UNASSIGNED,
persister.getKeyComboCode(getActivity().getString(
R.string.keycombo_shortcut_navigate_next)).longValue());
}
public void testOverwriteKeyComboDialogWithDefaultKeymap() {
setKeymapToDefaultKeymap();
KeyComboManager keyComboManager = TalkBackService.getInstance().getKeyComboManager();
// Confirm that DPAD LEFT is used for navigate previous.
assertEquals(getActivity().getString(R.string.keycombo_shortcut_navigate_previous),
keyComboManager.getKeyComboModel().getKeyForKeyComboCode(
KeyComboManager.getKeyComboCode(
KeyComboModel.NO_MODIFIER, KeyEvent.KEYCODE_DPAD_LEFT)));
startTalkBackKeyboardShortcutPreferencesActivity();
assertNotNull(mPreferencesActivity);
KeyboardShortcutDialogPreference dialogPreference =
openNavigateNextDialogPreference(mPreferencesActivity);
// Set ALT + DPAD LEFT.
assertAssignedKeyComboText(KeyEvent.META_ALT_ON, KeyEvent.KEYCODE_DPAD_RIGHT,
keyComboManager, dialogPreference);
sendKeyEventDownAndUp(KeyEvent.META_ALT_ON, KeyEvent.KEYCODE_DPAD_LEFT,
keyComboManager);
assertAssignedKeyComboText(KeyEvent.META_ALT_ON, KeyEvent.KEYCODE_DPAD_LEFT,
keyComboManager, dialogPreference);
// Try to set it. Overwrite confirm dialog will come up.
sendKeyEventDownAndUp(KeyComboModel.NO_MODIFIER, KeyEvent.KEYCODE_ENTER, keyComboManager);
// Select cancel with keyboard. Default focus is on cancel button.
// Note: do this operation with keyboard to test the dialog can handle key events properly.
sendKeyEventDownAndUp(KeyComboModel.NO_MODIFIER, KeyEvent.KEYCODE_ENTER, keyComboManager);
// Confirm that key combo is not changed.
assertEquals(KeyComboManager.getKeyComboCode(
KeyComboModel.NO_MODIFIER, KeyEvent.KEYCODE_DPAD_RIGHT),
keyComboManager.getKeyComboModel().getKeyComboCodeForKey(
getActivity().getString(R.string.keycombo_shortcut_navigate_next)));
assertEquals(KeyComboManager.getKeyComboCode(
KeyComboModel.NO_MODIFIER, KeyEvent.KEYCODE_DPAD_LEFT),
keyComboManager.getKeyComboModel().getKeyComboCodeForKey(
getActivity().getString(R.string.keycombo_shortcut_navigate_previous)));
// Confirm that ALT + DPAD LEFT is still set in the dialog, and try again.
assertAssignedKeyComboText(KeyEvent.META_ALT_ON, KeyEvent.KEYCODE_DPAD_LEFT,
keyComboManager, dialogPreference);
sendKeyEventDownAndUp(KeyComboModel.NO_MODIFIER, KeyEvent.KEYCODE_ENTER, keyComboManager);
// Press OK. Since default focus is on cancel button, we need to move focus to OK button.
int keyToSelectOK =
getLayoutDirection(mPreferencesActivity.findViewById(android.R.id.content)) ==
LayoutDirection.RTL ?
KeyEvent.KEYCODE_DPAD_LEFT : KeyEvent.KEYCODE_DPAD_RIGHT;
sendKeyEventDownAndUp(KeyComboModel.NO_MODIFIER, keyToSelectOK, keyComboManager);
sendKeyEventDownAndUp(KeyComboModel.NO_MODIFIER, KeyEvent.KEYCODE_ENTER, keyComboManager);
// Confirm that key combos are changed.
assertEquals(KeyComboManager.getKeyComboCode(
KeyComboModel.NO_MODIFIER, KeyEvent.KEYCODE_DPAD_LEFT),
keyComboManager.getKeyComboModel().getKeyComboCodeForKey(
getActivity().getString(R.string.keycombo_shortcut_navigate_next)));
assertEquals(KeyComboModel.KEY_COMBO_CODE_UNASSIGNED,
keyComboManager.getKeyComboModel().getKeyComboCodeForKey(
getActivity().getString(R.string.keycombo_shortcut_navigate_previous)));
}
public void testCancelKeyComboChangeWithDefaultKeymap() {
setKeymapToDefaultKeymap();
startTalkBackKeyboardShortcutPreferencesActivity();
assertNotNull(mPreferencesActivity);
KeyboardShortcutDialogPreference dialogPreference =
openNavigateNextDialogPreference(mPreferencesActivity);
// Change key combo to ALT + x.
KeyComboManager keyComboManager = TalkBackService.getInstance().getKeyComboManager();
assertAssignedKeyComboText(KeyEvent.META_ALT_ON, KeyEvent.KEYCODE_DPAD_RIGHT,
keyComboManager, dialogPreference);
sendKeyEventDownAndUp(KeyEvent.META_ALT_ON, KeyEvent.KEYCODE_X, keyComboManager);
assertAssignedKeyComboText(KeyEvent.META_ALT_ON, KeyEvent.KEYCODE_X,
keyComboManager, dialogPreference);
// Press ESC to cancel.
sendKeyEventDownAndUp(KeyComboModel.NO_MODIFIER, KeyEvent.KEYCODE_ESCAPE, keyComboManager);
// Confirm that key combo is not changed.
assertEquals(KeyComboManager.getKeyComboCode(
KeyComboModel.NO_MODIFIER, KeyEvent.KEYCODE_DPAD_RIGHT),
keyComboManager.getKeyComboModel().getKeyComboCodeForKey(
getActivity().getString(R.string.keycombo_shortcut_navigate_next)));
}
public void testKeyComboPreferenceIsDisabledIfTalkBackIsOffWithDefaultKeymap() {
setKeymapToDefaultKeymap();
startTalkBackKeyboardShortcutPreferencesActivity();
assertNotNull(mPreferencesActivity);
// Navigate next preference is enabled if TalkBack is on.
assertEquals(TalkBackService.SERVICE_STATE_ACTIVE, TalkBackService.getServiceState());
assertTrue(isNavigateNextPreferenceEnabled(mPreferencesActivity));
// Navigate next preference is disabled if TalkBack is not on.
disableAllServices();
assertEquals(TalkBackService.SERVICE_STATE_INACTIVE, TalkBackService.getServiceState());
assertFalse(isNavigateNextPreferenceEnabled(mPreferencesActivity));
}
/**
* Asserts that key combo can be changed from keyComboCodeBefore to keyComboCodeAfter.
*/
private void assertAssignKeyCombo(long keyComboCodeBeforeWithModifier,
long keyComboCodeAfterWithModifier,
long keyComboCodeAfterWithoutModifier,
KeyComboPersister persister) {
startTalkBackKeyboardShortcutPreferencesActivity();
assertNotNull(mPreferencesActivity);
KeyboardShortcutDialogPreference dialogPreference =
openNavigateNextDialogPreference(mPreferencesActivity);
KeyComboManager keyComboManager = TalkBackService.getInstance().getKeyComboManager();
// Assert current key combo.
assertAssignedKeyComboText(KeyComboManager.getModifier(keyComboCodeBeforeWithModifier),
KeyComboManager.getKeyCode(keyComboCodeBeforeWithModifier),
keyComboManager, dialogPreference);
// Press new key combo.
sendKeyEventDownAndUp(KeyComboManager.getModifier(keyComboCodeAfterWithModifier),
KeyComboManager.getKeyCode(keyComboCodeAfterWithModifier), keyComboManager);
// Assert that key combo has been changed to new key combo.
assertAssignedKeyComboText(KeyComboManager.getModifier(keyComboCodeAfterWithModifier),
KeyComboManager.getKeyCode(keyComboCodeAfterWithModifier),
keyComboManager, dialogPreference);
// Press enter.
sendKeyEventDownAndUp(KeyComboModel.NO_MODIFIER, KeyEvent.KEYCODE_ENTER, keyComboManager);
// Confirm that new key combo has been made persistent.
assertEquals(KeyComboManager.getKeyComboCode(
KeyComboManager.getModifier(keyComboCodeAfterWithoutModifier),
KeyComboManager.getKeyCode(keyComboCodeAfterWithoutModifier)),
persister.getKeyComboCode(getActivity().getString(
R.string.keycombo_shortcut_navigate_next)).longValue());
}
/**
* Sets keymap to classic keymap.
*/
private void setKeymapToClassicKeymap() {
setKeymap(R.string.classic_keymap_entry_value, new KeyComboModelApp(getActivity()));
}
/**
* Sets keymap to default keymap.
*/
private void setKeymapToDefaultKeymap() {
setKeymap(R.string.default_keymap_entry_value, new DefaultKeyComboModel(getActivity()));
}
/**
* Sets keymap to specified one.
*/
private void setKeymap(int keymapEntryValue, KeyComboModel keyComboModel) {
SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(getActivity());
pref.edit().putString(
getActivity().getString(R.string.pref_select_keymap_key),
getActivity().getString(keymapEntryValue)).commit();
TalkBackService.getInstance().getKeyComboManager().setKeyComboModel(keyComboModel);
}
/**
* Starts TalkBackKeyboardShortcutPreferencesActivity. The activity will be set to
* mPreferencesActivity. This test case automatically finish the activity in its tearDown.
*/
private void startTalkBackKeyboardShortcutPreferencesActivity() {
Intent intent = new Intent(getActivity(),
TalkBackKeyboardShortcutPreferencesActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
mPreferencesActivity = getInstrumentation().startActivitySync(intent);
}
/**
* Opens dialog preference of navigate next shortcut.
*/
private KeyboardShortcutDialogPreference openNavigateNextDialogPreference(
Activity preferenceActivity) {
OpenNavigateNextDialogPreferenceRunnable runnable =
new OpenNavigateNextDialogPreferenceRunnable(preferenceActivity);
getInstrumentation().runOnMainSync(runnable);
getInstrumentation().waitForIdleSync();
return runnable.dialog;
}
/**
* Asserts current assigned key combo text in dialog.
*/
private void assertAssignedKeyComboText(int modifier, int keyCode,
KeyComboManager keyComboManager,
KeyboardShortcutDialogPreference dialogPreference) {
GetAssignedCombinationRunnable runnable =
new GetAssignedCombinationRunnable(dialogPreference);
getInstrumentation().runOnMainSync(runnable);
getInstrumentation().waitForIdleSync();
assertEquals(keyComboManager.getKeyComboStringRepresentation(
KeyComboManager.getKeyComboCode(modifier, keyCode)),
runnable.assignedCombinationText);
}
/**
* Gets whether navigate next preference is enabled or not.
*/
private boolean isNavigateNextPreferenceEnabled(Activity activity) {
GetNavigateNextPreferenceIsEnabledRunnable runnable =
new GetNavigateNextPreferenceIsEnabledRunnable(activity);
getInstrumentation().runOnMainSync(runnable);
getInstrumentation().waitForIdleSync();
return runnable.mEnabled;
}
private static class OpenNavigateNextDialogPreferenceRunnable implements Runnable {
private final Activity mActivity;
public KeyboardShortcutDialogPreference dialog;
public OpenNavigateNextDialogPreferenceRunnable(Activity activity) {
mActivity = activity;
}
@Override
public void run() {
PreferenceFragment fragment =
(PreferenceFragment) mActivity.getFragmentManager().findFragmentById(
android.R.id.content);
dialog = (KeyboardShortcutDialogPreference) fragment.findPreference(
mActivity.getString(R.string.keycombo_shortcut_navigate_next));
dialog.showDialog(new Bundle());
}
}
private static class GetAssignedCombinationRunnable implements Runnable {
private final KeyboardShortcutDialogPreference mDialogPreference;
public String assignedCombinationText;
GetAssignedCombinationRunnable(KeyboardShortcutDialogPreference dialogPreference) {
mDialogPreference = dialogPreference;
}
@Override
public void run() {
TextView assignedCombination = (TextView) mDialogPreference.getDialog().findViewById(
R.id.assigned_combination);
assignedCombinationText = assignedCombination.getText().toString();
}
}
private static class GetNavigateNextPreferenceIsEnabledRunnable implements Runnable {
private final Activity mActivity;
public boolean mEnabled;
public GetNavigateNextPreferenceIsEnabledRunnable(Activity activity) {
mActivity = activity;
}
@Override
public void run() {
PreferenceFragment preferenceFragment =
(PreferenceFragment) mActivity.getFragmentManager().findFragmentById(
android.R.id.content);
DialogPreference dialogPreference =
(DialogPreference) preferenceFragment.findPreference(
mActivity.getString(R.string.keycombo_shortcut_navigate_next));
mEnabled = dialogPreference.isEnabled();
}
}
}