Add AppOps listening to STService

Listen for RECORD AppOp revocations for SoundTrigger clients,
pause recognition if they enter MODE_IGNORED, and resume recognition
after MODE_GRANTED.

Bug: 272147641
Fixes: 267960430
Fixes: 274960506
Test: AlwaysOnHotwordDetectorTest#
    testAppOpsLostReacquired_recognitionPausedResumed
Test: Manual verification of pause/resume flow when mic blocked
Test: Manual verification of pause/resume when booted w/ mic blocked
Change-Id: Ie907a46c9503ec163d466914f84e34c494ffd8d7
This commit is contained in:
Atneya Nair
2023-05-02 16:42:09 -07:00
parent 8db839c7f6
commit 70fabce73b
2 changed files with 91 additions and 6 deletions

View File

@@ -119,6 +119,9 @@ public class SoundTriggerHelper implements SoundTrigger.StatusListener {
@GuardedBy("mLock")
private SoundTriggerDeviceState mDeviceState = SoundTriggerDeviceState.DISABLE;
@GuardedBy("mLock")
private boolean mIsAppOpPermitted = true;
SoundTriggerHelper(Context context, EventLogger eventLogger,
@NonNull Function<SoundTrigger.StatusListener, SoundTriggerModule> moduleProvider,
int moduleId,
@@ -323,7 +326,7 @@ public class SoundTriggerHelper implements SoundTrigger.StatusListener {
modelData.setRunInBatterySaverMode(runInBatterySaverMode);
modelData.setSoundModel(soundModel);
if (isRecognitionAllowedByDeviceState(modelData)) {
if (isRecognitionAllowed(modelData)) {
int startRecoResult = updateRecognitionLocked(modelData,
false /* Don't notify for synchronous calls */);
if (startRecoResult == SoundTrigger.STATUS_OK) {
@@ -613,6 +616,16 @@ public class SoundTriggerHelper implements SoundTrigger.StatusListener {
}
}
public void onAppOpStateChanged(boolean isPermitted) {
synchronized (mLock) {
if (mIsAppOpPermitted == isPermitted) {
return;
}
mIsAppOpPermitted = isPermitted;
updateAllRecognitionsLocked();
}
}
public int getGenericModelState(UUID modelId) {
synchronized (mLock) {
MetricsLogger.count(mContext, "sth_get_generic_model_state", 1);
@@ -782,6 +795,7 @@ public class SoundTriggerHelper implements SoundTrigger.StatusListener {
return event instanceof KeyphraseRecognitionEvent;
}
@GuardedBy("mLock")
private void onGenericRecognitionLocked(GenericRecognitionEvent event) {
MetricsLogger.count(mContext, "sth_generic_recognition_event", 1);
if (event.status != SoundTrigger.RECOGNITION_STATUS_SUCCESS
@@ -866,6 +880,7 @@ public class SoundTriggerHelper implements SoundTrigger.StatusListener {
}
}
@GuardedBy("mLock")
private void onResourcesAvailableLocked() {
mEventLogger.enqueue(new SessionEvent(Type.RESOURCES_AVAILABLE, null));
updateAllRecognitionsLocked();
@@ -911,6 +926,7 @@ public class SoundTriggerHelper implements SoundTrigger.StatusListener {
return keyphraseExtras[0].id;
}
@GuardedBy("mLock")
private void onKeyphraseRecognitionLocked(KeyphraseRecognitionEvent event) {
Slog.i(TAG, "Recognition success");
MetricsLogger.count(mContext, "sth_keyphrase_recognition_event", 1);
@@ -956,6 +972,7 @@ public class SoundTriggerHelper implements SoundTrigger.StatusListener {
}
}
@GuardedBy("mLock")
private void updateAllRecognitionsLocked() {
// updateRecognitionLocked can possibly update the list of models
ArrayList<ModelData> modelDatas = new ArrayList<ModelData>(mModelDataMap.values());
@@ -964,8 +981,9 @@ public class SoundTriggerHelper implements SoundTrigger.StatusListener {
}
}
@GuardedBy("mLock")
private int updateRecognitionLocked(ModelData model, boolean notifyClientOnError) {
boolean shouldStartModel = model.isRequested() && isRecognitionAllowedByDeviceState(model);
boolean shouldStartModel = model.isRequested() && isRecognitionAllowed(model);
if (shouldStartModel == model.isModelStarted()) {
// No-op.
return STATUS_OK;
@@ -1184,7 +1202,10 @@ public class SoundTriggerHelper implements SoundTrigger.StatusListener {
* @return True if recognition is allowed to run at this time. False if not.
*/
@GuardedBy("mLock")
private boolean isRecognitionAllowedByDeviceState(ModelData modelData) {
private boolean isRecognitionAllowed(ModelData modelData) {
if (!mIsAppOpPermitted) {
return false;
}
return switch (mDeviceState) {
case DISABLE -> false;
case CRITICAL -> modelData.shouldRunInBatterySaverMode();
@@ -1195,6 +1216,7 @@ public class SoundTriggerHelper implements SoundTrigger.StatusListener {
// A single routine that implements the start recognition logic for both generic and keyphrase
// models.
@GuardedBy("mLock")
private int startRecognitionLocked(ModelData modelData, boolean notifyClientOnError) {
IRecognitionStatusCallback callback = modelData.getCallback();
RecognitionConfig config = modelData.getRecognitionConfig();
@@ -1205,7 +1227,7 @@ public class SoundTriggerHelper implements SoundTrigger.StatusListener {
return STATUS_ERROR;
}
if (!isRecognitionAllowedByDeviceState(modelData)) {
if (!isRecognitionAllowed(modelData)) {
// Nothing to do here.
Slog.w(TAG, "startRecognition requested but not allowed.");
MetricsLogger.count(mContext, "sth_start_recognition_not_allowed", 1);

View File

@@ -42,6 +42,7 @@ import android.Manifest;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityThread;
import android.app.AppOpsManager;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
@@ -110,6 +111,7 @@ import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.function.Consumer;
import java.util.List;
import java.util.Set;
import java.util.Deque;
@@ -235,6 +237,8 @@ public class SoundTriggerService extends SystemService {
private final DeviceStateHandler mDeviceStateHandler;
private final Executor mDeviceStateHandlerExecutor = Executors.newSingleThreadExecutor();
private PhoneCallStateHandler mPhoneCallStateHandler;
private AppOpsManager mAppOpsManager;
private PackageManager mPackageManager;
public SoundTriggerService(Context context) {
super(context);
@@ -257,6 +261,8 @@ public class SoundTriggerService extends SystemService {
Slog.d(TAG, "onBootPhase: " + phase + " : " + isSafeMode());
if (PHASE_THIRD_PARTY_APPS_CAN_START == phase) {
mDbHelper = new SoundTriggerDbHelper(mContext);
mAppOpsManager = mContext.getSystemService(AppOpsManager.class);
mPackageManager = mContext.getPackageManager();
final PowerManager powerManager = mContext.getSystemService(PowerManager.class);
// Hook up power state listener
mContext.registerReceiver(
@@ -348,6 +354,44 @@ public class SoundTriggerService extends SystemService {
}
}
class MyAppOpsListener implements AppOpsManager.OnOpChangedListener {
private final Identity mOriginatorIdentity;
private final Consumer<Boolean> mOnOpModeChanged;
MyAppOpsListener(Identity originatorIdentity, Consumer<Boolean> onOpModeChanged) {
mOriginatorIdentity = Objects.requireNonNull(originatorIdentity);
mOnOpModeChanged = Objects.requireNonNull(onOpModeChanged);
// Validate package name
try {
int uid = mPackageManager.getPackageUid(mOriginatorIdentity.packageName,
PackageManager.PackageInfoFlags.of(0));
if (uid != mOriginatorIdentity.uid) {
throw new SecurityException("Package name: " +
mOriginatorIdentity.packageName + "with uid: " + uid
+ "attempted to spoof as: " + mOriginatorIdentity.uid);
}
} catch (PackageManager.NameNotFoundException e) {
throw new SecurityException("Package name not found: "
+ mOriginatorIdentity.packageName);
}
}
@Override
public void onOpChanged(String op, String packageName) {
if (!Objects.equals(op, AppOpsManager.OPSTR_RECORD_AUDIO)) {
return;
}
final int mode = mAppOpsManager.checkOpNoThrow(
AppOpsManager.OPSTR_RECORD_AUDIO, mOriginatorIdentity.uid,
mOriginatorIdentity.packageName);
mOnOpModeChanged.accept(mode == AppOpsManager.MODE_ALLOWED);
}
void forceOpChangeRefresh() {
onOpChanged(AppOpsManager.OPSTR_RECORD_AUDIO, mOriginatorIdentity.packageName);
}
}
class SoundTriggerServiceStub extends ISoundTriggerService.Stub {
@Override
public ISoundTriggerSession attachAsOriginator(@NonNull Identity originatorIdentity,
@@ -461,6 +505,7 @@ public class SoundTriggerService extends SystemService {
private final Object mCallbacksLock = new Object();
private final TreeMap<UUID, IRecognitionStatusCallback> mCallbacks = new TreeMap<>();
private final EventLogger mEventLogger;
private final MyAppOpsListener mAppOpsListener;
SoundTriggerSessionStub(@NonNull IBinder client,
SoundTriggerHelper soundTriggerHelper, EventLogger eventLogger) {
@@ -477,6 +522,12 @@ public class SoundTriggerService extends SystemService {
}
mListener = (SoundTriggerDeviceState state)
-> mSoundTriggerHelper.onDeviceStateChanged(state);
mAppOpsListener = new MyAppOpsListener(mOriginatorIdentity,
mSoundTriggerHelper::onAppOpStateChanged);
mAppOpsListener.forceOpChangeRefresh();
mAppOpsManager.startWatchingMode(AppOpsManager.OPSTR_RECORD_AUDIO,
mOriginatorIdentity.packageName, AppOpsManager.WATCH_FOREGROUND_CHANGES,
mAppOpsListener);
mDeviceStateHandler.registerListener(mListener);
}
@@ -928,6 +979,9 @@ public class SoundTriggerService extends SystemService {
}
private void detach() {
if (mAppOpsListener != null) {
mAppOpsManager.stopWatchingMode(mAppOpsListener);
}
mDeviceStateHandler.unregisterListener(mListener);
mSoundTriggerHelper.detach();
detachSessionLogger(mEventLogger);
@@ -943,9 +997,8 @@ public class SoundTriggerService extends SystemService {
}
private void enforceDetectionPermissions(ComponentName detectionService) {
PackageManager packageManager = mContext.getPackageManager();
String packageName = detectionService.getPackageName();
if (packageManager.checkPermission(
if (mPackageManager.checkPermission(
Manifest.permission.CAPTURE_AUDIO_HOTWORD, packageName)
!= PackageManager.PERMISSION_GRANTED) {
throw new SecurityException(detectionService.getPackageName() + " does not have"
@@ -1633,6 +1686,7 @@ public class SoundTriggerService extends SystemService {
private final EventLogger mEventLogger;
private final Identity mOriginatorIdentity;
private final @NonNull DeviceStateListener mListener;
private final MyAppOpsListener mAppOpsListener;
private final SparseArray<UUID> mModelUuid = new SparseArray<>(1);
@@ -1653,6 +1707,12 @@ public class SoundTriggerService extends SystemService {
}
mListener = (SoundTriggerDeviceState state)
-> mSoundTriggerHelper.onDeviceStateChanged(state);
mAppOpsListener = new MyAppOpsListener(mOriginatorIdentity,
mSoundTriggerHelper::onAppOpStateChanged);
mAppOpsListener.forceOpChangeRefresh();
mAppOpsManager.startWatchingMode(AppOpsManager.OPSTR_RECORD_AUDIO,
mOriginatorIdentity.packageName, AppOpsManager.WATCH_FOREGROUND_CHANGES,
mAppOpsListener);
mDeviceStateHandler.registerListener(mListener);
}
@@ -1720,6 +1780,9 @@ public class SoundTriggerService extends SystemService {
}
private void detachInternal() {
if (mAppOpsListener != null) {
mAppOpsManager.stopWatchingMode(mAppOpsListener);
}
mEventLogger.enqueue(new SessionEvent(Type.DETACH, null));
detachSessionLogger(mEventLogger);
mDeviceStateHandler.unregisterListener(mListener);