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:
Ahaan Ugale
2021-03-22 22:55:52 -07:00
parent 624a0f2d0e
commit 50261c6ae9
2 changed files with 197 additions and 13 deletions

View File

@@ -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;
}
}

View File

@@ -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);