All Downloads are FREE. Search and download functionalities are using the official Maven repository.

src.com.android.server.audio.AudioService Maven / Gradle / Ivy

/*
 * Copyright (C) 2006 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 android.Manifest.permission.REMOTE_AUDIO_PLAYBACK;
import static android.media.AudioManager.RINGER_MODE_NORMAL;
import static android.media.AudioManager.RINGER_MODE_SILENT;
import static android.media.AudioManager.RINGER_MODE_VIBRATE;
import static android.media.AudioManager.STREAM_SYSTEM;
import static android.os.Process.FIRST_APPLICATION_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 android.Manifest;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityManager;
import android.app.ActivityManagerInternal;
import android.app.AppGlobals;
import android.app.AppOpsManager;
import android.app.IUidObserver;
import android.app.NotificationManager;
import android.app.role.OnRoleHoldersChangedListener;
import android.app.role.RoleManager;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothHeadset;
import android.bluetooth.BluetoothProfile;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.pm.UserInfo;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.content.res.XmlResourceParser;
import android.database.ContentObserver;
import android.hardware.hdmi.HdmiAudioSystemClient;
import android.hardware.hdmi.HdmiControlManager;
import android.hardware.hdmi.HdmiPlaybackClient;
import android.hardware.hdmi.HdmiTvClient;
import android.hardware.usb.UsbManager;
import android.media.AudioAttributes;
import android.media.AudioFocusInfo;
import android.media.AudioFocusRequest;
import android.media.AudioFormat;
import android.media.AudioManager;
import android.media.AudioManagerInternal;
import android.media.AudioPlaybackConfiguration;
import android.media.AudioRecordingConfiguration;
import android.media.AudioRoutesInfo;
import android.media.AudioSystem;
import android.media.IAudioFocusDispatcher;
import android.media.IAudioRoutesObserver;
import android.media.IAudioServerStateDispatcher;
import android.media.IAudioService;
import android.media.IPlaybackConfigDispatcher;
import android.media.IRecordingConfigDispatcher;
import android.media.IRingtonePlayer;
import android.media.IVolumeController;
import android.media.MediaExtractor;
import android.media.MediaFormat;
import android.media.MediaPlayer;
import android.media.MediaPlayer.OnCompletionListener;
import android.media.MediaPlayer.OnErrorListener;
import android.media.PlayerBase;
import android.media.SoundPool;
import android.media.VolumePolicy;
import android.media.audiofx.AudioEffect;
import android.media.audiopolicy.AudioMix;
import android.media.audiopolicy.AudioPolicy;
import android.media.audiopolicy.AudioPolicyConfig;
import android.media.audiopolicy.AudioProductStrategy;
import android.media.audiopolicy.AudioVolumeGroup;
import android.media.audiopolicy.IAudioPolicyCallback;
import android.media.projection.IMediaProjection;
import android.media.projection.IMediaProjectionCallback;
import android.media.projection.IMediaProjectionManager;
import android.net.Uri;
import android.os.Binder;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
import android.os.PowerManager;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.SystemClock;
import android.os.SystemProperties;
import android.os.UserHandle;
import android.os.UserManager;
import android.os.UserManagerInternal;
import android.os.UserManagerInternal.UserRestrictionsListener;
import android.os.VibrationEffect;
import android.os.Vibrator;
import android.provider.Settings;
import android.provider.Settings.System;
import android.service.notification.ZenModeConfig;
import android.telecom.TelecomManager;
import android.text.TextUtils;
import android.util.AndroidRuntimeException;
import android.util.IntArray;
import android.util.Log;
import android.util.MathUtils;
import android.util.Slog;
import android.util.SparseIntArray;
import android.view.KeyEvent;
import android.view.accessibility.AccessibilityManager;
import android.widget.Toast;

import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.DumpUtils;
import com.android.internal.util.Preconditions;
import com.android.internal.util.XmlUtils;
import com.android.server.EventLogTags;
import com.android.server.LocalServices;
import com.android.server.SystemService;
import com.android.server.audio.AudioServiceEvents.PhoneStateEvent;
import com.android.server.audio.AudioServiceEvents.VolumeEvent;
import com.android.server.pm.UserManagerService;
import com.android.server.wm.ActivityTaskManagerInternal;

import org.xmlpull.v1.XmlPullParserException;

import java.io.File;
import java.io.FileDescriptor;
import java.io.IOException;
import java.io.PrintWriter;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.Objects;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicBoolean;

/**
 * The implementation of the volume manager service.
 * 

* This implementation focuses on delivering a responsive UI. Most methods are * asynchronous to external calls. For example, the task of setting a volume * will update our internal state, but in a separate thread will set the system * volume and later persist to the database. Similarly, setting the ringer mode * will update the state and broadcast a change and in a separate thread later * persist the ringer mode. * * @hide */ public class AudioService extends IAudioService.Stub implements AccessibilityManager.TouchExplorationStateChangeListener, AccessibilityManager.AccessibilityServicesStateChangeListener { private static final String TAG = "AS.AudioService"; /** Debug audio mode */ protected static final boolean DEBUG_MODE = false; /** Debug audio policy feature */ protected static final boolean DEBUG_AP = false; /** Debug volumes */ protected static final boolean DEBUG_VOL = false; /** debug calls to devices APIs */ protected static final boolean DEBUG_DEVICES = false; /** How long to delay before persisting a change in volume/ringer mode. */ private static final int PERSIST_DELAY = 500; /** How long to delay after a volume down event before unmuting a stream */ private static final int UNMUTE_STREAM_DELAY = 350; /** * Only used in the result from {@link #checkForRingerModeChange(int, int, int)} */ private static final int FLAG_ADJUST_VOLUME = 1; private final Context mContext; private final ContentResolver mContentResolver; private final AppOpsManager mAppOps; // the platform type affects volume and silent mode behavior private final int mPlatformType; // indicates whether the system maps all streams to a single stream. private final boolean mIsSingleVolume; private boolean isPlatformVoice() { return mPlatformType == AudioSystem.PLATFORM_VOICE; } /*package*/ boolean isPlatformTelevision() { return mPlatformType == AudioSystem.PLATFORM_TELEVISION; } /*package*/ boolean isPlatformAutomotive() { return mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE); } /** The controller for the volume UI. */ private final VolumeController mVolumeController = new VolumeController(); // sendMsg() flags /** If the msg is already queued, replace it with this one. */ private static final int SENDMSG_REPLACE = 0; /** If the msg is already queued, ignore this one and leave the old. */ private static final int SENDMSG_NOOP = 1; /** If the msg is already queued, queue this one and leave the old. */ private static final int SENDMSG_QUEUE = 2; // AudioHandler messages private static final int MSG_SET_DEVICE_VOLUME = 0; private static final int MSG_PERSIST_VOLUME = 1; private static final int MSG_PERSIST_RINGER_MODE = 3; private static final int MSG_AUDIO_SERVER_DIED = 4; private static final int MSG_PLAY_SOUND_EFFECT = 5; private static final int MSG_LOAD_SOUND_EFFECTS = 7; private static final int MSG_SET_FORCE_USE = 8; private static final int MSG_BT_HEADSET_CNCT_FAILED = 9; private static final int MSG_SET_ALL_VOLUMES = 10; private static final int MSG_CHECK_MUSIC_ACTIVE = 11; private static final int MSG_CONFIGURE_SAFE_MEDIA_VOLUME = 12; private static final int MSG_CONFIGURE_SAFE_MEDIA_VOLUME_FORCED = 13; private static final int MSG_PERSIST_SAFE_VOLUME_STATE = 14; private static final int MSG_UNLOAD_SOUND_EFFECTS = 15; private static final int MSG_SYSTEM_READY = 16; private static final int MSG_PERSIST_MUSIC_ACTIVE_MS = 17; private static final int MSG_UNMUTE_STREAM = 18; private static final int MSG_DYN_POLICY_MIX_STATE_UPDATE = 19; private static final int MSG_INDICATE_SYSTEM_READY = 20; private static final int MSG_ACCESSORY_PLUG_MEDIA_UNMUTE = 21; private static final int MSG_NOTIFY_VOL_EVENT = 22; private static final int MSG_DISPATCH_AUDIO_SERVER_STATE = 23; private static final int MSG_ENABLE_SURROUND_FORMATS = 24; private static final int MSG_UPDATE_RINGER_MODE = 25; private static final int MSG_SET_DEVICE_STREAM_VOLUME = 26; private static final int MSG_OBSERVE_DEVICES_FOR_ALL_STREAMS = 27; private static final int MSG_HDMI_VOLUME_CHECK = 28; private static final int MSG_PLAYBACK_CONFIG_CHANGE = 29; // start of messages handled under wakelock // these messages can only be queued, i.e. sent with queueMsgUnderWakeLock(), // and not with sendMsg(..., ..., SENDMSG_QUEUE, ...) private static final int MSG_DISABLE_AUDIO_FOR_UID = 100; // end of messages handled under wakelock // retry delay in case of failure to indicate system ready to AudioFlinger private static final int INDICATE_SYSTEM_READY_RETRY_DELAY_MS = 1000; /** @see AudioSystemThread */ private AudioSystemThread mAudioSystemThread; /** @see AudioHandler */ private AudioHandler mAudioHandler; /** @see VolumeStreamState */ private VolumeStreamState[] mStreamStates; /*package*/ int getVssVolumeForDevice(int stream, int device) { return mStreamStates[stream].getIndex(device); } private SettingsObserver mSettingsObserver; private int mMode = AudioSystem.MODE_NORMAL; // protects mRingerMode private final Object mSettingsLock = new Object(); private SoundPool mSoundPool; private final Object mSoundEffectsLock = new Object(); private static final int NUM_SOUNDPOOL_CHANNELS = 4; /* Sound effect file names */ private static final String SOUND_EFFECTS_PATH = "/media/audio/ui/"; private static final List SOUND_EFFECT_FILES = new ArrayList(); /* Sound effect file name mapping sound effect id (AudioManager.FX_xxx) to * file index in SOUND_EFFECT_FILES[] (first column) and indicating if effect * uses soundpool (second column) */ private final int[][] SOUND_EFFECT_FILES_MAP = new int[AudioManager.NUM_SOUND_EFFECTS][2]; /** Maximum volume index values for audio streams */ protected static int[] MAX_STREAM_VOLUME = new int[] { 5, // STREAM_VOICE_CALL 7, // STREAM_SYSTEM 7, // STREAM_RING 15, // STREAM_MUSIC 7, // STREAM_ALARM 7, // STREAM_NOTIFICATION 15, // STREAM_BLUETOOTH_SCO 7, // STREAM_SYSTEM_ENFORCED 15, // STREAM_DTMF 15, // STREAM_TTS 15 // STREAM_ACCESSIBILITY }; /** Minimum volume index values for audio streams */ protected static int[] MIN_STREAM_VOLUME = new int[] { 1, // STREAM_VOICE_CALL 0, // STREAM_SYSTEM 0, // STREAM_RING 0, // STREAM_MUSIC 1, // STREAM_ALARM 0, // STREAM_NOTIFICATION 0, // STREAM_BLUETOOTH_SCO 0, // STREAM_SYSTEM_ENFORCED 0, // STREAM_DTMF 0, // STREAM_TTS 1 // STREAM_ACCESSIBILITY }; /* mStreamVolumeAlias[] indicates for each stream if it uses the volume settings * of another stream: This avoids multiplying the volume settings for hidden * stream types that follow other stream behavior for volume settings * NOTE: do not create loops in aliases! * Some streams alias to different streams according to device category (phone or tablet) or * use case (in call vs off call...). See updateStreamVolumeAlias() for more details. * mStreamVolumeAlias contains STREAM_VOLUME_ALIAS_VOICE aliases for a voice capable device * (phone), STREAM_VOLUME_ALIAS_TELEVISION for a television or set-top box and * STREAM_VOLUME_ALIAS_DEFAULT for other devices (e.g. tablets).*/ private final int[] STREAM_VOLUME_ALIAS_VOICE = new int[] { AudioSystem.STREAM_VOICE_CALL, // STREAM_VOICE_CALL AudioSystem.STREAM_RING, // STREAM_SYSTEM AudioSystem.STREAM_RING, // STREAM_RING AudioSystem.STREAM_MUSIC, // STREAM_MUSIC AudioSystem.STREAM_ALARM, // STREAM_ALARM AudioSystem.STREAM_RING, // STREAM_NOTIFICATION AudioSystem.STREAM_BLUETOOTH_SCO, // STREAM_BLUETOOTH_SCO AudioSystem.STREAM_RING, // STREAM_SYSTEM_ENFORCED AudioSystem.STREAM_RING, // STREAM_DTMF AudioSystem.STREAM_MUSIC, // STREAM_TTS AudioSystem.STREAM_MUSIC // STREAM_ACCESSIBILITY }; private final int[] STREAM_VOLUME_ALIAS_TELEVISION = new int[] { AudioSystem.STREAM_MUSIC, // STREAM_VOICE_CALL AudioSystem.STREAM_MUSIC, // STREAM_SYSTEM AudioSystem.STREAM_MUSIC, // STREAM_RING AudioSystem.STREAM_MUSIC, // STREAM_MUSIC AudioSystem.STREAM_MUSIC, // STREAM_ALARM AudioSystem.STREAM_MUSIC, // STREAM_NOTIFICATION AudioSystem.STREAM_MUSIC, // STREAM_BLUETOOTH_SCO AudioSystem.STREAM_MUSIC, // STREAM_SYSTEM_ENFORCED AudioSystem.STREAM_MUSIC, // STREAM_DTMF AudioSystem.STREAM_MUSIC, // STREAM_TTS AudioSystem.STREAM_MUSIC // STREAM_ACCESSIBILITY }; private final int[] STREAM_VOLUME_ALIAS_DEFAULT = new int[] { AudioSystem.STREAM_VOICE_CALL, // STREAM_VOICE_CALL AudioSystem.STREAM_RING, // STREAM_SYSTEM AudioSystem.STREAM_RING, // STREAM_RING AudioSystem.STREAM_MUSIC, // STREAM_MUSIC AudioSystem.STREAM_ALARM, // STREAM_ALARM AudioSystem.STREAM_RING, // STREAM_NOTIFICATION AudioSystem.STREAM_BLUETOOTH_SCO, // STREAM_BLUETOOTH_SCO AudioSystem.STREAM_RING, // STREAM_SYSTEM_ENFORCED AudioSystem.STREAM_RING, // STREAM_DTMF AudioSystem.STREAM_MUSIC, // STREAM_TTS AudioSystem.STREAM_MUSIC // STREAM_ACCESSIBILITY }; protected static int[] mStreamVolumeAlias; /** * Map AudioSystem.STREAM_* constants to app ops. This should be used * after mapping through mStreamVolumeAlias. */ private static final int[] STREAM_VOLUME_OPS = new int[] { AppOpsManager.OP_AUDIO_VOICE_VOLUME, // STREAM_VOICE_CALL AppOpsManager.OP_AUDIO_MEDIA_VOLUME, // STREAM_SYSTEM AppOpsManager.OP_AUDIO_RING_VOLUME, // STREAM_RING AppOpsManager.OP_AUDIO_MEDIA_VOLUME, // STREAM_MUSIC AppOpsManager.OP_AUDIO_ALARM_VOLUME, // STREAM_ALARM AppOpsManager.OP_AUDIO_NOTIFICATION_VOLUME, // STREAM_NOTIFICATION AppOpsManager.OP_AUDIO_BLUETOOTH_VOLUME, // STREAM_BLUETOOTH_SCO AppOpsManager.OP_AUDIO_MEDIA_VOLUME, // STREAM_SYSTEM_ENFORCED AppOpsManager.OP_AUDIO_MEDIA_VOLUME, // STREAM_DTMF AppOpsManager.OP_AUDIO_MEDIA_VOLUME, // STREAM_TTS AppOpsManager.OP_AUDIO_ACCESSIBILITY_VOLUME, // STREAM_ACCESSIBILITY }; private final boolean mUseFixedVolume; /** * Default stream type used for volume control in the absence of playback * e.g. user on homescreen, no app playing anything, presses hardware volume buttons, this * stream type is controlled. */ protected static final int DEFAULT_VOL_STREAM_NO_PLAYBACK = AudioSystem.STREAM_MUSIC; private final AudioSystem.ErrorCallback mAudioSystemCallback = new AudioSystem.ErrorCallback() { public void onError(int error) { switch (error) { case AudioSystem.AUDIO_STATUS_SERVER_DIED: mRecordMonitor.onAudioServerDied(); sendMsg(mAudioHandler, MSG_AUDIO_SERVER_DIED, SENDMSG_NOOP, 0, 0, null, 0); sendMsg(mAudioHandler, MSG_DISPATCH_AUDIO_SERVER_STATE, SENDMSG_QUEUE, 0, 0, null, 0); break; default: break; } } }; /** * Current ringer mode from one of {@link AudioManager#RINGER_MODE_NORMAL}, * {@link AudioManager#RINGER_MODE_SILENT}, or * {@link AudioManager#RINGER_MODE_VIBRATE}. */ @GuardedBy("mSettingsLock") private int mRingerMode; // internal ringer mode, affects muting of underlying streams @GuardedBy("mSettingsLock") private int mRingerModeExternal = -1; // reported ringer mode to outside clients (AudioManager) /** @see System#MODE_RINGER_STREAMS_AFFECTED */ private int mRingerModeAffectedStreams = 0; private int mZenModeAffectedStreams = 0; // Streams currently muted by ringer mode and dnd private int mRingerAndZenModeMutedStreams; /** Streams that can be muted. Do not resolve to aliases when checking. * @see System#MUTE_STREAMS_AFFECTED */ private int mMuteAffectedStreams; /** * NOTE: setVibrateSetting(), getVibrateSetting(), shouldVibrate() are deprecated. * mVibrateSetting is just maintained during deprecation period but vibration policy is * now only controlled by mHasVibrator and mRingerMode */ private int mVibrateSetting; // Is there a vibrator private final boolean mHasVibrator; // Used to play vibrations private Vibrator mVibrator; private static final AudioAttributes VIBRATION_ATTRIBUTES = new AudioAttributes.Builder() .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION) .setUsage(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION) .build(); // Broadcast receiver for device connections intent broadcasts private final BroadcastReceiver mReceiver = new AudioServiceBroadcastReceiver(); private IMediaProjectionManager mProjectionService; // to validate projection token /** Interface for UserManagerService. */ private final UserManagerInternal mUserManagerInternal; private final ActivityManagerInternal mActivityManagerInternal; private final UserRestrictionsListener mUserRestrictionsListener = new AudioServiceUserRestrictionsListener(); // List of binder death handlers for setMode() client processes. // The last process to have called setMode() is at the top of the list. // package-private so it can be accessed in AudioDeviceBroker.getSetModeDeathHandlers //TODO candidate to be moved to separate class that handles synchronization @GuardedBy("mDeviceBroker.mSetModeLock") /*package*/ final ArrayList mSetModeDeathHandlers = new ArrayList(); // true if boot sequence has been completed private boolean mSystemReady; // true if Intent.ACTION_USER_SWITCHED has ever been received private boolean mUserSwitchedReceived; // listener for SoundPool sample load completion indication private SoundPoolCallback mSoundPoolCallBack; // thread for SoundPool listener private SoundPoolListenerThread mSoundPoolListenerThread; // message looper for SoundPool listener private Looper mSoundPoolLooper = null; // volume applied to sound played with playSoundEffect() private static int sSoundEffectVolumeDb; // previous volume adjustment direction received by checkForRingerModeChange() private int mPrevVolDirection = AudioManager.ADJUST_SAME; // mVolumeControlStream is set by VolumePanel to temporarily force the stream type which volume // is controlled by Vol keys. private int mVolumeControlStream = -1; // interpretation of whether the volume stream has been selected by the user by clicking on a // volume slider to change which volume is controlled by the volume keys. Is false // when mVolumeControlStream is -1. private boolean mUserSelectedVolumeControlStream = false; private final Object mForceControlStreamLock = new Object(); // VolumePanel is currently the only client of forceVolumeControlStream() and runs in system // server process so in theory it is not necessary to monitor the client death. // However it is good to be ready for future evolutions. private ForceControlStreamClient mForceControlStreamClient = null; // Used to play ringtones outside system_server private volatile IRingtonePlayer mRingtonePlayer; // Devices for which the volume is fixed (volume is either max or muted) int mFixedVolumeDevices = AudioSystem.DEVICE_OUT_HDMI | AudioSystem.DEVICE_OUT_DGTL_DOCK_HEADSET | AudioSystem.DEVICE_OUT_ANLG_DOCK_HEADSET | AudioSystem.DEVICE_OUT_HDMI_ARC | AudioSystem.DEVICE_OUT_SPDIF | AudioSystem.DEVICE_OUT_AUX_LINE; // Devices for which the volume is always max, no volume panel int mFullVolumeDevices = 0; // Devices for the which use the "absolute volume" concept (framework sends audio signal // full scale, and volume control separately) and can be used for multiple use cases reflected // by the audio mode (e.g. media playback in MODE_NORMAL, and phone calls in MODE_IN_CALL). int mAbsVolumeMultiModeCaseDevices = AudioSystem.DEVICE_OUT_HEARING_AID; private final boolean mMonitorRotation; private boolean mDockAudioMediaEnabled = true; private int mDockState = Intent.EXTRA_DOCK_STATE_UNDOCKED; // Used when safe volume warning message display is requested by setStreamVolume(). In this // case, the new requested volume, stream type and device are stored in mPendingVolumeCommand // and used later when/if disableSafeMediaVolume() is called. private StreamVolumeCommand mPendingVolumeCommand; private PowerManager.WakeLock mAudioEventWakeLock; private final MediaFocusControl mMediaFocusControl; // Pre-scale for Bluetooth Absolute Volume private float[] mPrescaleAbsoluteVolume = new float[] { 0.5f, // Pre-scale for index 1 0.7f, // Pre-scale for index 2 0.85f, // Pre-scale for index 3 }; private NotificationManager mNm; private AudioManagerInternal.RingerModeDelegate mRingerModeDelegate; private VolumePolicy mVolumePolicy = VolumePolicy.DEFAULT; private long mLoweredFromNormalToVibrateTime; // Array of Uids of valid accessibility services to check if caller is one of them private int[] mAccessibilityServiceUids; private final Object mAccessibilityServiceUidsLock = new Object(); private int mEncodedSurroundMode; private String mEnabledSurroundFormats; private boolean mSurroundModeChanged; @GuardedBy("mSettingsLock") private int mAssistantUid; // Defines the format for the connection "address" for ALSA devices public static String makeAlsaAddressString(int card, int device) { return "card=" + card + ";device=" + device + ";"; } public static final class Lifecycle extends SystemService { private AudioService mService; public Lifecycle(Context context) { super(context); mService = new AudioService(context); } @Override public void onStart() { publishBinderService(Context.AUDIO_SERVICE, mService); } @Override public void onBootPhase(int phase) { if (phase == SystemService.PHASE_ACTIVITY_MANAGER_READY) { mService.systemReady(); } } } final private IUidObserver mUidObserver = new IUidObserver.Stub() { @Override public void onUidStateChanged(int uid, int procState, long procStateSeq) { } @Override public void onUidGone(int uid, boolean disabled) { // Once the uid is no longer running, no need to keep trying to disable its audio. disableAudioForUid(false, uid); } @Override public void onUidActive(int uid) throws RemoteException { } @Override public void onUidIdle(int uid, boolean disabled) { } @Override public void onUidCachedChanged(int uid, boolean cached) { disableAudioForUid(cached, uid); } private void disableAudioForUid(boolean disable, int uid) { queueMsgUnderWakeLock(mAudioHandler, MSG_DISABLE_AUDIO_FOR_UID, disable ? 1 : 0 /* arg1 */, uid /* arg2 */, null /* obj */, 0 /* delay */); } }; /////////////////////////////////////////////////////////////////////////// // Construction /////////////////////////////////////////////////////////////////////////// /** @hide */ public AudioService(Context context) { mContext = context; mContentResolver = context.getContentResolver(); mAppOps = (AppOpsManager)context.getSystemService(Context.APP_OPS_SERVICE); mPlatformType = AudioSystem.getPlatformType(context); mIsSingleVolume = AudioSystem.isSingleVolume(context); mUserManagerInternal = LocalServices.getService(UserManagerInternal.class); mActivityManagerInternal = LocalServices.getService(ActivityManagerInternal.class); PowerManager pm = (PowerManager)context.getSystemService(Context.POWER_SERVICE); mAudioEventWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "handleAudioEvent"); mVibrator = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE); mHasVibrator = mVibrator == null ? false : mVibrator.hasVibrator(); // Initialize volume // Priority 1 - Android Property // Priority 2 - Audio Policy Service // Priority 3 - Default Value if (AudioProductStrategy.getAudioProductStrategies().size() > 0) { int numStreamTypes = AudioSystem.getNumStreamTypes(); for (int streamType = numStreamTypes - 1; streamType >= 0; streamType--) { AudioAttributes attr = AudioProductStrategy.getAudioAttributesForStrategyWithLegacyStreamType( streamType); int maxVolume = AudioSystem.getMaxVolumeIndexForAttributes(attr); if (maxVolume != -1) { MAX_STREAM_VOLUME[streamType] = maxVolume; } int minVolume = AudioSystem.getMinVolumeIndexForAttributes(attr); if (minVolume != -1) { MIN_STREAM_VOLUME[streamType] = minVolume; } } } int maxCallVolume = SystemProperties.getInt("ro.config.vc_call_vol_steps", -1); if (maxCallVolume != -1) { MAX_STREAM_VOLUME[AudioSystem.STREAM_VOICE_CALL] = maxCallVolume; } int defaultCallVolume = SystemProperties.getInt("ro.config.vc_call_vol_default", -1); if (defaultCallVolume != -1 && defaultCallVolume <= MAX_STREAM_VOLUME[AudioSystem.STREAM_VOICE_CALL] && defaultCallVolume >= MIN_STREAM_VOLUME[AudioSystem.STREAM_VOICE_CALL]) { AudioSystem.DEFAULT_STREAM_VOLUME[AudioSystem.STREAM_VOICE_CALL] = defaultCallVolume; } else { AudioSystem.DEFAULT_STREAM_VOLUME[AudioSystem.STREAM_VOICE_CALL] = (maxCallVolume * 3) / 4; } int maxMusicVolume = SystemProperties.getInt("ro.config.media_vol_steps", -1); if (maxMusicVolume != -1) { MAX_STREAM_VOLUME[AudioSystem.STREAM_MUSIC] = maxMusicVolume; } int defaultMusicVolume = SystemProperties.getInt("ro.config.media_vol_default", -1); if (defaultMusicVolume != -1 && defaultMusicVolume <= MAX_STREAM_VOLUME[AudioSystem.STREAM_MUSIC] && defaultMusicVolume >= MIN_STREAM_VOLUME[AudioSystem.STREAM_MUSIC]) { AudioSystem.DEFAULT_STREAM_VOLUME[AudioSystem.STREAM_MUSIC] = defaultMusicVolume; } else { if (isPlatformTelevision()) { AudioSystem.DEFAULT_STREAM_VOLUME[AudioSystem.STREAM_MUSIC] = MAX_STREAM_VOLUME[AudioSystem.STREAM_MUSIC] / 4; } else { AudioSystem.DEFAULT_STREAM_VOLUME[AudioSystem.STREAM_MUSIC] = MAX_STREAM_VOLUME[AudioSystem.STREAM_MUSIC] / 3; } } int maxAlarmVolume = SystemProperties.getInt("ro.config.alarm_vol_steps", -1); if (maxAlarmVolume != -1) { MAX_STREAM_VOLUME[AudioSystem.STREAM_ALARM] = maxAlarmVolume; } int defaultAlarmVolume = SystemProperties.getInt("ro.config.alarm_vol_default", -1); if (defaultAlarmVolume != -1 && defaultAlarmVolume <= MAX_STREAM_VOLUME[AudioSystem.STREAM_ALARM]) { AudioSystem.DEFAULT_STREAM_VOLUME[AudioSystem.STREAM_ALARM] = defaultAlarmVolume; } else { // Default is 6 out of 7 (default maximum), so scale accordingly. AudioSystem.DEFAULT_STREAM_VOLUME[AudioSystem.STREAM_ALARM] = 6 * MAX_STREAM_VOLUME[AudioSystem.STREAM_ALARM] / 7; } int maxSystemVolume = SystemProperties.getInt("ro.config.system_vol_steps", -1); if (maxSystemVolume != -1) { MAX_STREAM_VOLUME[AudioSystem.STREAM_SYSTEM] = maxSystemVolume; } int defaultSystemVolume = SystemProperties.getInt("ro.config.system_vol_default", -1); if (defaultSystemVolume != -1 && defaultSystemVolume <= MAX_STREAM_VOLUME[AudioSystem.STREAM_SYSTEM]) { AudioSystem.DEFAULT_STREAM_VOLUME[AudioSystem.STREAM_SYSTEM] = defaultSystemVolume; } else { // Default is to use maximum. AudioSystem.DEFAULT_STREAM_VOLUME[AudioSystem.STREAM_SYSTEM] = MAX_STREAM_VOLUME[AudioSystem.STREAM_SYSTEM]; } sSoundEffectVolumeDb = context.getResources().getInteger( com.android.internal.R.integer.config_soundEffectVolumeDb); createAudioSystemThread(); AudioSystem.setErrorCallback(mAudioSystemCallback); boolean cameraSoundForced = readCameraSoundForced(); mCameraSoundForced = new Boolean(cameraSoundForced); sendMsg(mAudioHandler, MSG_SET_FORCE_USE, SENDMSG_QUEUE, AudioSystem.FOR_SYSTEM, cameraSoundForced ? AudioSystem.FORCE_SYSTEM_ENFORCED : AudioSystem.FORCE_NONE, new String("AudioService ctor"), 0); mSafeMediaVolumeState = Settings.Global.getInt(mContentResolver, Settings.Global.AUDIO_SAFE_VOLUME_STATE, SAFE_MEDIA_VOLUME_NOT_CONFIGURED); // The default safe volume index read here will be replaced by the actual value when // the mcc is read by onConfigureSafeVolume() mSafeMediaVolumeIndex = mContext.getResources().getInteger( com.android.internal.R.integer.config_safe_media_volume_index) * 10; mUseFixedVolume = mContext.getResources().getBoolean( com.android.internal.R.bool.config_useFixedVolume); mDeviceBroker = new AudioDeviceBroker(mContext, this); // must be called before readPersistedSettings() which needs a valid mStreamVolumeAlias[] // array initialized by updateStreamVolumeAlias() updateStreamVolumeAlias(false /*updateVolumes*/, TAG); readPersistedSettings(); readUserRestrictions(); mSettingsObserver = new SettingsObserver(); createStreamStates(); // mSafeUsbMediaVolumeIndex must be initialized after createStreamStates() because it // relies on audio policy having correct ranges for volume indexes. mSafeUsbMediaVolumeIndex = getSafeUsbMediaVolumeIndex(); mPlaybackMonitor = new PlaybackActivityMonitor(context, MAX_STREAM_VOLUME[AudioSystem.STREAM_ALARM]); mMediaFocusControl = new MediaFocusControl(mContext, mPlaybackMonitor); mRecordMonitor = new RecordingActivityMonitor(mContext); readAndSetLowRamDevice(); // Call setRingerModeInt() to apply correct mute // state on streams affected by ringer mode. mRingerAndZenModeMutedStreams = 0; setRingerModeInt(getRingerModeInternal(), false); // Register for device connection intent broadcasts. IntentFilter intentFilter = new IntentFilter(BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED); intentFilter.addAction(BluetoothHeadset.ACTION_ACTIVE_DEVICE_CHANGED); intentFilter.addAction(Intent.ACTION_DOCK_EVENT); intentFilter.addAction(Intent.ACTION_SCREEN_ON); intentFilter.addAction(Intent.ACTION_SCREEN_OFF); intentFilter.addAction(Intent.ACTION_USER_SWITCHED); intentFilter.addAction(Intent.ACTION_USER_BACKGROUND); intentFilter.addAction(Intent.ACTION_USER_FOREGROUND); intentFilter.addAction(UsbManager.ACTION_USB_DEVICE_ATTACHED); intentFilter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED); intentFilter.addAction(Intent.ACTION_PACKAGES_SUSPENDED); intentFilter.addAction(Intent.ACTION_CONFIGURATION_CHANGED); mMonitorRotation = SystemProperties.getBoolean("ro.audio.monitorRotation", false); if (mMonitorRotation) { RotationHelper.init(mContext, mAudioHandler); } intentFilter.addAction(AudioEffect.ACTION_OPEN_AUDIO_EFFECT_CONTROL_SESSION); intentFilter.addAction(AudioEffect.ACTION_CLOSE_AUDIO_EFFECT_CONTROL_SESSION); context.registerReceiverAsUser(mReceiver, UserHandle.ALL, intentFilter, null, null); LocalServices.addService(AudioManagerInternal.class, new AudioServiceInternal()); mUserManagerInternal.addUserRestrictionsListener(mUserRestrictionsListener); mRecordMonitor.initMonitor(); final float[] preScale = new float[3]; preScale[0] = mContext.getResources().getFraction( com.android.internal.R.fraction.config_prescaleAbsoluteVolume_index1, 1, 1); preScale[1] = mContext.getResources().getFraction( com.android.internal.R.fraction.config_prescaleAbsoluteVolume_index2, 1, 1); preScale[2] = mContext.getResources().getFraction( com.android.internal.R.fraction.config_prescaleAbsoluteVolume_index3, 1, 1); for (int i = 0; i < preScale.length; i++) { if (0.0f <= preScale[i] && preScale[i] <= 1.0f) { mPrescaleAbsoluteVolume[i] = preScale[i]; } } } public void systemReady() { sendMsg(mAudioHandler, MSG_SYSTEM_READY, SENDMSG_QUEUE, 0, 0, null, 0); if (false) { // This is turned off for now, because it is racy and thus causes apps to break. // Currently banning a uid means that if an app tries to start playing an audio // stream, that will be preventing, and unbanning it will not allow that stream // to resume. However these changes in uid state are racy with what the app is doing, // so that after taking a process out of the cached state we can't guarantee that // we will unban the uid before the app actually tries to start playing audio. // (To do that, the activity manager would need to wait until it knows for sure // that the ban has been removed, before telling the app to do whatever it is // supposed to do that caused it to go out of the cached state.) try { ActivityManager.getService().registerUidObserver(mUidObserver, ActivityManager.UID_OBSERVER_CACHED | ActivityManager.UID_OBSERVER_GONE, ActivityManager.PROCESS_STATE_UNKNOWN, null); } catch (RemoteException e) { // ignored; both services live in system_server } } } public void onSystemReady() { mSystemReady = true; scheduleLoadSoundEffects(); mDeviceBroker.onSystemReady(); if (mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_HDMI_CEC)) { synchronized (mHdmiClientLock) { mHdmiManager = mContext.getSystemService(HdmiControlManager.class); mHdmiTvClient = mHdmiManager.getTvClient(); if (mHdmiTvClient != null) { mFixedVolumeDevices &= ~AudioSystem.DEVICE_ALL_HDMI_SYSTEM_AUDIO_AND_SPEAKER; } mHdmiPlaybackClient = mHdmiManager.getPlaybackClient(); if (mHdmiPlaybackClient != null) { // not a television: HDMI output will be always at max mFixedVolumeDevices &= ~AudioSystem.DEVICE_OUT_HDMI; mFullVolumeDevices |= AudioSystem.DEVICE_OUT_HDMI; } mHdmiCecSink = false; mHdmiAudioSystemClient = mHdmiManager.getAudioSystemClient(); } } mNm = (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE); sendMsg(mAudioHandler, MSG_CONFIGURE_SAFE_MEDIA_VOLUME_FORCED, SENDMSG_REPLACE, 0, 0, TAG, SystemProperties.getBoolean("audio.safemedia.bypass", false) ? 0 : SAFE_VOLUME_CONFIGURE_TIMEOUT_MS); initA11yMonitoring(); mRoleObserver = new RoleObserver(); mRoleObserver.register(); onIndicateSystemReady(); } RoleObserver mRoleObserver; class RoleObserver implements OnRoleHoldersChangedListener { private RoleManager mRm; private final Executor mExecutor; RoleObserver() { mExecutor = mContext.getMainExecutor(); } public void register() { mRm = (RoleManager) mContext.getSystemService(Context.ROLE_SERVICE); if (mRm != null) { mRm.addOnRoleHoldersChangedListenerAsUser(mExecutor, this, UserHandle.ALL); updateAssistantUId(true); } } @Override public void onRoleHoldersChanged(@NonNull String roleName, @NonNull UserHandle user) { if (RoleManager.ROLE_ASSISTANT.equals(roleName)) { updateAssistantUId(false); } } public String getAssistantRoleHolder() { String assitantPackage = ""; if (mRm != null) { List assistants = mRm.getRoleHolders(RoleManager.ROLE_ASSISTANT); assitantPackage = assistants.size() == 0 ? "" : assistants.get(0); } return assitantPackage; } } void onIndicateSystemReady() { if (AudioSystem.systemReady() == AudioSystem.SUCCESS) { return; } sendMsg(mAudioHandler, MSG_INDICATE_SYSTEM_READY, SENDMSG_REPLACE, 0, 0, null, INDICATE_SYSTEM_READY_RETRY_DELAY_MS); } public void onAudioServerDied() { if (!mSystemReady || (AudioSystem.checkAudioFlinger() != AudioSystem.AUDIO_STATUS_OK)) { Log.e(TAG, "Audioserver died."); sendMsg(mAudioHandler, MSG_AUDIO_SERVER_DIED, SENDMSG_NOOP, 0, 0, null, 500); return; } Log.e(TAG, "Audioserver started."); // indicate to audio HAL that we start the reconfiguration phase after a media // server crash // Note that we only execute this when the media server // process restarts after a crash, not the first time it is started. AudioSystem.setParameters("restarting=true"); readAndSetLowRamDevice(); // Restore device connection states, BT state mDeviceBroker.onAudioServerDied(); // Restore call state if (AudioSystem.setPhoneState(mMode) == AudioSystem.AUDIO_STATUS_OK) { mModeLogger.log(new AudioEventLogger.StringEvent( "onAudioServerDied causes setPhoneState(" + AudioSystem.modeToString(mMode) + ")")); } final int forSys; synchronized (mSettingsLock) { forSys = mCameraSoundForced ? AudioSystem.FORCE_SYSTEM_ENFORCED : AudioSystem.FORCE_NONE; } mDeviceBroker.setForceUse_Async(AudioSystem.FOR_SYSTEM, forSys, "onAudioServerDied"); // Restore stream volumes int numStreamTypes = AudioSystem.getNumStreamTypes(); for (int streamType = numStreamTypes - 1; streamType >= 0; streamType--) { VolumeStreamState streamState = mStreamStates[streamType]; AudioSystem.initStreamVolume( streamType, streamState.mIndexMin / 10, streamState.mIndexMax / 10); streamState.applyAllVolumes(); } // Restore mono mode updateMasterMono(mContentResolver); // Restore audio balance updateMasterBalance(mContentResolver); // Restore ringer mode setRingerModeInt(getRingerModeInternal(), false); // Reset device rotation (if monitored for this device) if (mMonitorRotation) { RotationHelper.updateOrientation(); } synchronized (mSettingsLock) { final int forDock = mDockAudioMediaEnabled ? AudioSystem.FORCE_ANALOG_DOCK : AudioSystem.FORCE_NONE; mDeviceBroker.setForceUse_Async(AudioSystem.FOR_DOCK, forDock, "onAudioServerDied"); sendEncodedSurroundMode(mContentResolver, "onAudioServerDied"); sendEnabledSurroundFormats(mContentResolver, true); updateAssistantUId(true); updateRttEanbled(mContentResolver); } synchronized (mAccessibilityServiceUidsLock) { AudioSystem.setA11yServicesUids(mAccessibilityServiceUids); } synchronized (mHdmiClientLock) { if (mHdmiManager != null && mHdmiTvClient != null) { setHdmiSystemAudioSupported(mHdmiSystemAudioSupported); } } synchronized (mAudioPolicies) { for (AudioPolicyProxy policy : mAudioPolicies.values()) { policy.connectMixes(); } } onIndicateSystemReady(); // indicate the end of reconfiguration phase to audio HAL AudioSystem.setParameters("restarting=false"); sendMsg(mAudioHandler, MSG_DISPATCH_AUDIO_SERVER_STATE, SENDMSG_QUEUE, 1, 0, null, 0); } private void onDispatchAudioServerStateChange(boolean state) { synchronized (mAudioServerStateListeners) { for (AsdProxy asdp : mAudioServerStateListeners.values()) { try { asdp.callback().dispatchAudioServerStateChange(state); } catch (RemoteException e) { Log.w(TAG, "Could not call dispatchAudioServerStateChange()", e); } } } } private void createAudioSystemThread() { mAudioSystemThread = new AudioSystemThread(); mAudioSystemThread.start(); waitForAudioHandlerCreation(); } /** Waits for the volume handler to be created by the other thread. */ private void waitForAudioHandlerCreation() { synchronized(this) { while (mAudioHandler == null) { try { // Wait for mAudioHandler to be set by the other thread wait(); } catch (InterruptedException e) { Log.e(TAG, "Interrupted while waiting on volume handler."); } } } } /** * @return the {@link android.media.audiopolicy.AudioProductStrategy} discovered from the * platform configuration file. */ @NonNull public List getAudioProductStrategies() { return AudioProductStrategy.getAudioProductStrategies(); } /** * @return the List of {@link android.media.audiopolicy.AudioVolumeGroup} discovered from the * platform configuration file. */ @NonNull public List getAudioVolumeGroups() { return AudioVolumeGroup.getAudioVolumeGroups(); } private void checkAllAliasStreamVolumes() { synchronized (mSettingsLock) { synchronized (VolumeStreamState.class) { int numStreamTypes = AudioSystem.getNumStreamTypes(); for (int streamType = 0; streamType < numStreamTypes; streamType++) { mStreamStates[streamType] .setAllIndexes(mStreamStates[mStreamVolumeAlias[streamType]], TAG); // apply stream volume if (!mStreamStates[streamType].mIsMuted) { mStreamStates[streamType].applyAllVolumes(); } } } } } /** * Called from AudioDeviceBroker when DEVICE_OUT_HDMI is connected or disconnected. */ /*package*/ void postCheckVolumeCecOnHdmiConnection( @AudioService.ConnectionState int state, String caller) { sendMsg(mAudioHandler, MSG_HDMI_VOLUME_CHECK, SENDMSG_REPLACE, state /*arg1*/, 0 /*arg2 ignored*/, caller /*obj*/, 0 /*delay*/); } private void onCheckVolumeCecOnHdmiConnection( @AudioService.ConnectionState int state, String caller) { if (state == AudioService.CONNECTION_STATE_CONNECTED) { // DEVICE_OUT_HDMI is now connected if ((AudioSystem.DEVICE_OUT_HDMI & mSafeMediaVolumeDevices) != 0) { sendMsg(mAudioHandler, MSG_CHECK_MUSIC_ACTIVE, SENDMSG_REPLACE, 0, 0, caller, MUSIC_ACTIVE_POLL_PERIOD_MS); } if (isPlatformTelevision()) { checkAddAllFixedVolumeDevices(AudioSystem.DEVICE_OUT_HDMI, caller); synchronized (mHdmiClientLock) { if (mHdmiManager != null && mHdmiPlaybackClient != null) { mHdmiCecSink = false; mHdmiPlaybackClient.queryDisplayStatus(mHdmiDisplayStatusCallback); } } } sendEnabledSurroundFormats(mContentResolver, true); } else { // DEVICE_OUT_HDMI disconnected if (isPlatformTelevision()) { synchronized (mHdmiClientLock) { if (mHdmiManager != null) { mHdmiCecSink = false; } } } } } private void checkAddAllFixedVolumeDevices(int device, String caller) { final int numStreamTypes = AudioSystem.getNumStreamTypes(); for (int streamType = 0; streamType < numStreamTypes; streamType++) { if (!mStreamStates[streamType].hasIndexForDevice(device)) { // set the default value, if device is affected by a full/fix/abs volume rule, it // will taken into account in checkFixedVolumeDevices() mStreamStates[streamType].setIndex( mStreamStates[mStreamVolumeAlias[streamType]] .getIndex(AudioSystem.DEVICE_OUT_DEFAULT), device, caller); } mStreamStates[streamType].checkFixedVolumeDevices(); } } private void checkAllFixedVolumeDevices() { int numStreamTypes = AudioSystem.getNumStreamTypes(); for (int streamType = 0; streamType < numStreamTypes; streamType++) { mStreamStates[streamType].checkFixedVolumeDevices(); } } private void checkAllFixedVolumeDevices(int streamType) { mStreamStates[streamType].checkFixedVolumeDevices(); } private void checkMuteAffectedStreams() { // any stream with a min level > 0 is not muteable by definition // STREAM_VOICE_CALL and STREAM_BLUETOOTH_SCO can be muted by applications // that has the the MODIFY_PHONE_STATE permission. for (int i = 0; i < mStreamStates.length; i++) { final VolumeStreamState vss = mStreamStates[i]; if (vss.mIndexMin > 0 && (vss.mStreamType != AudioSystem.STREAM_VOICE_CALL && vss.mStreamType != AudioSystem.STREAM_BLUETOOTH_SCO)) { mMuteAffectedStreams &= ~(1 << vss.mStreamType); } } } private void createStreamStates() { int numStreamTypes = AudioSystem.getNumStreamTypes(); VolumeStreamState[] streams = mStreamStates = new VolumeStreamState[numStreamTypes]; for (int i = 0; i < numStreamTypes; i++) { streams[i] = new VolumeStreamState(System.VOLUME_SETTINGS_INT[mStreamVolumeAlias[i]], i); } checkAllFixedVolumeDevices(); checkAllAliasStreamVolumes(); checkMuteAffectedStreams(); updateDefaultVolumes(); } // Update default indexes from aliased streams. Must be called after mStreamStates is created private void updateDefaultVolumes() { for (int stream = 0; stream < mStreamStates.length; stream++) { if (stream != mStreamVolumeAlias[stream]) { AudioSystem.DEFAULT_STREAM_VOLUME[stream] = rescaleIndex( AudioSystem.DEFAULT_STREAM_VOLUME[mStreamVolumeAlias[stream]], mStreamVolumeAlias[stream], stream); } } } private void dumpStreamStates(PrintWriter pw) { pw.println("\nStream volumes (device: index)"); int numStreamTypes = AudioSystem.getNumStreamTypes(); for (int i = 0; i < numStreamTypes; i++) { pw.println("- " + AudioSystem.STREAM_NAMES[i] + ":"); mStreamStates[i].dump(pw); pw.println(""); } pw.print("\n- mute affected streams = 0x"); pw.println(Integer.toHexString(mMuteAffectedStreams)); } private void updateStreamVolumeAlias(boolean updateVolumes, String caller) { int dtmfStreamAlias; final int a11yStreamAlias = sIndependentA11yVolume ? AudioSystem.STREAM_ACCESSIBILITY : AudioSystem.STREAM_MUSIC; if (mIsSingleVolume) { mStreamVolumeAlias = STREAM_VOLUME_ALIAS_TELEVISION; dtmfStreamAlias = AudioSystem.STREAM_MUSIC; } else { switch (mPlatformType) { case AudioSystem.PLATFORM_VOICE: mStreamVolumeAlias = STREAM_VOLUME_ALIAS_VOICE; dtmfStreamAlias = AudioSystem.STREAM_RING; break; default: mStreamVolumeAlias = STREAM_VOLUME_ALIAS_DEFAULT; dtmfStreamAlias = AudioSystem.STREAM_MUSIC; } } if (mIsSingleVolume) { mRingerModeAffectedStreams = 0; } else { if (isInCommunication()) { dtmfStreamAlias = AudioSystem.STREAM_VOICE_CALL; mRingerModeAffectedStreams &= ~(1 << AudioSystem.STREAM_DTMF); } else { mRingerModeAffectedStreams |= (1 << AudioSystem.STREAM_DTMF); } } mStreamVolumeAlias[AudioSystem.STREAM_DTMF] = dtmfStreamAlias; mStreamVolumeAlias[AudioSystem.STREAM_ACCESSIBILITY] = a11yStreamAlias; if (updateVolumes && mStreamStates != null) { updateDefaultVolumes(); synchronized (mSettingsLock) { synchronized (VolumeStreamState.class) { mStreamStates[AudioSystem.STREAM_DTMF] .setAllIndexes(mStreamStates[dtmfStreamAlias], caller); mStreamStates[AudioSystem.STREAM_ACCESSIBILITY].mVolumeIndexSettingName = System.VOLUME_SETTINGS_INT[a11yStreamAlias]; mStreamStates[AudioSystem.STREAM_ACCESSIBILITY].setAllIndexes( mStreamStates[a11yStreamAlias], caller); } } if (sIndependentA11yVolume) { // restore the a11y values from the settings mStreamStates[AudioSystem.STREAM_ACCESSIBILITY].readSettings(); } // apply stream mute states according to new value of mRingerModeAffectedStreams setRingerModeInt(getRingerModeInternal(), false); sendMsg(mAudioHandler, MSG_SET_ALL_VOLUMES, SENDMSG_QUEUE, 0, 0, mStreamStates[AudioSystem.STREAM_DTMF], 0); sendMsg(mAudioHandler, MSG_SET_ALL_VOLUMES, SENDMSG_QUEUE, 0, 0, mStreamStates[AudioSystem.STREAM_ACCESSIBILITY], 0); } } private void readDockAudioSettings(ContentResolver cr) { mDockAudioMediaEnabled = Settings.Global.getInt( cr, Settings.Global.DOCK_AUDIO_MEDIA_ENABLED, 0) == 1; sendMsg(mAudioHandler, MSG_SET_FORCE_USE, SENDMSG_QUEUE, AudioSystem.FOR_DOCK, mDockAudioMediaEnabled ? AudioSystem.FORCE_ANALOG_DOCK : AudioSystem.FORCE_NONE, new String("readDockAudioSettings"), 0); } private void updateMasterMono(ContentResolver cr) { final boolean masterMono = System.getIntForUser( cr, System.MASTER_MONO, 0 /* default */, UserHandle.USER_CURRENT) == 1; if (DEBUG_VOL) { Log.d(TAG, String.format("Master mono %b", masterMono)); } AudioSystem.setMasterMono(masterMono); } private void updateMasterBalance(ContentResolver cr) { final float masterBalance = System.getFloatForUser( cr, System.MASTER_BALANCE, 0.f /* default */, UserHandle.USER_CURRENT); if (DEBUG_VOL) { Log.d(TAG, String.format("Master balance %f", masterBalance)); } if (AudioSystem.setMasterBalance(masterBalance) != 0) { Log.e(TAG, String.format("setMasterBalance failed for %f", masterBalance)); } } private void sendEncodedSurroundMode(ContentResolver cr, String eventSource) { final int encodedSurroundMode = Settings.Global.getInt( cr, Settings.Global.ENCODED_SURROUND_OUTPUT, Settings.Global.ENCODED_SURROUND_OUTPUT_AUTO); sendEncodedSurroundMode(encodedSurroundMode, eventSource); } private void sendEncodedSurroundMode(int encodedSurroundMode, String eventSource) { // initialize to guaranteed bad value int forceSetting = AudioSystem.NUM_FORCE_CONFIG; switch (encodedSurroundMode) { case Settings.Global.ENCODED_SURROUND_OUTPUT_AUTO: forceSetting = AudioSystem.FORCE_NONE; break; case Settings.Global.ENCODED_SURROUND_OUTPUT_NEVER: forceSetting = AudioSystem.FORCE_ENCODED_SURROUND_NEVER; break; case Settings.Global.ENCODED_SURROUND_OUTPUT_ALWAYS: forceSetting = AudioSystem.FORCE_ENCODED_SURROUND_ALWAYS; break; case Settings.Global.ENCODED_SURROUND_OUTPUT_MANUAL: forceSetting = AudioSystem.FORCE_ENCODED_SURROUND_MANUAL; break; default: Log.e(TAG, "updateSurroundSoundSettings: illegal value " + encodedSurroundMode); break; } if (forceSetting != AudioSystem.NUM_FORCE_CONFIG) { mDeviceBroker.setForceUse_Async(AudioSystem.FOR_ENCODED_SURROUND, forceSetting, eventSource); } } private void sendEnabledSurroundFormats(ContentResolver cr, boolean forceUpdate) { if (mEncodedSurroundMode != Settings.Global.ENCODED_SURROUND_OUTPUT_MANUAL) { // Manually enable surround formats only when the setting is in manual mode. return; } String enabledSurroundFormats = Settings.Global.getString( cr, Settings.Global.ENCODED_SURROUND_OUTPUT_ENABLED_FORMATS); if (enabledSurroundFormats == null) { // Never allow enabledSurroundFormats as a null, which could happen when // ENCODED_SURROUND_OUTPUT_ENABLED_FORMATS is not appear in settings DB. enabledSurroundFormats = ""; } if (!forceUpdate && TextUtils.equals(enabledSurroundFormats, mEnabledSurroundFormats)) { // Update enabled surround formats to AudioPolicyManager only when forceUpdate // is true or enabled surround formats changed. return; } mEnabledSurroundFormats = enabledSurroundFormats; String[] surroundFormats = TextUtils.split(enabledSurroundFormats, ","); ArrayList formats = new ArrayList<>(); for (String format : surroundFormats) { try { int audioFormat = Integer.valueOf(format); boolean isSurroundFormat = false; for (int sf : AudioFormat.SURROUND_SOUND_ENCODING) { if (sf == audioFormat) { isSurroundFormat = true; break; } } if (isSurroundFormat && !formats.contains(audioFormat)) { formats.add(audioFormat); } } catch (Exception e) { Log.e(TAG, "Invalid enabled surround format:" + format); } } // Set filtered surround formats to settings DB in case // there are invalid surround formats in original settings. Settings.Global.putString(mContext.getContentResolver(), Settings.Global.ENCODED_SURROUND_OUTPUT_ENABLED_FORMATS, TextUtils.join(",", formats)); sendMsg(mAudioHandler, MSG_ENABLE_SURROUND_FORMATS, SENDMSG_QUEUE, 0, 0, formats, 0); } private void onEnableSurroundFormats(ArrayList enabledSurroundFormats) { // Set surround format enabled accordingly. for (int surroundFormat : AudioFormat.SURROUND_SOUND_ENCODING) { boolean enabled = enabledSurroundFormats.contains(surroundFormat); int ret = AudioSystem.setSurroundFormatEnabled(surroundFormat, enabled); Log.i(TAG, "enable surround format:" + surroundFormat + " " + enabled + " " + ret); } } @GuardedBy("mSettingsLock") private void updateAssistantUId(boolean forceUpdate) { int assistantUid = 0; // Consider assistants in the following order of priority: // 1) apk in assistant role // 2) voice interaction service // 3) assistant service String packageName = ""; if (mRoleObserver != null) { packageName = mRoleObserver.getAssistantRoleHolder(); } if (TextUtils.isEmpty(packageName)) { String assistantName = Settings.Secure.getStringForUser( mContentResolver, Settings.Secure.VOICE_INTERACTION_SERVICE, UserHandle.USER_CURRENT); if (TextUtils.isEmpty(assistantName)) { assistantName = Settings.Secure.getStringForUser( mContentResolver, Settings.Secure.ASSISTANT, UserHandle.USER_CURRENT); } if (!TextUtils.isEmpty(assistantName)) { ComponentName componentName = ComponentName.unflattenFromString(assistantName); if (componentName == null) { Slog.w(TAG, "Invalid service name for " + Settings.Secure.VOICE_INTERACTION_SERVICE + ": " + assistantName); return; } packageName = componentName.getPackageName(); } } if (!TextUtils.isEmpty(packageName)) { PackageManager pm = mContext.getPackageManager(); if (pm.checkPermission(Manifest.permission.CAPTURE_AUDIO_HOTWORD, packageName) == PackageManager.PERMISSION_GRANTED) { try { assistantUid = pm.getPackageUid(packageName, 0); } catch (PackageManager.NameNotFoundException e) { Log.e(TAG, "updateAssistantUId() could not find UID for package: " + packageName); } } } if (assistantUid != mAssistantUid || forceUpdate) { AudioSystem.setAssistantUid(assistantUid); mAssistantUid = assistantUid; } } private void updateRttEanbled(ContentResolver cr) { final boolean rttEnabled = Settings.Secure.getIntForUser(cr, Settings.Secure.RTT_CALLING_MODE, 0, UserHandle.USER_CURRENT) != 0; AudioSystem.setRttEnabled(rttEnabled); } private void readPersistedSettings() { final ContentResolver cr = mContentResolver; int ringerModeFromSettings = Settings.Global.getInt( cr, Settings.Global.MODE_RINGER, AudioManager.RINGER_MODE_NORMAL); int ringerMode = ringerModeFromSettings; // sanity check in case the settings are restored from a device with incompatible // ringer modes if (!isValidRingerMode(ringerMode)) { ringerMode = AudioManager.RINGER_MODE_NORMAL; } if ((ringerMode == AudioManager.RINGER_MODE_VIBRATE) && !mHasVibrator) { ringerMode = AudioManager.RINGER_MODE_SILENT; } if (ringerMode != ringerModeFromSettings) { Settings.Global.putInt(cr, Settings.Global.MODE_RINGER, ringerMode); } if (mUseFixedVolume || mIsSingleVolume) { ringerMode = AudioManager.RINGER_MODE_NORMAL; } synchronized(mSettingsLock) { mRingerMode = ringerMode; if (mRingerModeExternal == -1) { mRingerModeExternal = mRingerMode; } // System.VIBRATE_ON is not used any more but defaults for mVibrateSetting // are still needed while setVibrateSetting() and getVibrateSetting() are being // deprecated. mVibrateSetting = AudioSystem.getValueForVibrateSetting(0, AudioManager.VIBRATE_TYPE_NOTIFICATION, mHasVibrator ? AudioManager.VIBRATE_SETTING_ONLY_SILENT : AudioManager.VIBRATE_SETTING_OFF); mVibrateSetting = AudioSystem.getValueForVibrateSetting(mVibrateSetting, AudioManager.VIBRATE_TYPE_RINGER, mHasVibrator ? AudioManager.VIBRATE_SETTING_ONLY_SILENT : AudioManager.VIBRATE_SETTING_OFF); updateRingerAndZenModeAffectedStreams(); readDockAudioSettings(cr); sendEncodedSurroundMode(cr, "readPersistedSettings"); sendEnabledSurroundFormats(cr, true); updateAssistantUId(true); updateRttEanbled(cr); } mMuteAffectedStreams = System.getIntForUser(cr, System.MUTE_STREAMS_AFFECTED, AudioSystem.DEFAULT_MUTE_STREAMS_AFFECTED, UserHandle.USER_CURRENT); updateMasterMono(cr); updateMasterBalance(cr); // Each stream will read its own persisted settings // Broadcast the sticky intents broadcastRingerMode(AudioManager.RINGER_MODE_CHANGED_ACTION, mRingerModeExternal); broadcastRingerMode(AudioManager.INTERNAL_RINGER_MODE_CHANGED_ACTION, mRingerMode); // Broadcast vibrate settings broadcastVibrateSetting(AudioManager.VIBRATE_TYPE_RINGER); broadcastVibrateSetting(AudioManager.VIBRATE_TYPE_NOTIFICATION); // Load settings for the volume controller mVolumeController.loadSettings(cr); } private void readUserRestrictions() { final int currentUser = getCurrentUserId(); // Check the current user restriction. boolean masterMute = mUserManagerInternal.getUserRestriction(currentUser, UserManager.DISALLOW_UNMUTE_DEVICE) || mUserManagerInternal.getUserRestriction(currentUser, UserManager.DISALLOW_ADJUST_VOLUME); if (mUseFixedVolume) { masterMute = false; AudioSystem.setMasterVolume(1.0f); } if (DEBUG_VOL) { Log.d(TAG, String.format("Master mute %s, user=%d", masterMute, currentUser)); } setSystemAudioMute(masterMute); AudioSystem.setMasterMute(masterMute); broadcastMasterMuteStatus(masterMute); boolean microphoneMute = mUserManagerInternal.getUserRestriction( currentUser, UserManager.DISALLOW_UNMUTE_MICROPHONE); if (DEBUG_VOL) { Log.d(TAG, String.format("Mic mute %s, user=%d", microphoneMute, currentUser)); } AudioSystem.muteMicrophone(microphoneMute); } private int rescaleIndex(int index, int srcStream, int dstStream) { int srcRange = mStreamStates[srcStream].getMaxIndex() - mStreamStates[srcStream].getMinIndex(); int dstRange = mStreamStates[dstStream].getMaxIndex() - mStreamStates[dstStream].getMinIndex(); if (srcRange == 0) { Log.e(TAG, "rescaleIndex : index range should not be zero"); return mStreamStates[dstStream].getMinIndex(); } return mStreamStates[dstStream].getMinIndex() + ((index - mStreamStates[srcStream].getMinIndex()) * dstRange + srcRange / 2) / srcRange; } /////////////////////////////////////////////////////////////////////////// // IPC methods /////////////////////////////////////////////////////////////////////////// /** @see AudioManager#adjustVolume(int, int) */ public void adjustSuggestedStreamVolume(int direction, int suggestedStreamType, int flags, String callingPackage, String caller) { final IAudioPolicyCallback extVolCtlr; synchronized (mExtVolumeControllerLock) { extVolCtlr = mExtVolumeController; } if (extVolCtlr != null) { sendMsg(mAudioHandler, MSG_NOTIFY_VOL_EVENT, SENDMSG_QUEUE, direction, 0 /*ignored*/, extVolCtlr, 0 /*delay*/); } else { adjustSuggestedStreamVolume(direction, suggestedStreamType, flags, callingPackage, caller, Binder.getCallingUid()); } } private void adjustSuggestedStreamVolume(int direction, int suggestedStreamType, int flags, String callingPackage, String caller, int uid) { if (DEBUG_VOL) Log.d(TAG, "adjustSuggestedStreamVolume() stream=" + suggestedStreamType + ", flags=" + flags + ", caller=" + caller + ", volControlStream=" + mVolumeControlStream + ", userSelect=" + mUserSelectedVolumeControlStream); if (direction != AudioManager.ADJUST_SAME) { sVolumeLogger.log(new VolumeEvent(VolumeEvent.VOL_ADJUST_SUGG_VOL, suggestedStreamType, direction/*val1*/, flags/*val2*/, new StringBuilder(callingPackage) .append("/").append(caller).append(" uid:").append(uid).toString())); } final int streamType; synchronized (mForceControlStreamLock) { // Request lock in case mVolumeControlStream is changed by other thread. if (mUserSelectedVolumeControlStream) { // implies mVolumeControlStream != -1 streamType = mVolumeControlStream; } else { final int maybeActiveStreamType = getActiveStreamType(suggestedStreamType); final boolean activeForReal; if (maybeActiveStreamType == AudioSystem.STREAM_RING || maybeActiveStreamType == AudioSystem.STREAM_NOTIFICATION) { activeForReal = wasStreamActiveRecently(maybeActiveStreamType, 0); } else { activeForReal = AudioSystem.isStreamActive(maybeActiveStreamType, 0); } if (activeForReal || mVolumeControlStream == -1) { streamType = maybeActiveStreamType; } else { streamType = mVolumeControlStream; } } } final boolean isMute = isMuteAdjust(direction); ensureValidStreamType(streamType); final int resolvedStream = mStreamVolumeAlias[streamType]; // Play sounds on STREAM_RING only. if ((flags & AudioManager.FLAG_PLAY_SOUND) != 0 && resolvedStream != AudioSystem.STREAM_RING) { flags &= ~AudioManager.FLAG_PLAY_SOUND; } // For notifications/ring, show the ui before making any adjustments // Don't suppress mute/unmute requests // Don't suppress adjustments for single volume device if (mVolumeController.suppressAdjustment(resolvedStream, flags, isMute) && !mIsSingleVolume) { direction = 0; flags &= ~AudioManager.FLAG_PLAY_SOUND; flags &= ~AudioManager.FLAG_VIBRATE; if (DEBUG_VOL) Log.d(TAG, "Volume controller suppressed adjustment"); } adjustStreamVolume(streamType, direction, flags, callingPackage, caller, uid); } /** @see AudioManager#adjustStreamVolume(int, int, int) */ public void adjustStreamVolume(int streamType, int direction, int flags, String callingPackage) { if ((streamType == AudioManager.STREAM_ACCESSIBILITY) && !canChangeAccessibilityVolume()) { Log.w(TAG, "Trying to call adjustStreamVolume() for a11y without" + "CHANGE_ACCESSIBILITY_VOLUME / callingPackage=" + callingPackage); return; } sVolumeLogger.log(new VolumeEvent(VolumeEvent.VOL_ADJUST_STREAM_VOL, streamType, direction/*val1*/, flags/*val2*/, callingPackage)); adjustStreamVolume(streamType, direction, flags, callingPackage, callingPackage, Binder.getCallingUid()); } protected void adjustStreamVolume(int streamType, int direction, int flags, String callingPackage, String caller, int uid) { if (mUseFixedVolume) { return; } if (DEBUG_VOL) Log.d(TAG, "adjustStreamVolume() stream=" + streamType + ", dir=" + direction + ", flags=" + flags + ", caller=" + caller); ensureValidDirection(direction); ensureValidStreamType(streamType); boolean isMuteAdjust = isMuteAdjust(direction); if (isMuteAdjust && !isStreamAffectedByMute(streamType)) { return; } // If adjust is mute and the stream is STREAM_VOICE_CALL or STREAM_BLUETOOTH_SCO, make sure // that the calling app have the MODIFY_PHONE_STATE permission. if (isMuteAdjust && (streamType == AudioSystem.STREAM_VOICE_CALL || streamType == AudioSystem.STREAM_BLUETOOTH_SCO) && mContext.checkCallingOrSelfPermission( android.Manifest.permission.MODIFY_PHONE_STATE) != PackageManager.PERMISSION_GRANTED) { Log.w(TAG, "MODIFY_PHONE_STATE Permission Denial: adjustStreamVolume from pid=" + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid()); return; } // use stream type alias here so that streams with same alias have the same behavior, // including with regard to silent mode control (e.g the use of STREAM_RING below and in // checkForRingerModeChange() in place of STREAM_RING or STREAM_NOTIFICATION) int streamTypeAlias = mStreamVolumeAlias[streamType]; VolumeStreamState streamState = mStreamStates[streamTypeAlias]; final int device = getDeviceForStream(streamTypeAlias); int aliasIndex = streamState.getIndex(device); boolean adjustVolume = true; int step; // skip a2dp absolute volume control request when the device // is not an a2dp device if ((device & AudioSystem.DEVICE_OUT_ALL_A2DP) == 0 && (flags & AudioManager.FLAG_BLUETOOTH_ABS_VOLUME) != 0) { return; } // If we are being called by the system (e.g. hardware keys) check for current user // so we handle user restrictions correctly. if (uid == android.os.Process.SYSTEM_UID) { uid = UserHandle.getUid(getCurrentUserId(), UserHandle.getAppId(uid)); } if (mAppOps.noteOp(STREAM_VOLUME_OPS[streamTypeAlias], uid, callingPackage) != AppOpsManager.MODE_ALLOWED) { return; } // reset any pending volume command synchronized (mSafeMediaVolumeStateLock) { mPendingVolumeCommand = null; } flags &= ~AudioManager.FLAG_FIXED_VOLUME; if ((streamTypeAlias == AudioSystem.STREAM_MUSIC) && ((device & mFixedVolumeDevices) != 0)) { flags |= AudioManager.FLAG_FIXED_VOLUME; // Always toggle between max safe volume and 0 for fixed volume devices where safe // volume is enforced, and max and 0 for the others. // This is simulated by stepping by the full allowed volume range if (mSafeMediaVolumeState == SAFE_MEDIA_VOLUME_ACTIVE && (device & mSafeMediaVolumeDevices) != 0) { step = safeMediaVolumeIndex(device); } else { step = streamState.getMaxIndex(); } if (aliasIndex != 0) { aliasIndex = step; } } else { // convert one UI step (+/-1) into a number of internal units on the stream alias step = rescaleIndex(10, streamType, streamTypeAlias); } // If either the client forces allowing ringer modes for this adjustment, // or the stream type is one that is affected by ringer modes if (((flags & AudioManager.FLAG_ALLOW_RINGER_MODES) != 0) || (streamTypeAlias == getUiSoundsStreamType())) { int ringerMode = getRingerModeInternal(); // do not vibrate if already in vibrate mode if (ringerMode == AudioManager.RINGER_MODE_VIBRATE) { flags &= ~AudioManager.FLAG_VIBRATE; } // Check if the ringer mode handles this adjustment. If it does we don't // need to adjust the volume further. final int result = checkForRingerModeChange(aliasIndex, direction, step, streamState.mIsMuted, callingPackage, flags); adjustVolume = (result & FLAG_ADJUST_VOLUME) != 0; // If suppressing a volume adjustment in silent mode, display the UI hint if ((result & AudioManager.FLAG_SHOW_SILENT_HINT) != 0) { flags |= AudioManager.FLAG_SHOW_SILENT_HINT; } // If suppressing a volume down adjustment in vibrate mode, display the UI hint if ((result & AudioManager.FLAG_SHOW_VIBRATE_HINT) != 0) { flags |= AudioManager.FLAG_SHOW_VIBRATE_HINT; } } // If the ringer mode or zen is muting the stream, do not change stream unless // it'll cause us to exit dnd if (!volumeAdjustmentAllowedByDnd(streamTypeAlias, flags)) { adjustVolume = false; } int oldIndex = mStreamStates[streamType].getIndex(device); if (adjustVolume && (direction != AudioManager.ADJUST_SAME)) { mAudioHandler.removeMessages(MSG_UNMUTE_STREAM); if (isMuteAdjust) { boolean state; if (direction == AudioManager.ADJUST_TOGGLE_MUTE) { state = !streamState.mIsMuted; } else { state = direction == AudioManager.ADJUST_MUTE; } if (streamTypeAlias == AudioSystem.STREAM_MUSIC) { setSystemAudioMute(state); } for (int stream = 0; stream < mStreamStates.length; stream++) { if (streamTypeAlias == mStreamVolumeAlias[stream]) { if (!(readCameraSoundForced() && (mStreamStates[stream].getStreamType() == AudioSystem.STREAM_SYSTEM_ENFORCED))) { mStreamStates[stream].mute(state); } } } } else if ((direction == AudioManager.ADJUST_RAISE) && !checkSafeMediaVolume(streamTypeAlias, aliasIndex + step, device)) { Log.e(TAG, "adjustStreamVolume() safe volume index = " + oldIndex); mVolumeController.postDisplaySafeVolumeWarning(flags); } else if (((device & mFullVolumeDevices) == 0) && (streamState.adjustIndex(direction * step, device, caller) || streamState.mIsMuted)) { // Post message to set system volume (it in turn will post a // message to persist). if (streamState.mIsMuted) { // Unmute the stream if it was previously muted if (direction == AudioManager.ADJUST_RAISE) { // unmute immediately for volume up streamState.mute(false); } else if (direction == AudioManager.ADJUST_LOWER) { if (mIsSingleVolume) { sendMsg(mAudioHandler, MSG_UNMUTE_STREAM, SENDMSG_QUEUE, streamTypeAlias, flags, null, UNMUTE_STREAM_DELAY); } } } sendMsg(mAudioHandler, MSG_SET_DEVICE_VOLUME, SENDMSG_QUEUE, device, 0, streamState, 0); } int newIndex = mStreamStates[streamType].getIndex(device); // Check if volume update should be send to AVRCP if (streamTypeAlias == AudioSystem.STREAM_MUSIC && (device & AudioSystem.DEVICE_OUT_ALL_A2DP) != 0 && (flags & AudioManager.FLAG_BLUETOOTH_ABS_VOLUME) == 0) { if (DEBUG_VOL) { Log.d(TAG, "adjustSreamVolume: postSetAvrcpAbsoluteVolumeIndex index=" + newIndex + "stream=" + streamType); } mDeviceBroker.postSetAvrcpAbsoluteVolumeIndex(newIndex / 10); } // Check if volume update should be send to Hearing Aid if ((device & AudioSystem.DEVICE_OUT_HEARING_AID) != 0) { // only modify the hearing aid attenuation when the stream to modify matches // the one expected by the hearing aid if (streamType == getHearingAidStreamType()) { if (DEBUG_VOL) { Log.d(TAG, "adjustSreamVolume postSetHearingAidVolumeIndex index=" + newIndex + " stream=" + streamType); } mDeviceBroker.postSetHearingAidVolumeIndex(newIndex, streamType); } } // Check if volume update should be sent to Hdmi system audio. if (streamTypeAlias == AudioSystem.STREAM_MUSIC) { setSystemAudioVolume(oldIndex, newIndex, getStreamMaxVolume(streamType), flags); } synchronized (mHdmiClientLock) { if (mHdmiManager != null) { // mHdmiCecSink true => mHdmiPlaybackClient != null if (mHdmiCecSink && streamTypeAlias == AudioSystem.STREAM_MUSIC // vol change on a full volume device && ((device & mFullVolumeDevices) != 0)) { int keyCode = KeyEvent.KEYCODE_UNKNOWN; switch (direction) { case AudioManager.ADJUST_RAISE: keyCode = KeyEvent.KEYCODE_VOLUME_UP; break; case AudioManager.ADJUST_LOWER: keyCode = KeyEvent.KEYCODE_VOLUME_DOWN; break; case AudioManager.ADJUST_TOGGLE_MUTE: keyCode = KeyEvent.KEYCODE_VOLUME_MUTE; break; default: break; } if (keyCode != KeyEvent.KEYCODE_UNKNOWN) { final long ident = Binder.clearCallingIdentity(); try { mHdmiPlaybackClient.sendKeyEvent(keyCode, true); mHdmiPlaybackClient.sendKeyEvent(keyCode, false); } finally { Binder.restoreCallingIdentity(ident); } } } if (mHdmiAudioSystemClient != null && mHdmiSystemAudioSupported && streamTypeAlias == AudioSystem.STREAM_MUSIC && (oldIndex != newIndex || isMuteAdjust)) { final long identity = Binder.clearCallingIdentity(); mHdmiAudioSystemClient.sendReportAudioStatusCecCommand( isMuteAdjust, getStreamVolume(AudioSystem.STREAM_MUSIC), getStreamMaxVolume(AudioSystem.STREAM_MUSIC), isStreamMute(AudioSystem.STREAM_MUSIC)); Binder.restoreCallingIdentity(identity); } } } } int index = mStreamStates[streamType].getIndex(device); sendVolumeUpdate(streamType, oldIndex, index, flags, device); } // Called after a delay when volume down is pressed while muted private void onUnmuteStream(int stream, int flags) { VolumeStreamState streamState = mStreamStates[stream]; streamState.mute(false); final int device = getDeviceForStream(stream); final int index = mStreamStates[stream].getIndex(device); sendVolumeUpdate(stream, index, index, flags, device); } private void setSystemAudioVolume(int oldVolume, int newVolume, int maxVolume, int flags) { // Sets the audio volume of AVR when we are in system audio mode. The new volume info // is tranformed to HDMI-CEC commands and passed through CEC bus. synchronized (mHdmiClientLock) { if (mHdmiManager == null || mHdmiTvClient == null || oldVolume == newVolume || (flags & AudioManager.FLAG_HDMI_SYSTEM_AUDIO_VOLUME) != 0 || !mHdmiSystemAudioSupported) { return; } final long token = Binder.clearCallingIdentity(); try { mHdmiTvClient.setSystemAudioVolume(oldVolume, newVolume, maxVolume); } finally { Binder.restoreCallingIdentity(token); } } } // StreamVolumeCommand contains the information needed to defer the process of // setStreamVolume() in case the user has to acknowledge the safe volume warning message. class StreamVolumeCommand { public final int mStreamType; public final int mIndex; public final int mFlags; public final int mDevice; StreamVolumeCommand(int streamType, int index, int flags, int device) { mStreamType = streamType; mIndex = index; mFlags = flags; mDevice = device; } @Override public String toString() { return new StringBuilder().append("{streamType=").append(mStreamType).append(",index=") .append(mIndex).append(",flags=").append(mFlags).append(",device=") .append(mDevice).append('}').toString(); } }; private int getNewRingerMode(int stream, int index, int flags) { // setRingerMode does nothing if the device is single volume,so the value would be unchanged if (mIsSingleVolume) { return getRingerModeExternal(); } // setting volume on ui sounds stream type also controls silent mode if (((flags & AudioManager.FLAG_ALLOW_RINGER_MODES) != 0) || (stream == getUiSoundsStreamType())) { int newRingerMode; if (index == 0) { newRingerMode = mHasVibrator ? AudioManager.RINGER_MODE_VIBRATE : mVolumePolicy.volumeDownToEnterSilent ? AudioManager.RINGER_MODE_SILENT : AudioManager.RINGER_MODE_NORMAL; } else { newRingerMode = AudioManager.RINGER_MODE_NORMAL; } return newRingerMode; } return getRingerModeExternal(); } private boolean isAndroidNPlus(String caller) { try { final ApplicationInfo applicationInfo = mContext.getPackageManager().getApplicationInfoAsUser( caller, 0, UserHandle.getUserId(Binder.getCallingUid())); if (applicationInfo.targetSdkVersion >= Build.VERSION_CODES.N) { return true; } return false; } catch (PackageManager.NameNotFoundException e) { return true; } } private boolean wouldToggleZenMode(int newMode) { if (getRingerModeExternal() == AudioManager.RINGER_MODE_SILENT && newMode != AudioManager.RINGER_MODE_SILENT) { return true; } else if (getRingerModeExternal() != AudioManager.RINGER_MODE_SILENT && newMode == AudioManager.RINGER_MODE_SILENT) { return true; } return false; } private void onSetStreamVolume(int streamType, int index, int flags, int device, String caller) { final int stream = mStreamVolumeAlias[streamType]; setStreamVolumeInt(stream, index, device, false, caller); // setting volume on ui sounds stream type also controls silent mode if (((flags & AudioManager.FLAG_ALLOW_RINGER_MODES) != 0) || (stream == getUiSoundsStreamType())) { setRingerMode(getNewRingerMode(stream, index, flags), TAG + ".onSetStreamVolume", false /*external*/); } // setting non-zero volume for a muted stream unmutes the stream and vice versa, // except for BT SCO stream where only explicit mute is allowed to comply to BT requirements if (streamType != AudioSystem.STREAM_BLUETOOTH_SCO) { mStreamStates[stream].mute(index == 0); } } private void enforceModifyAudioRoutingPermission() { if (mContext.checkCallingPermission( android.Manifest.permission.MODIFY_AUDIO_ROUTING) != PackageManager.PERMISSION_GRANTED) { throw new SecurityException("Missing MODIFY_AUDIO_ROUTING permission"); } } /** @see AudioManager#setVolumeIndexForAttributes(attr, int, int) */ public void setVolumeIndexForAttributes(@NonNull AudioAttributes attr, int index, int flags, String callingPackage) { enforceModifyAudioRoutingPermission(); Preconditions.checkNotNull(attr, "attr must not be null"); // @todo not hold the caller context, post message int stream = AudioProductStrategy.getLegacyStreamTypeForStrategyWithAudioAttributes(attr); final int device = getDeviceForStream(stream); int oldIndex = AudioSystem.getVolumeIndexForAttributes(attr, device); AudioSystem.setVolumeIndexForAttributes(attr, index, device); final int volumeGroup = getVolumeGroupIdForAttributes(attr); final AudioVolumeGroup avg = getAudioVolumeGroupById(volumeGroup); if (avg == null) { return; } for (final int groupedStream : avg.getLegacyStreamTypes()) { setStreamVolume(groupedStream, index, flags, callingPackage, callingPackage, Binder.getCallingUid()); } } @Nullable private AudioVolumeGroup getAudioVolumeGroupById(int volumeGroupId) { for (final AudioVolumeGroup avg : AudioVolumeGroup.getAudioVolumeGroups()) { if (avg.getId() == volumeGroupId) { return avg; } } Log.e(TAG, ": invalid volume group id: " + volumeGroupId + " requested"); return null; } /** @see AudioManager#getVolumeIndexForAttributes(attr) */ public int getVolumeIndexForAttributes(@NonNull AudioAttributes attr) { enforceModifyAudioRoutingPermission(); Preconditions.checkNotNull(attr, "attr must not be null"); int stream = AudioProductStrategy.getLegacyStreamTypeForStrategyWithAudioAttributes(attr); final int device = getDeviceForStream(stream); return AudioSystem.getVolumeIndexForAttributes(attr, device); } /** @see AudioManager#getMaxVolumeIndexForAttributes(attr) */ public int getMaxVolumeIndexForAttributes(@NonNull AudioAttributes attr) { enforceModifyAudioRoutingPermission(); Preconditions.checkNotNull(attr, "attr must not be null"); return AudioSystem.getMaxVolumeIndexForAttributes(attr); } /** @see AudioManager#getMinVolumeIndexForAttributes(attr) */ public int getMinVolumeIndexForAttributes(@NonNull AudioAttributes attr) { enforceModifyAudioRoutingPermission(); Preconditions.checkNotNull(attr, "attr must not be null"); return AudioSystem.getMinVolumeIndexForAttributes(attr); } /** @see AudioManager#setStreamVolume(int, int, int) */ public void setStreamVolume(int streamType, int index, int flags, String callingPackage) { if ((streamType == AudioManager.STREAM_ACCESSIBILITY) && !canChangeAccessibilityVolume()) { Log.w(TAG, "Trying to call setStreamVolume() for a11y without" + " CHANGE_ACCESSIBILITY_VOLUME callingPackage=" + callingPackage); return; } if ((streamType == AudioManager.STREAM_VOICE_CALL) && (index == 0) && (mContext.checkCallingOrSelfPermission( android.Manifest.permission.MODIFY_PHONE_STATE) != PackageManager.PERMISSION_GRANTED)) { Log.w(TAG, "Trying to call setStreamVolume() for STREAM_VOICE_CALL and index 0 without" + " MODIFY_PHONE_STATE callingPackage=" + callingPackage); return; } sVolumeLogger.log(new VolumeEvent(VolumeEvent.VOL_SET_STREAM_VOL, streamType, index/*val1*/, flags/*val2*/, callingPackage)); setStreamVolume(streamType, index, flags, callingPackage, callingPackage, Binder.getCallingUid()); } private boolean canChangeAccessibilityVolume() { synchronized (mAccessibilityServiceUidsLock) { if (PackageManager.PERMISSION_GRANTED == mContext.checkCallingOrSelfPermission( android.Manifest.permission.CHANGE_ACCESSIBILITY_VOLUME)) { return true; } if (mAccessibilityServiceUids != null) { int callingUid = Binder.getCallingUid(); for (int i = 0; i < mAccessibilityServiceUids.length; i++) { if (mAccessibilityServiceUids[i] == callingUid) { return true; } } } return false; } } /*package*/ int getHearingAidStreamType() { return getHearingAidStreamType(mMode); } private int getHearingAidStreamType(int mode) { switch (mode) { case AudioSystem.MODE_IN_COMMUNICATION: case AudioSystem.MODE_IN_CALL: return AudioSystem.STREAM_VOICE_CALL; case AudioSystem.MODE_NORMAL: default: // other conditions will influence the stream type choice, read on... break; } if (mVoiceActive.get()) { return AudioSystem.STREAM_VOICE_CALL; } return AudioSystem.STREAM_MUSIC; } private AtomicBoolean mVoiceActive = new AtomicBoolean(false); private final IPlaybackConfigDispatcher mVoiceActivityMonitor = new IPlaybackConfigDispatcher.Stub() { @Override public void dispatchPlaybackConfigChange(List configs, boolean flush) { sendMsg(mAudioHandler, MSG_PLAYBACK_CONFIG_CHANGE, SENDMSG_REPLACE, 0 /*arg1 ignored*/, 0 /*arg2 ignored*/, configs /*obj*/, 0 /*delay*/); } }; private void onPlaybackConfigChange(List configs) { boolean voiceActive = false; for (AudioPlaybackConfiguration config : configs) { final int usage = config.getAudioAttributes().getUsage(); if ((usage == AudioAttributes.USAGE_VOICE_COMMUNICATION || usage == AudioAttributes.USAGE_VOICE_COMMUNICATION_SIGNALLING) && config.getPlayerState() == AudioPlaybackConfiguration.PLAYER_STATE_STARTED) { voiceActive = true; break; } } if (mVoiceActive.getAndSet(voiceActive) != voiceActive) { updateHearingAidVolumeOnVoiceActivityUpdate(); } } private void updateHearingAidVolumeOnVoiceActivityUpdate() { final int streamType = getHearingAidStreamType(); final int index = getStreamVolume(streamType); sVolumeLogger.log(new VolumeEvent(VolumeEvent.VOL_VOICE_ACTIVITY_HEARING_AID, mVoiceActive.get(), streamType, index)); mDeviceBroker.postSetHearingAidVolumeIndex(index * 10, streamType); } /** * Manage an audio mode change for audio devices that use an "absolute volume" model, * i.e. the framework sends the full scale signal, and the actual volume for the use case * is communicated separately. */ void updateAbsVolumeMultiModeDevices(int oldMode, int newMode) { if (oldMode == newMode) { return; } switch (newMode) { case AudioSystem.MODE_IN_COMMUNICATION: case AudioSystem.MODE_IN_CALL: case AudioSystem.MODE_NORMAL: break; case AudioSystem.MODE_RINGTONE: // not changing anything for ringtone return; case AudioSystem.MODE_CURRENT: case AudioSystem.MODE_INVALID: default: // don't know what to do in this case, better bail return; } int streamType = getHearingAidStreamType(newMode); final int device = AudioSystem.getDevicesForStream(streamType); if ((device & mAbsVolumeMultiModeCaseDevices) == 0) { return; } // handling of specific interfaces goes here: if ((device & mAbsVolumeMultiModeCaseDevices) == AudioSystem.DEVICE_OUT_HEARING_AID) { final int index = getStreamVolume(streamType); sVolumeLogger.log(new VolumeEvent(VolumeEvent.VOL_MODE_CHANGE_HEARING_AID, newMode, streamType, index)); mDeviceBroker.postSetHearingAidVolumeIndex(index * 10, streamType); } } private void setStreamVolume(int streamType, int index, int flags, String callingPackage, String caller, int uid) { if (DEBUG_VOL) { Log.d(TAG, "setStreamVolume(stream=" + streamType+", index=" + index + ", calling=" + callingPackage + ")"); } if (mUseFixedVolume) { return; } ensureValidStreamType(streamType); int streamTypeAlias = mStreamVolumeAlias[streamType]; VolumeStreamState streamState = mStreamStates[streamTypeAlias]; final int device = getDeviceForStream(streamType); int oldIndex; // skip a2dp absolute volume control request when the device // is not an a2dp device if ((device & AudioSystem.DEVICE_OUT_ALL_A2DP) == 0 && (flags & AudioManager.FLAG_BLUETOOTH_ABS_VOLUME) != 0) { return; } // If we are being called by the system (e.g. hardware keys) check for current user // so we handle user restrictions correctly. if (uid == android.os.Process.SYSTEM_UID) { uid = UserHandle.getUid(getCurrentUserId(), UserHandle.getAppId(uid)); } if (mAppOps.noteOp(STREAM_VOLUME_OPS[streamTypeAlias], uid, callingPackage) != AppOpsManager.MODE_ALLOWED) { return; } if (isAndroidNPlus(callingPackage) && wouldToggleZenMode(getNewRingerMode(streamTypeAlias, index, flags)) && !mNm.isNotificationPolicyAccessGrantedForPackage(callingPackage)) { throw new SecurityException("Not allowed to change Do Not Disturb state"); } if (!volumeAdjustmentAllowedByDnd(streamTypeAlias, flags)) { return; } synchronized (mSafeMediaVolumeStateLock) { // reset any pending volume command mPendingVolumeCommand = null; oldIndex = streamState.getIndex(device); index = rescaleIndex(index * 10, streamType, streamTypeAlias); if (streamTypeAlias == AudioSystem.STREAM_MUSIC && (device & AudioSystem.DEVICE_OUT_ALL_A2DP) != 0 && (flags & AudioManager.FLAG_BLUETOOTH_ABS_VOLUME) == 0) { if (DEBUG_VOL) { Log.d(TAG, "setStreamVolume postSetAvrcpAbsoluteVolumeIndex index=" + index + "stream=" + streamType); } mDeviceBroker.postSetAvrcpAbsoluteVolumeIndex(index / 10); } if ((device & AudioSystem.DEVICE_OUT_HEARING_AID) != 0 && streamType == getHearingAidStreamType()) { Log.i(TAG, "setStreamVolume postSetHearingAidVolumeIndex index=" + index + " stream=" + streamType); mDeviceBroker.postSetHearingAidVolumeIndex(index, streamType); } if (streamTypeAlias == AudioSystem.STREAM_MUSIC) { setSystemAudioVolume(oldIndex, index, getStreamMaxVolume(streamType), flags); } flags &= ~AudioManager.FLAG_FIXED_VOLUME; if ((streamTypeAlias == AudioSystem.STREAM_MUSIC) && ((device & mFixedVolumeDevices) != 0)) { flags |= AudioManager.FLAG_FIXED_VOLUME; // volume is either 0 or max allowed for fixed volume devices if (index != 0) { if (mSafeMediaVolumeState == SAFE_MEDIA_VOLUME_ACTIVE && (device & mSafeMediaVolumeDevices) != 0) { index = safeMediaVolumeIndex(device); } else { index = streamState.getMaxIndex(); } } } if (!checkSafeMediaVolume(streamTypeAlias, index, device)) { mVolumeController.postDisplaySafeVolumeWarning(flags); mPendingVolumeCommand = new StreamVolumeCommand( streamType, index, flags, device); } else { onSetStreamVolume(streamType, index, flags, device, caller); index = mStreamStates[streamType].getIndex(device); } } synchronized (mHdmiClientLock) { if (mHdmiManager != null && mHdmiAudioSystemClient != null && mHdmiSystemAudioSupported && streamTypeAlias == AudioSystem.STREAM_MUSIC && (oldIndex != index)) { final long identity = Binder.clearCallingIdentity(); mHdmiAudioSystemClient.sendReportAudioStatusCecCommand( false, getStreamVolume(AudioSystem.STREAM_MUSIC), getStreamMaxVolume(AudioSystem.STREAM_MUSIC), isStreamMute(AudioSystem.STREAM_MUSIC)); Binder.restoreCallingIdentity(identity); } } sendVolumeUpdate(streamType, oldIndex, index, flags, device); } private int getVolumeGroupIdForAttributes(@NonNull AudioAttributes attributes) { Preconditions.checkNotNull(attributes, "attributes must not be null"); int volumeGroupId = getVolumeGroupIdForAttributesInt(attributes); if (volumeGroupId != AudioVolumeGroup.DEFAULT_VOLUME_GROUP) { return volumeGroupId; } // The default volume group is the one hosted by default product strategy, i.e. // supporting Default Attributes return getVolumeGroupIdForAttributesInt(AudioProductStrategy.sDefaultAttributes); } private int getVolumeGroupIdForAttributesInt(@NonNull AudioAttributes attributes) { Preconditions.checkNotNull(attributes, "attributes must not be null"); for (final AudioProductStrategy productStrategy : AudioProductStrategy.getAudioProductStrategies()) { int volumeGroupId = productStrategy.getVolumeGroupIdForAudioAttributes(attributes); if (volumeGroupId != AudioVolumeGroup.DEFAULT_VOLUME_GROUP) { return volumeGroupId; } } return AudioVolumeGroup.DEFAULT_VOLUME_GROUP; } // No ringer or zen muted stream volumes can be changed unless it'll exit dnd private boolean volumeAdjustmentAllowedByDnd(int streamTypeAlias, int flags) { switch (mNm.getZenMode()) { case Settings.Global.ZEN_MODE_OFF: return true; case Settings.Global.ZEN_MODE_NO_INTERRUPTIONS: case Settings.Global.ZEN_MODE_ALARMS: case Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS: return !isStreamMutedByRingerOrZenMode(streamTypeAlias) || streamTypeAlias == getUiSoundsStreamType() || (flags & AudioManager.FLAG_ALLOW_RINGER_MODES) != 0; } return true; } /** @see AudioManager#forceVolumeControlStream(int) */ public void forceVolumeControlStream(int streamType, IBinder cb) { if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.MODIFY_PHONE_STATE) != PackageManager.PERMISSION_GRANTED) { return; } if (DEBUG_VOL) { Log.d(TAG, String.format("forceVolumeControlStream(%d)", streamType)); } synchronized(mForceControlStreamLock) { if (mVolumeControlStream != -1 && streamType != -1) { mUserSelectedVolumeControlStream = true; } mVolumeControlStream = streamType; if (mVolumeControlStream == -1) { if (mForceControlStreamClient != null) { mForceControlStreamClient.release(); mForceControlStreamClient = null; } mUserSelectedVolumeControlStream = false; } else { if (null == mForceControlStreamClient) { mForceControlStreamClient = new ForceControlStreamClient(cb); } else { if (mForceControlStreamClient.getBinder() == cb) { Log.d(TAG, "forceVolumeControlStream cb:" + cb + " is already linked."); } else { mForceControlStreamClient.release(); mForceControlStreamClient = new ForceControlStreamClient(cb); } } } } } private class ForceControlStreamClient implements IBinder.DeathRecipient { private IBinder mCb; // To be notified of client's death ForceControlStreamClient(IBinder cb) { if (cb != null) { try { cb.linkToDeath(this, 0); } catch (RemoteException e) { // Client has died! Log.w(TAG, "ForceControlStreamClient() could not link to "+cb+" binder death"); cb = null; } } mCb = cb; } public void binderDied() { synchronized(mForceControlStreamLock) { Log.w(TAG, "SCO client died"); if (mForceControlStreamClient != this) { Log.w(TAG, "unregistered control stream client died"); } else { mForceControlStreamClient = null; mVolumeControlStream = -1; mUserSelectedVolumeControlStream = false; } } } public void release() { if (mCb != null) { mCb.unlinkToDeath(this, 0); mCb = null; } } public IBinder getBinder() { return mCb; } } private void sendBroadcastToAll(Intent intent) { intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND); final long ident = Binder.clearCallingIdentity(); try { mContext.sendBroadcastAsUser(intent, UserHandle.ALL); } finally { Binder.restoreCallingIdentity(ident); } } private void sendStickyBroadcastToAll(Intent intent) { intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND); final long ident = Binder.clearCallingIdentity(); try { mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL); } finally { Binder.restoreCallingIdentity(ident); } } private int getCurrentUserId() { final long ident = Binder.clearCallingIdentity(); try { UserInfo currentUser = ActivityManager.getService().getCurrentUser(); return currentUser.id; } catch (RemoteException e) { // Activity manager not running, nothing we can do assume user 0. } finally { Binder.restoreCallingIdentity(ident); } return UserHandle.USER_SYSTEM; } // UI update and Broadcast Intent protected void sendVolumeUpdate(int streamType, int oldIndex, int index, int flags, int device) { streamType = mStreamVolumeAlias[streamType]; if (streamType == AudioSystem.STREAM_MUSIC) { flags = updateFlagsForTvPlatform(flags); if ((device & mFullVolumeDevices) != 0) { flags &= ~AudioManager.FLAG_SHOW_UI; } } mVolumeController.postVolumeChanged(streamType, flags); } // If Hdmi-CEC system audio mode is on, we show volume bar only when TV // receives volume notification from Audio Receiver. private int updateFlagsForTvPlatform(int flags) { synchronized (mHdmiClientLock) { if (mHdmiTvClient != null) { if (mHdmiSystemAudioSupported && ((flags & AudioManager.FLAG_HDMI_SYSTEM_AUDIO_VOLUME) == 0)) { flags &= ~AudioManager.FLAG_SHOW_UI; } } } return flags; } // UI update and Broadcast Intent private void sendMasterMuteUpdate(boolean muted, int flags) { mVolumeController.postMasterMuteChanged(updateFlagsForTvPlatform(flags)); broadcastMasterMuteStatus(muted); } private void broadcastMasterMuteStatus(boolean muted) { Intent intent = new Intent(AudioManager.MASTER_MUTE_CHANGED_ACTION); intent.putExtra(AudioManager.EXTRA_MASTER_VOLUME_MUTED, muted); intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT | Intent.FLAG_RECEIVER_REPLACE_PENDING); sendStickyBroadcastToAll(intent); } /** * Sets the stream state's index, and posts a message to set system volume. * This will not call out to the UI. Assumes a valid stream type. * * @param streamType Type of the stream * @param index Desired volume index of the stream * @param device the device whose volume must be changed * @param force If true, set the volume even if the desired volume is same * as the current volume. */ private void setStreamVolumeInt(int streamType, int index, int device, boolean force, String caller) { if ((device & mFullVolumeDevices) != 0) { return; } VolumeStreamState streamState = mStreamStates[streamType]; if (streamState.setIndex(index, device, caller) || force) { // Post message to set system volume (it in turn will post a message // to persist). sendMsg(mAudioHandler, MSG_SET_DEVICE_VOLUME, SENDMSG_QUEUE, device, 0, streamState, 0); } } private void setSystemAudioMute(boolean state) { synchronized (mHdmiClientLock) { if (mHdmiManager == null || mHdmiTvClient == null || !mHdmiSystemAudioSupported) return; final long token = Binder.clearCallingIdentity(); try { mHdmiTvClient.setSystemAudioMute(state); } finally { Binder.restoreCallingIdentity(token); } } } /** get stream mute state. */ public boolean isStreamMute(int streamType) { if (streamType == AudioManager.USE_DEFAULT_STREAM_TYPE) { streamType = getActiveStreamType(streamType); } synchronized (VolumeStreamState.class) { ensureValidStreamType(streamType); return mStreamStates[streamType].mIsMuted; } } private class RmtSbmxFullVolDeathHandler implements IBinder.DeathRecipient { private IBinder mICallback; // To be notified of client's death RmtSbmxFullVolDeathHandler(IBinder cb) { mICallback = cb; try { cb.linkToDeath(this, 0/*flags*/); } catch (RemoteException e) { Log.e(TAG, "can't link to death", e); } } boolean isHandlerFor(IBinder cb) { return mICallback.equals(cb); } void forget() { try { mICallback.unlinkToDeath(this, 0/*flags*/); } catch (NoSuchElementException e) { Log.e(TAG, "error unlinking to death", e); } } public void binderDied() { Log.w(TAG, "Recorder with remote submix at full volume died " + mICallback); forceRemoteSubmixFullVolume(false, mICallback); } } /** * call must be synchronized on mRmtSbmxFullVolDeathHandlers * @return true if there is a registered death handler, false otherwise */ private boolean discardRmtSbmxFullVolDeathHandlerFor(IBinder cb) { Iterator it = mRmtSbmxFullVolDeathHandlers.iterator(); while (it.hasNext()) { final RmtSbmxFullVolDeathHandler handler = it.next(); if (handler.isHandlerFor(cb)) { handler.forget(); mRmtSbmxFullVolDeathHandlers.remove(handler); return true; } } return false; } /** call synchronized on mRmtSbmxFullVolDeathHandlers */ private boolean hasRmtSbmxFullVolDeathHandlerFor(IBinder cb) { Iterator it = mRmtSbmxFullVolDeathHandlers.iterator(); while (it.hasNext()) { if (it.next().isHandlerFor(cb)) { return true; } } return false; } private int mRmtSbmxFullVolRefCount = 0; private ArrayList mRmtSbmxFullVolDeathHandlers = new ArrayList(); public void forceRemoteSubmixFullVolume(boolean startForcing, IBinder cb) { if (cb == null) { return; } if ((PackageManager.PERMISSION_GRANTED != mContext.checkCallingOrSelfPermission( android.Manifest.permission.CAPTURE_AUDIO_OUTPUT))) { Log.w(TAG, "Trying to call forceRemoteSubmixFullVolume() without CAPTURE_AUDIO_OUTPUT"); return; } synchronized(mRmtSbmxFullVolDeathHandlers) { boolean applyRequired = false; if (startForcing) { if (!hasRmtSbmxFullVolDeathHandlerFor(cb)) { mRmtSbmxFullVolDeathHandlers.add(new RmtSbmxFullVolDeathHandler(cb)); if (mRmtSbmxFullVolRefCount == 0) { mFullVolumeDevices |= AudioSystem.DEVICE_OUT_REMOTE_SUBMIX; mFixedVolumeDevices |= AudioSystem.DEVICE_OUT_REMOTE_SUBMIX; applyRequired = true; } mRmtSbmxFullVolRefCount++; } } else { if (discardRmtSbmxFullVolDeathHandlerFor(cb) && (mRmtSbmxFullVolRefCount > 0)) { mRmtSbmxFullVolRefCount--; if (mRmtSbmxFullVolRefCount == 0) { mFullVolumeDevices &= ~AudioSystem.DEVICE_OUT_REMOTE_SUBMIX; mFixedVolumeDevices &= ~AudioSystem.DEVICE_OUT_REMOTE_SUBMIX; applyRequired = true; } } } if (applyRequired) { // Assumes only STREAM_MUSIC going through DEVICE_OUT_REMOTE_SUBMIX checkAllFixedVolumeDevices(AudioSystem.STREAM_MUSIC); mStreamStates[AudioSystem.STREAM_MUSIC].applyAllVolumes(); } } } private void setMasterMuteInternal(boolean mute, int flags, String callingPackage, int uid, int userId) { // If we are being called by the system check for user we are going to change // so we handle user restrictions correctly. if (uid == android.os.Process.SYSTEM_UID) { uid = UserHandle.getUid(userId, UserHandle.getAppId(uid)); } // If OP_AUDIO_MASTER_VOLUME is set, disallow unmuting. if (!mute && mAppOps.noteOp(AppOpsManager.OP_AUDIO_MASTER_VOLUME, uid, callingPackage) != AppOpsManager.MODE_ALLOWED) { return; } if (userId != UserHandle.getCallingUserId() && mContext.checkCallingOrSelfPermission( android.Manifest.permission.INTERACT_ACROSS_USERS_FULL) != PackageManager.PERMISSION_GRANTED) { return; } setMasterMuteInternalNoCallerCheck(mute, flags, userId); } private void setMasterMuteInternalNoCallerCheck(boolean mute, int flags, int userId) { if (DEBUG_VOL) { Log.d(TAG, String.format("Master mute %s, %d, user=%d", mute, flags, userId)); } if (!isPlatformAutomotive() && mUseFixedVolume) { // If using fixed volume, we don't mute. // TODO: remove the isPlatformAutomotive check here. // The isPlatformAutomotive check is added for safety but may not be necessary. return; } // For automotive, // - the car service is always running as system user // - foreground users are non-system users // Car service is in charge of dispatching the key event include master mute to Android. // Therefore, the getCurrentUser() is always different to the foreground user. if ((isPlatformAutomotive() && userId == UserHandle.USER_SYSTEM) || (getCurrentUserId() == userId)) { if (mute != AudioSystem.getMasterMute()) { setSystemAudioMute(mute); AudioSystem.setMasterMute(mute); sendMasterMuteUpdate(mute, flags); Intent intent = new Intent(AudioManager.MASTER_MUTE_CHANGED_ACTION); intent.putExtra(AudioManager.EXTRA_MASTER_VOLUME_MUTED, mute); sendBroadcastToAll(intent); } } } /** get master mute state. */ public boolean isMasterMute() { return AudioSystem.getMasterMute(); } public void setMasterMute(boolean mute, int flags, String callingPackage, int userId) { setMasterMuteInternal(mute, flags, callingPackage, Binder.getCallingUid(), userId); } /** @see AudioManager#getStreamVolume(int) */ public int getStreamVolume(int streamType) { ensureValidStreamType(streamType); int device = getDeviceForStream(streamType); synchronized (VolumeStreamState.class) { int index = mStreamStates[streamType].getIndex(device); // by convention getStreamVolume() returns 0 when a stream is muted. if (mStreamStates[streamType].mIsMuted) { index = 0; } if (index != 0 && (mStreamVolumeAlias[streamType] == AudioSystem.STREAM_MUSIC) && (device & mFixedVolumeDevices) != 0) { index = mStreamStates[streamType].getMaxIndex(); } return (index + 5) / 10; } } /** @see AudioManager#getStreamMaxVolume(int) */ public int getStreamMaxVolume(int streamType) { ensureValidStreamType(streamType); return (mStreamStates[streamType].getMaxIndex() + 5) / 10; } /** @see AudioManager#getStreamMinVolumeInt(int) */ public int getStreamMinVolume(int streamType) { ensureValidStreamType(streamType); return (mStreamStates[streamType].getMinIndex() + 5) / 10; } /** Get last audible volume before stream was muted. */ public int getLastAudibleStreamVolume(int streamType) { ensureValidStreamType(streamType); int device = getDeviceForStream(streamType); return (mStreamStates[streamType].getIndex(device) + 5) / 10; } /** @see AudioManager#getUiSoundsStreamType() */ public int getUiSoundsStreamType() { return mStreamVolumeAlias[AudioSystem.STREAM_SYSTEM]; } /** @see AudioManager#setMicrophoneMute(boolean) */ @Override public void setMicrophoneMute(boolean on, String callingPackage, int userId) { // If we are being called by the system check for user we are going to change // so we handle user restrictions correctly. int uid = Binder.getCallingUid(); if (uid == android.os.Process.SYSTEM_UID) { uid = UserHandle.getUid(userId, UserHandle.getAppId(uid)); } // If OP_MUTE_MICROPHONE is set, disallow unmuting. if (!on && mAppOps.noteOp(AppOpsManager.OP_MUTE_MICROPHONE, uid, callingPackage) != AppOpsManager.MODE_ALLOWED) { return; } if (!checkAudioSettingsPermission("setMicrophoneMute()")) { return; } if (userId != UserHandle.getCallingUserId() && mContext.checkCallingOrSelfPermission( android.Manifest.permission.INTERACT_ACROSS_USERS_FULL) != PackageManager.PERMISSION_GRANTED) { return; } setMicrophoneMuteNoCallerCheck(on, userId); } private void setMicrophoneMuteNoCallerCheck(boolean on, int userId) { if (DEBUG_VOL) { Log.d(TAG, String.format("Mic mute %s, user=%d", on, userId)); } // only mute for the current user if (getCurrentUserId() == userId) { final boolean currentMute = AudioSystem.isMicrophoneMuted(); final long identity = Binder.clearCallingIdentity(); AudioSystem.muteMicrophone(on); Binder.restoreCallingIdentity(identity); if (on != currentMute) { mContext.sendBroadcast(new Intent(AudioManager.ACTION_MICROPHONE_MUTE_CHANGED) .setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY)); } } } @Override public int getRingerModeExternal() { synchronized(mSettingsLock) { return mRingerModeExternal; } } @Override public int getRingerModeInternal() { synchronized(mSettingsLock) { return mRingerMode; } } private void ensureValidRingerMode(int ringerMode) { if (!isValidRingerMode(ringerMode)) { throw new IllegalArgumentException("Bad ringer mode " + ringerMode); } } /** @see AudioManager#isValidRingerMode(int) */ public boolean isValidRingerMode(int ringerMode) { return ringerMode >= 0 && ringerMode <= AudioManager.RINGER_MODE_MAX; } public void setRingerModeExternal(int ringerMode, String caller) { if (isAndroidNPlus(caller) && wouldToggleZenMode(ringerMode) && !mNm.isNotificationPolicyAccessGrantedForPackage(caller)) { throw new SecurityException("Not allowed to change Do Not Disturb state"); } setRingerMode(ringerMode, caller, true /*external*/); } public void setRingerModeInternal(int ringerMode, String caller) { enforceVolumeController("setRingerModeInternal"); setRingerMode(ringerMode, caller, false /*external*/); } public void silenceRingerModeInternal(String reason) { VibrationEffect effect = null; int ringerMode = AudioManager.RINGER_MODE_SILENT; int toastText = 0; int silenceRingerSetting = Settings.Secure.VOLUME_HUSH_OFF; if (mContext.getResources() .getBoolean(com.android.internal.R.bool.config_volumeHushGestureEnabled)) { silenceRingerSetting = Settings.Secure.getIntForUser(mContentResolver, Settings.Secure.VOLUME_HUSH_GESTURE, VOLUME_HUSH_OFF, UserHandle.USER_CURRENT); } switch(silenceRingerSetting) { case VOLUME_HUSH_MUTE: effect = VibrationEffect.get(VibrationEffect.EFFECT_DOUBLE_CLICK); ringerMode = AudioManager.RINGER_MODE_SILENT; toastText = com.android.internal.R.string.volume_dialog_ringer_guidance_silent; break; case VOLUME_HUSH_VIBRATE: effect = VibrationEffect.get(VibrationEffect.EFFECT_HEAVY_CLICK); ringerMode = AudioManager.RINGER_MODE_VIBRATE; toastText = com.android.internal.R.string.volume_dialog_ringer_guidance_vibrate; break; } maybeVibrate(effect, reason); setRingerModeInternal(ringerMode, reason); Toast.makeText(mContext, toastText, Toast.LENGTH_SHORT).show(); } private boolean maybeVibrate(VibrationEffect effect, String reason) { if (!mHasVibrator) { return false; } final boolean hapticsDisabled = Settings.System.getIntForUser(mContext.getContentResolver(), Settings.System.HAPTIC_FEEDBACK_ENABLED, 0, UserHandle.USER_CURRENT) == 0; if (hapticsDisabled) { return false; } if (effect == null) { return false; } mVibrator.vibrate(Binder.getCallingUid(), mContext.getOpPackageName(), effect, reason, VIBRATION_ATTRIBUTES); return true; } private void setRingerMode(int ringerMode, String caller, boolean external) { if (mUseFixedVolume || mIsSingleVolume) { return; } if (caller == null || caller.length() == 0) { throw new IllegalArgumentException("Bad caller: " + caller); } ensureValidRingerMode(ringerMode); if ((ringerMode == AudioManager.RINGER_MODE_VIBRATE) && !mHasVibrator) { ringerMode = AudioManager.RINGER_MODE_SILENT; } final long identity = Binder.clearCallingIdentity(); try { synchronized (mSettingsLock) { final int ringerModeInternal = getRingerModeInternal(); final int ringerModeExternal = getRingerModeExternal(); if (external) { setRingerModeExt(ringerMode); if (mRingerModeDelegate != null) { ringerMode = mRingerModeDelegate.onSetRingerModeExternal(ringerModeExternal, ringerMode, caller, ringerModeInternal, mVolumePolicy); } if (ringerMode != ringerModeInternal) { setRingerModeInt(ringerMode, true /*persist*/); } } else /*internal*/ { if (ringerMode != ringerModeInternal) { setRingerModeInt(ringerMode, true /*persist*/); } if (mRingerModeDelegate != null) { ringerMode = mRingerModeDelegate.onSetRingerModeInternal(ringerModeInternal, ringerMode, caller, ringerModeExternal, mVolumePolicy); } setRingerModeExt(ringerMode); } } } finally { Binder.restoreCallingIdentity(identity); } } private void setRingerModeExt(int ringerMode) { synchronized(mSettingsLock) { if (ringerMode == mRingerModeExternal) return; mRingerModeExternal = ringerMode; } // Send sticky broadcast broadcastRingerMode(AudioManager.RINGER_MODE_CHANGED_ACTION, ringerMode); } @GuardedBy("mSettingsLock") private void muteRingerModeStreams() { // Mute stream if not previously muted by ringer mode and (ringer mode // is not RINGER_MODE_NORMAL OR stream is zen muted) and stream is affected by ringer mode. // Unmute stream if previously muted by ringer/zen mode and ringer mode // is RINGER_MODE_NORMAL or stream is not affected by ringer mode. int numStreamTypes = AudioSystem.getNumStreamTypes(); if (mNm == null) { mNm = (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE); } final int ringerMode = mRingerMode; // Read ringer mode as reading primitives is atomic final boolean ringerModeMute = ringerMode == AudioManager.RINGER_MODE_VIBRATE || ringerMode == AudioManager.RINGER_MODE_SILENT; final boolean shouldRingSco = ringerMode == AudioManager.RINGER_MODE_VIBRATE && isBluetoothScoOn(); // Ask audio policy engine to force use Bluetooth SCO channel if needed final String eventSource = "muteRingerModeStreams() from u/pid:" + Binder.getCallingUid() + "/" + Binder.getCallingPid(); sendMsg(mAudioHandler, MSG_SET_FORCE_USE, SENDMSG_QUEUE, AudioSystem.FOR_VIBRATE_RINGING, shouldRingSco ? AudioSystem.FORCE_BT_SCO : AudioSystem.FORCE_NONE, eventSource, 0); for (int streamType = numStreamTypes - 1; streamType >= 0; streamType--) { final boolean isMuted = isStreamMutedByRingerOrZenMode(streamType); final boolean muteAllowedBySco = !(shouldRingSco && streamType == AudioSystem.STREAM_RING); final boolean shouldZenMute = shouldZenMuteStream(streamType); final boolean shouldMute = shouldZenMute || (ringerModeMute && isStreamAffectedByRingerMode(streamType) && muteAllowedBySco); if (isMuted == shouldMute) continue; if (!shouldMute) { // unmute // ring and notifications volume should never be 0 when not silenced if (mStreamVolumeAlias[streamType] == AudioSystem.STREAM_RING) { synchronized (VolumeStreamState.class) { final VolumeStreamState vss = mStreamStates[streamType]; for (int i = 0; i < vss.mIndexMap.size(); i++) { int device = vss.mIndexMap.keyAt(i); int value = vss.mIndexMap.valueAt(i); if (value == 0) { vss.setIndex(10, device, TAG); } } // Persist volume for stream ring when it is changed here final int device = getDeviceForStream(streamType); sendMsg(mAudioHandler, MSG_PERSIST_VOLUME, SENDMSG_QUEUE, device, 0, mStreamStates[streamType], PERSIST_DELAY); } } mStreamStates[streamType].mute(false); mRingerAndZenModeMutedStreams &= ~(1 << streamType); } else { // mute mStreamStates[streamType].mute(true); mRingerAndZenModeMutedStreams |= (1 << streamType); } } } private boolean isAlarm(int streamType) { return streamType == AudioSystem.STREAM_ALARM; } private boolean isNotificationOrRinger(int streamType) { return streamType == AudioSystem.STREAM_NOTIFICATION || streamType == AudioSystem.STREAM_RING; } private boolean isMedia(int streamType) { return streamType == AudioSystem.STREAM_MUSIC; } private boolean isSystem(int streamType) { return streamType == AudioSystem.STREAM_SYSTEM; } private void setRingerModeInt(int ringerMode, boolean persist) { final boolean change; synchronized(mSettingsLock) { change = mRingerMode != ringerMode; mRingerMode = ringerMode; muteRingerModeStreams(); } // Post a persist ringer mode msg if (persist) { sendMsg(mAudioHandler, MSG_PERSIST_RINGER_MODE, SENDMSG_REPLACE, 0, 0, null, PERSIST_DELAY); } if (change) { // Send sticky broadcast broadcastRingerMode(AudioManager.INTERNAL_RINGER_MODE_CHANGED_ACTION, ringerMode); } } /*package*/ void postUpdateRingerModeServiceInt() { sendMsg(mAudioHandler, MSG_UPDATE_RINGER_MODE, SENDMSG_QUEUE, 0, 0, null, 0); } private void onUpdateRingerModeServiceInt() { setRingerModeInt(getRingerModeInternal(), false); } /** @see AudioManager#shouldVibrate(int) */ public boolean shouldVibrate(int vibrateType) { if (!mHasVibrator) return false; switch (getVibrateSetting(vibrateType)) { case AudioManager.VIBRATE_SETTING_ON: return getRingerModeExternal() != AudioManager.RINGER_MODE_SILENT; case AudioManager.VIBRATE_SETTING_ONLY_SILENT: return getRingerModeExternal() == AudioManager.RINGER_MODE_VIBRATE; case AudioManager.VIBRATE_SETTING_OFF: // return false, even for incoming calls return false; default: return false; } } /** @see AudioManager#getVibrateSetting(int) */ public int getVibrateSetting(int vibrateType) { if (!mHasVibrator) return AudioManager.VIBRATE_SETTING_OFF; return (mVibrateSetting >> (vibrateType * 2)) & 3; } /** @see AudioManager#setVibrateSetting(int, int) */ public void setVibrateSetting(int vibrateType, int vibrateSetting) { if (!mHasVibrator) return; mVibrateSetting = AudioSystem.getValueForVibrateSetting(mVibrateSetting, vibrateType, vibrateSetting); // Broadcast change broadcastVibrateSetting(vibrateType); } /** * Return the pid of the current audio mode owner * @return 0 if nobody owns the mode */ /*package*/ int getModeOwnerPid() { int modeOwnerPid = 0; try { modeOwnerPid = mSetModeDeathHandlers.get(0).getPid(); } catch (Exception e) { // nothing to do, modeOwnerPid is not modified } return modeOwnerPid; } private class SetModeDeathHandler implements IBinder.DeathRecipient { private IBinder mCb; // To be notified of client's death private int mPid; private int mMode = AudioSystem.MODE_NORMAL; // Current mode set by this client SetModeDeathHandler(IBinder cb, int pid) { mCb = cb; mPid = pid; } public void binderDied() { int oldModeOwnerPid = 0; int newModeOwnerPid = 0; synchronized (mDeviceBroker.mSetModeLock) { Log.w(TAG, "setMode() client died"); if (!mSetModeDeathHandlers.isEmpty()) { oldModeOwnerPid = mSetModeDeathHandlers.get(0).getPid(); } int index = mSetModeDeathHandlers.indexOf(this); if (index < 0) { Log.w(TAG, "unregistered setMode() client died"); } else { newModeOwnerPid = setModeInt(AudioSystem.MODE_NORMAL, mCb, mPid, TAG); } } // when entering RINGTONE, IN_CALL or IN_COMMUNICATION mode, clear all // SCO connections not started by the application changing the mode when pid changes if ((newModeOwnerPid != oldModeOwnerPid) && (newModeOwnerPid != 0)) { mDeviceBroker.postDisconnectBluetoothSco(newModeOwnerPid); } } public int getPid() { return mPid; } public void setMode(int mode) { mMode = mode; } public int getMode() { return mMode; } public IBinder getBinder() { return mCb; } } /** @see AudioManager#setMode(int) */ public void setMode(int mode, IBinder cb, String callingPackage) { if (DEBUG_MODE) { Log.v(TAG, "setMode(mode=" + mode + ", callingPackage=" + callingPackage + ")"); } if (!checkAudioSettingsPermission("setMode()")) { return; } if ( (mode == AudioSystem.MODE_IN_CALL) && (mContext.checkCallingOrSelfPermission( android.Manifest.permission.MODIFY_PHONE_STATE) != PackageManager.PERMISSION_GRANTED)) { Log.w(TAG, "MODIFY_PHONE_STATE Permission Denial: setMode(MODE_IN_CALL) from pid=" + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid()); return; } if (mode < AudioSystem.MODE_CURRENT || mode >= AudioSystem.NUM_MODES) { return; } int oldModeOwnerPid = 0; int newModeOwnerPid = 0; synchronized (mDeviceBroker.mSetModeLock) { if (!mSetModeDeathHandlers.isEmpty()) { oldModeOwnerPid = mSetModeDeathHandlers.get(0).getPid(); } if (mode == AudioSystem.MODE_CURRENT) { mode = mMode; } newModeOwnerPid = setModeInt(mode, cb, Binder.getCallingPid(), callingPackage); } // when entering RINGTONE, IN_CALL or IN_COMMUNICATION mode, clear all // SCO connections not started by the application changing the mode when pid changes if ((newModeOwnerPid != oldModeOwnerPid) && (newModeOwnerPid != 0)) { mDeviceBroker.postDisconnectBluetoothSco(newModeOwnerPid); } } // setModeInt() returns a valid PID if the audio mode was successfully set to // any mode other than NORMAL. @GuardedBy("mDeviceBroker.mSetModeLock") private int setModeInt(int mode, IBinder cb, int pid, String caller) { if (DEBUG_MODE) { Log.v(TAG, "setModeInt(mode=" + mode + ", pid=" + pid + ", caller=" + caller + ")"); } int newModeOwnerPid = 0; if (cb == null) { Log.e(TAG, "setModeInt() called with null binder"); return newModeOwnerPid; } SetModeDeathHandler hdlr = null; Iterator iter = mSetModeDeathHandlers.iterator(); while (iter.hasNext()) { SetModeDeathHandler h = (SetModeDeathHandler)iter.next(); if (h.getPid() == pid) { hdlr = h; // Remove from client list so that it is re-inserted at top of list iter.remove(); hdlr.getBinder().unlinkToDeath(hdlr, 0); break; } } final int oldMode = mMode; int status = AudioSystem.AUDIO_STATUS_OK; int actualMode; do { actualMode = mode; if (mode == AudioSystem.MODE_NORMAL) { // get new mode from client at top the list if any if (!mSetModeDeathHandlers.isEmpty()) { hdlr = mSetModeDeathHandlers.get(0); cb = hdlr.getBinder(); actualMode = hdlr.getMode(); if (DEBUG_MODE) { Log.w(TAG, " using mode=" + mode + " instead due to death hdlr at pid=" + hdlr.mPid); } } } else { if (hdlr == null) { hdlr = new SetModeDeathHandler(cb, pid); } // Register for client death notification try { cb.linkToDeath(hdlr, 0); } catch (RemoteException e) { // Client has died! Log.w(TAG, "setMode() could not link to "+cb+" binder death"); } // Last client to call setMode() is always at top of client list // as required by SetModeDeathHandler.binderDied() mSetModeDeathHandlers.add(0, hdlr); hdlr.setMode(mode); } if (actualMode != mMode) { final long identity = Binder.clearCallingIdentity(); status = AudioSystem.setPhoneState(actualMode); Binder.restoreCallingIdentity(identity); if (status == AudioSystem.AUDIO_STATUS_OK) { if (DEBUG_MODE) { Log.v(TAG, " mode successfully set to " + actualMode); } mMode = actualMode; } else { if (hdlr != null) { mSetModeDeathHandlers.remove(hdlr); cb.unlinkToDeath(hdlr, 0); } // force reading new top of mSetModeDeathHandlers stack if (DEBUG_MODE) { Log.w(TAG, " mode set to MODE_NORMAL after phoneState pb"); } mode = AudioSystem.MODE_NORMAL; } } else { status = AudioSystem.AUDIO_STATUS_OK; } } while (status != AudioSystem.AUDIO_STATUS_OK && !mSetModeDeathHandlers.isEmpty()); if (status == AudioSystem.AUDIO_STATUS_OK) { if (actualMode != AudioSystem.MODE_NORMAL) { if (mSetModeDeathHandlers.isEmpty()) { Log.e(TAG, "setMode() different from MODE_NORMAL with empty mode client stack"); } else { newModeOwnerPid = mSetModeDeathHandlers.get(0).getPid(); } } // Note: newModeOwnerPid is always 0 when actualMode is MODE_NORMAL mModeLogger.log( new PhoneStateEvent(caller, pid, mode, newModeOwnerPid, actualMode)); int streamType = getActiveStreamType(AudioManager.USE_DEFAULT_STREAM_TYPE); int device = getDeviceForStream(streamType); int index = mStreamStates[mStreamVolumeAlias[streamType]].getIndex(device); setStreamVolumeInt(mStreamVolumeAlias[streamType], index, device, true, caller); updateStreamVolumeAlias(true /*updateVolumes*/, caller); // change of mode may require volume to be re-applied on some devices updateAbsVolumeMultiModeDevices(oldMode, actualMode); } return newModeOwnerPid; } /** @see AudioManager#getMode() */ public int getMode() { return mMode; } //========================================================================================== // Sound Effects //========================================================================================== private static final String TAG_AUDIO_ASSETS = "audio_assets"; private static final String ATTR_VERSION = "version"; private static final String TAG_GROUP = "group"; private static final String ATTR_GROUP_NAME = "name"; private static final String TAG_ASSET = "asset"; private static final String ATTR_ASSET_ID = "id"; private static final String ATTR_ASSET_FILE = "file"; private static final String ASSET_FILE_VERSION = "1.0"; private static final String GROUP_TOUCH_SOUNDS = "touch_sounds"; private static final int SOUND_EFFECTS_LOAD_TIMEOUT_MS = 5000; class LoadSoundEffectReply { public int mStatus = 1; }; private void loadTouchSoundAssetDefaults() { SOUND_EFFECT_FILES.add("Effect_Tick.ogg"); for (int i = 0; i < AudioManager.NUM_SOUND_EFFECTS; i++) { SOUND_EFFECT_FILES_MAP[i][0] = 0; SOUND_EFFECT_FILES_MAP[i][1] = -1; } } private void loadTouchSoundAssets() { XmlResourceParser parser = null; // only load assets once. if (!SOUND_EFFECT_FILES.isEmpty()) { return; } loadTouchSoundAssetDefaults(); try { parser = mContext.getResources().getXml(com.android.internal.R.xml.audio_assets); XmlUtils.beginDocument(parser, TAG_AUDIO_ASSETS); String version = parser.getAttributeValue(null, ATTR_VERSION); boolean inTouchSoundsGroup = false; if (ASSET_FILE_VERSION.equals(version)) { while (true) { XmlUtils.nextElement(parser); String element = parser.getName(); if (element == null) { break; } if (element.equals(TAG_GROUP)) { String name = parser.getAttributeValue(null, ATTR_GROUP_NAME); if (GROUP_TOUCH_SOUNDS.equals(name)) { inTouchSoundsGroup = true; break; } } } while (inTouchSoundsGroup) { XmlUtils.nextElement(parser); String element = parser.getName(); if (element == null) { break; } if (element.equals(TAG_ASSET)) { String id = parser.getAttributeValue(null, ATTR_ASSET_ID); String file = parser.getAttributeValue(null, ATTR_ASSET_FILE); int fx; try { Field field = AudioManager.class.getField(id); fx = field.getInt(null); } catch (Exception e) { Log.w(TAG, "Invalid touch sound ID: "+id); continue; } int i = SOUND_EFFECT_FILES.indexOf(file); if (i == -1) { i = SOUND_EFFECT_FILES.size(); SOUND_EFFECT_FILES.add(file); } SOUND_EFFECT_FILES_MAP[fx][0] = i; } else { break; } } } } catch (Resources.NotFoundException e) { Log.w(TAG, "audio assets file not found", e); } catch (XmlPullParserException e) { Log.w(TAG, "XML parser exception reading touch sound assets", e); } catch (IOException e) { Log.w(TAG, "I/O exception reading touch sound assets", e); } finally { if (parser != null) { parser.close(); } } } /** @see AudioManager#playSoundEffect(int) */ public void playSoundEffect(int effectType) { playSoundEffectVolume(effectType, -1.0f); } /** @see AudioManager#playSoundEffect(int, float) */ public void playSoundEffectVolume(int effectType, float volume) { // do not try to play the sound effect if the system stream is muted if (isStreamMutedByRingerOrZenMode(STREAM_SYSTEM)) { return; } if (effectType >= AudioManager.NUM_SOUND_EFFECTS || effectType < 0) { Log.w(TAG, "AudioService effectType value " + effectType + " out of range"); return; } sendMsg(mAudioHandler, MSG_PLAY_SOUND_EFFECT, SENDMSG_QUEUE, effectType, (int) (volume * 1000), null, 0); } /** * Loads samples into the soundpool. * This method must be called at first when sound effects are enabled */ public boolean loadSoundEffects() { int attempts = 3; LoadSoundEffectReply reply = new LoadSoundEffectReply(); synchronized (reply) { sendMsg(mAudioHandler, MSG_LOAD_SOUND_EFFECTS, SENDMSG_QUEUE, 0, 0, reply, 0); while ((reply.mStatus == 1) && (attempts-- > 0)) { try { reply.wait(SOUND_EFFECTS_LOAD_TIMEOUT_MS); } catch (InterruptedException e) { Log.w(TAG, "loadSoundEffects Interrupted while waiting sound pool loaded."); } } } return (reply.mStatus == 0); } /** * Schedule loading samples into the soundpool. * This method can be overridden to schedule loading at a later time. */ protected void scheduleLoadSoundEffects() { sendMsg(mAudioHandler, MSG_LOAD_SOUND_EFFECTS, SENDMSG_QUEUE, 0, 0, null, 0); } /** * Unloads samples from the sound pool. * This method can be called to free some memory when * sound effects are disabled. */ public void unloadSoundEffects() { sendMsg(mAudioHandler, MSG_UNLOAD_SOUND_EFFECTS, SENDMSG_QUEUE, 0, 0, null, 0); } class SoundPoolListenerThread extends Thread { public SoundPoolListenerThread() { super("SoundPoolListenerThread"); } @Override public void run() { Looper.prepare(); mSoundPoolLooper = Looper.myLooper(); synchronized (mSoundEffectsLock) { if (mSoundPool != null) { mSoundPoolCallBack = new SoundPoolCallback(); mSoundPool.setOnLoadCompleteListener(mSoundPoolCallBack); } mSoundEffectsLock.notify(); } Looper.loop(); } } private final class SoundPoolCallback implements android.media.SoundPool.OnLoadCompleteListener { int mStatus = 1; // 1 means neither error nor last sample loaded yet List mSamples = new ArrayList(); public int status() { return mStatus; } public void setSamples(int[] samples) { for (int i = 0; i < samples.length; i++) { // do not wait ack for samples rejected upfront by SoundPool if (samples[i] > 0) { mSamples.add(samples[i]); } } } public void onLoadComplete(SoundPool soundPool, int sampleId, int status) { synchronized (mSoundEffectsLock) { int i = mSamples.indexOf(sampleId); if (i >= 0) { mSamples.remove(i); } if ((status != 0) || mSamples. isEmpty()) { mStatus = status; mSoundEffectsLock.notify(); } } } } /** @see AudioManager#reloadAudioSettings() */ public void reloadAudioSettings() { readAudioSettings(false /*userSwitch*/); } private void readAudioSettings(boolean userSwitch) { // restore ringer mode, ringer mode affected streams, mute affected streams and vibrate settings readPersistedSettings(); readUserRestrictions(); // restore volume settings int numStreamTypes = AudioSystem.getNumStreamTypes(); for (int streamType = 0; streamType < numStreamTypes; streamType++) { VolumeStreamState streamState = mStreamStates[streamType]; if (userSwitch && mStreamVolumeAlias[streamType] == AudioSystem.STREAM_MUSIC) { continue; } streamState.readSettings(); synchronized (VolumeStreamState.class) { // unmute stream that was muted but is not affect by mute anymore if (streamState.mIsMuted && ((!isStreamAffectedByMute(streamType) && !isStreamMutedByRingerOrZenMode(streamType)) || mUseFixedVolume)) { streamState.mIsMuted = false; } } } // apply new ringer mode before checking volume for alias streams so that streams // muted by ringer mode have the correct volume setRingerModeInt(getRingerModeInternal(), false); checkAllFixedVolumeDevices(); checkAllAliasStreamVolumes(); checkMuteAffectedStreams(); synchronized (mSafeMediaVolumeStateLock) { mMusicActiveMs = MathUtils.constrain(Settings.Secure.getIntForUser(mContentResolver, Settings.Secure.UNSAFE_VOLUME_MUSIC_ACTIVE_MS, 0, UserHandle.USER_CURRENT), 0, UNSAFE_VOLUME_MUSIC_ACTIVE_MS_MAX); if (mSafeMediaVolumeState == SAFE_MEDIA_VOLUME_ACTIVE) { enforceSafeMediaVolume(TAG); } } } /** @see AudioManager#setSpeakerphoneOn(boolean) */ public void setSpeakerphoneOn(boolean on){ if (!checkAudioSettingsPermission("setSpeakerphoneOn()")) { return; } if (mContext.checkCallingOrSelfPermission( android.Manifest.permission.MODIFY_PHONE_STATE) != PackageManager.PERMISSION_GRANTED) { synchronized (mSetModeDeathHandlers) { for (SetModeDeathHandler h : mSetModeDeathHandlers) { if (h.getMode() == AudioSystem.MODE_IN_CALL) { Log.w(TAG, "getMode is call, Permission Denial: setSpeakerphoneOn from pid=" + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid()); return; } } } } // for logging only final String eventSource = new StringBuilder("setSpeakerphoneOn(").append(on) .append(") from u/pid:").append(Binder.getCallingUid()).append("/") .append(Binder.getCallingPid()).toString(); final boolean stateChanged = mDeviceBroker.setSpeakerphoneOn(on, eventSource); if (stateChanged) { final long ident = Binder.clearCallingIdentity(); try { mContext.sendBroadcastAsUser( new Intent(AudioManager.ACTION_SPEAKERPHONE_STATE_CHANGED) .setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY), UserHandle.ALL); } finally { Binder.restoreCallingIdentity(ident); } } } /** @see AudioManager#isSpeakerphoneOn() */ public boolean isSpeakerphoneOn() { return mDeviceBroker.isSpeakerphoneOn(); } /** @see AudioManager#setBluetoothScoOn(boolean) */ public void setBluetoothScoOn(boolean on) { if (!checkAudioSettingsPermission("setBluetoothScoOn()")) { return; } // Only enable calls from system components if (UserHandle.getCallingAppId() >= FIRST_APPLICATION_UID) { mDeviceBroker.setBluetoothScoOnByApp(on); return; } // for logging only final String eventSource = new StringBuilder("setBluetoothScoOn(").append(on) .append(") from u/pid:").append(Binder.getCallingUid()).append("/") .append(Binder.getCallingPid()).toString(); mDeviceBroker.setBluetoothScoOn(on, eventSource); } /** @see AudioManager#isBluetoothScoOn() * Note that it doesn't report internal state, but state seen by apps (which may have * called setBluetoothScoOn() */ public boolean isBluetoothScoOn() { return mDeviceBroker.isBluetoothScoOnForApp(); } // TODO investigate internal users due to deprecation of SDK API /** @see AudioManager#setBluetoothA2dpOn(boolean) */ public void setBluetoothA2dpOn(boolean on) { // for logging only final String eventSource = new StringBuilder("setBluetoothA2dpOn(").append(on) .append(") from u/pid:").append(Binder.getCallingUid()).append("/") .append(Binder.getCallingPid()).toString(); mDeviceBroker.setBluetoothA2dpOn_Async(on, eventSource); } /** @see AudioManager#isBluetoothA2dpOn() */ public boolean isBluetoothA2dpOn() { return mDeviceBroker.isBluetoothA2dpOn(); } /** @see AudioManager#startBluetoothSco() */ public void startBluetoothSco(IBinder cb, int targetSdkVersion) { final int scoAudioMode = (targetSdkVersion < Build.VERSION_CODES.JELLY_BEAN_MR2) ? BtHelper.SCO_MODE_VIRTUAL_CALL : BtHelper.SCO_MODE_UNDEFINED; final String eventSource = new StringBuilder("startBluetoothSco()") .append(") from u/pid:").append(Binder.getCallingUid()).append("/") .append(Binder.getCallingPid()).toString(); startBluetoothScoInt(cb, scoAudioMode, eventSource); } /** @see AudioManager#startBluetoothScoVirtualCall() */ public void startBluetoothScoVirtualCall(IBinder cb) { final String eventSource = new StringBuilder("startBluetoothScoVirtualCall()") .append(") from u/pid:").append(Binder.getCallingUid()).append("/") .append(Binder.getCallingPid()).toString(); startBluetoothScoInt(cb, BtHelper.SCO_MODE_VIRTUAL_CALL, eventSource); } void startBluetoothScoInt(IBinder cb, int scoAudioMode, @NonNull String eventSource) { if (!checkAudioSettingsPermission("startBluetoothSco()") || !mSystemReady) { return; } synchronized (mDeviceBroker.mSetModeLock) { mDeviceBroker.startBluetoothScoForClient_Sync(cb, scoAudioMode, eventSource); } } /** @see AudioManager#stopBluetoothSco() */ public void stopBluetoothSco(IBinder cb){ if (!checkAudioSettingsPermission("stopBluetoothSco()") || !mSystemReady) { return; } final String eventSource = new StringBuilder("stopBluetoothSco()") .append(") from u/pid:").append(Binder.getCallingUid()).append("/") .append(Binder.getCallingPid()).toString(); synchronized (mDeviceBroker.mSetModeLock) { mDeviceBroker.stopBluetoothScoForClient_Sync(cb, eventSource); } } /*package*/ ContentResolver getContentResolver() { return mContentResolver; } private void onCheckMusicActive(String caller) { synchronized (mSafeMediaVolumeStateLock) { if (mSafeMediaVolumeState == SAFE_MEDIA_VOLUME_INACTIVE) { int device = getDeviceForStream(AudioSystem.STREAM_MUSIC); if ((device & mSafeMediaVolumeDevices) != 0) { sendMsg(mAudioHandler, MSG_CHECK_MUSIC_ACTIVE, SENDMSG_REPLACE, 0, 0, caller, MUSIC_ACTIVE_POLL_PERIOD_MS); int index = mStreamStates[AudioSystem.STREAM_MUSIC].getIndex(device); if (AudioSystem.isStreamActive(AudioSystem.STREAM_MUSIC, 0) && (index > safeMediaVolumeIndex(device))) { // Approximate cumulative active music time mMusicActiveMs += MUSIC_ACTIVE_POLL_PERIOD_MS; if (mMusicActiveMs > UNSAFE_VOLUME_MUSIC_ACTIVE_MS_MAX) { setSafeMediaVolumeEnabled(true, caller); mMusicActiveMs = 0; } saveMusicActiveMs(); } } } } } private void saveMusicActiveMs() { mAudioHandler.obtainMessage(MSG_PERSIST_MUSIC_ACTIVE_MS, mMusicActiveMs, 0).sendToTarget(); } private int getSafeUsbMediaVolumeIndex() { // determine UI volume index corresponding to the wanted safe gain in dBFS int min = MIN_STREAM_VOLUME[AudioSystem.STREAM_MUSIC]; int max = MAX_STREAM_VOLUME[AudioSystem.STREAM_MUSIC]; mSafeUsbMediaVolumeDbfs = mContext.getResources().getInteger( com.android.internal.R.integer.config_safe_media_volume_usb_mB) / 100.0f; while (Math.abs(max - min) > 1) { int index = (max + min) / 2; float gainDB = AudioSystem.getStreamVolumeDB( AudioSystem.STREAM_MUSIC, index, AudioSystem.DEVICE_OUT_USB_HEADSET); if (Float.isNaN(gainDB)) { //keep last min in case of read error break; } else if (gainDB == mSafeUsbMediaVolumeDbfs) { min = index; break; } else if (gainDB < mSafeUsbMediaVolumeDbfs) { min = index; } else { max = index; } } return min * 10; } private void onConfigureSafeVolume(boolean force, String caller) { synchronized (mSafeMediaVolumeStateLock) { int mcc = mContext.getResources().getConfiguration().mcc; if ((mMcc != mcc) || ((mMcc == 0) && force)) { mSafeMediaVolumeIndex = mContext.getResources().getInteger( com.android.internal.R.integer.config_safe_media_volume_index) * 10; mSafeUsbMediaVolumeIndex = getSafeUsbMediaVolumeIndex(); boolean safeMediaVolumeEnabled = SystemProperties.getBoolean("audio.safemedia.force", false) || mContext.getResources().getBoolean( com.android.internal.R.bool.config_safe_media_volume_enabled); boolean safeMediaVolumeBypass = SystemProperties.getBoolean("audio.safemedia.bypass", false); // The persisted state is either "disabled" or "active": this is the state applied // next time we boot and cannot be "inactive" int persistedState; if (safeMediaVolumeEnabled && !safeMediaVolumeBypass) { persistedState = SAFE_MEDIA_VOLUME_ACTIVE; // The state can already be "inactive" here if the user has forced it before // the 30 seconds timeout for forced configuration. In this case we don't reset // it to "active". if (mSafeMediaVolumeState != SAFE_MEDIA_VOLUME_INACTIVE) { if (mMusicActiveMs == 0) { mSafeMediaVolumeState = SAFE_MEDIA_VOLUME_ACTIVE; enforceSafeMediaVolume(caller); } else { // We have existing playback time recorded, already confirmed. mSafeMediaVolumeState = SAFE_MEDIA_VOLUME_INACTIVE; } } } else { persistedState = SAFE_MEDIA_VOLUME_DISABLED; mSafeMediaVolumeState = SAFE_MEDIA_VOLUME_DISABLED; } mMcc = mcc; sendMsg(mAudioHandler, MSG_PERSIST_SAFE_VOLUME_STATE, SENDMSG_QUEUE, persistedState, 0, null, 0); } } } /////////////////////////////////////////////////////////////////////////// // Internal methods /////////////////////////////////////////////////////////////////////////// /** * Checks if the adjustment should change ringer mode instead of just * adjusting volume. If so, this will set the proper ringer mode and volume * indices on the stream states. */ private int checkForRingerModeChange(int oldIndex, int direction, int step, boolean isMuted, String caller, int flags) { int result = FLAG_ADJUST_VOLUME; if (isPlatformTelevision() || mIsSingleVolume) { return result; } int ringerMode = getRingerModeInternal(); switch (ringerMode) { case RINGER_MODE_NORMAL: if (direction == AudioManager.ADJUST_LOWER) { if (mHasVibrator) { // "step" is the delta in internal index units corresponding to a // change of 1 in UI index units. // Because of rounding when rescaling from one stream index range to its alias // index range, we cannot simply test oldIndex == step: // (step <= oldIndex < 2 * step) is equivalent to: (old UI index == 1) if (step <= oldIndex && oldIndex < 2 * step) { ringerMode = RINGER_MODE_VIBRATE; mLoweredFromNormalToVibrateTime = SystemClock.uptimeMillis(); } } else { if (oldIndex == step && mVolumePolicy.volumeDownToEnterSilent) { ringerMode = RINGER_MODE_SILENT; } } } else if (mIsSingleVolume && (direction == AudioManager.ADJUST_TOGGLE_MUTE || direction == AudioManager.ADJUST_MUTE)) { if (mHasVibrator) { ringerMode = RINGER_MODE_VIBRATE; } else { ringerMode = RINGER_MODE_SILENT; } // Setting the ringer mode will toggle mute result &= ~FLAG_ADJUST_VOLUME; } break; case RINGER_MODE_VIBRATE: if (!mHasVibrator) { Log.e(TAG, "checkForRingerModeChange() current ringer mode is vibrate" + "but no vibrator is present"); break; } if ((direction == AudioManager.ADJUST_LOWER)) { // This is the case we were muted with the volume turned up if (mIsSingleVolume && oldIndex >= 2 * step && isMuted) { ringerMode = RINGER_MODE_NORMAL; } else if (mPrevVolDirection != AudioManager.ADJUST_LOWER) { if (mVolumePolicy.volumeDownToEnterSilent) { final long diff = SystemClock.uptimeMillis() - mLoweredFromNormalToVibrateTime; if (diff > mVolumePolicy.vibrateToSilentDebounce && mRingerModeDelegate.canVolumeDownEnterSilent()) { ringerMode = RINGER_MODE_SILENT; } } else { result |= AudioManager.FLAG_SHOW_VIBRATE_HINT; } } } else if (direction == AudioManager.ADJUST_RAISE || direction == AudioManager.ADJUST_TOGGLE_MUTE || direction == AudioManager.ADJUST_UNMUTE) { ringerMode = RINGER_MODE_NORMAL; } result &= ~FLAG_ADJUST_VOLUME; break; case RINGER_MODE_SILENT: if (mIsSingleVolume && direction == AudioManager.ADJUST_LOWER && oldIndex >= 2 * step && isMuted) { // This is the case we were muted with the volume turned up ringerMode = RINGER_MODE_NORMAL; } else if (direction == AudioManager.ADJUST_RAISE || direction == AudioManager.ADJUST_TOGGLE_MUTE || direction == AudioManager.ADJUST_UNMUTE) { if (!mVolumePolicy.volumeUpToExitSilent) { result |= AudioManager.FLAG_SHOW_SILENT_HINT; } else { if (mHasVibrator && direction == AudioManager.ADJUST_RAISE) { ringerMode = RINGER_MODE_VIBRATE; } else { // If we don't have a vibrator or they were toggling mute // go straight back to normal. ringerMode = RINGER_MODE_NORMAL; } } } result &= ~FLAG_ADJUST_VOLUME; break; default: Log.e(TAG, "checkForRingerModeChange() wrong ringer mode: "+ringerMode); break; } if (isAndroidNPlus(caller) && wouldToggleZenMode(ringerMode) && !mNm.isNotificationPolicyAccessGrantedForPackage(caller) && (flags & AudioManager.FLAG_FROM_KEY) == 0) { throw new SecurityException("Not allowed to change Do Not Disturb state"); } setRingerMode(ringerMode, TAG + ".checkForRingerModeChange", false /*external*/); mPrevVolDirection = direction; return result; } @Override public boolean isStreamAffectedByRingerMode(int streamType) { return (mRingerModeAffectedStreams & (1 << streamType)) != 0; } private boolean shouldZenMuteStream(int streamType) { if (mNm.getZenMode() != Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS) { return false; } NotificationManager.Policy zenPolicy = mNm.getConsolidatedNotificationPolicy(); final boolean muteAlarms = (zenPolicy.priorityCategories & NotificationManager.Policy.PRIORITY_CATEGORY_ALARMS) == 0; final boolean muteMedia = (zenPolicy.priorityCategories & NotificationManager.Policy.PRIORITY_CATEGORY_MEDIA) == 0; final boolean muteSystem = (zenPolicy.priorityCategories & NotificationManager.Policy.PRIORITY_CATEGORY_SYSTEM) == 0; final boolean muteNotificationAndRing = ZenModeConfig .areAllPriorityOnlyNotificationZenSoundsMuted( mNm.getConsolidatedNotificationPolicy()); return muteAlarms && isAlarm(streamType) || muteMedia && isMedia(streamType) || muteSystem && isSystem(streamType) || muteNotificationAndRing && isNotificationOrRinger(streamType); } private boolean isStreamMutedByRingerOrZenMode(int streamType) { return (mRingerAndZenModeMutedStreams & (1 << streamType)) != 0; } /** * DND total silence: media and alarms streams are tied to the muted ringer * {@link ZenModeHelper.RingerModeDelegate#getRingerModeAffectedStreams(int)} * DND alarms only: notification, ringer + system muted (by default tied to muted ringer mode) * DND priority only: alarms, media, system streams can be muted separate from ringer based on * zenPolicy (this method determines which streams) * @return true if changed, else false */ private boolean updateZenModeAffectedStreams() { int zenModeAffectedStreams = 0; if (mSystemReady && mNm.getZenMode() == Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS) { NotificationManager.Policy zenPolicy = mNm.getConsolidatedNotificationPolicy(); if ((zenPolicy.priorityCategories & NotificationManager.Policy.PRIORITY_CATEGORY_ALARMS) == 0) { zenModeAffectedStreams |= 1 << AudioManager.STREAM_ALARM; } if ((zenPolicy.priorityCategories & NotificationManager.Policy.PRIORITY_CATEGORY_MEDIA) == 0) { zenModeAffectedStreams |= 1 << AudioManager.STREAM_MUSIC; } if ((zenPolicy.priorityCategories & NotificationManager.Policy.PRIORITY_CATEGORY_SYSTEM) == 0) { zenModeAffectedStreams |= 1 << AudioManager.STREAM_SYSTEM; } } if (mZenModeAffectedStreams != zenModeAffectedStreams) { mZenModeAffectedStreams = zenModeAffectedStreams; return true; } return false; } @GuardedBy("mSettingsLock") private boolean updateRingerAndZenModeAffectedStreams() { boolean updatedZenModeAffectedStreams = updateZenModeAffectedStreams(); int ringerModeAffectedStreams = Settings.System.getIntForUser(mContentResolver, Settings.System.MODE_RINGER_STREAMS_AFFECTED, ((1 << AudioSystem.STREAM_RING)|(1 << AudioSystem.STREAM_NOTIFICATION)| (1 << AudioSystem.STREAM_SYSTEM)|(1 << AudioSystem.STREAM_SYSTEM_ENFORCED)), UserHandle.USER_CURRENT); if (mIsSingleVolume) { ringerModeAffectedStreams = 0; } else if (mRingerModeDelegate != null) { ringerModeAffectedStreams = mRingerModeDelegate .getRingerModeAffectedStreams(ringerModeAffectedStreams); } if (mCameraSoundForced) { ringerModeAffectedStreams &= ~(1 << AudioSystem.STREAM_SYSTEM_ENFORCED); } else { ringerModeAffectedStreams |= (1 << AudioSystem.STREAM_SYSTEM_ENFORCED); } if (mStreamVolumeAlias[AudioSystem.STREAM_DTMF] == AudioSystem.STREAM_RING) { ringerModeAffectedStreams |= (1 << AudioSystem.STREAM_DTMF); } else { ringerModeAffectedStreams &= ~(1 << AudioSystem.STREAM_DTMF); } if (ringerModeAffectedStreams != mRingerModeAffectedStreams) { Settings.System.putIntForUser(mContentResolver, Settings.System.MODE_RINGER_STREAMS_AFFECTED, ringerModeAffectedStreams, UserHandle.USER_CURRENT); mRingerModeAffectedStreams = ringerModeAffectedStreams; return true; } return updatedZenModeAffectedStreams; } @Override public boolean isStreamAffectedByMute(int streamType) { return (mMuteAffectedStreams & (1 << streamType)) != 0; } private void ensureValidDirection(int direction) { switch (direction) { case AudioManager.ADJUST_LOWER: case AudioManager.ADJUST_RAISE: case AudioManager.ADJUST_SAME: case AudioManager.ADJUST_MUTE: case AudioManager.ADJUST_UNMUTE: case AudioManager.ADJUST_TOGGLE_MUTE: break; default: throw new IllegalArgumentException("Bad direction " + direction); } } private void ensureValidStreamType(int streamType) { if (streamType < 0 || streamType >= mStreamStates.length) { throw new IllegalArgumentException("Bad stream type " + streamType); } } private boolean isMuteAdjust(int adjust) { return adjust == AudioManager.ADJUST_MUTE || adjust == AudioManager.ADJUST_UNMUTE || adjust == AudioManager.ADJUST_TOGGLE_MUTE; } /*package*/ boolean isInCommunication() { boolean IsInCall = false; TelecomManager telecomManager = (TelecomManager) mContext.getSystemService(Context.TELECOM_SERVICE); final long ident = Binder.clearCallingIdentity(); IsInCall = telecomManager.isInCall(); Binder.restoreCallingIdentity(ident); return (IsInCall || getMode() == AudioManager.MODE_IN_COMMUNICATION || getMode() == AudioManager.MODE_IN_CALL); } /** * For code clarity for getActiveStreamType(int) * @param delay_ms max time since last stream activity to consider * @return true if stream is active in streams handled by AudioFlinger now or * in the last "delay_ms" ms. */ private boolean wasStreamActiveRecently(int stream, int delay_ms) { return AudioSystem.isStreamActive(stream, delay_ms) || AudioSystem.isStreamActiveRemotely(stream, delay_ms); } private int getActiveStreamType(int suggestedStreamType) { if (mIsSingleVolume && suggestedStreamType == AudioManager.USE_DEFAULT_STREAM_TYPE) { return AudioSystem.STREAM_MUSIC; } switch (mPlatformType) { case AudioSystem.PLATFORM_VOICE: if (isInCommunication()) { if (AudioSystem.getForceUse(AudioSystem.FOR_COMMUNICATION) == AudioSystem.FORCE_BT_SCO) { // Log.v(TAG, "getActiveStreamType: Forcing STREAM_BLUETOOTH_SCO..."); return AudioSystem.STREAM_BLUETOOTH_SCO; } else { // Log.v(TAG, "getActiveStreamType: Forcing STREAM_VOICE_CALL..."); return AudioSystem.STREAM_VOICE_CALL; } } else if (suggestedStreamType == AudioManager.USE_DEFAULT_STREAM_TYPE) { if (wasStreamActiveRecently(AudioSystem.STREAM_RING, sStreamOverrideDelayMs)) { if (DEBUG_VOL) Log.v(TAG, "getActiveStreamType: Forcing STREAM_RING stream active"); return AudioSystem.STREAM_RING; } else if (wasStreamActiveRecently( AudioSystem.STREAM_NOTIFICATION, sStreamOverrideDelayMs)) { if (DEBUG_VOL) Log.v(TAG, "getActiveStreamType: Forcing STREAM_NOTIFICATION stream active"); return AudioSystem.STREAM_NOTIFICATION; } else { if (DEBUG_VOL) { Log.v(TAG, "getActiveStreamType: Forcing DEFAULT_VOL_STREAM_NO_PLAYBACK(" + DEFAULT_VOL_STREAM_NO_PLAYBACK + ") b/c default"); } return DEFAULT_VOL_STREAM_NO_PLAYBACK; } } else if ( wasStreamActiveRecently(AudioSystem.STREAM_NOTIFICATION, sStreamOverrideDelayMs)) { if (DEBUG_VOL) Log.v(TAG, "getActiveStreamType: Forcing STREAM_NOTIFICATION stream active"); return AudioSystem.STREAM_NOTIFICATION; } else if (wasStreamActiveRecently(AudioSystem.STREAM_RING, sStreamOverrideDelayMs)) { if (DEBUG_VOL) Log.v(TAG, "getActiveStreamType: Forcing STREAM_RING stream active"); return AudioSystem.STREAM_RING; } default: if (isInCommunication()) { if (AudioSystem.getForceUse(AudioSystem.FOR_COMMUNICATION) == AudioSystem.FORCE_BT_SCO) { if (DEBUG_VOL) Log.v(TAG, "getActiveStreamType: Forcing STREAM_BLUETOOTH_SCO"); return AudioSystem.STREAM_BLUETOOTH_SCO; } else { if (DEBUG_VOL) Log.v(TAG, "getActiveStreamType: Forcing STREAM_VOICE_CALL"); return AudioSystem.STREAM_VOICE_CALL; } } else if (AudioSystem.isStreamActive( AudioSystem.STREAM_NOTIFICATION, sStreamOverrideDelayMs)) { if (DEBUG_VOL) Log.v(TAG, "getActiveStreamType: Forcing STREAM_NOTIFICATION"); return AudioSystem.STREAM_NOTIFICATION; } else if (AudioSystem.isStreamActive( AudioSystem.STREAM_RING, sStreamOverrideDelayMs)) { if (DEBUG_VOL) Log.v(TAG, "getActiveStreamType: Forcing STREAM_RING"); return AudioSystem.STREAM_RING; } else if (suggestedStreamType == AudioManager.USE_DEFAULT_STREAM_TYPE) { if (AudioSystem.isStreamActive( AudioSystem.STREAM_NOTIFICATION, sStreamOverrideDelayMs)) { if (DEBUG_VOL) Log.v(TAG, "getActiveStreamType: Forcing STREAM_NOTIFICATION"); return AudioSystem.STREAM_NOTIFICATION; } else if (AudioSystem.isStreamActive( AudioSystem.STREAM_RING, sStreamOverrideDelayMs)) { if (DEBUG_VOL) Log.v(TAG, "getActiveStreamType: Forcing STREAM_RING"); return AudioSystem.STREAM_RING; } else { if (DEBUG_VOL) { Log.v(TAG, "getActiveStreamType: Forcing DEFAULT_VOL_STREAM_NO_PLAYBACK(" + DEFAULT_VOL_STREAM_NO_PLAYBACK + ") b/c default"); } return DEFAULT_VOL_STREAM_NO_PLAYBACK; } } break; } if (DEBUG_VOL) Log.v(TAG, "getActiveStreamType: Returning suggested type " + suggestedStreamType); return suggestedStreamType; } private void broadcastRingerMode(String action, int ringerMode) { // Send sticky broadcast Intent broadcast = new Intent(action); broadcast.putExtra(AudioManager.EXTRA_RINGER_MODE, ringerMode); broadcast.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT | Intent.FLAG_RECEIVER_REPLACE_PENDING); sendStickyBroadcastToAll(broadcast); } private void broadcastVibrateSetting(int vibrateType) { // Send broadcast if (mActivityManagerInternal.isSystemReady()) { Intent broadcast = new Intent(AudioManager.VIBRATE_SETTING_CHANGED_ACTION); broadcast.putExtra(AudioManager.EXTRA_VIBRATE_TYPE, vibrateType); broadcast.putExtra(AudioManager.EXTRA_VIBRATE_SETTING, getVibrateSetting(vibrateType)); sendBroadcastToAll(broadcast); } } // Message helper methods /** * Queue a message on the given handler's message queue, after acquiring the service wake lock. * Note that the wake lock needs to be released after the message has been handled. */ private void queueMsgUnderWakeLock(Handler handler, int msg, int arg1, int arg2, Object obj, int delay) { final long ident = Binder.clearCallingIdentity(); // Always acquire the wake lock as AudioService because it is released by the // message handler. mAudioEventWakeLock.acquire(); Binder.restoreCallingIdentity(ident); sendMsg(handler, msg, SENDMSG_QUEUE, arg1, arg2, obj, delay); } private static void sendMsg(Handler handler, int msg, int existingMsgPolicy, int arg1, int arg2, Object obj, int delay) { if (existingMsgPolicy == SENDMSG_REPLACE) { handler.removeMessages(msg); } else if (existingMsgPolicy == SENDMSG_NOOP && handler.hasMessages(msg)) { return; } final long time = SystemClock.uptimeMillis() + delay; handler.sendMessageAtTime(handler.obtainMessage(msg, arg1, arg2, obj), time); } boolean checkAudioSettingsPermission(String method) { if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.MODIFY_AUDIO_SETTINGS) == PackageManager.PERMISSION_GRANTED) { return true; } String msg = "Audio Settings Permission Denial: " + method + " from pid=" + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid(); Log.w(TAG, msg); return false; } /*package*/ int getDeviceForStream(int stream) { int device = getDevicesForStream(stream); if ((device & (device - 1)) != 0) { // Multiple device selection is either: // - speaker + one other device: give priority to speaker in this case. // - one A2DP device + another device: happens with duplicated output. In this case // retain the device on the A2DP output as the other must not correspond to an active // selection if not the speaker. // - HDMI-CEC system audio mode only output: give priority to available item in order. if ((device & AudioSystem.DEVICE_OUT_SPEAKER) != 0) { device = AudioSystem.DEVICE_OUT_SPEAKER; } else if ((device & AudioSystem.DEVICE_OUT_HDMI_ARC) != 0) { device = AudioSystem.DEVICE_OUT_HDMI_ARC; } else if ((device & AudioSystem.DEVICE_OUT_SPDIF) != 0) { device = AudioSystem.DEVICE_OUT_SPDIF; } else if ((device & AudioSystem.DEVICE_OUT_AUX_LINE) != 0) { device = AudioSystem.DEVICE_OUT_AUX_LINE; } else { device &= AudioSystem.DEVICE_OUT_ALL_A2DP; } } return device; } private int getDevicesForStream(int stream) { return getDevicesForStream(stream, true /*checkOthers*/); } private int getDevicesForStream(int stream, boolean checkOthers) { ensureValidStreamType(stream); synchronized (VolumeStreamState.class) { return mStreamStates[stream].observeDevicesForStream_syncVSS(checkOthers); } } private void observeDevicesForStreams(int skipStream) { synchronized (VolumeStreamState.class) { for (int stream = 0; stream < mStreamStates.length; stream++) { if (stream != skipStream) { mStreamStates[stream].observeDevicesForStream_syncVSS(false /*checkOthers*/); } } } } /*package*/ void postObserveDevicesForAllStreams() { sendMsg(mAudioHandler, MSG_OBSERVE_DEVICES_FOR_ALL_STREAMS, SENDMSG_QUEUE, 0 /*arg1*/, 0 /*arg2*/, null /*obj*/, 0 /*delay*/); } private void onObserveDevicesForAllStreams() { observeDevicesForStreams(-1); } /*package*/ static final int CONNECTION_STATE_DISCONNECTED = 0; /*package*/ static final int CONNECTION_STATE_CONNECTED = 1; /** * The states that can be used with AudioService.setWiredDeviceConnectionState() * Attention: those values differ from those in BluetoothProfile, follow annotations to * distinguish between @ConnectionState and @BtProfileConnectionState */ @IntDef({ CONNECTION_STATE_DISCONNECTED, CONNECTION_STATE_CONNECTED, }) @Retention(RetentionPolicy.SOURCE) public @interface ConnectionState {} /** * see AudioManager.setWiredDeviceConnectionState() */ public void setWiredDeviceConnectionState(int type, @ConnectionState int state, String address, String name, String caller) { if (state != CONNECTION_STATE_CONNECTED && state != CONNECTION_STATE_DISCONNECTED) { throw new IllegalArgumentException("Invalid state " + state); } mDeviceBroker.setWiredDeviceConnectionState(type, state, address, name, caller); } /** * @hide * The states that can be used with AudioService.setBluetoothHearingAidDeviceConnectionState() * and AudioService.setBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent() */ @IntDef({ BluetoothProfile.STATE_DISCONNECTED, BluetoothProfile.STATE_CONNECTED, }) @Retention(RetentionPolicy.SOURCE) public @interface BtProfileConnectionState {} /** * See AudioManager.setBluetoothHearingAidDeviceConnectionState() */ public void setBluetoothHearingAidDeviceConnectionState( @NonNull BluetoothDevice device, @BtProfileConnectionState int state, boolean suppressNoisyIntent, int musicDevice) { if (device == null) { throw new IllegalArgumentException("Illegal null device"); } if (state != BluetoothProfile.STATE_CONNECTED && state != BluetoothProfile.STATE_DISCONNECTED) { throw new IllegalArgumentException("Illegal BluetoothProfile state for device " + " (dis)connection, got " + state); } if (state == BluetoothProfile.STATE_CONNECTED) { mPlaybackMonitor.registerPlaybackCallback(mVoiceActivityMonitor, true); } else { mPlaybackMonitor.unregisterPlaybackCallback(mVoiceActivityMonitor); } mDeviceBroker.postBluetoothHearingAidDeviceConnectionState( device, state, suppressNoisyIntent, musicDevice, "AudioService"); } /** * See AudioManager.setBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent() */ public void setBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent( @NonNull BluetoothDevice device, @AudioService.BtProfileConnectionState int state, int profile, boolean suppressNoisyIntent, int a2dpVolume) { if (device == null) { throw new IllegalArgumentException("Illegal null device"); } if (state != BluetoothProfile.STATE_CONNECTED && state != BluetoothProfile.STATE_DISCONNECTED) { throw new IllegalArgumentException("Illegal BluetoothProfile state for device " + " (dis)connection, got " + state); } mDeviceBroker.postBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent(device, state, profile, suppressNoisyIntent, a2dpVolume); } /** * See AudioManager.handleBluetoothA2dpDeviceConfigChange() * @param device */ public void handleBluetoothA2dpDeviceConfigChange(BluetoothDevice device) { if (device == null) { throw new IllegalArgumentException("Illegal null device"); } mDeviceBroker.postBluetoothA2dpDeviceConfigChange(device); } private static final int DEVICE_MEDIA_UNMUTED_ON_PLUG = AudioSystem.DEVICE_OUT_WIRED_HEADSET | AudioSystem.DEVICE_OUT_WIRED_HEADPHONE | AudioSystem.DEVICE_OUT_LINE | AudioSystem.DEVICE_OUT_ALL_A2DP | AudioSystem.DEVICE_OUT_ALL_USB | AudioSystem.DEVICE_OUT_HDMI; /*package*/ void postAccessoryPlugMediaUnmute(int newDevice) { sendMsg(mAudioHandler, MSG_ACCESSORY_PLUG_MEDIA_UNMUTE, SENDMSG_QUEUE, newDevice, 0, null, 0); } private void onAccessoryPlugMediaUnmute(int newDevice) { if (DEBUG_VOL) { Log.i(TAG, String.format("onAccessoryPlugMediaUnmute newDevice=%d [%s]", newDevice, AudioSystem.getOutputDeviceName(newDevice))); } if (mNm.getZenMode() != Settings.Global.ZEN_MODE_NO_INTERRUPTIONS && (newDevice & DEVICE_MEDIA_UNMUTED_ON_PLUG) != 0 && mStreamStates[AudioSystem.STREAM_MUSIC].mIsMuted && mStreamStates[AudioSystem.STREAM_MUSIC].getIndex(newDevice) != 0 && (newDevice & AudioSystem.getDevicesForStream(AudioSystem.STREAM_MUSIC)) != 0) { if (DEBUG_VOL) { Log.i(TAG, String.format(" onAccessoryPlugMediaUnmute unmuting device=%d [%s]", newDevice, AudioSystem.getOutputDeviceName(newDevice))); } mStreamStates[AudioSystem.STREAM_MUSIC].mute(false); } } /** * See AudioManager.hasHapticChannels(Uri). */ public boolean hasHapticChannels(Uri uri) { MediaExtractor extractor = new MediaExtractor(); try { extractor.setDataSource(mContext, uri, null); for (int i = 0; i < extractor.getTrackCount(); i++) { MediaFormat format = extractor.getTrackFormat(i); if (format.containsKey(MediaFormat.KEY_HAPTIC_CHANNEL_COUNT) && format.getInteger(MediaFormat.KEY_HAPTIC_CHANNEL_COUNT) > 0) { return true; } } } catch (IOException e) { Log.e(TAG, "hasHapticChannels failure:" + e); } return false; } /////////////////////////////////////////////////////////////////////////// // Inner classes /////////////////////////////////////////////////////////////////////////// // NOTE: Locking order for synchronized objects related to volume or ringer mode management: // 1 mScoclient OR mSafeMediaVolumeState // 2 mSetModeLock // 3 mSettingsLock // 4 VolumeStreamState.class private class VolumeStreamState { private final int mStreamType; private int mIndexMin; private int mIndexMax; private boolean mIsMuted; private String mVolumeIndexSettingName; private int mObservedDevices; private final SparseIntArray mIndexMap = new SparseIntArray(8); private final Intent mVolumeChanged; private final Intent mStreamDevicesChanged; private VolumeStreamState(String settingName, int streamType) { mVolumeIndexSettingName = settingName; mStreamType = streamType; mIndexMin = MIN_STREAM_VOLUME[streamType] * 10; mIndexMax = MAX_STREAM_VOLUME[streamType] * 10; AudioSystem.initStreamVolume(streamType, mIndexMin / 10, mIndexMax / 10); readSettings(); mVolumeChanged = new Intent(AudioManager.VOLUME_CHANGED_ACTION); mVolumeChanged.putExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, mStreamType); mStreamDevicesChanged = new Intent(AudioManager.STREAM_DEVICES_CHANGED_ACTION); mStreamDevicesChanged.putExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, mStreamType); } public int observeDevicesForStream_syncVSS(boolean checkOthers) { final int devices = AudioSystem.getDevicesForStream(mStreamType); if (devices == mObservedDevices) { return devices; } final int prevDevices = mObservedDevices; mObservedDevices = devices; if (checkOthers) { // one stream's devices have changed, check the others observeDevicesForStreams(mStreamType); } // log base stream changes to the event log if (mStreamVolumeAlias[mStreamType] == mStreamType) { EventLogTags.writeStreamDevicesChanged(mStreamType, prevDevices, devices); } sendBroadcastToAll(mStreamDevicesChanged .putExtra(AudioManager.EXTRA_PREV_VOLUME_STREAM_DEVICES, prevDevices) .putExtra(AudioManager.EXTRA_VOLUME_STREAM_DEVICES, devices)); return devices; } public @Nullable String getSettingNameForDevice(int device) { if (!hasValidSettingsName()) { return null; } final String suffix = AudioSystem.getOutputDeviceName(device); if (suffix.isEmpty()) { return mVolumeIndexSettingName; } return mVolumeIndexSettingName + "_" + suffix; } private boolean hasValidSettingsName() { return (mVolumeIndexSettingName != null && !mVolumeIndexSettingName.isEmpty()); } public void readSettings() { synchronized (mSettingsLock) { synchronized (VolumeStreamState.class) { // force maximum volume on all streams if fixed volume property is set if (mUseFixedVolume) { mIndexMap.put(AudioSystem.DEVICE_OUT_DEFAULT, mIndexMax); return; } // do not read system stream volume from settings: this stream is always aliased // to another stream type and its volume is never persisted. Values in settings can // only be stale values if ((mStreamType == AudioSystem.STREAM_SYSTEM) || (mStreamType == AudioSystem.STREAM_SYSTEM_ENFORCED)) { int index = 10 * AudioSystem.DEFAULT_STREAM_VOLUME[mStreamType]; if (mCameraSoundForced) { index = mIndexMax; } mIndexMap.put(AudioSystem.DEVICE_OUT_DEFAULT, index); return; } } } synchronized (VolumeStreamState.class) { int remainingDevices = AudioSystem.DEVICE_OUT_ALL; for (int i = 0; remainingDevices != 0; i++) { int device = (1 << i); if ((device & remainingDevices) == 0) { continue; } remainingDevices &= ~device; // retrieve current volume for device // if no volume stored for current stream and device, use default volume if default // device, continue otherwise int defaultIndex = (device == AudioSystem.DEVICE_OUT_DEFAULT) ? AudioSystem.DEFAULT_STREAM_VOLUME[mStreamType] : -1; int index; if (!hasValidSettingsName()) { index = defaultIndex; } else { String name = getSettingNameForDevice(device); index = Settings.System.getIntForUser( mContentResolver, name, defaultIndex, UserHandle.USER_CURRENT); } if (index == -1) { continue; } mIndexMap.put(device, getValidIndex(10 * index)); } } } private int getAbsoluteVolumeIndex(int index) { /* Special handling for Bluetooth Absolute Volume scenario * If we send full audio gain, some accessories are too loud even at its lowest * volume. We are not able to enumerate all such accessories, so here is the * workaround from phone side. * Pre-scale volume at lowest volume steps 1 2 and 3. * For volume step 0, set audio gain to 0 as some accessories won't mute on their end. */ if (index == 0) { // 0% for volume 0 index = 0; } else if (index > 0 && index <= 3) { // Pre-scale for volume steps 1 2 and 3 index = (int) (mIndexMax * mPrescaleAbsoluteVolume[index - 1]) / 10; } else { // otherwise, full gain index = (mIndexMax + 5) / 10; } return index; } private void setStreamVolumeIndex(int index, int device) { // Only set audio policy BT SCO stream volume to 0 when the stream is actually muted. // This allows RX path muting by the audio HAL only when explicitly muted but not when // index is just set to 0 to repect BT requirements if (mStreamType == AudioSystem.STREAM_BLUETOOTH_SCO && index == 0 && !mIsMuted) { index = 1; } AudioSystem.setStreamVolumeIndexAS(mStreamType, index, device); } // must be called while synchronized VolumeStreamState.class /*package*/ void applyDeviceVolume_syncVSS(int device, boolean isAvrcpAbsVolSupported) { int index; if (mIsMuted) { index = 0; } else if ((device & AudioSystem.DEVICE_OUT_ALL_A2DP) != 0 && isAvrcpAbsVolSupported) { index = getAbsoluteVolumeIndex((getIndex(device) + 5)/10); } else if ((device & mFullVolumeDevices) != 0) { index = (mIndexMax + 5)/10; } else if ((device & AudioSystem.DEVICE_OUT_HEARING_AID) != 0) { index = (mIndexMax + 5)/10; } else { index = (getIndex(device) + 5)/10; } setStreamVolumeIndex(index, device); } public void applyAllVolumes() { final boolean isAvrcpAbsVolSupported = mDeviceBroker.isAvrcpAbsoluteVolumeSupported(); synchronized (VolumeStreamState.class) { // apply device specific volumes first int index; for (int i = 0; i < mIndexMap.size(); i++) { final int device = mIndexMap.keyAt(i); if (device != AudioSystem.DEVICE_OUT_DEFAULT) { if (mIsMuted) { index = 0; } else if ((device & AudioSystem.DEVICE_OUT_ALL_A2DP) != 0 && isAvrcpAbsVolSupported) { index = getAbsoluteVolumeIndex((getIndex(device) + 5)/10); } else if ((device & mFullVolumeDevices) != 0) { index = (mIndexMax + 5)/10; } else if ((device & AudioSystem.DEVICE_OUT_HEARING_AID) != 0) { index = (mIndexMax + 5)/10; } else { index = (mIndexMap.valueAt(i) + 5)/10; } setStreamVolumeIndex(index, device); } } // apply default volume last: by convention , default device volume will be used // by audio policy manager if no explicit volume is present for a given device type if (mIsMuted) { index = 0; } else { index = (getIndex(AudioSystem.DEVICE_OUT_DEFAULT) + 5)/10; } setStreamVolumeIndex(index, AudioSystem.DEVICE_OUT_DEFAULT); } } public boolean adjustIndex(int deltaIndex, int device, String caller) { return setIndex(getIndex(device) + deltaIndex, device, caller); } public boolean setIndex(int index, int device, String caller) { boolean changed; int oldIndex; synchronized (mSettingsLock) { synchronized (VolumeStreamState.class) { oldIndex = getIndex(device); index = getValidIndex(index); if ((mStreamType == AudioSystem.STREAM_SYSTEM_ENFORCED) && mCameraSoundForced) { index = mIndexMax; } mIndexMap.put(device, index); changed = oldIndex != index; // Apply change to all streams using this one as alias if: // - the index actually changed OR // - there is no volume index stored for this device on alias stream. // If changing volume of current device, also change volume of current // device on aliased stream final boolean isCurrentDevice = (device == getDeviceForStream(mStreamType)); final int numStreamTypes = AudioSystem.getNumStreamTypes(); for (int streamType = numStreamTypes - 1; streamType >= 0; streamType--) { final VolumeStreamState aliasStreamState = mStreamStates[streamType]; if (streamType != mStreamType && mStreamVolumeAlias[streamType] == mStreamType && (changed || !aliasStreamState.hasIndexForDevice(device))) { final int scaledIndex = rescaleIndex(index, mStreamType, streamType); aliasStreamState.setIndex(scaledIndex, device, caller); if (isCurrentDevice) { aliasStreamState.setIndex(scaledIndex, getDeviceForStream(streamType), caller); } } } // Mirror changes in SPEAKER ringtone volume on SCO when if (changed && mStreamType == AudioSystem.STREAM_RING && device == AudioSystem.DEVICE_OUT_SPEAKER) { for (int i = 0; i < mIndexMap.size(); i++) { int otherDevice = mIndexMap.keyAt(i); if ((otherDevice & AudioSystem.DEVICE_OUT_ALL_SCO) != 0) { mIndexMap.put(otherDevice, index); } } } } } if (changed) { oldIndex = (oldIndex + 5) / 10; index = (index + 5) / 10; // log base stream changes to the event log if (mStreamVolumeAlias[mStreamType] == mStreamType) { if (caller == null) { Log.w(TAG, "No caller for volume_changed event", new Throwable()); } EventLogTags.writeVolumeChanged(mStreamType, oldIndex, index, mIndexMax / 10, caller); } // fire changed intents for all streams mVolumeChanged.putExtra(AudioManager.EXTRA_VOLUME_STREAM_VALUE, index); mVolumeChanged.putExtra(AudioManager.EXTRA_PREV_VOLUME_STREAM_VALUE, oldIndex); mVolumeChanged.putExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE_ALIAS, mStreamVolumeAlias[mStreamType]); sendBroadcastToAll(mVolumeChanged); } return changed; } public int getIndex(int device) { synchronized (VolumeStreamState.class) { int index = mIndexMap.get(device, -1); if (index == -1) { // there is always an entry for AudioSystem.DEVICE_OUT_DEFAULT index = mIndexMap.get(AudioSystem.DEVICE_OUT_DEFAULT); } return index; } } public boolean hasIndexForDevice(int device) { synchronized (VolumeStreamState.class) { return (mIndexMap.get(device, -1) != -1); } } public int getMaxIndex() { return mIndexMax; } public int getMinIndex() { return mIndexMin; } /** * Copies all device/index pairs from the given VolumeStreamState after initializing * them with the volume for DEVICE_OUT_DEFAULT. No-op if the source VolumeStreamState * has the same stream type as this instance. * @param srcStream * @param caller */ // must be sync'd on mSettingsLock before VolumeStreamState.class @GuardedBy("VolumeStreamState.class") public void setAllIndexes(VolumeStreamState srcStream, String caller) { if (mStreamType == srcStream.mStreamType) { return; } int srcStreamType = srcStream.getStreamType(); // apply default device volume from source stream to all devices first in case // some devices are present in this stream state but not in source stream state int index = srcStream.getIndex(AudioSystem.DEVICE_OUT_DEFAULT); index = rescaleIndex(index, srcStreamType, mStreamType); for (int i = 0; i < mIndexMap.size(); i++) { mIndexMap.put(mIndexMap.keyAt(i), index); } // Now apply actual volume for devices in source stream state SparseIntArray srcMap = srcStream.mIndexMap; for (int i = 0; i < srcMap.size(); i++) { int device = srcMap.keyAt(i); index = srcMap.valueAt(i); index = rescaleIndex(index, srcStreamType, mStreamType); setIndex(index, device, caller); } } // must be sync'd on mSettingsLock before VolumeStreamState.class @GuardedBy("VolumeStreamState.class") public void setAllIndexesToMax() { for (int i = 0; i < mIndexMap.size(); i++) { mIndexMap.put(mIndexMap.keyAt(i), mIndexMax); } } public void mute(boolean state) { boolean changed = false; synchronized (VolumeStreamState.class) { if (state != mIsMuted) { changed = true; mIsMuted = state; // Set the new mute volume. This propagates the values to // the audio system, otherwise the volume won't be changed // at the lower level. sendMsg(mAudioHandler, MSG_SET_ALL_VOLUMES, SENDMSG_QUEUE, 0, 0, this, 0); } } if (changed) { // Stream mute changed, fire the intent. Intent intent = new Intent(AudioManager.STREAM_MUTE_CHANGED_ACTION); intent.putExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, mStreamType); intent.putExtra(AudioManager.EXTRA_STREAM_VOLUME_MUTED, state); sendBroadcastToAll(intent); } } public int getStreamType() { return mStreamType; } public void checkFixedVolumeDevices() { final boolean isAvrcpAbsVolSupported = mDeviceBroker.isAvrcpAbsoluteVolumeSupported(); synchronized (VolumeStreamState.class) { // ignore settings for fixed volume devices: volume should always be at max or 0 if (mStreamVolumeAlias[mStreamType] == AudioSystem.STREAM_MUSIC) { for (int i = 0; i < mIndexMap.size(); i++) { int device = mIndexMap.keyAt(i); int index = mIndexMap.valueAt(i); if (((device & mFullVolumeDevices) != 0) || (((device & mFixedVolumeDevices) != 0) && index != 0)) { mIndexMap.put(device, mIndexMax); } applyDeviceVolume_syncVSS(device, isAvrcpAbsVolSupported); } } } } private int getValidIndex(int index) { if (index < mIndexMin) { return mIndexMin; } else if (mUseFixedVolume || index > mIndexMax) { return mIndexMax; } return index; } private void dump(PrintWriter pw) { pw.print(" Muted: "); pw.println(mIsMuted); pw.print(" Min: "); pw.println((mIndexMin + 5) / 10); pw.print(" Max: "); pw.println((mIndexMax + 5) / 10); pw.print(" streamVolume:"); pw.println(getStreamVolume(mStreamType)); pw.print(" Current: "); for (int i = 0; i < mIndexMap.size(); i++) { if (i > 0) { pw.print(", "); } final int device = mIndexMap.keyAt(i); pw.print(Integer.toHexString(device)); final String deviceName = device == AudioSystem.DEVICE_OUT_DEFAULT ? "default" : AudioSystem.getOutputDeviceName(device); if (!deviceName.isEmpty()) { pw.print(" ("); pw.print(deviceName); pw.print(")"); } pw.print(": "); final int index = (mIndexMap.valueAt(i) + 5) / 10; pw.print(index); } pw.println(); pw.print(" Devices: "); final int devices = getDevicesForStream(mStreamType); int device, i = 0, n = 0; // iterate all devices from 1 to DEVICE_OUT_DEFAULT exclusive // (the default device is not returned by getDevicesForStream) while ((device = 1 << i) != AudioSystem.DEVICE_OUT_DEFAULT) { if ((devices & device) != 0) { if (n++ > 0) { pw.print(", "); } pw.print(AudioSystem.getOutputDeviceName(device)); } i++; } } } /** Thread that handles native AudioSystem control. */ private class AudioSystemThread extends Thread { AudioSystemThread() { super("AudioService"); } @Override public void run() { // Set this thread up so the handler will work on it Looper.prepare(); synchronized(AudioService.this) { mAudioHandler = new AudioHandler(); // Notify that the handler has been created AudioService.this.notify(); } // Listen for volume change requests that are set by VolumePanel Looper.loop(); } } private static final class DeviceVolumeUpdate { final int mStreamType; final int mDevice; final @NonNull String mCaller; private static final int NO_NEW_INDEX = -2049; private final int mVssVolIndex; // Constructor with volume index, meant to cause this volume to be set and applied for the // given stream type on the given device DeviceVolumeUpdate(int streamType, int vssVolIndex, int device, @NonNull String caller) { mStreamType = streamType; mVssVolIndex = vssVolIndex; mDevice = device; mCaller = caller; } // Constructor with no volume index, meant to cause re-apply of volume for the given // stream type on the given device DeviceVolumeUpdate(int streamType, int device, @NonNull String caller) { mStreamType = streamType; mVssVolIndex = NO_NEW_INDEX; mDevice = device; mCaller = caller; } boolean hasVolumeIndex() { return mVssVolIndex != NO_NEW_INDEX; } int getVolumeIndex() throws IllegalStateException { Preconditions.checkState(mVssVolIndex != NO_NEW_INDEX); return mVssVolIndex; } } /*package*/ void postSetVolumeIndexOnDevice(int streamType, int vssVolIndex, int device, String caller) { sendMsg(mAudioHandler, MSG_SET_DEVICE_STREAM_VOLUME, SENDMSG_QUEUE, 0 /*arg1*/, 0 /*arg2*/, new DeviceVolumeUpdate(streamType, vssVolIndex, device, caller), 0 /*delay*/); } /*package*/ void postApplyVolumeOnDevice(int streamType, int device, @NonNull String caller) { sendMsg(mAudioHandler, MSG_SET_DEVICE_STREAM_VOLUME, SENDMSG_QUEUE, 0 /*arg1*/, 0 /*arg2*/, new DeviceVolumeUpdate(streamType, device, caller), 0 /*delay*/); } private void onSetVolumeIndexOnDevice(@NonNull DeviceVolumeUpdate update) { final VolumeStreamState streamState = mStreamStates[update.mStreamType]; if (update.hasVolumeIndex()) { final int index = update.getVolumeIndex(); streamState.setIndex(index, update.mDevice, update.mCaller); sVolumeLogger.log(new AudioEventLogger.StringEvent(update.mCaller + " dev:0x" + Integer.toHexString(update.mDevice) + " volIdx:" + index)); } else { sVolumeLogger.log(new AudioEventLogger.StringEvent(update.mCaller + " update vol on dev:0x" + Integer.toHexString(update.mDevice))); } setDeviceVolume(streamState, update.mDevice); } /*package*/ void setDeviceVolume(VolumeStreamState streamState, int device) { final boolean isAvrcpAbsVolSupported = mDeviceBroker.isAvrcpAbsoluteVolumeSupported(); synchronized (VolumeStreamState.class) { // Apply volume streamState.applyDeviceVolume_syncVSS(device, isAvrcpAbsVolSupported); // Apply change to all streams using this one as alias int numStreamTypes = AudioSystem.getNumStreamTypes(); for (int streamType = numStreamTypes - 1; streamType >= 0; streamType--) { if (streamType != streamState.mStreamType && mStreamVolumeAlias[streamType] == streamState.mStreamType) { // Make sure volume is also maxed out on A2DP device for aliased stream // that may have a different device selected int streamDevice = getDeviceForStream(streamType); if ((device != streamDevice) && isAvrcpAbsVolSupported && ((device & AudioSystem.DEVICE_OUT_ALL_A2DP) != 0)) { mStreamStates[streamType].applyDeviceVolume_syncVSS(device, isAvrcpAbsVolSupported); } mStreamStates[streamType].applyDeviceVolume_syncVSS(streamDevice, isAvrcpAbsVolSupported); } } } // Post a persist volume msg sendMsg(mAudioHandler, MSG_PERSIST_VOLUME, SENDMSG_QUEUE, device, 0, streamState, PERSIST_DELAY); } /** Handles internal volume messages in separate volume thread. */ private class AudioHandler extends Handler { private void setAllVolumes(VolumeStreamState streamState) { // Apply volume streamState.applyAllVolumes(); // Apply change to all streams using this one as alias int numStreamTypes = AudioSystem.getNumStreamTypes(); for (int streamType = numStreamTypes - 1; streamType >= 0; streamType--) { if (streamType != streamState.mStreamType && mStreamVolumeAlias[streamType] == streamState.mStreamType) { mStreamStates[streamType].applyAllVolumes(); } } } private void persistVolume(VolumeStreamState streamState, int device) { if (mUseFixedVolume) { return; } if (mIsSingleVolume && (streamState.mStreamType != AudioSystem.STREAM_MUSIC)) { return; } if (streamState.hasValidSettingsName()) { System.putIntForUser(mContentResolver, streamState.getSettingNameForDevice(device), (streamState.getIndex(device) + 5)/ 10, UserHandle.USER_CURRENT); } } private void persistRingerMode(int ringerMode) { if (mUseFixedVolume) { return; } Settings.Global.putInt(mContentResolver, Settings.Global.MODE_RINGER, ringerMode); } private String getSoundEffectFilePath(int effectType) { String filePath = Environment.getProductDirectory() + SOUND_EFFECTS_PATH + SOUND_EFFECT_FILES.get(SOUND_EFFECT_FILES_MAP[effectType][0]); if (!new File(filePath).isFile()) { filePath = Environment.getRootDirectory() + SOUND_EFFECTS_PATH + SOUND_EFFECT_FILES.get(SOUND_EFFECT_FILES_MAP[effectType][0]); } return filePath; } private boolean onLoadSoundEffects() { int status; synchronized (mSoundEffectsLock) { if (!mSystemReady) { Log.w(TAG, "onLoadSoundEffects() called before boot complete"); return false; } if (mSoundPool != null) { return true; } loadTouchSoundAssets(); mSoundPool = new SoundPool.Builder() .setMaxStreams(NUM_SOUNDPOOL_CHANNELS) .setAudioAttributes(new AudioAttributes.Builder() .setUsage(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION) .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION) .build()) .build(); mSoundPoolCallBack = null; mSoundPoolListenerThread = new SoundPoolListenerThread(); mSoundPoolListenerThread.start(); int attempts = 3; while ((mSoundPoolCallBack == null) && (attempts-- > 0)) { try { // Wait for mSoundPoolCallBack to be set by the other thread mSoundEffectsLock.wait(SOUND_EFFECTS_LOAD_TIMEOUT_MS); } catch (InterruptedException e) { Log.w(TAG, "Interrupted while waiting sound pool listener thread."); } } if (mSoundPoolCallBack == null) { Log.w(TAG, "onLoadSoundEffects() SoundPool listener or thread creation error"); if (mSoundPoolLooper != null) { mSoundPoolLooper.quit(); mSoundPoolLooper = null; } mSoundPoolListenerThread = null; mSoundPool.release(); mSoundPool = null; return false; } /* * poolId table: The value -1 in this table indicates that corresponding * file (same index in SOUND_EFFECT_FILES[] has not been loaded. * Once loaded, the value in poolId is the sample ID and the same * sample can be reused for another effect using the same file. */ int[] poolId = new int[SOUND_EFFECT_FILES.size()]; for (int fileIdx = 0; fileIdx < SOUND_EFFECT_FILES.size(); fileIdx++) { poolId[fileIdx] = -1; } /* * Effects whose value in SOUND_EFFECT_FILES_MAP[effect][1] is -1 must be loaded. * If load succeeds, value in SOUND_EFFECT_FILES_MAP[effect][1] is > 0: * this indicates we have a valid sample loaded for this effect. */ int numSamples = 0; for (int effect = 0; effect < AudioManager.NUM_SOUND_EFFECTS; effect++) { // Do not load sample if this effect uses the MediaPlayer if (SOUND_EFFECT_FILES_MAP[effect][1] == 0) { continue; } if (poolId[SOUND_EFFECT_FILES_MAP[effect][0]] == -1) { String filePath = getSoundEffectFilePath(effect); int sampleId = mSoundPool.load(filePath, 0); if (sampleId <= 0) { Log.w(TAG, "Soundpool could not load file: "+filePath); } else { SOUND_EFFECT_FILES_MAP[effect][1] = sampleId; poolId[SOUND_EFFECT_FILES_MAP[effect][0]] = sampleId; numSamples++; } } else { SOUND_EFFECT_FILES_MAP[effect][1] = poolId[SOUND_EFFECT_FILES_MAP[effect][0]]; } } // wait for all samples to be loaded if (numSamples > 0) { mSoundPoolCallBack.setSamples(poolId); attempts = 3; status = 1; while ((status == 1) && (attempts-- > 0)) { try { mSoundEffectsLock.wait(SOUND_EFFECTS_LOAD_TIMEOUT_MS); status = mSoundPoolCallBack.status(); } catch (InterruptedException e) { Log.w(TAG, "Interrupted while waiting sound pool callback."); } } } else { status = -1; } if (mSoundPoolLooper != null) { mSoundPoolLooper.quit(); mSoundPoolLooper = null; } mSoundPoolListenerThread = null; if (status != 0) { Log.w(TAG, "onLoadSoundEffects(), Error "+status+ " while loading samples"); for (int effect = 0; effect < AudioManager.NUM_SOUND_EFFECTS; effect++) { if (SOUND_EFFECT_FILES_MAP[effect][1] > 0) { SOUND_EFFECT_FILES_MAP[effect][1] = -1; } } mSoundPool.release(); mSoundPool = null; } } return (status == 0); } /** * Unloads samples from the sound pool. * This method can be called to free some memory when * sound effects are disabled. */ private void onUnloadSoundEffects() { synchronized (mSoundEffectsLock) { if (mSoundPool == null) { return; } int[] poolId = new int[SOUND_EFFECT_FILES.size()]; for (int fileIdx = 0; fileIdx < SOUND_EFFECT_FILES.size(); fileIdx++) { poolId[fileIdx] = 0; } for (int effect = 0; effect < AudioManager.NUM_SOUND_EFFECTS; effect++) { if (SOUND_EFFECT_FILES_MAP[effect][1] <= 0) { continue; } if (poolId[SOUND_EFFECT_FILES_MAP[effect][0]] == 0) { mSoundPool.unload(SOUND_EFFECT_FILES_MAP[effect][1]); SOUND_EFFECT_FILES_MAP[effect][1] = -1; poolId[SOUND_EFFECT_FILES_MAP[effect][0]] = -1; } } mSoundPool.release(); mSoundPool = null; } } private void onPlaySoundEffect(int effectType, int volume) { synchronized (mSoundEffectsLock) { onLoadSoundEffects(); if (mSoundPool == null) { return; } float volFloat; // use default if volume is not specified by caller if (volume < 0) { volFloat = (float)Math.pow(10, (float)sSoundEffectVolumeDb/20); } else { volFloat = volume / 1000.0f; } if (SOUND_EFFECT_FILES_MAP[effectType][1] > 0) { mSoundPool.play(SOUND_EFFECT_FILES_MAP[effectType][1], volFloat, volFloat, 0, 0, 1.0f); } else { MediaPlayer mediaPlayer = new MediaPlayer(); try { String filePath = getSoundEffectFilePath(effectType); mediaPlayer.setDataSource(filePath); mediaPlayer.setAudioStreamType(AudioSystem.STREAM_SYSTEM); mediaPlayer.prepare(); mediaPlayer.setVolume(volFloat); mediaPlayer.setOnCompletionListener(new OnCompletionListener() { public void onCompletion(MediaPlayer mp) { cleanupPlayer(mp); } }); mediaPlayer.setOnErrorListener(new OnErrorListener() { public boolean onError(MediaPlayer mp, int what, int extra) { cleanupPlayer(mp); return true; } }); mediaPlayer.start(); } catch (IOException ex) { Log.w(TAG, "MediaPlayer IOException: "+ex); } catch (IllegalArgumentException ex) { Log.w(TAG, "MediaPlayer IllegalArgumentException: "+ex); } catch (IllegalStateException ex) { Log.w(TAG, "MediaPlayer IllegalStateException: "+ex); } } } } private void cleanupPlayer(MediaPlayer mp) { if (mp != null) { try { mp.stop(); mp.release(); } catch (IllegalStateException ex) { Log.w(TAG, "MediaPlayer IllegalStateException: "+ex); } } } private void onPersistSafeVolumeState(int state) { Settings.Global.putInt(mContentResolver, Settings.Global.AUDIO_SAFE_VOLUME_STATE, state); } private void onNotifyVolumeEvent(@NonNull IAudioPolicyCallback apc, @AudioManager.VolumeAdjustment int direction) { try { apc.notifyVolumeAdjust(direction); } catch(Exception e) { // nothing we can do about this. Do not log error, too much potential for spam } } @Override public void handleMessage(Message msg) { switch (msg.what) { case MSG_SET_DEVICE_VOLUME: setDeviceVolume((VolumeStreamState) msg.obj, msg.arg1); break; case MSG_SET_ALL_VOLUMES: setAllVolumes((VolumeStreamState) msg.obj); break; case MSG_PERSIST_VOLUME: persistVolume((VolumeStreamState) msg.obj, msg.arg1); break; case MSG_PERSIST_RINGER_MODE: // note that the value persisted is the current ringer mode, not the // value of ringer mode as of the time the request was made to persist persistRingerMode(getRingerModeInternal()); break; case MSG_AUDIO_SERVER_DIED: onAudioServerDied(); break; case MSG_DISPATCH_AUDIO_SERVER_STATE: onDispatchAudioServerStateChange(msg.arg1 == 1); break; case MSG_UNLOAD_SOUND_EFFECTS: onUnloadSoundEffects(); break; case MSG_LOAD_SOUND_EFFECTS: //FIXME: onLoadSoundEffects() should be executed in a separate thread as it // can take several dozens of milliseconds to complete boolean loaded = onLoadSoundEffects(); if (msg.obj != null) { LoadSoundEffectReply reply = (LoadSoundEffectReply)msg.obj; synchronized (reply) { reply.mStatus = loaded ? 0 : -1; reply.notify(); } } break; case MSG_PLAY_SOUND_EFFECT: onPlaySoundEffect(msg.arg1, msg.arg2); break; case MSG_SET_FORCE_USE: { final String eventSource = (String) msg.obj; final int useCase = msg.arg1; final int config = msg.arg2; if (useCase == AudioSystem.FOR_MEDIA) { Log.wtf(TAG, "Invalid force use FOR_MEDIA in AudioService from " + eventSource); break; } sForceUseLogger.log( new AudioServiceEvents.ForceUseEvent(useCase, config, eventSource)); AudioSystem.setForceUse(useCase, config); } break; case MSG_DISABLE_AUDIO_FOR_UID: mPlaybackMonitor.disableAudioForUid( msg.arg1 == 1 /* disable */, msg.arg2 /* uid */); mAudioEventWakeLock.release(); break; case MSG_CHECK_MUSIC_ACTIVE: onCheckMusicActive((String) msg.obj); break; case MSG_CONFIGURE_SAFE_MEDIA_VOLUME_FORCED: case MSG_CONFIGURE_SAFE_MEDIA_VOLUME: onConfigureSafeVolume((msg.what == MSG_CONFIGURE_SAFE_MEDIA_VOLUME_FORCED), (String) msg.obj); break; case MSG_PERSIST_SAFE_VOLUME_STATE: onPersistSafeVolumeState(msg.arg1); break; case MSG_SYSTEM_READY: onSystemReady(); break; case MSG_INDICATE_SYSTEM_READY: onIndicateSystemReady(); break; case MSG_ACCESSORY_PLUG_MEDIA_UNMUTE: onAccessoryPlugMediaUnmute(msg.arg1); break; case MSG_PERSIST_MUSIC_ACTIVE_MS: final int musicActiveMs = msg.arg1; Settings.Secure.putIntForUser(mContentResolver, Settings.Secure.UNSAFE_VOLUME_MUSIC_ACTIVE_MS, musicActiveMs, UserHandle.USER_CURRENT); break; case MSG_UNMUTE_STREAM: onUnmuteStream(msg.arg1, msg.arg2); break; case MSG_DYN_POLICY_MIX_STATE_UPDATE: onDynPolicyMixStateUpdate((String) msg.obj, msg.arg1); break; case MSG_NOTIFY_VOL_EVENT: onNotifyVolumeEvent((IAudioPolicyCallback) msg.obj, msg.arg1); break; case MSG_ENABLE_SURROUND_FORMATS: onEnableSurroundFormats((ArrayList) msg.obj); break; case MSG_UPDATE_RINGER_MODE: onUpdateRingerModeServiceInt(); break; case MSG_SET_DEVICE_STREAM_VOLUME: onSetVolumeIndexOnDevice((DeviceVolumeUpdate) msg.obj); break; case MSG_OBSERVE_DEVICES_FOR_ALL_STREAMS: onObserveDevicesForAllStreams(); break; case MSG_HDMI_VOLUME_CHECK: onCheckVolumeCecOnHdmiConnection(msg.arg1, (String) msg.obj); break; case MSG_PLAYBACK_CONFIG_CHANGE: onPlaybackConfigChange((List) msg.obj); break; } } } private class SettingsObserver extends ContentObserver { SettingsObserver() { super(new Handler()); mContentResolver.registerContentObserver(Settings.Global.getUriFor( Settings.Global.ZEN_MODE), false, this); mContentResolver.registerContentObserver(Settings.Global.getUriFor( Settings.Global.ZEN_MODE_CONFIG_ETAG), false, this); mContentResolver.registerContentObserver(Settings.System.getUriFor( Settings.System.MODE_RINGER_STREAMS_AFFECTED), false, this); mContentResolver.registerContentObserver(Settings.Global.getUriFor( Settings.Global.DOCK_AUDIO_MEDIA_ENABLED), false, this); mContentResolver.registerContentObserver(Settings.System.getUriFor( Settings.System.MASTER_MONO), false, this); mContentResolver.registerContentObserver(Settings.System.getUriFor( Settings.System.MASTER_BALANCE), false, this); mEncodedSurroundMode = Settings.Global.getInt( mContentResolver, Settings.Global.ENCODED_SURROUND_OUTPUT, Settings.Global.ENCODED_SURROUND_OUTPUT_AUTO); mContentResolver.registerContentObserver(Settings.Global.getUriFor( Settings.Global.ENCODED_SURROUND_OUTPUT), false, this); mEnabledSurroundFormats = Settings.Global.getString( mContentResolver, Settings.Global.ENCODED_SURROUND_OUTPUT_ENABLED_FORMATS); mContentResolver.registerContentObserver(Settings.Global.getUriFor( Settings.Global.ENCODED_SURROUND_OUTPUT_ENABLED_FORMATS), false, this); mContentResolver.registerContentObserver(Settings.Secure.getUriFor( Settings.Secure.VOICE_INTERACTION_SERVICE), false, this); mContentResolver.registerContentObserver(Settings.Secure.getUriFor( Settings.Secure.RTT_CALLING_MODE), false, this); } @Override public void onChange(boolean selfChange) { super.onChange(selfChange); // FIXME This synchronized is not necessary if mSettingsLock only protects mRingerMode. // However there appear to be some missing locks around mRingerAndZenModeMutedStreams // and mRingerModeAffectedStreams, so will leave this synchronized for now. // mRingerAndZenModeMutedStreams and mMuteAffectedStreams are safe (only accessed once). synchronized (mSettingsLock) { if (updateRingerAndZenModeAffectedStreams()) { /* * Ensure all stream types that should be affected by ringer mode * are in the proper state. */ setRingerModeInt(getRingerModeInternal(), false); } readDockAudioSettings(mContentResolver); updateMasterMono(mContentResolver); updateMasterBalance(mContentResolver); updateEncodedSurroundOutput(); sendEnabledSurroundFormats(mContentResolver, mSurroundModeChanged); updateAssistantUId(false); updateRttEanbled(mContentResolver); } } private void updateEncodedSurroundOutput() { int newSurroundMode = Settings.Global.getInt( mContentResolver, Settings.Global.ENCODED_SURROUND_OUTPUT, Settings.Global.ENCODED_SURROUND_OUTPUT_AUTO); // Did it change? if (mEncodedSurroundMode != newSurroundMode) { // Send to AudioPolicyManager sendEncodedSurroundMode(newSurroundMode, "SettingsObserver"); mDeviceBroker.toggleHdmiIfConnected_Async(); mEncodedSurroundMode = newSurroundMode; mSurroundModeChanged = true; } else { mSurroundModeChanged = false; } } } public void avrcpSupportsAbsoluteVolume(String address, boolean support) { // address is not used for now, but may be used when multiple a2dp devices are supported sVolumeLogger.log(new AudioEventLogger.StringEvent("avrcpSupportsAbsoluteVolume addr=" + address + " support=" + support)); mDeviceBroker.setAvrcpAbsoluteVolumeSupported(support); sendMsg(mAudioHandler, MSG_SET_DEVICE_VOLUME, SENDMSG_QUEUE, AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, 0, mStreamStates[AudioSystem.STREAM_MUSIC], 0); } /** * @return true if there is currently a registered dynamic mixing policy that affects media */ /*package*/ boolean hasMediaDynamicPolicy() { synchronized (mAudioPolicies) { if (mAudioPolicies.isEmpty()) { return false; } final Collection appColl = mAudioPolicies.values(); for (AudioPolicyProxy app : appColl) { if (app.hasMixAffectingUsage(AudioAttributes.USAGE_MEDIA)) { return true; } } return false; } } /*package*/ void checkMusicActive(int deviceType, String caller) { if ((deviceType & mSafeMediaVolumeDevices) != 0) { sendMsg(mAudioHandler, MSG_CHECK_MUSIC_ACTIVE, SENDMSG_REPLACE, 0, 0, caller, MUSIC_ACTIVE_POLL_PERIOD_MS); } } /** * Receiver for misc intent broadcasts the Phone app cares about. */ private class AudioServiceBroadcastReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { final String action = intent.getAction(); int outDevice; int inDevice; int state; if (action.equals(Intent.ACTION_DOCK_EVENT)) { int dockState = intent.getIntExtra(Intent.EXTRA_DOCK_STATE, Intent.EXTRA_DOCK_STATE_UNDOCKED); int config; switch (dockState) { case Intent.EXTRA_DOCK_STATE_DESK: config = AudioSystem.FORCE_BT_DESK_DOCK; break; case Intent.EXTRA_DOCK_STATE_CAR: config = AudioSystem.FORCE_BT_CAR_DOCK; break; case Intent.EXTRA_DOCK_STATE_LE_DESK: config = AudioSystem.FORCE_ANALOG_DOCK; break; case Intent.EXTRA_DOCK_STATE_HE_DESK: config = AudioSystem.FORCE_DIGITAL_DOCK; break; case Intent.EXTRA_DOCK_STATE_UNDOCKED: default: config = AudioSystem.FORCE_NONE; } // Low end docks have a menu to enable or disable audio // (see mDockAudioMediaEnabled) if (!((dockState == Intent.EXTRA_DOCK_STATE_LE_DESK) || ((dockState == Intent.EXTRA_DOCK_STATE_UNDOCKED) && (mDockState == Intent.EXTRA_DOCK_STATE_LE_DESK)))) { mDeviceBroker.setForceUse_Async(AudioSystem.FOR_DOCK, config, "ACTION_DOCK_EVENT intent"); } mDockState = dockState; } else if (action.equals(BluetoothHeadset.ACTION_ACTIVE_DEVICE_CHANGED) || action.equals(BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED)) { mDeviceBroker.receiveBtEvent(intent); } else if (action.equals(Intent.ACTION_SCREEN_ON)) { if (mMonitorRotation) { RotationHelper.enable(); } AudioSystem.setParameters("screen_state=on"); } else if (action.equals(Intent.ACTION_SCREEN_OFF)) { if (mMonitorRotation) { //reduce wakeups (save current) by only listening when display is on RotationHelper.disable(); } AudioSystem.setParameters("screen_state=off"); } else if (action.equals(Intent.ACTION_CONFIGURATION_CHANGED)) { handleConfigurationChanged(context); } else if (action.equals(Intent.ACTION_USER_SWITCHED)) { if (mUserSwitchedReceived) { // attempt to stop music playback for background user except on first user // switch (i.e. first boot) mDeviceBroker.postBroadcastBecomingNoisy(); } mUserSwitchedReceived = true; // the current audio focus owner is no longer valid mMediaFocusControl.discardAudioFocusOwner(); // load volume settings for new user readAudioSettings(true /*userSwitch*/); // preserve STREAM_MUSIC volume from one user to the next. sendMsg(mAudioHandler, MSG_SET_ALL_VOLUMES, SENDMSG_QUEUE, 0, 0, mStreamStates[AudioSystem.STREAM_MUSIC], 0); } else if (action.equals(Intent.ACTION_USER_BACKGROUND)) { // Disable audio recording for the background user/profile int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1); if (userId >= 0) { // TODO Kill recording streams instead of killing processes holding permission UserInfo userInfo = UserManagerService.getInstance().getUserInfo(userId); killBackgroundUserProcessesWithRecordAudioPermission(userInfo); } UserManagerService.getInstance().setUserRestriction( UserManager.DISALLOW_RECORD_AUDIO, true, userId); } else if (action.equals(Intent.ACTION_USER_FOREGROUND)) { // Enable audio recording for foreground user/profile int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1); UserManagerService.getInstance().setUserRestriction( UserManager.DISALLOW_RECORD_AUDIO, false, userId); } else if (action.equals(BluetoothAdapter.ACTION_STATE_CHANGED)) { state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, -1); if (state == BluetoothAdapter.STATE_OFF || state == BluetoothAdapter.STATE_TURNING_OFF) { mDeviceBroker.disconnectAllBluetoothProfiles(); } } else if (action.equals(AudioEffect.ACTION_OPEN_AUDIO_EFFECT_CONTROL_SESSION) || action.equals(AudioEffect.ACTION_CLOSE_AUDIO_EFFECT_CONTROL_SESSION)) { handleAudioEffectBroadcast(context, intent); } else if (action.equals(Intent.ACTION_PACKAGES_SUSPENDED)) { final int[] suspendedUids = intent.getIntArrayExtra(Intent.EXTRA_CHANGED_UID_LIST); final String[] suspendedPackages = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST); if (suspendedPackages == null || suspendedUids == null || suspendedPackages.length != suspendedUids.length) { return; } for (int i = 0; i < suspendedUids.length; i++) { if (!TextUtils.isEmpty(suspendedPackages[i])) { mMediaFocusControl.noFocusForSuspendedApp( suspendedPackages[i], suspendedUids[i]); } } } } } // end class AudioServiceBroadcastReceiver private class AudioServiceUserRestrictionsListener implements UserRestrictionsListener { @Override public void onUserRestrictionsChanged(int userId, Bundle newRestrictions, Bundle prevRestrictions) { // Update mic mute state. { final boolean wasRestricted = prevRestrictions.getBoolean(UserManager.DISALLOW_UNMUTE_MICROPHONE); final boolean isRestricted = newRestrictions.getBoolean(UserManager.DISALLOW_UNMUTE_MICROPHONE); if (wasRestricted != isRestricted) { setMicrophoneMuteNoCallerCheck(isRestricted, userId); } } // Update speaker mute state. { final boolean wasRestricted = prevRestrictions.getBoolean(UserManager.DISALLOW_ADJUST_VOLUME) || prevRestrictions.getBoolean(UserManager.DISALLOW_UNMUTE_DEVICE); final boolean isRestricted = newRestrictions.getBoolean(UserManager.DISALLOW_ADJUST_VOLUME) || newRestrictions.getBoolean(UserManager.DISALLOW_UNMUTE_DEVICE); if (wasRestricted != isRestricted) { setMasterMuteInternalNoCallerCheck(isRestricted, /* flags =*/ 0, userId); } } } } // end class AudioServiceUserRestrictionsListener private void handleAudioEffectBroadcast(Context context, Intent intent) { String target = intent.getPackage(); if (target != null) { Log.w(TAG, "effect broadcast already targeted to " + target); return; } intent.addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES); // TODO this should target a user-selected panel List ril = context.getPackageManager().queryBroadcastReceivers( intent, 0 /* flags */); if (ril != null && ril.size() != 0) { ResolveInfo ri = ril.get(0); if (ri != null && ri.activityInfo != null && ri.activityInfo.packageName != null) { intent.setPackage(ri.activityInfo.packageName); context.sendBroadcastAsUser(intent, UserHandle.ALL); return; } } Log.w(TAG, "couldn't find receiver package for effect intent"); } private void killBackgroundUserProcessesWithRecordAudioPermission(UserInfo oldUser) { PackageManager pm = mContext.getPackageManager(); // Find the home activity of the user. It should not be killed to avoid expensive restart, // when the user switches back. For managed profiles, we should kill all recording apps ComponentName homeActivityName = null; if (!oldUser.isManagedProfile()) { homeActivityName = LocalServices.getService( ActivityTaskManagerInternal.class).getHomeActivityForUser(oldUser.id); } final String[] permissions = { Manifest.permission.RECORD_AUDIO }; List packages; try { packages = AppGlobals.getPackageManager() .getPackagesHoldingPermissions(permissions, 0, oldUser.id).getList(); } catch (RemoteException e) { throw new AndroidRuntimeException(e); } for (int j = packages.size() - 1; j >= 0; j--) { PackageInfo pkg = packages.get(j); // Skip system processes if (UserHandle.getAppId(pkg.applicationInfo.uid) < FIRST_APPLICATION_UID) { continue; } // Skip packages that have permission to interact across users if (pm.checkPermission(Manifest.permission.INTERACT_ACROSS_USERS, pkg.packageName) == PackageManager.PERMISSION_GRANTED) { continue; } if (homeActivityName != null && pkg.packageName.equals(homeActivityName.getPackageName()) && pkg.applicationInfo.isSystemApp()) { continue; } try { final int uid = pkg.applicationInfo.uid; ActivityManager.getService().killUid(UserHandle.getAppId(uid), UserHandle.getUserId(uid), "killBackgroundUserProcessesWithAudioRecordPermission"); } catch (RemoteException e) { Log.w(TAG, "Error calling killUid", e); } } } //========================================================================================== // Audio Focus //========================================================================================== /** * Returns whether a focus request is eligible to force ducking. * Will return true if: * - the AudioAttributes have a usage of USAGE_ASSISTANCE_ACCESSIBILITY, * - the focus request is AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK, * - the associated Bundle has KEY_ACCESSIBILITY_FORCE_FOCUS_DUCKING set to true, * - the uid of the requester is a known accessibility service or root. * @param aa AudioAttributes of the focus request * @param uid uid of the focus requester * @return true if ducking is to be forced */ private boolean forceFocusDuckingForAccessibility(@Nullable AudioAttributes aa, int request, int uid) { if (aa == null || aa.getUsage() != AudioAttributes.USAGE_ASSISTANCE_ACCESSIBILITY || request != AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK) { return false; } final Bundle extraInfo = aa.getBundle(); if (extraInfo == null || !extraInfo.getBoolean(AudioFocusRequest.KEY_ACCESSIBILITY_FORCE_FOCUS_DUCKING)) { return false; } if (uid == 0) { return true; } synchronized (mAccessibilityServiceUidsLock) { if (mAccessibilityServiceUids != null) { int callingUid = Binder.getCallingUid(); for (int i = 0; i < mAccessibilityServiceUids.length; i++) { if (mAccessibilityServiceUids[i] == callingUid) { return true; } } } } return false; } public int requestAudioFocus(AudioAttributes aa, int durationHint, IBinder cb, IAudioFocusDispatcher fd, String clientId, String callingPackageName, int flags, IAudioPolicyCallback pcb, int sdk) { // permission checks if ((flags & AudioManager.AUDIOFOCUS_FLAG_LOCK) == AudioManager.AUDIOFOCUS_FLAG_LOCK) { if (AudioSystem.IN_VOICE_COMM_FOCUS_ID.equals(clientId)) { if (PackageManager.PERMISSION_GRANTED != mContext.checkCallingOrSelfPermission( android.Manifest.permission.MODIFY_PHONE_STATE)) { Log.e(TAG, "Invalid permission to (un)lock audio focus", new Exception()); return AudioManager.AUDIOFOCUS_REQUEST_FAILED; } } else { // only a registered audio policy can be used to lock focus synchronized (mAudioPolicies) { if (!mAudioPolicies.containsKey(pcb.asBinder())) { Log.e(TAG, "Invalid unregistered AudioPolicy to (un)lock audio focus"); return AudioManager.AUDIOFOCUS_REQUEST_FAILED; } } } } if (callingPackageName == null || clientId == null || aa == null) { Log.e(TAG, "Invalid null parameter to request audio focus"); return AudioManager.AUDIOFOCUS_REQUEST_FAILED; } return mMediaFocusControl.requestAudioFocus(aa, durationHint, cb, fd, clientId, callingPackageName, flags, sdk, forceFocusDuckingForAccessibility(aa, durationHint, Binder.getCallingUid())); } public int abandonAudioFocus(IAudioFocusDispatcher fd, String clientId, AudioAttributes aa, String callingPackageName) { return mMediaFocusControl.abandonAudioFocus(fd, clientId, aa, callingPackageName); } public void unregisterAudioFocusClient(String clientId) { mMediaFocusControl.unregisterAudioFocusClient(clientId); } public int getCurrentAudioFocus() { return mMediaFocusControl.getCurrentAudioFocus(); } public int getFocusRampTimeMs(int focusGain, AudioAttributes attr) { return mMediaFocusControl.getFocusRampTimeMs(focusGain, attr); } /*package*/ boolean hasAudioFocusUsers() { return mMediaFocusControl.hasAudioFocusUsers(); } //========================================================================================== private boolean readCameraSoundForced() { return SystemProperties.getBoolean("audio.camerasound.force", false) || mContext.getResources().getBoolean( com.android.internal.R.bool.config_camera_sound_forced); } //========================================================================================== // Device orientation //========================================================================================== /** * Handles device configuration changes that may map to a change in rotation. * Monitoring rotation is optional, and is defined by the definition and value * of the "ro.audio.monitorRotation" system property. */ private void handleConfigurationChanged(Context context) { try { // reading new configuration "safely" (i.e. under try catch) in case anything // goes wrong. Configuration config = context.getResources().getConfiguration(); sendMsg(mAudioHandler, MSG_CONFIGURE_SAFE_MEDIA_VOLUME, SENDMSG_REPLACE, 0, 0, TAG, 0); boolean cameraSoundForced = readCameraSoundForced(); synchronized (mSettingsLock) { final boolean cameraSoundForcedChanged = (cameraSoundForced != mCameraSoundForced); mCameraSoundForced = cameraSoundForced; if (cameraSoundForcedChanged) { if (!mIsSingleVolume) { synchronized (VolumeStreamState.class) { VolumeStreamState s = mStreamStates[AudioSystem.STREAM_SYSTEM_ENFORCED]; if (cameraSoundForced) { s.setAllIndexesToMax(); mRingerModeAffectedStreams &= ~(1 << AudioSystem.STREAM_SYSTEM_ENFORCED); } else { s.setAllIndexes(mStreamStates[AudioSystem.STREAM_SYSTEM], TAG); mRingerModeAffectedStreams |= (1 << AudioSystem.STREAM_SYSTEM_ENFORCED); } } // take new state into account for streams muted by ringer mode setRingerModeInt(getRingerModeInternal(), false); } mDeviceBroker.setForceUse_Async(AudioSystem.FOR_SYSTEM, cameraSoundForced ? AudioSystem.FORCE_SYSTEM_ENFORCED : AudioSystem.FORCE_NONE, "handleConfigurationChanged"); sendMsg(mAudioHandler, MSG_SET_ALL_VOLUMES, SENDMSG_QUEUE, 0, 0, mStreamStates[AudioSystem.STREAM_SYSTEM_ENFORCED], 0); } } mVolumeController.setLayoutDirection(config.getLayoutDirection()); } catch (Exception e) { Log.e(TAG, "Error handling configuration change: ", e); } } @Override public void setRingtonePlayer(IRingtonePlayer player) { mContext.enforceCallingOrSelfPermission(REMOTE_AUDIO_PLAYBACK, null); mRingtonePlayer = player; } @Override public IRingtonePlayer getRingtonePlayer() { return mRingtonePlayer; } @Override public AudioRoutesInfo startWatchingRoutes(IAudioRoutesObserver observer) { return mDeviceBroker.startWatchingRoutes(observer); } //========================================================================================== // Safe media volume management. // MUSIC stream volume level is limited when headphones are connected according to safety // regulation. When the user attempts to raise the volume above the limit, a warning is // displayed and the user has to acknowlegde before the volume is actually changed. // The volume index corresponding to the limit is stored in config_safe_media_volume_index // property. Platforms with a different limit must set this property accordingly in their // overlay. //========================================================================================== // mSafeMediaVolumeState indicates whether the media volume is limited over headphones. // It is SAFE_MEDIA_VOLUME_NOT_CONFIGURED at boot time until a network service is connected // or the configure time is elapsed. It is then set to SAFE_MEDIA_VOLUME_ACTIVE or // SAFE_MEDIA_VOLUME_DISABLED according to country option. If not SAFE_MEDIA_VOLUME_DISABLED, it // can be set to SAFE_MEDIA_VOLUME_INACTIVE by calling AudioService.disableSafeMediaVolume() // (when user opts out). private static final int SAFE_MEDIA_VOLUME_NOT_CONFIGURED = 0; private static final int SAFE_MEDIA_VOLUME_DISABLED = 1; private static final int SAFE_MEDIA_VOLUME_INACTIVE = 2; // confirmed private static final int SAFE_MEDIA_VOLUME_ACTIVE = 3; // unconfirmed private int mSafeMediaVolumeState; private final Object mSafeMediaVolumeStateLock = new Object(); private int mMcc = 0; // mSafeMediaVolumeIndex is the cached value of config_safe_media_volume_index property private int mSafeMediaVolumeIndex; // mSafeUsbMediaVolumeDbfs is the cached value of the config_safe_media_volume_usb_mB // property, divided by 100.0. private float mSafeUsbMediaVolumeDbfs; // mSafeUsbMediaVolumeIndex is used for USB Headsets and is the music volume UI index // corresponding to a gain of mSafeUsbMediaVolumeDbfs (defaulting to -37dB) in audio // flinger mixer. // We remove -22 dBs from the theoretical -15dB to account for the EQ + bass boost // amplification when both effects are on with all band gains at maximum. // This level corresponds to a loudness of 85 dB SPL for the warning to be displayed when // the headset is compliant to EN 60950 with a max loudness of 100dB SPL. private int mSafeUsbMediaVolumeIndex; // mSafeMediaVolumeDevices lists the devices for which safe media volume is enforced, /*package*/ final int mSafeMediaVolumeDevices = AudioSystem.DEVICE_OUT_WIRED_HEADSET | AudioSystem.DEVICE_OUT_WIRED_HEADPHONE | AudioSystem.DEVICE_OUT_USB_HEADSET; // mMusicActiveMs is the cumulative time of music activity since safe volume was disabled. // When this time reaches UNSAFE_VOLUME_MUSIC_ACTIVE_MS_MAX, the safe media volume is re-enabled // automatically. mMusicActiveMs is rounded to a multiple of MUSIC_ACTIVE_POLL_PERIOD_MS. private int mMusicActiveMs; private static final int UNSAFE_VOLUME_MUSIC_ACTIVE_MS_MAX = (20 * 3600 * 1000); // 20 hours private static final int MUSIC_ACTIVE_POLL_PERIOD_MS = 60000; // 1 minute polling interval private static final int SAFE_VOLUME_CONFIGURE_TIMEOUT_MS = 30000; // 30s after boot completed private int safeMediaVolumeIndex(int device) { if ((device & mSafeMediaVolumeDevices) == 0) { return MAX_STREAM_VOLUME[AudioSystem.STREAM_MUSIC]; } if (device == AudioSystem.DEVICE_OUT_USB_HEADSET) { return mSafeUsbMediaVolumeIndex; } else { return mSafeMediaVolumeIndex; } } private void setSafeMediaVolumeEnabled(boolean on, String caller) { synchronized (mSafeMediaVolumeStateLock) { if ((mSafeMediaVolumeState != SAFE_MEDIA_VOLUME_NOT_CONFIGURED) && (mSafeMediaVolumeState != SAFE_MEDIA_VOLUME_DISABLED)) { if (on && (mSafeMediaVolumeState == SAFE_MEDIA_VOLUME_INACTIVE)) { mSafeMediaVolumeState = SAFE_MEDIA_VOLUME_ACTIVE; enforceSafeMediaVolume(caller); } else if (!on && (mSafeMediaVolumeState == SAFE_MEDIA_VOLUME_ACTIVE)) { mSafeMediaVolumeState = SAFE_MEDIA_VOLUME_INACTIVE; mMusicActiveMs = 1; // nonzero = confirmed saveMusicActiveMs(); sendMsg(mAudioHandler, MSG_CHECK_MUSIC_ACTIVE, SENDMSG_REPLACE, 0, 0, caller, MUSIC_ACTIVE_POLL_PERIOD_MS); } } } } private void enforceSafeMediaVolume(String caller) { VolumeStreamState streamState = mStreamStates[AudioSystem.STREAM_MUSIC]; int devices = mSafeMediaVolumeDevices; int i = 0; while (devices != 0) { int device = 1 << i++; if ((device & devices) == 0) { continue; } int index = streamState.getIndex(device); if (index > safeMediaVolumeIndex(device)) { streamState.setIndex(safeMediaVolumeIndex(device), device, caller); sendMsg(mAudioHandler, MSG_SET_DEVICE_VOLUME, SENDMSG_QUEUE, device, 0, streamState, 0); } devices &= ~device; } } private boolean checkSafeMediaVolume(int streamType, int index, int device) { synchronized (mSafeMediaVolumeStateLock) { if ((mSafeMediaVolumeState == SAFE_MEDIA_VOLUME_ACTIVE) && (mStreamVolumeAlias[streamType] == AudioSystem.STREAM_MUSIC) && ((device & mSafeMediaVolumeDevices) != 0) && (index > safeMediaVolumeIndex(device))) { return false; } return true; } } @Override public void disableSafeMediaVolume(String callingPackage) { enforceVolumeController("disable the safe media volume"); synchronized (mSafeMediaVolumeStateLock) { setSafeMediaVolumeEnabled(false, callingPackage); if (mPendingVolumeCommand != null) { onSetStreamVolume(mPendingVolumeCommand.mStreamType, mPendingVolumeCommand.mIndex, mPendingVolumeCommand.mFlags, mPendingVolumeCommand.mDevice, callingPackage); mPendingVolumeCommand = null; } } } //========================================================================================== // Hdmi CEC: // - System audio mode: // If Hdmi Cec's system audio mode is on, audio service should send the volume change // to HdmiControlService so that the audio receiver can handle it. // - CEC sink: // OUT_HDMI becomes a "full volume device", i.e. output is always at maximum level // and volume changes won't be taken into account on this device. Volume adjustments // are transformed into key events for the HDMI playback client. //========================================================================================== private class MyDisplayStatusCallback implements HdmiPlaybackClient.DisplayStatusCallback { public void onComplete(int status) { synchronized (mHdmiClientLock) { if (mHdmiManager != null) { mHdmiCecSink = (status != HdmiControlManager.POWER_STATUS_UNKNOWN); // Television devices without CEC service apply software volume on HDMI output if (mHdmiCecSink) { if (DEBUG_VOL) { Log.d(TAG, "CEC sink: setting HDMI as full vol device"); } mFullVolumeDevices |= AudioSystem.DEVICE_OUT_HDMI; } else { if (DEBUG_VOL) { Log.d(TAG, "TV, no CEC: setting HDMI as regular vol device"); } // Android TV devices without CEC service apply software volume on // HDMI output mFullVolumeDevices &= ~AudioSystem.DEVICE_OUT_HDMI; } checkAddAllFixedVolumeDevices(AudioSystem.DEVICE_OUT_HDMI, "HdmiPlaybackClient.DisplayStatusCallback"); } } } } private final Object mHdmiClientLock = new Object(); // If HDMI-CEC system audio is supported private boolean mHdmiSystemAudioSupported = false; // Set only when device is tv. @GuardedBy("mHdmiClientLock") private HdmiTvClient mHdmiTvClient; // true if the device has system feature PackageManager.FEATURE_LEANBACK. // cached HdmiControlManager interface @GuardedBy("mHdmiClientLock") private HdmiControlManager mHdmiManager; // Set only when device is a set-top box. @GuardedBy("mHdmiClientLock") private HdmiPlaybackClient mHdmiPlaybackClient; // true if we are a set-top box, an HDMI sink is connected and it supports CEC. private boolean mHdmiCecSink; // Set only when device is an audio system. @GuardedBy("mHdmiClientLock") private HdmiAudioSystemClient mHdmiAudioSystemClient; private MyDisplayStatusCallback mHdmiDisplayStatusCallback = new MyDisplayStatusCallback(); @Override public int setHdmiSystemAudioSupported(boolean on) { int device = AudioSystem.DEVICE_NONE; synchronized (mHdmiClientLock) { if (mHdmiManager != null) { if (mHdmiTvClient == null && mHdmiAudioSystemClient == null) { Log.w(TAG, "Only Hdmi-Cec enabled TV or audio system device supports" + "system audio mode."); return device; } if (mHdmiSystemAudioSupported != on) { mHdmiSystemAudioSupported = on; final int config = on ? AudioSystem.FORCE_HDMI_SYSTEM_AUDIO_ENFORCED : AudioSystem.FORCE_NONE; mDeviceBroker.setForceUse_Async(AudioSystem.FOR_HDMI_SYSTEM_AUDIO, config, "setHdmiSystemAudioSupported"); } device = getDevicesForStream(AudioSystem.STREAM_MUSIC); } } return device; } @Override public boolean isHdmiSystemAudioSupported() { return mHdmiSystemAudioSupported; } //========================================================================================== // Accessibility private void initA11yMonitoring() { final AccessibilityManager accessibilityManager = (AccessibilityManager) mContext.getSystemService(Context.ACCESSIBILITY_SERVICE); updateDefaultStreamOverrideDelay(accessibilityManager.isTouchExplorationEnabled()); updateA11yVolumeAlias(accessibilityManager.isAccessibilityVolumeStreamActive()); accessibilityManager.addTouchExplorationStateChangeListener(this, null); accessibilityManager.addAccessibilityServicesStateChangeListener(this, null); } //--------------------------------------------------------------------------------- // A11y: taking touch exploration into account for selecting the default // stream override timeout when adjusting volume //--------------------------------------------------------------------------------- // - STREAM_NOTIFICATION on tablets during this period after a notification stopped // - STREAM_RING on phones during this period after a notification stopped // - STREAM_MUSIC otherwise private static final int DEFAULT_STREAM_TYPE_OVERRIDE_DELAY_MS = 0; private static final int TOUCH_EXPLORE_STREAM_TYPE_OVERRIDE_DELAY_MS = 1000; private static int sStreamOverrideDelayMs; @Override public void onTouchExplorationStateChanged(boolean enabled) { updateDefaultStreamOverrideDelay(enabled); } private void updateDefaultStreamOverrideDelay(boolean touchExploreEnabled) { if (touchExploreEnabled) { sStreamOverrideDelayMs = TOUCH_EXPLORE_STREAM_TYPE_OVERRIDE_DELAY_MS; } else { sStreamOverrideDelayMs = DEFAULT_STREAM_TYPE_OVERRIDE_DELAY_MS; } if (DEBUG_VOL) Log.d(TAG, "Touch exploration enabled=" + touchExploreEnabled + " stream override delay is now " + sStreamOverrideDelayMs + " ms"); } //--------------------------------------------------------------------------------- // A11y: taking a11y state into account for the handling of a11y prompts volume //--------------------------------------------------------------------------------- private static boolean sIndependentA11yVolume = false; // implementation of AccessibilityServicesStateChangeListener @Override public void onAccessibilityServicesStateChanged(AccessibilityManager accessibilityManager) { updateA11yVolumeAlias(accessibilityManager.isAccessibilityVolumeStreamActive()); } private void updateA11yVolumeAlias(boolean a11VolEnabled) { if (DEBUG_VOL) Log.d(TAG, "Accessibility volume enabled = " + a11VolEnabled); if (sIndependentA11yVolume != a11VolEnabled) { sIndependentA11yVolume = a11VolEnabled; // update the volume mapping scheme updateStreamVolumeAlias(true /*updateVolumes*/, TAG); // update the volume controller behavior mVolumeController.setA11yMode(sIndependentA11yVolume ? VolumePolicy.A11Y_MODE_INDEPENDENT_A11Y_VOLUME : VolumePolicy.A11Y_MODE_MEDIA_A11Y_VOLUME); mVolumeController.postVolumeChanged(AudioManager.STREAM_ACCESSIBILITY, 0); } } //========================================================================================== // Camera shutter sound policy. // config_camera_sound_forced configuration option in config.xml defines if the camera shutter // sound is forced (sound even if the device is in silent mode) or not. This option is false by // default and can be overridden by country specific overlay in values-mccXXX/config.xml. //========================================================================================== // cached value of com.android.internal.R.bool.config_camera_sound_forced @GuardedBy("mSettingsLock") private boolean mCameraSoundForced; // called by android.hardware.Camera to populate CameraInfo.canDisableShutterSound public boolean isCameraSoundForced() { synchronized (mSettingsLock) { return mCameraSoundForced; } } //========================================================================================== // AudioService logging and dumpsys //========================================================================================== static final int LOG_NB_EVENTS_PHONE_STATE = 20; static final int LOG_NB_EVENTS_DEVICE_CONNECTION = 30; static final int LOG_NB_EVENTS_FORCE_USE = 20; static final int LOG_NB_EVENTS_VOLUME = 40; static final int LOG_NB_EVENTS_DYN_POLICY = 10; final private AudioEventLogger mModeLogger = new AudioEventLogger(LOG_NB_EVENTS_PHONE_STATE, "phone state (logged after successfull call to AudioSystem.setPhoneState(int))"); // logs for wired + A2DP device connections: // - wired: logged before onSetWiredDeviceConnectionState() is executed // - A2DP: logged at reception of method call /*package*/ static final AudioEventLogger sDeviceLogger = new AudioEventLogger( LOG_NB_EVENTS_DEVICE_CONNECTION, "wired/A2DP/hearing aid device connection"); static final AudioEventLogger sForceUseLogger = new AudioEventLogger( LOG_NB_EVENTS_FORCE_USE, "force use (logged before setForceUse() is executed)"); static final AudioEventLogger sVolumeLogger = new AudioEventLogger(LOG_NB_EVENTS_VOLUME, "volume changes (logged when command received by AudioService)"); final private AudioEventLogger mDynPolicyLogger = new AudioEventLogger(LOG_NB_EVENTS_DYN_POLICY, "dynamic policy events (logged when command received by AudioService)"); private static final String[] RINGER_MODE_NAMES = new String[] { "SILENT", "VIBRATE", "NORMAL" }; private void dumpRingerMode(PrintWriter pw) { pw.println("\nRinger mode: "); pw.println("- mode (internal) = " + RINGER_MODE_NAMES[mRingerMode]); pw.println("- mode (external) = " + RINGER_MODE_NAMES[mRingerModeExternal]); dumpRingerModeStreams(pw, "affected", mRingerModeAffectedStreams); dumpRingerModeStreams(pw, "muted", mRingerAndZenModeMutedStreams); pw.print("- delegate = "); pw.println(mRingerModeDelegate); } private void dumpRingerModeStreams(PrintWriter pw, String type, int streams) { pw.print("- ringer mode "); pw.print(type); pw.print(" streams = 0x"); pw.print(Integer.toHexString(streams)); if (streams != 0) { pw.print(" ("); boolean first = true; for (int i = 0; i < AudioSystem.STREAM_NAMES.length; i++) { final int stream = (1 << i); if ((streams & stream) != 0) { if (!first) pw.print(','); pw.print(AudioSystem.STREAM_NAMES[i]); streams &= ~stream; first = false; } } if (streams != 0) { if (!first) pw.print(','); pw.print(streams); } pw.print(')'); } pw.println(); } @Override protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return; mMediaFocusControl.dump(pw); dumpStreamStates(pw); dumpRingerMode(pw); pw.println("\nAudio routes:"); pw.print(" mMainType=0x"); pw.println(Integer.toHexString( mDeviceBroker.getCurAudioRoutes().mainType)); pw.print(" mBluetoothName="); pw.println(mDeviceBroker.getCurAudioRoutes().bluetoothName); pw.println("\nOther state:"); pw.print(" mVolumeController="); pw.println(mVolumeController); pw.print(" mSafeMediaVolumeState="); pw.println(safeMediaVolumeStateToString(mSafeMediaVolumeState)); pw.print(" mSafeMediaVolumeIndex="); pw.println(mSafeMediaVolumeIndex); pw.print(" mSafeUsbMediaVolumeIndex="); pw.println(mSafeUsbMediaVolumeIndex); pw.print(" mSafeUsbMediaVolumeDbfs="); pw.println(mSafeUsbMediaVolumeDbfs); pw.print(" sIndependentA11yVolume="); pw.println(sIndependentA11yVolume); pw.print(" mPendingVolumeCommand="); pw.println(mPendingVolumeCommand); pw.print(" mMusicActiveMs="); pw.println(mMusicActiveMs); pw.print(" mMcc="); pw.println(mMcc); pw.print(" mCameraSoundForced="); pw.println(mCameraSoundForced); pw.print(" mHasVibrator="); pw.println(mHasVibrator); pw.print(" mVolumePolicy="); pw.println(mVolumePolicy); pw.print(" mAvrcpAbsVolSupported="); pw.println(mDeviceBroker.isAvrcpAbsoluteVolumeSupported()); pw.print(" mIsSingleVolume="); pw.println(mIsSingleVolume); pw.print(" mUseFixedVolume="); pw.println(mUseFixedVolume); pw.print(" mFixedVolumeDevices=0x"); pw.println(Integer.toHexString(mFixedVolumeDevices)); pw.print(" mHdmiCecSink="); pw.println(mHdmiCecSink); pw.print(" mHdmiAudioSystemClient="); pw.println(mHdmiAudioSystemClient); pw.print(" mHdmiPlaybackClient="); pw.println(mHdmiPlaybackClient); pw.print(" mHdmiTvClient="); pw.println(mHdmiTvClient); pw.print(" mHdmiSystemAudioSupported="); pw.println(mHdmiSystemAudioSupported); dumpAudioPolicies(pw); mDynPolicyLogger.dump(pw); mPlaybackMonitor.dump(pw); mRecordMonitor.dump(pw); pw.println("\n"); pw.println("\nEvent logs:"); mModeLogger.dump(pw); pw.println("\n"); sDeviceLogger.dump(pw); pw.println("\n"); sForceUseLogger.dump(pw); pw.println("\n"); sVolumeLogger.dump(pw); } private static String safeMediaVolumeStateToString(int state) { switch(state) { case SAFE_MEDIA_VOLUME_NOT_CONFIGURED: return "SAFE_MEDIA_VOLUME_NOT_CONFIGURED"; case SAFE_MEDIA_VOLUME_DISABLED: return "SAFE_MEDIA_VOLUME_DISABLED"; case SAFE_MEDIA_VOLUME_INACTIVE: return "SAFE_MEDIA_VOLUME_INACTIVE"; case SAFE_MEDIA_VOLUME_ACTIVE: return "SAFE_MEDIA_VOLUME_ACTIVE"; } return null; } // Inform AudioFlinger of our device's low RAM attribute private static void readAndSetLowRamDevice() { boolean isLowRamDevice = ActivityManager.isLowRamDeviceStatic(); long totalMemory = 1024 * 1024 * 1024; // 1GB is the default if ActivityManager fails. try { final ActivityManager.MemoryInfo info = new ActivityManager.MemoryInfo(); ActivityManager.getService().getMemoryInfo(info); totalMemory = info.totalMem; } catch (RemoteException e) { Log.w(TAG, "Cannot obtain MemoryInfo from ActivityManager, assume low memory device"); isLowRamDevice = true; } final int status = AudioSystem.setLowRamDevice(isLowRamDevice, totalMemory); if (status != 0) { Log.w(TAG, "AudioFlinger informed of device's low RAM attribute; status " + status); } } private void enforceVolumeController(String action) { mContext.enforceCallingOrSelfPermission(android.Manifest.permission.STATUS_BAR_SERVICE, "Only SystemUI can " + action); } @Override public void setVolumeController(final IVolumeController controller) { enforceVolumeController("set the volume controller"); // return early if things are not actually changing if (mVolumeController.isSameBinder(controller)) { return; } // dismiss the old volume controller mVolumeController.postDismiss(); if (controller != null) { // we are about to register a new controller, listen for its death try { controller.asBinder().linkToDeath(new DeathRecipient() { @Override public void binderDied() { if (mVolumeController.isSameBinder(controller)) { Log.w(TAG, "Current remote volume controller died, unregistering"); setVolumeController(null); } } }, 0); } catch (RemoteException e) { // noop } } mVolumeController.setController(controller); if (DEBUG_VOL) Log.d(TAG, "Volume controller: " + mVolumeController); } @Override public void notifyVolumeControllerVisible(final IVolumeController controller, boolean visible) { enforceVolumeController("notify about volume controller visibility"); // return early if the controller is not current if (!mVolumeController.isSameBinder(controller)) { return; } mVolumeController.setVisible(visible); if (DEBUG_VOL) Log.d(TAG, "Volume controller visible: " + visible); } @Override public void setVolumePolicy(VolumePolicy policy) { enforceVolumeController("set volume policy"); if (policy != null && !policy.equals(mVolumePolicy)) { mVolumePolicy = policy; if (DEBUG_VOL) Log.d(TAG, "Volume policy changed: " + mVolumePolicy); } } public static class VolumeController { private static final String TAG = "VolumeController"; private IVolumeController mController; private boolean mVisible; private long mNextLongPress; private int mLongPressTimeout; public void setController(IVolumeController controller) { mController = controller; mVisible = false; } public void loadSettings(ContentResolver cr) { mLongPressTimeout = Settings.Secure.getIntForUser(cr, Settings.Secure.LONG_PRESS_TIMEOUT, 500, UserHandle.USER_CURRENT); } public boolean suppressAdjustment(int resolvedStream, int flags, boolean isMute) { if (isMute) { return false; } boolean suppress = false; if (resolvedStream == DEFAULT_VOL_STREAM_NO_PLAYBACK && mController != null) { final long now = SystemClock.uptimeMillis(); if ((flags & AudioManager.FLAG_SHOW_UI) != 0 && !mVisible) { // ui will become visible if (mNextLongPress < now) { mNextLongPress = now + mLongPressTimeout; } suppress = true; } else if (mNextLongPress > 0) { // in a long-press if (now > mNextLongPress) { // long press triggered, no more suppression mNextLongPress = 0; } else { // keep suppressing until the long press triggers suppress = true; } } } return suppress; } public void setVisible(boolean visible) { mVisible = visible; } public boolean isSameBinder(IVolumeController controller) { return Objects.equals(asBinder(), binder(controller)); } public IBinder asBinder() { return binder(mController); } private static IBinder binder(IVolumeController controller) { return controller == null ? null : controller.asBinder(); } @Override public String toString() { return "VolumeController(" + asBinder() + ",mVisible=" + mVisible + ")"; } public void postDisplaySafeVolumeWarning(int flags) { if (mController == null) return; try { mController.displaySafeVolumeWarning(flags); } catch (RemoteException e) { Log.w(TAG, "Error calling displaySafeVolumeWarning", e); } } public void postVolumeChanged(int streamType, int flags) { if (mController == null) return; try { mController.volumeChanged(streamType, flags); } catch (RemoteException e) { Log.w(TAG, "Error calling volumeChanged", e); } } public void postMasterMuteChanged(int flags) { if (mController == null) return; try { mController.masterMuteChanged(flags); } catch (RemoteException e) { Log.w(TAG, "Error calling masterMuteChanged", e); } } public void setLayoutDirection(int layoutDirection) { if (mController == null) return; try { mController.setLayoutDirection(layoutDirection); } catch (RemoteException e) { Log.w(TAG, "Error calling setLayoutDirection", e); } } public void postDismiss() { if (mController == null) return; try { mController.dismiss(); } catch (RemoteException e) { Log.w(TAG, "Error calling dismiss", e); } } public void setA11yMode(int a11yMode) { if (mController == null) return; try { mController.setA11yMode(a11yMode); } catch (RemoteException e) { Log.w(TAG, "Error calling setA11Mode", e); } } } /** * Interface for system components to get some extra functionality through * LocalServices. */ final class AudioServiceInternal extends AudioManagerInternal { @Override public void setRingerModeDelegate(RingerModeDelegate delegate) { mRingerModeDelegate = delegate; if (mRingerModeDelegate != null) { synchronized (mSettingsLock) { updateRingerAndZenModeAffectedStreams(); } setRingerModeInternal(getRingerModeInternal(), TAG + ".setRingerModeDelegate"); } } @Override public void adjustSuggestedStreamVolumeForUid(int streamType, int direction, int flags, String callingPackage, int uid) { // direction and stream type swap here because the public // adjustSuggested has a different order than the other methods. adjustSuggestedStreamVolume(direction, streamType, flags, callingPackage, callingPackage, uid); } @Override public void adjustStreamVolumeForUid(int streamType, int direction, int flags, String callingPackage, int uid) { if (direction != AudioManager.ADJUST_SAME) { sVolumeLogger.log(new VolumeEvent(VolumeEvent.VOL_ADJUST_VOL_UID, streamType, direction/*val1*/, flags/*val2*/, new StringBuilder(callingPackage) .append(" uid:").append(uid).toString())); } adjustStreamVolume(streamType, direction, flags, callingPackage, callingPackage, uid); } @Override public void setStreamVolumeForUid(int streamType, int direction, int flags, String callingPackage, int uid) { setStreamVolume(streamType, direction, flags, callingPackage, callingPackage, uid); } @Override public int getRingerModeInternal() { return AudioService.this.getRingerModeInternal(); } @Override public void setRingerModeInternal(int ringerMode, String caller) { AudioService.this.setRingerModeInternal(ringerMode, caller); } @Override public void silenceRingerModeInternal(String caller) { AudioService.this.silenceRingerModeInternal(caller); } @Override public void updateRingerModeAffectedStreamsInternal() { synchronized (mSettingsLock) { if (updateRingerAndZenModeAffectedStreams()) { setRingerModeInt(getRingerModeInternal(), false); } } } @Override public void setAccessibilityServiceUids(IntArray uids) { synchronized (mAccessibilityServiceUidsLock) { if (uids.size() == 0) { mAccessibilityServiceUids = null; } else { boolean changed = (mAccessibilityServiceUids == null) || (mAccessibilityServiceUids.length != uids.size()); if (!changed) { for (int i = 0; i < mAccessibilityServiceUids.length; i++) { if (uids.get(i) != mAccessibilityServiceUids[i]) { changed = true; break; } } } if (changed) { mAccessibilityServiceUids = uids.toArray(); } } AudioSystem.setA11yServicesUids(mAccessibilityServiceUids); } } } //========================================================================================== // Audio policy management //========================================================================================== public String registerAudioPolicy(AudioPolicyConfig policyConfig, IAudioPolicyCallback pcb, boolean hasFocusListener, boolean isFocusPolicy, boolean isTestFocusPolicy, boolean isVolumeController, IMediaProjection projection) { AudioSystem.setDynamicPolicyCallback(mDynPolicyCallback); if (!isPolicyRegisterAllowed(policyConfig, isFocusPolicy || isTestFocusPolicy || hasFocusListener, isVolumeController, projection)) { Slog.w(TAG, "Permission denied to register audio policy for pid " + Binder.getCallingPid() + " / uid " + Binder.getCallingUid() + ", need MODIFY_AUDIO_ROUTING or MediaProjection that can project audio"); return null; } mDynPolicyLogger.log((new AudioEventLogger.StringEvent("registerAudioPolicy for " + pcb.asBinder() + " with config:" + policyConfig)).printLog(TAG)); String regId = null; synchronized (mAudioPolicies) { if (mAudioPolicies.containsKey(pcb.asBinder())) { Slog.e(TAG, "Cannot re-register policy"); return null; } try { AudioPolicyProxy app = new AudioPolicyProxy(policyConfig, pcb, hasFocusListener, isFocusPolicy, isTestFocusPolicy, isVolumeController, projection); pcb.asBinder().linkToDeath(app, 0/*flags*/); regId = app.getRegistrationId(); mAudioPolicies.put(pcb.asBinder(), app); } catch (RemoteException e) { // audio policy owner has already died! Slog.w(TAG, "Audio policy registration failed, could not link to " + pcb + " binder death", e); return null; } catch (IllegalStateException e) { Slog.w(TAG, "Audio policy registration failed for binder " + pcb, e); return null; } } return regId; } /** * Apps with MODIFY_AUDIO_ROUTING can register any policy. * Apps with an audio capable MediaProjection are allowed to register a RENDER|LOOPBACK policy * as those policy do not modify the audio routing. */ private boolean isPolicyRegisterAllowed(AudioPolicyConfig policyConfig, boolean hasFocusAccess, boolean isVolumeController, IMediaProjection projection) { boolean requireValidProjection = false; boolean requireCaptureAudioOrMediaOutputPerm = false; boolean requireModifyRouting = false; if (hasFocusAccess || isVolumeController) { requireModifyRouting |= true; } else if (policyConfig.getMixes().isEmpty()) { // An empty policy could be used to lock the focus or add mixes later requireModifyRouting |= true; } for (AudioMix mix : policyConfig.getMixes()) { // If mix is requesting a privileged capture if (mix.getRule().allowPrivilegedPlaybackCapture()) { // then it must have CAPTURE_MEDIA_OUTPUT or CAPTURE_AUDIO_OUTPUT permission requireCaptureAudioOrMediaOutputPerm |= true; // and its format must be low quality enough String error = mix.canBeUsedForPrivilegedCapture(mix.getFormat()); if (error != null) { Log.e(TAG, error); return false; } } // If mix is RENDER|LOOPBACK, then an audio MediaProjection is enough // otherwise MODIFY_AUDIO_ROUTING permission is required if (mix.getRouteFlags() == mix.ROUTE_FLAG_LOOP_BACK_RENDER && projection != null) { requireValidProjection |= true; } else { requireModifyRouting |= true; } } if (requireCaptureAudioOrMediaOutputPerm && !callerHasPermission(android.Manifest.permission.CAPTURE_MEDIA_OUTPUT) && !callerHasPermission(android.Manifest.permission.CAPTURE_AUDIO_OUTPUT)) { Log.e(TAG, "Privileged audio capture requires CAPTURE_MEDIA_OUTPUT or " + "CAPTURE_AUDIO_OUTPUT system permission"); return false; } if (requireValidProjection && !canProjectAudio(projection)) { return false; } if (requireModifyRouting && !callerHasPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)) { Log.e(TAG, "Can not capture audio without MODIFY_AUDIO_ROUTING"); return false; } return true; } private boolean callerHasPermission(String permission) { return mContext.checkCallingPermission(permission) == PackageManager.PERMISSION_GRANTED; } /** @return true if projection is a valid MediaProjection that can project audio. */ private boolean canProjectAudio(IMediaProjection projection) { if (projection == null) { Log.e(TAG, "MediaProjection is null"); return false; } IMediaProjectionManager projectionService = getProjectionService(); if (projectionService == null) { Log.e(TAG, "Can't get service IMediaProjectionManager"); return false; } try { if (!projectionService.isValidMediaProjection(projection)) { Log.w(TAG, "App passed invalid MediaProjection token"); return false; } } catch (RemoteException e) { Log.e(TAG, "Can't call .isValidMediaProjection() on IMediaProjectionManager" + projectionService.asBinder(), e); return false; } try { if (!projection.canProjectAudio()) { Log.w(TAG, "App passed MediaProjection that can not project audio"); return false; } } catch (RemoteException e) { Log.e(TAG, "Can't call .canProjectAudio() on valid IMediaProjection" + projection.asBinder(), e); return false; } return true; } private IMediaProjectionManager getProjectionService() { if (mProjectionService == null) { IBinder b = ServiceManager.getService(Context.MEDIA_PROJECTION_SERVICE); mProjectionService = IMediaProjectionManager.Stub.asInterface(b); } return mProjectionService; } /** * See {@link AudioManager#unregisterAudioPolicyAsync(AudioPolicy)} * Declared oneway * @param pcb nullable because on service interface */ public void unregisterAudioPolicyAsync(@Nullable IAudioPolicyCallback pcb) { unregisterAudioPolicy(pcb); } /** * See {@link AudioManager#unregisterAudioPolicy(AudioPolicy)} * @param pcb nullable because on service interface */ public void unregisterAudioPolicy(@Nullable IAudioPolicyCallback pcb) { if (pcb == null) { return; } unregisterAudioPolicyInt(pcb); } private void unregisterAudioPolicyInt(@NonNull IAudioPolicyCallback pcb) { mDynPolicyLogger.log((new AudioEventLogger.StringEvent("unregisterAudioPolicyAsync for " + pcb.asBinder()).printLog(TAG))); synchronized (mAudioPolicies) { AudioPolicyProxy app = mAudioPolicies.remove(pcb.asBinder()); if (app == null) { Slog.w(TAG, "Trying to unregister unknown audio policy for pid " + Binder.getCallingPid() + " / uid " + Binder.getCallingUid()); return; } else { pcb.asBinder().unlinkToDeath(app, 0/*flags*/); } app.release(); } // TODO implement clearing mix attribute matching info in native audio policy } /** * Checks whether caller has MODIFY_AUDIO_ROUTING permission, and the policy is registered. * @param errorMsg log warning if permission check failed. * @return null if the operation on the audio mixes should be cancelled. */ @GuardedBy("mAudioPolicies") private AudioPolicyProxy checkUpdateForPolicy(IAudioPolicyCallback pcb, String errorMsg) { // permission check final boolean hasPermissionForPolicy = (PackageManager.PERMISSION_GRANTED == mContext.checkCallingPermission( android.Manifest.permission.MODIFY_AUDIO_ROUTING)); if (!hasPermissionForPolicy) { Slog.w(TAG, errorMsg + " for pid " + + Binder.getCallingPid() + " / uid " + Binder.getCallingUid() + ", need MODIFY_AUDIO_ROUTING"); return null; } // policy registered? final AudioPolicyProxy app = mAudioPolicies.get(pcb.asBinder()); if (app == null) { Slog.w(TAG, errorMsg + " for pid " + + Binder.getCallingPid() + " / uid " + Binder.getCallingUid() + ", unregistered policy"); return null; } return app; } public int addMixForPolicy(AudioPolicyConfig policyConfig, IAudioPolicyCallback pcb) { if (DEBUG_AP) { Log.d(TAG, "addMixForPolicy for " + pcb.asBinder() + " with config:" + policyConfig); } synchronized (mAudioPolicies) { final AudioPolicyProxy app = checkUpdateForPolicy(pcb, "Cannot add AudioMix in audio policy"); if (app == null){ return AudioManager.ERROR; } return app.addMixes(policyConfig.getMixes()) == AudioSystem.SUCCESS ? AudioManager.SUCCESS : AudioManager.ERROR; } } public int removeMixForPolicy(AudioPolicyConfig policyConfig, IAudioPolicyCallback pcb) { if (DEBUG_AP) { Log.d(TAG, "removeMixForPolicy for " + pcb.asBinder() + " with config:" + policyConfig); } synchronized (mAudioPolicies) { final AudioPolicyProxy app = checkUpdateForPolicy(pcb, "Cannot add AudioMix in audio policy"); if (app == null) { return AudioManager.ERROR; } return app.removeMixes(policyConfig.getMixes()) == AudioSystem.SUCCESS ? AudioManager.SUCCESS : AudioManager.ERROR; } } /** see AudioPolicy.setUidDeviceAffinity() */ public int setUidDeviceAffinity(IAudioPolicyCallback pcb, int uid, @NonNull int[] deviceTypes, @NonNull String[] deviceAddresses) { if (DEBUG_AP) { Log.d(TAG, "setUidDeviceAffinity for " + pcb.asBinder() + " uid:" + uid); } synchronized (mAudioPolicies) { final AudioPolicyProxy app = checkUpdateForPolicy(pcb, "Cannot change device affinity in audio policy"); if (app == null) { return AudioManager.ERROR; } if (!app.hasMixRoutedToDevices(deviceTypes, deviceAddresses)) { return AudioManager.ERROR; } return app.setUidDeviceAffinities(uid, deviceTypes, deviceAddresses); } } /** see AudioPolicy.removeUidDeviceAffinity() */ public int removeUidDeviceAffinity(IAudioPolicyCallback pcb, int uid) { if (DEBUG_AP) { Log.d(TAG, "removeUidDeviceAffinity for " + pcb.asBinder() + " uid:" + uid); } synchronized (mAudioPolicies) { final AudioPolicyProxy app = checkUpdateForPolicy(pcb, "Cannot remove device affinity in audio policy"); if (app == null) { return AudioManager.ERROR; } return app.removeUidDeviceAffinities(uid); } } public int setFocusPropertiesForPolicy(int duckingBehavior, IAudioPolicyCallback pcb) { if (DEBUG_AP) Log.d(TAG, "setFocusPropertiesForPolicy() duck behavior=" + duckingBehavior + " policy " + pcb.asBinder()); synchronized (mAudioPolicies) { final AudioPolicyProxy app = checkUpdateForPolicy(pcb, "Cannot change audio policy focus properties"); if (app == null){ return AudioManager.ERROR; } if (!mAudioPolicies.containsKey(pcb.asBinder())) { Slog.e(TAG, "Cannot change audio policy focus properties, unregistered policy"); return AudioManager.ERROR; } if (duckingBehavior == AudioPolicy.FOCUS_POLICY_DUCKING_IN_POLICY) { // is there already one policy managing ducking? for (AudioPolicyProxy policy : mAudioPolicies.values()) { if (policy.mFocusDuckBehavior == AudioPolicy.FOCUS_POLICY_DUCKING_IN_POLICY) { Slog.e(TAG, "Cannot change audio policy ducking behavior, already handled"); return AudioManager.ERROR; } } } app.mFocusDuckBehavior = duckingBehavior; mMediaFocusControl.setDuckingInExtPolicyAvailable( duckingBehavior == AudioPolicy.FOCUS_POLICY_DUCKING_IN_POLICY); } return AudioManager.SUCCESS; } /** see AudioManager.hasRegisteredDynamicPolicy */ public boolean hasRegisteredDynamicPolicy() { synchronized (mAudioPolicies) { return !mAudioPolicies.isEmpty(); } } private final Object mExtVolumeControllerLock = new Object(); private IAudioPolicyCallback mExtVolumeController; private void setExtVolumeController(IAudioPolicyCallback apc) { if (!mContext.getResources().getBoolean( com.android.internal.R.bool.config_handleVolumeKeysInWindowManager)) { Log.e(TAG, "Cannot set external volume controller: device not set for volume keys" + " handled in PhoneWindowManager"); return; } synchronized (mExtVolumeControllerLock) { if (mExtVolumeController != null && !mExtVolumeController.asBinder().pingBinder()) { Log.e(TAG, "Cannot set external volume controller: existing controller"); } mExtVolumeController = apc; } } private void dumpAudioPolicies(PrintWriter pw) { pw.println("\nAudio policies:"); synchronized (mAudioPolicies) { for (AudioPolicyProxy policy : mAudioPolicies.values()) { pw.println(policy.toLogFriendlyString()); } } } //====================== // Audio policy callbacks from AudioSystem for dynamic policies //====================== private final AudioSystem.DynamicPolicyCallback mDynPolicyCallback = new AudioSystem.DynamicPolicyCallback() { public void onDynamicPolicyMixStateUpdate(String regId, int state) { if (!TextUtils.isEmpty(regId)) { sendMsg(mAudioHandler, MSG_DYN_POLICY_MIX_STATE_UPDATE, SENDMSG_QUEUE, state /*arg1*/, 0 /*arg2 ignored*/, regId /*obj*/, 0 /*delay*/); } } }; private void onDynPolicyMixStateUpdate(String regId, int state) { if (DEBUG_AP) Log.d(TAG, "onDynamicPolicyMixStateUpdate("+ regId + ", " + state +")"); synchronized (mAudioPolicies) { for (AudioPolicyProxy policy : mAudioPolicies.values()) { for (AudioMix mix : policy.getMixes()) { if (mix.getRegistration().equals(regId)) { try { policy.mPolicyCallback.notifyMixStateUpdate(regId, state); } catch (RemoteException e) { Log.e(TAG, "Can't call notifyMixStateUpdate() on IAudioPolicyCallback " + policy.mPolicyCallback.asBinder(), e); } return; } } } } } //====================== // Audio policy callbacks from AudioSystem for recording configuration updates //====================== private final RecordingActivityMonitor mRecordMonitor; public void registerRecordingCallback(IRecordingConfigDispatcher rcdb) { final boolean isPrivileged = (PackageManager.PERMISSION_GRANTED == mContext.checkCallingPermission( android.Manifest.permission.MODIFY_AUDIO_ROUTING)); mRecordMonitor.registerRecordingCallback(rcdb, isPrivileged); } public void unregisterRecordingCallback(IRecordingConfigDispatcher rcdb) { mRecordMonitor.unregisterRecordingCallback(rcdb); } public List getActiveRecordingConfigurations() { final boolean isPrivileged = (PackageManager.PERMISSION_GRANTED == mContext.checkCallingPermission( android.Manifest.permission.MODIFY_AUDIO_ROUTING)); return mRecordMonitor.getActiveRecordingConfigurations(isPrivileged); } //====================== // Audio recording state notification from clients //====================== /** * Track a recorder provided by the client */ public int trackRecorder(IBinder recorder) { return mRecordMonitor.trackRecorder(recorder); } /** * Receive an event from the client about a tracked recorder */ public void recorderEvent(int riid, int event) { mRecordMonitor.recorderEvent(riid, event); } /** * Stop tracking the recorder */ public void releaseRecorder(int riid) { mRecordMonitor.releaseRecorder(riid); } public void disableRingtoneSync(final int userId) { final int callingUserId = UserHandle.getCallingUserId(); if (callingUserId != userId) { mContext.enforceCallingOrSelfPermission(Manifest.permission.INTERACT_ACROSS_USERS_FULL, "disable sound settings syncing for another profile"); } final long token = Binder.clearCallingIdentity(); try { // Disable the sync setting so the profile uses its own sound settings. Settings.Secure.putIntForUser(mContentResolver, Settings.Secure.SYNC_PARENT_SOUNDS, 0 /* false */, userId); } finally { Binder.restoreCallingIdentity(token); } } //====================== // Audio playback notification //====================== private final PlaybackActivityMonitor mPlaybackMonitor; public void registerPlaybackCallback(IPlaybackConfigDispatcher pcdb) { final boolean isPrivileged = (PackageManager.PERMISSION_GRANTED == mContext.checkCallingOrSelfPermission( android.Manifest.permission.MODIFY_AUDIO_ROUTING)); mPlaybackMonitor.registerPlaybackCallback(pcdb, isPrivileged); } public void unregisterPlaybackCallback(IPlaybackConfigDispatcher pcdb) { mPlaybackMonitor.unregisterPlaybackCallback(pcdb); } public List getActivePlaybackConfigurations() { final boolean isPrivileged = (PackageManager.PERMISSION_GRANTED == mContext.checkCallingOrSelfPermission( android.Manifest.permission.MODIFY_AUDIO_ROUTING)); return mPlaybackMonitor.getActivePlaybackConfigurations(isPrivileged); } public int trackPlayer(PlayerBase.PlayerIdCard pic) { return mPlaybackMonitor.trackPlayer(pic); } public void playerAttributes(int piid, AudioAttributes attr) { mPlaybackMonitor.playerAttributes(piid, attr, Binder.getCallingUid()); } public void playerEvent(int piid, int event) { mPlaybackMonitor.playerEvent(piid, event, Binder.getCallingUid()); } public void playerHasOpPlayAudio(int piid, boolean hasOpPlayAudio) { mPlaybackMonitor.playerHasOpPlayAudio(piid, hasOpPlayAudio, Binder.getCallingUid()); } public void releasePlayer(int piid) { mPlaybackMonitor.releasePlayer(piid, Binder.getCallingUid()); } //====================== // Audio device management //====================== private final AudioDeviceBroker mDeviceBroker; //====================== // Audio policy proxy //====================== private static final class AudioDeviceArray { final @NonNull int[] mDeviceTypes; final @NonNull String[] mDeviceAddresses; AudioDeviceArray(@NonNull int[] types, @NonNull String[] addresses) { mDeviceTypes = types; mDeviceAddresses = addresses; } } /** * This internal class inherits from AudioPolicyConfig, each instance contains all the * mixes of an AudioPolicy and their configurations. */ public class AudioPolicyProxy extends AudioPolicyConfig implements IBinder.DeathRecipient { private static final String TAG = "AudioPolicyProxy"; final IAudioPolicyCallback mPolicyCallback; final boolean mHasFocusListener; final boolean mIsVolumeController; final HashMap mUidDeviceAffinities = new HashMap(); final IMediaProjection mProjection; private final class UnregisterOnStopCallback extends IMediaProjectionCallback.Stub { public void onStop() { unregisterAudioPolicyAsync(mPolicyCallback); } }; UnregisterOnStopCallback mProjectionCallback; /** * Audio focus ducking behavior for an audio policy. * This variable reflects the value that was successfully set in * {@link AudioService#setFocusPropertiesForPolicy(int, IAudioPolicyCallback)}. This * implies that a value of FOCUS_POLICY_DUCKING_IN_POLICY means the corresponding policy * is handling ducking for audio focus. */ int mFocusDuckBehavior = AudioPolicy.FOCUS_POLICY_DUCKING_DEFAULT; boolean mIsFocusPolicy = false; boolean mIsTestFocusPolicy = false; AudioPolicyProxy(AudioPolicyConfig config, IAudioPolicyCallback token, boolean hasFocusListener, boolean isFocusPolicy, boolean isTestFocusPolicy, boolean isVolumeController, IMediaProjection projection) { super(config); setRegistration(new String(config.hashCode() + ":ap:" + mAudioPolicyCounter++)); mPolicyCallback = token; mHasFocusListener = hasFocusListener; mIsVolumeController = isVolumeController; mProjection = projection; if (mHasFocusListener) { mMediaFocusControl.addFocusFollower(mPolicyCallback); // can only ever be true if there is a focus listener if (isFocusPolicy) { mIsFocusPolicy = true; mIsTestFocusPolicy = isTestFocusPolicy; mMediaFocusControl.setFocusPolicy(mPolicyCallback, mIsTestFocusPolicy); } } if (mIsVolumeController) { setExtVolumeController(mPolicyCallback); } if (mProjection != null) { mProjectionCallback = new UnregisterOnStopCallback(); try { mProjection.registerCallback(mProjectionCallback); } catch (RemoteException e) { release(); throw new IllegalStateException("MediaProjection callback registration failed, " + "could not link to " + projection + " binder death", e); } } int status = connectMixes(); if (status != AudioSystem.SUCCESS) { release(); throw new IllegalStateException("Could not connect mix, error: " + status); } } public void binderDied() { synchronized (mAudioPolicies) { Log.i(TAG, "audio policy " + mPolicyCallback + " died"); release(); mAudioPolicies.remove(mPolicyCallback.asBinder()); } if (mIsVolumeController) { synchronized (mExtVolumeControllerLock) { mExtVolumeController = null; } } } String getRegistrationId() { return getRegistration(); } void release() { if (mIsFocusPolicy) { mMediaFocusControl.unsetFocusPolicy(mPolicyCallback, mIsTestFocusPolicy); } if (mFocusDuckBehavior == AudioPolicy.FOCUS_POLICY_DUCKING_IN_POLICY) { mMediaFocusControl.setDuckingInExtPolicyAvailable(false); } if (mHasFocusListener) { mMediaFocusControl.removeFocusFollower(mPolicyCallback); } if (mProjectionCallback != null) { try { mProjection.unregisterCallback(mProjectionCallback); } catch (RemoteException e) { Log.e(TAG, "Fail to unregister Audiopolicy callback from MediaProjection"); } } final long identity = Binder.clearCallingIdentity(); AudioSystem.registerPolicyMixes(mMixes, false); Binder.restoreCallingIdentity(identity); } boolean hasMixAffectingUsage(int usage) { for (AudioMix mix : mMixes) { if (mix.isAffectingUsage(usage)) { return true; } } return false; } // Verify all the devices in the array are served by mixes defined in this policy boolean hasMixRoutedToDevices(@NonNull int[] deviceTypes, @NonNull String[] deviceAddresses) { for (int i = 0; i < deviceTypes.length; i++) { boolean hasDevice = false; for (AudioMix mix : mMixes) { // this will check both that the mix has ROUTE_FLAG_RENDER and the device // is reached by this mix if (mix.isRoutedToDevice(deviceTypes[i], deviceAddresses[i])) { hasDevice = true; break; } } if (!hasDevice) { return false; } } return true; } int addMixes(@NonNull ArrayList mixes) { // TODO optimize to not have to unregister the mixes already in place synchronized (mMixes) { AudioSystem.registerPolicyMixes(mMixes, false); this.add(mixes); return AudioSystem.registerPolicyMixes(mMixes, true); } } int removeMixes(@NonNull ArrayList mixes) { // TODO optimize to not have to unregister the mixes already in place synchronized (mMixes) { AudioSystem.registerPolicyMixes(mMixes, false); this.remove(mixes); return AudioSystem.registerPolicyMixes(mMixes, true); } } int connectMixes() { final long identity = Binder.clearCallingIdentity(); int status = AudioSystem.registerPolicyMixes(mMixes, true); Binder.restoreCallingIdentity(identity); return status; } int setUidDeviceAffinities(int uid, @NonNull int[] types, @NonNull String[] addresses) { final Integer Uid = new Integer(uid); int res; if (mUidDeviceAffinities.remove(Uid) != null) { final long identity = Binder.clearCallingIdentity(); res = AudioSystem.removeUidDeviceAffinities(uid); Binder.restoreCallingIdentity(identity); if (res != AudioSystem.SUCCESS) { Log.e(TAG, "AudioSystem. removeUidDeviceAffinities(" + uid + ") failed, " + " cannot call AudioSystem.setUidDeviceAffinities"); return AudioManager.ERROR; } } final long identity = Binder.clearCallingIdentity(); res = AudioSystem.setUidDeviceAffinities(uid, types, addresses); Binder.restoreCallingIdentity(identity); if (res == AudioSystem.SUCCESS) { mUidDeviceAffinities.put(Uid, new AudioDeviceArray(types, addresses)); return AudioManager.SUCCESS; } Log.e(TAG, "AudioSystem. setUidDeviceAffinities(" + uid + ") failed"); return AudioManager.ERROR; } int removeUidDeviceAffinities(int uid) { if (mUidDeviceAffinities.remove(new Integer(uid)) != null) { final long identity = Binder.clearCallingIdentity(); final int res = AudioSystem.removeUidDeviceAffinities(uid); Binder.restoreCallingIdentity(identity); if (res == AudioSystem.SUCCESS) { return AudioManager.SUCCESS; } } Log.e(TAG, "AudioSystem. removeUidDeviceAffinities failed"); return AudioManager.ERROR; } /** @return human readable debug informations summarizing the state of the object. */ public String toLogFriendlyString() { String textDump = super.toLogFriendlyString(); textDump += " Proxy:\n"; textDump += " is focus policy= " + mIsFocusPolicy + "\n"; if (mIsFocusPolicy) { textDump += " focus duck behaviour= " + mFocusDuckBehavior + "\n"; textDump += " is test focus policy= " + mIsTestFocusPolicy + "\n"; textDump += " has focus listener= " + mHasFocusListener + "\n"; } textDump += " media projection= " + mProjection + "\n"; return textDump; } }; //====================== // Audio policy: focus //====================== /** */ public int dispatchFocusChange(AudioFocusInfo afi, int focusChange, IAudioPolicyCallback pcb) { if (afi == null) { throw new IllegalArgumentException("Illegal null AudioFocusInfo"); } if (pcb == null) { throw new IllegalArgumentException("Illegal null AudioPolicy callback"); } synchronized (mAudioPolicies) { if (!mAudioPolicies.containsKey(pcb.asBinder())) { throw new IllegalStateException("Unregistered AudioPolicy for focus dispatch"); } return mMediaFocusControl.dispatchFocusChange(afi, focusChange); } } public void setFocusRequestResultFromExtPolicy(AudioFocusInfo afi, int requestResult, IAudioPolicyCallback pcb) { if (afi == null) { throw new IllegalArgumentException("Illegal null AudioFocusInfo"); } if (pcb == null) { throw new IllegalArgumentException("Illegal null AudioPolicy callback"); } synchronized (mAudioPolicies) { if (!mAudioPolicies.containsKey(pcb.asBinder())) { throw new IllegalStateException("Unregistered AudioPolicy for external focus"); } mMediaFocusControl.setFocusRequestResultFromExtPolicy(afi, requestResult); } } //====================== // Audioserver state displatch //====================== private class AsdProxy implements IBinder.DeathRecipient { private final IAudioServerStateDispatcher mAsd; AsdProxy(IAudioServerStateDispatcher asd) { mAsd = asd; } public void binderDied() { synchronized (mAudioServerStateListeners) { mAudioServerStateListeners.remove(mAsd.asBinder()); } } IAudioServerStateDispatcher callback() { return mAsd; } } private HashMap mAudioServerStateListeners = new HashMap(); private void checkMonitorAudioServerStatePermission() { if (!(mContext.checkCallingOrSelfPermission( android.Manifest.permission.MODIFY_PHONE_STATE) == PackageManager.PERMISSION_GRANTED || mContext.checkCallingOrSelfPermission( android.Manifest.permission.MODIFY_AUDIO_ROUTING) == PackageManager.PERMISSION_GRANTED)) { throw new SecurityException("Not allowed to monitor audioserver state"); } } public void registerAudioServerStateDispatcher(IAudioServerStateDispatcher asd) { checkMonitorAudioServerStatePermission(); synchronized (mAudioServerStateListeners) { if (mAudioServerStateListeners.containsKey(asd.asBinder())) { Slog.w(TAG, "Cannot re-register audio server state dispatcher"); return; } AsdProxy asdp = new AsdProxy(asd); try { asd.asBinder().linkToDeath(asdp, 0/*flags*/); } catch (RemoteException e) { } mAudioServerStateListeners.put(asd.asBinder(), asdp); } } public void unregisterAudioServerStateDispatcher(IAudioServerStateDispatcher asd) { checkMonitorAudioServerStatePermission(); synchronized (mAudioServerStateListeners) { AsdProxy asdp = mAudioServerStateListeners.remove(asd.asBinder()); if (asdp == null) { Slog.w(TAG, "Trying to unregister unknown audioserver state dispatcher for pid " + Binder.getCallingPid() + " / uid " + Binder.getCallingUid()); return; } else { asd.asBinder().unlinkToDeath(asdp, 0/*flags*/); } } } public boolean isAudioServerRunning() { checkMonitorAudioServerStatePermission(); return (AudioSystem.checkAudioFlinger() == AudioSystem.AUDIO_STATUS_OK); } //====================== // misc //====================== private final HashMap mAudioPolicies = new HashMap(); @GuardedBy("mAudioPolicies") private int mAudioPolicyCounter = 0; }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy