/*
* Copyright (C) 2016 The Android Open Source Project
*
* 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.keyboard;
import android.content.Context;
import android.content.SharedPreferences;
import android.preference.PreferenceManager;
import android.view.KeyEvent;
import com.android.talkback.KeyComboManager;
import com.android.talkback.R;
import java.util.Map;
import java.util.TreeMap;
/**
* Manages key and key combo code.
*
* TODO: Rename this class to ClassicKeyComboModel after DefaultKeyComboModel becomes
* default one.
*/
public class KeyComboModelApp implements KeyComboModel {
private final Context mContext;
private final KeyComboPersister mPersister;
private final Map<String, Long> mKeyComboCodeMap = new TreeMap<>();
/**
* Search key (meta key) cannot be used as part of key combination since onKey method of
* KeyboardShortcutDialogPreference is not called if search key is contained.
*/
private final int ELIGIBLE_MODIFIER_MASK = KeyEvent.META_SHIFT_ON | KeyEvent.META_ALT_ON |
KeyEvent.META_CTRL_ON;
private final int REQUIRED_MODIFIER_MASK = KeyEvent.META_ALT_ON | KeyEvent.META_CTRL_ON;
public KeyComboModelApp(Context context) {
mContext = context;
mPersister = new KeyComboPersister(mContext, null /* no prefix */);
loadCombos();
}
@Override
public int getTriggerModifier() {
return NO_MODIFIER;
}
@Override
public void notifyTriggerModifierChanged() {}
@Override
public String getPreferenceKeyForTriggerModifier() {
return null;
}
@Override
public Map<String, Long> getKeyComboCodeMap() {
return mKeyComboCodeMap;
}
@Override
public String getKeyForKeyComboCode(long keyComboCode) {
if (keyComboCode == KEY_COMBO_CODE_UNASSIGNED) {
return null;
}
for (Map.Entry<String, Long> entry : mKeyComboCodeMap.entrySet()) {
if (entry.getValue() == keyComboCode) {
return entry.getKey();
}
}
return null;
}
@Override
public long getKeyComboCodeForKey(String key) {
if (key != null && mKeyComboCodeMap.containsKey(key)) {
return mKeyComboCodeMap.get(key);
} else {
return KEY_COMBO_CODE_UNASSIGNED;
}
}
/**
* Loads default key combinations.
*/
private void loadCombos() {
addCombo(mContext.getString(R.string.keycombo_shortcut_navigate_next));
addCombo(mContext.getString(R.string.keycombo_shortcut_navigate_previous));
addCombo(mContext.getString(R.string.keycombo_shortcut_navigate_first));
addCombo(mContext.getString(R.string.keycombo_shortcut_navigate_last));
addCombo(mContext.getString(R.string.keycombo_shortcut_perform_click));
addCombo(mContext.getString(R.string.keycombo_shortcut_global_back));
addCombo(mContext.getString(R.string.keycombo_shortcut_global_home));
addCombo(mContext.getString(R.string.keycombo_shortcut_global_recents));
addCombo(mContext.getString(R.string.keycombo_shortcut_global_notifications));
addCombo(mContext.getString(R.string.keycombo_shortcut_global_suspend));
addCombo(mContext.getString(R.string.keycombo_shortcut_granularity_increase));
addCombo(mContext.getString(R.string.keycombo_shortcut_granularity_decrease));
addCombo(mContext.getString(R.string.keycombo_shortcut_other_read_from_top));
addCombo(mContext.getString(R.string.keycombo_shortcut_other_read_from_next_item));
addCombo(mContext.getString(R.string.keycombo_shortcut_other_toggle_search));
addCombo(mContext.getString(R.string.keycombo_shortcut_other_local_context_menu));
addCombo(mContext.getString(R.string.keycombo_shortcut_other_global_context_menu));
}
private void addCombo(String key) {
if (!mPersister.contains(key)) {
mPersister.saveKeyCombo(key, getDefaultKeyComboCode(key));
}
long keyComboCode = mPersister.getKeyComboCode(key);
mKeyComboCodeMap.put(key, keyComboCode);
}
@Override
public long getDefaultKeyComboCode(String key) {
if (key == null) {
return KEY_COMBO_CODE_UNASSIGNED;
}
if (key.equals(mContext.getString(R.string.keycombo_shortcut_navigate_next))) {
return KeyComboManager.getKeyComboCode(KeyEvent.META_ALT_ON | KeyEvent.META_SHIFT_ON,
KeyEvent.KEYCODE_DPAD_RIGHT);
}
if (key.equals(mContext.getString(R.string.keycombo_shortcut_navigate_previous))) {
return KeyComboManager.getKeyComboCode(KeyEvent.META_ALT_ON | KeyEvent.META_SHIFT_ON,
KeyEvent.KEYCODE_DPAD_LEFT);
}
if (key.equals(mContext.getString(R.string.keycombo_shortcut_navigate_first))) {
return KeyComboManager.getKeyComboCode(KeyEvent.META_ALT_ON | KeyEvent.META_SHIFT_ON,
KeyEvent.KEYCODE_DPAD_UP);
}
if (key.equals(mContext.getString(R.string.keycombo_shortcut_navigate_last))) {
return KeyComboManager.getKeyComboCode(KeyEvent.META_ALT_ON | KeyEvent.META_SHIFT_ON,
KeyEvent.KEYCODE_DPAD_DOWN);
}
if (key.equals(mContext.getString(R.string.keycombo_shortcut_perform_click))) {
return KeyComboManager.getKeyComboCode(KeyEvent.META_ALT_ON | KeyEvent.META_SHIFT_ON,
KeyEvent.KEYCODE_ENTER);
}
if (key.equals(mContext.getString(R.string.keycombo_shortcut_global_back))) {
return KeyComboManager.getKeyComboCode(KeyEvent.META_ALT_ON | KeyEvent.META_SHIFT_ON,
KeyEvent.KEYCODE_DEL);
}
if (key.equals(mContext.getString(R.string.keycombo_shortcut_global_home))) {
return KeyComboManager.getKeyComboCode(KeyEvent.META_ALT_ON | KeyEvent.META_SHIFT_ON,
KeyEvent.KEYCODE_H);
}
if (key.equals(mContext.getString(R.string.keycombo_shortcut_global_recents))) {
return KeyComboManager.getKeyComboCode(KeyEvent.META_ALT_ON | KeyEvent.META_SHIFT_ON,
KeyEvent.KEYCODE_R);
}
if (key.equals(mContext.getString(R.string.keycombo_shortcut_global_notifications))) {
return KeyComboManager.getKeyComboCode(KeyEvent.META_ALT_ON | KeyEvent.META_SHIFT_ON,
KeyEvent.KEYCODE_N);
}
if (key.equals(mContext.getString(R.string.keycombo_shortcut_other_toggle_search))) {
return KeyComboManager.getKeyComboCode(KeyEvent.META_ALT_ON | KeyEvent.META_SHIFT_ON,
KeyEvent.KEYCODE_SLASH);
}
if (key.equals(mContext.getString(R.string.keycombo_shortcut_global_suspend))) {
return KeyComboManager.getKeyComboCode(KeyEvent.META_ALT_ON | KeyEvent.META_SHIFT_ON,
KeyEvent.KEYCODE_Z);
}
if (key.equals(mContext.getString(R.string.keycombo_shortcut_granularity_increase))) {
return KeyComboManager.getKeyComboCode(KeyEvent.META_ALT_ON | KeyEvent.META_SHIFT_ON,
KeyEvent.KEYCODE_EQUALS);
}
if (key.equals(mContext.getString(R.string.keycombo_shortcut_granularity_decrease))) {
return KeyComboManager.getKeyComboCode(KeyEvent.META_ALT_ON | KeyEvent.META_SHIFT_ON,
KeyEvent.KEYCODE_MINUS);
}
if (key.equals(mContext.getString(R.string.keycombo_shortcut_other_local_context_menu))) {
return KeyComboManager.getKeyComboCode(KeyEvent.META_ALT_ON | KeyEvent.META_SHIFT_ON,
KeyEvent.KEYCODE_L);
}
if (key.equals(mContext.getString(R.string.keycombo_shortcut_other_global_context_menu))) {
return KeyComboManager.getKeyComboCode(KeyEvent.META_ALT_ON | KeyEvent.META_SHIFT_ON,
KeyEvent.KEYCODE_G);
}
return KEY_COMBO_CODE_UNASSIGNED;
}
@Override
public void clearKeyComboCode(String key) {
saveKeyComboCode(key, KEY_COMBO_CODE_UNASSIGNED);
}
@Override
public void saveKeyComboCode(String key, long keyComboCode) {
mPersister.saveKeyCombo(key, keyComboCode);
if (mKeyComboCodeMap.containsKey(key)) {
mKeyComboCodeMap.put(key, keyComboCode);
}
}
@Override
public boolean isEligibleKeyComboCode(long keyComboCode) {
if (keyComboCode == KEY_COMBO_CODE_UNASSIGNED) {
return true;
}
int modifier = KeyComboManager.getModifier(keyComboCode);
if ((modifier & REQUIRED_MODIFIER_MASK) == 0 ||
(modifier | ELIGIBLE_MODIFIER_MASK) != ELIGIBLE_MODIFIER_MASK) {
return false;
}
int keyCode = KeyComboManager.getKeyCode(keyComboCode);
return keyCode != 0 &&
keyCode != KeyEvent.KEYCODE_SHIFT_LEFT && keyCode != KeyEvent.KEYCODE_SHIFT_RIGHT &&
keyCode != KeyEvent.KEYCODE_ALT_LEFT && keyCode != KeyEvent.KEYCODE_ALT_RIGHT &&
keyCode != KeyEvent.KEYCODE_CTRL_LEFT && keyCode != KeyEvent.KEYCODE_CTRL_RIGHT;
}
@Override
public String getDescriptionOfEligibleKeyCombo() {
return mContext.getString(R.string.keycombo_assign_dialog_instruction);
}
@Override
public void updateVersion(int previousVersion) {
// TalkBack 4.4 fixes an issue with the default keyboard shortcuts, but the changes need
// to be re-persisted for users upgrading from older versions who haven't customized their
// shortcut keys.
if (previousVersion < 40400000) {
changeGranularityKeyCombos();
}
// TalkBack 4.5 assigns default key combos for showing local or global context menu. We need
// to re-persist them if user hasn't changed them from old default ones.
if (previousVersion < 40500000) {
changeLocalGlobalContextMenuKeyCombos();
}
}
/**
* If the user hasn't changed their key combos for changing granularity/navigation settings,
* we should switch out the existing key combos for the new key combos.
*/
private void changeGranularityKeyCombos() {
// Old shortcut for increase granularity was Alt-Plus.
updateKeyCombo(mContext.getString(R.string.keycombo_shortcut_granularity_increase),
KeyEvent.META_ALT_ON,
KeyEvent.KEYCODE_PLUS);
// Old shortcut for decrease granularity was Alt-Minus.
updateKeyCombo(mContext.getString(R.string.keycombo_shortcut_granularity_decrease),
KeyEvent.META_ALT_ON,
KeyEvent.KEYCODE_MINUS);
}
/**
* If user hasn't changed key combos for showing local or global context menu, change them to
* new ones.
*/
private void changeLocalGlobalContextMenuKeyCombos() {
// Old shortcut for local context menu is unassigned.
updateKeyCombo(mContext.getString(R.string.keycombo_shortcut_other_local_context_menu),
KEY_COMBO_CODE_UNASSIGNED);
// Old shortcut for global context menu is unassigned.
updateKeyCombo(mContext.getString(R.string.keycombo_shortcut_other_global_context_menu),
KEY_COMBO_CODE_UNASSIGNED);
}
/**
* Updates a key combo if the user has not yet changed it from the old default value.
* @param key the name of the key combo to change
* @param oldModifier the old default modifier assigned to the key combo
* @param oldKeyCode the old default keycode assigned to the key combo
*/
public void updateKeyCombo(String key, int oldModifier, int oldKeyCode) {
updateKeyCombo(key, KeyComboManager.getKeyComboCode(oldModifier, oldKeyCode));
}
/**
* Updates a key combo if the user has not yet changed it from the old default value.
* @param key the name of the key combo to change
* @param oldDefaultKeyComboCode the old default key combo.
*/
public void updateKeyCombo(String key, long oldDefaultKeyComboCode) {
final long newKeyComboCode = getDefaultKeyComboCode(key);
if (getKeyForKeyComboCode(newKeyComboCode) != null) {
return; // User is already using the new key combo.
}
if (mPersister.contains(key)) {
final long actualKeyComboCode = mPersister.getKeyComboCode(key);
if (oldDefaultKeyComboCode != actualKeyComboCode) {
return; // User has modified the key combo.
}
}
if (newKeyComboCode != KEY_COMBO_CODE_UNASSIGNED) {
saveKeyComboCode(key, newKeyComboCode);
}
}
}