diff --git a/src/com/android/settings/SettingsActivity.java b/src/com/android/settings/SettingsActivity.java index 789ad2a1460..329e4bf8768 100644 --- a/src/com/android/settings/SettingsActivity.java +++ b/src/com/android/settings/SettingsActivity.java @@ -40,6 +40,9 @@ import android.content.pm.ResolveInfo; import android.content.res.Configuration; import android.content.res.TypedArray; import android.content.res.XmlResourceParser; +import android.database.ContentObserver; +import android.hardware.input.InputManager; +import android.net.Uri; import android.nfc.NfcAdapter; import android.os.Bundle; import android.os.Handler; @@ -57,7 +60,7 @@ import android.preference.PreferenceScreen; import android.print.PrintManager; import android.printservice.PrintService; import android.printservice.PrintServiceInfo; -import android.provider.SearchIndexableData; +import android.provider.UserDictionary; import android.text.TextUtils; import android.util.AttributeSet; import android.util.Log; @@ -69,6 +72,8 @@ import android.view.MenuItem; import android.view.View; import android.view.View.OnClickListener; import android.view.accessibility.AccessibilityManager; +import android.view.inputmethod.InputMethodInfo; +import android.view.inputmethod.InputMethodManager; import android.widget.Button; import android.widget.ListView; @@ -119,7 +124,6 @@ import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; -import java.util.Iterator; import java.util.List; import static com.android.settings.dashboard.Header.HEADER_ID_UNDEFINED; @@ -315,8 +319,8 @@ public class SettingsActivity extends Activity } }; - private final DynamicIndexablePackageMonitor mDynamicIndexablePackageMonitor = - new DynamicIndexablePackageMonitor(); + private final DynamicIndexableContentMonitor mDynamicIndexableContentMonitor = + new DynamicIndexableContentMonitor(); private Button mNextButton; private ActionBar mActionBar; @@ -625,7 +629,7 @@ public class SettingsActivity extends Activity registerReceiver(mBatteryInfoReceiver, new IntentFilter(Intent.ACTION_BATTERY_CHANGED)); - mDynamicIndexablePackageMonitor.register(this); + mDynamicIndexableContentMonitor.register(this); } @Override @@ -641,7 +645,7 @@ public class SettingsActivity extends Activity mDevelopmentPreferencesListener = null; - mDynamicIndexablePackageMonitor.unregister(); + mDynamicIndexableContentMonitor.unregister(); } @Override @@ -1290,13 +1294,18 @@ public class SettingsActivity extends Activity mSearchMenuItem.collapseActionView(); } - private static final class DynamicIndexablePackageMonitor extends PackageMonitor { + private static final class DynamicIndexableContentMonitor extends PackageMonitor implements + InputManager.InputDeviceListener { + private static final Intent ACCESSIBILITY_SERVICE_INTENT = new Intent(AccessibilityService.SERVICE_INTERFACE); private static final Intent PRINT_SERVICE_INTENT = new Intent(PrintService.SERVICE_INTERFACE); + private static final Intent IME_SERVICE_INTENT = + new Intent("android.view.InputMethod"); + private static final long DELAY_PROCESS_PACKAGE_CHANGE = 2000; private static final int MSG_PACKAGE_AVAILABLE = 1; @@ -1304,6 +1313,7 @@ public class SettingsActivity extends Activity private final List mAccessibilityServices = new ArrayList(); private final List mPrintServices = new ArrayList(); + private final List mImeServices = new ArrayList(); private final Handler mHandler = new Handler() { @Override @@ -1322,6 +1332,8 @@ public class SettingsActivity extends Activity } }; + private final ContentObserver mContentObserver = new MyContentObserver(mHandler); + private Context mContext; public void register(Context context) { @@ -1350,13 +1362,41 @@ public class SettingsActivity extends Activity .serviceInfo.packageName); } + // Cache IME service packages to know when they go away. + InputMethodManager imeManager = (InputMethodManager) + mContext.getSystemService(Context.INPUT_METHOD_SERVICE); + List inputMethods = imeManager.getInputMethodList(); + final int inputMethodCount = inputMethods.size(); + for (int i = 0; i < inputMethodCount; i++) { + InputMethodInfo inputMethod = inputMethods.get(i); + mImeServices.add(inputMethod.getServiceInfo().packageName); + } + + // Watch for related content URIs. + mContext.getContentResolver().registerContentObserver( + UserDictionary.Words.CONTENT_URI, true, mContentObserver); + + // Watch for input device changes. + InputManager inputManager = (InputManager) context.getSystemService( + Context.INPUT_SERVICE); + inputManager.registerInputDeviceListener(this, mHandler); + + // Start tracking packages. register(context, Looper.getMainLooper(), UserHandle.CURRENT, false); } public void unregister() { super.unregister(); + + InputManager inputManager = (InputManager) mContext.getSystemService( + Context.INPUT_SERVICE); + inputManager.unregisterInputDeviceListener(this); + + mContext.getContentResolver().unregisterContentObserver(mContentObserver); + mAccessibilityServices.clear(); mPrintServices.clear(); + mImeServices.clear(); } // Covers installed, appeared external storage with the package, upgraded. @@ -1385,6 +1425,23 @@ public class SettingsActivity extends Activity } } + @Override + public void onInputDeviceAdded(int deviceId) { + Index.getInstance(mContext).updateFromClassNameResource( + InputMethodAndLanguageSettings.class.getName(), false, true); + } + + @Override + public void onInputDeviceRemoved(int deviceId) { + onInputDeviceChanged(deviceId); + } + + @Override + public void onInputDeviceChanged(int deviceId) { + Index.getInstance(mContext).updateFromClassNameResource( + InputMethodAndLanguageSettings.class.getName(), true, true); + } + private void postMessage(int what, String packageName) { Message message = mHandler.obtainMessage(what, packageName); mHandler.sendMessageDelayed(message, DELAY_PROCESS_PACKAGE_CHANGE); @@ -1412,6 +1469,17 @@ public class SettingsActivity extends Activity } intent.setPackage(null); } + + if (!mImeServices.contains(packageName)) { + Intent intent = IME_SERVICE_INTENT; + intent.setPackage(packageName); + if (!mContext.getPackageManager().queryIntentServices(intent, 0).isEmpty()) { + mImeServices.add(packageName); + Index.getInstance(mContext).updateFromClassNameResource( + InputMethodAndLanguageSettings.class.getName(), false, true); + } + intent.setPackage(null); + } } private void handlePackageUnavailable(String packageName) { @@ -1429,5 +1497,20 @@ public class SettingsActivity extends Activity PrintSettingsFragment.class.getName(), true, true); } } + + private final class MyContentObserver extends ContentObserver { + + public MyContentObserver(Handler handler) { + super(handler); + } + + @Override + public void onChange(boolean selfChange, Uri uri) { + if (UserDictionary.Words.CONTENT_URI.equals(uri)) { + Index.getInstance(mContext).updateFromClassNameResource( + InputMethodAndLanguageSettings.class.getName(), true, true); + } + }; + } } } diff --git a/src/com/android/settings/inputmethod/InputMethodAndLanguageSettings.java b/src/com/android/settings/inputmethod/InputMethodAndLanguageSettings.java index dbfa1bceb0b..c0e930e4b20 100644 --- a/src/com/android/settings/inputmethod/InputMethodAndLanguageSettings.java +++ b/src/com/android/settings/inputmethod/InputMethodAndLanguageSettings.java @@ -23,6 +23,9 @@ import com.android.settings.SettingsPreferenceFragment; import com.android.settings.UserDictionarySettings; import com.android.settings.Utils; import com.android.settings.VoiceInputOutputSettings; +import com.android.settings.search.BaseSearchIndexProvider; +import com.android.settings.search.Indexable; +import com.android.settings.search.SearchIndexableRaw; import android.app.Activity; import android.app.Fragment; @@ -30,6 +33,7 @@ import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; import android.content.res.Configuration; import android.content.res.Resources; import android.database.ContentObserver; @@ -47,10 +51,13 @@ import android.preference.PreferenceCategory; import android.preference.PreferenceScreen; import android.provider.Settings; import android.provider.Settings.System; +import android.speech.RecognitionService; +import android.speech.tts.TtsEngines; import android.text.TextUtils; import android.view.InputDevice; import android.view.inputmethod.InputMethodInfo; import android.view.inputmethod.InputMethodManager; +import android.view.inputmethod.InputMethodSubtype; import android.widget.BaseAdapter; import java.util.ArrayList; @@ -60,7 +67,7 @@ import java.util.TreeSet; public class InputMethodAndLanguageSettings extends SettingsPreferenceFragment implements Preference.OnPreferenceChangeListener, InputManager.InputDeviceListener, - KeyboardLayoutDialogFragment.OnSetupKeyboardLayoutsListener { + KeyboardLayoutDialogFragment.OnSetupKeyboardLayoutsListener, Indexable { private static final String KEY_PHONE_LANGUAGE = "phone_language"; private static final String KEY_CURRENT_INPUT_METHOD = "current_input_method"; @@ -242,34 +249,8 @@ public class InputMethodAndLanguageSettings extends SettingsPreferenceFragment if (!mIsOnlyImeSettings) { if (mLanguagePref != null) { - Configuration conf = getResources().getConfiguration(); - String language = conf.locale.getLanguage(); - String localeString; - // TODO: This is not an accurate way to display the locale, as it is - // just working around the fact that we support limited dialects - // and want to pretend that the language is valid for all locales. - // We need a way to support languages that aren't tied to a particular - // locale instead of hiding the locale qualifier. - if (language.equals("zz")) { - String country = conf.locale.getCountry(); - if (country.equals("ZZ")) { - localeString = "[Developer] Accented English (zz_ZZ)"; - } else if (country.equals("ZY")) { - localeString = "[Developer] Fake Bi-Directional (zz_ZY)"; - } else { - localeString = ""; - } - } else if (hasOnlyOneLanguageInstance(language, - Resources.getSystem().getAssets().getLocales())) { - localeString = conf.locale.getDisplayLanguage(conf.locale); - } else { - localeString = conf.locale.getDisplayName(conf.locale); - } - if (localeString.length() > 1) { - localeString = Character.toUpperCase(localeString.charAt(0)) - + localeString.substring(1); - mLanguagePref.setSummary(localeString); - } + String localeName = getLocaleName(getResources()); + mLanguagePref.setSummary(localeName); } updateUserDictionaryPreference(findPreference(KEY_USER_DICTIONARY_SETTINGS)); @@ -361,7 +342,40 @@ public class InputMethodAndLanguageSettings extends SettingsPreferenceFragment return super.onPreferenceTreeClick(preferenceScreen, preference); } - private boolean hasOnlyOneLanguageInstance(String languageCode, String[] locales) { + private static String getLocaleName(Resources resources) { + Configuration conf = resources.getConfiguration(); + String language = conf.locale.getLanguage(); + String localeName; + // TODO: This is not an accurate way to display the locale, as it is + // just working around the fact that we support limited dialects + // and want to pretend that the language is valid for all locales. + // We need a way to support languages that aren't tied to a particular + // locale instead of hiding the locale qualifier. + if (language.equals("zz")) { + String country = conf.locale.getCountry(); + if (country.equals("ZZ")) { + localeName = "[Developer] Accented English (zz_ZZ)"; + } else if (country.equals("ZY")) { + localeName = "[Developer] Fake Bi-Directional (zz_ZY)"; + } else { + localeName = ""; + } + } else if (hasOnlyOneLanguageInstance(language, + Resources.getSystem().getAssets().getLocales())) { + localeName = conf.locale.getDisplayLanguage(conf.locale); + } else { + localeName = conf.locale.getDisplayName(conf.locale); + } + + if (localeName.length() > 1) { + localeName = Character.toUpperCase(localeName.charAt(0)) + + localeName.substring(1); + } + + return localeName; + } + + private static boolean hasOnlyOneLanguageInstance(String languageCode, String[] locales) { int count = 0; for (String localeCode : locales) { if (localeCode.length() > 2 @@ -582,7 +596,7 @@ public class InputMethodAndLanguageSettings extends SettingsPreferenceFragment } } - private boolean haveInputDeviceWithVibrator() { + private static boolean haveInputDeviceWithVibrator() { final int[] devices = InputDevice.getDeviceIds(); for (int i = 0; i < devices.length; i++) { InputDevice device = InputDevice.getDevice(devices[i]); @@ -617,4 +631,225 @@ public class InputMethodAndLanguageSettings extends SettingsPreferenceFragment mContext.getContentResolver().unregisterContentObserver(this); } } + + public static final Indexable.SearchIndexProvider SEARCH_INDEX_DATA_PROVIDER = + new BaseSearchIndexProvider() { + @Override + public List getRawDataToIndex(Context context, boolean enabled) { + List indexables = new ArrayList(); + + Resources resources = context.getResources(); + String screenTitle = context.getString(R.string.language_keyboard_settings_title); + + // Locale picker. + if (context.getAssets().getLocales().length > 1) { + String localeName = getLocaleName(resources); + SearchIndexableRaw indexable = new SearchIndexableRaw(context); + indexable.title = context.getString(R.string.phone_language); + indexable.summaryOn = localeName; + indexable.summaryOff = localeName; + indexable.screenTitle = screenTitle; + indexables.add(indexable); + } + + // Spell checker. + SearchIndexableRaw indexable = new SearchIndexableRaw(context); + indexable.title = context.getString(R.string.spellcheckers_settings_title); + indexable.screenTitle = screenTitle; + indexables.add(indexable); + + // User dictionary. + if (UserDictionaryList.getUserDictionaryLocalesSet(context) != null) { + indexable = new SearchIndexableRaw(context); + indexable.title = context.getString(R.string.user_dict_settings_title); + indexable.screenTitle = screenTitle; + indexables.add(indexable); + } + + // Keyboard settings. + indexable = new SearchIndexableRaw(context); + indexable.title = context.getString(R.string.keyboard_settings_category); + indexable.screenTitle = screenTitle; + indexables.add(indexable); + + InputMethodSettingValuesWrapper immValues = InputMethodSettingValuesWrapper + .getInstance(context); + immValues.refreshAllInputMethodAndSubtypes(); + + // Current IME. + String currImeName = immValues.getCurrentInputMethodName(context).toString(); + indexable = new SearchIndexableRaw(context); + indexable.title = context.getString(R.string.current_input_method); + indexable.summaryOn = currImeName; + indexable.summaryOff = currImeName; + indexable.screenTitle = screenTitle; + indexables.add(indexable); + + InputMethodManager inputMethodManager = (InputMethodManager) context.getSystemService( + Context.INPUT_METHOD_SERVICE); + + // All other IMEs. + List inputMethods = immValues.getInputMethodList(); + final int inputMethodCount = (inputMethods == null ? 0 : inputMethods.size()); + for (int i = 0; i < inputMethodCount; ++i) { + InputMethodInfo inputMethod = inputMethods.get(i); + + StringBuilder builder = new StringBuilder(); + List subtypes = inputMethodManager + .getEnabledInputMethodSubtypeList(inputMethod, true); + final int subtypeCount = subtypes.size(); + for (int j = 0; j < subtypeCount; j++) { + InputMethodSubtype subtype = subtypes.get(j); + if (builder.length() > 0) { + builder.append(','); + } + CharSequence subtypeLabel = subtype.getDisplayName(context, + inputMethod.getPackageName(), inputMethod.getServiceInfo() + .applicationInfo); + builder.append(subtypeLabel); + } + String summary = builder.toString(); + + indexable = new SearchIndexableRaw(context); + indexable.title = inputMethod.loadLabel(context.getPackageManager()).toString(); + indexable.summaryOn = summary; + indexable.summaryOff = summary; + indexable.screenTitle = screenTitle; + indexables.add(indexable); + } + + // Hard keyboards + InputManager inputManager = (InputManager) context.getSystemService( + Context.INPUT_SERVICE); + if (resources.getConfiguration().keyboard == Configuration.KEYBOARD_QWERTY) { + boolean hasHardKeyboards = false; + + final int[] devices = InputDevice.getDeviceIds(); + for (int i = 0; i < devices.length; i++) { + InputDevice device = InputDevice.getDevice(devices[i]); + if (device == null || device.isVirtual() || !device.isFullKeyboard()) { + continue; + } + + hasHardKeyboards = true; + + InputDeviceIdentifier identifier = device.getIdentifier(); + String keyboardLayoutDescriptor = + inputManager.getCurrentKeyboardLayoutForInputDevice(identifier); + KeyboardLayout keyboardLayout = keyboardLayoutDescriptor != null ? + inputManager.getKeyboardLayout(keyboardLayoutDescriptor) : null; + + String summary; + if (keyboardLayout != null) { + summary = keyboardLayout.toString(); + } else { + summary = context.getString(R.string.keyboard_layout_default_label); + } + + indexable = new SearchIndexableRaw(context); + indexable.title = device.getName(); + indexable.summaryOn = summary; + indexable.summaryOff = summary; + indexable.screenTitle = screenTitle; + indexables.add(indexable); + } + + if (hasHardKeyboards) { + // Hard keyboard category. + indexable = new SearchIndexableRaw(context); + indexable.title = context.getString( + R.string.builtin_keyboard_settings_title); + indexable.screenTitle = screenTitle; + indexables.add(indexable); + + // Auto replace. + indexable = new SearchIndexableRaw(context); + indexable.title = context.getString(R.string.auto_replace); + indexable.summaryOn = context.getString(R.string.auto_replace_summary); + indexable.summaryOff = context.getString(R.string.auto_replace_summary); + indexable.screenTitle = screenTitle; + indexables.add(indexable); + + // Auto caps. + indexable = new SearchIndexableRaw(context); + indexable.title = context.getString(R.string.auto_caps); + indexable.summaryOn = context.getString(R.string.auto_caps_summary); + indexable.summaryOff = context.getString(R.string.auto_caps_summary); + indexable.screenTitle = screenTitle; + indexables.add(indexable); + + // Auto punctuate. + indexable = new SearchIndexableRaw(context); + indexable.title = context.getString(R.string.auto_punctuate); + indexable.summaryOn = context.getString(R.string.auto_punctuate_summary); + indexable.summaryOff = context.getString(R.string.auto_punctuate_summary); + indexable.screenTitle = screenTitle; + indexables.add(indexable); + } + } + + // Voice recognizers. + List recognizers = context.getPackageManager() + .queryIntentServices(new Intent(RecognitionService.SERVICE_INTERFACE), + PackageManager.GET_META_DATA); + + final int recognizerCount = recognizers.size(); + + // Recognizer settings. + if (recognizerCount > 0) { + indexable = new SearchIndexableRaw(context); + indexable.title = context.getString(R.string.recognizer_settings_title); + indexable.screenTitle = screenTitle; + indexables.add(indexable); + } + + if (recognizerCount > 1) { + // Recognizer chooser. + indexable = new SearchIndexableRaw(context); + indexable.title = context.getString(R.string.recognizer_title); + indexable.screenTitle = screenTitle; + indexables.add(indexable); + } + + for (int i = 0; i < recognizerCount; i++) { + ResolveInfo recognizer = recognizers.get(i); + indexable = new SearchIndexableRaw(context); + indexable.title = recognizer.loadLabel(context.getPackageManager()).toString(); + indexable.screenTitle = screenTitle; + indexables.add(indexable); + } + + // Text-to-speech. + TtsEngines ttsEngines = new TtsEngines(context); + if (!ttsEngines.getEngines().isEmpty()) { + indexable = new SearchIndexableRaw(context); + indexable.title = context.getString(R.string.tts_settings_title); + indexable.screenTitle = screenTitle; + indexables.add(indexable); + } + + // Pointer settings. + indexable = new SearchIndexableRaw(context); + indexable.title = context.getString(R.string.pointer_settings_category); + indexable.screenTitle = screenTitle; + indexables.add(indexable); + + indexable = new SearchIndexableRaw(context); + indexable.title = context.getString(R.string.pointer_speed); + indexable.screenTitle = screenTitle; + indexables.add(indexable); + + // Game controllers. + if (haveInputDeviceWithVibrator()) { + indexable = new SearchIndexableRaw(context); + indexable.title = context.getString(R.string.vibrate_input_devices); + indexable.summaryOn = context.getString(R.string.vibrate_input_devices_summary); + indexable.summaryOff = context.getString(R.string.vibrate_input_devices_summary); + indexable.screenTitle = screenTitle; + indexables.add(indexable); + } + + return indexables; + } + }; } diff --git a/src/com/android/settings/inputmethod/UserDictionaryList.java b/src/com/android/settings/inputmethod/UserDictionaryList.java index 24acb4aea8f..7439001b857 100644 --- a/src/com/android/settings/inputmethod/UserDictionaryList.java +++ b/src/com/android/settings/inputmethod/UserDictionaryList.java @@ -72,22 +72,27 @@ public class UserDictionaryList extends SettingsPreferenceFragment { mLocale = locale; } - public static TreeSet getUserDictionaryLocalesSet(Activity activity) { - @SuppressWarnings("deprecation") - final Cursor cursor = activity.managedQuery(UserDictionary.Words.CONTENT_URI, - new String[] { UserDictionary.Words.LOCALE }, + public static TreeSet getUserDictionaryLocalesSet(Context context) { + final Cursor cursor = context.getContentResolver().query( + UserDictionary.Words.CONTENT_URI, new String[] { UserDictionary.Words.LOCALE }, null, null, null); final TreeSet localeSet = new TreeSet(); if (null == cursor) { // The user dictionary service is not present or disabled. Return null. return null; - } else if (cursor.moveToFirst()) { - final int columnIndex = cursor.getColumnIndex(UserDictionary.Words.LOCALE); - do { - final String locale = cursor.getString(columnIndex); - localeSet.add(null != locale ? locale : ""); - } while (cursor.moveToNext()); } + try { + if (cursor.moveToFirst()) { + final int columnIndex = cursor.getColumnIndex(UserDictionary.Words.LOCALE); + do { + final String locale = cursor.getString(columnIndex); + localeSet.add(null != locale ? locale : ""); + } while (cursor.moveToNext()); + } + } finally { + cursor.close(); + } + // CAVEAT: Keep this for consistency of the implementation between Keyboard and Settings // if (!UserDictionarySettings.IS_SHORTCUT_API_SUPPORTED) { // // For ICS, we need to show "For all languages" in case that the keyboard locale @@ -96,7 +101,7 @@ public class UserDictionaryList extends SettingsPreferenceFragment { // } final InputMethodManager imm = - (InputMethodManager)activity.getSystemService(Context.INPUT_METHOD_SERVICE); + (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE); final List imis = imm.getEnabledInputMethodList(); for (final InputMethodInfo imi : imis) { final List subtypes = diff --git a/src/com/android/settings/search/SearchIndexableResources.java b/src/com/android/settings/search/SearchIndexableResources.java index cfc90c6e7c2..6f8efce96f0 100644 --- a/src/com/android/settings/search/SearchIndexableResources.java +++ b/src/com/android/settings/search/SearchIndexableResources.java @@ -168,7 +168,7 @@ public final class SearchIndexableResources { sResMap.put(InputMethodAndLanguageSettings.class.getName(), new SearchIndexableResource(RANK_IME, - R.xml.language_settings, + NO_DATA_RES_ID, InputMethodAndLanguageSettings.class.getName(), R.drawable.ic_settings_language));