/* * Copyright (C) 2017 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.settings.applications.assist; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.content.pm.ServiceInfo; import android.content.res.Resources; import android.content.res.TypedArray; import android.content.res.XmlResourceParser; import android.provider.Settings; import android.service.voice.VoiceInteractionService; import android.service.voice.VoiceInteractionServiceInfo; import android.speech.RecognitionService; import android.util.ArraySet; import android.util.AttributeSet; import android.util.Log; import android.util.Xml; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; import java.io.IOException; import java.util.ArrayList; import java.util.Collections; import java.util.List; public final class VoiceInputHelper { static final String TAG = "VoiceInputHelper"; final Context mContext; final List mAvailableVoiceInteractions; final List mAvailableRecognition; static public class BaseInfo implements Comparable { public final ServiceInfo service; public final ComponentName componentName; public final String key; public final ComponentName settings; public final CharSequence label; public final String labelStr; public final CharSequence appLabel; public BaseInfo(PackageManager pm, ServiceInfo _service, String _settings) { service = _service; componentName = new ComponentName(_service.packageName, _service.name); key = componentName.flattenToShortString(); settings = _settings != null ? new ComponentName(_service.packageName, _settings) : null; label = _service.loadLabel(pm); labelStr = label.toString(); appLabel = _service.applicationInfo.loadLabel(pm); } @Override public int compareTo(Object another) { return labelStr.compareTo(((BaseInfo) another).labelStr); } } static public class InteractionInfo extends BaseInfo { public final VoiceInteractionServiceInfo serviceInfo; public InteractionInfo(PackageManager pm, VoiceInteractionServiceInfo _service) { super(pm, _service.getServiceInfo(), _service.getSettingsActivity()); serviceInfo = _service; } } static public class RecognizerInfo extends BaseInfo { public RecognizerInfo(PackageManager pm, ServiceInfo _service, String _settings) { super(pm, _service, _settings); } } final ArrayList mAvailableInteractionInfos = new ArrayList<>(); final ArrayList mAvailableRecognizerInfos = new ArrayList<>(); ComponentName mCurrentVoiceInteraction; ComponentName mCurrentRecognizer; public VoiceInputHelper(Context context) { mContext = context; mAvailableVoiceInteractions = mContext.getPackageManager().queryIntentServices( new Intent(VoiceInteractionService.SERVICE_INTERFACE), PackageManager.GET_META_DATA); mAvailableRecognition = mContext.getPackageManager().queryIntentServices( new Intent(RecognitionService.SERVICE_INTERFACE), PackageManager.GET_META_DATA); } public void buildUi() { // Get the currently selected interactor from the secure setting. String currentSetting = Settings.Secure.getString( mContext.getContentResolver(), Settings.Secure.VOICE_INTERACTION_SERVICE); if (currentSetting != null && !currentSetting.isEmpty()) { mCurrentVoiceInteraction = ComponentName.unflattenFromString(currentSetting); } else { mCurrentVoiceInteraction = null; } ArraySet interactorRecognizers = new ArraySet<>(); // Iterate through all the available interactors and load up their info to show // in the preference. int size = mAvailableVoiceInteractions.size(); for (int i = 0; i < size; i++) { ResolveInfo resolveInfo = mAvailableVoiceInteractions.get(i); VoiceInteractionServiceInfo info = new VoiceInteractionServiceInfo( mContext.getPackageManager(), resolveInfo.serviceInfo); if (info.getParseError() != null) { Log.w("VoiceInteractionService", "Error in VoiceInteractionService " + resolveInfo.serviceInfo.packageName + "/" + resolveInfo.serviceInfo.name + ": " + info.getParseError()); continue; } mAvailableInteractionInfos.add(new InteractionInfo(mContext.getPackageManager(), info)); interactorRecognizers.add(new ComponentName(resolveInfo.serviceInfo.packageName, info.getRecognitionService())); } Collections.sort(mAvailableInteractionInfos); // Get the currently selected recognizer from the secure setting. currentSetting = Settings.Secure.getString( mContext.getContentResolver(), Settings.Secure.VOICE_RECOGNITION_SERVICE); if (currentSetting != null && !currentSetting.isEmpty()) { mCurrentRecognizer = ComponentName.unflattenFromString(currentSetting); } else { mCurrentRecognizer = null; } // Iterate through all the available recognizers and load up their info to show // in the preference. size = mAvailableRecognition.size(); for (int i = 0; i < size; i++) { ResolveInfo resolveInfo = mAvailableRecognition.get(i); ComponentName comp = new ComponentName(resolveInfo.serviceInfo.packageName, resolveInfo.serviceInfo.name); if (interactorRecognizers.contains(comp)) { //continue; } ServiceInfo si = resolveInfo.serviceInfo; XmlResourceParser parser = null; String settingsActivity = null; try { parser = si.loadXmlMetaData(mContext.getPackageManager(), RecognitionService.SERVICE_META_DATA); if (parser == null) { throw new XmlPullParserException("No " + RecognitionService.SERVICE_META_DATA + " meta-data for " + si.packageName); } Resources res = mContext.getPackageManager().getResourcesForApplication( si.applicationInfo); 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 = res.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); } catch (PackageManager.NameNotFoundException e) { Log.e(TAG, "error parsing recognition service meta-data", e); } finally { if (parser != null) parser.close(); } mAvailableRecognizerInfos.add(new RecognizerInfo(mContext.getPackageManager(), resolveInfo.serviceInfo, settingsActivity)); } Collections.sort(mAvailableRecognizerInfos); } }