Unset default reco if selectableAsDefault=false.
A non selectableAsDefault recognition service can still become the default if no other services are available. Bug: 180964085 Test: non selectableAsDefault reco is unset at boot and replaced Test: selectableAsDefault recos aren't affected Test: system default reco still works Change-Id: I429e6e457bc81ef0642ca90ccd9eb47eb10fbab8
This commit is contained in:
@@ -0,0 +1,148 @@
|
||||
/*
|
||||
* 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<RecognitionServiceInfo> getAvailableServices(
|
||||
@NonNull Context context, @UserIdInt int user) {
|
||||
List<RecognitionServiceInfo> services = new ArrayList<>();
|
||||
|
||||
List<ResolveInfo> 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;
|
||||
}
|
||||
}
|
||||
@@ -71,7 +71,6 @@ import android.service.voice.VoiceInteractionManagerInternal;
|
||||
import android.service.voice.VoiceInteractionService;
|
||||
import android.service.voice.VoiceInteractionServiceInfo;
|
||||
import android.service.voice.VoiceInteractionSession;
|
||||
import android.speech.RecognitionService;
|
||||
import android.text.TextUtils;
|
||||
import android.util.ArrayMap;
|
||||
import android.util.ArraySet;
|
||||
@@ -103,6 +102,7 @@ import com.android.server.wm.ActivityTaskManagerInternal;
|
||||
import java.io.FileDescriptor;
|
||||
import java.io.PrintWriter;
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.ListIterator;
|
||||
@@ -404,9 +404,30 @@ public class VoiceInteractionManagerService extends SystemService {
|
||||
ComponentName curInteractor = !TextUtils.isEmpty(curInteractorStr)
|
||||
? ComponentName.unflattenFromString(curInteractorStr) : null;
|
||||
try {
|
||||
recognizerInfo = pm.getServiceInfo(curRecognizer,
|
||||
recognizerInfo = pm.getServiceInfo(
|
||||
curRecognizer,
|
||||
PackageManager.MATCH_DIRECT_BOOT_AWARE
|
||||
| PackageManager.MATCH_DIRECT_BOOT_UNAWARE, userHandle);
|
||||
| PackageManager.MATCH_DIRECT_BOOT_UNAWARE
|
||||
| PackageManager.GET_META_DATA,
|
||||
userHandle);
|
||||
if (recognizerInfo != null) {
|
||||
RecognitionServiceInfo rsi =
|
||||
RecognitionServiceInfo.parseInfo(
|
||||
mContext.getPackageManager(), recognizerInfo);
|
||||
if (!TextUtils.isEmpty(rsi.getParseError())) {
|
||||
Log.w(TAG, "Parse error in getAvailableServices: "
|
||||
+ rsi.getParseError());
|
||||
// We still use the recognizer to preserve pre-existing behavior.
|
||||
}
|
||||
if (!rsi.isSelectableAsDefault()) {
|
||||
if (DEBUG) {
|
||||
Slog.d(TAG, "Found non selectableAsDefault recognizer as"
|
||||
+ " default. Unsetting the default and looking for another"
|
||||
+ " one.");
|
||||
}
|
||||
recognizerInfo = null;
|
||||
}
|
||||
}
|
||||
if (curInteractor != null) {
|
||||
interactorInfo = pm.getServiceInfo(curInteractor,
|
||||
PackageManager.MATCH_DIRECT_BOOT_AWARE
|
||||
@@ -650,19 +671,23 @@ public class VoiceInteractionManagerService extends SystemService {
|
||||
prefPackage = getDefaultRecognizer();
|
||||
}
|
||||
|
||||
List<ResolveInfo> available =
|
||||
mContext.getPackageManager().queryIntentServicesAsUser(
|
||||
new Intent(RecognitionService.SERVICE_INTERFACE),
|
||||
PackageManager.MATCH_DIRECT_BOOT_AWARE
|
||||
| PackageManager.MATCH_DIRECT_BOOT_UNAWARE, userHandle);
|
||||
int numAvailable = available.size();
|
||||
if (numAvailable == 0) {
|
||||
List<RecognitionServiceInfo> available =
|
||||
RecognitionServiceInfo.getAvailableServices(mContext, userHandle);
|
||||
if (available.size() == 0) {
|
||||
Slog.w(TAG, "no available voice recognition services found for user " + userHandle);
|
||||
return null;
|
||||
} else {
|
||||
List<RecognitionServiceInfo> nonSelectableAsDefault =
|
||||
removeNonSelectableAsDefault(available);
|
||||
if (available.size() == 0) {
|
||||
Slog.w(TAG, "No selectableAsDefault recognition services found for user "
|
||||
+ userHandle + ". Falling back to non selectableAsDefault ones.");
|
||||
available = nonSelectableAsDefault;
|
||||
}
|
||||
int numAvailable = available.size();
|
||||
if (prefPackage != null) {
|
||||
for (int i=0; i<numAvailable; i++) {
|
||||
ServiceInfo serviceInfo = available.get(i).serviceInfo;
|
||||
for (int i = 0; i < numAvailable; i++) {
|
||||
ServiceInfo serviceInfo = available.get(i).getServiceInfo();
|
||||
if (prefPackage.equals(serviceInfo.packageName)) {
|
||||
return new ComponentName(serviceInfo.packageName, serviceInfo.name);
|
||||
}
|
||||
@@ -672,11 +697,22 @@ public class VoiceInteractionManagerService extends SystemService {
|
||||
Slog.w(TAG, "more than one voice recognition service found, picking first");
|
||||
}
|
||||
|
||||
ServiceInfo serviceInfo = available.get(0).serviceInfo;
|
||||
ServiceInfo serviceInfo = available.get(0).getServiceInfo();
|
||||
return new ComponentName(serviceInfo.packageName, serviceInfo.name);
|
||||
}
|
||||
}
|
||||
|
||||
private List<RecognitionServiceInfo> removeNonSelectableAsDefault(
|
||||
List<RecognitionServiceInfo> services) {
|
||||
List<RecognitionServiceInfo> nonSelectableAsDefault = new ArrayList<>();
|
||||
for (int i = services.size() - 1; i >= 0; i--) {
|
||||
if (!services.get(i).isSelectableAsDefault()) {
|
||||
nonSelectableAsDefault.add(services.remove(i));
|
||||
}
|
||||
}
|
||||
return nonSelectableAsDefault;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String getDefaultRecognizer() {
|
||||
String recognizer = mContext.getString(R.string.config_systemSpeechRecognizer);
|
||||
|
||||
Reference in New Issue
Block a user