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 static com.android.tv.settings.util.InstrumentationUtils.logEntrySelected; 20 21 import android.app.tvsettings.TvSettingsEnums; 22 import android.content.Context; 23 import android.content.Intent; 24 import android.content.pm.PackageManager; 25 import android.os.Bundle; 26 import android.os.UserHandle; 27 import android.text.TextUtils; 28 import android.util.ArraySet; 29 import android.view.inputmethod.InputMethodInfo; 30 31 import androidx.annotation.Keep; 32 import androidx.annotation.VisibleForTesting; 33 import androidx.preference.ListPreference; 34 import androidx.preference.Preference; 35 import androidx.preference.PreferenceCategory; 36 import androidx.preference.PreferenceScreen; 37 38 import com.android.settingslib.applications.DefaultAppInfo; 39 import com.android.tv.settings.R; 40 import com.android.tv.settings.SettingsPreferenceFragment; 41 import com.android.tv.settings.autofill.AutofillHelper; 42 import com.android.tv.settings.overlay.FlavorUtils; 43 import com.android.tv.settings.util.SliceUtils; 44 import com.android.tv.twopanelsettings.slices.SlicePreference; 45 46 import java.util.ArrayList; 47 import java.util.List; 48 import java.util.Set; 49 50 /** 51 * Fragment for managing IMEs and Autofills 52 */ 53 @Keep 54 public class KeyboardFragment extends SettingsPreferenceFragment { 55 private static final String TAG = "KeyboardFragment"; 56 57 // Order of input methods, make sure they are inserted between 1 (currentKeyboard) and 58 // 3 (manageKeyboards). 59 private static final int INPUT_METHOD_PREFERENCE_ORDER = 2; 60 61 @VisibleForTesting 62 static final String KEY_KEYBOARD_CATEGORY = "keyboardCategory"; 63 64 @VisibleForTesting 65 static final String KEY_CURRENT_KEYBOARD = "currentKeyboard"; 66 67 private static final String KEY_KEYBOARD_SETTINGS_PREFIX = "keyboardSettings:"; 68 69 @VisibleForTesting 70 static final String KEY_AUTOFILL_CATEGORY = "autofillCategory"; 71 72 @VisibleForTesting 73 static final String KEY_CURRENT_AUTOFILL = "currentAutofill"; 74 75 private static final String KEY_AUTOFILL_SETTINGS_PREFIX = "autofillSettings:"; 76 77 private PackageManager mPm; 78 79 /** 80 * @return New fragment instance 81 */ newInstance()82 public static KeyboardFragment newInstance() { 83 return new KeyboardFragment(); 84 } 85 86 @Override onAttach(Context context)87 public void onAttach(Context context) { 88 super.onAttach(context); 89 mPm = context.getPackageManager(); 90 } 91 92 @Override onCreatePreferences(Bundle savedInstanceState, String rootKey)93 public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { 94 setPreferencesFromResource(R.xml.keyboard, null); 95 96 findPreference(KEY_CURRENT_KEYBOARD).setOnPreferenceChangeListener( 97 (preference, newValue) -> { 98 logEntrySelected(TvSettingsEnums.SYSTEM_KEYBOARD_CURRENT_KEYBOARD); 99 InputMethodHelper.setDefaultInputMethodId(getContext(), (String) newValue); 100 return true; 101 }); 102 103 updateUi(); 104 } 105 106 @Override onResume()107 public void onResume() { 108 super.onResume(); 109 updateUi(); 110 } 111 112 @VisibleForTesting updateUi()113 void updateUi() { 114 updateAutofill(); 115 updateKeyboards(); 116 } 117 updateKeyboards()118 private void updateKeyboards() { 119 updateCurrentKeyboardPreference((ListPreference) findPreference(KEY_CURRENT_KEYBOARD)); 120 updateKeyboardsSettings(); 121 } 122 updateCurrentKeyboardPreference(ListPreference currentKeyboardPref)123 private void updateCurrentKeyboardPreference(ListPreference currentKeyboardPref) { 124 final PackageManager packageManager = getContext().getPackageManager(); 125 List<InputMethodInfo> enabledInputMethodInfos = InputMethodHelper 126 .getEnabledSystemInputMethodList(getContext()); 127 final List<CharSequence> entries = new ArrayList<>(enabledInputMethodInfos.size()); 128 final List<CharSequence> values = new ArrayList<>(enabledInputMethodInfos.size()); 129 130 int defaultIndex = 0; 131 final String defaultId = InputMethodHelper.getDefaultInputMethodId(getContext()); 132 133 for (final InputMethodInfo info : enabledInputMethodInfos) { 134 entries.add(info.loadLabel(packageManager)); 135 final String id = info.getId(); 136 values.add(id); 137 if (TextUtils.equals(id, defaultId)) { 138 defaultIndex = values.size() - 1; 139 } 140 } 141 142 currentKeyboardPref.setEntries(entries.toArray(new CharSequence[entries.size()])); 143 currentKeyboardPref.setEntryValues(values.toArray(new CharSequence[values.size()])); 144 if (entries.size() > 0) { 145 currentKeyboardPref.setValueIndex(defaultIndex); 146 } 147 } 148 getPreferenceContext()149 Context getPreferenceContext() { 150 return getPreferenceManager().getContext(); 151 } 152 updateKeyboardsSettings()153 private void updateKeyboardsSettings() { 154 final Context preferenceContext = getPreferenceContext(); 155 final PackageManager packageManager = getContext().getPackageManager(); 156 List<InputMethodInfo> enabledInputMethodInfos = InputMethodHelper 157 .getEnabledSystemInputMethodList(getContext()); 158 159 PreferenceScreen preferenceScreen = getPreferenceScreen(); 160 final Set<String> enabledInputMethodKeys = new ArraySet<>(enabledInputMethodInfos.size()); 161 // Add per-IME settings 162 for (final InputMethodInfo info : enabledInputMethodInfos) { 163 final String uri = InputMethodHelper.getInputMethodsSettingsUri(getContext(), info); 164 final Intent settingsIntent = InputMethodHelper.getInputMethodSettingsIntent(info); 165 if (uri == null && settingsIntent == null) { 166 continue; 167 } 168 final String key = KEY_KEYBOARD_SETTINGS_PREFIX + info.getId(); 169 170 Preference preference = preferenceScreen.findPreference(key); 171 boolean useSlice = FlavorUtils.isTwoPanel(getContext()) && uri != null; 172 if (preference == null) { 173 if (useSlice) { 174 preference = new SlicePreference(preferenceContext); 175 } else { 176 preference = new Preference(preferenceContext); 177 } 178 preference.setOrder(INPUT_METHOD_PREFERENCE_ORDER); 179 preferenceScreen.addPreference(preference); 180 } 181 preference.setTitle(getContext().getString(R.string.title_settings, 182 info.loadLabel(packageManager))); 183 preference.setKey(key); 184 if (useSlice) { 185 ((SlicePreference) preference).setUri(uri); 186 preference.setFragment(SliceUtils.PATH_SLICE_FRAGMENT); 187 } else { 188 preference.setIntent(settingsIntent); 189 } 190 enabledInputMethodKeys.add(key); 191 } 192 193 for (int i = 0; i < preferenceScreen.getPreferenceCount(); ) { 194 final Preference preference = preferenceScreen.getPreference(i); 195 final String key = preference.getKey(); 196 if (!TextUtils.isEmpty(key) 197 && key.startsWith(KEY_KEYBOARD_SETTINGS_PREFIX) 198 && !enabledInputMethodKeys.contains(key)) { 199 preferenceScreen.removePreference(preference); 200 } else { 201 i++; 202 } 203 } 204 } 205 206 /** 207 * Update autofill related preferences. 208 */ updateAutofill()209 private void updateAutofill() { 210 final PreferenceCategory autofillCategory = (PreferenceCategory) 211 findPreference(KEY_AUTOFILL_CATEGORY); 212 List<DefaultAppInfo> candidates = getAutofillCandidates(); 213 if (candidates.isEmpty()) { 214 // No need to show keyboard category and autofill category. 215 // Keyboard only preference screen: 216 findPreference(KEY_KEYBOARD_CATEGORY).setVisible(false); 217 autofillCategory.setVisible(false); 218 getPreferenceScreen().setTitle(R.string.system_keyboard); 219 } else { 220 // Show both keyboard category and autofill category in keyboard & autofill screen. 221 findPreference(KEY_KEYBOARD_CATEGORY).setVisible(true); 222 autofillCategory.setVisible(true); 223 final Preference currentAutofillPref = findPreference(KEY_CURRENT_AUTOFILL); 224 updateCurrentAutofillPreference(currentAutofillPref, candidates); 225 updateAutofillSettings(candidates); 226 getPreferenceScreen().setTitle(R.string.system_keyboard_autofill); 227 } 228 229 } 230 getAutofillCandidates()231 private List<DefaultAppInfo> getAutofillCandidates() { 232 return AutofillHelper.getAutofillCandidates(getContext(), 233 mPm, UserHandle.myUserId()); 234 } 235 updateCurrentAutofillPreference(Preference currentAutofillPref, List<DefaultAppInfo> candidates)236 private void updateCurrentAutofillPreference(Preference currentAutofillPref, 237 List<DefaultAppInfo> candidates) { 238 239 DefaultAppInfo app = AutofillHelper.getCurrentAutofill(getContext(), candidates); 240 241 CharSequence summary = app == null ? getContext().getString(R.string.autofill_none) 242 : app.loadLabel(); 243 currentAutofillPref.setSummary(summary); 244 } 245 updateAutofillSettings(List<DefaultAppInfo> candidates)246 private void updateAutofillSettings(List<DefaultAppInfo> candidates) { 247 final Context preferenceContext = getPreferenceContext(); 248 249 final PreferenceCategory autofillCategory = (PreferenceCategory) 250 findPreference(KEY_AUTOFILL_CATEGORY); 251 252 final Set<String> autofillServicesKeys = new ArraySet<>(candidates.size()); 253 for (final DefaultAppInfo info : candidates) { 254 final Intent settingsIntent = AutofillHelper.getAutofillSettingsIntent(getContext(), 255 mPm, info); 256 if (settingsIntent == null) { 257 continue; 258 } 259 final String key = KEY_AUTOFILL_SETTINGS_PREFIX + info.getKey(); 260 261 Preference preference = findPreference(key); 262 if (preference == null) { 263 preference = new Preference(preferenceContext); 264 autofillCategory.addPreference(preference); 265 } 266 preference.setTitle(getContext().getString(R.string.title_settings, info.loadLabel())); 267 preference.setKey(key); 268 preference.setIntent(settingsIntent); 269 autofillServicesKeys.add(key); 270 } 271 272 for (int i = 0; i < autofillCategory.getPreferenceCount();) { 273 final Preference preference = autofillCategory.getPreference(i); 274 final String key = preference.getKey(); 275 if (!TextUtils.isEmpty(key) 276 && key.startsWith(KEY_AUTOFILL_SETTINGS_PREFIX) 277 && !autofillServicesKeys.contains(key)) { 278 autofillCategory.removePreference(preference); 279 } else { 280 i++; 281 } 282 } 283 } 284 285 @Override getPageId()286 protected int getPageId() { 287 return TvSettingsEnums.SYSTEM_KEYBOARD; 288 } 289 } 290