/*
|
* 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.
|
* <p>
|
* 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<String> SOUND_EFFECT_FILES = new ArrayList<String>();
|
|
/* 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<SetModeDeathHandler> mSetModeDeathHandlers =
|
new ArrayList<SetModeDeathHandler>();
|
|
// 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 maxRingVolume = SystemProperties.getInt("ro.config.ring_vol_steps", -1);
|
if (maxRingVolume != -1) {
|
MAX_STREAM_VOLUME[AudioSystem.STREAM_RING] = maxRingVolume;
|
}
|
|
int defaultRingVolume = SystemProperties.getInt("ro.config.ring_vol_default", -1);
|
if (defaultRingVolume != -1 &&
|
defaultRingVolume <= MAX_STREAM_VOLUME[AudioSystem.STREAM_RING]) {
|
AudioSystem.DEFAULT_STREAM_VOLUME[AudioSystem.STREAM_RING] = defaultRingVolume;
|
} else {
|
AudioSystem.DEFAULT_STREAM_VOLUME[AudioSystem.STREAM_RING] =
|
MAX_STREAM_VOLUME[AudioSystem.STREAM_RING];
|
}
|
|
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<String> 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<AudioProductStrategy> getAudioProductStrategies() {
|
return AudioProductStrategy.getAudioProductStrategies();
|
}
|
|
/**
|
* @return the List of {@link android.media.audiopolicy.AudioVolumeGroup} discovered from the
|
* platform configuration file.
|
*/
|
@NonNull
|
public List<AudioVolumeGroup> 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<Integer> 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<Integer> 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<AudioPlaybackConfiguration> 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<AudioPlaybackConfiguration> 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<RmtSbmxFullVolDeathHandler> 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<RmtSbmxFullVolDeathHandler> it = mRmtSbmxFullVolDeathHandlers.iterator();
|
while (it.hasNext()) {
|
if (it.next().isHandlerFor(cb)) {
|
return true;
|
}
|
}
|
return false;
|
}
|
|
private int mRmtSbmxFullVolRefCount = 0;
|
private ArrayList<RmtSbmxFullVolDeathHandler> mRmtSbmxFullVolDeathHandlers =
|
new ArrayList<RmtSbmxFullVolDeathHandler>();
|
|
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) {
|
enforceModifyAudioRoutingPermission();
|
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<Integer> mSamples = new ArrayList<Integer>();
|
|
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<Integer>) 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<AudioPlaybackConfiguration>) 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<AudioPolicyProxy> 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<ResolveInfo> 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<PackageInfo> 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<AudioRecordingConfiguration> 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<AudioPlaybackConfiguration> 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<Integer, AudioDeviceArray> mUidDeviceAffinities =
|
new HashMap<Integer, AudioDeviceArray>();
|
|
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<AudioMix> 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<AudioMix> 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<IBinder, AsdProxy> mAudioServerStateListeners =
|
new HashMap<IBinder, AsdProxy>();
|
|
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<IBinder, AudioPolicyProxy> mAudioPolicies =
|
new HashMap<IBinder, AudioPolicyProxy>();
|
@GuardedBy("mAudioPolicies")
|
private int mAudioPolicyCounter = 0;
|
}
|