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

Commit 65fc1f06 authored by Jean-Michel Trivi's avatar Jean-Michel Trivi Committed by Android (Google) Code Review
Browse files

Merge "AudioService: protect volume APIs for AAOS" into main

parents 3178b562 68390dc2
Loading
Loading
Loading
Loading
+24 −4
Original line number Diff line number Diff line
@@ -20,6 +20,8 @@ import static android.companion.virtual.VirtualDeviceParams.DEVICE_POLICY_DEFAUL
import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_AUDIO;
import static android.content.Context.DEVICE_ID_DEFAULT;

import static com.android.media.audio.flags.Flags.autoPublicVolumeApiHardening;

import android.Manifest;
import android.annotation.CallbackExecutor;
import android.annotation.IntDef;
@@ -1060,9 +1062,18 @@ public class AudioManager {
     * @see #isVolumeFixed()
     */
    public void adjustVolume(int direction, @PublicVolumeFlags int flags) {
        if (autoPublicVolumeApiHardening()) {
            final IAudioService service = getService();
            try {
                service.adjustVolume(direction, flags);
            } catch (RemoteException e) {
                throw e.rethrowFromSystemServer();
            }
        } else {
            MediaSessionLegacyHelper helper = MediaSessionLegacyHelper.getHelper(getContext());
            helper.sendAdjustVolumeBy(USE_DEFAULT_STREAM_TYPE, direction, flags);
        }
    }

    /**
     * Adjusts the volume of the most relevant stream, or the given fallback
@@ -1090,9 +1101,18 @@ public class AudioManager {
     */
    public void adjustSuggestedStreamVolume(int direction, int suggestedStreamType,
            @PublicVolumeFlags int flags) {
        if (autoPublicVolumeApiHardening()) {
            final IAudioService service = getService();
            try {
                service.adjustSuggestedStreamVolume(direction, suggestedStreamType, flags);
            } catch (RemoteException e) {
                throw e.rethrowFromSystemServer();
            }
        } else {
            MediaSessionLegacyHelper helper = MediaSessionLegacyHelper.getHelper(getContext());
            helper.sendAdjustVolumeBy(suggestedStreamType, direction, flags);
        }
    }

    /** @hide */
    @UnsupportedAppUsage
+4 −0
Original line number Diff line number Diff line
@@ -498,6 +498,10 @@ interface IAudioService {
            in String packageName, int uid, int pid, in UserHandle userHandle,
            int targetSdkVersion);

    oneway void adjustVolume(int direction, int flags);

    oneway void adjustSuggestedStreamVolume(int direction, int suggestedStreamType, int flags);

    boolean isMusicActive(in boolean remotely);

    int getDeviceMaskForStream(in int streamType);
+59 −2
Original line number Diff line number Diff line
@@ -16,8 +16,8 @@
package com.android.server.audio;
import static android.Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED;
import static android.app.BroadcastOptions.DELIVERY_GROUP_POLICY_MOST_RECENT;
import static android.Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED;
import static android.media.AudioDeviceInfo.TYPE_BLE_HEADSET;
import static android.media.AudioDeviceInfo.TYPE_BLE_SPEAKER;
import static android.media.AudioDeviceInfo.TYPE_BLUETOOTH_A2DP;
@@ -150,6 +150,7 @@ import android.media.permission.SafeCloseable;
import android.media.projection.IMediaProjection;
import android.media.projection.IMediaProjectionCallback;
import android.media.projection.IMediaProjectionManager;
import android.media.session.MediaSessionManager;
import android.net.Uri;
import android.os.Binder;
import android.os.Build;
@@ -309,6 +310,9 @@ public class AudioService extends IAudioService.Stub
    private final ContentResolver mContentResolver;
    private final AppOpsManager mAppOps;
    /** do not use directly, use getMediaSessionManager() which handles lazy initialization */
    @Nullable private volatile MediaSessionManager mMediaSessionManager;
    // the platform type affects volume and silent mode behavior
    private final int mPlatformType;
@@ -940,6 +944,8 @@ public class AudioService extends IAudioService.Stub
    private final SoundDoseHelper mSoundDoseHelper;
    private final HardeningEnforcer mHardeningEnforcer;
    private final Object mSupportedSystemUsagesLock = new Object();
    @GuardedBy("mSupportedSystemUsagesLock")
    private @AttributeSystemUsage int[] mSupportedSystemUsages =
@@ -1314,6 +1320,8 @@ public class AudioService extends IAudioService.Stub
        mDisplayManager = context.getSystemService(DisplayManager.class);
        mMusicFxHelper = new MusicFxHelper(mContext, mAudioHandler);
        mHardeningEnforcer = new HardeningEnforcer(mContext, isPlatformAutomotive());
    }
    private void initVolumeStreamStates() {
@@ -1383,7 +1391,6 @@ public class AudioService extends IAudioService.Stub
        // check on volume initialization
        checkVolumeRangeInitialization("AudioService()");
    }
    private SubscriptionManager.OnSubscriptionsChangedListener mSubscriptionChangedListener =
@@ -1396,6 +1403,14 @@ public class AudioService extends IAudioService.Stub
                }
            };
    private MediaSessionManager getMediaSessionManager() {
        if (mMediaSessionManager == null) {
            mMediaSessionManager = (MediaSessionManager) mContext
                    .getSystemService(Context.MEDIA_SESSION_SERVICE);
        }
        return mMediaSessionManager;
    }
    /**
     * Initialize intent receives and settings observers for this service.
     * Must be called after createStreamStates() as the handling of some events
@@ -3424,6 +3439,10 @@ public class AudioService extends IAudioService.Stub
     * Part of service interface, check permissions here */
    public void adjustStreamVolumeWithAttribution(int streamType, int direction, int flags,
            String callingPackage, String attributionTag) {
        if (mHardeningEnforcer.blockVolumeMethod(
                HardeningEnforcer.METHOD_AUDIO_MANAGER_ADJUST_STREAM_VOLUME)) {
            return;
        }
        if ((streamType == AudioManager.STREAM_ACCESSIBILITY) && !canChangeAccessibilityVolume()) {
            Log.w(TAG, "Trying to call adjustStreamVolume() for a11y without"
                    + "CHANGE_ACCESSIBILITY_VOLUME / callingPackage=" + callingPackage);
@@ -4200,6 +4219,10 @@ public class AudioService extends IAudioService.Stub
     * Part of service interface, check permissions here */
    public void setStreamVolumeWithAttribution(int streamType, int index, int flags,
            String callingPackage, String attributionTag) {
        if (mHardeningEnforcer.blockVolumeMethod(
                HardeningEnforcer.METHOD_AUDIO_MANAGER_SET_STREAM_VOLUME)) {
            return;
        }
        setStreamVolumeWithAttributionInt(streamType, index, flags, /*device*/ null,
                callingPackage, attributionTag);
    }
@@ -5052,6 +5075,7 @@ public class AudioService extends IAudioService.Stub
    /** @see AudioManager#setMasterMute(boolean, int) */
    public void setMasterMute(boolean mute, int flags, String callingPackage, int userId,
            String attributionTag) {
        super.setMasterMute_enforcePermission();
        setMasterMuteInternal(mute, flags, callingPackage,
@@ -5417,6 +5441,10 @@ public class AudioService extends IAudioService.Stub
    }
    public void setRingerModeExternal(int ringerMode, String caller) {
        if (mHardeningEnforcer.blockVolumeMethod(
                HardeningEnforcer.METHOD_AUDIO_MANAGER_SET_RINGER_MODE)) {
            return;
        }
        if (isAndroidNPlus(caller) && wouldToggleZenMode(ringerMode)
                && !mNm.isNotificationPolicyAccessGrantedForPackage(caller)) {
            throw new SecurityException("Not allowed to change Do Not Disturb state");
@@ -6169,6 +6197,35 @@ public class AudioService extends IAudioService.Stub
                AudioDeviceVolumeManager.ADJUST_MODE_NORMAL);
    }
    /**
      * @see AudioManager#adjustVolume(int, int)
      * This method is redirected from AudioManager to AudioService for API hardening rules
      * enforcement then to MediaSession for implementation.
      */
    @Override
    public void adjustVolume(int direction, int flags) {
        if (mHardeningEnforcer.blockVolumeMethod(
                HardeningEnforcer.METHOD_AUDIO_MANAGER_ADJUST_VOLUME)) {
            return;
        }
        getMediaSessionManager().dispatchAdjustVolume(AudioManager.USE_DEFAULT_STREAM_TYPE,
                    direction, flags);
    }
    /**
     * @see AudioManager#adjustSuggestedStreamVolume(int, int, int)
     * This method is redirected from AudioManager to AudioService for API hardening rules
     * enforcement then to MediaSession for implementation.
     */
    @Override
    public void adjustSuggestedStreamVolume(int direction, int suggestedStreamType, int flags) {
        if (mHardeningEnforcer.blockVolumeMethod(
                HardeningEnforcer.METHOD_AUDIO_MANAGER_ADJUST_SUGGESTED_STREAM_VOLUME)) {
            return;
        }
        getMediaSessionManager().dispatchAdjustVolume(suggestedStreamType, direction, flags);
    }
    /** @see AudioManager#setStreamVolumeForUid(int, int, int, String, int, int, int) */
    @Override
    public void setStreamVolumeForUid(int streamType, int index, int flags,
+109 −0
Original line number Diff line number Diff line
/*
 * Copyright 2023 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.android.server.audio;

import static com.android.media.audio.flags.Flags.autoPublicVolumeApiHardening;

import android.Manifest;
import android.content.Context;
import android.content.pm.PackageManager;
import android.media.AudioManager;
import android.os.Binder;
import android.os.UserHandle;
import android.text.TextUtils;
import android.util.Log;

/**
 * Class to encapsulate all audio API hardening operations
 */
public class HardeningEnforcer {

    private static final String TAG = "AS.HardeningEnforcer";

    final Context mContext;
    final boolean mIsAutomotive;

    /**
     * Matches calls from {@link AudioManager#setStreamVolume(int, int, int)}
     */
    public static final int METHOD_AUDIO_MANAGER_SET_STREAM_VOLUME = 100;
    /**
     * Matches calls from {@link AudioManager#adjustVolume(int, int)}
     */
    public static final int METHOD_AUDIO_MANAGER_ADJUST_VOLUME = 101;
    /**
     * Matches calls from {@link AudioManager#adjustSuggestedStreamVolume(int, int, int)}
     */
    public static final int METHOD_AUDIO_MANAGER_ADJUST_SUGGESTED_STREAM_VOLUME = 102;
    /**
     * Matches calls from {@link AudioManager#adjustStreamVolume(int, int, int)}
     */
    public static final int METHOD_AUDIO_MANAGER_ADJUST_STREAM_VOLUME = 103;
    /**
     * Matches calls from {@link AudioManager#setRingerMode(int)}
     */
    public static final int METHOD_AUDIO_MANAGER_SET_RINGER_MODE = 200;

    public HardeningEnforcer(Context ctxt, boolean isAutomotive) {
        mContext = ctxt;
        mIsAutomotive = isAutomotive;
    }

    /**
     * Checks whether the call in the current thread should be allowed or blocked
     * @param volumeMethod name of the method to check, for logging purposes
     * @return false if the method call is allowed, true if it should be a no-op
     */
    protected boolean blockVolumeMethod(int volumeMethod) {
        // for Auto, volume methods require MODIFY_AUDIO_SETTINGS_PRIVILEGED
        if (mIsAutomotive) {
            if (!autoPublicVolumeApiHardening()) {
                // automotive hardening flag disabled, no blocking on auto
                return false;
            }
            if (mContext.checkCallingOrSelfPermission(
                    Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED)
                    == PackageManager.PERMISSION_GRANTED) {
                return false;
            }
            if (Binder.getCallingUid() < UserHandle.AID_APP_START) {
                return false;
            }
            // TODO metrics?
            // TODO log for audio dumpsys?
            Log.e(TAG, "Preventing volume method " + volumeMethod + " for "
                    + getPackNameForUid(Binder.getCallingUid()));
            return true;
        }
        // not blocking
        return false;
    }

    private String getPackNameForUid(int uid) {
        final long token = Binder.clearCallingIdentity();
        try {
            final String[] names = mContext.getPackageManager().getPackagesForUid(uid);
            if (names == null
                    || names.length == 0
                    || TextUtils.isEmpty(names[0])) {
                return "[" + uid + "]";
            }
            return names[0];
        } finally {
            Binder.restoreCallingIdentity(token);
        }
    }
}