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