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