Currently under `Settings > System > Languages & input > Speech` there is a `Voice input` entry with a cog which starts the SSBG model manager settings activity. Under the same preference group another entry labeled `On-device recognition` is added which would open a similar model manager settings activity, but for the on-device recognizer. That settings activity is yet to be implemented. The new entry should appear only under the following conditions: - An on-device speech recognition service must be available in the system. - A speech recognition service with a proper settings activity meta-data must exist in the same package as the default on-device speech recognition service. Bug: 235457391 Test: Manual, already existing robotests Change-Id: I17208c8725500ccb3dd2fa51a12b003d32073c4e
229 lines
9.4 KiB
Java
229 lines
9.4 KiB
Java
/*
|
|
* Copyright (C) 2022 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.language;
|
|
|
|
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.speech.RecognitionService;
|
|
import android.util.AttributeSet;
|
|
import android.util.Log;
|
|
import android.util.Pair;
|
|
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;
|
|
|
|
/** Helper class of the Voice Input setting. */
|
|
public final class VoiceInputHelper {
|
|
static final String TAG = "VoiceInputHelper";
|
|
final Context mContext;
|
|
|
|
/**
|
|
* Base info of the Voice Input provider.
|
|
*
|
|
* TODO: Remove this superclass as we only have 1 class now (RecognizerInfo).
|
|
* TODO: Group recognition service xml meta-data attributes in a single class.
|
|
*/
|
|
public static class BaseInfo implements Comparable<BaseInfo> {
|
|
public final ServiceInfo mService;
|
|
public final ComponentName mComponentName;
|
|
public final String mKey;
|
|
public final ComponentName mSettings;
|
|
public final CharSequence mLabel;
|
|
public final String mLabelStr;
|
|
public final CharSequence mAppLabel;
|
|
|
|
public BaseInfo(PackageManager pm, ServiceInfo service, String settings) {
|
|
mService = service;
|
|
mComponentName = new ComponentName(service.packageName, service.name);
|
|
mKey = mComponentName.flattenToShortString();
|
|
mSettings = settings != null
|
|
? new ComponentName(service.packageName, settings) : null;
|
|
mLabel = service.loadLabel(pm);
|
|
mLabelStr = mLabel.toString();
|
|
mAppLabel = service.applicationInfo.loadLabel(pm);
|
|
}
|
|
|
|
@Override
|
|
public int compareTo(BaseInfo another) {
|
|
return mLabelStr.compareTo(another.mLabelStr);
|
|
}
|
|
}
|
|
|
|
/** Info of the speech recognizer (i.e. recognition service). */
|
|
public static class RecognizerInfo extends BaseInfo {
|
|
public final boolean mSelectableAsDefault;
|
|
|
|
public RecognizerInfo(PackageManager pm,
|
|
ServiceInfo serviceInfo,
|
|
String settings,
|
|
boolean selectableAsDefault) {
|
|
super(pm, serviceInfo, settings);
|
|
this.mSelectableAsDefault = selectableAsDefault;
|
|
}
|
|
}
|
|
|
|
ArrayList<RecognizerInfo> mAvailableRecognizerInfos = new ArrayList<>();
|
|
|
|
ComponentName mCurrentRecognizer;
|
|
|
|
public VoiceInputHelper(Context context) {
|
|
mContext = context;
|
|
}
|
|
|
|
/** Draws the UI of the Voice Input picker page. */
|
|
public void buildUi() {
|
|
// Get the currently selected recognizer from the secure setting.
|
|
String currentSetting = Settings.Secure.getString(
|
|
mContext.getContentResolver(), Settings.Secure.VOICE_RECOGNITION_SERVICE);
|
|
if (currentSetting != null && !currentSetting.isEmpty()) {
|
|
mCurrentRecognizer = ComponentName.unflattenFromString(currentSetting);
|
|
} else {
|
|
mCurrentRecognizer = null;
|
|
}
|
|
|
|
final ArrayList<RecognizerInfo> validRecognitionServices =
|
|
validRecognitionServices(mContext);
|
|
|
|
// Filter all recognizers which can be selected as default or are the current recognizer.
|
|
mAvailableRecognizerInfos = new ArrayList<>();
|
|
for (RecognizerInfo recognizerInfo: validRecognitionServices) {
|
|
if (recognizerInfo.mSelectableAsDefault || new ComponentName(
|
|
recognizerInfo.mService.packageName, recognizerInfo.mService.name)
|
|
.equals(mCurrentRecognizer)) {
|
|
mAvailableRecognizerInfos.add(recognizerInfo);
|
|
}
|
|
}
|
|
|
|
Collections.sort(mAvailableRecognizerInfos);
|
|
}
|
|
|
|
/**
|
|
* Query all services with {@link RecognitionService#SERVICE_INTERFACE} intent. Filter only
|
|
* those which have proper xml meta-data which start with a `recognition-service` tag.
|
|
* Filtered services are sorted by their labels in the ascending order.
|
|
*
|
|
* @param context {@link Context} inside which the settings app is run.
|
|
*
|
|
* @return {@link ArrayList}<{@link RecognizerInfo}>
|
|
* containing info about the filtered speech recognition services.
|
|
*/
|
|
static ArrayList<RecognizerInfo> validRecognitionServices(Context context) {
|
|
final List<ResolveInfo> resolvedRecognitionServices =
|
|
context.getPackageManager().queryIntentServices(
|
|
new Intent(RecognitionService.SERVICE_INTERFACE),
|
|
PackageManager.GET_META_DATA);
|
|
|
|
final ArrayList<RecognizerInfo> validRecognitionServices = new ArrayList<>();
|
|
|
|
for (ResolveInfo resolveInfo: resolvedRecognitionServices) {
|
|
final ServiceInfo serviceInfo = resolveInfo.serviceInfo;
|
|
|
|
final Pair<String, Boolean> recognitionServiceAttributes =
|
|
parseRecognitionServiceXmlMetadata(context, serviceInfo);
|
|
|
|
if (recognitionServiceAttributes != null) {
|
|
validRecognitionServices.add(new RecognizerInfo(
|
|
context.getPackageManager(),
|
|
serviceInfo,
|
|
recognitionServiceAttributes.first /* settingsActivity */,
|
|
recognitionServiceAttributes.second /* selectableAsDefault */));
|
|
}
|
|
}
|
|
|
|
return validRecognitionServices;
|
|
}
|
|
|
|
/**
|
|
* Load recognition service's xml meta-data and parse it. Return the meta-data attributes,
|
|
* namely, `settingsActivity` {@link String} and `selectableAsDefault` {@link Boolean}.
|
|
*
|
|
* <p>Parsing fails if the meta-data for the given service is not found
|
|
* or the found meta-data does not start with a `recognition-service`.</p>
|
|
*
|
|
* @param context {@link Context} inside which the settings app is run.
|
|
* @param serviceInfo {@link ServiceInfo} containing info
|
|
* about the speech recognition service in question.
|
|
*
|
|
* @return {@link Pair}<{@link String}, {@link Boolean}> containing `settingsActivity`
|
|
* and `selectableAsDefault` attributes if the parsing was successful, {@code null} otherwise.
|
|
*/
|
|
private static Pair<String, Boolean> parseRecognitionServiceXmlMetadata(
|
|
Context context, ServiceInfo serviceInfo) {
|
|
// Default recognition service attribute values.
|
|
// Every recognizer can be selected unless specified otherwise.
|
|
String settingsActivity;
|
|
boolean selectableAsDefault = true;
|
|
|
|
// Parse xml meta-data.
|
|
try (XmlResourceParser parser = serviceInfo.loadXmlMetaData(
|
|
context.getPackageManager(), RecognitionService.SERVICE_META_DATA)) {
|
|
if (parser == null) {
|
|
throw new XmlPullParserException(String.format("No %s meta-data for %s package",
|
|
RecognitionService.SERVICE_META_DATA, serviceInfo.packageName));
|
|
}
|
|
|
|
final Resources res = context.getPackageManager().getResourcesForApplication(
|
|
serviceInfo.applicationInfo);
|
|
final AttributeSet attrs = Xml.asAttributeSet(parser);
|
|
|
|
// Xml meta-data must start with a `recognition-service tag`.
|
|
int type;
|
|
while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
|
|
&& type != XmlPullParser.START_TAG) {
|
|
// Intentionally do nothing.
|
|
}
|
|
|
|
final String nodeName = parser.getName();
|
|
if (!"recognition-service".equals(nodeName)) {
|
|
throw new XmlPullParserException(String.format(
|
|
"%s package meta-data does not start with a `recognition-service` tag",
|
|
serviceInfo.packageName));
|
|
}
|
|
|
|
final TypedArray array = res.obtainAttributes(attrs,
|
|
com.android.internal.R.styleable.RecognitionService);
|
|
settingsActivity = array.getString(
|
|
com.android.internal.R.styleable.RecognitionService_settingsActivity);
|
|
selectableAsDefault = array.getBoolean(
|
|
com.android.internal.R.styleable.RecognitionService_selectableAsDefault,
|
|
selectableAsDefault);
|
|
array.recycle();
|
|
} catch (XmlPullParserException | IOException
|
|
| PackageManager.NameNotFoundException e) {
|
|
Log.e(TAG, String.format("Error parsing %s package recognition service meta-data",
|
|
serviceInfo.packageName), e);
|
|
return null;
|
|
}
|
|
|
|
return Pair.create(settingsActivity, selectableAsDefault);
|
|
}
|
|
}
|