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