diff options
| -rw-r--r-- | services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java | 30 | ||||
| -rw-r--r-- | services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java | 67 |
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); |