1 /* 2 * Copyright (C) 2017 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.applications.assist; 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 BaseInfo(PackageManager pm, ServiceInfo _service, String _settings)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 compareTo(Object another)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 InteractionInfo(PackageManager pm, VoiceInteractionServiceInfo _service)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 { RecognizerInfo(PackageManager pm, ServiceInfo _service, String _settings)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 VoiceInputHelper(Context context)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 buildUi()110 public void buildUi() { 111 // Get the currently selected interactor from the secure setting. 112 String currentSetting = Settings.Secure.getString( 113 mContext.getContentResolver(), Settings.Secure.VOICE_INTERACTION_SERVICE); 114 if (currentSetting != null && !currentSetting.isEmpty()) { 115 mCurrentVoiceInteraction = ComponentName.unflattenFromString(currentSetting); 116 } else { 117 mCurrentVoiceInteraction = null; 118 } 119 120 ArraySet<ComponentName> interactorRecognizers = new ArraySet<>(); 121 122 // Iterate through all the available interactors and load up their info to show 123 // in the preference. 124 int size = mAvailableVoiceInteractions.size(); 125 for (int i = 0; i < size; i++) { 126 ResolveInfo resolveInfo = mAvailableVoiceInteractions.get(i); 127 VoiceInteractionServiceInfo info = new VoiceInteractionServiceInfo( 128 mContext.getPackageManager(), resolveInfo.serviceInfo); 129 if (info.getParseError() != null) { 130 Log.w("VoiceInteractionService", "Error in VoiceInteractionService " 131 + resolveInfo.serviceInfo.packageName + "/" 132 + resolveInfo.serviceInfo.name + ": " + info.getParseError()); 133 continue; 134 } 135 mAvailableInteractionInfos.add(new InteractionInfo(mContext.getPackageManager(), info)); 136 interactorRecognizers.add(new ComponentName(resolveInfo.serviceInfo.packageName, 137 info.getRecognitionService())); 138 } 139 Collections.sort(mAvailableInteractionInfos); 140 141 // Get the currently selected recognizer from the secure setting. 142 currentSetting = Settings.Secure.getString( 143 mContext.getContentResolver(), Settings.Secure.VOICE_RECOGNITION_SERVICE); 144 if (currentSetting != null && !currentSetting.isEmpty()) { 145 mCurrentRecognizer = ComponentName.unflattenFromString(currentSetting); 146 } else { 147 mCurrentRecognizer = null; 148 } 149 150 // Iterate through all the available recognizers and load up their info to show 151 // in the preference. 152 size = mAvailableRecognition.size(); 153 for (int i = 0; i < size; i++) { 154 ResolveInfo resolveInfo = mAvailableRecognition.get(i); 155 ComponentName comp = new ComponentName(resolveInfo.serviceInfo.packageName, 156 resolveInfo.serviceInfo.name); 157 if (interactorRecognizers.contains(comp)) { 158 //continue; 159 } 160 ServiceInfo si = resolveInfo.serviceInfo; 161 XmlResourceParser parser = null; 162 String settingsActivity = null; 163 try { 164 parser = si.loadXmlMetaData(mContext.getPackageManager(), 165 RecognitionService.SERVICE_META_DATA); 166 if (parser == null) { 167 throw new XmlPullParserException("No " + RecognitionService.SERVICE_META_DATA + 168 " meta-data for " + si.packageName); 169 } 170 171 Resources res = mContext.getPackageManager().getResourcesForApplication( 172 si.applicationInfo); 173 174 AttributeSet attrs = Xml.asAttributeSet(parser); 175 176 int type; 177 while ((type = parser.next()) != XmlPullParser.END_DOCUMENT 178 && type != XmlPullParser.START_TAG) { 179 } 180 181 String nodeName = parser.getName(); 182 if (!"recognition-service".equals(nodeName)) { 183 throw new XmlPullParserException( 184 "Meta-data does not start with recognition-service tag"); 185 } 186 187 TypedArray array = res.obtainAttributes(attrs, 188 com.android.internal.R.styleable.RecognitionService); 189 settingsActivity = array.getString( 190 com.android.internal.R.styleable.RecognitionService_settingsActivity); 191 array.recycle(); 192 } catch (XmlPullParserException e) { 193 Log.e(TAG, "error parsing recognition service meta-data", e); 194 } catch (IOException e) { 195 Log.e(TAG, "error parsing recognition service meta-data", e); 196 } catch (PackageManager.NameNotFoundException e) { 197 Log.e(TAG, "error parsing recognition service meta-data", e); 198 } finally { 199 if (parser != null) parser.close(); 200 } 201 mAvailableRecognizerInfos.add(new RecognizerInfo(mContext.getPackageManager(), 202 resolveInfo.serviceInfo, settingsActivity)); 203 } 204 Collections.sort(mAvailableRecognizerInfos); 205 } 206 } 207