Add VIService ST module selection TestApis

Allow VIService to enumerate available underlying ST modules for test
purposes.

Allow VIService to select a specific STModule (via ModuleProperties)
when creating an AOHD.

If no STModule is passed, AOHD will attach to the first available
module -- retaining existing behavior.

Test: Manual verification of assistant recognition flow
Bug: 269358382
Change-Id: Id18240ba8db2c096b5703d068917fc022f0cd61e
This commit is contained in:
Atneya Nair
2023-02-13 09:45:15 -08:00
parent 9e31351327
commit 333b72b7b5
5 changed files with 126 additions and 22 deletions

View File

@@ -2800,6 +2800,12 @@ package android.service.voice {
ctor public VisibleActivityInfo(int, @NonNull android.os.IBinder);
}
public class VoiceInteractionService extends android.app.Service {
method @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_HOTWORD_DETECTION) public final android.service.voice.AlwaysOnHotwordDetector createAlwaysOnHotwordDetectorForTest(@NonNull String, @NonNull java.util.Locale, @NonNull android.hardware.soundtrigger.SoundTrigger.ModuleProperties, @NonNull java.util.concurrent.Executor, @NonNull android.service.voice.AlwaysOnHotwordDetector.Callback);
method @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_HOTWORD_DETECTION) public final android.service.voice.AlwaysOnHotwordDetector createAlwaysOnHotwordDetectorForTest(@NonNull String, @NonNull java.util.Locale, @Nullable android.os.PersistableBundle, @Nullable android.os.SharedMemory, @NonNull android.hardware.soundtrigger.SoundTrigger.ModuleProperties, @NonNull java.util.concurrent.Executor, @NonNull android.service.voice.AlwaysOnHotwordDetector.Callback);
method @NonNull public final java.util.List<android.hardware.soundtrigger.SoundTrigger.ModuleProperties> listModuleProperties();
}
public static class VoiceInteractionSession.ActivityId {
method @NonNull public android.os.IBinder getAssistToken();
method public int getTaskId();

View File

@@ -784,8 +784,13 @@ public class AlwaysOnHotwordDetector extends AbstractDetector {
mSupportSandboxedDetectionService = supportSandboxedDetectionService;
}
// Do nothing. This method should not be abstract.
// TODO (b/269355519) un-subclass AOHD.
@Override
void initialize(@Nullable PersistableBundle options, @Nullable SharedMemory sharedMemory) {
void initialize(@Nullable PersistableBundle options, @Nullable SharedMemory sharedMemory) {}
void initialize(@Nullable PersistableBundle options, @Nullable SharedMemory sharedMemory,
@Nullable SoundTrigger.ModuleProperties moduleProperties) {
if (mSupportSandboxedDetectionService) {
initAndVerifyDetector(options, sharedMemory, mInternalCallback,
DETECTOR_TYPE_TRUSTED_HOTWORD_DSP);
@@ -793,9 +798,18 @@ public class AlwaysOnHotwordDetector extends AbstractDetector {
try {
Identity identity = new Identity();
identity.packageName = ActivityThread.currentOpPackageName();
if (moduleProperties == null) {
List<SoundTrigger.ModuleProperties> modulePropList =
mModelManagementService.listModuleProperties(identity);
if (modulePropList.size() > 0) {
moduleProperties = modulePropList.get(0);
}
// (@atneya) intentionally let a null moduleProperties through until
// all CTS tests are fixed
}
mSoundTriggerSession =
mModelManagementService.createSoundTriggerSessionAsOriginator(
identity, mBinder);
identity, mBinder, moduleProperties);
} catch (RemoteException e) {
throw e.rethrowAsRuntimeException();
}

View File

@@ -24,6 +24,8 @@ import android.annotation.RequiresPermission;
import android.annotation.SdkConstant;
import android.annotation.SuppressLint;
import android.annotation.SystemApi;
import android.annotation.TestApi;
import android.app.ActivityThread;
import android.app.Service;
import android.app.compat.CompatChanges;
import android.compat.annotation.ChangeId;
@@ -33,7 +35,9 @@ import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.hardware.soundtrigger.KeyphraseEnrollmentInfo;
import android.hardware.soundtrigger.SoundTrigger;
import android.media.voice.KeyphraseModelManager;
import android.media.permission.Identity;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
@@ -385,6 +389,22 @@ public class VoiceInteractionService extends Service {
}
}
/**
* List available ST modules to attach to for test purposes.
* @hide
*/
@TestApi
@NonNull
public final List<SoundTrigger.ModuleProperties> listModuleProperties() {
Identity identity = new Identity();
identity.packageName = ActivityThread.currentOpPackageName();
try {
return mSystemService.listModuleProperties(identity);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/**
* Creates an {@link AlwaysOnHotwordDetector} for the given keyphrase and locale.
* This instance must be retained and used by the client.
@@ -422,7 +442,8 @@ public class VoiceInteractionService extends Service {
@SuppressLint("MissingNullability") AlwaysOnHotwordDetector.Callback callback) {
return createAlwaysOnHotwordDetectorInternal(keyphrase, locale,
/* supportHotwordDetectionService= */ false, /* options= */ null,
/* sharedMemory= */ null, /* executor= */ null, callback);
/* sharedMemory= */ null, /* moduleProperties */ null,
/* executor= */ null, callback);
}
/**
@@ -461,9 +482,36 @@ public class VoiceInteractionService extends Service {
Objects.requireNonNull(callback);
return createAlwaysOnHotwordDetectorInternal(keyphrase, locale,
/* supportHotwordDetectionService= */ false, /* options= */ null,
/* sharedMemory= */ null, executor, callback);
/* sharedMemory= */ null, /* moduleProperties */ null, executor, callback);
}
/**
* Same as {@link createAlwaysOnHotwordDetector(String, Locale, Executor,
* AlwaysOnHotwordDetector.Callback)}, but allow explicit selection of the underlying ST
* module to attach to.
* Use {@link listModuleProperties} to get available modules to attach to.
* @hide
*/
@TestApi
@RequiresPermission(Manifest.permission.MANAGE_HOTWORD_DETECTION)
@NonNull
public final AlwaysOnHotwordDetector createAlwaysOnHotwordDetectorForTest(
@NonNull String keyphrase, @SuppressLint("UseIcu") @NonNull Locale locale,
@NonNull SoundTrigger.ModuleProperties moduleProperties,
@NonNull @CallbackExecutor Executor executor,
@NonNull AlwaysOnHotwordDetector.Callback callback) {
Objects.requireNonNull(keyphrase);
Objects.requireNonNull(locale);
Objects.requireNonNull(moduleProperties);
Objects.requireNonNull(executor);
Objects.requireNonNull(callback);
return createAlwaysOnHotwordDetectorInternal(keyphrase, locale,
/* supportHotwordDetectionService= */ false, /* options= */ null,
/* sharedMemory= */ null, moduleProperties, executor, callback);
}
/**
* Create an {@link AlwaysOnHotwordDetector} and trigger a {@link HotwordDetectionService}
* service, then it will also pass the read-only data to hotword detection service.
@@ -515,7 +563,7 @@ public class VoiceInteractionService extends Service {
@SuppressLint("MissingNullability") AlwaysOnHotwordDetector.Callback callback) {
return createAlwaysOnHotwordDetectorInternal(keyphrase, locale,
/* supportHotwordDetectionService= */ true, options, sharedMemory,
/* executor= */ null, callback);
/* modulProperties */ null, /* executor= */ null, callback);
}
/**
@@ -566,16 +614,46 @@ public class VoiceInteractionService extends Service {
Objects.requireNonNull(executor);
Objects.requireNonNull(callback);
return createAlwaysOnHotwordDetectorInternal(keyphrase, locale,
/* supportHotwordDetectionService= */ true, options, sharedMemory, executor,
callback);
/* supportHotwordDetectionService= */ true, options, sharedMemory,
/* moduleProperties */ null, executor, callback);
}
/**
* Same as {@link createAlwaysOnHotwordDetector(String, Locale,
* PersistableBundle, SharedMemory, Executor, AlwaysOnHotwordDetector.Callback)},
* but allow explicit selection of the underlying ST module to attach to.
* Use {@link listModuleProperties} to get available modules to attach to.
* @hide
*/
@TestApi
@RequiresPermission(Manifest.permission.MANAGE_HOTWORD_DETECTION)
@NonNull
public final AlwaysOnHotwordDetector createAlwaysOnHotwordDetectorForTest(
@NonNull String keyphrase, @SuppressLint("UseIcu") @NonNull Locale locale,
@Nullable PersistableBundle options, @Nullable SharedMemory sharedMemory,
@NonNull SoundTrigger.ModuleProperties moduleProperties,
@NonNull @CallbackExecutor Executor executor,
@NonNull AlwaysOnHotwordDetector.Callback callback) {
Objects.requireNonNull(keyphrase);
Objects.requireNonNull(locale);
Objects.requireNonNull(moduleProperties);
Objects.requireNonNull(executor);
Objects.requireNonNull(callback);
return createAlwaysOnHotwordDetectorInternal(keyphrase, locale,
/* supportHotwordDetectionService= */ true, options, sharedMemory,
moduleProperties, executor, callback);
}
private AlwaysOnHotwordDetector createAlwaysOnHotwordDetectorInternal(
@SuppressLint("MissingNullability") String keyphrase, // TODO: nullability properly
@SuppressLint({"MissingNullability", "UseIcu"}) Locale locale,
boolean supportHotwordDetectionService,
@Nullable PersistableBundle options,
@Nullable SharedMemory sharedMemory,
@Nullable SoundTrigger.ModuleProperties moduleProperties,
@Nullable @CallbackExecutor Executor executor,
@SuppressLint("MissingNullability") AlwaysOnHotwordDetector.Callback callback) {
@@ -609,7 +687,8 @@ public class VoiceInteractionService extends Service {
try {
dspDetector.registerOnDestroyListener(this::onHotwordDetectorDestroyed);
dspDetector.initialize(options, sharedMemory);
// If moduleProperties is null, the default STModule is used.
dspDetector.initialize(options, sharedMemory, moduleProperties);
} catch (Exception e) {
mActiveDetectors.remove(dspDetector);
dspDetector.destroy();

View File

@@ -243,7 +243,14 @@ interface IVoiceInteractionManagerService {
*/
IVoiceInteractionSoundTriggerSession createSoundTriggerSessionAsOriginator(
in Identity originatorIdentity,
IBinder client);
IBinder client,
in SoundTrigger.ModuleProperties moduleProperties);
/**
* Lists properties of SoundTrigger modules that can be attached to by
* @{link createSoundTriggerSessionAsOriginator}.
*/
List<SoundTrigger.ModuleProperties> listModuleProperties(in Identity originatorIdentity);
/**
* Set configuration and pass read-only data to hotword detection service.

View File

@@ -377,7 +377,8 @@ public class VoiceInteractionManagerService extends SystemService {
@Override
public @NonNull IVoiceInteractionSoundTriggerSession createSoundTriggerSessionAsOriginator(
@NonNull Identity originatorIdentity, IBinder client) {
@NonNull Identity originatorIdentity, IBinder client,
@NonNull ModuleProperties moduleProperties) {
Objects.requireNonNull(originatorIdentity);
boolean forHotwordDetectionService;
synchronized (VoiceInteractionManagerServiceStub.this) {
@@ -396,7 +397,7 @@ public class VoiceInteractionManagerService extends SystemService {
originatorIdentity.uid = Binder.getCallingUid();
originatorIdentity.pid = Binder.getCallingPid();
session = new SoundTriggerSessionPermissionsDecorator(
createSoundTriggerSessionForSelfIdentity(client),
createSoundTriggerSessionForSelfIdentity(client, moduleProperties),
mContext,
originatorIdentity);
} else {
@@ -406,14 +407,14 @@ public class VoiceInteractionManagerService extends SystemService {
try (SafeCloseable ignored = PermissionUtil.establishIdentityDirect(
originatorIdentity)) {
session = new SoundTriggerSession(mSoundTriggerInternal.attach(client,
getDefaultModuleProperties(originatorIdentity)));
moduleProperties));
}
}
return new SoundTriggerSessionBinderProxy(session);
}
private IVoiceInteractionSoundTriggerSession createSoundTriggerSessionForSelfIdentity(
IBinder client) {
IBinder client, ModuleProperties moduleProperties) {
Identity identity = new Identity();
identity.uid = Process.myUid();
identity.pid = Process.myPid();
@@ -421,20 +422,17 @@ public class VoiceInteractionManagerService extends SystemService {
return Binder.withCleanCallingIdentity(() -> {
try (SafeCloseable ignored = IdentityContext.create(identity)) {
return new SoundTriggerSession(
mSoundTriggerInternal.attach(client,
getDefaultModuleProperties(identity)));
mSoundTriggerInternal.attach(client, moduleProperties));
}
});
}
private ModuleProperties getDefaultModuleProperties(Identity originatorIdentity) {
List<ModuleProperties> modulePropList = mSoundTriggerInternal
.listModuleProperties(originatorIdentity);
if (modulePropList.isEmpty()) {
return null;
} else {
return modulePropList.get(0);
@Override
public List<ModuleProperties> listModuleProperties(Identity originatorIdentity) {
synchronized (VoiceInteractionManagerServiceStub.this) {
enforceIsCurrentVoiceInteractionService();
}
return mSoundTriggerInternal.listModuleProperties(originatorIdentity);
}
// TODO: VI Make sure the caller is the current user or profile