/* * Copyright 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 android.app.ActionBar; import android.app.Activity; import android.app.AlertDialog; import android.content.Context; import android.content.DialogInterface; import android.os.Bundle; import android.preference.ListPreference; import android.preference.Preference; import android.preference.Preference.OnPreferenceChangeListener; import android.preference.PreferenceCategory; import android.preference.PreferenceFragment; import android.preference.PreferenceGroup; import android.preference.PreferenceScreen; import android.support.v4.os.BuildCompat; import android.view.KeyEvent; import android.view.MenuItem; import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityManager; import android.widget.Button; import com.android.talkback.keyboard.DefaultKeyComboModel; import com.android.talkback.keyboard.KeyComboModel; import com.google.android.marvin.talkback.TalkBackService; import java.util.HashSet; import java.util.Set; /** * Activity used to set TalkBack's keyboard shortcut preferences. */ public class TalkBackKeyboardShortcutPreferencesActivity extends Activity { /** * Utility method for announcing text via accessibility event. */ public static void announceText(String text, Context context) { AccessibilityManager accessibilityManager = (AccessibilityManager) context.getSystemService(Context.ACCESSIBILITY_SERVICE); if (accessibilityManager.isEnabled()) { AccessibilityEvent event = AccessibilityEvent.obtain( AccessibilityEvent.TYPE_ANNOUNCEMENT); event.setContentDescription(text); accessibilityManager.sendAccessibilityEvent(event); } } private static void focusCancelButton(AlertDialog alertDialog) { Button cancelButton = alertDialog.getButton(AlertDialog.BUTTON_NEGATIVE); cancelButton.setFocusableInTouchMode(true); cancelButton.requestFocus(); } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setTitle(getString(R.string.title_pref_manage_keyboard_shortcuts)); ActionBar actionBar = getActionBar(); if (actionBar != null) { actionBar.setDisplayHomeAsUpEnabled(true); } TalkBackService talkBackService = TalkBackService.getInstance(); KeyComboManager keyComboManager = talkBackService == null ? KeyComboManager.create(this) : talkBackService.getKeyComboManager(); TalkBackKeyboardShortcutPreferenceFragment fragment = TalkBackKeyboardShortcutPreferenceFragment.createFor(keyComboManager.getKeymap()); getFragmentManager().beginTransaction() .replace(android.R.id.content, fragment) .commit(); } @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case android.R.id.home: finish(); return true; default: return super.onOptionsItemSelected(item); } } public static class TalkBackKeyboardShortcutPreferenceFragment extends PreferenceFragment { private static final String BUNDLE_KEYMAP = "bundle_keymap"; private static final boolean IS_IN_ARC = TalkBackService.isInArc(); private static final int[] HIDDEN_SHORTCUT_KEY_IDS_IN_ARC = { R.string.keycombo_shortcut_global_suspend, R.string.keycombo_shortcut_global_home, R.string.keycombo_shortcut_global_recents, R.string.keycombo_shortcut_global_notifications, R.string.keycombo_shortcut_navigate_next_window, R.string.keycombo_shortcut_navigate_previous_window }; private static final int[] HIDDEN_SHORTCUT_KEY_IDS_IN_NON_ARC = { R.string.keycombo_shortcut_open_manage_keyboard_shortcuts, R.string.keycombo_shortcut_open_talkback_settings }; public static TalkBackKeyboardShortcutPreferenceFragment createFor(String keymap) { TalkBackKeyboardShortcutPreferenceFragment preferenceFragment = new TalkBackKeyboardShortcutPreferenceFragment(); Bundle bundle = new Bundle(); bundle.putString(BUNDLE_KEYMAP, keymap); preferenceFragment.setArguments(bundle); return preferenceFragment; } private String mKeymap; private final OnPreferenceChangeListener mPreferenceChangeListener = new OnPreferenceChangeListener() { @Override public boolean onPreferenceChange(Preference preference, Object newValue) { String preferenceKeyForTriggerModifier = getKeyComboManager() .getKeyComboModel().getPreferenceKeyForTriggerModifier(); if (preference instanceof KeyboardShortcutDialogPreference && newValue instanceof Long) { KeyboardShortcutDialogPreference keyBoardPreference = (KeyboardShortcutDialogPreference) preference; keyBoardPreference.setKeyComboCode((Long) newValue); keyBoardPreference.notifyChanged(); } else if (preference.getKey() != null && preference.getKey().equals(getString( R.string.pref_select_keymap_key)) && newValue instanceof String) { String newKeymap = (String) newValue; // Do nothing if keymap is the same. if (mKeymap.equals(newKeymap)) { return false; } // Replace preference fragment. TalkBackKeyboardShortcutPreferenceFragment fragment = TalkBackKeyboardShortcutPreferenceFragment.createFor(newKeymap); getFragmentManager().beginTransaction() .replace(android.R.id.content, fragment).commit(); // Set new key combo model. KeyComboManager keyComboManager = getKeyComboManager(); keyComboManager.setKeyComboModel( keyComboManager.createKeyComboModelFor(newKeymap)); // Announce new keymap. announceText(String.format( getString(R.string.keycombo_menu_announce_active_keymap), getKeymapName(newKeymap)), getActivity()); } else if (preference.getKey() != null && preference.getKey().equals(preferenceKeyForTriggerModifier) && newValue instanceof String) { mTriggerModifierToBeSet = (String) newValue; ListPreference listPreference = (ListPreference) preference; if (listPreference.getValue().equals(mTriggerModifierToBeSet)) { return false; } CharSequence[] entries = listPreference.getEntries(); CharSequence newTriggerModifier = entries[ listPreference.findIndexOfValue(mTriggerModifierToBeSet)]; // Show alert dialog. AlertDialog dialog = new AlertDialog.Builder(getActivity()) .setTitle(R.string .keycombo_menu_alert_title_trigger_modifier) .setMessage(getString(R.string .keycombo_menu_alert_message_trigger_modifier, newTriggerModifier)) .setPositiveButton(android.R.string.ok, mChooseTriggerModifierConfirmDialogPositive) .setNegativeButton(android.R.string.cancel, mChooseTriggerModifierConfirmDialogNegative) .show(); focusCancelButton(dialog); return false; } return true; } }; private String mTriggerModifierToBeSet; private final DialogInterface.OnClickListener mChooseTriggerModifierConfirmDialogPositive = new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialogInterface, int i) { resetKeymap(); KeyComboModel keyComboModel = getKeyComboManager().getKeyComboModel(); // Update preference. String preferenceKeyForTriggerModifier = keyComboModel.getPreferenceKeyForTriggerModifier(); ListPreference listPreference = (ListPreference) findPreference(preferenceKeyForTriggerModifier); listPreference.setValue(mTriggerModifierToBeSet); // Update KeyComboModel. keyComboModel.notifyTriggerModifierChanged(); // Update UI. Set<String> keySet = getKeyComboManager().getKeyComboModel().getKeyComboCodeMap().keySet(); for (String key : keySet) { KeyboardShortcutDialogPreference preference = (KeyboardShortcutDialogPreference) findPreference(key); preference.onTriggerModifierChanged(); } // Announce that trigger modifier has changed. CharSequence[] entries = listPreference.getEntries(); CharSequence newTriggerModifier = entries[listPreference.findIndexOfValue(mTriggerModifierToBeSet)]; announceText(getString(R.string.keycombo_menu_announce_new_trigger_modifier, newTriggerModifier), getActivity()); mTriggerModifierToBeSet = null; } }; private final DialogInterface.OnClickListener mChooseTriggerModifierConfirmDialogNegative = new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialogInterface, int i) { mTriggerModifierToBeSet = null; } }; private final Preference.OnPreferenceClickListener mResetKeymapPreferenceClickListener = new Preference.OnPreferenceClickListener() { @Override public boolean onPreferenceClick(Preference preference) { // Show confirm dialog. AlertDialog dialog = new AlertDialog.Builder(getActivity()) .setTitle(getString(R.string.keycombo_menu_reset_keymap)) .setMessage(getString(R.string.message_in_reset_keymap_confirm_dialog)) .setPositiveButton(R.string.reset_button_in_reset_keymap_confirm_dialog, mResetKeymapConfirmDialogPositive) .setNegativeButton(android.R.string.cancel, mResetKeymapConfirmDialogNegative) .show(); focusCancelButton(dialog); return true; } }; private final DialogInterface.OnClickListener mResetKeymapConfirmDialogPositive = new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialogInterface, int i) { resetKeymap(); dialogInterface.dismiss(); announceText(getString(R.string.keycombo_menu_announce_reset_keymap), getActivity()); } }; private void resetKeymap() { KeyComboModel keyComboModel = getKeyComboManager().getKeyComboModel(); for (String key : keyComboModel.getKeyComboCodeMap().keySet()) { long defaultKeyComboCode = keyComboModel.getDefaultKeyComboCode(key); // Do nothing if key combo code is not changed from default one. if (defaultKeyComboCode == keyComboModel.getKeyComboCodeForKey(key)) { continue; } // Save with default key combo code. keyComboModel.saveKeyComboCode(key, defaultKeyComboCode); // Update UI. KeyboardShortcutDialogPreference keyboardShortcutDialogPreference = (KeyboardShortcutDialogPreference) findPreference(key); keyboardShortcutDialogPreference.setKeyComboCode(defaultKeyComboCode); keyboardShortcutDialogPreference.notifyChanged(); } } private final DialogInterface.OnClickListener mResetKeymapConfirmDialogNegative = new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialogInterface, int i) { dialogInterface.cancel(); } }; void performClickOnResetKeymapForTesting() { Preference resetKeymapPreference = findPreference(getString(R.string.pref_reset_keymap_key)); mResetKeymapPreferenceClickListener.onPreferenceClick(resetKeymapPreference); } private KeyComboManager getKeyComboManager() { TalkBackService talkBackService = TalkBackService.getInstance(); return talkBackService == null ? KeyComboManager.create(getActivity()) : talkBackService.getKeyComboManager(); } private String getKeymapName(String keymap) { if (keymap.equals(getString(R.string.classic_keymap_entry_value))) { return getString(R.string.value_classic_keymap); } else if (keymap.equals(getString(R.string.default_keymap_entry_value))) { return getString(R.string.value_default_keymap); } return null; } private int getPreferenceResourceId(String keymap) { if (keymap.equals(getString(R.string.classic_keymap_entry_value))) { return R.xml.key_combo_preferences; } else if (keymap.equals(getString(R.string.default_keymap_entry_value))) { return R.xml.default_key_combo_preferences; } return R.xml.key_combo_preferences; } @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // Set preferences to use device-protected storage. if (BuildCompat.isAtLeastN()) { getPreferenceManager().setStorageDeviceProtected(); } mKeymap = getArguments().getString(BUNDLE_KEYMAP); addPreferencesFromResource(getPreferenceResourceId(mKeymap)); PreferenceScreen resetKeymapPreferenceScreen = (PreferenceScreen) findPreference(getString(R.string.pref_reset_keymap_key)); resetKeymapPreferenceScreen.setOnPreferenceClickListener( mResetKeymapPreferenceClickListener); // Hide select keymap preference in Arc if current keymap is already set to default // keymap. if (IS_IN_ARC && getKeyComboManager().getKeyComboModel() instanceof DefaultKeyComboModel) { PreferenceCategory keymapPreferenceCategory = (PreferenceCategory) getPreferenceScreen().findPreference(getString( R.string.pref_keymap_category_key)); ListPreference keymapListPreference = (ListPreference) keymapPreferenceCategory.findPreference(getString( R.string.pref_select_keymap_key)); keymapPreferenceCategory.removePreference(keymapListPreference); } int[] hiddenShortcutKeyIds = IS_IN_ARC ? HIDDEN_SHORTCUT_KEY_IDS_IN_ARC : HIDDEN_SHORTCUT_KEY_IDS_IN_NON_ARC; Set<String> hiddenShortcutKeys = new HashSet<>(); for (int id : hiddenShortcutKeyIds) { hiddenShortcutKeys.add(getString(id)); } initPreferenceUIs(getPreferenceScreen(), hiddenShortcutKeys); } /** * Initialize preference UIs. * @param root Root element of preference UIs. * @param hiddenShortcutKeys Set of shortcut keys which will be made hidden. Note that * preference is made hidden only when its shortcut is disabled in * the key combo model. */ private void initPreferenceUIs(PreferenceGroup root, Set<String> hiddenShortcutKeys) { if (root == null) { return; } final KeyComboModel keyComboModel = getKeyComboManager().getKeyComboModel(); final String preferenceKeyForTriggerModifier = keyComboModel.getPreferenceKeyForTriggerModifier(); for (int i = 0; i < root.getPreferenceCount(); i++) { final Preference preference = root.getPreference(i); final String key = preference.getKey(); if (key != null && preference instanceof KeyboardShortcutDialogPreference && !keyComboModel.getKeyComboCodeMap().containsKey(key)) { // Disable or hide preference of unavailable key combo on this device. if (hiddenShortcutKeys.contains(key)) { root.removePreference(preference); i--; } else { preference.setEnabled(false); } } else if (preference instanceof KeyboardShortcutDialogPreference || (key != null && key.equals(getString(R.string.pref_select_keymap_key))) || (key != null && key.equals(preferenceKeyForTriggerModifier))) { // Set onPreferenceChangeListener. preference.setOnPreferenceChangeListener(mPreferenceChangeListener); } else if (preference instanceof PreferenceGroup) { initPreferenceUIs((PreferenceGroup) preference, hiddenShortcutKeys); } } } } }