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.speech.RecognitionService; 30 import android.util.AttributeSet; 31 import android.util.Log; 32 import android.util.Xml; 33 34 import org.xmlpull.v1.XmlPullParser; 35 import org.xmlpull.v1.XmlPullParserException; 36 37 import java.io.IOException; 38 import java.util.ArrayList; 39 import java.util.Collections; 40 import java.util.List; 41 42 public final class VoiceInputHelper { 43 static final String TAG = "VoiceInputHelper"; 44 final Context mContext; 45 46 final List<ResolveInfo> mAvailableRecognition; 47 48 // TODO: Remove this superclass as we only have 1 class now (RecognizerInfo). 49 static public class BaseInfo implements Comparable { 50 public final ServiceInfo service; 51 public final ComponentName componentName; 52 public final String key; 53 public final ComponentName settings; 54 public final CharSequence label; 55 public final String labelStr; 56 public final CharSequence appLabel; 57 BaseInfo(PackageManager pm, ServiceInfo _service, String _settings)58 public BaseInfo(PackageManager pm, ServiceInfo _service, String _settings) { 59 service = _service; 60 componentName = new ComponentName(_service.packageName, _service.name); 61 key = componentName.flattenToShortString(); 62 settings = _settings != null 63 ? new ComponentName(_service.packageName, _settings) : null; 64 label = _service.loadLabel(pm); 65 labelStr = label.toString(); 66 appLabel = _service.applicationInfo.loadLabel(pm); 67 } 68 69 @Override compareTo(Object another)70 public int compareTo(Object another) { 71 return labelStr.compareTo(((BaseInfo) another).labelStr); 72 } 73 } 74 75 static public class RecognizerInfo extends BaseInfo { 76 public final boolean mSelectableAsDefault; 77 RecognizerInfo(PackageManager pm, ServiceInfo serviceInfo, String settings, boolean selectableAsDefault)78 public RecognizerInfo(PackageManager pm, 79 ServiceInfo serviceInfo, 80 String settings, 81 boolean selectableAsDefault) { 82 super(pm, serviceInfo, settings); 83 this.mSelectableAsDefault = selectableAsDefault; 84 } 85 } 86 87 final ArrayList<RecognizerInfo> mAvailableRecognizerInfos = new ArrayList<>(); 88 89 ComponentName mCurrentRecognizer; 90 VoiceInputHelper(Context context)91 public VoiceInputHelper(Context context) { 92 mContext = context; 93 94 mAvailableRecognition = mContext.getPackageManager().queryIntentServices( 95 new Intent(RecognitionService.SERVICE_INTERFACE), 96 PackageManager.GET_META_DATA); 97 } 98 buildUi()99 public void buildUi() { 100 // Get the currently selected recognizer from the secure setting. 101 String currentSetting = Settings.Secure.getString( 102 mContext.getContentResolver(), Settings.Secure.VOICE_RECOGNITION_SERVICE); 103 if (currentSetting != null && !currentSetting.isEmpty()) { 104 mCurrentRecognizer = ComponentName.unflattenFromString(currentSetting); 105 } else { 106 mCurrentRecognizer = null; 107 } 108 109 // Iterate through all the available recognizers and load up their info to show 110 // in the preference. 111 int size = mAvailableRecognition.size(); 112 for (int i = 0; i < size; i++) { 113 ResolveInfo resolveInfo = mAvailableRecognition.get(i); 114 ComponentName comp = new ComponentName(resolveInfo.serviceInfo.packageName, 115 resolveInfo.serviceInfo.name); 116 ServiceInfo si = resolveInfo.serviceInfo; 117 String settingsActivity = null; 118 // Always show in voice input settings unless specifically set to False. 119 boolean selectableAsDefault = true; 120 try (XmlResourceParser parser = si.loadXmlMetaData(mContext.getPackageManager(), 121 RecognitionService.SERVICE_META_DATA)) { 122 if (parser == null) { 123 throw new XmlPullParserException("No " + RecognitionService.SERVICE_META_DATA + 124 " meta-data for " + si.packageName); 125 } 126 127 Resources res = mContext.getPackageManager().getResourcesForApplication( 128 si.applicationInfo); 129 130 AttributeSet attrs = Xml.asAttributeSet(parser); 131 132 int type; 133 while ((type = parser.next()) != XmlPullParser.END_DOCUMENT 134 && type != XmlPullParser.START_TAG) { 135 } 136 137 String nodeName = parser.getName(); 138 if (!"recognition-service".equals(nodeName)) { 139 throw new XmlPullParserException( 140 "Meta-data does not start with recognition-service tag"); 141 } 142 143 TypedArray array = res.obtainAttributes(attrs, 144 com.android.internal.R.styleable.RecognitionService); 145 settingsActivity = array.getString( 146 com.android.internal.R.styleable.RecognitionService_settingsActivity); 147 selectableAsDefault = array.getBoolean( 148 com.android.internal.R.styleable.RecognitionService_selectableAsDefault, 149 true); 150 array.recycle(); 151 } catch (XmlPullParserException e) { 152 Log.e(TAG, "error parsing recognition service meta-data", e); 153 } catch (IOException e) { 154 Log.e(TAG, "error parsing recognition service meta-data", e); 155 } catch (PackageManager.NameNotFoundException e) { 156 Log.e(TAG, "error parsing recognition service meta-data", e); 157 } 158 // The current recognizer must always be shown in the settings, whatever its 159 // selectableAsDefault value is. 160 if (selectableAsDefault || comp.equals(mCurrentRecognizer)) { 161 mAvailableRecognizerInfos.add(new RecognizerInfo(mContext.getPackageManager(), 162 resolveInfo.serviceInfo, settingsActivity, selectableAsDefault)); 163 } 164 } 165 Collections.sort(mAvailableRecognizerInfos); 166 } 167 } 168