1 /* 2 * Copyright (C) 2017 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.tv.settings.inputmethod; 18 19 import android.content.Context; 20 import android.content.Intent; 21 import android.content.pm.PackageManager; 22 import android.os.Bundle; 23 import android.os.UserHandle; 24 import android.text.TextUtils; 25 import android.util.ArraySet; 26 import android.view.inputmethod.InputMethodInfo; 27 28 import androidx.annotation.Keep; 29 import androidx.annotation.VisibleForTesting; 30 import androidx.preference.ListPreference; 31 import androidx.preference.Preference; 32 import androidx.preference.PreferenceCategory; 33 import androidx.preference.PreferenceScreen; 34 35 import com.android.internal.logging.nano.MetricsProto; 36 import com.android.settingslib.applications.DefaultAppInfo; 37 import com.android.tv.settings.R; 38 import com.android.tv.settings.SettingsPreferenceFragment; 39 import com.android.tv.settings.autofill.AutofillHelper; 40 41 import java.util.ArrayList; 42 import java.util.List; 43 import java.util.Set; 44 45 /** 46 * Fragment for managing IMEs and Autofills 47 */ 48 @Keep 49 public class KeyboardFragment extends SettingsPreferenceFragment { 50 private static final String TAG = "KeyboardFragment"; 51 52 // Order of input methods, make sure they are inserted between 1 (currentKeyboard) and 53 // 3 (manageKeyboards). 54 private static final int INPUT_METHOD_PREFERENCE_ORDER = 2; 55 56 @VisibleForTesting 57 static final String KEY_KEYBOARD_CATEGORY = "keyboardCategory"; 58 59 @VisibleForTesting 60 static final String KEY_CURRENT_KEYBOARD = "currentKeyboard"; 61 62 private static final String KEY_KEYBOARD_SETTINGS_PREFIX = "keyboardSettings:"; 63 64 @VisibleForTesting 65 static final String KEY_AUTOFILL_CATEGORY = "autofillCategory"; 66 67 @VisibleForTesting 68 static final String KEY_CURRENT_AUTOFILL = "currentAutofill"; 69 70 private static final String KEY_AUTOFILL_SETTINGS_PREFIX = "autofillSettings:"; 71 72 private PackageManager mPm; 73 74 /** 75 * @return New fragment instance 76 */ newInstance()77 public static KeyboardFragment newInstance() { 78 return new KeyboardFragment(); 79 } 80 81 @Override onAttach(Context context)82 public void onAttach(Context context) { 83 super.onAttach(context); 84 mPm = context.getPackageManager(); 85 } 86 87 @Override onCreatePreferences(Bundle savedInstanceState, String rootKey)88 public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { 89 setPreferencesFromResource(R.xml.keyboard, null); 90 91 findPreference(KEY_CURRENT_KEYBOARD).setOnPreferenceChangeListener( 92 (preference, newValue) -> { 93 InputMethodHelper.setDefaultInputMethodId(getContext(), (String) newValue); 94 return true; 95 }); 96 97 updateUi(); 98 } 99 100 @Override onResume()101 public void onResume() { 102 super.onResume(); 103 updateUi(); 104 } 105 106 @VisibleForTesting updateUi()107 void updateUi() { 108 updateAutofill(); 109 updateKeyboards(); 110 } 111 updateKeyboards()112 private void updateKeyboards() { 113 updateCurrentKeyboardPreference((ListPreference) findPreference(KEY_CURRENT_KEYBOARD)); 114 updateKeyboardsSettings(); 115 } 116 updateCurrentKeyboardPreference(ListPreference currentKeyboardPref)117 private void updateCurrentKeyboardPreference(ListPreference currentKeyboardPref) { 118 final PackageManager packageManager = getContext().getPackageManager(); 119 List<InputMethodInfo> enabledInputMethodInfos = InputMethodHelper 120 .getEnabledSystemInputMethodList(getContext()); 121 final List<CharSequence> entries = new ArrayList<>(enabledInputMethodInfos.size()); 122 final List<CharSequence> values = new ArrayList<>(enabledInputMethodInfos.size()); 123 124 int defaultIndex = 0; 125 final String defaultId = InputMethodHelper.getDefaultInputMethodId(getContext()); 126 127 for (final InputMethodInfo info : enabledInputMethodInfos) { 128 entries.add(info.loadLabel(packageManager)); 129 final String id = info.getId(); 130 values.add(id); 131 if (TextUtils.equals(id, defaultId)) { 132 defaultIndex = values.size() - 1; 133 } 134 } 135 136 currentKeyboardPref.setEntries(entries.toArray(new CharSequence[entries.size()])); 137 currentKeyboardPref.setEntryValues(values.toArray(new CharSequence[values.size()])); 138 if (entries.size() > 0) { 139 currentKeyboardPref.setValueIndex(defaultIndex); 140 } 141 } 142 getPreferenceContext()143 Context getPreferenceContext() { 144 return getPreferenceManager().getContext(); 145 } 146 updateKeyboardsSettings()147 private void updateKeyboardsSettings() { 148 final Context preferenceContext = getPreferenceContext(); 149 final PackageManager packageManager = getContext().getPackageManager(); 150 List<InputMethodInfo> enabledInputMethodInfos = InputMethodHelper 151 .getEnabledSystemInputMethodList(getContext()); 152 153 PreferenceScreen preferenceScreen = getPreferenceScreen(); 154 final Set<String> enabledInputMethodKeys = new ArraySet<>(enabledInputMethodInfos.size()); 155 // Add per-IME settings 156 for (final InputMethodInfo info : enabledInputMethodInfos) { 157 final Intent settingsIntent = InputMethodHelper.getInputMethodSettingsIntent(info); 158 if (settingsIntent == null) { 159 continue; 160 } 161 final String key = KEY_KEYBOARD_SETTINGS_PREFIX + info.getId(); 162 163 Preference preference = preferenceScreen.findPreference(key); 164 if (preference == null) { 165 preference = new Preference(preferenceContext); 166 preference.setOrder(INPUT_METHOD_PREFERENCE_ORDER); 167 preferenceScreen.addPreference(preference); 168 } 169 preference.setTitle(getContext().getString(R.string.title_settings, 170 info.loadLabel(packageManager))); 171 preference.setKey(key); 172 preference.setIntent(settingsIntent); 173 enabledInputMethodKeys.add(key); 174 } 175 176 for (int i = 0; i < preferenceScreen.getPreferenceCount();) { 177 final Preference preference = preferenceScreen.getPreference(i); 178 final String key = preference.getKey(); 179 if (!TextUtils.isEmpty(key) 180 && key.startsWith(KEY_KEYBOARD_SETTINGS_PREFIX) 181 && !enabledInputMethodKeys.contains(key)) { 182 preferenceScreen.removePreference(preference); 183 } else { 184 i++; 185 } 186 } 187 } 188 189 /** 190 * Update autofill related preferences. 191 */ updateAutofill()192 private void updateAutofill() { 193 final PreferenceCategory autofillCategory = (PreferenceCategory) 194 findPreference(KEY_AUTOFILL_CATEGORY); 195 List<DefaultAppInfo> candidates = getAutofillCandidates(); 196 if (candidates.isEmpty()) { 197 // No need to show keyboard category and autofill category. 198 // Keyboard only preference screen: 199 findPreference(KEY_KEYBOARD_CATEGORY).setVisible(false); 200 autofillCategory.setVisible(false); 201 getPreferenceScreen().setTitle(R.string.system_keyboard); 202 } else { 203 // Show both keyboard category and autofill category in keyboard & autofill screen. 204 findPreference(KEY_KEYBOARD_CATEGORY).setVisible(true); 205 autofillCategory.setVisible(true); 206 final Preference currentAutofillPref = findPreference(KEY_CURRENT_AUTOFILL); 207 updateCurrentAutofillPreference(currentAutofillPref, candidates); 208 updateAutofillSettings(candidates); 209 getPreferenceScreen().setTitle(R.string.system_keyboard_autofill); 210 } 211 212 } 213 getAutofillCandidates()214 private List<DefaultAppInfo> getAutofillCandidates() { 215 return AutofillHelper.getAutofillCandidates(getContext(), 216 mPm, UserHandle.myUserId()); 217 } 218 updateCurrentAutofillPreference(Preference currentAutofillPref, List<DefaultAppInfo> candidates)219 private void updateCurrentAutofillPreference(Preference currentAutofillPref, 220 List<DefaultAppInfo> candidates) { 221 222 DefaultAppInfo app = AutofillHelper.getCurrentAutofill(getContext(), candidates); 223 224 CharSequence summary = app == null ? getContext().getString(R.string.autofill_none) 225 : app.loadLabel(); 226 currentAutofillPref.setSummary(summary); 227 } 228 updateAutofillSettings(List<DefaultAppInfo> candidates)229 private void updateAutofillSettings(List<DefaultAppInfo> candidates) { 230 final Context preferenceContext = getPreferenceContext(); 231 232 final PreferenceCategory autofillCategory = (PreferenceCategory) 233 findPreference(KEY_AUTOFILL_CATEGORY); 234 235 final Set<String> autofillServicesKeys = new ArraySet<>(candidates.size()); 236 for (final DefaultAppInfo info : candidates) { 237 final Intent settingsIntent = AutofillHelper.getAutofillSettingsIntent(getContext(), 238 mPm, info); 239 if (settingsIntent == null) { 240 continue; 241 } 242 final String key = KEY_AUTOFILL_SETTINGS_PREFIX + info.getKey(); 243 244 Preference preference = findPreference(key); 245 if (preference == null) { 246 preference = new Preference(preferenceContext); 247 autofillCategory.addPreference(preference); 248 } 249 preference.setTitle(getContext().getString(R.string.title_settings, info.loadLabel())); 250 preference.setKey(key); 251 preference.setIntent(settingsIntent); 252 autofillServicesKeys.add(key); 253 } 254 255 for (int i = 0; i < autofillCategory.getPreferenceCount();) { 256 final Preference preference = autofillCategory.getPreference(i); 257 final String key = preference.getKey(); 258 if (!TextUtils.isEmpty(key) 259 && key.startsWith(KEY_AUTOFILL_SETTINGS_PREFIX) 260 && !autofillServicesKeys.contains(key)) { 261 autofillCategory.removePreference(preference); 262 } else { 263 i++; 264 } 265 } 266 } 267 268 @Override getMetricsCategory()269 public int getMetricsCategory() { 270 return MetricsProto.MetricsEvent.INPUTMETHOD_KEYBOARD; 271 } 272 } 273