summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java30
-rw-r--r--services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java67
2 files changed, 91 insertions, 6 deletions
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java
index c1a539ffac2c..f1e1a5adc2c3 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java
@@ -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);
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java
index 3553a5ace311..9fb5509141a7 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java
@@ -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;
@@ -111,6 +112,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;
@@ -236,6 +238,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);
@@ -258,6 +262,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(
@@ -349,6 +355,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,
@@ -463,6 +507,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) {
@@ -479,6 +524,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);
}
@@ -930,6 +981,9 @@ public class SoundTriggerService extends SystemService {
}
private void detach() {
+ if (mAppOpsListener != null) {
+ mAppOpsManager.stopWatchingMode(mAppOpsListener);
+ }
mDeviceStateHandler.unregisterListener(mListener);
mSoundTriggerHelper.detach();
detachSessionLogger(mEventLogger);
@@ -945,9 +999,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"
@@ -1635,6 +1688,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);
@@ -1655,6 +1709,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);
}
@@ -1722,6 +1782,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);