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

Commit 40069e89 authored by Ahaan Ugale's avatar Ahaan Ugale Committed by Android (Google) Code Review
Browse files

Merge "Grant HotwordDetectionService RECORD_AUDIO and CAPTURE_AUDIO_HOTWORD." into sc-dev

parents 4e46adb8 5fc69102
Loading
Loading
Loading
Loading
+37 −0
Original line number Diff line number Diff line
@@ -16,9 +16,12 @@

package android.service.voice;

import android.annotation.Nullable;
import android.os.Bundle;
import android.os.IBinder;

import com.android.internal.annotations.Immutable;


/**
 * @hide
@@ -46,4 +49,38 @@ public abstract class VoiceInteractionManagerInternal {
     * Returns whether the given package is currently in an active session
     */
    public abstract boolean hasActiveSession(String packageName);

    /**
     * Gets the identity of the currently active HotwordDetectionService.
     *
     * @see HotwordDetectionServiceIdentity
     */
    @Nullable
    public abstract HotwordDetectionServiceIdentity getHotwordDetectionServiceIdentity();

    /**
     * Provides the uids of the currently active
     * {@link android.service.voice.HotwordDetectionService} and its owning package. The
     * HotwordDetectionService is an isolated service, so it has a separate uid.
     */
    @Immutable
    public static class HotwordDetectionServiceIdentity {
        private final int mIsolatedUid;
        private final int mOwnerUid;

        public HotwordDetectionServiceIdentity(int isolatedUid, int ownerUid) {
            mIsolatedUid = isolatedUid;
            mOwnerUid = ownerUid;
        }

        /** Gets the uid of the currently active isolated process hosting the service. */
        public int getIsolatedUid() {
            return mIsolatedUid;
        }

        /** Gets the uid of the package that provides the HotwordDetectionService. */
        public int getOwnerUid() {
            return mOwnerUid;
        }
    }
}
 No newline at end of file
+67 −25
Original line number Diff line number Diff line
@@ -17,7 +17,9 @@
package com.android.server.pm.permission;

import static android.Manifest.permission.ADJUST_RUNTIME_PERMISSIONS_POLICY;
import static android.Manifest.permission.CAPTURE_AUDIO_HOTWORD;
import static android.Manifest.permission.READ_EXTERNAL_STORAGE;
import static android.Manifest.permission.RECORD_AUDIO;
import static android.Manifest.permission.WRITE_EXTERNAL_STORAGE;
import static android.app.AppOpsManager.MODE_ALLOWED;
import static android.app.AppOpsManager.MODE_ERRORED;
@@ -148,6 +150,7 @@ import com.android.server.pm.UserManagerInternal;
import com.android.server.pm.UserManagerService;
import com.android.server.pm.parsing.PackageInfoUtils;
import com.android.server.pm.parsing.pkg.AndroidPackage;
import com.android.server.pm.permission.PermissionManagerServiceInternal.HotwordDetectionServiceProvider;
import com.android.server.pm.permission.PermissionManagerServiceInternal.OnRuntimePermissionStateChangedListener;
import com.android.server.policy.PermissionPolicyInternal;
import com.android.server.policy.SoftRestrictedPermissionPolicy;
@@ -308,6 +311,9 @@ public class PermissionManagerService extends IPermissionManager.Stub {
    @NonNull
    private final OnPermissionChangeListeners mOnPermissionChangeListeners;

    @Nullable
    private HotwordDetectionServiceProvider mHotwordDetectionServiceProvider;

    // TODO: Take a look at the methods defined in the callback.
    // The callback was initially created to support the split between permission
    // manager and the package manager. However, it's started to be used for other
@@ -5200,6 +5206,16 @@ public class PermissionManagerService extends IPermissionManager.Stub {
        public int[] getGidsForUid(int uid) {
            return PermissionManagerService.this.getGidsForUid(uid);
        }

        @Override
        public void setHotwordDetectionServiceProvider(HotwordDetectionServiceProvider provider) {
            mHotwordDetectionServiceProvider = provider;
        }

        @Override
        public HotwordDetectionServiceProvider getHotwordDetectionServiceProvider() {
            return mHotwordDetectionServiceProvider;
        }
    }

    /**
@@ -5476,10 +5492,13 @@ public class PermissionManagerService extends IPermissionManager.Stub {

        private final @NonNull Context mContext;
        private final @NonNull AppOpsManager mAppOpsManager;
        private final @NonNull PermissionManagerServiceInternal mPermissionManagerServiceInternal;

        PermissionCheckerService(@NonNull Context context) {
            mContext = context;
            mAppOpsManager = mContext.getSystemService(AppOpsManager.class);
            mPermissionManagerServiceInternal =
                    LocalServices.getService(PermissionManagerServiceInternal.class);
        }

        @Override
@@ -5492,8 +5511,9 @@ public class PermissionManagerService extends IPermissionManager.Stub {
            Objects.requireNonNull(attributionSourceState);
            final AttributionSource attributionSource = new AttributionSource(
                    attributionSourceState);
            final int result = checkPermission(mContext, permission, attributionSource, message,
                    forDataDelivery, startDataDelivery, fromDatasource, attributedOp);
            final int result = checkPermission(mContext, mPermissionManagerServiceInternal,
                    permission, attributionSource, message, forDataDelivery, startDataDelivery,
                    fromDatasource, attributedOp);
            // Finish any started op if some step in the attribution chain failed.
            if (startDataDelivery && result != PermissionChecker.PERMISSION_GRANTED
                    && result != PermissionChecker.PERMISSION_SOFT_DENIED) {
@@ -5582,10 +5602,11 @@ public class PermissionManagerService extends IPermissionManager.Stub {
        }

        @PermissionCheckerManager.PermissionResult
        private static int checkPermission(@NonNull Context context, @NonNull String permission,
                @NonNull AttributionSource attributionSource, @Nullable String message,
                boolean forDataDelivery, boolean startDataDelivery, boolean fromDatasource,
                int attributedOp) {
        private static int checkPermission(@NonNull Context context,
                @NonNull PermissionManagerServiceInternal permissionManagerServiceInt,
                @NonNull String permission, @NonNull AttributionSource attributionSource,
                @Nullable String message, boolean forDataDelivery, boolean startDataDelivery,
                boolean fromDatasource, int attributedOp) {
            PermissionInfo permissionInfo = sPlatformPermissions.get(permission);

            if (permissionInfo == null) {
@@ -5602,22 +5623,25 @@ public class PermissionManagerService extends IPermissionManager.Stub {
            }

            if (permissionInfo.isAppOp()) {
                return checkAppOpPermission(context, permission, attributionSource, message,
                        forDataDelivery, fromDatasource);
                return checkAppOpPermission(context, permissionManagerServiceInt, permission,
                        attributionSource, message, forDataDelivery, fromDatasource);
            }
            if (permissionInfo.isRuntime()) {
                return checkRuntimePermission(context, permission, attributionSource, message,
                        forDataDelivery, startDataDelivery, fromDatasource, attributedOp);
                return checkRuntimePermission(context, permissionManagerServiceInt, permission,
                        attributionSource, message, forDataDelivery, startDataDelivery,
                        fromDatasource, attributedOp);
            }

            if (!fromDatasource && !checkPermission(context, permission, attributionSource.getUid(),
            if (!fromDatasource && !checkPermission(context, permissionManagerServiceInt,
                    permission, attributionSource.getUid(),
                    attributionSource.getRenouncedPermissions())) {
                return PermissionChecker.PERMISSION_HARD_DENIED;
            }

            if (attributionSource.getNext() != null) {
                return checkPermission(context, permission, attributionSource.getNext(), message,
                        forDataDelivery, startDataDelivery, /*fromDatasource*/ false, attributedOp);
                return checkPermission(context, permissionManagerServiceInt, permission,
                        attributionSource.getNext(), message, forDataDelivery, startDataDelivery,
                        /*fromDatasource*/ false, attributedOp);
            }

            return PermissionChecker.PERMISSION_GRANTED;
@@ -5625,6 +5649,7 @@ public class PermissionManagerService extends IPermissionManager.Stub {

        @PermissionCheckerManager.PermissionResult
        private static int checkAppOpPermission(@NonNull Context context,
                @NonNull PermissionManagerServiceInternal permissionManagerServiceInt,
                @NonNull String permission, @NonNull AttributionSource attributionSource,
                @Nullable String message, boolean forDataDelivery, boolean fromDatasource) {
            final int op = AppOpsManager.permissionToOpCode(permission);
@@ -5668,13 +5693,13 @@ public class PermissionManagerService extends IPermissionManager.Stub {
                        return PermissionChecker.PERMISSION_HARD_DENIED;
                    }
                    case AppOpsManager.MODE_DEFAULT: {
                        if (!skipCurrentChecks && !checkPermission(context, permission,
                                attributionSource.getUid(), attributionSource
                                        .getRenouncedPermissions())) {
                        if (!skipCurrentChecks && !checkPermission(context,
                                permissionManagerServiceInt, permission, attributionSource.getUid(),
                                attributionSource.getRenouncedPermissions())) {
                            return PermissionChecker.PERMISSION_HARD_DENIED;
                        }
                        if (next != null && !checkPermission(context, permission,
                                next.getUid(), next.getRenouncedPermissions())) {
                        if (next != null && !checkPermission(context, permissionManagerServiceInt,
                                permission, next.getUid(), next.getRenouncedPermissions())) {
                            return PermissionChecker.PERMISSION_HARD_DENIED;
                        }
                    }
@@ -5689,6 +5714,7 @@ public class PermissionManagerService extends IPermissionManager.Stub {
        }

        private static int checkRuntimePermission(@NonNull Context context,
                @NonNull PermissionManagerServiceInternal permissionManagerServiceInt,
                @NonNull String permission, @NonNull AttributionSource attributionSource,
                @Nullable String message, boolean forDataDelivery, boolean startDataDelivery,
                boolean fromDatasource, int attributedOp) {
@@ -5713,13 +5739,13 @@ public class PermissionManagerService extends IPermissionManager.Stub {
                }

                // If we already checked the permission for this one, skip the work
                if (!skipCurrentChecks && !checkPermission(context, permission,
                        current.getUid(), current.getRenouncedPermissions())) {
                if (!skipCurrentChecks && !checkPermission(context, permissionManagerServiceInt,
                        permission, current.getUid(), current.getRenouncedPermissions())) {
                    return PermissionChecker.PERMISSION_HARD_DENIED;
                }

                if (next != null && !checkPermission(context, permission,
                        next.getUid(), next.getRenouncedPermissions())) {
                if (next != null && !checkPermission(context, permissionManagerServiceInt,
                        permission, next.getUid(), next.getRenouncedPermissions())) {
                    return PermissionChecker.PERMISSION_HARD_DENIED;
                }

@@ -5774,10 +5800,26 @@ public class PermissionManagerService extends IPermissionManager.Stub {
            }
        }

        private static boolean checkPermission(@NonNull Context context, @NonNull String permission,
                int uid, @NonNull Set<String> renouncedPermissions) {
            final boolean permissionGranted = context.checkPermission(permission, /*pid*/ -1,
        private static boolean checkPermission(@NonNull Context context,
                @NonNull PermissionManagerServiceInternal permissionManagerServiceInt,
                @NonNull String permission, int uid, @NonNull Set<String> renouncedPermissions) {
            boolean permissionGranted = context.checkPermission(permission, /*pid*/ -1,
                    uid) == PackageManager.PERMISSION_GRANTED;

            // Override certain permissions checks for the HotwordDetectionService. This service is
            // an isolated service, which ordinarily cannot hold permissions.
            // There's probably a cleaner, more generalizable way to do this. For now, this is
            // the only use case for this, so simply override here.
            if (!permissionGranted
                    && Process.isIsolated(uid) // simple check which fails-fast for the common case
                    && (permission.equals(RECORD_AUDIO)
                    || permission.equals(CAPTURE_AUDIO_HOTWORD))) {
                HotwordDetectionServiceProvider hotwordServiceProvider =
                        permissionManagerServiceInt.getHotwordDetectionServiceProvider();
                permissionGranted = hotwordServiceProvider != null
                        && uid == hotwordServiceProvider.getUid();
            }

            if (permissionGranted && renouncedPermissions.contains(permission)
                    && context.checkPermission(Manifest.permission.RENOUNCE_PERMISSIONS,
                    /*pid*/ -1, uid) == PackageManager.PERMISSION_GRANTED) {
+24 −0
Original line number Diff line number Diff line
@@ -428,4 +428,28 @@ public interface PermissionManagerServiceInternal extends PermissionManagerInter
            }
        }
    }

    /**
     * Sets the provider of the currently active HotwordDetectionService.
     *
     * @see HotwordDetectionServiceProvider
     */
    void setHotwordDetectionServiceProvider(@Nullable HotwordDetectionServiceProvider provider);

    /**
     * Gets the provider of the currently active HotwordDetectionService.
     *
     * @see HotwordDetectionServiceProvider
     */
    @Nullable
    HotwordDetectionServiceProvider getHotwordDetectionServiceProvider();

    /**
     * Provides the uid of the currently active
     * {@link android.service.voice.HotwordDetectionService}, which should be granted RECORD_AUDIO
     * and CAPTURE_AUDIO_HOTWORD permissions.
     */
    interface HotwordDetectionServiceProvider {
        int getUid();
    }
}
+33 −5
Original line number Diff line number Diff line
@@ -33,7 +33,10 @@ import android.content.pm.ResolveInfo;
import android.location.LocationManagerInternal;
import android.net.Uri;
import android.os.IBinder;
import android.os.Process;
import android.os.UserHandle;
import android.service.voice.VoiceInteractionManagerInternal;
import android.service.voice.VoiceInteractionManagerInternal.HotwordDetectionServiceIdentity;
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.ArraySet;
@@ -78,6 +81,9 @@ public final class AppOpsPolicy implements AppOpsManagerInternal.CheckOpsDelegat
    @NonNull
    private final RoleManager mRoleManager;

    @NonNull
    private final VoiceInteractionManagerInternal mVoiceInteractionManagerInternal;

    /**
     * The locking policy around the location tags is a bit special. Since we want to
     * avoid grabbing the lock on every op note we are taking the approach where the
@@ -101,6 +107,8 @@ public final class AppOpsPolicy implements AppOpsManagerInternal.CheckOpsDelegat
    public AppOpsPolicy(@NonNull Context context) {
        mContext = context;
        mRoleManager = mContext.getSystemService(RoleManager.class);
        mVoiceInteractionManagerInternal = LocalServices.getService(
                VoiceInteractionManagerInternal.class);

        final LocationManagerInternal locationManagerInternal = LocalServices.getService(
                LocationManagerInternal.class);
@@ -150,7 +158,7 @@ public final class AppOpsPolicy implements AppOpsManagerInternal.CheckOpsDelegat
    public int checkOperation(int code, int uid, String packageName,
            @Nullable String attributionTag, boolean raw,
            QuintFunction<Integer, Integer, String, String, Boolean, Integer> superImpl) {
        return superImpl.apply(code, uid, packageName, attributionTag, raw);
        return superImpl.apply(code, resolveUid(code, uid), packageName, attributionTag, raw);
    }

    @Override
@@ -164,8 +172,8 @@ public final class AppOpsPolicy implements AppOpsManagerInternal.CheckOpsDelegat
            @Nullable String attributionTag, boolean shouldCollectAsyncNotedOp, @Nullable
            String message, boolean shouldCollectMessage, @NonNull HeptFunction<Integer, Integer,
                    String, String, Boolean, String, Boolean, SyncNotedAppOp> superImpl) {
        return superImpl.apply(resolveDatasourceOp(code, uid, packageName, attributionTag), uid,
                packageName, attributionTag, shouldCollectAsyncNotedOp,
        return superImpl.apply(resolveDatasourceOp(code, uid, packageName, attributionTag),
                resolveUid(code, uid), packageName, attributionTag, shouldCollectAsyncNotedOp,
                message, shouldCollectMessage);
    }

@@ -190,8 +198,9 @@ public final class AppOpsPolicy implements AppOpsManagerInternal.CheckOpsDelegat
                    String, Boolean, Boolean, String, Boolean, Integer, Integer,
            SyncNotedAppOp> superImpl) {
        return superImpl.apply(token, resolveDatasourceOp(code, uid, packageName, attributionTag),
                uid, packageName, attributionTag, startIfModeDefault, shouldCollectAsyncNotedOp,
                message, shouldCollectMessage, attributionFlags, attributionChainId);
                resolveUid(code, uid), packageName, attributionTag, startIfModeDefault,
                shouldCollectAsyncNotedOp, message, shouldCollectMessage, attributionFlags,
                attributionChainId);
    }

    @Override
@@ -404,4 +413,23 @@ public final class AppOpsPolicy implements AppOpsManagerInternal.CheckOpsDelegat
        }
        return code;
    }

    private int resolveUid(int code, int uid) {
        // The HotwordDetectionService is an isolated service, which ordinarily cannot hold
        // permissions. So we allow it to assume the owning package identity for certain
        // operations.
        // Note: The package name coming from the audio server is already the one for the owning
        // package, so we don't need to modify it.
        if (Process.isIsolated(uid) // simple check which fails-fast for the common case
                && (code == AppOpsManager.OP_RECORD_AUDIO
                || code == AppOpsManager.OP_RECORD_AUDIO_HOTWORD)) {
            final HotwordDetectionServiceIdentity hotwordDetectionServiceIdentity =
                    mVoiceInteractionManagerInternal.getHotwordDetectionServiceIdentity();
            if (hotwordDetectionServiceIdentity != null
                    && uid == hotwordDetectionServiceIdentity.getIsolatedUid()) {
                uid = hotwordDetectionServiceIdentity.getOwnerUid();
            }
        }
        return uid;
    }
}
+22 −3
Original line number Diff line number Diff line
@@ -34,6 +34,7 @@ import android.media.AudioFormat;
import android.media.AudioManager;
import android.media.AudioRecord;
import android.media.MediaRecorder;
import android.os.Binder;
import android.os.Bundle;
import android.os.IBinder;
import android.os.IRemoteCallback;
@@ -48,6 +49,7 @@ import android.service.voice.HotwordRejectedResult;
import android.service.voice.IDspHotwordDetectionCallback;
import android.service.voice.IHotwordDetectionService;
import android.service.voice.IMicrophoneHotwordDetectionVoiceInteractionCallback;
import android.service.voice.VoiceInteractionManagerInternal.HotwordDetectionServiceIdentity;
import android.util.Pair;
import android.util.Slog;
import android.view.contentcapture.IContentCaptureManager;
@@ -56,6 +58,8 @@ import com.android.internal.annotations.GuardedBy;
import com.android.internal.app.IHotwordRecognitionStatusCallback;
import com.android.internal.infra.AndroidFuture;
import com.android.internal.infra.ServiceConnector;
import com.android.server.LocalServices;
import com.android.server.pm.permission.PermissionManagerServiceInternal;

import java.io.Closeable;
import java.io.IOException;
@@ -94,20 +98,24 @@ final class HotwordDetectionConnection {
    private final AtomicBoolean mUpdateStateFinish = new AtomicBoolean(false);

    final Object mLock;
    final int mVoiceInteractionServiceUid;
    final ComponentName mDetectionComponentName;
    final int mUser;
    final Context mContext;
    final @NonNull ServiceConnector<IHotwordDetectionService> mRemoteHotwordDetectionService;
    boolean mBound;
    volatile HotwordDetectionServiceIdentity mIdentity;

    @GuardedBy("mLock")
    private ParcelFileDescriptor mCurrentAudioSink;

    HotwordDetectionConnection(Object lock, Context context, ComponentName serviceName,
            int userId, boolean bindInstantServiceAllowed, @Nullable PersistableBundle options,
            @Nullable SharedMemory sharedMemory, IHotwordRecognitionStatusCallback callback) {
    HotwordDetectionConnection(Object lock, Context context, int voiceInteractionServiceUid,
            ComponentName serviceName, int userId, boolean bindInstantServiceAllowed,
            @Nullable PersistableBundle options, @Nullable SharedMemory sharedMemory,
            IHotwordRecognitionStatusCallback callback) {
        mLock = lock;
        mContext = context;
        mVoiceInteractionServiceUid = voiceInteractionServiceUid;
        mDetectionComponentName = serviceName;
        mUser = userId;
        final Intent intent = new Intent(HotwordDetectionService.SERVICE_INTERFACE);
@@ -164,7 +172,15 @@ final class HotwordDetectionConnection {
                public void sendResult(Bundle bundle) throws RemoteException {
                    if (DEBUG) {
                        Slog.d(TAG, "updateState finish");
                    }
                        Slog.d(TAG, "updating hotword UID " + Binder.getCallingUid());
                    }
                    // TODO: Do this earlier than this callback and have the provider point to the
                    // current state stored in VoiceInteractionManagerServiceImpl.
                    final int uid = Binder.getCallingUid();
                    LocalServices.getService(PermissionManagerServiceInternal.class)
                            .setHotwordDetectionServiceProvider(() -> uid);
                    mIdentity =
                            new HotwordDetectionServiceIdentity(uid, mVoiceInteractionServiceUid);
                    future.complete(null);
                    try {
                        if (mUpdateStateFinish.getAndSet(true)) {
@@ -235,6 +251,9 @@ final class HotwordDetectionConnection {
        if (mBound) {
            mRemoteHotwordDetectionService.unbind();
            mBound = false;
            LocalServices.getService(PermissionManagerServiceInternal.class)
                    .setHotwordDetectionServiceProvider(null);
            mIdentity = null;
        }
    }

Loading