/* * Copyright (C) 2021 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.server.voiceinteraction; import android.annotation.NonNull; import android.annotation.UserIdInt; 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.speech.RecognitionService; import android.text.TextUtils; 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.List; // TODO: Move this class somewhere else, along with the default recognizer logic in // VoiceInteractionManagerService. // TODO: Use this class in com.android.settings.applications.assist.VoiceInputHelper. /** * {@link ServiceInfo} and parsed metadata for a {@link RecognitionService}. */ class RecognitionServiceInfo { private static final String TAG = "RecognitionServiceInfo"; private final String mParseError; private final ServiceInfo mServiceInfo; private final boolean mSelectableAsDefault; /** * Queries the valid recognition services available for the user. */ static List getAvailableServices( @NonNull Context context, @UserIdInt int user) { List services = new ArrayList<>(); List resolveInfos = context.getPackageManager().queryIntentServicesAsUser( new Intent(RecognitionService.SERVICE_INTERFACE), PackageManager.MATCH_DIRECT_BOOT_AWARE | PackageManager.MATCH_DIRECT_BOOT_UNAWARE, user); for (ResolveInfo resolveInfo : resolveInfos) { RecognitionServiceInfo service = parseInfo(context.getPackageManager(), resolveInfo.serviceInfo); if (!TextUtils.isEmpty(service.mParseError)) { Log.w(TAG, "Parse error in getAvailableServices: " + service.mParseError); // We still use the recognizer to preserve pre-existing behavior. } services.add(service); } return services; } /** * Loads the service metadata published by the component. Success is indicated by {@link * #getParseError()}. * * @param pm A PackageManager from which the XML can be loaded; usually the * PackageManager from which {@code si} was originally retrieved. * @param si The {@link android.speech.RecognitionService} info. */ static RecognitionServiceInfo parseInfo(@NonNull PackageManager pm, @NonNull ServiceInfo si) { String parseError = ""; boolean selectableAsDefault = true; // default try (XmlResourceParser parser = si.loadXmlMetaData( pm, RecognitionService.SERVICE_META_DATA)) { if (parser == null) { parseError = "No " + RecognitionService.SERVICE_META_DATA + " meta-data for " + si.packageName; return new RecognitionServiceInfo(si, selectableAsDefault, parseError); } Resources res = pm.getResourcesForApplication(si.applicationInfo); AttributeSet attrs = Xml.asAttributeSet(parser); int type = 0; while (type != XmlPullParser.END_DOCUMENT && type != XmlPullParser.START_TAG) { type = parser.next(); } String nodeName = parser.getName(); if (!"recognition-service".equals(nodeName)) { throw new XmlPullParserException( "Meta-data does not start with recognition-service tag"); } TypedArray values = res.obtainAttributes( attrs, com.android.internal.R.styleable.RecognitionService); selectableAsDefault = values.getBoolean( com.android.internal.R.styleable.RecognitionService_selectableAsDefault, selectableAsDefault); values.recycle(); } catch (XmlPullParserException | IOException | PackageManager.NameNotFoundException e) { parseError = "Error parsing recognition service meta-data: " + e; } return new RecognitionServiceInfo(si, selectableAsDefault, parseError); } private RecognitionServiceInfo( @NonNull ServiceInfo si, boolean selectableAsDefault, @NonNull String parseError) { mServiceInfo = si; mSelectableAsDefault = selectableAsDefault; mParseError = parseError; } @NonNull public String getParseError() { return mParseError; } @NonNull public ServiceInfo getServiceInfo() { return mServiceInfo; } public boolean isSelectableAsDefault() { return mSelectableAsDefault; } }