1 /* 2 * Copyright (C) 2018 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.content.Context; 20 import android.content.Intent; 21 import android.database.Cursor; 22 import android.provider.UserDictionary; 23 import android.text.TextUtils; 24 import android.view.inputmethod.InputMethodInfo; 25 import android.view.inputmethod.InputMethodManager; 26 import android.view.inputmethod.InputMethodSubtype; 27 28 import androidx.annotation.NonNull; 29 import androidx.annotation.VisibleForTesting; 30 import androidx.preference.Preference; 31 import androidx.preference.PreferenceScreen; 32 33 import com.android.settings.R; 34 import com.android.settings.Utils; 35 import com.android.settings.core.BasePreferenceController; 36 import com.android.settingslib.core.lifecycle.LifecycleObserver; 37 import com.android.settingslib.core.lifecycle.events.OnStart; 38 39 import java.util.List; 40 import java.util.Locale; 41 import java.util.TreeSet; 42 43 public class UserDictionaryListPreferenceController extends BasePreferenceController implements 44 LifecycleObserver, OnStart { 45 46 public static final String USER_DICTIONARY_SETTINGS_INTENT_ACTION = 47 "android.settings.USER_DICTIONARY_SETTINGS"; 48 private final String KEY_ALL_LANGUAGE = "all_languages"; 49 private String mLocale; 50 private PreferenceScreen mScreen; 51 UserDictionaryListPreferenceController(Context context, String key)52 public UserDictionaryListPreferenceController(Context context, String key) { 53 super(context, key); 54 } 55 setLocale(String locale)56 public void setLocale(String locale) { 57 mLocale = locale; 58 } 59 60 @Override getAvailabilityStatus()61 public int getAvailabilityStatus() { 62 return AVAILABLE; 63 } 64 65 @Override displayPreference(PreferenceScreen screen)66 public void displayPreference(PreferenceScreen screen) { 67 super.displayPreference(screen); 68 // This is to make newly inserted languages being sorted alphabetically when updating 69 // the existing preferenceScreen, and for "For all languages" to be always on the top. 70 screen.setOrderingAsAdded(false); 71 mScreen = screen; 72 } 73 74 @Override onStart()75 public void onStart() { 76 createUserDictSettings(); 77 } 78 79 @NonNull getUserDictionaryLocalesSet(Context context)80 public static TreeSet<String> getUserDictionaryLocalesSet(Context context) { 81 final Cursor cursor = context.getContentResolver().query( 82 UserDictionary.Words.CONTENT_URI, new String[]{UserDictionary.Words.LOCALE}, 83 null, null, null); 84 final TreeSet<String> localeSet = new TreeSet<>(); 85 if (cursor == null) { 86 // The user dictionary service is not present or disabled. Return empty set. 87 return localeSet; 88 } 89 try { 90 if (cursor.moveToFirst()) { 91 final int columnIndex = cursor.getColumnIndex(UserDictionary.Words.LOCALE); 92 do { 93 final String locale = cursor.getString(columnIndex); 94 localeSet.add(null != locale ? locale : ""); 95 } while (cursor.moveToNext()); 96 } 97 } finally { 98 cursor.close(); 99 } 100 101 // CAVEAT: Keep this for consistency of the implementation between Keyboard and Settings 102 // if (!UserDictionarySettings.IS_SHORTCUT_API_SUPPORTED) { 103 // // For ICS, we need to show "For all languages" in case that the keyboard locale 104 // // is different from the system locale 105 // localeSet.add(""); 106 // } 107 108 final InputMethodManager imm = 109 (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE); 110 final List<InputMethodInfo> imis = imm.getEnabledInputMethodList(); 111 for (final InputMethodInfo imi : imis) { 112 final List<InputMethodSubtype> subtypes = 113 imm.getEnabledInputMethodSubtypeList( 114 imi, true /* allowsImplicitlySelectedSubtypes */); 115 for (InputMethodSubtype subtype : subtypes) { 116 final String locale = subtype.getLocale(); 117 if (!TextUtils.isEmpty(locale)) { 118 localeSet.add(locale); 119 } 120 } 121 } 122 123 // We come here after we have collected locales from existing user dictionary entries and 124 // enabled subtypes. If we already have the locale-without-country version of the system 125 // locale, we don't add the system locale to avoid confusion even though it's technically 126 // correct to add it. 127 if (!localeSet.contains(Locale.getDefault().getLanguage())) { 128 localeSet.add(Locale.getDefault().toString()); 129 } 130 131 return localeSet; 132 } 133 134 @VisibleForTesting getUserDictLocalesSet(Context context)135 TreeSet<String> getUserDictLocalesSet(Context context) { 136 return getUserDictionaryLocalesSet(context); 137 } 138 139 /** 140 * Creates the entries that allow the user to go into the user dictionary for each locale. 141 */ createUserDictSettings()142 private void createUserDictSettings() { 143 final TreeSet<String> localeSet = getUserDictLocalesSet(mContext); 144 final int prefCount = mScreen.getPreferenceCount(); 145 String prefKey; 146 147 if (mLocale != null) { 148 // If the caller explicitly specify empty string as a locale, we'll show "all languages" 149 // in the list. 150 localeSet.add(mLocale); 151 } 152 if (localeSet.size() > 1) { 153 // Have an "All languages" entry in the languages list if there are two or more active 154 // languages 155 localeSet.add(""); 156 } 157 158 // Update the existing preferenceScreen according to the corresponding data set. 159 if (prefCount > 0) { 160 for (int i = prefCount - 1; i >= 0; i--) { 161 prefKey = mScreen.getPreference(i).getKey(); 162 if (TextUtils.isEmpty(prefKey) || TextUtils.equals(KEY_ALL_LANGUAGE, prefKey)) { 163 continue; 164 } 165 if (!localeSet.isEmpty() && localeSet.contains(prefKey)) { 166 localeSet.remove(prefKey); 167 continue; 168 } 169 mScreen.removePreference(mScreen.findPreference(prefKey)); 170 } 171 } 172 173 if (localeSet.isEmpty() && prefCount == 0) { 174 mScreen.addPreference(createUserDictionaryPreference(null)); 175 } else { 176 for (String locale : localeSet) { 177 final Preference pref = createUserDictionaryPreference(locale); 178 if (mScreen.findPreference(pref.getKey()) == null) { 179 mScreen.addPreference(pref); 180 } 181 } 182 } 183 } 184 185 /** 186 * Create a single User Dictionary Preference object, with its parameters set. 187 * 188 * @param locale The locale for which this user dictionary is for. 189 * @return The corresponding preference. 190 */ createUserDictionaryPreference(String locale)191 private Preference createUserDictionaryPreference(String locale) { 192 final String KEY_LOCALE = "locale"; 193 final Preference newPref = new Preference(mScreen.getContext()); 194 final Intent intent = new Intent(USER_DICTIONARY_SETTINGS_INTENT_ACTION) 195 .setPackage(mContext.getPackageName()); 196 if (locale == null) { 197 newPref.setTitle(Locale.getDefault().getDisplayName()); 198 newPref.setKey(Locale.getDefault().toString()); 199 } else { 200 if (TextUtils.isEmpty(locale)) { 201 newPref.setTitle(mContext.getString(R.string.user_dict_settings_all_languages)); 202 newPref.setKey(KEY_ALL_LANGUAGE); 203 newPref.setOrder(0); 204 } else { 205 newPref.setTitle(Utils.createLocaleFromString(locale).getDisplayName()); 206 newPref.setKey(locale); 207 } 208 intent.putExtra(KEY_LOCALE, locale); 209 newPref.getExtras().putString(KEY_LOCALE, locale); 210 } 211 newPref.setIntent(intent); 212 newPref.setFragment(UserDictionarySettings.class.getName()); 213 return newPref; 214 } 215 } 216