1 /* 2 * Copyright (C) 2014 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.voice; 18 19 import android.content.ComponentName; 20 import android.content.Context; 21 import android.content.Intent; 22 import android.content.pm.PackageManager; 23 import android.content.pm.ResolveInfo; 24 import android.content.pm.ServiceInfo; 25 import android.content.res.Resources; 26 import android.content.res.TypedArray; 27 import android.content.res.XmlResourceParser; 28 import android.provider.Settings; 29 import android.service.voice.VoiceInteractionService; 30 import android.service.voice.VoiceInteractionServiceInfo; 31 import android.speech.RecognitionService; 32 import android.util.ArraySet; 33 import android.util.AttributeSet; 34 import android.util.Log; 35 import android.util.Xml; 36 37 import org.xmlpull.v1.XmlPullParser; 38 import org.xmlpull.v1.XmlPullParserException; 39 40 import java.io.IOException; 41 import java.util.ArrayList; 42 import java.util.Collections; 43 import java.util.List; 44 45 public final class VoiceInputHelper { 46 static final String TAG = "VoiceInputHelper"; 47 final Context mContext; 48 49 final List<ResolveInfo> mAvailableVoiceInteractions; 50 final List<ResolveInfo> mAvailableRecognition; 51 52 static public class BaseInfo implements Comparable { 53 public final ServiceInfo service; 54 public final ComponentName componentName; 55 public final String key; 56 public final ComponentName settings; 57 public final CharSequence label; 58 public final String labelStr; 59 public final CharSequence appLabel; 60 61 public BaseInfo(PackageManager pm, ServiceInfo _service, String _settings) { 62 service = _service; 63 componentName = new ComponentName(_service.packageName, _service.name); 64 key = componentName.flattenToShortString(); 65 settings = _settings != null 66 ? new ComponentName(_service.packageName, _settings) : null; 67 label = _service.loadLabel(pm); 68 labelStr = label.toString(); 69 appLabel = _service.applicationInfo.loadLabel(pm); 70 } 71 72 @Override 73 public int compareTo(Object another) { 74 return labelStr.compareTo(((BaseInfo)another).labelStr); 75 } 76 } 77 78 static public class InteractionInfo extends BaseInfo { 79 public final VoiceInteractionServiceInfo serviceInfo; 80 81 public InteractionInfo(PackageManager pm, VoiceInteractionServiceInfo _service) { 82 super(pm, _service.getServiceInfo(), _service.getSettingsActivity()); 83 serviceInfo = _service; 84 } 85 } 86 87 static public class RecognizerInfo extends BaseInfo { 88 public RecognizerInfo(PackageManager pm, ServiceInfo _service, String _settings) { 89 super(pm, _service, _settings); 90 } 91 } 92 93 final ArrayList<InteractionInfo> mAvailableInteractionInfos = new ArrayList<>(); 94 final ArrayList<RecognizerInfo> mAvailableRecognizerInfos = new ArrayList<>(); 95 96 ComponentName mCurrentVoiceInteraction; 97 ComponentName mCurrentRecognizer; 98 99 public VoiceInputHelper(Context context) { 100 mContext = context; 101 102 mAvailableVoiceInteractions = mContext.getPackageManager().queryIntentServices( 103 new Intent(VoiceInteractionService.SERVICE_INTERFACE), 104 PackageManager.GET_META_DATA); 105 mAvailableRecognition = mContext.getPackageManager().queryIntentServices( 106 new Intent(RecognitionService.SERVICE_INTERFACE), 107 PackageManager.GET_META_DATA); 108 } 109 110 public boolean hasItems() { 111 return mAvailableVoiceInteractions.size() > 0 || mAvailableRecognition.size() > 0; 112 } 113 114 public void buildUi() { 115 // Get the currently selected interactor from the secure setting. 116 String currentSetting = Settings.Secure.getString( 117 mContext.getContentResolver(), Settings.Secure.VOICE_INTERACTION_SERVICE); 118 if (currentSetting != null && !currentSetting.isEmpty()) { 119 mCurrentVoiceInteraction = ComponentName.unflattenFromString(currentSetting); 120 } else { 121 mCurrentVoiceInteraction = null; 122 } 123 124 ArraySet<ComponentName> interactorRecognizers = new ArraySet<>(); 125 126 // Iterate through all the available interactors and load up their info to show 127 // in the preference. 128 int size = mAvailableVoiceInteractions.size(); 129 for (int i = 0; i < size; i++) { 130 ResolveInfo resolveInfo = mAvailableVoiceInteractions.get(i); 131 VoiceInteractionServiceInfo info = new VoiceInteractionServiceInfo( 132 mContext.getPackageManager(), resolveInfo.serviceInfo); 133 if (info.getParseError() != null) { 134 Log.w("VoiceInteractionService", "Error in VoiceInteractionService " 135 + resolveInfo.serviceInfo.packageName + "/" 136 + resolveInfo.serviceInfo.name + ": " + info.getParseError()); 137 continue; 138 } 139 mAvailableInteractionInfos.add(new InteractionInfo(mContext.getPackageManager(), info)); 140 interactorRecognizers.add(new ComponentName(resolveInfo.serviceInfo.packageName, 141 info.getRecognitionService())); 142 } 143 Collections.sort(mAvailableInteractionInfos); 144 145 // Get the currently selected recognizer from the secure setting. 146 currentSetting = Settings.Secure.getString( 147 mContext.getContentResolver(), Settings.Secure.VOICE_RECOGNITION_SERVICE); 148 if (currentSetting != null && !currentSetting.isEmpty()) { 149 mCurrentRecognizer = ComponentName.unflattenFromString(currentSetting); 150 } else { 151 mCurrentRecognizer = null; 152 } 153 154 // Iterate through all the available recognizers and load up their info to show 155 // in the preference. 156 size = mAvailableRecognition.size(); 157 for (int i = 0; i < size; i++) { 158 ResolveInfo resolveInfo = mAvailableRecognition.get(i); 159 ComponentName comp = new ComponentName(resolveInfo.serviceInfo.packageName, 160 resolveInfo.serviceInfo.name); 161 if (interactorRecognizers.contains(comp)) { 162 //continue; 163 } 164 ServiceInfo si = resolveInfo.serviceInfo; 165 XmlResourceParser parser = null; 166 String settingsActivity = null; 167 try { 168 parser = si.loadXmlMetaData(mContext.getPackageManager(), 169 RecognitionService.SERVICE_META_DATA); 170 if (parser == null) { 171 throw new XmlPullParserException("No " + RecognitionService.SERVICE_META_DATA + 172 " meta-data for " + si.packageName); 173 } 174 175 Resources res = mContext.getPackageManager().getResourcesForApplication( 176 si.applicationInfo); 177 178 AttributeSet attrs = Xml.asAttributeSet(parser); 179 180 int type; 181 while ((type=parser.next()) != XmlPullParser.END_DOCUMENT 182 && type != XmlPullParser.START_TAG) { 183 } 184 185 String nodeName = parser.getName(); 186 if (!"recognition-service".equals(nodeName)) { 187 throw new XmlPullParserException( 188 "Meta-data does not start with recognition-service tag"); 189 } 190 191 TypedArray array = res.obtainAttributes(attrs, 192 com.android.internal.R.styleable.RecognitionService); 193 settingsActivity = array.getString( 194 com.android.internal.R.styleable.RecognitionService_settingsActivity); 195 array.recycle(); 196 } catch (XmlPullParserException e) { 197 Log.e(TAG, "error parsing recognition service meta-data", e); 198 } catch (IOException e) { 199 Log.e(TAG, "error parsing recognition service meta-data", e); 200 } catch (PackageManager.NameNotFoundException e) { 201 Log.e(TAG, "error parsing recognition service meta-data", e); 202 } finally { 203 if (parser != null) parser.close(); 204 } 205 mAvailableRecognizerInfos.add(new RecognizerInfo(mContext.getPackageManager(), 206 resolveInfo.serviceInfo, settingsActivity)); 207 } 208 Collections.sort(mAvailableRecognizerInfos); 209 } 210 } 211