Merge changes from topic "st_cts_workaround"

* changes:
  Temporarily allow STHelper without module
  Add module selection APIs to ST
This commit is contained in:
Atneya Nair
2023-02-16 23:40:24 +00:00
committed by Android (Google) Code Review
6 changed files with 170 additions and 92 deletions

View File

@@ -17,6 +17,7 @@
package com.android.internal.app;
import android.media.permission.Identity;
import android.hardware.soundtrigger.SoundTrigger;
import com.android.internal.app.ISoundTriggerSession;
/**
@@ -43,6 +44,7 @@ interface ISoundTriggerService {
* to clean-up whenever that happens.
*/
ISoundTriggerSession attachAsOriginator(in Identity originatorIdentity,
in SoundTrigger.ModuleProperties moduleProperties,
IBinder client);
/**
@@ -64,5 +66,12 @@ interface ISoundTriggerService {
*/
ISoundTriggerSession attachAsMiddleman(in Identity middlemanIdentity,
in Identity originatorIdentity,
in SoundTrigger.ModuleProperties moduleProperties,
IBinder client);
/**
* Get available underlying SoundTrigger modules to attach to.
*/
List<SoundTrigger.ModuleProperties> listModuleProperties(in Identity originatorIdentity);
}

View File

@@ -32,6 +32,7 @@ import android.hardware.soundtrigger.SoundTrigger;
import android.hardware.soundtrigger.SoundTrigger.GenericSoundModel;
import android.hardware.soundtrigger.SoundTrigger.KeyphraseSoundModel;
import android.hardware.soundtrigger.SoundTrigger.ModelParamRange;
import android.hardware.soundtrigger.SoundTrigger.ModuleProperties;
import android.hardware.soundtrigger.SoundTrigger.RecognitionConfig;
import android.hardware.soundtrigger.SoundTrigger.SoundModel;
import android.media.permission.ClearCallingIdentityContext;
@@ -52,6 +53,7 @@ import com.android.internal.app.ISoundTriggerSession;
import com.android.internal.util.Preconditions;
import java.util.HashMap;
import java.util.List;
import java.util.Objects;
import java.util.UUID;
@@ -92,8 +94,16 @@ public final class SoundTriggerManager {
originatorIdentity.packageName = ActivityThread.currentOpPackageName();
try (SafeCloseable ignored = ClearCallingIdentityContext.create()) {
mSoundTriggerSession = soundTriggerService.attachAsOriginator(originatorIdentity,
mBinderToken);
List<ModuleProperties> modulePropertiesList = soundTriggerService
.listModuleProperties(originatorIdentity);
if (!modulePropertiesList.isEmpty()) {
mSoundTriggerSession = soundTriggerService.attachAsOriginator(
originatorIdentity,
modulePropertiesList.get(0),
mBinderToken);
} else {
mSoundTriggerSession = null;
}
}
} catch (RemoteException e) {
throw e.rethrowAsRuntimeException();
@@ -110,6 +120,9 @@ public final class SoundTriggerManager {
@RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER)
@Deprecated
public void updateModel(Model model) {
if (mSoundTriggerSession == null) {
throw new IllegalStateException("No underlying SoundTriggerModule available");
}
try {
mSoundTriggerSession.updateSoundModel(model.getGenericSoundModel());
} catch (RemoteException e) {
@@ -128,6 +141,9 @@ public final class SoundTriggerManager {
@Nullable
@Deprecated
public Model getModel(UUID soundModelId) {
if (mSoundTriggerSession == null) {
throw new IllegalStateException("No underlying SoundTriggerModule available");
}
try {
GenericSoundModel model =
mSoundTriggerSession.getSoundModel(new ParcelUuid(soundModelId));
@@ -149,6 +165,10 @@ public final class SoundTriggerManager {
@RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER)
@Deprecated
public void deleteModel(UUID soundModelId) {
if (mSoundTriggerSession == null) {
throw new IllegalStateException("No underlying SoundTriggerModule available");
}
try {
mSoundTriggerSession.deleteSoundModel(new ParcelUuid(soundModelId));
} catch (RemoteException e) {
@@ -176,7 +196,7 @@ public final class SoundTriggerManager {
@RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER)
public SoundTriggerDetector createSoundTriggerDetector(UUID soundModelId,
@NonNull SoundTriggerDetector.Callback callback, @Nullable Handler handler) {
if (soundModelId == null) {
if (soundModelId == null || mSoundTriggerSession == null) {
return null;
}
@@ -342,7 +362,7 @@ public final class SoundTriggerManager {
@RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER)
@UnsupportedAppUsage
public int loadSoundModel(SoundModel soundModel) {
if (soundModel == null) {
if (soundModel == null || mSoundTriggerSession == null) {
return STATUS_ERROR;
}
@@ -389,7 +409,9 @@ public final class SoundTriggerManager {
Preconditions.checkNotNull(soundModelId);
Preconditions.checkNotNull(detectionService);
Preconditions.checkNotNull(config);
if (mSoundTriggerSession == null) {
return STATUS_ERROR;
}
try {
return mSoundTriggerSession.startRecognitionForService(new ParcelUuid(soundModelId),
params, detectionService, config);
@@ -405,7 +427,7 @@ public final class SoundTriggerManager {
@RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER)
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
public int stopRecognition(UUID soundModelId) {
if (soundModelId == null) {
if (soundModelId == null || mSoundTriggerSession == null) {
return STATUS_ERROR;
}
try {
@@ -422,7 +444,7 @@ public final class SoundTriggerManager {
@RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER)
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
public int unloadSoundModel(UUID soundModelId) {
if (soundModelId == null) {
if (soundModelId == null || mSoundTriggerSession == null) {
return STATUS_ERROR;
}
try {
@@ -440,7 +462,7 @@ public final class SoundTriggerManager {
@RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER)
@UnsupportedAppUsage
public boolean isRecognitionActive(UUID soundModelId) {
if (soundModelId == null) {
if (soundModelId == null || mSoundTriggerSession == null) {
return false;
}
try {
@@ -475,7 +497,7 @@ public final class SoundTriggerManager {
@RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER)
@UnsupportedAppUsage
public int getModelState(UUID soundModelId) {
if (soundModelId == null) {
if (soundModelId == null || mSoundTriggerSession == null) {
return STATUS_ERROR;
}
try {
@@ -492,8 +514,10 @@ public final class SoundTriggerManager {
*/
@RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER)
@Nullable
public SoundTrigger.ModuleProperties getModuleProperties() {
public ModuleProperties getModuleProperties() {
if (mSoundTriggerSession == null) {
return null;
}
try {
return mSoundTriggerSession.getModuleProperties();
} catch (RemoteException e) {
@@ -520,6 +544,10 @@ public final class SoundTriggerManager {
@RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER)
public int setParameter(@Nullable UUID soundModelId,
@ModelParams int modelParam, int value) {
if (mSoundTriggerSession == null) {
return SoundTrigger.STATUS_INVALID_OPERATION;
}
try {
return mSoundTriggerSession.setParameter(new ParcelUuid(soundModelId), modelParam,
value);
@@ -543,6 +571,10 @@ public final class SoundTriggerManager {
@RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER)
public int getParameter(@NonNull UUID soundModelId,
@ModelParams int modelParam) {
if (mSoundTriggerSession == null) {
throw new IllegalArgumentException("Sound model is not loaded: "
+ soundModelId.toString());
}
try {
return mSoundTriggerSession.getParameter(new ParcelUuid(soundModelId), modelParam);
} catch (RemoteException e) {
@@ -563,6 +595,9 @@ public final class SoundTriggerManager {
@Nullable
public ModelParamRange queryParameter(@Nullable UUID soundModelId,
@ModelParams int modelParam) {
if (mSoundTriggerSession == null) {
return null;
}
try {
return mSoundTriggerSession.queryParameter(new ParcelUuid(soundModelId), modelParam);
} catch (RemoteException e) {

View File

@@ -56,9 +56,12 @@ import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.UUID;
import java.util.function.Function;
import java.util.function.Supplier;
/**
* Helper for {@link SoundTrigger} APIs. Supports two types of models:
@@ -74,6 +77,9 @@ public class SoundTriggerHelper implements SoundTrigger.StatusListener {
static final String TAG = "SoundTriggerHelper";
static final boolean DBG = false;
// Module ID if there is no available module to connect to.
public static final int INVALID_MODULE_ID = -1;
/**
* Return codes for {@link #startRecognition(int, KeyphraseSoundModel,
* IRecognitionStatusCallback, RecognitionConfig)},
@@ -84,10 +90,6 @@ public class SoundTriggerHelper implements SoundTrigger.StatusListener {
private static final int INVALID_VALUE = Integer.MIN_VALUE;
/** The {@link ModuleProperties} for the system, or null if none exists. */
final ModuleProperties mModuleProperties;
/** The properties for the DSP module */
private SoundTriggerModule mModule;
private final Object mLock = new Object();
private final Context mContext;
@@ -114,7 +116,6 @@ public class SoundTriggerHelper implements SoundTrigger.StatusListener {
private PowerSaveModeListener mPowerSaveModeListener;
private final SoundTriggerModuleProvider mModuleProvider;
// Handler to process call state changes will delay to allow time for the audio
// and sound trigger HALs to process the end of call notifications
@@ -123,46 +124,28 @@ public class SoundTriggerHelper implements SoundTrigger.StatusListener {
private static final int MSG_CALL_STATE_CHANGED = 0;
private static final int CALL_INACTIVE_MSG_DELAY_MS = 1000;
/**
* Provider interface for retrieving SoundTriggerModule instances
*/
public interface SoundTriggerModuleProvider {
/**
* Populate module properties for all available modules
*
* @param modules List of ModuleProperties to be populated
* @return Status int 0 on success.
*/
int listModuleProperties(@NonNull ArrayList<SoundTrigger.ModuleProperties> modules);
// TODO(b/269366605) Temporary solution to query correct moduleProperties
private final int mModuleId;
private final Function<SoundTrigger.StatusListener, SoundTriggerModule> mModuleProvider;
private final Supplier<List<ModuleProperties>> mModulePropertiesProvider;
/**
* Get SoundTriggerModule based on {@link SoundTrigger.ModuleProperties#getId()}
*
* @param moduleId Module ID
* @param statusListener Client listener to be associated with the returned module
* @return Module associated with moduleId
*/
SoundTriggerModule getModule(int moduleId, SoundTrigger.StatusListener statusListener);
}
SoundTriggerHelper(Context context, SoundTriggerModuleProvider moduleProvider) {
ArrayList <ModuleProperties> modules = new ArrayList<>();
mModuleProvider = moduleProvider;
int status = mModuleProvider.listModuleProperties(modules);
SoundTriggerHelper(Context context,
@NonNull Function<SoundTrigger.StatusListener, SoundTriggerModule> moduleProvider,
int moduleId,
@NonNull Supplier<List<ModuleProperties>> modulePropertiesProvider) {
mModuleId = moduleId;
mContext = context;
mTelephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
mPowerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
mModelDataMap = new HashMap<UUID, ModelData>();
mKeyphraseUuidMap = new HashMap<Integer, UUID>();
if (status != SoundTrigger.STATUS_OK || modules.size() == 0) {
Slog.w(TAG, "listModules status=" + status + ", # of modules=" + modules.size());
mModuleProperties = null;
mModuleProvider = moduleProvider;
mModulePropertiesProvider = modulePropertiesProvider;
if (moduleId == INVALID_MODULE_ID) {
mModule = null;
} else {
// TODO: Figure out how to determine which module corresponds to the DSP hardware.
mModuleProperties = modules.get(0);
mModule = mModuleProvider.apply(this);
}
Looper looper = Looper.myLooper();
if (looper == null) {
looper = Looper.getMainLooper();
@@ -245,7 +228,6 @@ public class SoundTriggerHelper implements SoundTrigger.StatusListener {
+ " soundModel=" + soundModel + ", callback=" + callback.asBinder()
+ ", recognitionConfig=" + recognitionConfig
+ ", runInBatterySaverMode=" + runInBatterySaverMode);
Slog.d(TAG, "moduleProperties=" + mModuleProperties);
dumpModelStateLocked();
}
@@ -290,11 +272,8 @@ public class SoundTriggerHelper implements SoundTrigger.StatusListener {
private int prepareForRecognition(ModelData modelData) {
if (mModule == null) {
mModule = mModuleProvider.getModule(mModuleProperties.getId(), this);
if (mModule == null) {
Slog.w(TAG, "prepareForRecognition: cannot attach to sound trigger module");
return STATUS_ERROR;
}
Slog.w(TAG, "prepareForRecognition: cannot attach to sound trigger module");
return STATUS_ERROR;
}
// Load the model if it is not loaded.
if (!modelData.isModelLoaded()) {
@@ -336,11 +315,6 @@ public class SoundTriggerHelper implements SoundTrigger.StatusListener {
IRecognitionStatusCallback callback, RecognitionConfig recognitionConfig,
int keyphraseId, boolean runInBatterySaverMode) {
synchronized (mLock) {
if (mModuleProperties == null) {
Slog.w(TAG, "Attempting startRecognition without the capability");
return STATUS_ERROR;
}
IRecognitionStatusCallback oldCallback = modelData.getCallback();
if (oldCallback != null && oldCallback.asBinder() != callback.asBinder()) {
Slog.w(TAG, "Canceling previous recognition for model id: "
@@ -486,8 +460,8 @@ public class SoundTriggerHelper implements SoundTrigger.StatusListener {
if (callback == null) {
return STATUS_ERROR;
}
if (mModuleProperties == null || mModule == null) {
Slog.w(TAG, "Attempting stopRecognition without the capability");
if (mModule == null) {
Slog.w(TAG, "Attempting stopRecognition after detach");
return STATUS_ERROR;
}
@@ -563,7 +537,13 @@ public class SoundTriggerHelper implements SoundTrigger.StatusListener {
}
public ModuleProperties getModuleProperties() {
return mModuleProperties;
for (ModuleProperties moduleProperties : mModulePropertiesProvider.get()) {
if (moduleProperties.getId() == mModuleId) {
return moduleProperties;
}
}
Slog.e(TAG, "Module properties not found for existing moduleId " + mModuleId);
return null;
}
int unloadKeyphraseSoundModel(int keyphraseId) {
@@ -1027,7 +1007,7 @@ public class SoundTriggerHelper implements SoundTrigger.StatusListener {
internalClearGlobalStateLocked();
if (mModule != null) {
mModule.detach();
mModule = null;
mModule = mModuleProvider.apply(this);
}
}
}
@@ -1098,7 +1078,6 @@ public class SoundTriggerHelper implements SoundTrigger.StatusListener {
void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
synchronized (mLock) {
pw.print(" module properties=");
pw.println(mModuleProperties == null ? "null" : mModuleProperties);
pw.print(" call active=");
pw.println(mCallActive);
pw.println(" SoundTrigger Power State=" + mSoundTriggerPowerSaveMode);
@@ -1444,7 +1423,7 @@ public class SoundTriggerHelper implements SoundTrigger.StatusListener {
// Computes whether we have any recognition running at all (voice or generic). Sets
// the mRecognitionRequested variable with the result.
private boolean computeRecognitionRequestedLocked() {
if (mModuleProperties == null || mModule == null) {
if (mModule == null) {
mRecognitionRequested = false;
return mRecognitionRequested;
}

View File

@@ -26,12 +26,14 @@ import android.hardware.soundtrigger.SoundTrigger.KeyphraseSoundModel;
import android.hardware.soundtrigger.SoundTrigger.ModelParamRange;
import android.hardware.soundtrigger.SoundTrigger.ModuleProperties;
import android.hardware.soundtrigger.SoundTrigger.RecognitionConfig;
import android.media.permission.Identity;
import android.os.IBinder;
import com.android.server.voiceinteraction.VoiceInteractionManagerService;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.List;
/**
* Provides a local service for managing voice-related recoginition models. This is primarily used
@@ -46,7 +48,11 @@ public interface SoundTriggerInternal {
int STATUS_ERROR = SoundTrigger.STATUS_ERROR;
int STATUS_OK = SoundTrigger.STATUS_OK;
Session attach(@NonNull IBinder client);
// Attach to a specific underlying STModule
Session attach(@NonNull IBinder client, ModuleProperties underlyingModule);
// Enumerate possible STModules to attach to
List<ModuleProperties> listModuleProperties(Identity originatorIdentity);
/**
* Dumps service-wide information.

View File

@@ -51,7 +51,6 @@ import android.hardware.soundtrigger.SoundTrigger.ModelParamRange;
import android.hardware.soundtrigger.SoundTrigger.ModuleProperties;
import android.hardware.soundtrigger.SoundTrigger.RecognitionConfig;
import android.hardware.soundtrigger.SoundTrigger.SoundModel;
import android.hardware.soundtrigger.SoundTriggerModule;
import android.media.AudioAttributes;
import android.media.AudioFormat;
import android.media.AudioRecord;
@@ -89,6 +88,7 @@ import com.android.server.utils.EventLogger;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.TreeMap;
@@ -215,47 +215,73 @@ public class SoundTriggerService extends SystemService {
}
}
private SoundTriggerHelper newSoundTriggerHelper() {
private SoundTriggerHelper newSoundTriggerHelper(ModuleProperties moduleProperties) {
Identity middlemanIdentity = new Identity();
middlemanIdentity.packageName = ActivityThread.currentOpPackageName();
Identity originatorIdentity = IdentityContext.getNonNull();
return new SoundTriggerHelper(mContext,
new SoundTriggerHelper.SoundTriggerModuleProvider() {
@Override
public int listModuleProperties(ArrayList<ModuleProperties> modules) {
return SoundTrigger.listModulesAsMiddleman(modules, middlemanIdentity,
originatorIdentity);
}
ArrayList<ModuleProperties> moduleList = new ArrayList<>();
SoundTrigger.listModulesAsMiddleman(moduleList, middlemanIdentity,
originatorIdentity);
@Override
public SoundTriggerModule getModule(int moduleId,
SoundTrigger.StatusListener statusListener) {
return SoundTrigger.attachModuleAsMiddleman(moduleId, statusListener, null,
middlemanIdentity, originatorIdentity);
}
// Don't fail existing CTS tests which run without a ST module
final int moduleId = (moduleProperties != null) ?
moduleProperties.getId() : SoundTriggerHelper.INVALID_MODULE_ID;
if (moduleId != SoundTriggerHelper.INVALID_MODULE_ID) {
if (!moduleList.contains(moduleProperties)) {
throw new IllegalArgumentException("Invalid module properties");
}
}
return new SoundTriggerHelper(
mContext,
(SoundTrigger.StatusListener statusListener) ->
SoundTrigger.attachModuleAsMiddleman(
moduleId, statusListener, null /* handler */,
middlemanIdentity, originatorIdentity),
moduleId,
() -> {
ArrayList<ModuleProperties> modulePropList = new ArrayList<>();
SoundTrigger.listModulesAsMiddleman(modulePropList, middlemanIdentity,
originatorIdentity);
return modulePropList;
});
}
class SoundTriggerServiceStub extends ISoundTriggerService.Stub {
@Override
public ISoundTriggerSession attachAsOriginator(Identity originatorIdentity,
public ISoundTriggerSession attachAsOriginator(@NonNull Identity originatorIdentity,
@NonNull ModuleProperties moduleProperties,
@NonNull IBinder client) {
try (SafeCloseable ignored = PermissionUtil.establishIdentityDirect(
originatorIdentity)) {
return new SoundTriggerSessionStub(client);
return new SoundTriggerSessionStub(client, newSoundTriggerHelper(moduleProperties));
}
}
@Override
public ISoundTriggerSession attachAsMiddleman(Identity originatorIdentity,
Identity middlemanIdentity,
public ISoundTriggerSession attachAsMiddleman(@NonNull Identity originatorIdentity,
@NonNull Identity middlemanIdentity,
@NonNull ModuleProperties moduleProperties,
@NonNull IBinder client) {
try (SafeCloseable ignored = PermissionUtil.establishIdentityIndirect(mContext,
SOUNDTRIGGER_DELEGATE_IDENTITY, middlemanIdentity,
originatorIdentity)) {
return new SoundTriggerSessionStub(client);
return new SoundTriggerSessionStub(client, newSoundTriggerHelper(moduleProperties));
}
}
@Override
public List<ModuleProperties> listModuleProperties(@NonNull Identity originatorIdentity) {
try (SafeCloseable ignored = PermissionUtil.establishIdentityDirect(
originatorIdentity)) {
Identity middlemanIdentity = new Identity();
middlemanIdentity.packageName = ActivityThread.currentOpPackageName();
ArrayList<ModuleProperties> moduleList = new ArrayList<>();
SoundTrigger.listModulesAsMiddleman(moduleList, middlemanIdentity,
originatorIdentity);
return moduleList;
}
}
}
@@ -269,8 +295,8 @@ public class SoundTriggerService extends SystemService {
private final Object mCallbacksLock = new Object();
private final TreeMap<UUID, IRecognitionStatusCallback> mCallbacks = new TreeMap<>();
SoundTriggerSessionStub(@NonNull IBinder client) {
mSoundTriggerHelper = newSoundTriggerHelper();
SoundTriggerSessionStub(@NonNull IBinder client, SoundTriggerHelper soundTriggerHelper) {
mSoundTriggerHelper = soundTriggerHelper;
mClient = client;
mOriginatorIdentity = IdentityContext.getNonNull();
try {
@@ -1615,8 +1641,18 @@ public class SoundTriggerService extends SystemService {
}
@Override
public Session attach(@NonNull IBinder client) {
return new SessionImpl(newSoundTriggerHelper(), client);
public Session attach(@NonNull IBinder client, ModuleProperties underlyingModule) {
return new SessionImpl(newSoundTriggerHelper(underlyingModule), client);
}
@Override
public List<ModuleProperties> listModuleProperties(Identity originatorIdentity) {
Identity middlemanIdentity = new Identity();
middlemanIdentity.packageName = ActivityThread.currentOpPackageName();
ArrayList<ModuleProperties> moduleList = new ArrayList<>();
SoundTrigger.listModulesAsMiddleman(moduleList, middlemanIdentity,
originatorIdentity);
return moduleList;
}
@Override

View File

@@ -405,7 +405,8 @@ public class VoiceInteractionManagerService extends SystemService {
}
try (SafeCloseable ignored = PermissionUtil.establishIdentityDirect(
originatorIdentity)) {
session = new SoundTriggerSession(mSoundTriggerInternal.attach(client));
session = new SoundTriggerSession(mSoundTriggerInternal.attach(client,
getDefaultModuleProperties(originatorIdentity)));
}
}
return new SoundTriggerSessionBinderProxy(session);
@@ -419,11 +420,23 @@ public class VoiceInteractionManagerService extends SystemService {
identity.packageName = ActivityThread.currentOpPackageName();
return Binder.withCleanCallingIdentity(() -> {
try (SafeCloseable ignored = IdentityContext.create(identity)) {
return new SoundTriggerSession(mSoundTriggerInternal.attach(client));
return new SoundTriggerSession(
mSoundTriggerInternal.attach(client,
getDefaultModuleProperties(identity)));
}
});
}
private ModuleProperties getDefaultModuleProperties(Identity originatorIdentity) {
List<ModuleProperties> modulePropList = mSoundTriggerInternal
.listModuleProperties(originatorIdentity);
if (modulePropList.isEmpty()) {
return null;
} else {
return modulePropList.get(0);
}
}
// TODO: VI Make sure the caller is the current user or profile
void startLocalVoiceInteraction(@NonNull final IBinder token,
@Nullable String attributionTag, @Nullable Bundle options) {