/* * 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); } } }