1 /* 2 * Copyright (C) 2016 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.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.content.ComponentName; 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.support.v7.preference.PreferenceScreen; 35 import android.view.inputmethod.InputMethodInfo; 36 import android.view.inputmethod.InputMethodManager; 37 import android.view.inputmethod.InputMethodSubtype; 38 39 import com.android.internal.logging.nano.MetricsProto.MetricsEvent; 40 import com.android.settings.R; 41 import com.android.settings.SettingsPreferenceFragment; 42 import com.android.settings.search.BaseSearchIndexProvider; 43 import com.android.settings.search.Indexable; 44 import com.android.settings.search.SearchIndexableRaw; 45 import com.android.settingslib.inputmethod.InputMethodAndSubtypeUtil; 46 import com.android.settingslib.inputmethod.InputMethodPreference; 47 import com.android.settingslib.inputmethod.InputMethodSettingValuesWrapper; 48 49 import java.text.Collator; 50 import java.util.ArrayList; 51 import java.util.List; 52 53 public final class AvailableVirtualKeyboardFragment extends SettingsPreferenceFragment 54 implements InputMethodPreference.OnSavePreferenceListener, Indexable { 55 56 private final ArrayList<InputMethodPreference> mInputMethodPreferenceList = new ArrayList<>(); 57 private InputMethodSettingValuesWrapper mInputMethodSettingValues; 58 private InputMethodManager mImm; 59 private DevicePolicyManager mDpm; 60 61 @Override onCreatePreferences(Bundle bundle, String s)62 public void onCreatePreferences(Bundle bundle, String s) { 63 Activity activity = getActivity(); 64 PreferenceScreen screen = getPreferenceManager().createPreferenceScreen(activity); 65 screen.setTitle(activity.getString(R.string.available_virtual_keyboard_category)); 66 setPreferenceScreen(screen); 67 mInputMethodSettingValues = InputMethodSettingValuesWrapper.getInstance(activity); 68 mImm = activity.getSystemService(InputMethodManager.class); 69 mDpm = activity.getSystemService(DevicePolicyManager.class); 70 } 71 72 @Override onResume()73 public void onResume() { 74 super.onResume(); 75 // Refresh internal states in mInputMethodSettingValues to keep the latest 76 // "InputMethodInfo"s and "InputMethodSubtype"s 77 mInputMethodSettingValues.refreshAllInputMethodAndSubtypes(); 78 updateInputMethodPreferenceViews(); 79 } 80 81 @Override onSaveInputMethodPreference(final InputMethodPreference pref)82 public void onSaveInputMethodPreference(final InputMethodPreference pref) { 83 final boolean hasHardwareKeyboard = getResources().getConfiguration().keyboard 84 == Configuration.KEYBOARD_QWERTY; 85 InputMethodAndSubtypeUtil.saveInputMethodSubtypeList(this, getContentResolver(), 86 mImm.getInputMethodList(), hasHardwareKeyboard); 87 // Update input method settings and preference list. 88 mInputMethodSettingValues.refreshAllInputMethodAndSubtypes(); 89 for (final InputMethodPreference p : mInputMethodPreferenceList) { 90 p.updatePreferenceViews(); 91 } 92 } 93 94 @Override getMetricsCategory()95 public int getMetricsCategory() { 96 return MetricsEvent.ENABLE_VIRTUAL_KEYBOARDS; 97 } 98 99 @Nullable loadDrawable(@onNull final PackageManager packageManager, @NonNull final String packageName, @DrawableRes final int resId, @NonNull final ApplicationInfo applicationInfo)100 private static Drawable loadDrawable(@NonNull final PackageManager packageManager, 101 @NonNull final String packageName, @DrawableRes final int resId, 102 @NonNull final ApplicationInfo applicationInfo) { 103 if (resId == 0) { 104 return null; 105 } 106 try { 107 return packageManager.getDrawable(packageName, resId, applicationInfo); 108 } catch (Exception e){ 109 return null; 110 } 111 } 112 113 @NonNull getInputMethodIcon(@onNull final PackageManager packageManager, @NonNull final InputMethodInfo imi)114 private static Drawable getInputMethodIcon(@NonNull final PackageManager packageManager, 115 @NonNull final InputMethodInfo imi) { 116 final ServiceInfo si = imi.getServiceInfo(); 117 final ApplicationInfo ai = si != null ? si.applicationInfo : null; 118 final String packageName = imi.getPackageName(); 119 if (si == null || ai == null || packageName == null) { 120 return new ColorDrawable(Color.TRANSPARENT); 121 } 122 // We do not use ServiceInfo#loadLogo() and ServiceInfo#loadIcon here since those methods 123 // internally have some fallback rules, which we want to do manually. 124 Drawable drawable = loadDrawable(packageManager, packageName, si.logo, ai); 125 if (drawable != null) { 126 return drawable; 127 } 128 drawable = loadDrawable(packageManager, packageName, si.icon, ai); 129 if (drawable != null) { 130 return drawable; 131 } 132 // We do not use ApplicationInfo#loadLogo() and ApplicationInfo#loadIcon here since those 133 // methods internally have some fallback rules, which we want to do manually. 134 drawable = loadDrawable(packageManager, packageName, ai.logo, ai); 135 if (drawable != null) { 136 return drawable; 137 } 138 drawable = loadDrawable(packageManager, packageName, ai.icon, ai); 139 if (drawable != null) { 140 return drawable; 141 } 142 return new ColorDrawable(Color.TRANSPARENT); 143 } 144 updateInputMethodPreferenceViews()145 private void updateInputMethodPreferenceViews() { 146 mInputMethodSettingValues.refreshAllInputMethodAndSubtypes(); 147 // Clear existing "InputMethodPreference"s 148 mInputMethodPreferenceList.clear(); 149 List<String> permittedList = mDpm.getPermittedInputMethodsForCurrentUser(); 150 final Context context = getPrefContext(); 151 final PackageManager packageManager = getActivity().getPackageManager(); 152 final List<InputMethodInfo> imis = mInputMethodSettingValues.getInputMethodList(); 153 final int numImis = (imis == null ? 0 : imis.size()); 154 for (int i = 0; i < numImis; ++i) { 155 final InputMethodInfo imi = imis.get(i); 156 final boolean isAllowedByOrganization = permittedList == null 157 || permittedList.contains(imi.getPackageName()); 158 final InputMethodPreference pref = new InputMethodPreference( 159 context, imi, true, isAllowedByOrganization, this); 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 getAllSubtypesOf(final InputMethodInfo imi)175 private static List<InputMethodSubtype> getAllSubtypesOf(final InputMethodInfo imi) { 176 final int subtypeCount = imi.getSubtypeCount(); 177 final List<InputMethodSubtype> allSubtypes = new ArrayList<>(subtypeCount); 178 for (int index = 0; index < subtypeCount; index++) { 179 allSubtypes.add(imi.getSubtypeAt(index)); 180 } 181 return allSubtypes; 182 } 183 buildSearchIndexOfInputMethods(final Context context, final List<InputMethodInfo> inputMethods, final String screenTitle)184 static List<SearchIndexableRaw> buildSearchIndexOfInputMethods(final Context context, 185 final List<InputMethodInfo> inputMethods, final String screenTitle) { 186 final List<SearchIndexableRaw> indexes = new ArrayList<>(); 187 for (int i = 0; i < inputMethods.size(); i++) { 188 final InputMethodInfo imi = inputMethods.get(i); 189 final ServiceInfo serviceInfo = imi.getServiceInfo(); 190 final SearchIndexableRaw index = new SearchIndexableRaw(context); 191 index.key = new ComponentName(serviceInfo.packageName, serviceInfo.name) 192 .flattenToString(); 193 index.title = imi.loadLabel(context.getPackageManager()).toString(); 194 index.summaryOn = index.summaryOff = InputMethodAndSubtypeUtil 195 .getSubtypeLocaleNameListAsSentence(getAllSubtypesOf(imi), context, imi); 196 index.screenTitle = screenTitle; 197 indexes.add(index); 198 } 199 return indexes; 200 } 201 202 public static final Indexable.SearchIndexProvider SEARCH_INDEX_DATA_PROVIDER = 203 new BaseSearchIndexProvider() { 204 @Override 205 public List<SearchIndexableRaw> getRawDataToIndex(Context context, boolean enabled) { 206 final InputMethodManager imm = context.getSystemService(InputMethodManager.class); 207 final List<InputMethodInfo> enabledInputMethods = imm.getEnabledInputMethodList(); 208 final List<InputMethodInfo> disabledInputMethods = new ArrayList<>(); 209 for (final InputMethodInfo imi : imm.getInputMethodList()) { 210 if (!enabledInputMethods.contains(imi)) { 211 disabledInputMethods.add(imi); 212 } 213 } 214 final String screenTitle = context.getString( 215 R.string.available_virtual_keyboard_category); 216 return buildSearchIndexOfInputMethods(context, disabledInputMethods, screenTitle); 217 } 218 }; 219 } 220