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