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.app.admin.DevicePolicyManager; 20 import android.app.settings.SettingsEnums; 21 import android.content.Context; 22 import android.content.res.Configuration; 23 import android.os.Bundle; 24 import android.os.UserHandle; 25 import android.os.UserManager; 26 import android.provider.SearchIndexableResource; 27 import android.view.inputmethod.InputMethodInfo; 28 import android.view.inputmethod.InputMethodManager; 29 30 import com.android.internal.annotations.VisibleForTesting; 31 import com.android.settings.R; 32 import com.android.settings.Utils; 33 import com.android.settings.dashboard.DashboardFragment; 34 import com.android.settings.dashboard.profileselector.ProfileSelectFragment; 35 import com.android.settings.search.BaseSearchIndexProvider; 36 import com.android.settingslib.inputmethod.InputMethodAndSubtypeUtilCompat; 37 import com.android.settingslib.inputmethod.InputMethodPreference; 38 import com.android.settingslib.inputmethod.InputMethodSettingValuesWrapper; 39 import com.android.settingslib.search.SearchIndexable; 40 41 import java.text.Collator; 42 import java.util.ArrayList; 43 import java.util.List; 44 45 /** 46 * The fragment for on-screen keyboard settings which used to display user installed IMEs. 47 */ 48 @SearchIndexable 49 public class AvailableVirtualKeyboardFragment extends DashboardFragment 50 implements InputMethodPreference.OnSavePreferenceListener { 51 private static final String TAG = "AvailableVirtualKeyboardFragment"; 52 53 @VisibleForTesting 54 final ArrayList<InputMethodPreference> mInputMethodPreferenceList = new ArrayList<>(); 55 56 @VisibleForTesting 57 InputMethodSettingValuesWrapper mInputMethodSettingValues; 58 59 @VisibleForTesting 60 Context mUserAwareContext; 61 62 private int mUserId; 63 64 @Override onCreatePreferences(Bundle bundle, String s)65 public void onCreatePreferences(Bundle bundle, String s) { 66 addPreferencesFromResource(R.xml.available_virtual_keyboard); 67 mInputMethodSettingValues = InputMethodSettingValuesWrapper.getInstance(mUserAwareContext); 68 } 69 70 @Override onAttach(Context context)71 public void onAttach(Context context) { 72 super.onAttach(context); 73 final int profileType = getArguments().getInt(ProfileSelectFragment.EXTRA_PROFILE); 74 final UserManager userManager = context.getSystemService(UserManager.class); 75 final int currentUserId = UserHandle.myUserId(); 76 final int newUserId; 77 final Context newUserAwareContext; 78 switch (profileType) { 79 case ProfileSelectFragment.ProfileType.WORK: { 80 // If the user is a managed profile user, use currentUserId directly. Or get the 81 // managed profile userId instead. 82 newUserId = userManager.isManagedProfile() 83 ? currentUserId : Utils.getManagedProfileId(userManager, currentUserId); 84 newUserAwareContext = context.createContextAsUser(UserHandle.of(newUserId), 0); 85 break; 86 } 87 case ProfileSelectFragment.ProfileType.PRIVATE: { 88 // If the user is a private profile user, use currentUserId directly. Or get the 89 // private profile userId instead. 90 newUserId = userManager.isPrivateProfile() 91 ? currentUserId 92 : Utils.getCurrentUserIdOfType( 93 userManager, ProfileSelectFragment.ProfileType.PRIVATE); 94 newUserAwareContext = context.createContextAsUser(UserHandle.of(newUserId), 0); 95 break; 96 } 97 case ProfileSelectFragment.ProfileType.PERSONAL: { 98 // Use the parent user of the current user if the current user is profile. 99 final UserHandle currentUser = UserHandle.of(currentUserId); 100 final UserHandle userProfileParent = userManager.getProfileParent(currentUser); 101 if (userProfileParent != null) { 102 newUserId = userProfileParent.getIdentifier(); 103 newUserAwareContext = context.createContextAsUser(userProfileParent, 0); 104 } else { 105 newUserId = currentUserId; 106 newUserAwareContext = context; 107 } 108 break; 109 } 110 default: 111 newUserId = currentUserId; 112 newUserAwareContext = context; 113 } 114 mUserId = newUserId; 115 mUserAwareContext = newUserAwareContext; 116 } 117 118 @Override onResume()119 public void onResume() { 120 super.onResume(); 121 // Refresh internal states in mInputMethodSettingValues to keep the latest 122 // "InputMethodInfo"s and "InputMethodSubtype"s 123 mInputMethodSettingValues.refreshAllInputMethodAndSubtypes(); 124 updateInputMethodPreferenceViews(); 125 } 126 127 @Override getPreferenceScreenResId()128 protected int getPreferenceScreenResId() { 129 return R.xml.available_virtual_keyboard; 130 } 131 132 @Override getLogTag()133 protected String getLogTag() { 134 return TAG; 135 } 136 137 @Override onSaveInputMethodPreference(final InputMethodPreference pref)138 public void onSaveInputMethodPreference(final InputMethodPreference pref) { 139 final boolean hasHardwareKeyboard = getResources().getConfiguration().keyboard 140 == Configuration.KEYBOARD_QWERTY; 141 InputMethodAndSubtypeUtilCompat.saveInputMethodSubtypeListForUser(this, 142 mUserAwareContext.getContentResolver(), 143 getContext().getSystemService( 144 InputMethodManager.class).getInputMethodListAsUser(mUserId), 145 hasHardwareKeyboard, mUserId); 146 // Update input method settings and preference list. 147 mInputMethodSettingValues.refreshAllInputMethodAndSubtypes(); 148 for (final InputMethodPreference p : mInputMethodPreferenceList) { 149 p.updatePreferenceViews(); 150 } 151 } 152 153 @Override getMetricsCategory()154 public int getMetricsCategory() { 155 return SettingsEnums.ENABLE_VIRTUAL_KEYBOARDS; 156 } 157 158 @VisibleForTesting updateInputMethodPreferenceViews()159 void updateInputMethodPreferenceViews() { 160 mInputMethodSettingValues.refreshAllInputMethodAndSubtypes(); 161 // Clear existing "InputMethodPreference"s 162 mInputMethodPreferenceList.clear(); 163 final List<String> permittedList = mUserAwareContext.getSystemService( 164 DevicePolicyManager.class).getPermittedInputMethods(); 165 final Context prefContext = getPrefContext(); 166 final List<InputMethodInfo> imis = mInputMethodSettingValues.getInputMethodList(); 167 final List<InputMethodInfo> enabledImis = getContext().getSystemService( 168 InputMethodManager.class).getEnabledInputMethodListAsUser(UserHandle.of(mUserId)); 169 final int numImis = (imis == null ? 0 : imis.size()); 170 for (int i = 0; i < numImis; ++i) { 171 final InputMethodInfo imi = imis.get(i); 172 // TODO (b/182876800): Move this logic out of isAllowedByOrganization and 173 // into a new boolean. 174 // If an input method is enabled but not included in the permitted list, then set it as 175 // allowed by organization. Doing so will allow the user to disable the input method and 176 // remain complaint with the organization's policy. Once disabled, the input method 177 // cannot be re-enabled because it is not in the permitted list. 178 final boolean isAllowedByOrganization = permittedList == null 179 || permittedList.contains(imi.getPackageName()) 180 || enabledImis.contains(imi); 181 final InputMethodPreference pref = new InputMethodPreference(prefContext, imi, 182 isAllowedByOrganization, this, mUserId); 183 pref.setIcon(imi.loadIcon(mUserAwareContext.getPackageManager())); 184 mInputMethodPreferenceList.add(pref); 185 } 186 final Collator collator = Collator.getInstance(); 187 mInputMethodPreferenceList.sort((lhs, rhs) -> lhs.compareTo(rhs, collator)); 188 getPreferenceScreen().removeAll(); 189 for (int i = 0; i < numImis; ++i) { 190 final InputMethodPreference pref = mInputMethodPreferenceList.get(i); 191 pref.setOrder(i); 192 getPreferenceScreen().addPreference(pref); 193 InputMethodAndSubtypeUtilCompat.removeUnnecessaryNonPersistentPreference(pref); 194 pref.updatePreferenceViews(); 195 } 196 } 197 198 public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER = 199 new BaseSearchIndexProvider() { 200 @Override 201 public List<SearchIndexableResource> getXmlResourcesToIndex(Context context, 202 boolean enabled) { 203 List<SearchIndexableResource> res = new ArrayList<>(); 204 SearchIndexableResource index = new SearchIndexableResource(context); 205 index.xmlResId = R.xml.available_virtual_keyboard; 206 res.add(index); 207 return res; 208 } 209 }; 210 } 211