diff options
4 files changed, 197 insertions, 6 deletions
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java index 5b880797b7fd..9ad5c3e79543 100644 --- a/media/java/android/media/AudioManager.java +++ b/media/java/android/media/AudioManager.java @@ -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,8 +1062,17 @@ public class AudioManager { * @see #isVolumeFixed() */ public void adjustVolume(int direction, @PublicVolumeFlags int flags) { - MediaSessionLegacyHelper helper = MediaSessionLegacyHelper.getHelper(getContext()); - helper.sendAdjustVolumeBy(USE_DEFAULT_STREAM_TYPE, direction, 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); + } } /** @@ -1090,8 +1101,17 @@ public class AudioManager { */ public void adjustSuggestedStreamVolume(int direction, int suggestedStreamType, @PublicVolumeFlags int flags) { - MediaSessionLegacyHelper helper = MediaSessionLegacyHelper.getHelper(getContext()); - helper.sendAdjustVolumeBy(suggestedStreamType, direction, 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 */ diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl index 0e7718b060bc..8584dbc62ef9 100644 --- a/media/java/android/media/IAudioService.aidl +++ b/media/java/android/media/IAudioService.aidl @@ -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); diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java index 3243385c3b18..42745ddd2d74 100644 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -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; @@ -36,6 +36,7 @@ import static android.os.Process.INVALID_UID; import static android.provider.Settings.Secure.VOLUME_HUSH_MUTE; import static android.provider.Settings.Secure.VOLUME_HUSH_OFF; import static android.provider.Settings.Secure.VOLUME_HUSH_VIBRATE; + import static com.android.server.audio.SoundDoseHelper.ACTION_CHECK_MUSIC_ACTIVE; import static com.android.server.utils.EventLogger.Event.ALOGE; import static com.android.server.utils.EventLogger.Event.ALOGI; @@ -149,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; @@ -307,6 +309,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; @@ -938,6 +943,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 = @@ -1312,6 +1319,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() { @@ -1381,7 +1390,6 @@ public class AudioService extends IAudioService.Stub // check on volume initialization checkVolumeRangeInitialization("AudioService()"); - } private SubscriptionManager.OnSubscriptionsChangedListener mSubscriptionChangedListener = @@ -1394,6 +1402,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 @@ -3405,6 +3421,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); @@ -4181,6 +4201,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); } @@ -5033,6 +5057,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, @@ -5398,6 +5423,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"); @@ -6150,6 +6179,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, diff --git a/services/core/java/com/android/server/audio/HardeningEnforcer.java b/services/core/java/com/android/server/audio/HardeningEnforcer.java new file mode 100644 index 000000000000..c7556dacb783 --- /dev/null +++ b/services/core/java/com/android/server/audio/HardeningEnforcer.java @@ -0,0 +1,109 @@ +/* + * 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); + } + } +} |