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