From: Svetoslav Date: Tue, 15 Apr 2014 00:14:59 +0000 (-0700) Subject: Adding search for dynamic accessibility settings. X-Git-Tag: android-x86-6.0-r1~2563 X-Git-Url: http://git.osdn.net/view?a=commitdiff_plain;h=990159abaad7f314fbe1f9eaf064a088806ffb19;p=android-x86%2Fpackages-apps-Settings.git Adding search for dynamic accessibility settings. The language and input settings are highly dynamic and this change adds search support for that. This category depends on installed IMEs, input devices, user dictionary configuration, etc. We not only compute the right preferences to be indexed but also track related system state in the settings app to rebuild the index if needed. bug:14066763 Change-Id: Ia89d9e35bd79abf8d74614691aedf4ca9b11b6f2 --- diff --git a/src/com/android/settings/SettingsActivity.java b/src/com/android/settings/SettingsActivity.java index 789ad2a146..329e4bf876 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 dbfa1bceb0..c0e930e4b2 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 24acb4aea8..7439001b85 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 cfc90c6e7c..6f8efce96f 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));