Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 70fabce7 authored by Atneya Nair's avatar Atneya Nair
Browse files

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
parent 8db839c7
Loading
Loading
Loading
Loading
+26 −4
Original line number Diff line number Diff line
@@ -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);
+65 −2
Original line number Diff line number Diff line
@@ -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);