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.annotation.DrawableRes;
20 import android.annotation.NonNull;
21 import android.annotation.Nullable;
22 import android.app.Activity;
23 import android.app.admin.DevicePolicyManager;
24 import android.app.tvsettings.TvSettingsEnums;
25 import android.content.Context;
26 import android.content.pm.ApplicationInfo;
27 import android.content.pm.PackageManager;
28 import android.content.pm.ServiceInfo;
29 import android.content.res.Configuration;
30 import android.graphics.Color;
31 import android.graphics.drawable.ColorDrawable;
32 import android.graphics.drawable.Drawable;
33 import android.os.Bundle;
34 import android.view.inputmethod.InputMethodInfo;
35 import android.view.inputmethod.InputMethodManager;
36 
37 import androidx.annotation.Keep;
38 import androidx.preference.PreferenceScreen;
39 
40 import com.android.internal.logging.nano.MetricsProto;
41 import com.android.settingslib.inputmethod.InputMethodAndSubtypeUtil;
42 import com.android.settingslib.inputmethod.InputMethodPreference;
43 import com.android.settingslib.inputmethod.InputMethodSettingValuesWrapper;
44 import com.android.tv.settings.R;
45 import com.android.tv.settings.SettingsPreferenceFragment;
46 
47 import java.text.Collator;
48 import java.util.ArrayList;
49 import java.util.List;
50 
51 /**
52  * Fragment for enabling/disabling virtual keyboard IMEs
53  */
54 @Keep
55 public final class AvailableVirtualKeyboardFragment extends SettingsPreferenceFragment
56         implements InputMethodPreference.OnSavePreferenceListener {
57 
58     private final ArrayList<InputMethodPreference> mInputMethodPreferenceList = new ArrayList<>();
59     private InputMethodSettingValuesWrapper mInputMethodSettingValues;
60     private InputMethodManager mImm;
61     private DevicePolicyManager mDpm;
62 
63     @Override
onCreatePreferences(Bundle bundle, String s)64     public void onCreatePreferences(Bundle bundle, String s) {
65         Activity activity = getActivity();
66         PreferenceScreen screen = getPreferenceManager().createPreferenceScreen(activity);
67         screen.setTitle(activity.getString(R.string.available_virtual_keyboard_category));
68         setPreferenceScreen(screen);
69         mInputMethodSettingValues = InputMethodSettingValuesWrapper.getInstance(activity);
70         mImm = activity.getSystemService(InputMethodManager.class);
71         mDpm = activity.getSystemService(DevicePolicyManager.class);
72     }
73 
74     @Override
onResume()75     public void onResume() {
76         super.onResume();
77         // Refresh internal states in mInputMethodSettingValues to keep the latest
78         // "InputMethodInfo"s and "InputMethodSubtype"s
79         mInputMethodSettingValues.refreshAllInputMethodAndSubtypes();
80         updateInputMethodPreferenceViews();
81     }
82 
83     @Override
onSaveInputMethodPreference(final InputMethodPreference pref)84     public void onSaveInputMethodPreference(final InputMethodPreference pref) {
85         final boolean hasHardwareKeyboard = getResources().getConfiguration().keyboard
86                 == Configuration.KEYBOARD_QWERTY;
87         InputMethodAndSubtypeUtil.saveInputMethodSubtypeList(this,
88                 getContext().getContentResolver(), mImm.getInputMethodList(), hasHardwareKeyboard);
89         // Update input method settings and preference list.
90         mInputMethodSettingValues.refreshAllInputMethodAndSubtypes();
91         for (final InputMethodPreference p : mInputMethodPreferenceList) {
92             p.updatePreferenceViews();
93         }
94     }
95 
96     @Nullable
loadDrawable(@onNull final PackageManager packageManager, @NonNull final String packageName, @DrawableRes final int resId, @NonNull final ApplicationInfo applicationInfo)97     private static Drawable loadDrawable(@NonNull final PackageManager packageManager,
98             @NonNull final String packageName, @DrawableRes final int resId,
99             @NonNull final ApplicationInfo applicationInfo) {
100         if (resId == 0) {
101             return null;
102         }
103         try {
104             return packageManager.getDrawable(packageName, resId, applicationInfo);
105         } catch (Exception e) {
106             return null;
107         }
108     }
109 
110     @NonNull
getInputMethodIcon(@onNull final PackageManager packageManager, @NonNull final InputMethodInfo imi)111     private static Drawable getInputMethodIcon(@NonNull final PackageManager packageManager,
112             @NonNull final InputMethodInfo imi) {
113         final ServiceInfo si = imi.getServiceInfo();
114         final ApplicationInfo ai = si != null ? si.applicationInfo : null;
115         final String packageName = imi.getPackageName();
116         if (si == null || ai == null || packageName == null) {
117             return new ColorDrawable(Color.TRANSPARENT);
118         }
119         // We do not use ServiceInfo#loadLogo() and ServiceInfo#loadIcon here since those methods
120         // internally have some fallback rules, which we want to do manually.
121         Drawable drawable = loadDrawable(packageManager, packageName, si.logo, ai);
122         if (drawable != null) {
123             return drawable;
124         }
125         drawable = loadDrawable(packageManager, packageName, si.icon, ai);
126         if (drawable != null) {
127             return drawable;
128         }
129         // We do not use ApplicationInfo#loadLogo() and ApplicationInfo#loadIcon here since those
130         // methods internally have some fallback rules, which we want to do manually.
131         drawable = loadDrawable(packageManager, packageName, ai.logo, ai);
132         if (drawable != null) {
133             return drawable;
134         }
135         drawable = loadDrawable(packageManager, packageName, ai.icon, ai);
136         if (drawable != null) {
137             return drawable;
138         }
139         return new ColorDrawable(Color.TRANSPARENT);
140     }
141 
updateInputMethodPreferenceViews()142     private void updateInputMethodPreferenceViews() {
143         mInputMethodSettingValues.refreshAllInputMethodAndSubtypes();
144         // Clear existing "InputMethodPreference"s
145         mInputMethodPreferenceList.clear();
146         List<String> permittedList = mDpm.getPermittedInputMethodsForCurrentUser();
147         final Context context = getPreferenceManager().getContext();
148         final PackageManager packageManager = getActivity().getPackageManager();
149         final List<InputMethodInfo> imis = mInputMethodSettingValues.getInputMethodList();
150         final int numImis = (imis == null ? 0 : imis.size());
151         for (int i = 0; i < numImis; ++i) {
152             final InputMethodInfo imi = imis.get(i);
153             final boolean isAllowedByOrganization = permittedList == null
154                     || permittedList.contains(imi.getPackageName());
155             final InputMethodPreference pref = new InputMethodPreference(
156                     context, imi, true, isAllowedByOrganization, this);
157             // TODO: Update the icon container in leanback_preference.xml to use LinearLayout.
158             // This is a workaround to avoid the crash. b/146654624
159             pref.setIconSize(0);
160             pref.setIcon(getInputMethodIcon(packageManager, imi));
161             mInputMethodPreferenceList.add(pref);
162         }
163         final Collator collator = Collator.getInstance();
164         mInputMethodPreferenceList.sort((lhs, rhs) -> lhs.compareTo(rhs, collator));
165         getPreferenceScreen().removeAll();
166         for (int i = 0; i < numImis; ++i) {
167             final InputMethodPreference pref = mInputMethodPreferenceList.get(i);
168             pref.setOrder(i);
169             getPreferenceScreen().addPreference(pref);
170             InputMethodAndSubtypeUtil.removeUnnecessaryNonPersistentPreference(pref);
171             pref.updatePreferenceViews();
172         }
173     }
174 
175     @Override
getMetricsCategory()176     public int getMetricsCategory() {
177         return MetricsProto.MetricsEvent.ENABLE_VIRTUAL_KEYBOARDS;
178     }
179 
180     @Override
getPageId()181     protected int getPageId() {
182         return TvSettingsEnums.SYSTEM_KEYBOARD_MANAGE_KEYBOARDS;
183     }
184 }
185