Dynamically populate a list of available recognition services in voice settings,
only showing the option to choose if there is more than one to choose from. Use
the new settingsActivity meta-data to target the appropriate settings activity
for the chosen recognizer.
diff --git a/src/com/android/settings/VoiceInputOutputSettings.java b/src/com/android/settings/VoiceInputOutputSettings.java
index 7586bd0..62c909f 100644
--- a/src/com/android/settings/VoiceInputOutputSettings.java
+++ b/src/com/android/settings/VoiceInputOutputSettings.java
@@ -16,55 +16,192 @@
 
 package com.android.settings;
 
-import android.content.Context;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import android.content.ComponentName;
 import android.content.Intent;
 import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
+import android.content.res.TypedArray;
+import android.content.res.XmlResourceParser;
 import android.os.Bundle;
+import android.preference.ListPreference;
 import android.preference.Preference;
 import android.preference.PreferenceActivity;
+import android.preference.PreferenceCategory;
 import android.preference.PreferenceGroup;
+import android.preference.PreferenceScreen;
+import android.preference.Preference.OnPreferenceChangeListener;
+import android.provider.Settings;
+import android.speech.RecognitionService;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.util.Xml;
 
+import java.io.IOException;
+import java.util.HashMap;
 import java.util.List;
 
 /**
  * Settings screen for voice input/output.
  */
-public class VoiceInputOutputSettings extends PreferenceActivity {
+public class VoiceInputOutputSettings extends PreferenceActivity
+        implements OnPreferenceChangeListener {
+    
+    private static final String TAG = "VoiceInputOutputSettings";
     
     private static final String KEY_PARENT = "parent";
-    private static final String KEY_VOICE_SEARCH_SETTINGS = "voice_search_settings";
-    private static final String KEY_KEYBOARD_SETTINGS = "keyboard_settings";
+    private static final String KEY_VOICE_INPUT_CATEGORY = "voice_input_category";
+    private static final String KEY_RECOGNIZER = "recognizer";
+    private static final String KEY_RECOGNIZER_SETTINGS = "recognizer_settings";
+    
+    private PreferenceGroup mParent;
+    private PreferenceCategory mVoiceInputCategory;
+    private ListPreference mRecognizerPref;
+    private PreferenceScreen mSettingsPref;
+    
+    private HashMap<String, ResolveInfo> mAvailableRecognizersMap;
     
     @Override
     protected void onCreate(Bundle icicle) {
         super.onCreate(icicle);
 
         addPreferencesFromResource(R.xml.voice_input_output_settings);
+
+        mParent = (PreferenceGroup) findPreference(KEY_PARENT);
+        mVoiceInputCategory = (PreferenceCategory) mParent.findPreference(KEY_VOICE_INPUT_CATEGORY);
+        mRecognizerPref = (ListPreference) mParent.findPreference(KEY_RECOGNIZER);
+        mRecognizerPref.setOnPreferenceChangeListener(this);
+        mSettingsPref = (PreferenceScreen) mParent.findPreference(KEY_RECOGNIZER_SETTINGS);
         
-        removePreferenceIfNecessary(KEY_VOICE_SEARCH_SETTINGS);
-        removePreferenceIfNecessary(KEY_KEYBOARD_SETTINGS);
+        mAvailableRecognizersMap = new HashMap<String, ResolveInfo>();
+        
+        populateOrRemoveRecognizerPreference();
     }
-
-    /**
-     * Removes a preference if there is no activity to handle its intent.
-     */
-    private void removePreferenceIfNecessary(String preferenceKey) {
-        PreferenceGroup parent = (PreferenceGroup) findPreference(KEY_PARENT);
+    
+    private void populateOrRemoveRecognizerPreference() {
+        List<ResolveInfo> availableRecognitionServices = getPackageManager().queryIntentServices(
+                new Intent(RecognitionService.SERVICE_INTERFACE), PackageManager.GET_META_DATA);
+        int numAvailable = availableRecognitionServices.size();
         
-        Preference preference = parent.findPreference(preferenceKey);
-        if (preference == null) {
-            return;
+        if (numAvailable == 0) {
+            // No recognizer available - remove all related preferences.
+            removePreference(mVoiceInputCategory);
+            removePreference(mRecognizerPref);
+            removePreference(mSettingsPref);
+        } else if (numAvailable == 1) {
+            // Only one recognizer available, so don't show the list of choices.
+            removePreference(mRecognizerPref);
+        } else {
+            // Multiple recognizers available, so show the full list of choices.
+            populateRecognizerPreference(availableRecognitionServices);
         }
+    }
+    
+    private void removePreference(Preference pref) {
+        if (pref != null) {
+            mParent.removePreference(pref);
+        }
+    }
+    
+    private void populateRecognizerPreference(List<ResolveInfo> recognizers) {
+        int size = recognizers.size();
+        CharSequence[] entries = new CharSequence[size];
+        CharSequence[] values = new CharSequence[size];
+        
+        // Get the current value from the secure setting.
+        String currentSetting = Settings.Secure.getString(
+                getContentResolver(), Settings.Secure.VOICE_RECOGNITION_SERVICE);
+        
+        // Iterate through all the available recognizers and load up their info to show
+        // in the preference. Also build up a map of recognizer component names to their
+        // ResolveInfos - we'll need that a little later.
+        for (int i = 0; i < size; i++) {
+            ResolveInfo resolveInfo = recognizers.get(i);
+            String recognizerComponent =
+                    new ComponentName(resolveInfo.serviceInfo.packageName,
+                            resolveInfo.serviceInfo.name).flattenToString();
+            
+            mAvailableRecognizersMap.put(recognizerComponent, resolveInfo);
 
-        Intent intent = preference.getIntent();
-        if (intent != null) {
-            PackageManager pm = getPackageManager();
-            if (!pm.queryIntentActivities(intent, 0).isEmpty()) {
-                return;
+            entries[i] = resolveInfo.loadLabel(getPackageManager());
+            values[i] = recognizerComponent;
+        }
+        
+        mRecognizerPref.setEntries(entries);
+        mRecognizerPref.setEntryValues(values);
+        
+        mRecognizerPref.setDefaultValue(currentSetting);
+        mRecognizerPref.setValue(currentSetting);
+        
+        updateSettingsLink(currentSetting);
+    }
+    
+    private void updateSettingsLink(String currentSetting) {
+        ResolveInfo currentRecognizer = mAvailableRecognizersMap.get(currentSetting);
+        ServiceInfo si = currentRecognizer.serviceInfo;
+        XmlResourceParser parser = null;
+        String settingsActivity = null;
+        try {
+            parser = si.loadXmlMetaData(getPackageManager(), RecognitionService.SERVICE_META_DATA);
+            if (parser == null) {
+                throw new XmlPullParserException("No " + RecognitionService.SERVICE_META_DATA +
+                        " meta-data for " + si.packageName);
             }
+            
+            AttributeSet attrs = Xml.asAttributeSet(parser);
+            
+            int type;
+            while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
+                    && type != XmlPullParser.START_TAG) {
+            }
+            
+            String nodeName = parser.getName();
+            if (!"recognition-service".equals(nodeName)) {
+                throw new XmlPullParserException(
+                        "Meta-data does not start with recognition-service tag");
+            }
+            
+            TypedArray array = getResources().obtainAttributes(attrs,
+                    com.android.internal.R.styleable.RecognitionService);
+            settingsActivity = array.getString(
+                    com.android.internal.R.styleable.RecognitionService_settingsActivity);
+            array.recycle();
+        } catch (XmlPullParserException e) {
+            Log.e(TAG, "error parsing recognition service meta-data", e);
+        } catch (IOException e) {
+            Log.e(TAG, "error parsing recognition service meta-data", e);
+        } finally {
+            if (parser != null) parser.close();
         }
         
-        // Did not find a matching activity, so remove the preference.
-        parent.removePreference(preference);
+        if (settingsActivity == null) {
+            // No settings preference available - hide the preference.
+            Log.w(TAG, "no recognizer settings available for " + si.packageName);
+            mSettingsPref.setIntent(null);
+            mParent.removePreference(mSettingsPref);
+        } else {
+            Intent i = new Intent(Intent.ACTION_MAIN);
+            i.setComponent(new ComponentName(si.packageName, settingsActivity));
+            mSettingsPref.setIntent(i);
+        }
+    }
+    
+    public boolean onPreferenceChange(Preference preference, Object newValue) {
+        if (preference == mRecognizerPref) {
+            String setting = (String) newValue;
+            
+            // Put the new value back into secure settings.
+            Settings.Secure.putString(
+                    getContentResolver(),
+                    Settings.Secure.VOICE_RECOGNITION_SERVICE,
+                    setting);
+            
+            // Update the settings item so it points to the right settings.
+            updateSettingsLink(setting);
+        }
+        return true;
     }
 }