summaryrefslogtreecommitdiffstats
path: root/media
diff options
context:
space:
mode:
authorJohn Spurlock <jspurlock@google.com>2015-02-06 19:46:04 -0500
committerJohn Spurlock <jspurlock@google.com>2015-02-09 17:13:38 -0500
commit6156017c2217d0fbbbb03434986250ec6bbd69d8 (patch)
tree2bf29bdfad945edeeb161b9ef9bb6b8108093042 /media
parent7d85bc4c435bc37284a814fb8baf53980396ab5b (diff)
downloadframeworks_base-6156017c2217d0fbbbb03434986250ec6bbd69d8.zip
frameworks_base-6156017c2217d0fbbbb03434986250ec6bbd69d8.tar.gz
frameworks_base-6156017c2217d0fbbbb03434986250ec6bbd69d8.tar.bz2
Move AudioService to services.
...and a few dependencies. Move remaining shared items to AudioSystem. Change-Id: Ib9623ff867678d34977337856bb0156e8cdaeeb5
Diffstat (limited to 'media')
-rw-r--r--media/java/android/media/AudioFocusInfo.java5
-rw-r--r--media/java/android/media/AudioManager.java7
-rw-r--r--media/java/android/media/AudioRoutesInfo.java28
-rw-r--r--media/java/android/media/AudioService.java5989
-rw-r--r--media/java/android/media/AudioSystem.java104
-rw-r--r--media/java/android/media/FocusRequester.java328
-rw-r--r--media/java/android/media/MediaFocusControl.java2197
-rw-r--r--media/java/android/media/MediaRouter.java24
-rw-r--r--media/java/android/media/PlayerRecord.java357
9 files changed, 136 insertions, 8903 deletions
diff --git a/media/java/android/media/AudioFocusInfo.java b/media/java/android/media/AudioFocusInfo.java
index fbdda3c..540c328 100644
--- a/media/java/android/media/AudioFocusInfo.java
+++ b/media/java/android/media/AudioFocusInfo.java
@@ -45,8 +45,9 @@ public final class AudioFocusInfo implements Parcelable {
* @param gainRequest
* @param lossReceived
* @param flags
+ * @hide
*/
- AudioFocusInfo(AudioAttributes aa, String clientId, String packageName,
+ public AudioFocusInfo(AudioAttributes aa, String clientId, String packageName,
int gainRequest, int lossReceived, int flags) {
mAttributes = aa == null ? new AudioAttributes.Builder().build() : aa;
mClientId = clientId == null ? "" : clientId;
@@ -91,7 +92,7 @@ public final class AudioFocusInfo implements Parcelable {
public int getLossReceived() { return mLossReceived; }
/** @hide */
- void clearLossReceived() { mLossReceived = 0; }
+ public void clearLossReceived() { mLossReceived = 0; }
/**
* The flags set in the audio focus request.
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index 07b19a4..7084eba 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -663,8 +663,7 @@ public class AudioManager {
int keyCode = event.getKeyCode();
if (keyCode != KeyEvent.KEYCODE_VOLUME_DOWN && keyCode != KeyEvent.KEYCODE_VOLUME_UP
&& keyCode != KeyEvent.KEYCODE_VOLUME_MUTE
- && mVolumeKeyUpTime + AudioService.PLAY_SOUND_DELAY
- > SystemClock.uptimeMillis()) {
+ && mVolumeKeyUpTime + AudioSystem.PLAY_SOUND_DELAY > SystemClock.uptimeMillis()) {
/*
* The user has hit another key during the delay (e.g., 300ms)
* since the last volume key up, so cancel any sounds.
@@ -2501,7 +2500,7 @@ public class AudioManager {
service.requestAudioFocus(new AudioAttributes.Builder()
.setInternalLegacyStreamType(streamType).build(),
durationHint, mICallBack, null,
- MediaFocusControl.IN_VOICE_COMM_FOCUS_ID,
+ AudioSystem.IN_VOICE_COMM_FOCUS_ID,
mContext.getOpPackageName(),
AUDIOFOCUS_FLAG_LOCK,
null /* policy token */);
@@ -2519,7 +2518,7 @@ public class AudioManager {
public void abandonAudioFocusForCall() {
IAudioService service = getService();
try {
- service.abandonAudioFocus(null, MediaFocusControl.IN_VOICE_COMM_FOCUS_ID,
+ service.abandonAudioFocus(null, AudioSystem.IN_VOICE_COMM_FOCUS_ID,
null /*AudioAttributes, legacy behavior*/);
} catch (RemoteException e) {
Log.e(TAG, "Can't call abandonAudioFocusForCall() on AudioService:", e);
diff --git a/media/java/android/media/AudioRoutesInfo.java b/media/java/android/media/AudioRoutesInfo.java
index 3e0ec07..6ae0d46 100644
--- a/media/java/android/media/AudioRoutesInfo.java
+++ b/media/java/android/media/AudioRoutesInfo.java
@@ -25,27 +25,27 @@ import android.text.TextUtils;
* @hide
*/
public class AudioRoutesInfo implements Parcelable {
- static final int MAIN_SPEAKER = 0;
- static final int MAIN_HEADSET = 1<<0;
- static final int MAIN_HEADPHONES = 1<<1;
- static final int MAIN_DOCK_SPEAKERS = 1<<2;
- static final int MAIN_HDMI = 1<<3;
- static final int MAIN_USB = 1<<4;
+ public static final int MAIN_SPEAKER = 0;
+ public static final int MAIN_HEADSET = 1<<0;
+ public static final int MAIN_HEADPHONES = 1<<1;
+ public static final int MAIN_DOCK_SPEAKERS = 1<<2;
+ public static final int MAIN_HDMI = 1<<3;
+ public static final int MAIN_USB = 1<<4;
- CharSequence mBluetoothName;
- int mMainType = MAIN_SPEAKER;
+ public CharSequence bluetoothName;
+ public int mainType = MAIN_SPEAKER;
public AudioRoutesInfo() {
}
public AudioRoutesInfo(AudioRoutesInfo o) {
- mBluetoothName = o.mBluetoothName;
- mMainType = o.mMainType;
+ bluetoothName = o.bluetoothName;
+ mainType = o.mainType;
}
AudioRoutesInfo(Parcel src) {
- mBluetoothName = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(src);
- mMainType = src.readInt();
+ bluetoothName = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(src);
+ mainType = src.readInt();
}
@Override
@@ -55,8 +55,8 @@ public class AudioRoutesInfo implements Parcelable {
@Override
public void writeToParcel(Parcel dest, int flags) {
- TextUtils.writeToParcel(mBluetoothName, dest, flags);
- dest.writeInt(mMainType);
+ TextUtils.writeToParcel(bluetoothName, dest, flags);
+ dest.writeInt(mainType);
}
public static final Parcelable.Creator<AudioRoutesInfo> CREATOR
diff --git a/media/java/android/media/AudioService.java b/media/java/android/media/AudioService.java
deleted file mode 100644
index edb6923..0000000
--- a/media/java/android/media/AudioService.java
+++ /dev/null
@@ -1,5989 +0,0 @@
-/*
- * 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 android.media;
-
-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 android.app.ActivityManager;
-import android.app.ActivityManagerNative;
-import android.app.AppOpsManager;
-import android.app.KeyguardManager;
-import android.bluetooth.BluetoothA2dp;
-import android.bluetooth.BluetoothAdapter;
-import android.bluetooth.BluetoothClass;
-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.PackageManager;
-import android.content.res.Configuration;
-import android.content.res.Resources;
-import android.content.res.XmlResourceParser;
-import android.database.ContentObserver;
-import android.hardware.hdmi.HdmiControlManager;
-import android.hardware.hdmi.HdmiPlaybackClient;
-import android.hardware.hdmi.HdmiTvClient;
-import android.hardware.usb.UsbManager;
-import android.media.MediaPlayer.OnCompletionListener;
-import android.media.MediaPlayer.OnErrorListener;
-import android.media.audiopolicy.AudioMix;
-import android.media.audiopolicy.AudioPolicy;
-import android.media.audiopolicy.AudioPolicyConfig;
-import android.media.audiopolicy.IAudioPolicyCallback;
-import android.os.Binder;
-import android.os.Build;
-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.RemoteCallbackList;
-import android.os.RemoteException;
-import android.os.SystemClock;
-import android.os.SystemProperties;
-import android.os.UserHandle;
-import android.os.Vibrator;
-import android.provider.Settings;
-import android.provider.Settings.System;
-import android.telecom.TelecomManager;
-import android.text.TextUtils;
-import android.util.Log;
-import android.util.MathUtils;
-import android.util.Slog;
-import android.view.KeyEvent;
-import android.view.OrientationEventListener;
-import android.view.Surface;
-import android.view.WindowManager;
-import android.view.accessibility.AccessibilityManager;
-
-import com.android.internal.util.XmlUtils;
-import com.android.server.LocalServices;
-
-import org.xmlpull.v1.XmlPullParserException;
-
-import java.io.FileDescriptor;
-import java.io.IOException;
-import java.io.PrintWriter;
-import java.lang.reflect.Field;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-import java.util.NoSuchElementException;
-import java.util.Objects;
-import java.util.Set;
-import java.util.concurrent.ConcurrentHashMap;
-
-/**
- * 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 {
-
- private static final String TAG = "AudioService";
-
- /** Debug audio mode */
- protected static final boolean DEBUG_MODE = Log.isLoggable(TAG + ".MOD", Log.DEBUG);
-
- /** Debug audio policy feature */
- protected static final boolean DEBUG_AP = Log.isLoggable(TAG + ".AP", Log.DEBUG);
-
- /** Debug volumes */
- protected static final boolean DEBUG_VOL = Log.isLoggable(TAG + ".VOL", Log.DEBUG);
-
- /** debug calls to media session apis */
- private static final boolean DEBUG_SESSIONS = Log.isLoggable(TAG + ".SESSIONS", Log.DEBUG);
-
- /** Allow volume changes to set ringer mode to silent? */
- private static final boolean VOLUME_SETS_RINGER_MODE_SILENT = false;
-
- /** In silent mode, are volume adjustments (raises) prevented? */
- private static final boolean PREVENT_VOLUME_ADJUSTMENT_IF_SILENT = true;
-
- /** 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;
-
- /**
- * The delay before playing a sound. This small period exists so the user
- * can press another key (non-volume keys, too) to have it NOT be audible.
- * <p>
- * PhoneWindow will implement this part.
- */
- public static final int PLAY_SOUND_DELAY = 300;
-
- /**
- * 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 has no specific capabilities
- public static final int PLATFORM_DEFAULT = 0;
- // the platform is voice call capable (a phone)
- public static final int PLATFORM_VOICE = 1;
- // the platform is a television or a set-top box
- public static final int PLATFORM_TELEVISION = 2;
- // the platform type affects volume and silent mode behavior
- private final int mPlatformType;
-
- private boolean isPlatformVoice() {
- return mPlatformType == PLATFORM_VOICE;
- }
-
- private boolean isPlatformTelevision() {
- return mPlatformType == PLATFORM_TELEVISION;
- }
-
- /** 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_MASTER_VOLUME = 2;
- private static final int MSG_PERSIST_RINGER_MODE = 3;
- private static final int MSG_MEDIA_SERVER_DIED = 4;
- private static final int MSG_PLAY_SOUND_EFFECT = 5;
- private static final int MSG_BTA2DP_DOCK_TIMEOUT = 6;
- 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_PERSIST_MASTER_VOLUME_MUTE = 11;
- private static final int MSG_REPORT_NEW_ROUTES = 12;
- private static final int MSG_SET_FORCE_BT_A2DP_USE = 13;
- private static final int MSG_CHECK_MUSIC_ACTIVE = 14;
- private static final int MSG_BROADCAST_AUDIO_BECOMING_NOISY = 15;
- private static final int MSG_CONFIGURE_SAFE_MEDIA_VOLUME = 16;
- private static final int MSG_CONFIGURE_SAFE_MEDIA_VOLUME_FORCED = 17;
- private static final int MSG_PERSIST_SAFE_VOLUME_STATE = 18;
- private static final int MSG_BROADCAST_BT_CONNECTION_STATE = 19;
- private static final int MSG_UNLOAD_SOUND_EFFECTS = 20;
- private static final int MSG_SYSTEM_READY = 21;
- private static final int MSG_PERSIST_MUSIC_ACTIVE_MS = 22;
- private static final int MSG_PERSIST_MICROPHONE_MUTE = 23;
- private static final int MSG_UNMUTE_STREAM = 24;
- // 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_SET_WIRED_DEVICE_CONNECTION_STATE = 100;
- private static final int MSG_SET_A2DP_SRC_CONNECTION_STATE = 101;
- private static final int MSG_SET_A2DP_SINK_CONNECTION_STATE = 102;
- // end of messages handled under wakelock
-
- private static final int BTA2DP_DOCK_TIMEOUT_MILLIS = 8000;
- // Timeout for connection to bluetooth headset service
- private static final int BT_HEADSET_CNCT_TIMEOUT_MS = 3000;
-
- /** @see AudioSystemThread */
- private AudioSystemThread mAudioSystemThread;
- /** @see AudioHandler */
- private AudioHandler mAudioHandler;
- /** @see VolumeStreamState */
- private VolumeStreamState[] mStreamStates;
- 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;
-
- // Internally master volume is a float in the 0.0 - 1.0 range,
- // but to support integer based AudioManager API we translate it to 0 - 100
- private static final int MAX_MASTER_VOLUME = 100;
-
- // Maximum volume adjust steps allowed in a single batch call.
- private static final int MAX_BATCH_VOLUME_ADJUST_STEPS = 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];
-
- /** @hide Maximum volume index values for audio streams */
- private 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
- };
-
- private static int[] DEFAULT_STREAM_VOLUME = new int[] {
- 4, // STREAM_VOICE_CALL
- 7, // STREAM_SYSTEM
- 5, // STREAM_RING
- 11, // STREAM_MUSIC
- 6, // STREAM_ALARM
- 5, // STREAM_NOTIFICATION
- 7, // STREAM_BLUETOOTH_SCO
- 7, // STREAM_SYSTEM_ENFORCED
- 11, // STREAM_DTMF
- 11 // STREAM_TTS
- };
-
- /* 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
- };
- 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
- };
- 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
- };
- private int[] mStreamVolumeAlias;
-
- /**
- * Map AudioSystem.STREAM_* constants to app ops. This should be used
- * after mapping through mStreamVolumeAlias.
- */
- private static final int[] STEAM_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
- };
-
- private final boolean mUseFixedVolume;
-
- // stream names used by dumpStreamStates()
- private static final String[] STREAM_NAMES = new String[] {
- "STREAM_VOICE_CALL",
- "STREAM_SYSTEM",
- "STREAM_RING",
- "STREAM_MUSIC",
- "STREAM_ALARM",
- "STREAM_NOTIFICATION",
- "STREAM_BLUETOOTH_SCO",
- "STREAM_SYSTEM_ENFORCED",
- "STREAM_DTMF",
- "STREAM_TTS"
- };
-
- public static final int DEFAULT_MUTE_STREAMS_AFFECTED =
- (1 << AudioSystem.STREAM_MUSIC) |
- (1 << AudioSystem.STREAM_RING) |
- (1 << AudioSystem.STREAM_NOTIFICATION) |
- (1 << AudioSystem.STREAM_SYSTEM);
-
- private final AudioSystem.ErrorCallback mAudioSystemCallback = new AudioSystem.ErrorCallback() {
- public void onError(int error) {
- switch (error) {
- case AudioSystem.AUDIO_STATUS_SERVER_DIED:
- sendMsg(mAudioHandler, MSG_MEDIA_SERVER_DIED,
- SENDMSG_NOOP, 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}.
- */
- // protected by mSettingsLock
- private int mRingerMode; // internal ringer mode, affects muting of underlying streams
- private int mRingerModeExternal = -1; // reported ringer mode to outside clients (AudioManager)
-
- /** @see System#MODE_RINGER_STREAMS_AFFECTED */
- private int mRingerModeAffectedStreams = 0;
-
- // Streams currently muted by ringer mode
- private int mRingerModeMutedStreams;
-
- /** @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;
-
- // Broadcast receiver for device connections intent broadcasts
- private final BroadcastReceiver mReceiver = new AudioServiceBroadcastReceiver();
-
- // Devices currently connected
- private final HashMap <Integer, String> mConnectedDevices = new HashMap <Integer, String>();
-
- // Forced device usage for communications
- private int mForcedUseForComm;
-
- // True if we have master volume support
- private final boolean mUseMasterVolume;
-
- private final int[] mMasterVolumeRamp;
-
- // List of binder death handlers for setMode() client processes.
- // The last process to have called setMode() is at the top of the list.
- private final ArrayList <SetModeDeathHandler> mSetModeDeathHandlers = new ArrayList <SetModeDeathHandler>();
-
- // List of clients having issued a SCO start request
- private final ArrayList <ScoClient> mScoClients = new ArrayList <ScoClient>();
-
- // BluetoothHeadset API to control SCO connection
- private BluetoothHeadset mBluetoothHeadset;
-
- // Bluetooth headset device
- private BluetoothDevice mBluetoothHeadsetDevice;
-
- // Indicate if SCO audio connection is currently active and if the initiator is
- // audio service (internal) or bluetooth headset (external)
- private int mScoAudioState;
- // SCO audio state is not active
- private static final int SCO_STATE_INACTIVE = 0;
- // SCO audio activation request waiting for headset service to connect
- private static final int SCO_STATE_ACTIVATE_REQ = 1;
- // SCO audio state is active or starting due to a request from AudioManager API
- private static final int SCO_STATE_ACTIVE_INTERNAL = 3;
- // SCO audio deactivation request waiting for headset service to connect
- private static final int SCO_STATE_DEACTIVATE_REQ = 5;
-
- // SCO audio state is active due to an action in BT handsfree (either voice recognition or
- // in call audio)
- private static final int SCO_STATE_ACTIVE_EXTERNAL = 2;
- // Deactivation request for all SCO connections (initiated by audio mode change)
- // waiting for headset service to connect
- private static final int SCO_STATE_DEACTIVATE_EXT_REQ = 4;
-
- // Indicates the mode used for SCO audio connection. The mode is virtual call if the request
- // originated from an app targeting an API version before JB MR2 and raw audio after that.
- private int mScoAudioMode;
- // SCO audio mode is undefined
- private static final int SCO_MODE_UNDEFINED = -1;
- // SCO audio mode is virtual voice call (BluetoothHeadset.startScoUsingVirtualVoiceCall())
- private static final int SCO_MODE_VIRTUAL_CALL = 0;
- // SCO audio mode is raw audio (BluetoothHeadset.connectAudio())
- private static final int SCO_MODE_RAW = 1;
- // SCO audio mode is Voice Recognition (BluetoothHeadset.startVoiceRecognition())
- private static final int SCO_MODE_VR = 2;
-
- private static final int SCO_MODE_MAX = 2;
-
- // Current connection state indicated by bluetooth headset
- private int mScoConnectionState;
-
- // true if boot sequence has been completed
- private boolean mSystemReady;
- // 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;
- // Keyguard manager proxy
- private KeyguardManager mKeyguardManager;
- // mVolumeControlStream is set by VolumePanel to temporarily force the stream type which volume
- // is controlled by Vol keys.
- private int mVolumeControlStream = -1;
- 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;
-
- private int mDeviceOrientation = Configuration.ORIENTATION_UNDEFINED;
- private int mDeviceRotation = Surface.ROTATION_0;
-
- // Request to override default use of A2DP for media.
- private boolean mBluetoothA2dpEnabled;
- private final Object mBluetoothA2dpEnabledLock = new Object();
-
- // Monitoring of audio routes. Protected by mCurAudioRoutes.
- final AudioRoutesInfo mCurAudioRoutes = new AudioRoutesInfo();
- final RemoteCallbackList<IAudioRoutesObserver> mRoutesObservers
- = new RemoteCallbackList<IAudioRoutesObserver>();
-
- // Devices for which the volume is fixed and VolumePanel slider should be disabled
- 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;
- int mFullVolumeDevices = 0;
-
- // TODO merge orientation and rotation
- private final boolean mMonitorOrientation;
- 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;
-
- // Reference to BluetoothA2dp to query for AbsoluteVolume.
- private BluetoothA2dp mA2dp;
- // lock always taken synchronized on mConnectedDevices
- private final Object mA2dpAvrcpLock = new Object();
- // If absolute volume is supported in AVRCP device
- private boolean mAvrcpAbsVolSupported = false;
-
- private AudioOrientationEventListener mOrientationListener;
-
- private static Long mLastDeviceConnectMsgTime = new Long(0);
-
- private AudioManagerInternal.RingerModeDelegate mRingerModeDelegate;
-
- // Intent "extra" data keys.
- public static final String CONNECT_INTENT_KEY_PORT_NAME = "portName";
- public static final String CONNECT_INTENT_KEY_STATE = "state";
- public static final String CONNECT_INTENT_KEY_ADDRESS = "address";
- public static final String CONNECT_INTENT_KEY_HAS_PLAYBACK = "hasPlayback";
- public static final String CONNECT_INTENT_KEY_HAS_CAPTURE = "hasCapture";
- public static final String CONNECT_INTENT_KEY_HAS_MIDI = "hasMIDI";
- public static final String CONNECT_INTENT_KEY_DEVICE_CLASS = "class";
-
- // Defines the format for the connection "address" for ALSA devices
- public static String makeAlsaAddressString(int card, int device) {
- return "card=" + card + ";device=" + device + ";";
- }
-
- ///////////////////////////////////////////////////////////////////////////
- // Construction
- ///////////////////////////////////////////////////////////////////////////
-
- /** @hide */
- public AudioService(Context context) {
- mContext = context;
- mContentResolver = context.getContentResolver();
- mAppOps = (AppOpsManager)context.getSystemService(Context.APP_OPS_SERVICE);
-
- mPlatformType = getPlatformType(context);
-
- PowerManager pm = (PowerManager)context.getSystemService(Context.POWER_SERVICE);
- mAudioEventWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "handleAudioEvent");
-
- Vibrator vibrator = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE);
- mHasVibrator = vibrator == null ? false : vibrator.hasVibrator();
-
- // Intialized volume
- int maxVolume = SystemProperties.getInt("ro.config.vc_call_vol_steps",
- MAX_STREAM_VOLUME[AudioSystem.STREAM_VOICE_CALL]);
- if (maxVolume != MAX_STREAM_VOLUME[AudioSystem.STREAM_VOICE_CALL]) {
- MAX_STREAM_VOLUME[AudioSystem.STREAM_VOICE_CALL] = maxVolume;
- DEFAULT_STREAM_VOLUME[AudioSystem.STREAM_VOICE_CALL] = (maxVolume * 3) / 4;
- }
- maxVolume = SystemProperties.getInt("ro.config.media_vol_steps",
- MAX_STREAM_VOLUME[AudioSystem.STREAM_MUSIC]);
- if (maxVolume != MAX_STREAM_VOLUME[AudioSystem.STREAM_MUSIC]) {
- MAX_STREAM_VOLUME[AudioSystem.STREAM_MUSIC] = maxVolume;
- DEFAULT_STREAM_VOLUME[AudioSystem.STREAM_MUSIC] = (maxVolume * 3) / 4;
- }
-
- sSoundEffectVolumeDb = context.getResources().getInteger(
- com.android.internal.R.integer.config_soundEffectVolumeDb);
-
- mForcedUseForComm = AudioSystem.FORCE_NONE;
-
- createAudioSystemThread();
-
- mMediaFocusControl = new MediaFocusControl(mAudioHandler.getLooper(),
- mContext, mVolumeController, this);
-
- AudioSystem.setErrorCallback(mAudioSystemCallback);
-
- boolean cameraSoundForced = mContext.getResources().getBoolean(
- com.android.internal.R.bool.config_camera_sound_forced);
- mCameraSoundForced = new Boolean(cameraSoundForced);
- sendMsg(mAudioHandler,
- MSG_SET_FORCE_USE,
- SENDMSG_QUEUE,
- AudioSystem.FOR_SYSTEM,
- cameraSoundForced ?
- AudioSystem.FORCE_SYSTEM_ENFORCED : AudioSystem.FORCE_NONE,
- null,
- 0);
-
- mSafeMediaVolumeState = new Integer(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);
- mUseMasterVolume = context.getResources().getBoolean(
- com.android.internal.R.bool.config_useMasterVolume);
- mMasterVolumeRamp = context.getResources().getIntArray(
- com.android.internal.R.array.config_masterVolumeRamp);
-
- // must be called before readPersistedSettings() which needs a valid mStreamVolumeAlias[]
- // array initialized by updateStreamVolumeAlias()
- updateStreamVolumeAlias(false /*updateVolumes*/);
- readPersistedSettings();
- mSettingsObserver = new SettingsObserver();
- createStreamStates();
-
- readAndSetLowRamDevice();
-
- // Call setRingerModeInt() to apply correct mute
- // state on streams affected by ringer mode.
- mRingerModeMutedStreams = 0;
- setRingerModeInt(getRingerModeInternal(), false);
-
- // Register for device connection intent broadcasts.
- IntentFilter intentFilter =
- new IntentFilter(BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED);
- intentFilter.addAction(BluetoothHeadset.ACTION_CONNECTION_STATE_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(UsbManager.ACTION_USB_DEVICE_ATTACHED);
-
- intentFilter.addAction(Intent.ACTION_CONFIGURATION_CHANGED);
- // TODO merge orientation and rotation
- mMonitorOrientation = SystemProperties.getBoolean("ro.audio.monitorOrientation", false);
- if (mMonitorOrientation) {
- Log.v(TAG, "monitoring device orientation");
- // initialize orientation in AudioSystem
- setOrientationForAudioSystem();
- }
- mMonitorRotation = SystemProperties.getBoolean("ro.audio.monitorRotation", false);
- if (mMonitorRotation) {
- mDeviceRotation = ((WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE))
- .getDefaultDisplay().getRotation();
- Log.v(TAG, "monitoring device rotation, initial=" + mDeviceRotation);
-
- mOrientationListener = new AudioOrientationEventListener(mContext);
- mOrientationListener.enable();
-
- // initialize rotation in AudioSystem
- setRotationForAudioSystem();
- }
-
- context.registerReceiver(mReceiver, intentFilter);
-
- restoreMasterVolume();
-
- LocalServices.addService(AudioManagerInternal.class, new AudioServiceInternal());
- }
-
- /**
- * Return the platform type that this is running on. One of:
- * <ul>
- * <li>{@link #PLATFORM_VOICE}</li>
- * <li>{@link #PLATFORM_TELEVISION}</li>
- * <li>{@link #PLATFORM_DEFAULT}</li>
- * </ul>
- */
- public static int getPlatformType(Context context) {
- if (context.getResources().getBoolean(
- com.android.internal.R.bool.config_voice_capable)) {
- return PLATFORM_VOICE;
- } else if (context.getPackageManager().hasSystemFeature(
- PackageManager.FEATURE_LEANBACK)) {
- return PLATFORM_TELEVISION;
- } else {
- return PLATFORM_DEFAULT;
- }
- }
-
- public void systemReady() {
- sendMsg(mAudioHandler, MSG_SYSTEM_READY, SENDMSG_QUEUE,
- 0, 0, null, 0);
- }
-
- public void onSystemReady() {
- mSystemReady = true;
- sendMsg(mAudioHandler, MSG_LOAD_SOUND_EFFECTS, SENDMSG_QUEUE,
- 0, 0, null, 0);
-
- mKeyguardManager =
- (KeyguardManager) mContext.getSystemService(Context.KEYGUARD_SERVICE);
- mScoConnectionState = AudioManager.SCO_AUDIO_STATE_ERROR;
- resetBluetoothSco();
- getBluetoothHeadset();
- //FIXME: this is to maintain compatibility with deprecated intent
- // AudioManager.ACTION_SCO_AUDIO_STATE_CHANGED. Remove when appropriate.
- Intent newIntent = new Intent(AudioManager.ACTION_SCO_AUDIO_STATE_CHANGED);
- newIntent.putExtra(AudioManager.EXTRA_SCO_AUDIO_STATE,
- AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
- sendStickyBroadcastToAll(newIntent);
-
- BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
- if (adapter != null) {
- adapter.getProfileProxy(mContext, mBluetoothProfileServiceListener,
- BluetoothProfile.A2DP);
- }
-
- mHdmiManager =
- (HdmiControlManager) mContext.getSystemService(Context.HDMI_CONTROL_SERVICE);
- if (mHdmiManager != null) {
- synchronized (mHdmiManager) {
- mHdmiTvClient = mHdmiManager.getTvClient();
- if (mHdmiTvClient != null) {
- mFixedVolumeDevices &= ~AudioSystem.DEVICE_ALL_HDMI_SYSTEM_AUDIO_AND_SPEAKER;
- }
- mHdmiPlaybackClient = mHdmiManager.getPlaybackClient();
- mHdmiCecSink = false;
- }
- }
-
- sendMsg(mAudioHandler,
- MSG_CONFIGURE_SAFE_MEDIA_VOLUME_FORCED,
- SENDMSG_REPLACE,
- 0,
- 0,
- null,
- SAFE_VOLUME_CONFIGURE_TIMEOUT_MS);
-
- StreamOverride.init(mContext);
- }
-
- 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.");
- }
- }
- }
- }
-
- private void checkAllAliasStreamVolumes() {
- synchronized (VolumeStreamState.class) {
- int numStreamTypes = AudioSystem.getNumStreamTypes();
- for (int streamType = 0; streamType < numStreamTypes; streamType++) {
- if (streamType != mStreamVolumeAlias[streamType]) {
- mStreamStates[streamType].
- setAllIndexes(mStreamStates[mStreamVolumeAlias[streamType]]);
- }
- // apply stream volume
- if (!mStreamStates[streamType].mIsMuted) {
- mStreamStates[streamType].applyAllVolumes();
- }
- }
- }
- }
-
- 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 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[mStreamVolumeAlias[i]], i);
- }
-
- checkAllFixedVolumeDevices();
- checkAllAliasStreamVolumes();
- }
-
- private void dumpStreamStates(PrintWriter pw) {
- pw.println("\nStream volumes (device: index)");
- int numStreamTypes = AudioSystem.getNumStreamTypes();
- for (int i = 0; i < numStreamTypes; i++) {
- pw.println("- "+STREAM_NAMES[i]+":");
- mStreamStates[i].dump(pw);
- pw.println("");
- }
- pw.print("\n- mute affected streams = 0x");
- pw.println(Integer.toHexString(mMuteAffectedStreams));
- }
-
- /** @hide */
- public static String streamToString(int stream) {
- if (stream >= 0 && stream < STREAM_NAMES.length) return STREAM_NAMES[stream];
- if (stream == AudioManager.USE_DEFAULT_STREAM_TYPE) return "USE_DEFAULT_STREAM_TYPE";
- return "UNKNOWN_STREAM_" + stream;
- }
-
- private void updateStreamVolumeAlias(boolean updateVolumes) {
- int dtmfStreamAlias;
-
- switch (mPlatformType) {
- case PLATFORM_VOICE:
- mStreamVolumeAlias = STREAM_VOLUME_ALIAS_VOICE;
- dtmfStreamAlias = AudioSystem.STREAM_RING;
- break;
- case PLATFORM_TELEVISION:
- mStreamVolumeAlias = STREAM_VOLUME_ALIAS_TELEVISION;
- dtmfStreamAlias = AudioSystem.STREAM_MUSIC;
- break;
- default:
- mStreamVolumeAlias = STREAM_VOLUME_ALIAS_DEFAULT;
- dtmfStreamAlias = AudioSystem.STREAM_MUSIC;
- }
-
- if (isPlatformTelevision()) {
- 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;
- if (updateVolumes) {
- mStreamStates[AudioSystem.STREAM_DTMF].setAllIndexes(mStreamStates[dtmfStreamAlias]);
- // 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);
- }
- }
-
- 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,
- null,
- 0);
- }
-
- 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 || isPlatformTelevision()) {
- 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 = getValueForVibrateSetting(0,
- AudioManager.VIBRATE_TYPE_NOTIFICATION,
- mHasVibrator ? AudioManager.VIBRATE_SETTING_ONLY_SILENT
- : AudioManager.VIBRATE_SETTING_OFF);
- mVibrateSetting = getValueForVibrateSetting(mVibrateSetting,
- AudioManager.VIBRATE_TYPE_RINGER,
- mHasVibrator ? AudioManager.VIBRATE_SETTING_ONLY_SILENT
- : AudioManager.VIBRATE_SETTING_OFF);
-
- updateRingerModeAffectedStreams();
- readDockAudioSettings(cr);
- }
-
- mMuteAffectedStreams = System.getIntForUser(cr,
- System.MUTE_STREAMS_AFFECTED, DEFAULT_MUTE_STREAMS_AFFECTED,
- UserHandle.USER_CURRENT);
-
- boolean masterMute = System.getIntForUser(cr, System.VOLUME_MASTER_MUTE,
- 0, UserHandle.USER_CURRENT) == 1;
- if (mUseFixedVolume) {
- masterMute = false;
- AudioSystem.setMasterVolume(1.0f);
- }
- AudioSystem.setMasterMute(masterMute);
- broadcastMasterMuteStatus(masterMute);
-
- boolean microphoneMute =
- System.getIntForUser(cr, System.MICROPHONE_MUTE, 0, UserHandle.USER_CURRENT) == 1;
- AudioSystem.muteMicrophone(microphoneMute);
-
- // 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 int rescaleIndex(int index, int srcStream, int dstStream) {
- return (index * mStreamStates[dstStream].getMaxIndex() + mStreamStates[srcStream].getMaxIndex() / 2) / mStreamStates[srcStream].getMaxIndex();
- }
-
- private class AudioOrientationEventListener
- extends OrientationEventListener {
- public AudioOrientationEventListener(Context context) {
- super(context);
- }
-
- @Override
- public void onOrientationChanged(int orientation) {
- //Even though we're responding to phone orientation events,
- //use display rotation so audio stays in sync with video/dialogs
- int newRotation = ((WindowManager) mContext.getSystemService(
- Context.WINDOW_SERVICE)).getDefaultDisplay().getRotation();
- if (newRotation != mDeviceRotation) {
- mDeviceRotation = newRotation;
- setRotationForAudioSystem();
- }
- }
- }
-
- ///////////////////////////////////////////////////////////////////////////
- // IPC methods
- ///////////////////////////////////////////////////////////////////////////
- /** @see AudioManager#adjustVolume(int, int) */
- public void adjustSuggestedStreamVolume(int direction, int suggestedStreamType, int flags,
- String callingPackage) {
- adjustSuggestedStreamVolume(direction, suggestedStreamType, flags, callingPackage,
- Binder.getCallingUid());
- }
-
- private void adjustSuggestedStreamVolume(int direction, int suggestedStreamType, int flags,
- String callingPackage, int uid) {
- if (DEBUG_VOL) Log.d(TAG, "adjustSuggestedStreamVolume() stream="+suggestedStreamType
- + ", flags=" + flags);
- int streamType;
- boolean isMute = isMuteAdjust(direction);
- if (mVolumeControlStream != -1) {
- streamType = mVolumeControlStream;
- } else {
- streamType = getActiveStreamType(suggestedStreamType);
- }
- 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
- if (mVolumeController.suppressAdjustment(resolvedStream, flags, isMute)) {
- 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, uid);
- }
-
- /** @see AudioManager#adjustStreamVolume(int, int, int) */
- public void adjustStreamVolume(int streamType, int direction, int flags,
- String callingPackage) {
- adjustStreamVolume(streamType, direction, flags, callingPackage, Binder.getCallingUid());
- }
-
- private void adjustStreamVolume(int streamType, int direction, int flags,
- String callingPackage, int uid) {
- if (mUseFixedVolume) {
- return;
- }
- if (DEBUG_VOL) Log.d(TAG, "adjustStreamVolume() stream="+streamType+", dir="+direction
- + ", flags="+flags);
-
- ensureValidDirection(direction);
- ensureValidStreamType(streamType);
-
- boolean isMuteAdjust = isMuteAdjust(direction);
-
- // 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];
-
- if (isMuteAdjust && !isStreamAffectedByMute(streamTypeAlias)) {
- return;
- }
-
- 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 (mAppOps.noteOp(STEAM_VOLUME_OPS[streamTypeAlias], uid, callingPackage)
- != AppOpsManager.MODE_ALLOWED) {
- return;
- }
-
- // reset any pending volume command
- synchronized (mSafeMediaVolumeState) {
- 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 = mSafeMediaVolumeIndex;
- } 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 == getMasterStreamType())) {
- 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);
- 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;
- }
- }
-
- int oldIndex = mStreamStates[streamType].getIndex(device);
-
- if (adjustVolume && (direction != AudioManager.ADJUST_SAME)) {
- mAudioHandler.removeMessages(MSG_UNMUTE_STREAM);
-
- // 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) {
- synchronized (mA2dpAvrcpLock) {
- if (mA2dp != null && mAvrcpAbsVolSupported) {
- mA2dp.adjustAvrcpAbsoluteVolume(direction);
- }
- }
- }
-
- 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]) {
- mStreamStates[stream].mute(state);
-
- Intent intent = new Intent(AudioManager.STREAM_MUTE_CHANGED_ACTION);
- intent.putExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, stream);
- intent.putExtra(AudioManager.EXTRA_STREAM_VOLUME_MUTED, state);
- sendBroadcastToAll(intent);
- }
- }
- } else if ((direction == AudioManager.ADJUST_RAISE) &&
- !checkSafeMediaVolume(streamTypeAlias, aliasIndex + step, device)) {
- Log.e(TAG, "adjustStreamVolume() safe volume index = " + oldIndex);
- mVolumeController.postDisplaySafeVolumeWarning(flags);
- } else if (streamState.adjustIndex(direction * step, device) || 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) {
- 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);
- }
-
- // Check if volume update should be sent to Hdmi system audio.
- int newIndex = mStreamStates[streamType].getIndex(device);
- if (streamTypeAlias == AudioSystem.STREAM_MUSIC) {
- setSystemAudioVolume(oldIndex, newIndex, getStreamMaxVolume(streamType), flags);
- }
- if (mHdmiManager != null) {
- synchronized (mHdmiManager) {
- // mHdmiCecSink true => mHdmiPlaybackClient != null
- if (mHdmiCecSink &&
- streamTypeAlias == AudioSystem.STREAM_MUSIC &&
- oldIndex != newIndex) {
- synchronized (mHdmiPlaybackClient) {
- int keyCode = (direction == -1) ? KeyEvent.KEYCODE_VOLUME_DOWN :
- KeyEvent.KEYCODE_VOLUME_UP;
- mHdmiPlaybackClient.sendKeyEvent(keyCode, true);
- mHdmiPlaybackClient.sendKeyEvent(keyCode, false);
- }
- }
- }
- }
- }
- int index = mStreamStates[streamType].getIndex(device);
- sendVolumeUpdate(streamType, oldIndex, index, flags);
- }
-
- // 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);
- }
-
- private void setSystemAudioVolume(int oldVolume, int newVolume, int maxVolume, int flags) {
- if (mHdmiManager == null
- || mHdmiTvClient == null
- || oldVolume == newVolume
- || (flags & AudioManager.FLAG_HDMI_SYSTEM_AUDIO_VOLUME) != 0) return;
-
- // 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 (mHdmiManager) {
- if (!mHdmiSystemAudioSupported) return;
- synchronized (mHdmiTvClient) {
- final long token = Binder.clearCallingIdentity();
- try {
- mHdmiTvClient.setSystemAudioVolume(
- (oldVolume + 5) / 10, (newVolume + 5) / 10, maxVolume);
- } finally {
- Binder.restoreCallingIdentity(token);
- }
- }
- }
- }
-
- /** @see AudioManager#adjustMasterVolume(int, int) */
- public void adjustMasterVolume(int steps, int flags, String callingPackage) {
- adjustMasterVolume(steps, flags, callingPackage, Binder.getCallingUid());
- }
-
- public void adjustMasterVolume(int steps, int flags, String callingPackage, int uid) {
- if (mUseFixedVolume) {
- return;
- }
- if (isMuteAdjust(steps)) {
- setMasterMuteInternal(steps, flags, callingPackage, uid);
- return;
- }
- ensureValidSteps(steps);
- int volume = Math.round(AudioSystem.getMasterVolume() * MAX_MASTER_VOLUME);
- int delta = 0;
- int numSteps = Math.abs(steps);
- int direction = steps > 0 ? AudioManager.ADJUST_RAISE : AudioManager.ADJUST_LOWER;
- for (int i = 0; i < numSteps; ++i) {
- delta = findVolumeDelta(direction, volume);
- volume += delta;
- }
-
- //Log.d(TAG, "adjustMasterVolume volume: " + volume + " steps: " + steps);
- setMasterVolume(volume, flags, callingPackage, uid);
- }
-
- // 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 void onSetStreamVolume(int streamType, int index, int flags, int device) {
- setStreamVolumeInt(mStreamVolumeAlias[streamType], index, device, false);
- // setting volume on master stream type also controls silent mode
- if (((flags & AudioManager.FLAG_ALLOW_RINGER_MODES) != 0) ||
- (mStreamVolumeAlias[streamType] == getMasterStreamType())) {
- int newRingerMode;
- if (index == 0) {
- newRingerMode = mHasVibrator ? AudioManager.RINGER_MODE_VIBRATE
- : VOLUME_SETS_RINGER_MODE_SILENT ? AudioManager.RINGER_MODE_SILENT
- : AudioManager.RINGER_MODE_NORMAL;
- } else {
- newRingerMode = AudioManager.RINGER_MODE_NORMAL;
- }
- setRingerMode(newRingerMode, TAG + ".onSetStreamVolume", false /*external*/);
- }
- }
-
- /** @see AudioManager#setStreamVolume(int, int, int) */
- public void setStreamVolume(int streamType, int index, int flags, String callingPackage) {
- setStreamVolume(streamType, index, flags, callingPackage, Binder.getCallingUid());
- }
-
- private void setStreamVolume(int streamType, int index, int flags, String callingPackage,
- int uid) {
- 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 (mAppOps.noteOp(STEAM_VOLUME_OPS[streamTypeAlias], uid, callingPackage)
- != AppOpsManager.MODE_ALLOWED) {
- return;
- }
-
- synchronized (mSafeMediaVolumeState) {
- // 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) {
- synchronized (mA2dpAvrcpLock) {
- if (mA2dp != null && mAvrcpAbsVolSupported) {
- mA2dp.setAvrcpAbsoluteVolume(index / 10);
- }
- }
- }
-
- 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 = mSafeMediaVolumeIndex;
- } 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);
- index = mStreamStates[streamType].getIndex(device);
- }
- }
- sendVolumeUpdate(streamType, oldIndex, index, flags);
- }
-
- /** @see AudioManager#forceVolumeControlStream(int) */
- public void forceVolumeControlStream(int streamType, IBinder cb) {
- synchronized(mForceControlStreamLock) {
- mVolumeControlStream = streamType;
- if (mVolumeControlStream == -1) {
- if (mForceControlStreamClient != null) {
- mForceControlStreamClient.release();
- mForceControlStreamClient = null;
- }
- } else {
- 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;
- }
- }
- }
-
- public void release() {
- if (mCb != null) {
- mCb.unlinkToDeath(this, 0);
- mCb = null;
- }
- }
- }
-
- private int findVolumeDelta(int direction, int volume) {
- int delta = 0;
- if (direction == AudioManager.ADJUST_RAISE) {
- if (volume == MAX_MASTER_VOLUME) {
- return 0;
- }
- // This is the default value if we make it to the end
- delta = mMasterVolumeRamp[1];
- // If we're raising the volume move down the ramp array until we
- // find the volume we're above and use that groups delta.
- for (int i = mMasterVolumeRamp.length - 1; i > 1; i -= 2) {
- if (volume >= mMasterVolumeRamp[i - 1]) {
- delta = mMasterVolumeRamp[i];
- break;
- }
- }
- } else if (direction == AudioManager.ADJUST_LOWER){
- if (volume == 0) {
- return 0;
- }
- int length = mMasterVolumeRamp.length;
- // This is the default value if we make it to the end
- delta = -mMasterVolumeRamp[length - 1];
- // If we're lowering the volume move up the ramp array until we
- // find the volume we're below and use the group below it's delta
- for (int i = 2; i < length; i += 2) {
- if (volume <= mMasterVolumeRamp[i]) {
- delta = -mMasterVolumeRamp[i - 1];
- break;
- }
- }
- }
- return delta;
- }
-
- private void sendBroadcastToAll(Intent intent) {
- intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
- final long ident = Binder.clearCallingIdentity();
- try {
- mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
- } finally {
- Binder.restoreCallingIdentity(ident);
- }
- }
-
- private void sendStickyBroadcastToAll(Intent intent) {
- final long ident = Binder.clearCallingIdentity();
- try {
- mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
- } finally {
- Binder.restoreCallingIdentity(ident);
- }
- }
-
- // UI update and Broadcast Intent
- private void sendVolumeUpdate(int streamType, int oldIndex, int index, int flags) {
- if (!isPlatformVoice() && (streamType == AudioSystem.STREAM_RING)) {
- streamType = AudioSystem.STREAM_NOTIFICATION;
- }
-
- if (streamType == AudioSystem.STREAM_MUSIC) {
- flags = updateFlagsForSystemAudio(flags);
- }
- mVolumeController.postVolumeChanged(streamType, flags);
-
- if ((flags & AudioManager.FLAG_FIXED_VOLUME) == 0) {
- oldIndex = (oldIndex + 5) / 10;
- index = (index + 5) / 10;
- Intent intent = new Intent(AudioManager.VOLUME_CHANGED_ACTION);
- intent.putExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, streamType);
- intent.putExtra(AudioManager.EXTRA_VOLUME_STREAM_VALUE, index);
- intent.putExtra(AudioManager.EXTRA_PREV_VOLUME_STREAM_VALUE, oldIndex);
- sendBroadcastToAll(intent);
- }
- }
-
- // If Hdmi-CEC system audio mode is on, we show volume bar only when TV
- // receives volume notification from Audio Receiver.
- private int updateFlagsForSystemAudio(int flags) {
- if (mHdmiTvClient != null) {
- synchronized (mHdmiTvClient) {
- if (mHdmiSystemAudioSupported &&
- ((flags & AudioManager.FLAG_HDMI_SYSTEM_AUDIO_VOLUME) == 0)) {
- flags &= ~AudioManager.FLAG_SHOW_UI;
- }
- }
- }
- return flags;
- }
-
- // UI update and Broadcast Intent
- private void sendMasterVolumeUpdate(int flags, int oldVolume, int newVolume) {
- mVolumeController.postMasterVolumeChanged(updateFlagsForSystemAudio(flags));
-
- Intent intent = new Intent(AudioManager.MASTER_VOLUME_CHANGED_ACTION);
- intent.putExtra(AudioManager.EXTRA_PREV_MASTER_VOLUME_VALUE, oldVolume);
- intent.putExtra(AudioManager.EXTRA_MASTER_VOLUME_VALUE, newVolume);
- sendBroadcastToAll(intent);
- }
-
- // UI update and Broadcast Intent
- private void sendMasterMuteUpdate(boolean muted, int flags) {
- mVolumeController.postMasterMuteChanged(updateFlagsForSystemAudio(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) {
- VolumeStreamState streamState = mStreamStates[streamType];
-
- if (streamState.setIndex(index, device) || 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) {
- if (mHdmiManager == null || mHdmiTvClient == null) return;
- synchronized (mHdmiManager) {
- if (!mHdmiSystemAudioSupported) return;
- synchronized (mHdmiTvClient) {
- 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) {
- 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(int adjust, int flags, String callingPackage, int uid) {
- if (mAppOps.noteOp(AppOpsManager.OP_AUDIO_MASTER_VOLUME, uid, callingPackage)
- != AppOpsManager.MODE_ALLOWED) {
- return;
- }
- boolean state;
- if (adjust == AudioManager.ADJUST_TOGGLE_MUTE) {
- state = !AudioSystem.getMasterMute();
- } else {
- state = adjust == AudioManager.ADJUST_MUTE;
- }
- if (state != AudioSystem.getMasterMute()) {
- setSystemAudioMute(state);
- AudioSystem.setMasterMute(state);
- // Post a persist master volume msg
- sendMsg(mAudioHandler, MSG_PERSIST_MASTER_VOLUME_MUTE, SENDMSG_REPLACE, state ? 1
- : 0, UserHandle.getCallingUserId(), null, PERSIST_DELAY);
- sendMasterMuteUpdate(state, flags);
-
- Intent intent = new Intent(AudioManager.MASTER_MUTE_CHANGED_ACTION);
- intent.putExtra(AudioManager.EXTRA_MASTER_VOLUME_MUTED, state);
- sendBroadcastToAll(intent);
- }
- }
-
- /** get master mute state. */
- public boolean isMasterMute() {
- return AudioSystem.getMasterMute();
- }
-
- protected static int getMaxStreamVolume(int streamType) {
- return MAX_STREAM_VOLUME[streamType];
- }
-
- public static int getDefaultStreamVolume(int streamType) {
- return DEFAULT_STREAM_VOLUME[streamType];
- }
-
- /** @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;
- }
- }
-
- @Override
- public int getMasterVolume() {
- if (isMasterMute()) return 0;
- return getLastAudibleMasterVolume();
- }
-
- @Override
- public void setMasterVolume(int volume, int flags, String callingPackage) {
- setMasterVolume(volume, flags, callingPackage, Binder.getCallingUid());
- }
-
- public void setMasterVolume(int volume, int flags, String callingPackage, int uid) {
- if (mUseFixedVolume) {
- return;
- }
-
- if (mAppOps.noteOp(AppOpsManager.OP_AUDIO_MASTER_VOLUME, uid, callingPackage)
- != AppOpsManager.MODE_ALLOWED) {
- return;
- }
-
- if (volume < 0) {
- volume = 0;
- } else if (volume > MAX_MASTER_VOLUME) {
- volume = MAX_MASTER_VOLUME;
- }
- doSetMasterVolume((float)volume / MAX_MASTER_VOLUME, flags);
- }
-
- private void doSetMasterVolume(float volume, int flags) {
- // don't allow changing master volume when muted
- if (!AudioSystem.getMasterMute()) {
- int oldVolume = getMasterVolume();
- AudioSystem.setMasterVolume(volume);
-
- int newVolume = getMasterVolume();
- if (newVolume != oldVolume) {
- // Post a persist master volume msg
- sendMsg(mAudioHandler, MSG_PERSIST_MASTER_VOLUME, SENDMSG_REPLACE,
- Math.round(volume * (float)1000.0), 0, null, PERSIST_DELAY);
- setSystemAudioVolume(oldVolume, newVolume, getMasterMaxVolume(), flags);
- }
- // Send the volume update regardless whether there was a change.
- sendMasterVolumeUpdate(flags, oldVolume, newVolume);
- }
- }
-
- /** @see AudioManager#getStreamMaxVolume(int) */
- public int getStreamMaxVolume(int streamType) {
- ensureValidStreamType(streamType);
- return (mStreamStates[streamType].getMaxIndex() + 5) / 10;
- }
-
- public int getMasterMaxVolume() {
- return MAX_MASTER_VOLUME;
- }
-
- /** 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;
- }
-
- /** Get last audible master volume before it was muted. */
- public int getLastAudibleMasterVolume() {
- return Math.round(AudioSystem.getMasterVolume() * MAX_MASTER_VOLUME);
- }
-
- /** @see AudioManager#getMasterStreamType() */
- public int getMasterStreamType() {
- return mStreamVolumeAlias[AudioSystem.STREAM_SYSTEM];
- }
-
- /** @see AudioManager#setMicrophoneMute(boolean) */
- public void setMicrophoneMute(boolean on, String callingPackage) {
- if (mAppOps.noteOp(AppOpsManager.OP_MUTE_MICROPHONE, Binder.getCallingUid(),
- callingPackage) != AppOpsManager.MODE_ALLOWED) {
- return;
- }
- if (!checkAudioSettingsPermission("setMicrophoneMute()")) {
- return;
- }
-
- AudioSystem.muteMicrophone(on);
- // Post a persist microphone msg.
- sendMsg(mAudioHandler, MSG_PERSIST_MICROPHONE_MUTE, SENDMSG_REPLACE, on ? 1
- : 0, UserHandle.getCallingUserId(), null, PERSIST_DELAY);
- }
-
- @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) {
- setRingerMode(ringerMode, caller, true /*external*/);
- }
-
- public void setRingerModeInternal(int ringerMode, String caller) {
- enforceSelfOrSystemUI("setRingerModeInternal");
- setRingerMode(ringerMode, caller, false /*external*/);
- }
-
- private void setRingerMode(int ringerMode, String caller, boolean external) {
- if (mUseFixedVolume || isPlatformTelevision()) {
- 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);
- }
- 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);
- }
- 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);
- }
-
- private void setRingerModeInt(int ringerMode, boolean persist) {
- final boolean change;
- synchronized(mSettingsLock) {
- change = mRingerMode != ringerMode;
- mRingerMode = ringerMode;
- }
-
- // Mute stream if not previously muted by ringer mode and ringer mode
- // is not RINGER_MODE_NORMAL and stream is affected by ringer mode.
- // Unmute stream if previously muted by ringer mode and ringer mode
- // is RINGER_MODE_NORMAL or stream is not affected by ringer mode.
- int numStreamTypes = AudioSystem.getNumStreamTypes();
- final boolean ringerModeMute = ringerMode == AudioManager.RINGER_MODE_VIBRATE
- || ringerMode == AudioManager.RINGER_MODE_SILENT;
- for (int streamType = numStreamTypes - 1; streamType >= 0; streamType--) {
- final boolean isMuted = isStreamMutedByRingerMode(streamType);
- final boolean shouldMute = ringerModeMute && isStreamAffectedByRingerMode(streamType);
- if (isMuted == shouldMute) continue;
- if (!shouldMute) {
- // unmute
- // ring and notifications volume should never be 0 when not silenced
- // on voice capable devices or devices that support vibration
- if ((isPlatformVoice() || mHasVibrator) &&
- mStreamVolumeAlias[streamType] == AudioSystem.STREAM_RING) {
- synchronized (VolumeStreamState.class) {
- Set set = mStreamStates[streamType].mIndex.entrySet();
- Iterator i = set.iterator();
- while (i.hasNext()) {
- Map.Entry entry = (Map.Entry)i.next();
- if ((Integer)entry.getValue() == 0) {
- entry.setValue(10);
- }
- }
- }
- }
- mStreamStates[streamType].mute(false);
- mRingerModeMutedStreams &= ~(1 << streamType);
- } else {
- // mute
- mStreamStates[streamType].mute(true);
- mRingerModeMutedStreams |= (1 << streamType);
- }
- }
-
- // 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);
- }
- }
-
- private void restoreMasterVolume() {
- if (mUseFixedVolume) {
- AudioSystem.setMasterVolume(1.0f);
- return;
- }
- if (mUseMasterVolume) {
- float volume = Settings.System.getFloatForUser(mContentResolver,
- Settings.System.VOLUME_MASTER, -1.0f, UserHandle.USER_CURRENT);
- if (volume >= 0.0f) {
- AudioSystem.setMasterVolume(volume);
- }
- }
- }
-
- /** @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 = getValueForVibrateSetting(mVibrateSetting, vibrateType, vibrateSetting);
-
- // Broadcast change
- broadcastVibrateSetting(vibrateType);
-
- }
-
- /**
- * @see #setVibrateSetting(int, int)
- */
- public static int getValueForVibrateSetting(int existingValue, int vibrateType,
- int vibrateSetting) {
-
- // First clear the existing setting. Each vibrate type has two bits in
- // the value. Note '3' is '11' in binary.
- existingValue &= ~(3 << (vibrateType * 2));
-
- // Set into the old value
- existingValue |= (vibrateSetting & 3) << (vibrateType * 2);
-
- return existingValue;
- }
-
- 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 newModeOwnerPid = 0;
- synchronized(mSetModeDeathHandlers) {
- Log.w(TAG, "setMode() client died");
- int index = mSetModeDeathHandlers.indexOf(this);
- if (index < 0) {
- Log.w(TAG, "unregistered setMode() client died");
- } else {
- newModeOwnerPid = setModeInt(AudioSystem.MODE_NORMAL, mCb, mPid);
- }
- }
- // when entering RINGTONE, IN_CALL or IN_COMMUNICATION mode, clear all
- // SCO connections not started by the application changing the mode
- if (newModeOwnerPid != 0) {
- final long ident = Binder.clearCallingIdentity();
- disconnectBluetoothSco(newModeOwnerPid);
- Binder.restoreCallingIdentity(ident);
- }
- }
-
- 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) {
- if (DEBUG_MODE) { Log.v(TAG, "setMode(mode=" + mode + ")"); }
- 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 newModeOwnerPid = 0;
- synchronized(mSetModeDeathHandlers) {
- if (mode == AudioSystem.MODE_CURRENT) {
- mode = mMode;
- }
- newModeOwnerPid = setModeInt(mode, cb, Binder.getCallingPid());
- }
- // when entering RINGTONE, IN_CALL or IN_COMMUNICATION mode, clear all
- // SCO connections not started by the application changing the mode
- if (newModeOwnerPid != 0) {
- disconnectBluetoothSco(newModeOwnerPid);
- }
- }
-
- // must be called synchronized on mSetModeDeathHandlers
- // setModeInt() returns a valid PID if the audio mode was successfully set to
- // any mode other than NORMAL.
- private int setModeInt(int mode, IBinder cb, int pid) {
- if (DEBUG_MODE) { Log.v(TAG, "setModeInt(mode=" + mode + ", pid=" + pid + ")"); }
- 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;
- }
- }
- int status = AudioSystem.AUDIO_STATUS_OK;
- do {
- 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();
- mode = 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 (mode != mMode) {
- status = AudioSystem.setPhoneState(mode);
- if (status == AudioSystem.AUDIO_STATUS_OK) {
- if (DEBUG_MODE) { Log.v(TAG, " mode successfully set to " + mode); }
- mMode = mode;
- } 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 (mode != 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();
- }
- }
- 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);
-
- updateStreamVolumeAlias(true /*updateVolumes*/);
- }
- 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) {
- 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);
- }
-
- /**
- * 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();
-
- // 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) &&
- !isStreamMutedByRingerMode(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();
-
- synchronized (mSafeMediaVolumeState) {
- 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();
- }
- }
- }
-
- /** @see AudioManager#setSpeakerphoneOn(boolean) */
- public void setSpeakerphoneOn(boolean on){
- if (!checkAudioSettingsPermission("setSpeakerphoneOn()")) {
- return;
- }
-
- if (on) {
- if (mForcedUseForComm == AudioSystem.FORCE_BT_SCO) {
- sendMsg(mAudioHandler, MSG_SET_FORCE_USE, SENDMSG_QUEUE,
- AudioSystem.FOR_RECORD, AudioSystem.FORCE_NONE, null, 0);
- }
- mForcedUseForComm = AudioSystem.FORCE_SPEAKER;
- } else if (mForcedUseForComm == AudioSystem.FORCE_SPEAKER){
- mForcedUseForComm = AudioSystem.FORCE_NONE;
- }
-
- sendMsg(mAudioHandler, MSG_SET_FORCE_USE, SENDMSG_QUEUE,
- AudioSystem.FOR_COMMUNICATION, mForcedUseForComm, null, 0);
- }
-
- /** @see AudioManager#isSpeakerphoneOn() */
- public boolean isSpeakerphoneOn() {
- return (mForcedUseForComm == AudioSystem.FORCE_SPEAKER);
- }
-
- /** @see AudioManager#setBluetoothScoOn(boolean) */
- public void setBluetoothScoOn(boolean on){
- if (!checkAudioSettingsPermission("setBluetoothScoOn()")) {
- return;
- }
-
- if (on) {
- mForcedUseForComm = AudioSystem.FORCE_BT_SCO;
- } else if (mForcedUseForComm == AudioSystem.FORCE_BT_SCO) {
- mForcedUseForComm = AudioSystem.FORCE_NONE;
- }
-
- sendMsg(mAudioHandler, MSG_SET_FORCE_USE, SENDMSG_QUEUE,
- AudioSystem.FOR_COMMUNICATION, mForcedUseForComm, null, 0);
- sendMsg(mAudioHandler, MSG_SET_FORCE_USE, SENDMSG_QUEUE,
- AudioSystem.FOR_RECORD, mForcedUseForComm, null, 0);
- }
-
- /** @see AudioManager#isBluetoothScoOn() */
- public boolean isBluetoothScoOn() {
- return (mForcedUseForComm == AudioSystem.FORCE_BT_SCO);
- }
-
- /** @see AudioManager#setBluetoothA2dpOn(boolean) */
- public void setBluetoothA2dpOn(boolean on) {
- synchronized (mBluetoothA2dpEnabledLock) {
- mBluetoothA2dpEnabled = on;
- sendMsg(mAudioHandler, MSG_SET_FORCE_BT_A2DP_USE, SENDMSG_QUEUE,
- AudioSystem.FOR_MEDIA,
- mBluetoothA2dpEnabled ? AudioSystem.FORCE_NONE : AudioSystem.FORCE_NO_BT_A2DP,
- null, 0);
- }
- }
-
- /** @see AudioManager#isBluetoothA2dpOn() */
- public boolean isBluetoothA2dpOn() {
- synchronized (mBluetoothA2dpEnabledLock) {
- return mBluetoothA2dpEnabled;
- }
- }
-
- /** @see AudioManager#startBluetoothSco() */
- public void startBluetoothSco(IBinder cb, int targetSdkVersion) {
- int scoAudioMode =
- (targetSdkVersion < Build.VERSION_CODES.JELLY_BEAN_MR2) ?
- SCO_MODE_VIRTUAL_CALL : SCO_MODE_UNDEFINED;
- startBluetoothScoInt(cb, scoAudioMode);
- }
-
- /** @see AudioManager#startBluetoothScoVirtualCall() */
- public void startBluetoothScoVirtualCall(IBinder cb) {
- startBluetoothScoInt(cb, SCO_MODE_VIRTUAL_CALL);
- }
-
- void startBluetoothScoInt(IBinder cb, int scoAudioMode){
- if (!checkAudioSettingsPermission("startBluetoothSco()") ||
- !mSystemReady) {
- return;
- }
- ScoClient client = getScoClient(cb, true);
- // The calling identity must be cleared before calling ScoClient.incCount().
- // inCount() calls requestScoState() which in turn can call BluetoothHeadset APIs
- // and this must be done on behalf of system server to make sure permissions are granted.
- // The caller identity must be cleared after getScoClient() because it is needed if a new
- // client is created.
- final long ident = Binder.clearCallingIdentity();
- client.incCount(scoAudioMode);
- Binder.restoreCallingIdentity(ident);
- }
-
- /** @see AudioManager#stopBluetoothSco() */
- public void stopBluetoothSco(IBinder cb){
- if (!checkAudioSettingsPermission("stopBluetoothSco()") ||
- !mSystemReady) {
- return;
- }
- ScoClient client = getScoClient(cb, false);
- // The calling identity must be cleared before calling ScoClient.decCount().
- // decCount() calls requestScoState() which in turn can call BluetoothHeadset APIs
- // and this must be done on behalf of system server to make sure permissions are granted.
- final long ident = Binder.clearCallingIdentity();
- if (client != null) {
- client.decCount();
- }
- Binder.restoreCallingIdentity(ident);
- }
-
-
- private class ScoClient implements IBinder.DeathRecipient {
- private IBinder mCb; // To be notified of client's death
- private int mCreatorPid;
- private int mStartcount; // number of SCO connections started by this client
-
- ScoClient(IBinder cb) {
- mCb = cb;
- mCreatorPid = Binder.getCallingPid();
- mStartcount = 0;
- }
-
- public void binderDied() {
- synchronized(mScoClients) {
- Log.w(TAG, "SCO client died");
- int index = mScoClients.indexOf(this);
- if (index < 0) {
- Log.w(TAG, "unregistered SCO client died");
- } else {
- clearCount(true);
- mScoClients.remove(this);
- }
- }
- }
-
- public void incCount(int scoAudioMode) {
- synchronized(mScoClients) {
- requestScoState(BluetoothHeadset.STATE_AUDIO_CONNECTED, scoAudioMode);
- if (mStartcount == 0) {
- try {
- mCb.linkToDeath(this, 0);
- } catch (RemoteException e) {
- // client has already died!
- Log.w(TAG, "ScoClient incCount() could not link to "+mCb+" binder death");
- }
- }
- mStartcount++;
- }
- }
-
- public void decCount() {
- synchronized(mScoClients) {
- if (mStartcount == 0) {
- Log.w(TAG, "ScoClient.decCount() already 0");
- } else {
- mStartcount--;
- if (mStartcount == 0) {
- try {
- mCb.unlinkToDeath(this, 0);
- } catch (NoSuchElementException e) {
- Log.w(TAG, "decCount() going to 0 but not registered to binder");
- }
- }
- requestScoState(BluetoothHeadset.STATE_AUDIO_DISCONNECTED, 0);
- }
- }
- }
-
- public void clearCount(boolean stopSco) {
- synchronized(mScoClients) {
- if (mStartcount != 0) {
- try {
- mCb.unlinkToDeath(this, 0);
- } catch (NoSuchElementException e) {
- Log.w(TAG, "clearCount() mStartcount: "+mStartcount+" != 0 but not registered to binder");
- }
- }
- mStartcount = 0;
- if (stopSco) {
- requestScoState(BluetoothHeadset.STATE_AUDIO_DISCONNECTED, 0);
- }
- }
- }
-
- public int getCount() {
- return mStartcount;
- }
-
- public IBinder getBinder() {
- return mCb;
- }
-
- public int getPid() {
- return mCreatorPid;
- }
-
- public int totalCount() {
- synchronized(mScoClients) {
- int count = 0;
- int size = mScoClients.size();
- for (int i = 0; i < size; i++) {
- count += mScoClients.get(i).getCount();
- }
- return count;
- }
- }
-
- private void requestScoState(int state, int scoAudioMode) {
- checkScoAudioState();
- if (totalCount() == 0) {
- if (state == BluetoothHeadset.STATE_AUDIO_CONNECTED) {
- // Make sure that the state transitions to CONNECTING even if we cannot initiate
- // the connection.
- broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_CONNECTING);
- // Accept SCO audio activation only in NORMAL audio mode or if the mode is
- // currently controlled by the same client process.
- synchronized(mSetModeDeathHandlers) {
- if ((mSetModeDeathHandlers.isEmpty() ||
- mSetModeDeathHandlers.get(0).getPid() == mCreatorPid) &&
- (mScoAudioState == SCO_STATE_INACTIVE ||
- mScoAudioState == SCO_STATE_DEACTIVATE_REQ)) {
- if (mScoAudioState == SCO_STATE_INACTIVE) {
- mScoAudioMode = scoAudioMode;
- if (scoAudioMode == SCO_MODE_UNDEFINED) {
- if (mBluetoothHeadsetDevice != null) {
- mScoAudioMode = new Integer(Settings.Global.getInt(
- mContentResolver,
- "bluetooth_sco_channel_"+
- mBluetoothHeadsetDevice.getAddress(),
- SCO_MODE_VIRTUAL_CALL));
- if (mScoAudioMode > SCO_MODE_MAX || mScoAudioMode < 0) {
- mScoAudioMode = SCO_MODE_VIRTUAL_CALL;
- }
- } else {
- mScoAudioMode = SCO_MODE_RAW;
- }
- }
- if (mBluetoothHeadset != null && mBluetoothHeadsetDevice != null) {
- boolean status = false;
- if (mScoAudioMode == SCO_MODE_RAW) {
- status = mBluetoothHeadset.connectAudio();
- } else if (mScoAudioMode == SCO_MODE_VIRTUAL_CALL) {
- status = mBluetoothHeadset.startScoUsingVirtualVoiceCall(
- mBluetoothHeadsetDevice);
- } else if (mScoAudioMode == SCO_MODE_VR) {
- status = mBluetoothHeadset.startVoiceRecognition(
- mBluetoothHeadsetDevice);
- }
-
- if (status) {
- mScoAudioState = SCO_STATE_ACTIVE_INTERNAL;
- } else {
- broadcastScoConnectionState(
- AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
- }
- } else if (getBluetoothHeadset()) {
- mScoAudioState = SCO_STATE_ACTIVATE_REQ;
- }
- } else {
- mScoAudioState = SCO_STATE_ACTIVE_INTERNAL;
- broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_CONNECTED);
- }
- } else {
- broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
- }
- }
- } else if (state == BluetoothHeadset.STATE_AUDIO_DISCONNECTED &&
- (mScoAudioState == SCO_STATE_ACTIVE_INTERNAL ||
- mScoAudioState == SCO_STATE_ACTIVATE_REQ)) {
- if (mScoAudioState == SCO_STATE_ACTIVE_INTERNAL) {
- if (mBluetoothHeadset != null && mBluetoothHeadsetDevice != null) {
- boolean status = false;
- if (mScoAudioMode == SCO_MODE_RAW) {
- status = mBluetoothHeadset.disconnectAudio();
- } else if (mScoAudioMode == SCO_MODE_VIRTUAL_CALL) {
- status = mBluetoothHeadset.stopScoUsingVirtualVoiceCall(
- mBluetoothHeadsetDevice);
- } else if (mScoAudioMode == SCO_MODE_VR) {
- status = mBluetoothHeadset.stopVoiceRecognition(
- mBluetoothHeadsetDevice);
- }
-
- if (!status) {
- mScoAudioState = SCO_STATE_INACTIVE;
- broadcastScoConnectionState(
- AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
- }
- } else if (getBluetoothHeadset()) {
- mScoAudioState = SCO_STATE_DEACTIVATE_REQ;
- }
- } else {
- mScoAudioState = SCO_STATE_INACTIVE;
- broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
- }
- }
- }
- }
- }
-
- private void checkScoAudioState() {
- if (mBluetoothHeadset != null && mBluetoothHeadsetDevice != null &&
- mScoAudioState == SCO_STATE_INACTIVE &&
- mBluetoothHeadset.getAudioState(mBluetoothHeadsetDevice)
- != BluetoothHeadset.STATE_AUDIO_DISCONNECTED) {
- mScoAudioState = SCO_STATE_ACTIVE_EXTERNAL;
- }
- }
-
- private ScoClient getScoClient(IBinder cb, boolean create) {
- synchronized(mScoClients) {
- ScoClient client = null;
- int size = mScoClients.size();
- for (int i = 0; i < size; i++) {
- client = mScoClients.get(i);
- if (client.getBinder() == cb)
- return client;
- }
- if (create) {
- client = new ScoClient(cb);
- mScoClients.add(client);
- }
- return client;
- }
- }
-
- public void clearAllScoClients(int exceptPid, boolean stopSco) {
- synchronized(mScoClients) {
- ScoClient savedClient = null;
- int size = mScoClients.size();
- for (int i = 0; i < size; i++) {
- ScoClient cl = mScoClients.get(i);
- if (cl.getPid() != exceptPid) {
- cl.clearCount(stopSco);
- } else {
- savedClient = cl;
- }
- }
- mScoClients.clear();
- if (savedClient != null) {
- mScoClients.add(savedClient);
- }
- }
- }
-
- private boolean getBluetoothHeadset() {
- boolean result = false;
- BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
- if (adapter != null) {
- result = adapter.getProfileProxy(mContext, mBluetoothProfileServiceListener,
- BluetoothProfile.HEADSET);
- }
- // If we could not get a bluetooth headset proxy, send a failure message
- // without delay to reset the SCO audio state and clear SCO clients.
- // If we could get a proxy, send a delayed failure message that will reset our state
- // in case we don't receive onServiceConnected().
- sendMsg(mAudioHandler, MSG_BT_HEADSET_CNCT_FAILED,
- SENDMSG_REPLACE, 0, 0, null, result ? BT_HEADSET_CNCT_TIMEOUT_MS : 0);
- return result;
- }
-
- private void disconnectBluetoothSco(int exceptPid) {
- synchronized(mScoClients) {
- checkScoAudioState();
- if (mScoAudioState == SCO_STATE_ACTIVE_EXTERNAL ||
- mScoAudioState == SCO_STATE_DEACTIVATE_EXT_REQ) {
- if (mBluetoothHeadsetDevice != null) {
- if (mBluetoothHeadset != null) {
- if (!mBluetoothHeadset.stopVoiceRecognition(
- mBluetoothHeadsetDevice)) {
- sendMsg(mAudioHandler, MSG_BT_HEADSET_CNCT_FAILED,
- SENDMSG_REPLACE, 0, 0, null, 0);
- }
- } else if (mScoAudioState == SCO_STATE_ACTIVE_EXTERNAL &&
- getBluetoothHeadset()) {
- mScoAudioState = SCO_STATE_DEACTIVATE_EXT_REQ;
- }
- }
- } else {
- clearAllScoClients(exceptPid, true);
- }
- }
- }
-
- private void resetBluetoothSco() {
- synchronized(mScoClients) {
- clearAllScoClients(0, false);
- mScoAudioState = SCO_STATE_INACTIVE;
- broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
- }
- }
-
- private void broadcastScoConnectionState(int state) {
- sendMsg(mAudioHandler, MSG_BROADCAST_BT_CONNECTION_STATE,
- SENDMSG_QUEUE, state, 0, null, 0);
- }
-
- private void onBroadcastScoConnectionState(int state) {
- if (state != mScoConnectionState) {
- Intent newIntent = new Intent(AudioManager.ACTION_SCO_AUDIO_STATE_UPDATED);
- newIntent.putExtra(AudioManager.EXTRA_SCO_AUDIO_STATE, state);
- newIntent.putExtra(AudioManager.EXTRA_SCO_AUDIO_PREVIOUS_STATE,
- mScoConnectionState);
- sendStickyBroadcastToAll(newIntent);
- mScoConnectionState = state;
- }
- }
-
- private BluetoothProfile.ServiceListener mBluetoothProfileServiceListener =
- new BluetoothProfile.ServiceListener() {
- public void onServiceConnected(int profile, BluetoothProfile proxy) {
- BluetoothDevice btDevice;
- List<BluetoothDevice> deviceList;
- switch(profile) {
- case BluetoothProfile.A2DP:
- synchronized (mConnectedDevices) {
- synchronized (mA2dpAvrcpLock) {
- mA2dp = (BluetoothA2dp) proxy;
- deviceList = mA2dp.getConnectedDevices();
- if (deviceList.size() > 0) {
- btDevice = deviceList.get(0);
- int state = mA2dp.getConnectionState(btDevice);
- int delay = checkSendBecomingNoisyIntent(
- AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP,
- (state == BluetoothA2dp.STATE_CONNECTED) ? 1 : 0);
- queueMsgUnderWakeLock(mAudioHandler,
- MSG_SET_A2DP_SINK_CONNECTION_STATE,
- state,
- 0,
- btDevice,
- delay);
- }
- }
- }
- break;
-
- case BluetoothProfile.A2DP_SINK:
- deviceList = proxy.getConnectedDevices();
- if (deviceList.size() > 0) {
- btDevice = deviceList.get(0);
- synchronized (mConnectedDevices) {
- int state = proxy.getConnectionState(btDevice);
- queueMsgUnderWakeLock(mAudioHandler,
- MSG_SET_A2DP_SRC_CONNECTION_STATE,
- state,
- 0,
- btDevice,
- 0 /* delay */);
- }
- }
- break;
-
- case BluetoothProfile.HEADSET:
- synchronized (mScoClients) {
- // Discard timeout message
- mAudioHandler.removeMessages(MSG_BT_HEADSET_CNCT_FAILED);
- mBluetoothHeadset = (BluetoothHeadset) proxy;
- deviceList = mBluetoothHeadset.getConnectedDevices();
- if (deviceList.size() > 0) {
- mBluetoothHeadsetDevice = deviceList.get(0);
- } else {
- mBluetoothHeadsetDevice = null;
- }
- // Refresh SCO audio state
- checkScoAudioState();
- // Continue pending action if any
- if (mScoAudioState == SCO_STATE_ACTIVATE_REQ ||
- mScoAudioState == SCO_STATE_DEACTIVATE_REQ ||
- mScoAudioState == SCO_STATE_DEACTIVATE_EXT_REQ) {
- boolean status = false;
- if (mBluetoothHeadsetDevice != null) {
- switch (mScoAudioState) {
- case SCO_STATE_ACTIVATE_REQ:
- mScoAudioState = SCO_STATE_ACTIVE_INTERNAL;
- if (mScoAudioMode == SCO_MODE_RAW) {
- status = mBluetoothHeadset.connectAudio();
- } else if (mScoAudioMode == SCO_MODE_VIRTUAL_CALL) {
- status = mBluetoothHeadset.startScoUsingVirtualVoiceCall(
- mBluetoothHeadsetDevice);
- } else if (mScoAudioMode == SCO_MODE_VR) {
- status = mBluetoothHeadset.startVoiceRecognition(
- mBluetoothHeadsetDevice);
- }
- break;
- case SCO_STATE_DEACTIVATE_REQ:
- if (mScoAudioMode == SCO_MODE_RAW) {
- status = mBluetoothHeadset.disconnectAudio();
- } else if (mScoAudioMode == SCO_MODE_VIRTUAL_CALL) {
- status = mBluetoothHeadset.stopScoUsingVirtualVoiceCall(
- mBluetoothHeadsetDevice);
- } else if (mScoAudioMode == SCO_MODE_VR) {
- status = mBluetoothHeadset.stopVoiceRecognition(
- mBluetoothHeadsetDevice);
- }
- break;
- case SCO_STATE_DEACTIVATE_EXT_REQ:
- status = mBluetoothHeadset.stopVoiceRecognition(
- mBluetoothHeadsetDevice);
- }
- }
- if (!status) {
- sendMsg(mAudioHandler, MSG_BT_HEADSET_CNCT_FAILED,
- SENDMSG_REPLACE, 0, 0, null, 0);
- }
- }
- }
- break;
-
- default:
- break;
- }
- }
- public void onServiceDisconnected(int profile) {
- switch(profile) {
- case BluetoothProfile.A2DP:
- synchronized (mConnectedDevices) {
- synchronized (mA2dpAvrcpLock) {
- mA2dp = null;
- if (mConnectedDevices.containsKey(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP)) {
- makeA2dpDeviceUnavailableNow(
- mConnectedDevices.get(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP));
- }
- }
- }
- break;
-
- case BluetoothProfile.A2DP_SINK:
- synchronized (mConnectedDevices) {
- if (mConnectedDevices.containsKey(AudioSystem.DEVICE_IN_BLUETOOTH_A2DP)) {
- makeA2dpSrcUnavailable(
- mConnectedDevices.get(AudioSystem.DEVICE_IN_BLUETOOTH_A2DP));
- }
- }
- break;
-
- case BluetoothProfile.HEADSET:
- synchronized (mScoClients) {
- mBluetoothHeadset = null;
- }
- break;
-
- default:
- break;
- }
- }
- };
-
- private void onCheckMusicActive() {
- synchronized (mSafeMediaVolumeState) {
- 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,
- null,
- MUSIC_ACTIVE_POLL_PERIOD_MS);
- int index = mStreamStates[AudioSystem.STREAM_MUSIC].getIndex(device);
- if (AudioSystem.isStreamActive(AudioSystem.STREAM_MUSIC, 0) &&
- (index > mSafeMediaVolumeIndex)) {
- // Approximate cumulative active music time
- mMusicActiveMs += MUSIC_ACTIVE_POLL_PERIOD_MS;
- if (mMusicActiveMs > UNSAFE_VOLUME_MUSIC_ACTIVE_MS_MAX) {
- setSafeMediaVolumeEnabled(true);
- mMusicActiveMs = 0;
- }
- saveMusicActiveMs();
- }
- }
- }
- }
- }
-
- private void saveMusicActiveMs() {
- mAudioHandler.obtainMessage(MSG_PERSIST_MUSIC_ACTIVE_MS, mMusicActiveMs, 0).sendToTarget();
- }
-
- private void onConfigureSafeVolume(boolean force) {
- synchronized (mSafeMediaVolumeState) {
- 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;
- boolean safeMediaVolumeEnabled =
- SystemProperties.getBoolean("audio.safemedia.force", false)
- || mContext.getResources().getBoolean(
- com.android.internal.R.bool.config_safe_media_volume_enabled);
-
- // 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) {
- 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();
- } 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) {
- int result = FLAG_ADJUST_VOLUME;
- 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;
- }
- } else {
- // (oldIndex < step) is equivalent to (old UI index == 0)
- if ((oldIndex < step)
- && VOLUME_SETS_RINGER_MODE_SILENT
- && mPrevVolDirection != AudioManager.ADJUST_LOWER) {
- ringerMode = RINGER_MODE_SILENT;
- }
- }
- } else if (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 (oldIndex >= 2 * step && isMuted) {
- ringerMode = RINGER_MODE_NORMAL;
- } else if (mPrevVolDirection != AudioManager.ADJUST_LOWER) {
- if (VOLUME_SETS_RINGER_MODE_SILENT) {
- 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 (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 (PREVENT_VOLUME_ADJUSTMENT_IF_SILENT) {
- 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;
- }
-
- setRingerMode(ringerMode, TAG + ".checkForRingerModeChange", false /*external*/);
-
- mPrevVolDirection = direction;
-
- return result;
- }
-
- @Override
- public boolean isStreamAffectedByRingerMode(int streamType) {
- return (mRingerModeAffectedStreams & (1 << streamType)) != 0;
- }
-
- private boolean isStreamMutedByRingerMode(int streamType) {
- return (mRingerModeMutedStreams & (1 << streamType)) != 0;
- }
-
- boolean updateRingerModeAffectedStreams() {
- int ringerModeAffectedStreams;
- // make sure settings for ringer mode are consistent with device type: non voice capable
- // devices (tablets) include media stream in silent mode whereas phones don't.
- 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);
-
- // ringtone, notification and system streams are always affected by ringer mode
- ringerModeAffectedStreams |= (1 << AudioSystem.STREAM_RING)|
- (1 << AudioSystem.STREAM_NOTIFICATION)|
- (1 << AudioSystem.STREAM_SYSTEM);
-
- switch (mPlatformType) {
- case PLATFORM_TELEVISION:
- ringerModeAffectedStreams = 0;
- break;
- default:
- ringerModeAffectedStreams &= ~(1 << AudioSystem.STREAM_MUSIC);
- break;
- }
-
- synchronized (mCameraSoundForced) {
- 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 false;
- }
-
- 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 ensureValidSteps(int steps) {
- if (Math.abs(steps) > MAX_BATCH_VOLUME_ADJUST_STEPS) {
- throw new IllegalArgumentException("Bad volume adjust steps " + steps);
- }
- }
-
- 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;
- }
-
- private 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);
- }
-
- /**
- * For code clarity for getActiveStreamType(int)
- * @param delay_ms max time since last STREAM_MUSIC activity to consider
- * @return true if STREAM_MUSIC is active in streams handled by AudioFlinger now or
- * in the last "delay_ms" ms.
- */
- private boolean isAfMusicActiveRecently(int delay_ms) {
- return AudioSystem.isStreamActive(AudioSystem.STREAM_MUSIC, delay_ms)
- || AudioSystem.isStreamActiveRemotely(AudioSystem.STREAM_MUSIC, delay_ms);
- }
-
- private int getActiveStreamType(int suggestedStreamType) {
- switch (mPlatformType) {
- case 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 (isAfMusicActiveRecently(StreamOverride.sDelayMs)) {
- if (DEBUG_VOL)
- Log.v(TAG, "getActiveStreamType: Forcing STREAM_MUSIC stream active");
- return AudioSystem.STREAM_MUSIC;
- } else {
- if (DEBUG_VOL)
- Log.v(TAG, "getActiveStreamType: Forcing STREAM_RING b/c default");
- return AudioSystem.STREAM_RING;
- }
- } else if (isAfMusicActiveRecently(0)) {
- if (DEBUG_VOL)
- Log.v(TAG, "getActiveStreamType: Forcing STREAM_MUSIC stream active");
- return AudioSystem.STREAM_MUSIC;
- }
- break;
- case PLATFORM_TELEVISION:
- if (suggestedStreamType == AudioManager.USE_DEFAULT_STREAM_TYPE) {
- // TV always defaults to STREAM_MUSIC
- return AudioSystem.STREAM_MUSIC;
- }
- break;
- 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,
- StreamOverride.sDelayMs) ||
- AudioSystem.isStreamActive(AudioSystem.STREAM_RING,
- StreamOverride.sDelayMs)) {
- if (DEBUG_VOL) Log.v(TAG, "getActiveStreamType: Forcing STREAM_NOTIFICATION");
- return AudioSystem.STREAM_NOTIFICATION;
- } else if (suggestedStreamType == AudioManager.USE_DEFAULT_STREAM_TYPE) {
- if (isAfMusicActiveRecently(StreamOverride.sDelayMs)) {
- if (DEBUG_VOL) Log.v(TAG, "getActiveStreamType: forcing STREAM_MUSIC");
- return AudioSystem.STREAM_MUSIC;
- } else {
- if (DEBUG_VOL) Log.v(TAG,
- "getActiveStreamType: using STREAM_NOTIFICATION as default");
- return AudioSystem.STREAM_NOTIFICATION;
- }
- }
- 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 (ActivityManagerNative.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;
- }
- synchronized (mLastDeviceConnectMsgTime) {
- long time = SystemClock.uptimeMillis() + delay;
- handler.sendMessageAtTime(handler.obtainMessage(msg, arg1, arg2, obj), time);
- if (msg == MSG_SET_WIRED_DEVICE_CONNECTION_STATE ||
- msg == MSG_SET_A2DP_SRC_CONNECTION_STATE ||
- msg == MSG_SET_A2DP_SINK_CONNECTION_STATE) {
- mLastDeviceConnectMsgTime = 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;
- }
-
- private int getDeviceForStream(int stream) {
- int device = AudioSystem.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;
- }
-
- /*
- * A class just for packaging up a set of connection parameters.
- */
- private class WiredDeviceConnectionState {
- public int mType;
- public int mState;
- public String mAddress;
- public String mName;
-
- public WiredDeviceConnectionState(int type, int state, String address, String name) {
- mType = type;
- mState = state;
- mAddress = address;
- mName = name;
- }
- }
-
- public void setWiredDeviceConnectionState(int type, int state, String address,
- String name) {
- synchronized (mConnectedDevices) {
- int delay = checkSendBecomingNoisyIntent(type, state);
- queueMsgUnderWakeLock(mAudioHandler,
- MSG_SET_WIRED_DEVICE_CONNECTION_STATE,
- 0,
- 0,
- new WiredDeviceConnectionState(type, state, address, name),
- delay);
- }
- }
-
- public int setBluetoothA2dpDeviceConnectionState(BluetoothDevice device, int state, int profile)
- {
- int delay;
- if (profile != BluetoothProfile.A2DP && profile != BluetoothProfile.A2DP_SINK) {
- throw new IllegalArgumentException("invalid profile " + profile);
- }
- synchronized (mConnectedDevices) {
- if (profile == BluetoothProfile.A2DP) {
- delay = checkSendBecomingNoisyIntent(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP,
- (state == BluetoothA2dp.STATE_CONNECTED) ? 1 : 0);
- } else {
- delay = 0;
- }
- queueMsgUnderWakeLock(mAudioHandler,
- (profile == BluetoothProfile.A2DP ?
- MSG_SET_A2DP_SINK_CONNECTION_STATE : MSG_SET_A2DP_SRC_CONNECTION_STATE),
- state,
- 0,
- device,
- delay);
- }
- return delay;
- }
-
- ///////////////////////////////////////////////////////////////////////////
- // Inner classes
- ///////////////////////////////////////////////////////////////////////////
-
- // NOTE: Locking order for synchronized objects related to volume or ringer mode management:
- // 1 mScoclient OR mSafeMediaVolumeState
- // 2 mSetModeDeathHandlers
- // 3 mSettingsLock
- // 4 VolumeStreamState.class
- // 5 mCameraSoundForced
- public class VolumeStreamState {
- private final int mStreamType;
-
- private boolean mIsMuted;
- private String mVolumeIndexSettingName;
- private int mIndexMax;
- private final ConcurrentHashMap<Integer, Integer> mIndex =
- new ConcurrentHashMap<Integer, Integer>(8, 0.75f, 4);
-
- private VolumeStreamState(String settingName, int streamType) {
-
- mVolumeIndexSettingName = settingName;
-
- mStreamType = streamType;
- mIndexMax = MAX_STREAM_VOLUME[streamType];
- AudioSystem.initStreamVolume(streamType, 0, mIndexMax);
- mIndexMax *= 10;
-
- readSettings();
- }
-
- public String getSettingNameForDevice(int device) {
- String name = mVolumeIndexSettingName;
- String suffix = AudioSystem.getOutputDeviceName(device);
- if (suffix.isEmpty()) {
- return name;
- }
- return name + "_" + suffix;
- }
-
- public void readSettings() {
- synchronized (VolumeStreamState.class) {
- // force maximum volume on all streams if fixed volume property
- // or master volume property is set
- if (mUseFixedVolume || mUseMasterVolume) {
- mIndex.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 * DEFAULT_STREAM_VOLUME[mStreamType];
- synchronized (mCameraSoundForced) {
- if (mCameraSoundForced) {
- index = mIndexMax;
- }
- }
- mIndex.put(AudioSystem.DEVICE_OUT_DEFAULT, index);
- return;
- }
-
- 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
- String name = getSettingNameForDevice(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) ?
- DEFAULT_STREAM_VOLUME[mStreamType] : -1;
- int index = Settings.System.getIntForUser(
- mContentResolver, name, defaultIndex, UserHandle.USER_CURRENT);
- if (index == -1) {
- continue;
- }
-
- mIndex.put(device, getValidIndex(10 * index));
- }
- }
- }
-
- // must be called while synchronized VolumeStreamState.class
- public void applyDeviceVolume_syncVSS(int device) {
- int index;
- if (mIsMuted) {
- index = 0;
- } else if (((device & AudioSystem.DEVICE_OUT_ALL_A2DP) != 0 && mAvrcpAbsVolSupported)
- || ((device & mFullVolumeDevices) != 0)) {
- index = (mIndexMax + 5)/10;
- } else {
- index = (getIndex(device) + 5)/10;
- }
- AudioSystem.setStreamVolumeIndex(mStreamType, index, device);
- }
-
- public void applyAllVolumes() {
- synchronized (VolumeStreamState.class) {
- // apply default volume first: by convention this will reset all
- // devices volumes in audio policy manager to the supplied value
- int index;
- if (mIsMuted) {
- index = 0;
- } else {
- index = (getIndex(AudioSystem.DEVICE_OUT_DEFAULT) + 5)/10;
- }
- AudioSystem.setStreamVolumeIndex(mStreamType, index, AudioSystem.DEVICE_OUT_DEFAULT);
- // then apply device specific volumes
- Set set = mIndex.entrySet();
- Iterator i = set.iterator();
- while (i.hasNext()) {
- Map.Entry entry = (Map.Entry)i.next();
- int device = ((Integer)entry.getKey()).intValue();
- if (device != AudioSystem.DEVICE_OUT_DEFAULT) {
- if (mIsMuted) {
- index = 0;
- } else if (((device & AudioSystem.DEVICE_OUT_ALL_A2DP) != 0 &&
- mAvrcpAbsVolSupported)
- || ((device & mFullVolumeDevices) != 0))
- {
- index = (mIndexMax + 5)/10;
- } else {
- index = ((Integer)entry.getValue() + 5)/10;
- }
- AudioSystem.setStreamVolumeIndex(mStreamType, index, device);
- }
- }
- }
- }
-
- public boolean adjustIndex(int deltaIndex, int device) {
- return setIndex(getIndex(device) + deltaIndex,
- device);
- }
-
- public boolean setIndex(int index, int device) {
- synchronized (VolumeStreamState.class) {
- int oldIndex = getIndex(device);
- index = getValidIndex(index);
- synchronized (mCameraSoundForced) {
- if ((mStreamType == AudioSystem.STREAM_SYSTEM_ENFORCED) && mCameraSoundForced) {
- index = mIndexMax;
- }
- }
- mIndex.put(device, index);
-
- if (oldIndex != index) {
- // Apply change to all streams using this one as alias
- // if changing volume of current device, also change volume of current
- // device on aliased stream
- boolean currentDevice = (device == getDeviceForStream(mStreamType));
- int numStreamTypes = AudioSystem.getNumStreamTypes();
- for (int streamType = numStreamTypes - 1; streamType >= 0; streamType--) {
- if (streamType != mStreamType &&
- mStreamVolumeAlias[streamType] == mStreamType) {
- int scaledIndex = rescaleIndex(index, mStreamType, streamType);
- mStreamStates[streamType].setIndex(scaledIndex,
- device);
- if (currentDevice) {
- mStreamStates[streamType].setIndex(scaledIndex,
- getDeviceForStream(streamType));
- }
- }
- }
- return true;
- } else {
- return false;
- }
- }
- }
-
- public int getIndex(int device) {
- synchronized (VolumeStreamState.class) {
- Integer index = mIndex.get(device);
- if (index == null) {
- // there is always an entry for AudioSystem.DEVICE_OUT_DEFAULT
- index = mIndex.get(AudioSystem.DEVICE_OUT_DEFAULT);
- }
- return index.intValue();
- }
- }
-
- public int getMaxIndex() {
- return mIndexMax;
- }
-
- public void setAllIndexes(VolumeStreamState srcStream) {
- synchronized (VolumeStreamState.class) {
- 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);
- Set set = mIndex.entrySet();
- Iterator i = set.iterator();
- while (i.hasNext()) {
- Map.Entry entry = (Map.Entry)i.next();
- entry.setValue(index);
- }
- // Now apply actual volume for devices in source stream state
- set = srcStream.mIndex.entrySet();
- i = set.iterator();
- while (i.hasNext()) {
- Map.Entry entry = (Map.Entry)i.next();
- int device = ((Integer)entry.getKey()).intValue();
- index = ((Integer)entry.getValue()).intValue();
- index = rescaleIndex(index, srcStreamType, mStreamType);
-
- setIndex(index, device);
- }
- }
- }
-
- public void setAllIndexesToMax() {
- synchronized (VolumeStreamState.class) {
- Set set = mIndex.entrySet();
- Iterator i = set.iterator();
- while (i.hasNext()) {
- Map.Entry entry = (Map.Entry)i.next();
- entry.setValue(mIndexMax);
- }
- }
- }
-
- public void mute(boolean state) {
- synchronized (VolumeStreamState.class) {
- if (state != mIsMuted) {
- 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);
- }
- }
- }
-
- public int getStreamType() {
- return mStreamType;
- }
-
- public void checkFixedVolumeDevices() {
- synchronized (VolumeStreamState.class) {
- // ignore settings for fixed volume devices: volume should always be at max or 0
- if (mStreamVolumeAlias[mStreamType] == AudioSystem.STREAM_MUSIC) {
- Set set = mIndex.entrySet();
- Iterator i = set.iterator();
- while (i.hasNext()) {
- Map.Entry entry = (Map.Entry)i.next();
- int device = ((Integer)entry.getKey()).intValue();
- int index = ((Integer)entry.getValue()).intValue();
- if (((device & mFullVolumeDevices) != 0)
- || (((device & mFixedVolumeDevices) != 0) && index != 0)) {
- entry.setValue(mIndexMax);
- }
- applyDeviceVolume_syncVSS(device);
- }
- }
- }
- }
-
- private int getValidIndex(int index) {
- if (index < 0) {
- return 0;
- } else if (mUseFixedVolume || mUseMasterVolume || index > mIndexMax) {
- return mIndexMax;
- }
-
- return index;
- }
-
- private void dump(PrintWriter pw) {
- pw.print(" Muted: ");
- pw.println(mIsMuted);
- pw.print(" Max: ");
- pw.println((mIndexMax + 5) / 10);
- pw.print(" Current: ");
- Set set = mIndex.entrySet();
- Iterator i = set.iterator();
- while (i.hasNext()) {
- Map.Entry entry = (Map.Entry)i.next();
- final int device = (Integer) entry.getKey();
- 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 = (((Integer) entry.getValue()) + 5) / 10;
- pw.print(index);
- if (i.hasNext()) {
- pw.print(", ");
- }
- }
- }
- }
-
- /** 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();
- }
- }
-
- /** Handles internal volume messages in separate volume thread. */
- private class AudioHandler extends Handler {
-
- private void setDeviceVolume(VolumeStreamState streamState, int device) {
-
- synchronized (VolumeStreamState.class) {
- // Apply volume
- streamState.applyDeviceVolume_syncVSS(device);
-
- // 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) && mAvrcpAbsVolSupported &&
- ((device & AudioSystem.DEVICE_OUT_ALL_A2DP) != 0)) {
- mStreamStates[streamType].applyDeviceVolume_syncVSS(device);
- }
- mStreamStates[streamType].applyDeviceVolume_syncVSS(streamDevice);
- }
- }
- }
- // Post a persist volume msg
- sendMsg(mAudioHandler,
- MSG_PERSIST_VOLUME,
- SENDMSG_QUEUE,
- device,
- 0,
- streamState,
- PERSIST_DELAY);
-
- }
-
- 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 (isPlatformTelevision() && (streamState.mStreamType != AudioSystem.STREAM_MUSIC)) {
- return;
- }
- 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 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 = Environment.getRootDirectory()
- + SOUND_EFFECTS_PATH
- + SOUND_EFFECT_FILES.get(SOUND_EFFECT_FILES_MAP[effect][0]);
- 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 = Environment.getRootDirectory() + SOUND_EFFECTS_PATH +
- SOUND_EFFECT_FILES.get(SOUND_EFFECT_FILES_MAP[effectType][0]);
- 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 setForceUse(int usage, int config) {
- synchronized (mConnectedDevices) {
- setForceUseInt_SyncDevices(usage, config);
- }
- }
-
- private void onPersistSafeVolumeState(int state) {
- Settings.Global.putInt(mContentResolver,
- Settings.Global.AUDIO_SAFE_VOLUME_STATE,
- state);
- }
-
- @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_MASTER_VOLUME:
- if (mUseFixedVolume) {
- return;
- }
- Settings.System.putFloatForUser(mContentResolver,
- Settings.System.VOLUME_MASTER,
- msg.arg1 / (float)1000.0,
- UserHandle.USER_CURRENT);
- break;
-
- case MSG_PERSIST_MASTER_VOLUME_MUTE:
- if (mUseFixedVolume) {
- return;
- }
- Settings.System.putIntForUser(mContentResolver,
- Settings.System.VOLUME_MASTER_MUTE,
- msg.arg1,
- msg.arg2);
- 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_MEDIA_SERVER_DIED:
- if (!mSystemReady ||
- (AudioSystem.checkAudioFlinger() != AudioSystem.AUDIO_STATUS_OK)) {
- Log.e(TAG, "Media server died.");
- sendMsg(mAudioHandler, MSG_MEDIA_SERVER_DIED, SENDMSG_NOOP, 0, 0,
- null, 500);
- break;
- }
- Log.e(TAG, "Media server 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
- synchronized (mConnectedDevices) {
- Set set = mConnectedDevices.entrySet();
- Iterator i = set.iterator();
- while (i.hasNext()) {
- Map.Entry device = (Map.Entry)i.next();
- AudioSystem.setDeviceConnectionState(
- ((Integer)device.getKey()).intValue(),
- AudioSystem.DEVICE_STATE_AVAILABLE,
- (String)device.getValue(),
- "unknown-device");
- }
- }
- // Restore call state
- AudioSystem.setPhoneState(mMode);
-
- // Restore forced usage for communcations and record
- AudioSystem.setForceUse(AudioSystem.FOR_COMMUNICATION, mForcedUseForComm);
- AudioSystem.setForceUse(AudioSystem.FOR_RECORD, mForcedUseForComm);
- AudioSystem.setForceUse(AudioSystem.FOR_SYSTEM, mCameraSoundForced ?
- AudioSystem.FORCE_SYSTEM_ENFORCED : AudioSystem.FORCE_NONE);
-
- // Restore stream volumes
- int numStreamTypes = AudioSystem.getNumStreamTypes();
- for (int streamType = numStreamTypes - 1; streamType >= 0; streamType--) {
- VolumeStreamState streamState = mStreamStates[streamType];
- AudioSystem.initStreamVolume(streamType, 0, (streamState.mIndexMax + 5) / 10);
-
- streamState.applyAllVolumes();
- }
-
- // Restore ringer mode
- setRingerModeInt(getRingerModeInternal(), false);
-
- // Restore master volume
- restoreMasterVolume();
-
- // Reset device orientation (if monitored for this device)
- if (mMonitorOrientation) {
- setOrientationForAudioSystem();
- }
- if (mMonitorRotation) {
- setRotationForAudioSystem();
- }
-
- synchronized (mBluetoothA2dpEnabledLock) {
- AudioSystem.setForceUse(AudioSystem.FOR_MEDIA,
- mBluetoothA2dpEnabled ?
- AudioSystem.FORCE_NONE : AudioSystem.FORCE_NO_BT_A2DP);
- }
-
- synchronized (mSettingsLock) {
- AudioSystem.setForceUse(AudioSystem.FOR_DOCK,
- mDockAudioMediaEnabled ?
- AudioSystem.FORCE_ANALOG_DOCK : AudioSystem.FORCE_NONE);
- }
- if (mHdmiManager != null) {
- synchronized (mHdmiManager) {
- if (mHdmiTvClient != null) {
- setHdmiSystemAudioSupported(mHdmiSystemAudioSupported);
- }
- }
- }
-
- synchronized (mAudioPolicies) {
- for(AudioPolicyProxy policy : mAudioPolicies.values()) {
- policy.connectMixes();
- }
- }
-
- // indicate the end of reconfiguration phase to audio HAL
- AudioSystem.setParameters("restarting=false");
- 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_BTA2DP_DOCK_TIMEOUT:
- // msg.obj == address of BTA2DP device
- synchronized (mConnectedDevices) {
- makeA2dpDeviceUnavailableNow( (String) msg.obj );
- }
- break;
-
- case MSG_SET_FORCE_USE:
- case MSG_SET_FORCE_BT_A2DP_USE:
- setForceUse(msg.arg1, msg.arg2);
- break;
-
- case MSG_BT_HEADSET_CNCT_FAILED:
- resetBluetoothSco();
- break;
-
- case MSG_SET_WIRED_DEVICE_CONNECTION_STATE:
- { WiredDeviceConnectionState connectState =
- (WiredDeviceConnectionState)msg.obj;
- onSetWiredDeviceConnectionState(connectState.mType, connectState.mState,
- connectState.mAddress, connectState.mName);
- mAudioEventWakeLock.release();
- }
- break;
-
- case MSG_SET_A2DP_SRC_CONNECTION_STATE:
- onSetA2dpSourceConnectionState((BluetoothDevice)msg.obj, msg.arg1);
- mAudioEventWakeLock.release();
- break;
-
- case MSG_SET_A2DP_SINK_CONNECTION_STATE:
- onSetA2dpSinkConnectionState((BluetoothDevice)msg.obj, msg.arg1);
- mAudioEventWakeLock.release();
- break;
-
- case MSG_REPORT_NEW_ROUTES: {
- int N = mRoutesObservers.beginBroadcast();
- if (N > 0) {
- AudioRoutesInfo routes;
- synchronized (mCurAudioRoutes) {
- routes = new AudioRoutesInfo(mCurAudioRoutes);
- }
- while (N > 0) {
- N--;
- IAudioRoutesObserver obs = mRoutesObservers.getBroadcastItem(N);
- try {
- obs.dispatchAudioRoutesChanged(routes);
- } catch (RemoteException e) {
- }
- }
- }
- mRoutesObservers.finishBroadcast();
- break;
- }
-
- case MSG_CHECK_MUSIC_ACTIVE:
- onCheckMusicActive();
- break;
-
- case MSG_BROADCAST_AUDIO_BECOMING_NOISY:
- onSendBecomingNoisyIntent();
- break;
-
- case MSG_CONFIGURE_SAFE_MEDIA_VOLUME_FORCED:
- case MSG_CONFIGURE_SAFE_MEDIA_VOLUME:
- onConfigureSafeVolume((msg.what == MSG_CONFIGURE_SAFE_MEDIA_VOLUME_FORCED));
- break;
- case MSG_PERSIST_SAFE_VOLUME_STATE:
- onPersistSafeVolumeState(msg.arg1);
- break;
-
- case MSG_BROADCAST_BT_CONNECTION_STATE:
- onBroadcastScoConnectionState(msg.arg1);
- break;
-
- case MSG_SYSTEM_READY:
- onSystemReady();
- 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_PERSIST_MICROPHONE_MUTE:
- Settings.System.putIntForUser(mContentResolver,
- Settings.System.MICROPHONE_MUTE,
- msg.arg1,
- msg.arg2);
- break;
- case MSG_UNMUTE_STREAM:
- onUnmuteStream(msg.arg1, msg.arg2);
- break;
- }
- }
- }
-
- private class SettingsObserver extends ContentObserver {
-
- SettingsObserver() {
- super(new Handler());
- 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);
- }
-
- @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 mRingerModeMutedStreams
- // and mRingerModeAffectedStreams, so will leave this synchronized for now.
- // mRingerModeMutedStreams and mMuteAffectedStreams are safe (only accessed once).
- synchronized (mSettingsLock) {
- if (updateRingerModeAffectedStreams()) {
- /*
- * Ensure all stream types that should be affected by ringer mode
- * are in the proper state.
- */
- setRingerModeInt(getRingerModeInternal(), false);
- }
- readDockAudioSettings(mContentResolver);
- }
- }
- }
-
- // must be called synchronized on mConnectedDevices
- private void makeA2dpDeviceAvailable(String address) {
- // enable A2DP before notifying A2DP connection to avoid unecessary processing in
- // audio policy manager
- VolumeStreamState streamState = mStreamStates[AudioSystem.STREAM_MUSIC];
- sendMsg(mAudioHandler, MSG_SET_DEVICE_VOLUME, SENDMSG_QUEUE,
- AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, 0, streamState, 0);
- setBluetoothA2dpOnInt(true);
- AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP,
- AudioSystem.DEVICE_STATE_AVAILABLE,
- address,
- "a2dp-device");
- // Reset A2DP suspend state each time a new sink is connected
- AudioSystem.setParameters("A2dpSuspended=false");
- mConnectedDevices.put( new Integer(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP),
- address);
- }
-
- private void onSendBecomingNoisyIntent() {
- sendBroadcastToAll(new Intent(AudioManager.ACTION_AUDIO_BECOMING_NOISY));
- }
-
- // must be called synchronized on mConnectedDevices
- private void makeA2dpDeviceUnavailableNow(String address) {
- synchronized (mA2dpAvrcpLock) {
- mAvrcpAbsVolSupported = false;
- }
- AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP,
- AudioSystem.DEVICE_STATE_UNAVAILABLE,
- address,
- "a2dp-device");
- mConnectedDevices.remove(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP);
- synchronized (mCurAudioRoutes) {
- // Remove A2DP routes as well
- if (mCurAudioRoutes.mBluetoothName != null) {
- mCurAudioRoutes.mBluetoothName = null;
- sendMsg(mAudioHandler, MSG_REPORT_NEW_ROUTES,
- SENDMSG_NOOP, 0, 0, null, 0);
- }
- }
- }
-
- // must be called synchronized on mConnectedDevices
- private void makeA2dpDeviceUnavailableLater(String address) {
- // prevent any activity on the A2DP audio output to avoid unwanted
- // reconnection of the sink.
- AudioSystem.setParameters("A2dpSuspended=true");
- // the device will be made unavailable later, so consider it disconnected right away
- mConnectedDevices.remove(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP);
- // send the delayed message to make the device unavailable later
- Message msg = mAudioHandler.obtainMessage(MSG_BTA2DP_DOCK_TIMEOUT, address);
- mAudioHandler.sendMessageDelayed(msg, BTA2DP_DOCK_TIMEOUT_MILLIS);
-
- }
-
- // must be called synchronized on mConnectedDevices
- private void makeA2dpSrcAvailable(String address) {
- AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_IN_BLUETOOTH_A2DP,
- AudioSystem.DEVICE_STATE_AVAILABLE,
- address,
- "a2dp-device");
- mConnectedDevices.put( new Integer(AudioSystem.DEVICE_IN_BLUETOOTH_A2DP),
- address);
- }
-
- // must be called synchronized on mConnectedDevices
- private void makeA2dpSrcUnavailable(String address) {
- AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_IN_BLUETOOTH_A2DP,
- AudioSystem.DEVICE_STATE_UNAVAILABLE,
- address,
- "a2dp-device");
- mConnectedDevices.remove(AudioSystem.DEVICE_IN_BLUETOOTH_A2DP);
- }
-
- // must be called synchronized on mConnectedDevices
- private void cancelA2dpDeviceTimeout() {
- mAudioHandler.removeMessages(MSG_BTA2DP_DOCK_TIMEOUT);
- }
-
- // must be called synchronized on mConnectedDevices
- private boolean hasScheduledA2dpDockTimeout() {
- return mAudioHandler.hasMessages(MSG_BTA2DP_DOCK_TIMEOUT);
- }
-
- private void onSetA2dpSinkConnectionState(BluetoothDevice btDevice, int state)
- {
- if (DEBUG_VOL) {
- Log.d(TAG, "onSetA2dpSinkConnectionState btDevice="+btDevice+"state="+state);
- }
- if (btDevice == null) {
- return;
- }
- String address = btDevice.getAddress();
- if (!BluetoothAdapter.checkBluetoothAddress(address)) {
- address = "";
- }
-
- synchronized (mConnectedDevices) {
- boolean isConnected =
- (mConnectedDevices.containsKey(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP) &&
- mConnectedDevices.get(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP).equals(address));
-
- if (isConnected && state != BluetoothProfile.STATE_CONNECTED) {
- if (btDevice.isBluetoothDock()) {
- if (state == BluetoothProfile.STATE_DISCONNECTED) {
- // introduction of a delay for transient disconnections of docks when
- // power is rapidly turned off/on, this message will be canceled if
- // we reconnect the dock under a preset delay
- makeA2dpDeviceUnavailableLater(address);
- // the next time isConnected is evaluated, it will be false for the dock
- }
- } else {
- makeA2dpDeviceUnavailableNow(address);
- }
- synchronized (mCurAudioRoutes) {
- if (mCurAudioRoutes.mBluetoothName != null) {
- mCurAudioRoutes.mBluetoothName = null;
- sendMsg(mAudioHandler, MSG_REPORT_NEW_ROUTES,
- SENDMSG_NOOP, 0, 0, null, 0);
- }
- }
- } else if (!isConnected && state == BluetoothProfile.STATE_CONNECTED) {
- if (btDevice.isBluetoothDock()) {
- // this could be a reconnection after a transient disconnection
- cancelA2dpDeviceTimeout();
- mDockAddress = address;
- } else {
- // this could be a connection of another A2DP device before the timeout of
- // a dock: cancel the dock timeout, and make the dock unavailable now
- if(hasScheduledA2dpDockTimeout()) {
- cancelA2dpDeviceTimeout();
- makeA2dpDeviceUnavailableNow(mDockAddress);
- }
- }
- makeA2dpDeviceAvailable(address);
- synchronized (mCurAudioRoutes) {
- String name = btDevice.getAliasName();
- if (!TextUtils.equals(mCurAudioRoutes.mBluetoothName, name)) {
- mCurAudioRoutes.mBluetoothName = name;
- sendMsg(mAudioHandler, MSG_REPORT_NEW_ROUTES,
- SENDMSG_NOOP, 0, 0, null, 0);
- }
- }
- }
- }
- }
-
- private void onSetA2dpSourceConnectionState(BluetoothDevice btDevice, int state)
- {
- if (DEBUG_VOL) {
- Log.d(TAG, "onSetA2dpSourceConnectionState btDevice="+btDevice+" state="+state);
- }
- if (btDevice == null) {
- return;
- }
- String address = btDevice.getAddress();
- if (!BluetoothAdapter.checkBluetoothAddress(address)) {
- address = "";
- }
-
- synchronized (mConnectedDevices) {
- boolean isConnected =
- (mConnectedDevices.containsKey(AudioSystem.DEVICE_IN_BLUETOOTH_A2DP) &&
- mConnectedDevices.get(AudioSystem.DEVICE_IN_BLUETOOTH_A2DP).equals(address));
-
- if (isConnected && state != BluetoothProfile.STATE_CONNECTED) {
- makeA2dpSrcUnavailable(address);
- } else if (!isConnected && state == BluetoothProfile.STATE_CONNECTED) {
- makeA2dpSrcAvailable(address);
- }
- }
- }
-
- public void avrcpSupportsAbsoluteVolume(String address, boolean support) {
- // address is not used for now, but may be used when multiple a2dp devices are supported
- synchronized (mA2dpAvrcpLock) {
- mAvrcpAbsVolSupported = support;
- sendMsg(mAudioHandler, MSG_SET_DEVICE_VOLUME, SENDMSG_QUEUE,
- AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, 0,
- mStreamStates[AudioSystem.STREAM_MUSIC], 0);
- sendMsg(mAudioHandler, MSG_SET_DEVICE_VOLUME, SENDMSG_QUEUE,
- AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, 0,
- mStreamStates[AudioSystem.STREAM_RING], 0);
- }
- }
-
- private boolean handleDeviceConnection(boolean connect, int device, String address, String deviceName) {
- Slog.i(TAG, "handleDeviceConnection(" + connect +
- " dev:" + Integer.toHexString(device) +
- " address:" + address +
- " name:" + deviceName + ")");
- synchronized (mConnectedDevices) {
- boolean isConnected = (mConnectedDevices.containsKey(device) &&
- (address.isEmpty() || mConnectedDevices.get(device).equals(address)));
-
- if (isConnected && !connect) {
- AudioSystem.setDeviceConnectionState(device,
- AudioSystem.DEVICE_STATE_UNAVAILABLE,
- address, deviceName);
- mConnectedDevices.remove(device);
- return true;
- } else if (!isConnected && connect) {
- AudioSystem.setDeviceConnectionState(device,
- AudioSystem.DEVICE_STATE_AVAILABLE,
- address, deviceName);
- mConnectedDevices.put(new Integer(device), address);
- return true;
- }
- }
- return false;
- }
-
- // Devices which removal triggers intent ACTION_AUDIO_BECOMING_NOISY. The intent is only
- // sent if none of these devices is connected.
- // Access synchronized on mConnectedDevices
- int mBecomingNoisyIntentDevices =
- AudioSystem.DEVICE_OUT_WIRED_HEADSET | AudioSystem.DEVICE_OUT_WIRED_HEADPHONE |
- AudioSystem.DEVICE_OUT_ALL_A2DP | AudioSystem.DEVICE_OUT_HDMI |
- AudioSystem.DEVICE_OUT_ANLG_DOCK_HEADSET | AudioSystem.DEVICE_OUT_DGTL_DOCK_HEADSET |
- AudioSystem.DEVICE_OUT_ALL_USB | AudioSystem.DEVICE_OUT_LINE;
-
- // must be called before removing the device from mConnectedDevices
- // Called synchronized on mConnectedDevices
- private int checkSendBecomingNoisyIntent(int device, int state) {
- int delay = 0;
- if ((state == 0) && ((device & mBecomingNoisyIntentDevices) != 0)) {
- int devices = 0;
- for (int dev : mConnectedDevices.keySet()) {
- if (((dev & AudioSystem.DEVICE_BIT_IN) == 0) &&
- ((dev & mBecomingNoisyIntentDevices) != 0)) {
- devices |= dev;
- }
- }
- if (devices == device) {
- sendMsg(mAudioHandler,
- MSG_BROADCAST_AUDIO_BECOMING_NOISY,
- SENDMSG_REPLACE,
- 0,
- 0,
- null,
- 0);
- delay = 1000;
- }
- }
-
- if (mAudioHandler.hasMessages(MSG_SET_A2DP_SRC_CONNECTION_STATE) ||
- mAudioHandler.hasMessages(MSG_SET_A2DP_SINK_CONNECTION_STATE) ||
- mAudioHandler.hasMessages(MSG_SET_WIRED_DEVICE_CONNECTION_STATE)) {
- synchronized (mLastDeviceConnectMsgTime) {
- long time = SystemClock.uptimeMillis();
- if (mLastDeviceConnectMsgTime > time) {
- delay = (int)(mLastDeviceConnectMsgTime - time) + 30;
- }
- }
- }
- return delay;
- }
-
- private void sendDeviceConnectionIntent(int device, int state, String address, String deviceName)
- {
- Slog.i(TAG, "sendDeviceConnectionIntent(dev:0x" + Integer.toHexString(device) +
- " state:0x" + Integer.toHexString(state) +
- " address:" + address +
- " name:" + deviceName + ");");
- Intent intent = new Intent();
-
- intent.putExtra(CONNECT_INTENT_KEY_STATE, state);
- intent.putExtra(CONNECT_INTENT_KEY_ADDRESS, address);
- intent.putExtra(CONNECT_INTENT_KEY_PORT_NAME, deviceName);
-
- intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
-
- int connType = 0;
-
- if (device == AudioSystem.DEVICE_OUT_WIRED_HEADSET) {
- connType = AudioRoutesInfo.MAIN_HEADSET;
- intent.setAction(Intent.ACTION_HEADSET_PLUG);
- intent.putExtra("microphone", 1);
- } else if (device == AudioSystem.DEVICE_OUT_WIRED_HEADPHONE ||
- device == AudioSystem.DEVICE_OUT_LINE) {
- /*do apps care about line-out vs headphones?*/
- connType = AudioRoutesInfo.MAIN_HEADPHONES;
- intent.setAction(Intent.ACTION_HEADSET_PLUG);
- intent.putExtra("microphone", 0);
- } else if (device == AudioSystem.DEVICE_OUT_HDMI ||
- device == AudioSystem.DEVICE_OUT_HDMI_ARC) {
- connType = AudioRoutesInfo.MAIN_HDMI;
- configureHdmiPlugIntent(intent, state);
- } else if (device == AudioSystem.DEVICE_OUT_USB_DEVICE) {
- connType = AudioRoutesInfo.MAIN_USB;
- }
-
- synchronized (mCurAudioRoutes) {
- if (connType != 0) {
- int newConn = mCurAudioRoutes.mMainType;
- if (state != 0) {
- newConn |= connType;
- } else {
- newConn &= ~connType;
- }
- if (newConn != mCurAudioRoutes.mMainType) {
- mCurAudioRoutes.mMainType = newConn;
- sendMsg(mAudioHandler, MSG_REPORT_NEW_ROUTES,
- SENDMSG_NOOP, 0, 0, null, 0);
- }
- }
- }
-
- final long ident = Binder.clearCallingIdentity();
- try {
- ActivityManagerNative.broadcastStickyIntent(intent, null, UserHandle.USER_ALL);
- } finally {
- Binder.restoreCallingIdentity(ident);
- }
- }
-
- private void onSetWiredDeviceConnectionState(int device, int state, String address,
- String deviceName)
- {
- Slog.i(TAG, "onSetWiredDeviceConnectionState(dev:" + Integer.toHexString(device)
- + " state:" + Integer.toHexString(state)
- + " address:" + address
- + " deviceName:" + deviceName + ");");
-
- synchronized (mConnectedDevices) {
- if ((state == 0) && ((device == AudioSystem.DEVICE_OUT_WIRED_HEADSET) ||
- (device == AudioSystem.DEVICE_OUT_WIRED_HEADPHONE) ||
- (device == AudioSystem.DEVICE_OUT_LINE))) {
- setBluetoothA2dpOnInt(true);
- }
- boolean isUsb = ((device & ~AudioSystem.DEVICE_OUT_ALL_USB) == 0) ||
- (((device & AudioSystem.DEVICE_BIT_IN) != 0) &&
- ((device & ~AudioSystem.DEVICE_IN_ALL_USB) == 0));
- handleDeviceConnection(state == 1, device, address, deviceName);
- if (state != 0) {
- if ((device == AudioSystem.DEVICE_OUT_WIRED_HEADSET) ||
- (device == AudioSystem.DEVICE_OUT_WIRED_HEADPHONE) ||
- (device == AudioSystem.DEVICE_OUT_LINE)) {
- setBluetoothA2dpOnInt(false);
- }
- if ((device & mSafeMediaVolumeDevices) != 0) {
- sendMsg(mAudioHandler,
- MSG_CHECK_MUSIC_ACTIVE,
- SENDMSG_REPLACE,
- 0,
- 0,
- null,
- MUSIC_ACTIVE_POLL_PERIOD_MS);
- }
- // Television devices without CEC service apply software volume on HDMI output
- if (isPlatformTelevision() && ((device & AudioSystem.DEVICE_OUT_HDMI) != 0)) {
- mFixedVolumeDevices |= AudioSystem.DEVICE_OUT_HDMI;
- checkAllFixedVolumeDevices();
- if (mHdmiManager != null) {
- synchronized (mHdmiManager) {
- if (mHdmiPlaybackClient != null) {
- mHdmiCecSink = false;
- mHdmiPlaybackClient.queryDisplayStatus(mHdmiDisplayStatusCallback);
- }
- }
- }
- }
- } else {
- if (isPlatformTelevision() && ((device & AudioSystem.DEVICE_OUT_HDMI) != 0)) {
- if (mHdmiManager != null) {
- synchronized (mHdmiManager) {
- mHdmiCecSink = false;
- }
- }
- }
- }
- if (!isUsb && device != AudioSystem.DEVICE_IN_WIRED_HEADSET) {
- sendDeviceConnectionIntent(device, state, address, deviceName);
- }
- }
- }
-
- private void configureHdmiPlugIntent(Intent intent, int state) {
- intent.setAction(AudioManager.ACTION_HDMI_AUDIO_PLUG);
- intent.putExtra(AudioManager.EXTRA_AUDIO_PLUG_STATE, state);
- if (state == 1) {
- ArrayList<AudioPort> ports = new ArrayList<AudioPort>();
- int[] portGeneration = new int[1];
- int status = AudioSystem.listAudioPorts(ports, portGeneration);
- if (status == AudioManager.SUCCESS) {
- for (AudioPort port : ports) {
- if (port instanceof AudioDevicePort) {
- final AudioDevicePort devicePort = (AudioDevicePort) port;
- if (devicePort.type() == AudioManager.DEVICE_OUT_HDMI ||
- devicePort.type() == AudioManager.DEVICE_OUT_HDMI_ARC) {
- // format the list of supported encodings
- int[] formats = devicePort.formats();
- if (formats.length > 0) {
- ArrayList<Integer> encodingList = new ArrayList(1);
- for (int format : formats) {
- // a format in the list can be 0, skip it
- if (format != AudioFormat.ENCODING_INVALID) {
- encodingList.add(format);
- }
- }
- int[] encodingArray = new int[encodingList.size()];
- for (int i = 0 ; i < encodingArray.length ; i++) {
- encodingArray[i] = encodingList.get(i);
- }
- intent.putExtra(AudioManager.EXTRA_ENCODINGS, encodingArray);
- }
- // find the maximum supported number of channels
- int maxChannels = 0;
- for (int mask : devicePort.channelMasks()) {
- int channelCount = AudioFormat.channelCountFromOutChannelMask(mask);
- if (channelCount > maxChannels) {
- maxChannels = channelCount;
- }
- }
- intent.putExtra(AudioManager.EXTRA_MAX_CHANNEL_COUNT, maxChannels);
- }
- }
- }
- }
- }
- }
-
- /* cache of the address of the last dock the device was connected to */
- private String mDockAddress;
-
- /**
- * Receiver for misc intent broadcasts the Phone app cares about.
- */
- private class AudioServiceBroadcastReceiver extends BroadcastReceiver {
- @Override
- public void onReceive(Context context, Intent intent) {
- 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)))) {
- AudioSystem.setForceUse(AudioSystem.FOR_DOCK, config);
- }
- mDockState = dockState;
- } else if (action.equals(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED)) {
- state = intent.getIntExtra(BluetoothProfile.EXTRA_STATE,
- BluetoothProfile.STATE_DISCONNECTED);
- outDevice = AudioSystem.DEVICE_OUT_BLUETOOTH_SCO;
- inDevice = AudioSystem.DEVICE_IN_BLUETOOTH_SCO_HEADSET;
- String address = null;
-
- BluetoothDevice btDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
- if (btDevice == null) {
- return;
- }
-
- address = btDevice.getAddress();
- BluetoothClass btClass = btDevice.getBluetoothClass();
- if (btClass != null) {
- switch (btClass.getDeviceClass()) {
- case BluetoothClass.Device.AUDIO_VIDEO_WEARABLE_HEADSET:
- case BluetoothClass.Device.AUDIO_VIDEO_HANDSFREE:
- outDevice = AudioSystem.DEVICE_OUT_BLUETOOTH_SCO_HEADSET;
- break;
- case BluetoothClass.Device.AUDIO_VIDEO_CAR_AUDIO:
- outDevice = AudioSystem.DEVICE_OUT_BLUETOOTH_SCO_CARKIT;
- break;
- }
- }
-
- if (!BluetoothAdapter.checkBluetoothAddress(address)) {
- address = "";
- }
-
- boolean connected = (state == BluetoothProfile.STATE_CONNECTED);
- boolean success =
- handleDeviceConnection(connected, outDevice, address, "Bluetooth Headset") &&
- handleDeviceConnection(connected, inDevice, address, "Bluetooth Headset");
- if (success) {
- synchronized (mScoClients) {
- if (connected) {
- mBluetoothHeadsetDevice = btDevice;
- } else {
- mBluetoothHeadsetDevice = null;
- resetBluetoothSco();
- }
- }
- }
- } else if (action.equals(BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED)) {
- boolean broadcast = false;
- int scoAudioState = AudioManager.SCO_AUDIO_STATE_ERROR;
- synchronized (mScoClients) {
- int btState = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, -1);
- // broadcast intent if the connection was initated by AudioService
- if (!mScoClients.isEmpty() &&
- (mScoAudioState == SCO_STATE_ACTIVE_INTERNAL ||
- mScoAudioState == SCO_STATE_ACTIVATE_REQ ||
- mScoAudioState == SCO_STATE_DEACTIVATE_REQ)) {
- broadcast = true;
- }
- switch (btState) {
- case BluetoothHeadset.STATE_AUDIO_CONNECTED:
- scoAudioState = AudioManager.SCO_AUDIO_STATE_CONNECTED;
- if (mScoAudioState != SCO_STATE_ACTIVE_INTERNAL &&
- mScoAudioState != SCO_STATE_DEACTIVATE_REQ &&
- mScoAudioState != SCO_STATE_DEACTIVATE_EXT_REQ) {
- mScoAudioState = SCO_STATE_ACTIVE_EXTERNAL;
- }
- break;
- case BluetoothHeadset.STATE_AUDIO_DISCONNECTED:
- scoAudioState = AudioManager.SCO_AUDIO_STATE_DISCONNECTED;
- mScoAudioState = SCO_STATE_INACTIVE;
- clearAllScoClients(0, false);
- break;
- case BluetoothHeadset.STATE_AUDIO_CONNECTING:
- if (mScoAudioState != SCO_STATE_ACTIVE_INTERNAL &&
- mScoAudioState != SCO_STATE_DEACTIVATE_REQ &&
- mScoAudioState != SCO_STATE_DEACTIVATE_EXT_REQ) {
- mScoAudioState = SCO_STATE_ACTIVE_EXTERNAL;
- }
- default:
- // do not broadcast CONNECTING or invalid state
- broadcast = false;
- break;
- }
- }
- if (broadcast) {
- broadcastScoConnectionState(scoAudioState);
- //FIXME: this is to maintain compatibility with deprecated intent
- // AudioManager.ACTION_SCO_AUDIO_STATE_CHANGED. Remove when appropriate.
- Intent newIntent = new Intent(AudioManager.ACTION_SCO_AUDIO_STATE_CHANGED);
- newIntent.putExtra(AudioManager.EXTRA_SCO_AUDIO_STATE, scoAudioState);
- sendStickyBroadcastToAll(newIntent);
- }
- } else if (action.equals(Intent.ACTION_SCREEN_ON)) {
- if (mMonitorRotation) {
- mOrientationListener.onOrientationChanged(0); //argument is ignored anyway
- mOrientationListener.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
- mOrientationListener.disable();
- }
- AudioSystem.setParameters("screen_state=off");
- } else if (action.equals(Intent.ACTION_CONFIGURATION_CHANGED)) {
- handleConfigurationChanged(context);
- } else if (action.equals(Intent.ACTION_USER_SWITCHED)) {
- // attempt to stop music playback for background user
- sendMsg(mAudioHandler,
- MSG_BROADCAST_AUDIO_BECOMING_NOISY,
- SENDMSG_REPLACE,
- 0,
- 0,
- null,
- 0);
- // 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);
- }
- }
- } // end class AudioServiceBroadcastReceiver
-
- //==========================================================================================
- // RemoteControlDisplay / RemoteControlClient / Remote info
- //==========================================================================================
- public boolean registerRemoteController(IRemoteControlDisplay rcd, int w, int h,
- ComponentName listenerComp) {
- return mMediaFocusControl.registerRemoteController(rcd, w, h, listenerComp);
- }
-
- public boolean registerRemoteControlDisplay(IRemoteControlDisplay rcd, int w, int h) {
- return mMediaFocusControl.registerRemoteControlDisplay(rcd, w, h);
- }
-
- public void unregisterRemoteControlDisplay(IRemoteControlDisplay rcd) {
- mMediaFocusControl.unregisterRemoteControlDisplay(rcd);
- }
-
- public void remoteControlDisplayUsesBitmapSize(IRemoteControlDisplay rcd, int w, int h) {
- mMediaFocusControl.remoteControlDisplayUsesBitmapSize(rcd, w, h);
- }
-
- public void remoteControlDisplayWantsPlaybackPositionSync(IRemoteControlDisplay rcd,
- boolean wantsSync) {
- mMediaFocusControl.remoteControlDisplayWantsPlaybackPositionSync(rcd, wantsSync);
- }
-
- @Override
- public void setRemoteStreamVolume(int index) {
- enforceSelfOrSystemUI("set the remote stream volume");
- mMediaFocusControl.setRemoteStreamVolume(index);
- }
-
- //==========================================================================================
- // Audio Focus
- //==========================================================================================
- public int requestAudioFocus(AudioAttributes aa, int durationHint, IBinder cb,
- IAudioFocusDispatcher fd, String clientId, String callingPackageName, int flags,
- IAudioPolicyCallback pcb) {
- // permission checks
- if ((flags & AudioManager.AUDIOFOCUS_FLAG_LOCK) == AudioManager.AUDIOFOCUS_FLAG_LOCK) {
- if (mMediaFocusControl.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;
- }
- }
- }
- }
-
- return mMediaFocusControl.requestAudioFocus(aa, durationHint, cb, fd,
- clientId, callingPackageName, flags);
- }
-
- public int abandonAudioFocus(IAudioFocusDispatcher fd, String clientId, AudioAttributes aa) {
- return mMediaFocusControl.abandonAudioFocus(fd, clientId, aa);
- }
-
- public void unregisterAudioFocusClient(String clientId) {
- mMediaFocusControl.unregisterAudioFocusClient(clientId);
- }
-
- public int getCurrentAudioFocus() {
- return mMediaFocusControl.getCurrentAudioFocus();
- }
-
- //==========================================================================================
- // Device orientation
- //==========================================================================================
- /**
- * Handles device configuration changes that may map to a change in the orientation
- * or orientation.
- * Monitoring orientation and rotation is optional, and is defined by the definition and value
- * of the "ro.audio.monitorOrientation" and "ro.audio.monitorRotation" system properties.
- */
- private void handleConfigurationChanged(Context context) {
- try {
- // reading new orientation "safely" (i.e. under try catch) in case anything
- // goes wrong when obtaining resources and configuration
- Configuration config = context.getResources().getConfiguration();
- // TODO merge rotation and orientation
- if (mMonitorOrientation) {
- int newOrientation = config.orientation;
- if (newOrientation != mDeviceOrientation) {
- mDeviceOrientation = newOrientation;
- setOrientationForAudioSystem();
- }
- }
- sendMsg(mAudioHandler,
- MSG_CONFIGURE_SAFE_MEDIA_VOLUME,
- SENDMSG_REPLACE,
- 0,
- 0,
- null,
- 0);
-
- boolean cameraSoundForced = mContext.getResources().getBoolean(
- com.android.internal.R.bool.config_camera_sound_forced);
- synchronized (mSettingsLock) {
- boolean cameraSoundForcedChanged = false;
- synchronized (mCameraSoundForced) {
- if (cameraSoundForced != mCameraSoundForced) {
- mCameraSoundForced = cameraSoundForced;
- cameraSoundForcedChanged = true;
- }
- }
- if (cameraSoundForcedChanged) {
- if (!isPlatformTelevision()) {
- VolumeStreamState s = mStreamStates[AudioSystem.STREAM_SYSTEM_ENFORCED];
- if (cameraSoundForced) {
- s.setAllIndexesToMax();
- mRingerModeAffectedStreams &=
- ~(1 << AudioSystem.STREAM_SYSTEM_ENFORCED);
- } else {
- s.setAllIndexes(mStreamStates[AudioSystem.STREAM_SYSTEM]);
- mRingerModeAffectedStreams |=
- (1 << AudioSystem.STREAM_SYSTEM_ENFORCED);
- }
- // take new state into account for streams muted by ringer mode
- setRingerModeInt(getRingerModeInternal(), false);
- }
-
- sendMsg(mAudioHandler,
- MSG_SET_FORCE_USE,
- SENDMSG_QUEUE,
- AudioSystem.FOR_SYSTEM,
- cameraSoundForced ?
- AudioSystem.FORCE_SYSTEM_ENFORCED : AudioSystem.FORCE_NONE,
- null,
- 0);
-
- 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);
- }
- }
-
- private void setOrientationForAudioSystem() {
- switch (mDeviceOrientation) {
- case Configuration.ORIENTATION_LANDSCAPE:
- //Log.i(TAG, "orientation is landscape");
- AudioSystem.setParameters("orientation=landscape");
- break;
- case Configuration.ORIENTATION_PORTRAIT:
- //Log.i(TAG, "orientation is portrait");
- AudioSystem.setParameters("orientation=portrait");
- break;
- case Configuration.ORIENTATION_SQUARE:
- //Log.i(TAG, "orientation is square");
- AudioSystem.setParameters("orientation=square");
- break;
- case Configuration.ORIENTATION_UNDEFINED:
- //Log.i(TAG, "orientation is undefined");
- AudioSystem.setParameters("orientation=undefined");
- break;
- default:
- Log.e(TAG, "Unknown orientation");
- }
- }
-
- private void setRotationForAudioSystem() {
- switch (mDeviceRotation) {
- case Surface.ROTATION_0:
- AudioSystem.setParameters("rotation=0");
- break;
- case Surface.ROTATION_90:
- AudioSystem.setParameters("rotation=90");
- break;
- case Surface.ROTATION_180:
- AudioSystem.setParameters("rotation=180");
- break;
- case Surface.ROTATION_270:
- AudioSystem.setParameters("rotation=270");
- break;
- default:
- Log.e(TAG, "Unknown device rotation");
- }
- }
-
-
- // Handles request to override default use of A2DP for media.
- // Must be called synchronized on mConnectedDevices
- public void setBluetoothA2dpOnInt(boolean on) {
- synchronized (mBluetoothA2dpEnabledLock) {
- mBluetoothA2dpEnabled = on;
- mAudioHandler.removeMessages(MSG_SET_FORCE_BT_A2DP_USE);
- setForceUseInt_SyncDevices(AudioSystem.FOR_MEDIA,
- mBluetoothA2dpEnabled ? AudioSystem.FORCE_NONE : AudioSystem.FORCE_NO_BT_A2DP);
- }
- }
-
- // Must be called synchronized on mConnectedDevices
- private void setForceUseInt_SyncDevices(int usage, int config) {
- switch (usage) {
- case AudioSystem.FOR_MEDIA:
- if (config == AudioSystem.FORCE_NO_BT_A2DP) {
- mBecomingNoisyIntentDevices &= ~AudioSystem.DEVICE_OUT_ALL_A2DP;
- } else { // config == AudioSystem.FORCE_NONE
- mBecomingNoisyIntentDevices |= AudioSystem.DEVICE_OUT_ALL_A2DP;
- }
- break;
- case AudioSystem.FOR_DOCK:
- if (config == AudioSystem.FORCE_ANALOG_DOCK) {
- mBecomingNoisyIntentDevices |= AudioSystem.DEVICE_OUT_ANLG_DOCK_HEADSET;
- } else { // config == AudioSystem.FORCE_NONE
- mBecomingNoisyIntentDevices &= ~AudioSystem.DEVICE_OUT_ANLG_DOCK_HEADSET;
- }
- break;
- default:
- // usage doesn't affect the broadcast of ACTION_AUDIO_BECOMING_NOISY
- }
- AudioSystem.setForceUse(usage, config);
- }
-
- @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) {
- synchronized (mCurAudioRoutes) {
- AudioRoutesInfo routes = new AudioRoutesInfo(mCurAudioRoutes);
- mRoutesObservers.register(observer);
- return routes;
- }
- }
-
-
- //==========================================================================================
- // 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 Integer mSafeMediaVolumeState;
-
- private int mMcc = 0;
- // mSafeMediaVolumeIndex is the cached value of config_safe_media_volume_index property
- private int mSafeMediaVolumeIndex;
- // mSafeMediaVolumeDevices lists the devices for which safe media volume is enforced,
- private final int mSafeMediaVolumeDevices = AudioSystem.DEVICE_OUT_WIRED_HEADSET |
- AudioSystem.DEVICE_OUT_WIRED_HEADPHONE;
- // 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 void setSafeMediaVolumeEnabled(boolean on) {
- synchronized (mSafeMediaVolumeState) {
- 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();
- } 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,
- null,
- MUSIC_ACTIVE_POLL_PERIOD_MS);
- }
- }
- }
- }
-
- private void enforceSafeMediaVolume() {
- 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 > mSafeMediaVolumeIndex) {
- streamState.setIndex(mSafeMediaVolumeIndex, device);
- sendMsg(mAudioHandler,
- MSG_SET_DEVICE_VOLUME,
- SENDMSG_QUEUE,
- device,
- 0,
- streamState,
- 0);
- }
- devices &= ~device;
- }
- }
-
- private boolean checkSafeMediaVolume(int streamType, int index, int device) {
- synchronized (mSafeMediaVolumeState) {
- if ((mSafeMediaVolumeState == SAFE_MEDIA_VOLUME_ACTIVE) &&
- (mStreamVolumeAlias[streamType] == AudioSystem.STREAM_MUSIC) &&
- ((device & mSafeMediaVolumeDevices) != 0) &&
- (index > mSafeMediaVolumeIndex)) {
- return false;
- }
- return true;
- }
- }
-
- @Override
- public void disableSafeMediaVolume() {
- enforceSelfOrSystemUI("disable the safe media volume");
- synchronized (mSafeMediaVolumeState) {
- setSafeMediaVolumeEnabled(false);
- if (mPendingVolumeCommand != null) {
- onSetStreamVolume(mPendingVolumeCommand.mStreamType,
- mPendingVolumeCommand.mIndex,
- mPendingVolumeCommand.mFlags,
- mPendingVolumeCommand.mDevice);
- mPendingVolumeCommand = null;
- }
- }
- }
-
- //==========================================================================================
- // Hdmi Cec system audio mode.
- // If Hdmi Cec's system audio mode is on, audio service should notify volume change
- // to HdmiControlService so that audio recevier can handle volume change.
- //==========================================================================================
-
- private class MyDisplayStatusCallback implements HdmiPlaybackClient.DisplayStatusCallback {
- public void onComplete(int status) {
- if (mHdmiManager != null) {
- synchronized (mHdmiManager) {
- mHdmiCecSink = (status != HdmiControlManager.POWER_STATUS_UNKNOWN);
- // Television devices without CEC service apply software volume on HDMI output
- if (isPlatformTelevision() && !mHdmiCecSink) {
- mFixedVolumeDevices &= ~AudioSystem.DEVICE_OUT_HDMI;
- }
- checkAllFixedVolumeDevices();
- }
- }
- }
- };
-
- // If HDMI-CEC system audio is supported
- private boolean mHdmiSystemAudioSupported = false;
- // Set only when device is tv.
- private HdmiTvClient mHdmiTvClient;
- // true if the device has system feature PackageManager.FEATURE_LEANBACK.
- // cached HdmiControlManager interface
- private HdmiControlManager mHdmiManager;
- // Set only when device is a set-top box.
- private HdmiPlaybackClient mHdmiPlaybackClient;
- // true if we are a set-top box, an HDMI sink is connected and it supports CEC.
- private boolean mHdmiCecSink;
-
- private MyDisplayStatusCallback mHdmiDisplayStatusCallback = new MyDisplayStatusCallback();
-
- @Override
- public int setHdmiSystemAudioSupported(boolean on) {
- int device = AudioSystem.DEVICE_NONE;
- if (mHdmiManager != null) {
- synchronized (mHdmiManager) {
- if (mHdmiTvClient == null) {
- Log.w(TAG, "Only Hdmi-Cec enabled TV device supports system audio mode.");
- return device;
- }
-
- synchronized (mHdmiTvClient) {
- if (mHdmiSystemAudioSupported != on) {
- mHdmiSystemAudioSupported = on;
- AudioSystem.setForceUse(AudioSystem.FOR_HDMI_SYSTEM_AUDIO,
- on ? AudioSystem.FORCE_HDMI_SYSTEM_AUDIO_ENFORCED :
- AudioSystem.FORCE_NONE);
- }
- device = AudioSystem.getDevicesForStream(AudioSystem.STREAM_MUSIC);
- }
- }
- }
- return device;
- }
-
- @Override
- public boolean isHdmiSystemAudioSupported() {
- return mHdmiSystemAudioSupported;
- }
-
- //==========================================================================================
- // Accessibility: taking touch exploration into account for selecting the default
- // stream override timeout when adjusting volume
- //==========================================================================================
- private static class StreamOverride
- implements AccessibilityManager.TouchExplorationStateChangeListener {
-
- // AudioService.getActiveStreamType() will return:
- // - STREAM_NOTIFICATION on tablets during this period after a notification stopped
- // - STREAM_MUSIC on phones during this period after music or talkback/voice search prompt
- // stopped
- private static final int DEFAULT_STREAM_TYPE_OVERRIDE_DELAY_MS = 5000;
- private static final int TOUCH_EXPLORE_STREAM_TYPE_OVERRIDE_DELAY_MS = 1000;
-
- static int sDelayMs;
-
- static void init(Context ctxt) {
- AccessibilityManager accessibilityManager =
- (AccessibilityManager) ctxt.getSystemService(Context.ACCESSIBILITY_SERVICE);
- updateDefaultStreamOverrideDelay(
- accessibilityManager.isTouchExplorationEnabled());
- accessibilityManager.addTouchExplorationStateChangeListener(
- new StreamOverride());
- }
-
- @Override
- public void onTouchExplorationStateChanged(boolean enabled) {
- updateDefaultStreamOverrideDelay(enabled);
- }
-
- private static void updateDefaultStreamOverrideDelay(boolean touchExploreEnabled) {
- if (touchExploreEnabled) {
- sDelayMs = TOUCH_EXPLORE_STREAM_TYPE_OVERRIDE_DELAY_MS;
- } else {
- sDelayMs = DEFAULT_STREAM_TYPE_OVERRIDE_DELAY_MS;
- }
- if (DEBUG_VOL) Log.d(TAG, "Touch exploration enabled=" + touchExploreEnabled
- + " stream override delay is now " + sDelayMs + " ms");
- }
- }
-
- //==========================================================================================
- // 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
- private Boolean mCameraSoundForced;
-
- // called by android.hardware.Camera to populate CameraInfo.canDisableShutterSound
- public boolean isCameraSoundForced() {
- synchronized (mCameraSoundForced) {
- return mCameraSoundForced;
- }
- }
-
- 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]);
- pw.print("- ringer mode affected streams = 0x");
- pw.println(Integer.toHexString(mRingerModeAffectedStreams));
- pw.print("- ringer mode muted streams = 0x");
- pw.println(Integer.toHexString(mRingerModeMutedStreams));
- pw.print("- delegate = "); pw.println(mRingerModeDelegate);
- }
-
- @Override
- protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
- mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DUMP, TAG);
-
- mMediaFocusControl.dump(pw);
- dumpStreamStates(pw);
- dumpRingerMode(pw);
- pw.println("\nAudio routes:");
- pw.print(" mMainType=0x"); pw.println(Integer.toHexString(mCurAudioRoutes.mMainType));
- pw.print(" mBluetoothName="); pw.println(mCurAudioRoutes.mBluetoothName);
-
- 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(" mPendingVolumeCommand="); pw.println(mPendingVolumeCommand);
- pw.print(" mMusicActiveMs="); pw.println(mMusicActiveMs);
- pw.print(" mMcc="); pw.println(mMcc);
- pw.print(" mHasVibrator="); pw.println(mHasVibrator);
-
- dumpAudioPolicies(pw);
- }
-
- private static String safeMediaVolumeStateToString(Integer 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()
- {
- int status = AudioSystem.setLowRamDevice(ActivityManager.isLowRamDeviceStatic());
- if (status != 0) {
- Log.w(TAG, "AudioFlinger informed of device's low RAM attribute; status " + status);
- }
- }
-
- private void enforceSelfOrSystemUI(String action) {
- mContext.enforceCallingOrSelfPermission(android.Manifest.permission.STATUS_BAR_SERVICE,
- "Only SystemUI can " + action);
- }
-
- @Override
- public void setVolumeController(final IVolumeController controller) {
- enforceSelfOrSystemUI("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) {
- enforceSelfOrSystemUI("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);
- }
-
- 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 == AudioSystem.STREAM_RING && 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 postMasterVolumeChanged(int flags) {
- if (mController == null)
- return;
- try {
- mController.masterVolumeChanged(flags);
- } catch (RemoteException e) {
- Log.w(TAG, "Error calling masterVolumeChanged", 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);
- }
- }
- }
-
- /**
- * 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) {
- 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, uid);
- }
-
- @Override
- public void adjustStreamVolumeForUid(int streamType, int direction, int flags,
- String callingPackage, int uid) {
- adjustStreamVolume(streamType, direction, flags, callingPackage, uid);
- }
-
- @Override
- public void setStreamVolumeForUid(int streamType, int direction, int flags,
- String callingPackage, int uid) {
- setStreamVolume(streamType, direction, flags, callingPackage, uid);
- }
-
- @Override
- public void adjustMasterVolumeForUid(int steps, int flags, String callingPackage,
- int uid) {
- adjustMasterVolume(steps, flags, callingPackage, uid);
- }
-
- @Override
- public int getRingerModeInternal() {
- return AudioService.this.getRingerModeInternal();
- }
-
- @Override
- public void setRingerModeInternal(int ringerMode, String caller) {
- AudioService.this.setRingerModeInternal(ringerMode, caller);
- }
- }
-
- //==========================================================================================
- // Audio policy management
- //==========================================================================================
- public String registerAudioPolicy(AudioPolicyConfig policyConfig, IAudioPolicyCallback pcb,
- boolean hasFocusListener) {
- if (DEBUG_AP) Log.d(TAG, "registerAudioPolicy for " + pcb.asBinder()
- + " with config:" + policyConfig);
- String regId = null;
- // error handling
- boolean hasPermissionForPolicy =
- (PackageManager.PERMISSION_GRANTED == mContext.checkCallingPermission(
- android.Manifest.permission.MODIFY_AUDIO_ROUTING));
- if (!hasPermissionForPolicy) {
- Slog.w(TAG, "Can't register audio policy for pid " + Binder.getCallingPid() + " / uid "
- + Binder.getCallingUid() + ", need MODIFY_AUDIO_ROUTING");
- return null;
- }
-
- synchronized (mAudioPolicies) {
- try {
- if (mAudioPolicies.containsKey(pcb.asBinder())) {
- Slog.e(TAG, "Cannot re-register policy");
- return null;
- }
- AudioPolicyProxy app = new AudioPolicyProxy(policyConfig, pcb, hasFocusListener);
- 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;
- }
- }
- return regId;
- }
-
- public void unregisterAudioPolicyAsync(IAudioPolicyCallback pcb) {
- if (DEBUG_AP) Log.d(TAG, "unregisterAudioPolicyAsync for " + pcb.asBinder());
- 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
- }
-
- public int setFocusPropertiesForPolicy(int duckingBehavior, IAudioPolicyCallback pcb) {
- if (DEBUG_AP) Log.d(TAG, "setFocusPropertiesForPolicy() duck behavior=" + duckingBehavior
- + " policy " + pcb.asBinder());
- // error handling
- boolean hasPermissionForPolicy =
- (PackageManager.PERMISSION_GRANTED == mContext.checkCallingPermission(
- android.Manifest.permission.MODIFY_AUDIO_ROUTING));
- if (!hasPermissionForPolicy) {
- Slog.w(TAG, "Cannot change audio policy ducking handling for pid " +
- + Binder.getCallingPid() + " / uid "
- + Binder.getCallingUid() + ", need MODIFY_AUDIO_ROUTING");
- return AudioManager.ERROR;
- }
-
- synchronized (mAudioPolicies) {
- if (!mAudioPolicies.containsKey(pcb.asBinder())) {
- Slog.e(TAG, "Cannot change audio policy focus properties, unregistered policy");
- return AudioManager.ERROR;
- }
- final AudioPolicyProxy app = mAudioPolicies.get(pcb.asBinder());
- 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;
- }
-
- private void dumpAudioPolicies(PrintWriter pw) {
- pw.println("\nAudio policies:");
- synchronized (mAudioPolicies) {
- for(AudioPolicyProxy policy : mAudioPolicies.values()) {
- pw.println(policy.toLogFriendlyString());
- }
- }
- }
-
- //======================
- // Audio policy proxy
- //======================
- /**
- * 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";
- AudioPolicyConfig mConfig;
- IAudioPolicyCallback mPolicyToken;
- boolean mHasFocusListener;
- /**
- * 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;
-
- AudioPolicyProxy(AudioPolicyConfig config, IAudioPolicyCallback token,
- boolean hasFocusListener) {
- super(config);
- setRegistration(new String(config.hashCode() + ":ap:" + mAudioPolicyCounter++));
- mPolicyToken = token;
- mHasFocusListener = hasFocusListener;
- if (mHasFocusListener) {
- mMediaFocusControl.addFocusFollower(mPolicyToken);
- }
- connectMixes();
- }
-
- public void binderDied() {
- synchronized (mAudioPolicies) {
- Log.i(TAG, "audio policy " + mPolicyToken + " died");
- release();
- mAudioPolicies.remove(mPolicyToken.asBinder());
- }
- }
-
- String getRegistrationId() {
- return getRegistration();
- }
-
- void release() {
- if (mFocusDuckBehavior == AudioPolicy.FOCUS_POLICY_DUCKING_IN_POLICY) {
- mMediaFocusControl.setDuckingInExtPolicyAvailable(false);
- }
- if (mHasFocusListener) {
- mMediaFocusControl.removeFocusFollower(mPolicyToken);
- }
- AudioSystem.registerPolicyMixes(mMixes, false);
- }
-
- void connectMixes() {
- AudioSystem.registerPolicyMixes(mMixes, true);
- }
- };
-
- private HashMap<IBinder, AudioPolicyProxy> mAudioPolicies =
- new HashMap<IBinder, AudioPolicyProxy>();
- private int mAudioPolicyCounter = 0; // always accessed synchronized on mAudioPolicies
-}
diff --git a/media/java/android/media/AudioSystem.java b/media/java/android/media/AudioSystem.java
index 7084656..787320e 100644
--- a/media/java/android/media/AudioSystem.java
+++ b/media/java/android/media/AudioSystem.java
@@ -16,7 +16,10 @@
package android.media;
+import android.content.Context;
+import android.content.pm.PackageManager;
import android.media.audiopolicy.AudioMix;
+
import java.util.ArrayList;
/* IF YOU CHANGE ANY OF THE CONSTANTS IN THIS FILE, DO NOT FORGET
@@ -65,6 +68,19 @@ public class AudioSystem
private static final int NUM_STREAM_TYPES = 10;
public static final int getNumStreamTypes() { return NUM_STREAM_TYPES; }
+ public static final String[] STREAM_NAMES = new String[] {
+ "STREAM_VOICE_CALL",
+ "STREAM_SYSTEM",
+ "STREAM_RING",
+ "STREAM_MUSIC",
+ "STREAM_ALARM",
+ "STREAM_NOTIFICATION",
+ "STREAM_BLUETOOTH_SCO",
+ "STREAM_SYSTEM_ENFORCED",
+ "STREAM_DTMF",
+ "STREAM_TTS"
+ };
+
/*
* Sets the microphone mute on or off.
*
@@ -570,5 +586,93 @@ public class AudioSystem
public static native int getAudioHwSyncForSession(int sessionId);
public static native int registerPolicyMixes(ArrayList<AudioMix> mixes, boolean register);
+
+
+ // Items shared with audio service
+
+ /**
+ * The delay before playing a sound. This small period exists so the user
+ * can press another key (non-volume keys, too) to have it NOT be audible.
+ * <p>
+ * PhoneWindow will implement this part.
+ */
+ public static final int PLAY_SOUND_DELAY = 300;
+
+ /**
+ * Constant to identify a focus stack entry that is used to hold the focus while the phone
+ * is ringing or during a call. Used by com.android.internal.telephony.CallManager when
+ * entering and exiting calls.
+ */
+ public final static String IN_VOICE_COMM_FOCUS_ID = "AudioFocus_For_Phone_Ring_And_Calls";
+
+ /**
+ * @see AudioManager#setVibrateSetting(int, int)
+ */
+ public static int getValueForVibrateSetting(int existingValue, int vibrateType,
+ int vibrateSetting) {
+
+ // First clear the existing setting. Each vibrate type has two bits in
+ // the value. Note '3' is '11' in binary.
+ existingValue &= ~(3 << (vibrateType * 2));
+
+ // Set into the old value
+ existingValue |= (vibrateSetting & 3) << (vibrateType * 2);
+
+ return existingValue;
+ }
+
+ public static int getDefaultStreamVolume(int streamType) {
+ return DEFAULT_STREAM_VOLUME[streamType];
+ }
+
+ public static int[] DEFAULT_STREAM_VOLUME = new int[] {
+ 4, // STREAM_VOICE_CALL
+ 7, // STREAM_SYSTEM
+ 5, // STREAM_RING
+ 11, // STREAM_MUSIC
+ 6, // STREAM_ALARM
+ 5, // STREAM_NOTIFICATION
+ 7, // STREAM_BLUETOOTH_SCO
+ 7, // STREAM_SYSTEM_ENFORCED
+ 11, // STREAM_DTMF
+ 11 // STREAM_TTS
+ };
+
+ public static String streamToString(int stream) {
+ if (stream >= 0 && stream < STREAM_NAMES.length) return STREAM_NAMES[stream];
+ if (stream == AudioManager.USE_DEFAULT_STREAM_TYPE) return "USE_DEFAULT_STREAM_TYPE";
+ return "UNKNOWN_STREAM_" + stream;
+ }
+
+ /** The platform has no specific capabilities */
+ public static final int PLATFORM_DEFAULT = 0;
+ /** The platform is voice call capable (a phone) */
+ public static final int PLATFORM_VOICE = 1;
+ /** The platform is a television or a set-top box */
+ public static final int PLATFORM_TELEVISION = 2;
+
+ /**
+ * Return the platform type that this is running on. One of:
+ * <ul>
+ * <li>{@link #PLATFORM_VOICE}</li>
+ * <li>{@link #PLATFORM_TELEVISION}</li>
+ * <li>{@link #PLATFORM_DEFAULT}</li>
+ * </ul>
+ */
+ public static int getPlatformType(Context context) {
+ if (context.getResources().getBoolean(com.android.internal.R.bool.config_voice_capable)) {
+ return PLATFORM_VOICE;
+ } else if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_LEANBACK)) {
+ return PLATFORM_TELEVISION;
+ } else {
+ return PLATFORM_DEFAULT;
+ }
+ }
+
+ public static final int DEFAULT_MUTE_STREAMS_AFFECTED =
+ (1 << STREAM_MUSIC) |
+ (1 << STREAM_RING) |
+ (1 << STREAM_NOTIFICATION) |
+ (1 << STREAM_SYSTEM);
}
diff --git a/media/java/android/media/FocusRequester.java b/media/java/android/media/FocusRequester.java
deleted file mode 100644
index bbe5fd2..0000000
--- a/media/java/android/media/FocusRequester.java
+++ /dev/null
@@ -1,328 +0,0 @@
-/*
- * Copyright (C) 2013 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 android.media;
-
-import android.annotation.NonNull;
-import android.media.MediaFocusControl.AudioFocusDeathHandler;
-import android.os.IBinder;
-import android.util.Log;
-
-import java.io.PrintWriter;
-
-/**
- * @hide
- * Class to handle all the information about a user of audio focus. The lifecycle of each
- * instance is managed by android.media.MediaFocusControl, from its addition to the audio focus
- * stack to its release.
- */
-class FocusRequester {
-
- // on purpose not using this classe's name, as it will only be used from MediaFocusControl
- private static final String TAG = "MediaFocusControl";
- private static final boolean DEBUG = false;
-
- private AudioFocusDeathHandler mDeathHandler;
- private final IAudioFocusDispatcher mFocusDispatcher; // may be null
- private final IBinder mSourceRef;
- private final String mClientId;
- private final String mPackageName;
- private final int mCallingUid;
- private final MediaFocusControl mFocusController; // never null
- /**
- * the audio focus gain request that caused the addition of this object in the focus stack.
- */
- private final int mFocusGainRequest;
- /**
- * the flags associated with the gain request that qualify the type of grant (e.g. accepting
- * delay vs grant must be immediate)
- */
- private final int mGrantFlags;
- /**
- * the audio focus loss received my mFocusDispatcher, is AudioManager.AUDIOFOCUS_NONE if
- * it never lost focus.
- */
- private int mFocusLossReceived;
- /**
- * the audio attributes associated with the focus request
- */
- private final AudioAttributes mAttributes;
-
- /**
- * Class constructor
- * @param aa
- * @param focusRequest
- * @param grantFlags
- * @param afl
- * @param source
- * @param id
- * @param hdlr
- * @param pn
- * @param uid
- * @param ctlr cannot be null
- */
- FocusRequester(AudioAttributes aa, int focusRequest, int grantFlags,
- IAudioFocusDispatcher afl, IBinder source, String id, AudioFocusDeathHandler hdlr,
- String pn, int uid, @NonNull MediaFocusControl ctlr) {
- mAttributes = aa;
- mFocusDispatcher = afl;
- mSourceRef = source;
- mClientId = id;
- mDeathHandler = hdlr;
- mPackageName = pn;
- mCallingUid = uid;
- mFocusGainRequest = focusRequest;
- mGrantFlags = grantFlags;
- mFocusLossReceived = AudioManager.AUDIOFOCUS_NONE;
- mFocusController = ctlr;
- }
-
-
- boolean hasSameClient(String otherClient) {
- try {
- return mClientId.compareTo(otherClient) == 0;
- } catch (NullPointerException e) {
- return false;
- }
- }
-
- boolean isLockedFocusOwner() {
- return ((mGrantFlags & AudioManager.AUDIOFOCUS_FLAG_LOCK) != 0);
- }
-
- boolean hasSameBinder(IBinder ib) {
- return (mSourceRef != null) && mSourceRef.equals(ib);
- }
-
- boolean hasSamePackage(String pack) {
- try {
- return mPackageName.compareTo(pack) == 0;
- } catch (NullPointerException e) {
- return false;
- }
- }
-
- boolean hasSameUid(int uid) {
- return mCallingUid == uid;
- }
-
- String getClientId() {
- return mClientId;
- }
-
- int getGainRequest() {
- return mFocusGainRequest;
- }
-
- int getGrantFlags() {
- return mGrantFlags;
- }
-
- AudioAttributes getAudioAttributes() {
- return mAttributes;
- }
-
-
- private static String focusChangeToString(int focus) {
- switch(focus) {
- case AudioManager.AUDIOFOCUS_NONE:
- return "none";
- case AudioManager.AUDIOFOCUS_GAIN:
- return "GAIN";
- case AudioManager.AUDIOFOCUS_GAIN_TRANSIENT:
- return "GAIN_TRANSIENT";
- case AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK:
- return "GAIN_TRANSIENT_MAY_DUCK";
- case AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE:
- return "GAIN_TRANSIENT_EXCLUSIVE";
- case AudioManager.AUDIOFOCUS_LOSS:
- return "LOSS";
- case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
- return "LOSS_TRANSIENT";
- case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
- return "LOSS_TRANSIENT_CAN_DUCK";
- default:
- return "[invalid focus change" + focus + "]";
- }
- }
-
- private String focusGainToString() {
- return focusChangeToString(mFocusGainRequest);
- }
-
- private String focusLossToString() {
- return focusChangeToString(mFocusLossReceived);
- }
-
- private static String flagsToString(int flags) {
- String msg = new String();
- if ((flags & AudioManager.AUDIOFOCUS_FLAG_DELAY_OK) != 0) {
- msg += "DELAY_OK";
- }
- if ((flags & AudioManager.AUDIOFOCUS_FLAG_LOCK) != 0) {
- if (!msg.isEmpty()) { msg += "|"; }
- msg += "LOCK";
- }
- if ((flags & AudioManager.AUDIOFOCUS_FLAG_PAUSES_ON_DUCKABLE_LOSS) != 0) {
- if (!msg.isEmpty()) { msg += "|"; }
- msg += "PAUSES_ON_DUCKABLE_LOSS";
- }
- return msg;
- }
-
- void dump(PrintWriter pw) {
- pw.println(" source:" + mSourceRef
- + " -- pack: " + mPackageName
- + " -- client: " + mClientId
- + " -- gain: " + focusGainToString()
- + " -- flags: " + flagsToString(mGrantFlags)
- + " -- loss: " + focusLossToString()
- + " -- uid: " + mCallingUid
- + " -- attr: " + mAttributes);
- }
-
-
- void release() {
- try {
- if (mSourceRef != null && mDeathHandler != null) {
- mSourceRef.unlinkToDeath(mDeathHandler, 0);
- mDeathHandler = null;
- }
- } catch (java.util.NoSuchElementException e) {
- Log.e(TAG, "FocusRequester.release() hit ", e);
- }
- }
-
- @Override
- protected void finalize() throws Throwable {
- release();
- super.finalize();
- }
-
- /**
- * For a given audio focus gain request, return the audio focus loss type that will result
- * from it, taking into account any previous focus loss.
- * @param gainRequest
- * @return the audio focus loss type that matches the gain request
- */
- private int focusLossForGainRequest(int gainRequest) {
- switch(gainRequest) {
- case AudioManager.AUDIOFOCUS_GAIN:
- switch(mFocusLossReceived) {
- case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
- case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
- case AudioManager.AUDIOFOCUS_LOSS:
- case AudioManager.AUDIOFOCUS_NONE:
- return AudioManager.AUDIOFOCUS_LOSS;
- }
- case AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE:
- case AudioManager.AUDIOFOCUS_GAIN_TRANSIENT:
- switch(mFocusLossReceived) {
- case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
- case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
- case AudioManager.AUDIOFOCUS_NONE:
- return AudioManager.AUDIOFOCUS_LOSS_TRANSIENT;
- case AudioManager.AUDIOFOCUS_LOSS:
- return AudioManager.AUDIOFOCUS_LOSS;
- }
- case AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK:
- switch(mFocusLossReceived) {
- case AudioManager.AUDIOFOCUS_NONE:
- case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
- return AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK;
- case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
- return AudioManager.AUDIOFOCUS_LOSS_TRANSIENT;
- case AudioManager.AUDIOFOCUS_LOSS:
- return AudioManager.AUDIOFOCUS_LOSS;
- }
- default:
- Log.e(TAG, "focusLossForGainRequest() for invalid focus request "+ gainRequest);
- return AudioManager.AUDIOFOCUS_NONE;
- }
- }
-
- /**
- * Called synchronized on MediaFocusControl.mAudioFocusLock
- */
- void handleExternalFocusGain(int focusGain) {
- int focusLoss = focusLossForGainRequest(focusGain);
- handleFocusLoss(focusLoss);
- }
-
- /**
- * Called synchronized on MediaFocusControl.mAudioFocusLock
- */
- void handleFocusGain(int focusGain) {
- try {
- mFocusLossReceived = AudioManager.AUDIOFOCUS_NONE;
- mFocusController.notifyExtPolicyFocusGrant_syncAf(toAudioFocusInfo(),
- AudioManager.AUDIOFOCUS_REQUEST_GRANTED);
- if (mFocusDispatcher != null) {
- if (DEBUG) {
- Log.v(TAG, "dispatching " + focusChangeToString(focusGain) + " to "
- + mClientId);
- }
- mFocusDispatcher.dispatchAudioFocusChange(focusGain, mClientId);
- }
- } catch (android.os.RemoteException e) {
- Log.e(TAG, "Failure to signal gain of audio focus due to: ", e);
- }
- }
-
- /**
- * Called synchronized on MediaFocusControl.mAudioFocusLock
- */
- void handleFocusLoss(int focusLoss) {
- try {
- if (focusLoss != mFocusLossReceived) {
- mFocusLossReceived = focusLoss;
- // before dispatching a focus loss, check if the following conditions are met:
- // 1/ the framework is not supposed to notify the focus loser on a DUCK loss
- // 2/ it is a DUCK loss
- // 3/ the focus loser isn't flagged as pausing in a DUCK loss
- // if they are, do not notify the focus loser
- if (!mFocusController.mustNotifyFocusOwnerOnDuck()
- && mFocusLossReceived == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK
- && (mGrantFlags
- & AudioManager.AUDIOFOCUS_FLAG_PAUSES_ON_DUCKABLE_LOSS) == 0) {
- if (DEBUG) {
- Log.v(TAG, "NOT dispatching " + focusChangeToString(mFocusLossReceived)
- + " to " + mClientId + ", to be handled externally");
- }
- mFocusController.notifyExtPolicyFocusLoss_syncAf(
- toAudioFocusInfo(), false /* wasDispatched */);
- return;
- }
- if (mFocusDispatcher != null) {
- if (DEBUG) {
- Log.v(TAG, "dispatching " + focusChangeToString(mFocusLossReceived) + " to "
- + mClientId);
- }
- mFocusController.notifyExtPolicyFocusLoss_syncAf(
- toAudioFocusInfo(), true /* wasDispatched */);
- mFocusDispatcher.dispatchAudioFocusChange(mFocusLossReceived, mClientId);
- }
- }
- } catch (android.os.RemoteException e) {
- Log.e(TAG, "Failure to signal loss of audio focus due to:", e);
- }
- }
-
- AudioFocusInfo toAudioFocusInfo() {
- return new AudioFocusInfo(mAttributes, mClientId, mPackageName,
- mFocusGainRequest, mFocusLossReceived, mGrantFlags);
- }
-}
diff --git a/media/java/android/media/MediaFocusControl.java b/media/java/android/media/MediaFocusControl.java
deleted file mode 100644
index 6518bd1..0000000
--- a/media/java/android/media/MediaFocusControl.java
+++ /dev/null
@@ -1,2197 +0,0 @@
-/*
- * Copyright (C) 2013 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 android.media;
-
-import android.app.Activity;
-import android.app.ActivityManager;
-import android.app.AppOpsManager;
-import android.app.KeyguardManager;
-import android.app.PendingIntent;
-import android.app.PendingIntent.CanceledException;
-import android.app.PendingIntent.OnFinished;
-import android.content.ActivityNotFoundException;
-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.PackageManager;
-import android.database.ContentObserver;
-import android.media.PlayerRecord.RemotePlaybackState;
-import android.media.audiopolicy.IAudioPolicyCallback;
-import android.net.Uri;
-import android.os.Binder;
-import android.os.Bundle;
-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.UserHandle;
-import android.os.IBinder.DeathRecipient;
-import android.provider.Settings;
-import android.speech.RecognizerIntent;
-import android.telephony.PhoneStateListener;
-import android.telephony.TelephonyManager;
-import android.util.Log;
-import android.util.Slog;
-import android.view.KeyEvent;
-
-import java.io.PrintWriter;
-import java.util.ArrayList;
-import java.util.Iterator;
-import java.util.Stack;
-
-/**
- * @hide
- *
- */
-public class MediaFocusControl implements OnFinished {
-
- private static final String TAG = "MediaFocusControl";
-
- /** Debug remote control client/display feature */
- protected static final boolean DEBUG_RC = false;
- /** Debug volumes */
- protected static final boolean DEBUG_VOL = false;
-
- /** Used to alter media button redirection when the phone is ringing. */
- private boolean mIsRinging = false;
-
- private final PowerManager.WakeLock mMediaEventWakeLock;
- private final MediaEventHandler mEventHandler;
- private final Context mContext;
- private final ContentResolver mContentResolver;
- private final AudioService.VolumeController mVolumeController;
- private final AppOpsManager mAppOps;
- private final KeyguardManager mKeyguardManager;
- private final AudioService mAudioService;
- private final NotificationListenerObserver mNotifListenerObserver;
-
- protected MediaFocusControl(Looper looper, Context cntxt,
- AudioService.VolumeController volumeCtrl, AudioService as) {
- mEventHandler = new MediaEventHandler(looper);
- mContext = cntxt;
- mContentResolver = mContext.getContentResolver();
- mVolumeController = volumeCtrl;
- mAudioService = as;
-
- PowerManager pm = (PowerManager)mContext.getSystemService(Context.POWER_SERVICE);
- mMediaEventWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "handleMediaEvent");
- mMainRemote = new RemotePlaybackState(-1,
- AudioService.getMaxStreamVolume(AudioManager.STREAM_MUSIC),
- AudioService.getMaxStreamVolume(AudioManager.STREAM_MUSIC));
-
- // Register for phone state monitoring
- TelephonyManager tmgr = (TelephonyManager)
- mContext.getSystemService(Context.TELEPHONY_SERVICE);
- tmgr.listen(mPhoneStateListener, PhoneStateListener.LISTEN_CALL_STATE);
-
- mAppOps = (AppOpsManager)mContext.getSystemService(Context.APP_OPS_SERVICE);
- mKeyguardManager =
- (KeyguardManager) mContext.getSystemService(Context.KEYGUARD_SERVICE);
- mNotifListenerObserver = new NotificationListenerObserver();
-
- mHasRemotePlayback = false;
- mMainRemoteIsActive = false;
-
- PlayerRecord.setMediaFocusControl(this);
-
- postReevaluateRemote();
- }
-
- protected void dump(PrintWriter pw) {
- dumpFocusStack(pw);
- dumpRCStack(pw);
- dumpRCCStack(pw);
- dumpRCDList(pw);
- }
-
- //==========================================================================================
- // Management of RemoteControlDisplay registration permissions
- //==========================================================================================
- private final static Uri ENABLED_NOTIFICATION_LISTENERS_URI =
- Settings.Secure.getUriFor(Settings.Secure.ENABLED_NOTIFICATION_LISTENERS);
-
- private class NotificationListenerObserver extends ContentObserver {
-
- NotificationListenerObserver() {
- super(mEventHandler);
- mContentResolver.registerContentObserver(Settings.Secure.getUriFor(
- Settings.Secure.ENABLED_NOTIFICATION_LISTENERS), false, this);
- }
-
- @Override
- public void onChange(boolean selfChange, Uri uri) {
- if (!ENABLED_NOTIFICATION_LISTENERS_URI.equals(uri) || selfChange) {
- return;
- }
- if (DEBUG_RC) { Log.d(TAG, "NotificationListenerObserver.onChange()"); }
- postReevaluateRemoteControlDisplays();
- }
- }
-
- private final static int RCD_REG_FAILURE = 0;
- private final static int RCD_REG_SUCCESS_PERMISSION = 1;
- private final static int RCD_REG_SUCCESS_ENABLED_NOTIF = 2;
-
- /**
- * Checks a caller's authorization to register an IRemoteControlDisplay.
- * Authorization is granted if one of the following is true:
- * <ul>
- * <li>the caller has android.Manifest.permission.MEDIA_CONTENT_CONTROL permission</li>
- * <li>the caller's listener is one of the enabled notification listeners</li>
- * </ul>
- * @return RCD_REG_FAILURE if it's not safe to proceed with the IRemoteControlDisplay
- * registration.
- */
- private int checkRcdRegistrationAuthorization(ComponentName listenerComp) {
- // MEDIA_CONTENT_CONTROL permission check
- if (PackageManager.PERMISSION_GRANTED == mContext.checkCallingOrSelfPermission(
- android.Manifest.permission.MEDIA_CONTENT_CONTROL)) {
- if (DEBUG_RC) { Log.d(TAG, "ok to register Rcd: has MEDIA_CONTENT_CONTROL permission");}
- return RCD_REG_SUCCESS_PERMISSION;
- }
-
- // ENABLED_NOTIFICATION_LISTENERS settings check
- if (listenerComp != null) {
- // this call is coming from an app, can't use its identity to read secure settings
- final long ident = Binder.clearCallingIdentity();
- try {
- final int currentUser = ActivityManager.getCurrentUser();
- final String enabledNotifListeners = Settings.Secure.getStringForUser(
- mContext.getContentResolver(),
- Settings.Secure.ENABLED_NOTIFICATION_LISTENERS,
- currentUser);
- if (enabledNotifListeners != null) {
- final String[] components = enabledNotifListeners.split(":");
- for (int i=0; i<components.length; i++) {
- final ComponentName component =
- ComponentName.unflattenFromString(components[i]);
- if (component != null) {
- if (listenerComp.equals(component)) {
- if (DEBUG_RC) { Log.d(TAG, "ok to register RCC: " + component +
- " is authorized notification listener"); }
- return RCD_REG_SUCCESS_ENABLED_NOTIF;
- }
- }
- }
- }
- if (DEBUG_RC) { Log.d(TAG, "not ok to register RCD, " + listenerComp +
- " is not in list of ENABLED_NOTIFICATION_LISTENERS"); }
- } finally {
- Binder.restoreCallingIdentity(ident);
- }
- }
-
- return RCD_REG_FAILURE;
- }
-
- protected boolean registerRemoteController(IRemoteControlDisplay rcd, int w, int h,
- ComponentName listenerComp) {
- int reg = checkRcdRegistrationAuthorization(listenerComp);
- if (reg != RCD_REG_FAILURE) {
- registerRemoteControlDisplay_int(rcd, w, h, listenerComp);
- return true;
- } else {
- Slog.w(TAG, "Access denied to process: " + Binder.getCallingPid() +
- ", must have permission " + android.Manifest.permission.MEDIA_CONTENT_CONTROL +
- " or be an enabled NotificationListenerService for registerRemoteController");
- return false;
- }
- }
-
- protected boolean registerRemoteControlDisplay(IRemoteControlDisplay rcd, int w, int h) {
- int reg = checkRcdRegistrationAuthorization(null);
- if (reg != RCD_REG_FAILURE) {
- registerRemoteControlDisplay_int(rcd, w, h, null);
- return true;
- } else {
- Slog.w(TAG, "Access denied to process: " + Binder.getCallingPid() +
- ", must have permission " + android.Manifest.permission.MEDIA_CONTENT_CONTROL +
- " to register IRemoteControlDisplay");
- return false;
- }
- }
-
- private void postReevaluateRemoteControlDisplays() {
- sendMsg(mEventHandler, MSG_REEVALUATE_RCD, SENDMSG_QUEUE, 0, 0, null, 0);
- }
-
- private void onReevaluateRemoteControlDisplays() {
- if (DEBUG_RC) { Log.d(TAG, "onReevaluateRemoteControlDisplays()"); }
- // read which components are enabled notification listeners
- final int currentUser = ActivityManager.getCurrentUser();
- final String enabledNotifListeners = Settings.Secure.getStringForUser(
- mContext.getContentResolver(),
- Settings.Secure.ENABLED_NOTIFICATION_LISTENERS,
- currentUser);
- if (DEBUG_RC) { Log.d(TAG, " > enabled list: " + enabledNotifListeners); }
- synchronized(mAudioFocusLock) {
- synchronized(mPRStack) {
- // check whether the "enable" status of each RCD with a notification listener
- // has changed
- final String[] enabledComponents;
- if (enabledNotifListeners == null) {
- enabledComponents = null;
- } else {
- enabledComponents = enabledNotifListeners.split(":");
- }
- final Iterator<DisplayInfoForServer> displayIterator = mRcDisplays.iterator();
- while (displayIterator.hasNext()) {
- final DisplayInfoForServer di =
- displayIterator.next();
- if (di.mClientNotifListComp != null) {
- boolean wasEnabled = di.mEnabled;
- di.mEnabled = isComponentInStringArray(di.mClientNotifListComp,
- enabledComponents);
- if (wasEnabled != di.mEnabled){
- try {
- // tell the RCD whether it's enabled
- di.mRcDisplay.setEnabled(di.mEnabled);
- // tell the RCCs about the change for this RCD
- enableRemoteControlDisplayForClient_syncRcStack(
- di.mRcDisplay, di.mEnabled);
- // when enabling, refresh the information on the display
- if (di.mEnabled) {
- sendMsg(mEventHandler, MSG_RCDISPLAY_INIT_INFO, SENDMSG_QUEUE,
- di.mArtworkExpectedWidth /*arg1*/,
- di.mArtworkExpectedHeight/*arg2*/,
- di.mRcDisplay /*obj*/, 0/*delay*/);
- }
- } catch (RemoteException e) {
- Log.e(TAG, "Error en/disabling RCD: ", e);
- }
- }
- }
- }
- }
- }
- }
-
- /**
- * @param comp a non-null ComponentName
- * @param enabledArray may be null
- * @return
- */
- private boolean isComponentInStringArray(ComponentName comp, String[] enabledArray) {
- if (enabledArray == null || enabledArray.length == 0) {
- if (DEBUG_RC) { Log.d(TAG, " > " + comp + " is NOT enabled"); }
- return false;
- }
- final String compString = comp.flattenToString();
- for (int i=0; i<enabledArray.length; i++) {
- if (compString.equals(enabledArray[i])) {
- if (DEBUG_RC) { Log.d(TAG, " > " + compString + " is enabled"); }
- return true;
- }
- }
- if (DEBUG_RC) { Log.d(TAG, " > " + compString + " is NOT enabled"); }
- return false;
- }
-
- //==========================================================================================
- // Internal event handling
- //==========================================================================================
-
- // event handler messages
- private static final int MSG_RCDISPLAY_CLEAR = 1;
- private static final int MSG_RCDISPLAY_UPDATE = 2;
- private static final int MSG_REEVALUATE_REMOTE = 3;
- private static final int MSG_RCC_NEW_PLAYBACK_INFO = 4;
- private static final int MSG_RCC_NEW_VOLUME_OBS = 5;
- private static final int MSG_RCC_NEW_PLAYBACK_STATE = 6;
- private static final int MSG_RCC_SEEK_REQUEST = 7;
- private static final int MSG_RCC_UPDATE_METADATA = 8;
- private static final int MSG_RCDISPLAY_INIT_INFO = 9;
- private static final int MSG_REEVALUATE_RCD = 10;
- private static final int MSG_UNREGISTER_MEDIABUTTONINTENT = 11;
-
- // 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;
-
- 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;
- }
-
- handler.sendMessageDelayed(handler.obtainMessage(msg, arg1, arg2, obj), delay);
- }
-
- private class MediaEventHandler extends Handler {
- MediaEventHandler(Looper looper) {
- super(looper);
- }
-
- @Override
- public void handleMessage(Message msg) {
- switch(msg.what) {
- case MSG_RCDISPLAY_CLEAR:
- onRcDisplayClear();
- break;
-
- case MSG_RCDISPLAY_UPDATE:
- // msg.obj is guaranteed to be non null
- onRcDisplayUpdate( (PlayerRecord) msg.obj, msg.arg1);
- break;
-
- case MSG_REEVALUATE_REMOTE:
- onReevaluateRemote();
- break;
-
- case MSG_RCC_NEW_VOLUME_OBS:
- onRegisterVolumeObserverForRcc(msg.arg1 /* rccId */,
- (IRemoteVolumeObserver)msg.obj /* rvo */);
- break;
-
- case MSG_RCDISPLAY_INIT_INFO:
- // msg.obj is guaranteed to be non null
- onRcDisplayInitInfo((IRemoteControlDisplay)msg.obj /*newRcd*/,
- msg.arg1/*w*/, msg.arg2/*h*/);
- break;
-
- case MSG_REEVALUATE_RCD:
- onReevaluateRemoteControlDisplays();
- break;
-
- case MSG_UNREGISTER_MEDIABUTTONINTENT:
- unregisterMediaButtonIntent( (PendingIntent) msg.obj );
- break;
- }
- }
- }
-
-
- //==========================================================================================
- // AudioFocus
- //==========================================================================================
-
- /**
- * Constant to identify a focus stack entry that is used to hold the focus while the phone
- * is ringing or during a call. Used by com.android.internal.telephony.CallManager when
- * entering and exiting calls.
- */
- protected final static String IN_VOICE_COMM_FOCUS_ID = "AudioFocus_For_Phone_Ring_And_Calls";
-
- private final static Object mAudioFocusLock = new Object();
-
- private final static Object mRingingLock = new Object();
-
- private PhoneStateListener mPhoneStateListener = new PhoneStateListener() {
- @Override
- public void onCallStateChanged(int state, String incomingNumber) {
- if (state == TelephonyManager.CALL_STATE_RINGING) {
- //Log.v(TAG, " CALL_STATE_RINGING");
- synchronized(mRingingLock) {
- mIsRinging = true;
- }
- } else if ((state == TelephonyManager.CALL_STATE_OFFHOOK)
- || (state == TelephonyManager.CALL_STATE_IDLE)) {
- synchronized(mRingingLock) {
- mIsRinging = false;
- }
- }
- }
- };
-
- /**
- * Discard the current audio focus owner.
- * Notify top of audio focus stack that it lost focus (regardless of possibility to reassign
- * focus), remove it from the stack, and clear the remote control display.
- */
- protected void discardAudioFocusOwner() {
- synchronized(mAudioFocusLock) {
- if (!mFocusStack.empty()) {
- // notify the current focus owner it lost focus after removing it from stack
- final FocusRequester exFocusOwner = mFocusStack.pop();
- exFocusOwner.handleFocusLoss(AudioManager.AUDIOFOCUS_LOSS);
- exFocusOwner.release();
- }
- }
- }
-
- /**
- * Called synchronized on mAudioFocusLock
- */
- private void notifyTopOfAudioFocusStack() {
- // notify the top of the stack it gained focus
- if (!mFocusStack.empty()) {
- if (canReassignAudioFocus()) {
- mFocusStack.peek().handleFocusGain(AudioManager.AUDIOFOCUS_GAIN);
- }
- }
- }
-
- /**
- * Focus is requested, propagate the associated loss throughout the stack.
- * @param focusGain the new focus gain that will later be added at the top of the stack
- */
- private void propagateFocusLossFromGain_syncAf(int focusGain) {
- // going through the audio focus stack to signal new focus, traversing order doesn't
- // matter as all entries respond to the same external focus gain
- Iterator<FocusRequester> stackIterator = mFocusStack.iterator();
- while(stackIterator.hasNext()) {
- stackIterator.next().handleExternalFocusGain(focusGain);
- }
- }
-
- private final Stack<FocusRequester> mFocusStack = new Stack<FocusRequester>();
-
- /**
- * Helper function:
- * Display in the log the current entries in the audio focus stack
- */
- private void dumpFocusStack(PrintWriter pw) {
- pw.println("\nAudio Focus stack entries (last is top of stack):");
- synchronized(mAudioFocusLock) {
- Iterator<FocusRequester> stackIterator = mFocusStack.iterator();
- while(stackIterator.hasNext()) {
- stackIterator.next().dump(pw);
- }
- }
- pw.println("\n Notify on duck: " + mNotifyFocusOwnerOnDuck +"\n");
- }
-
- /**
- * Helper function:
- * Called synchronized on mAudioFocusLock
- * Remove a focus listener from the focus stack.
- * @param clientToRemove the focus listener
- * @param signal if true and the listener was at the top of the focus stack, i.e. it was holding
- * focus, notify the next item in the stack it gained focus.
- */
- private void removeFocusStackEntry(String clientToRemove, boolean signal,
- boolean notifyFocusFollowers) {
- // is the current top of the focus stack abandoning focus? (because of request, not death)
- if (!mFocusStack.empty() && mFocusStack.peek().hasSameClient(clientToRemove))
- {
- //Log.i(TAG, " removeFocusStackEntry() removing top of stack");
- FocusRequester fr = mFocusStack.pop();
- fr.release();
- if (notifyFocusFollowers) {
- final AudioFocusInfo afi = fr.toAudioFocusInfo();
- afi.clearLossReceived();
- notifyExtPolicyFocusLoss_syncAf(afi, false);
- }
- if (signal) {
- // notify the new top of the stack it gained focus
- notifyTopOfAudioFocusStack();
- }
- } else {
- // focus is abandoned by a client that's not at the top of the stack,
- // no need to update focus.
- // (using an iterator on the stack so we can safely remove an entry after having
- // evaluated it, traversal order doesn't matter here)
- Iterator<FocusRequester> stackIterator = mFocusStack.iterator();
- while(stackIterator.hasNext()) {
- FocusRequester fr = stackIterator.next();
- if(fr.hasSameClient(clientToRemove)) {
- Log.i(TAG, "AudioFocus removeFocusStackEntry(): removing entry for "
- + clientToRemove);
- stackIterator.remove();
- fr.release();
- }
- }
- }
- }
-
- /**
- * Helper function:
- * Called synchronized on mAudioFocusLock
- * Remove focus listeners from the focus stack for a particular client when it has died.
- */
- private void removeFocusStackEntryForClient(IBinder cb) {
- // is the owner of the audio focus part of the client to remove?
- boolean isTopOfStackForClientToRemove = !mFocusStack.isEmpty() &&
- mFocusStack.peek().hasSameBinder(cb);
- // (using an iterator on the stack so we can safely remove an entry after having
- // evaluated it, traversal order doesn't matter here)
- Iterator<FocusRequester> stackIterator = mFocusStack.iterator();
- while(stackIterator.hasNext()) {
- FocusRequester fr = stackIterator.next();
- if(fr.hasSameBinder(cb)) {
- Log.i(TAG, "AudioFocus removeFocusStackEntry(): removing entry for " + cb);
- stackIterator.remove();
- // the client just died, no need to unlink to its death
- }
- }
- if (isTopOfStackForClientToRemove) {
- // we removed an entry at the top of the stack:
- // notify the new top of the stack it gained focus.
- notifyTopOfAudioFocusStack();
- }
- }
-
- /**
- * Helper function:
- * Returns true if the system is in a state where the focus can be reevaluated, false otherwise.
- * The implementation guarantees that a state where focus cannot be immediately reassigned
- * implies that an "locked" focus owner is at the top of the focus stack.
- * Modifications to the implementation that break this assumption will cause focus requests to
- * misbehave when honoring the AudioManager.AUDIOFOCUS_FLAG_DELAY_OK flag.
- */
- private boolean canReassignAudioFocus() {
- // focus requests are rejected during a phone call or when the phone is ringing
- // this is equivalent to IN_VOICE_COMM_FOCUS_ID having the focus
- if (!mFocusStack.isEmpty() && isLockedFocusOwner(mFocusStack.peek())) {
- return false;
- }
- return true;
- }
-
- private boolean isLockedFocusOwner(FocusRequester fr) {
- return (fr.hasSameClient(IN_VOICE_COMM_FOCUS_ID) || fr.isLockedFocusOwner());
- }
-
- /**
- * Helper function
- * Pre-conditions: focus stack is not empty, there is one or more locked focus owner
- * at the top of the focus stack
- * Push the focus requester onto the audio focus stack at the first position immediately
- * following the locked focus owners.
- * @return {@link AudioManager#AUDIOFOCUS_REQUEST_GRANTED} or
- * {@link AudioManager#AUDIOFOCUS_REQUEST_DELAYED}
- */
- private int pushBelowLockedFocusOwners(FocusRequester nfr) {
- int lastLockedFocusOwnerIndex = mFocusStack.size();
- for (int index = mFocusStack.size()-1; index >= 0; index--) {
- if (isLockedFocusOwner(mFocusStack.elementAt(index))) {
- lastLockedFocusOwnerIndex = index;
- }
- }
- if (lastLockedFocusOwnerIndex == mFocusStack.size()) {
- // this should not happen, but handle it and log an error
- Log.e(TAG, "No exclusive focus owner found in propagateFocusLossFromGain_syncAf()",
- new Exception());
- // no exclusive owner, push at top of stack, focus is granted, propagate change
- propagateFocusLossFromGain_syncAf(nfr.getGainRequest());
- mFocusStack.push(nfr);
- return AudioManager.AUDIOFOCUS_REQUEST_GRANTED;
- } else {
- mFocusStack.insertElementAt(nfr, lastLockedFocusOwnerIndex);
- return AudioManager.AUDIOFOCUS_REQUEST_DELAYED;
- }
- }
-
- /**
- * Inner class to monitor audio focus client deaths, and remove them from the audio focus
- * stack if necessary.
- */
- protected class AudioFocusDeathHandler implements IBinder.DeathRecipient {
- private IBinder mCb; // To be notified of client's death
-
- AudioFocusDeathHandler(IBinder cb) {
- mCb = cb;
- }
-
- public void binderDied() {
- synchronized(mAudioFocusLock) {
- Log.w(TAG, " AudioFocus audio focus client died");
- removeFocusStackEntryForClient(mCb);
- }
- }
-
- public IBinder getBinder() {
- return mCb;
- }
- }
-
- /**
- * Indicates whether to notify an audio focus owner when it loses focus
- * with {@link AudioManager#AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK} if it will only duck.
- * This variable being false indicates an AudioPolicy has been registered and has signaled
- * it will handle audio ducking.
- */
- private boolean mNotifyFocusOwnerOnDuck = true;
-
- protected void setDuckingInExtPolicyAvailable(boolean available) {
- mNotifyFocusOwnerOnDuck = !available;
- }
-
- boolean mustNotifyFocusOwnerOnDuck() { return mNotifyFocusOwnerOnDuck; }
-
- private ArrayList<IAudioPolicyCallback> mFocusFollowers = new ArrayList<IAudioPolicyCallback>();
-
- void addFocusFollower(IAudioPolicyCallback ff) {
- if (ff == null) {
- return;
- }
- synchronized(mAudioFocusLock) {
- boolean found = false;
- for (IAudioPolicyCallback pcb : mFocusFollowers) {
- if (pcb.asBinder().equals(ff.asBinder())) {
- found = true;
- break;
- }
- }
- if (found) {
- return;
- } else {
- mFocusFollowers.add(ff);
- }
- }
- }
-
- void removeFocusFollower(IAudioPolicyCallback ff) {
- if (ff == null) {
- return;
- }
- synchronized(mAudioFocusLock) {
- for (IAudioPolicyCallback pcb : mFocusFollowers) {
- if (pcb.asBinder().equals(ff.asBinder())) {
- mFocusFollowers.remove(pcb);
- break;
- }
- }
- }
- }
-
- /**
- * Called synchronized on mAudioFocusLock
- */
- void notifyExtPolicyFocusGrant_syncAf(AudioFocusInfo afi, int requestResult) {
- for (IAudioPolicyCallback pcb : mFocusFollowers) {
- try {
- // oneway
- pcb.notifyAudioFocusGrant(afi, requestResult);
- } catch (RemoteException e) {
- Log.e(TAG, "Can't call newAudioFocusLoser() on IAudioPolicyCallback "
- + pcb.asBinder(), e);
- }
- }
- }
-
- /**
- * Called synchronized on mAudioFocusLock
- */
- void notifyExtPolicyFocusLoss_syncAf(AudioFocusInfo afi, boolean wasDispatched) {
- for (IAudioPolicyCallback pcb : mFocusFollowers) {
- try {
- // oneway
- pcb.notifyAudioFocusLoss(afi, wasDispatched);
- } catch (RemoteException e) {
- Log.e(TAG, "Can't call newAudioFocusLoser() on IAudioPolicyCallback "
- + pcb.asBinder(), e);
- }
- }
- }
-
- protected int getCurrentAudioFocus() {
- synchronized(mAudioFocusLock) {
- if (mFocusStack.empty()) {
- return AudioManager.AUDIOFOCUS_NONE;
- } else {
- return mFocusStack.peek().getGainRequest();
- }
- }
- }
-
- /** @see AudioManager#requestAudioFocus(AudioManager.OnAudioFocusChangeListener, int, int, int) */
- protected int requestAudioFocus(AudioAttributes aa, int focusChangeHint, IBinder cb,
- IAudioFocusDispatcher fd, String clientId, String callingPackageName, int flags) {
- Log.i(TAG, " AudioFocus requestAudioFocus() from " + clientId + " req=" + focusChangeHint +
- "flags=0x" + Integer.toHexString(flags));
- // we need a valid binder callback for clients
- if (!cb.pingBinder()) {
- Log.e(TAG, " AudioFocus DOA client for requestAudioFocus(), aborting.");
- return AudioManager.AUDIOFOCUS_REQUEST_FAILED;
- }
-
- if (mAppOps.noteOp(AppOpsManager.OP_TAKE_AUDIO_FOCUS, Binder.getCallingUid(),
- callingPackageName) != AppOpsManager.MODE_ALLOWED) {
- return AudioManager.AUDIOFOCUS_REQUEST_FAILED;
- }
-
- synchronized(mAudioFocusLock) {
- boolean focusGrantDelayed = false;
- if (!canReassignAudioFocus()) {
- if ((flags & AudioManager.AUDIOFOCUS_FLAG_DELAY_OK) == 0) {
- return AudioManager.AUDIOFOCUS_REQUEST_FAILED;
- } else {
- // request has AUDIOFOCUS_FLAG_DELAY_OK: focus can't be
- // granted right now, so the requester will be inserted in the focus stack
- // to receive focus later
- focusGrantDelayed = true;
- }
- }
-
- // handle the potential premature death of the new holder of the focus
- // (premature death == death before abandoning focus)
- // Register for client death notification
- AudioFocusDeathHandler afdh = new AudioFocusDeathHandler(cb);
- try {
- cb.linkToDeath(afdh, 0);
- } catch (RemoteException e) {
- // client has already died!
- Log.w(TAG, "AudioFocus requestAudioFocus() could not link to "+cb+" binder death");
- return AudioManager.AUDIOFOCUS_REQUEST_FAILED;
- }
-
- if (!mFocusStack.empty() && mFocusStack.peek().hasSameClient(clientId)) {
- // if focus is already owned by this client and the reason for acquiring the focus
- // hasn't changed, don't do anything
- final FocusRequester fr = mFocusStack.peek();
- if (fr.getGainRequest() == focusChangeHint && fr.getGrantFlags() == flags) {
- // unlink death handler so it can be gc'ed.
- // linkToDeath() creates a JNI global reference preventing collection.
- cb.unlinkToDeath(afdh, 0);
- notifyExtPolicyFocusGrant_syncAf(fr.toAudioFocusInfo(),
- AudioManager.AUDIOFOCUS_REQUEST_GRANTED);
- return AudioManager.AUDIOFOCUS_REQUEST_GRANTED;
- }
- // the reason for the audio focus request has changed: remove the current top of
- // stack and respond as if we had a new focus owner
- if (!focusGrantDelayed) {
- mFocusStack.pop();
- // the entry that was "popped" is the same that was "peeked" above
- fr.release();
- }
- }
-
- // focus requester might already be somewhere below in the stack, remove it
- removeFocusStackEntry(clientId, false /* signal */, false /*notifyFocusFollowers*/);
-
- final FocusRequester nfr = new FocusRequester(aa, focusChangeHint, flags, fd, cb,
- clientId, afdh, callingPackageName, Binder.getCallingUid(), this);
- if (focusGrantDelayed) {
- // focusGrantDelayed being true implies we can't reassign focus right now
- // which implies the focus stack is not empty.
- final int requestResult = pushBelowLockedFocusOwners(nfr);
- if (requestResult != AudioManager.AUDIOFOCUS_REQUEST_FAILED) {
- notifyExtPolicyFocusGrant_syncAf(nfr.toAudioFocusInfo(), requestResult);
- }
- return requestResult;
- } else {
- // propagate the focus change through the stack
- if (!mFocusStack.empty()) {
- propagateFocusLossFromGain_syncAf(focusChangeHint);
- }
-
- // push focus requester at the top of the audio focus stack
- mFocusStack.push(nfr);
- }
- notifyExtPolicyFocusGrant_syncAf(nfr.toAudioFocusInfo(),
- AudioManager.AUDIOFOCUS_REQUEST_GRANTED);
-
- }//synchronized(mAudioFocusLock)
-
- return AudioManager.AUDIOFOCUS_REQUEST_GRANTED;
- }
-
- /**
- * @see AudioManager#abandonAudioFocus(AudioManager.OnAudioFocusChangeListener, AudioAttributes)
- * */
- protected int abandonAudioFocus(IAudioFocusDispatcher fl, String clientId, AudioAttributes aa) {
- // AudioAttributes are currently ignored, to be used for zones
- Log.i(TAG, " AudioFocus abandonAudioFocus() from " + clientId);
- try {
- // this will take care of notifying the new focus owner if needed
- synchronized(mAudioFocusLock) {
- removeFocusStackEntry(clientId, true /*signal*/, true /*notifyFocusFollowers*/);
- }
- } catch (java.util.ConcurrentModificationException cme) {
- // Catching this exception here is temporary. It is here just to prevent
- // a crash seen when the "Silent" notification is played. This is believed to be fixed
- // but this try catch block is left just to be safe.
- Log.e(TAG, "FATAL EXCEPTION AudioFocus abandonAudioFocus() caused " + cme);
- cme.printStackTrace();
- }
-
- return AudioManager.AUDIOFOCUS_REQUEST_GRANTED;
- }
-
-
- protected void unregisterAudioFocusClient(String clientId) {
- synchronized(mAudioFocusLock) {
- removeFocusStackEntry(clientId, false, true /*notifyFocusFollowers*/);
- }
- }
-
-
- //==========================================================================================
- // RemoteControl
- //==========================================================================================
- /**
- * No-op if the key code for keyEvent is not a valid media key
- * (see {@link #isValidMediaKeyEvent(KeyEvent)})
- * @param keyEvent the key event to send
- */
- protected void dispatchMediaKeyEvent(KeyEvent keyEvent) {
- filterMediaKeyEvent(keyEvent, false /*needWakeLock*/);
- }
-
- /**
- * No-op if the key code for keyEvent is not a valid media key
- * (see {@link #isValidMediaKeyEvent(KeyEvent)})
- * @param keyEvent the key event to send
- */
- protected void dispatchMediaKeyEventUnderWakelock(KeyEvent keyEvent) {
- filterMediaKeyEvent(keyEvent, true /*needWakeLock*/);
- }
-
- private void filterMediaKeyEvent(KeyEvent keyEvent, boolean needWakeLock) {
- // sanity check on the incoming key event
- if (!isValidMediaKeyEvent(keyEvent)) {
- Log.e(TAG, "not dispatching invalid media key event " + keyEvent);
- return;
- }
- // event filtering for telephony
- synchronized(mRingingLock) {
- synchronized(mPRStack) {
- if ((mMediaReceiverForCalls != null) &&
- (mIsRinging || (mAudioService.getMode() == AudioSystem.MODE_IN_CALL))) {
- dispatchMediaKeyEventForCalls(keyEvent, needWakeLock);
- return;
- }
- }
- }
- // event filtering based on voice-based interactions
- if (isValidVoiceInputKeyCode(keyEvent.getKeyCode())) {
- filterVoiceInputKeyEvent(keyEvent, needWakeLock);
- } else {
- dispatchMediaKeyEvent(keyEvent, needWakeLock);
- }
- }
-
- /**
- * Handles the dispatching of the media button events to the telephony package.
- * Precondition: mMediaReceiverForCalls != null
- * @param keyEvent a non-null KeyEvent whose key code is one of the supported media buttons
- * @param needWakeLock true if a PARTIAL_WAKE_LOCK needs to be held while this key event
- * is dispatched.
- */
- private void dispatchMediaKeyEventForCalls(KeyEvent keyEvent, boolean needWakeLock) {
- Intent keyIntent = new Intent(Intent.ACTION_MEDIA_BUTTON, null);
- keyIntent.putExtra(Intent.EXTRA_KEY_EVENT, keyEvent);
- keyIntent.setPackage(mMediaReceiverForCalls.getPackageName());
- if (needWakeLock) {
- mMediaEventWakeLock.acquire();
- keyIntent.putExtra(EXTRA_WAKELOCK_ACQUIRED, WAKELOCK_RELEASE_ON_FINISHED);
- }
- final long ident = Binder.clearCallingIdentity();
- try {
- mContext.sendOrderedBroadcastAsUser(keyIntent, UserHandle.ALL,
- null, mKeyEventDone, mEventHandler, Activity.RESULT_OK, null, null);
- } finally {
- Binder.restoreCallingIdentity(ident);
- }
- }
-
- /**
- * Handles the dispatching of the media button events to one of the registered listeners,
- * or if there was none, broadcast an ACTION_MEDIA_BUTTON intent to the rest of the system.
- * @param keyEvent a non-null KeyEvent whose key code is one of the supported media buttons
- * @param needWakeLock true if a PARTIAL_WAKE_LOCK needs to be held while this key event
- * is dispatched.
- */
- private void dispatchMediaKeyEvent(KeyEvent keyEvent, boolean needWakeLock) {
- if (needWakeLock) {
- mMediaEventWakeLock.acquire();
- }
- Intent keyIntent = new Intent(Intent.ACTION_MEDIA_BUTTON, null);
- keyIntent.putExtra(Intent.EXTRA_KEY_EVENT, keyEvent);
- synchronized(mPRStack) {
- if (!mPRStack.empty()) {
- // send the intent that was registered by the client
- try {
- mPRStack.peek().getMediaButtonIntent().send(mContext,
- needWakeLock ? WAKELOCK_RELEASE_ON_FINISHED : 0 /*code*/,
- keyIntent, this, mEventHandler);
- } catch (CanceledException e) {
- Log.e(TAG, "Error sending pending intent " + mPRStack.peek());
- e.printStackTrace();
- }
- } else {
- // legacy behavior when nobody registered their media button event receiver
- // through AudioManager
- if (needWakeLock) {
- keyIntent.putExtra(EXTRA_WAKELOCK_ACQUIRED, WAKELOCK_RELEASE_ON_FINISHED);
- }
- final long ident = Binder.clearCallingIdentity();
- try {
- mContext.sendOrderedBroadcastAsUser(keyIntent, UserHandle.ALL,
- null, mKeyEventDone,
- mEventHandler, Activity.RESULT_OK, null, null);
- } finally {
- Binder.restoreCallingIdentity(ident);
- }
- }
- }
- }
-
- /**
- * The different actions performed in response to a voice button key event.
- */
- private final static int VOICEBUTTON_ACTION_DISCARD_CURRENT_KEY_PRESS = 1;
- private final static int VOICEBUTTON_ACTION_START_VOICE_INPUT = 2;
- private final static int VOICEBUTTON_ACTION_SIMULATE_KEY_PRESS = 3;
-
- private final Object mVoiceEventLock = new Object();
- private boolean mVoiceButtonDown;
- private boolean mVoiceButtonHandled;
-
- /**
- * Filter key events that may be used for voice-based interactions
- * @param keyEvent a non-null KeyEvent whose key code is that of one of the supported
- * media buttons that can be used to trigger voice-based interactions.
- * @param needWakeLock true if a PARTIAL_WAKE_LOCK needs to be held while this key event
- * is dispatched.
- */
- private void filterVoiceInputKeyEvent(KeyEvent keyEvent, boolean needWakeLock) {
- if (DEBUG_RC) {
- Log.v(TAG, "voice input key event: " + keyEvent + ", needWakeLock=" + needWakeLock);
- }
-
- int voiceButtonAction = VOICEBUTTON_ACTION_DISCARD_CURRENT_KEY_PRESS;
- int keyAction = keyEvent.getAction();
- synchronized (mVoiceEventLock) {
- if (keyAction == KeyEvent.ACTION_DOWN) {
- if (keyEvent.getRepeatCount() == 0) {
- // initial down
- mVoiceButtonDown = true;
- mVoiceButtonHandled = false;
- } else if (mVoiceButtonDown && !mVoiceButtonHandled
- && (keyEvent.getFlags() & KeyEvent.FLAG_LONG_PRESS) != 0) {
- // long-press, start voice-based interactions
- mVoiceButtonHandled = true;
- voiceButtonAction = VOICEBUTTON_ACTION_START_VOICE_INPUT;
- }
- } else if (keyAction == KeyEvent.ACTION_UP) {
- if (mVoiceButtonDown) {
- // voice button up
- mVoiceButtonDown = false;
- if (!mVoiceButtonHandled && !keyEvent.isCanceled()) {
- voiceButtonAction = VOICEBUTTON_ACTION_SIMULATE_KEY_PRESS;
- }
- }
- }
- }//synchronized (mVoiceEventLock)
-
- // take action after media button event filtering for voice-based interactions
- switch (voiceButtonAction) {
- case VOICEBUTTON_ACTION_DISCARD_CURRENT_KEY_PRESS:
- if (DEBUG_RC) Log.v(TAG, " ignore key event");
- break;
- case VOICEBUTTON_ACTION_START_VOICE_INPUT:
- if (DEBUG_RC) Log.v(TAG, " start voice-based interactions");
- // then start the voice-based interactions
- startVoiceBasedInteractions(needWakeLock);
- break;
- case VOICEBUTTON_ACTION_SIMULATE_KEY_PRESS:
- if (DEBUG_RC) Log.v(TAG, " send simulated key event, wakelock=" + needWakeLock);
- sendSimulatedMediaButtonEvent(keyEvent, needWakeLock);
- break;
- }
- }
-
- private void sendSimulatedMediaButtonEvent(KeyEvent originalKeyEvent, boolean needWakeLock) {
- // send DOWN event
- KeyEvent keyEvent = KeyEvent.changeAction(originalKeyEvent, KeyEvent.ACTION_DOWN);
- dispatchMediaKeyEvent(keyEvent, needWakeLock);
- // send UP event
- keyEvent = KeyEvent.changeAction(originalKeyEvent, KeyEvent.ACTION_UP);
- dispatchMediaKeyEvent(keyEvent, needWakeLock);
-
- }
-
- private static boolean isValidMediaKeyEvent(KeyEvent keyEvent) {
- if (keyEvent == null) {
- return false;
- }
- return KeyEvent.isMediaKey(keyEvent.getKeyCode());
- }
-
- /**
- * Checks whether the given key code is one that can trigger the launch of voice-based
- * interactions.
- * @param keyCode the key code associated with the key event
- * @return true if the key is one of the supported voice-based interaction triggers
- */
- private static boolean isValidVoiceInputKeyCode(int keyCode) {
- if (keyCode == KeyEvent.KEYCODE_HEADSETHOOK) {
- return true;
- } else {
- return false;
- }
- }
-
- /**
- * Tell the system to start voice-based interactions / voice commands
- */
- private void startVoiceBasedInteractions(boolean needWakeLock) {
- Intent voiceIntent = null;
- // select which type of search to launch:
- // - screen on and device unlocked: action is ACTION_WEB_SEARCH
- // - device locked or screen off: action is ACTION_VOICE_SEARCH_HANDS_FREE
- // with EXTRA_SECURE set to true if the device is securely locked
- PowerManager pm = (PowerManager)mContext.getSystemService(Context.POWER_SERVICE);
- boolean isLocked = mKeyguardManager != null && mKeyguardManager.isKeyguardLocked();
- if (!isLocked && pm.isScreenOn()) {
- voiceIntent = new Intent(android.speech.RecognizerIntent.ACTION_WEB_SEARCH);
- Log.i(TAG, "voice-based interactions: about to use ACTION_WEB_SEARCH");
- } else {
- voiceIntent = new Intent(RecognizerIntent.ACTION_VOICE_SEARCH_HANDS_FREE);
- voiceIntent.putExtra(RecognizerIntent.EXTRA_SECURE,
- isLocked && mKeyguardManager.isKeyguardSecure());
- Log.i(TAG, "voice-based interactions: about to use ACTION_VOICE_SEARCH_HANDS_FREE");
- }
- // start the search activity
- if (needWakeLock) {
- mMediaEventWakeLock.acquire();
- }
- final long identity = Binder.clearCallingIdentity();
- try {
- if (voiceIntent != null) {
- voiceIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
- | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
- mContext.startActivityAsUser(voiceIntent, UserHandle.CURRENT);
- }
- } catch (ActivityNotFoundException e) {
- Log.w(TAG, "No activity for search: " + e);
- } finally {
- Binder.restoreCallingIdentity(identity);
- if (needWakeLock) {
- mMediaEventWakeLock.release();
- }
- }
- }
-
- private static final int WAKELOCK_RELEASE_ON_FINISHED = 1980; //magic number
-
- // only set when wakelock was acquired, no need to check value when received
- private static final String EXTRA_WAKELOCK_ACQUIRED =
- "android.media.AudioService.WAKELOCK_ACQUIRED";
-
- public void onSendFinished(PendingIntent pendingIntent, Intent intent,
- int resultCode, String resultData, Bundle resultExtras) {
- if (resultCode == WAKELOCK_RELEASE_ON_FINISHED) {
- mMediaEventWakeLock.release();
- }
- }
-
- BroadcastReceiver mKeyEventDone = new BroadcastReceiver() {
- public void onReceive(Context context, Intent intent) {
- if (intent == null) {
- return;
- }
- Bundle extras = intent.getExtras();
- if (extras == null) {
- return;
- }
- if (extras.containsKey(EXTRA_WAKELOCK_ACQUIRED)) {
- mMediaEventWakeLock.release();
- }
- }
- };
-
- /**
- * Synchronization on mCurrentRcLock always inside a block synchronized on mPRStack
- */
- private final Object mCurrentRcLock = new Object();
- /**
- * The one remote control client which will receive a request for display information.
- * This object may be null.
- * Access protected by mCurrentRcLock.
- */
- private IRemoteControlClient mCurrentRcClient = null;
- /**
- * The PendingIntent associated with mCurrentRcClient. Its value is irrelevant
- * if mCurrentRcClient is null
- */
- private PendingIntent mCurrentRcClientIntent = null;
-
- private final static int RC_INFO_NONE = 0;
- private final static int RC_INFO_ALL =
- RemoteControlClient.FLAG_INFORMATION_REQUEST_ALBUM_ART |
- RemoteControlClient.FLAG_INFORMATION_REQUEST_KEY_MEDIA |
- RemoteControlClient.FLAG_INFORMATION_REQUEST_METADATA |
- RemoteControlClient.FLAG_INFORMATION_REQUEST_PLAYSTATE;
-
- /**
- * A monotonically increasing generation counter for mCurrentRcClient.
- * Only accessed with a lock on mCurrentRcLock.
- * No value wrap-around issues as we only act on equal values.
- */
- private int mCurrentRcClientGen = 0;
-
-
- /**
- * Internal cache for the playback information of the RemoteControlClient whose volume gets to
- * be controlled by the volume keys ("main"), so we don't have to iterate over the RC stack
- * every time we need this info.
- */
- private RemotePlaybackState mMainRemote;
- /**
- * Indicates whether the "main" RemoteControlClient is considered active.
- * Use synchronized on mMainRemote.
- */
- private boolean mMainRemoteIsActive;
- /**
- * Indicates whether there is remote playback going on. True even if there is no "active"
- * remote playback (mMainRemoteIsActive is false), but a RemoteControlClient has declared it
- * handles remote playback.
- * Use synchronized on mMainRemote.
- */
- private boolean mHasRemotePlayback;
-
- /**
- * The stack of remote control event receivers.
- * All read and write operations on mPRStack are synchronized.
- */
- private final Stack<PlayerRecord> mPRStack = new Stack<PlayerRecord>();
-
- /**
- * The component the telephony package can register so telephony calls have priority to
- * handle media button events
- */
- private ComponentName mMediaReceiverForCalls = null;
-
- /**
- * Helper function:
- * Display in the log the current entries in the remote control focus stack
- */
- private void dumpRCStack(PrintWriter pw) {
- pw.println("\nRemote Control stack entries (last is top of stack):");
- synchronized(mPRStack) {
- Iterator<PlayerRecord> stackIterator = mPRStack.iterator();
- while(stackIterator.hasNext()) {
- stackIterator.next().dump(pw, true);
- }
- }
- }
-
- /**
- * Helper function:
- * Display in the log the current entries in the remote control stack, focusing
- * on RemoteControlClient data
- */
- private void dumpRCCStack(PrintWriter pw) {
- pw.println("\nRemote Control Client stack entries (last is top of stack):");
- synchronized(mPRStack) {
- Iterator<PlayerRecord> stackIterator = mPRStack.iterator();
- while(stackIterator.hasNext()) {
- stackIterator.next().dump(pw, false);
- }
- synchronized(mCurrentRcLock) {
- pw.println("\nCurrent remote control generation ID = " + mCurrentRcClientGen);
- }
- }
- synchronized (mMainRemote) {
- pw.println("\nRemote Volume State:");
- pw.println(" has remote: " + mHasRemotePlayback);
- pw.println(" is remote active: " + mMainRemoteIsActive);
- pw.println(" rccId: " + mMainRemote.mRccId);
- pw.println(" volume handling: "
- + ((mMainRemote.mVolumeHandling == RemoteControlClient.PLAYBACK_VOLUME_FIXED) ?
- "PLAYBACK_VOLUME_FIXED(0)" : "PLAYBACK_VOLUME_VARIABLE(1)"));
- pw.println(" volume: " + mMainRemote.mVolume);
- pw.println(" volume steps: " + mMainRemote.mVolumeMax);
- }
- }
-
- /**
- * Helper function:
- * Display in the log the current entries in the list of remote control displays
- */
- private void dumpRCDList(PrintWriter pw) {
- pw.println("\nRemote Control Display list entries:");
- synchronized(mPRStack) {
- final Iterator<DisplayInfoForServer> displayIterator = mRcDisplays.iterator();
- while (displayIterator.hasNext()) {
- final DisplayInfoForServer di = displayIterator.next();
- pw.println(" IRCD: " + di.mRcDisplay +
- " -- w:" + di.mArtworkExpectedWidth +
- " -- h:" + di.mArtworkExpectedHeight +
- " -- wantsPosSync:" + di.mWantsPositionSync +
- " -- " + (di.mEnabled ? "enabled" : "disabled"));
- }
- }
- }
-
- /**
- * Helper function:
- * Push the new media button receiver "near" the top of the PlayerRecord stack.
- * "Near the top" is defined as:
- * - at the top if the current PlayerRecord at the top is not playing
- * - below the entries at the top of the stack that correspond to the playing PlayerRecord
- * otherwise
- * Called synchronized on mPRStack
- * precondition: mediaIntent != null
- * @return true if the top of mPRStack was changed, false otherwise
- */
- private boolean pushMediaButtonReceiver_syncPrs(PendingIntent mediaIntent,
- ComponentName target, IBinder token) {
- if (mPRStack.empty()) {
- mPRStack.push(new PlayerRecord(mediaIntent, target, token));
- return true;
- } else if (mPRStack.peek().hasMatchingMediaButtonIntent(mediaIntent)) {
- // already at top of stack
- return false;
- }
- if (mAppOps.noteOp(AppOpsManager.OP_TAKE_MEDIA_BUTTONS, Binder.getCallingUid(),
- mediaIntent.getCreatorPackage()) != AppOpsManager.MODE_ALLOWED) {
- return false;
- }
- PlayerRecord oldTopPrse = mPRStack.lastElement(); // top of the stack before any changes
- boolean topChanged = false;
- PlayerRecord prse = null;
- int lastPlayingIndex = mPRStack.size();
- int inStackIndex = -1;
- try {
- // go through the stack from the top to figure out who's playing, and the position
- // of this media button receiver (note that it may not be in the stack)
- for (int index = mPRStack.size()-1; index >= 0; index--) {
- prse = mPRStack.elementAt(index);
- if (prse.isPlaybackActive()) {
- lastPlayingIndex = index;
- }
- if (prse.hasMatchingMediaButtonIntent(mediaIntent)) {
- inStackIndex = index;
- }
- }
-
- if (inStackIndex == -1) {
- // is not in stack
- prse = new PlayerRecord(mediaIntent, target, token);
- // it's new so it's not playing (no RemoteControlClient to give a playstate),
- // therefore it goes after the ones with active playback
- mPRStack.add(lastPlayingIndex, prse);
- } else {
- // is in the stack
- if (mPRStack.size() > 1) { // no need to remove and add if stack contains only 1
- prse = mPRStack.elementAt(inStackIndex);
- // remove it from its old location in the stack
- mPRStack.removeElementAt(inStackIndex);
- if (prse.isPlaybackActive()) {
- // and put it at the top
- mPRStack.push(prse);
- } else {
- // and put it after the ones with active playback
- if (inStackIndex > lastPlayingIndex) {
- mPRStack.add(lastPlayingIndex, prse);
- } else {
- mPRStack.add(lastPlayingIndex - 1, prse);
- }
- }
- }
- }
-
- } catch (ArrayIndexOutOfBoundsException e) {
- // not expected to happen, indicates improper concurrent modification or bad index
- Log.e(TAG, "Wrong index (inStack=" + inStackIndex + " lastPlaying=" + lastPlayingIndex
- + " size=" + mPRStack.size()
- + " accessing media button stack", e);
- }
-
- return (topChanged);
- }
-
- /**
- * Helper function:
- * Remove the remote control receiver from the RC focus stack.
- * Called synchronized on mPRStack
- * precondition: pi != null
- */
- private void removeMediaButtonReceiver_syncPrs(PendingIntent pi) {
- try {
- for (int index = mPRStack.size()-1; index >= 0; index--) {
- final PlayerRecord prse = mPRStack.elementAt(index);
- if (prse.hasMatchingMediaButtonIntent(pi)) {
- prse.destroy();
- // ok to remove element while traversing the stack since we're leaving the loop
- mPRStack.removeElementAt(index);
- break;
- }
- }
- } catch (ArrayIndexOutOfBoundsException e) {
- // not expected to happen, indicates improper concurrent modification
- Log.e(TAG, "Wrong index accessing media button stack, lock error? ", e);
- }
- }
-
- /**
- * Helper function:
- * Called synchronized on mPRStack
- */
- private boolean isCurrentRcController(PendingIntent pi) {
- if (!mPRStack.empty() && mPRStack.peek().hasMatchingMediaButtonIntent(pi)) {
- return true;
- }
- return false;
- }
-
- //==========================================================================================
- // Remote control display / client
- //==========================================================================================
- /**
- * Update the remote control displays with the new "focused" client generation
- */
- private void setNewRcClientOnDisplays_syncRcsCurrc(int newClientGeneration,
- PendingIntent newMediaIntent, boolean clearing) {
- synchronized(mPRStack) {
- if (mRcDisplays.size() > 0) {
- final Iterator<DisplayInfoForServer> displayIterator = mRcDisplays.iterator();
- while (displayIterator.hasNext()) {
- final DisplayInfoForServer di = displayIterator.next();
- try {
- di.mRcDisplay.setCurrentClientId(
- newClientGeneration, newMediaIntent, clearing);
- } catch (RemoteException e) {
- Log.e(TAG, "Dead display in setNewRcClientOnDisplays_syncRcsCurrc()",e);
- di.release();
- displayIterator.remove();
- }
- }
- }
- }
- }
-
- /**
- * Update the remote control clients with the new "focused" client generation
- */
- private void setNewRcClientGenerationOnClients_syncRcsCurrc(int newClientGeneration) {
- // (using an iterator on the stack so we can safely remove an entry if needed,
- // traversal order doesn't matter here as we update all entries)
- Iterator<PlayerRecord> stackIterator = mPRStack.iterator();
- while(stackIterator.hasNext()) {
- PlayerRecord se = stackIterator.next();
- if ((se != null) && (se.getRcc() != null)) {
- try {
- se.getRcc().setCurrentClientGenerationId(newClientGeneration);
- } catch (RemoteException e) {
- Log.w(TAG, "Dead client in setNewRcClientGenerationOnClients_syncRcsCurrc()",e);
- stackIterator.remove();
- se.unlinkToRcClientDeath();
- }
- }
- }
- }
-
- /**
- * Update the displays and clients with the new "focused" client generation and name
- * @param newClientGeneration the new generation value matching a client update
- * @param newMediaIntent the media button event receiver associated with the client.
- * May be null, which implies there is no registered media button event receiver.
- * @param clearing true if the new client generation value maps to a remote control update
- * where the display should be cleared.
- */
- private void setNewRcClient_syncRcsCurrc(int newClientGeneration,
- PendingIntent newMediaIntent, boolean clearing) {
- // send the new valid client generation ID to all displays
- setNewRcClientOnDisplays_syncRcsCurrc(newClientGeneration, newMediaIntent, clearing);
- // send the new valid client generation ID to all clients
- setNewRcClientGenerationOnClients_syncRcsCurrc(newClientGeneration);
- }
-
- /**
- * Called when processing MSG_RCDISPLAY_CLEAR event
- */
- private void onRcDisplayClear() {
- if (DEBUG_RC) Log.i(TAG, "Clear remote control display");
-
- synchronized(mPRStack) {
- synchronized(mCurrentRcLock) {
- mCurrentRcClientGen++;
- // synchronously update the displays and clients with the new client generation
- setNewRcClient_syncRcsCurrc(mCurrentRcClientGen,
- null /*newMediaIntent*/, true /*clearing*/);
- }
- }
- }
-
- /**
- * Called when processing MSG_RCDISPLAY_UPDATE event
- */
- private void onRcDisplayUpdate(PlayerRecord prse, int flags /* USED ?*/) {
- synchronized(mPRStack) {
- synchronized(mCurrentRcLock) {
- if ((mCurrentRcClient != null) && (mCurrentRcClient.equals(prse.getRcc()))) {
- if (DEBUG_RC) Log.i(TAG, "Display/update remote control ");
-
- mCurrentRcClientGen++;
- // synchronously update the displays and clients with
- // the new client generation
- setNewRcClient_syncRcsCurrc(mCurrentRcClientGen,
- prse.getMediaButtonIntent() /*newMediaIntent*/,
- false /*clearing*/);
-
- // tell the current client that it needs to send info
- try {
- //TODO change name to informationRequestForAllDisplays()
- mCurrentRcClient.onInformationRequested(mCurrentRcClientGen, flags);
- } catch (RemoteException e) {
- Log.e(TAG, "Current valid remote client is dead: "+e);
- mCurrentRcClient = null;
- }
- } else {
- // the remote control display owner has changed between the
- // the message to update the display was sent, and the time it
- // gets to be processed (now)
- }
- }
- }
- }
-
- /**
- * Called when processing MSG_RCDISPLAY_INIT_INFO event
- * Causes the current RemoteControlClient to send its info (metadata, playstate...) to
- * a single RemoteControlDisplay, NOT all of them, as with MSG_RCDISPLAY_UPDATE.
- */
- private void onRcDisplayInitInfo(IRemoteControlDisplay newRcd, int w, int h) {
- synchronized(mPRStack) {
- synchronized(mCurrentRcLock) {
- if (mCurrentRcClient != null) {
- if (DEBUG_RC) { Log.i(TAG, "Init RCD with current info"); }
- try {
- // synchronously update the new RCD with the current client generation
- // and matching PendingIntent
- newRcd.setCurrentClientId(mCurrentRcClientGen, mCurrentRcClientIntent,
- false);
-
- // tell the current RCC that it needs to send info, but only to the new RCD
- try {
- mCurrentRcClient.informationRequestForDisplay(newRcd, w, h);
- } catch (RemoteException e) {
- Log.e(TAG, "Current valid remote client is dead: ", e);
- mCurrentRcClient = null;
- }
- } catch (RemoteException e) {
- Log.e(TAG, "Dead display in onRcDisplayInitInfo()", e);
- }
- }
- }
- }
- }
-
- /**
- * Helper function:
- * Called synchronized on mPRStack
- */
- private void clearRemoteControlDisplay_syncPrs() {
- synchronized(mCurrentRcLock) {
- mCurrentRcClient = null;
- }
- // will cause onRcDisplayClear() to be called in AudioService's handler thread
- mEventHandler.sendMessage( mEventHandler.obtainMessage(MSG_RCDISPLAY_CLEAR) );
- }
-
- /**
- * Helper function for code readability: only to be called from
- * checkUpdateRemoteControlDisplay_syncPrs() which checks the preconditions for
- * this method.
- * Preconditions:
- * - called synchronized on mPRStack
- * - mPRStack.isEmpty() is false
- */
- private void updateRemoteControlDisplay_syncPrs(int infoChangedFlags) {
- PlayerRecord prse = mPRStack.peek();
- int infoFlagsAboutToBeUsed = infoChangedFlags;
- // this is where we enforce opt-in for information display on the remote controls
- // with the new AudioManager.registerRemoteControlClient() API
- if (prse.getRcc() == null) {
- //Log.w(TAG, "Can't update remote control display with null remote control client");
- clearRemoteControlDisplay_syncPrs();
- return;
- }
- synchronized(mCurrentRcLock) {
- if (!prse.getRcc().equals(mCurrentRcClient)) {
- // new RC client, assume every type of information shall be queried
- infoFlagsAboutToBeUsed = RC_INFO_ALL;
- }
- mCurrentRcClient = prse.getRcc();
- mCurrentRcClientIntent = prse.getMediaButtonIntent();
- }
- // will cause onRcDisplayUpdate() to be called in AudioService's handler thread
- mEventHandler.sendMessage( mEventHandler.obtainMessage(MSG_RCDISPLAY_UPDATE,
- infoFlagsAboutToBeUsed /* arg1 */, 0, prse /* obj, != null */) );
- }
-
- /**
- * Helper function:
- * Called synchronized on mPRStack
- * Check whether the remote control display should be updated, triggers the update if required
- * @param infoChangedFlags the flags corresponding to the remote control client information
- * that has changed, if applicable (checking for the update conditions might trigger a
- * clear, rather than an update event).
- */
- private void checkUpdateRemoteControlDisplay_syncPrs(int infoChangedFlags) {
- // determine whether the remote control display should be refreshed
- // if the player record stack is empty, there is nothing to display, so clear the RC display
- if (mPRStack.isEmpty()) {
- clearRemoteControlDisplay_syncPrs();
- return;
- }
-
- // this is where more rules for refresh go
-
- // refresh conditions were verified: update the remote controls
- // ok to call: synchronized on mPRStack, mPRStack is not empty
- updateRemoteControlDisplay_syncPrs(infoChangedFlags);
- }
-
- /**
- * see AudioManager.registerMediaButtonIntent(PendingIntent pi, ComponentName c)
- * precondition: mediaIntent != null
- */
- protected void registerMediaButtonIntent(PendingIntent mediaIntent, ComponentName eventReceiver,
- IBinder token) {
- Log.i(TAG, " Remote Control registerMediaButtonIntent() for " + mediaIntent);
-
- synchronized(mPRStack) {
- if (pushMediaButtonReceiver_syncPrs(mediaIntent, eventReceiver, token)) {
- // new RC client, assume every type of information shall be queried
- checkUpdateRemoteControlDisplay_syncPrs(RC_INFO_ALL);
- }
- }
- }
-
- /**
- * see AudioManager.unregisterMediaButtonIntent(PendingIntent mediaIntent)
- * precondition: mediaIntent != null, eventReceiver != null
- */
- protected void unregisterMediaButtonIntent(PendingIntent mediaIntent)
- {
- Log.i(TAG, " Remote Control unregisterMediaButtonIntent() for " + mediaIntent);
-
- synchronized(mPRStack) {
- boolean topOfStackWillChange = isCurrentRcController(mediaIntent);
- removeMediaButtonReceiver_syncPrs(mediaIntent);
- if (topOfStackWillChange) {
- // current RC client will change, assume every type of info needs to be queried
- checkUpdateRemoteControlDisplay_syncPrs(RC_INFO_ALL);
- }
- }
- }
-
- protected void unregisterMediaButtonIntentAsync(final PendingIntent mediaIntent) {
- mEventHandler.sendMessage(
- mEventHandler.obtainMessage(MSG_UNREGISTER_MEDIABUTTONINTENT, 0, 0,
- mediaIntent));
- }
-
- /**
- * see AudioManager.registerMediaButtonEventReceiverForCalls(ComponentName c)
- * precondition: c != null
- */
- protected void registerMediaButtonEventReceiverForCalls(ComponentName c) {
- if (mContext.checkCallingPermission("android.permission.MODIFY_PHONE_STATE")
- != PackageManager.PERMISSION_GRANTED) {
- Log.e(TAG, "Invalid permissions to register media button receiver for calls");
- return;
- }
- synchronized(mPRStack) {
- mMediaReceiverForCalls = c;
- }
- }
-
- /**
- * see AudioManager.unregisterMediaButtonEventReceiverForCalls()
- */
- protected void unregisterMediaButtonEventReceiverForCalls() {
- if (mContext.checkCallingPermission("android.permission.MODIFY_PHONE_STATE")
- != PackageManager.PERMISSION_GRANTED) {
- Log.e(TAG, "Invalid permissions to unregister media button receiver for calls");
- return;
- }
- synchronized(mPRStack) {
- mMediaReceiverForCalls = null;
- }
- }
-
- /**
- * see AudioManager.registerRemoteControlClient(ComponentName eventReceiver, ...)
- * @return the unique ID of the PlayerRecord associated with the RemoteControlClient
- * Note: using this method with rcClient == null is a way to "disable" the IRemoteControlClient
- * without modifying the RC stack, but while still causing the display to refresh (will
- * become blank as a result of this)
- */
- protected int registerRemoteControlClient(PendingIntent mediaIntent,
- IRemoteControlClient rcClient, String callingPackageName) {
- if (DEBUG_RC) Log.i(TAG, "Register remote control client rcClient="+rcClient);
- int rccId = RemoteControlClient.RCSE_ID_UNREGISTERED;
- synchronized(mPRStack) {
- // store the new display information
- try {
- for (int index = mPRStack.size()-1; index >= 0; index--) {
- final PlayerRecord prse = mPRStack.elementAt(index);
- if(prse.hasMatchingMediaButtonIntent(mediaIntent)) {
- prse.resetControllerInfoForRcc(rcClient, callingPackageName,
- Binder.getCallingUid());
-
- if (rcClient == null) {
- break;
- }
-
- rccId = prse.getRccId();
-
- // there is a new (non-null) client:
- // give the new client the displays (if any)
- if (mRcDisplays.size() > 0) {
- plugRemoteControlDisplaysIntoClient_syncPrs(prse.getRcc());
- }
- break;
- }
- }//for
- } catch (ArrayIndexOutOfBoundsException e) {
- // not expected to happen, indicates improper concurrent modification
- Log.e(TAG, "Wrong index accessing RC stack, lock error? ", e);
- }
-
- // if the eventReceiver is at the top of the stack
- // then check for potential refresh of the remote controls
- if (isCurrentRcController(mediaIntent)) {
- checkUpdateRemoteControlDisplay_syncPrs(RC_INFO_ALL);
- }
- }//synchronized(mPRStack)
- return rccId;
- }
-
- /**
- * see AudioManager.unregisterRemoteControlClient(PendingIntent pi, ...)
- * rcClient is guaranteed non-null
- */
- protected void unregisterRemoteControlClient(PendingIntent mediaIntent,
- IRemoteControlClient rcClient) {
- if (DEBUG_RC) Log.i(TAG, "Unregister remote control client rcClient="+rcClient);
- synchronized(mPRStack) {
- boolean topRccChange = false;
- try {
- for (int index = mPRStack.size()-1; index >= 0; index--) {
- final PlayerRecord prse = mPRStack.elementAt(index);
- if ((prse.hasMatchingMediaButtonIntent(mediaIntent))
- && rcClient.equals(prse.getRcc())) {
- // we found the IRemoteControlClient to unregister
- prse.resetControllerInfoForNoRcc();
- topRccChange = (index == mPRStack.size()-1);
- // there can only be one matching RCC in the RC stack, we're done
- break;
- }
- }
- } catch (ArrayIndexOutOfBoundsException e) {
- // not expected to happen, indicates improper concurrent modification
- Log.e(TAG, "Wrong index accessing RC stack, lock error? ", e);
- }
- if (topRccChange) {
- // no more RCC for the RCD, check for potential refresh of the remote controls
- checkUpdateRemoteControlDisplay_syncPrs(RC_INFO_ALL);
- }
- }
- }
-
-
- /**
- * A class to encapsulate all the information about a remote control display.
- * After instanciation, init() must always be called before the object is added in the list
- * of displays.
- * Before being removed from the list of displays, release() must always be called (otherwise
- * it will leak death handlers).
- */
- private class DisplayInfoForServer implements IBinder.DeathRecipient {
- /** may never be null */
- private final IRemoteControlDisplay mRcDisplay;
- private final IBinder mRcDisplayBinder;
- private int mArtworkExpectedWidth = -1;
- private int mArtworkExpectedHeight = -1;
- private boolean mWantsPositionSync = false;
- private ComponentName mClientNotifListComp;
- private boolean mEnabled = true;
-
- public DisplayInfoForServer(IRemoteControlDisplay rcd, int w, int h) {
- if (DEBUG_RC) Log.i(TAG, "new DisplayInfoForServer for " + rcd + " w=" + w + " h=" + h);
- mRcDisplay = rcd;
- mRcDisplayBinder = rcd.asBinder();
- mArtworkExpectedWidth = w;
- mArtworkExpectedHeight = h;
- }
-
- public boolean init() {
- try {
- mRcDisplayBinder.linkToDeath(this, 0);
- } catch (RemoteException e) {
- // remote control display is DOA, disqualify it
- Log.w(TAG, "registerRemoteControlDisplay() has a dead client " + mRcDisplayBinder);
- return false;
- }
- return true;
- }
-
- public void release() {
- try {
- mRcDisplayBinder.unlinkToDeath(this, 0);
- } catch (java.util.NoSuchElementException e) {
- // not much we can do here, the display should have been unregistered anyway
- Log.e(TAG, "Error in DisplaInfoForServer.relase()", e);
- }
- }
-
- public void binderDied() {
- synchronized(mPRStack) {
- Log.w(TAG, "RemoteControl: display " + mRcDisplay + " died");
- // remove the display from the list
- final Iterator<DisplayInfoForServer> displayIterator = mRcDisplays.iterator();
- while (displayIterator.hasNext()) {
- final DisplayInfoForServer di = displayIterator.next();
- if (di.mRcDisplay == mRcDisplay) {
- if (DEBUG_RC) Log.w(TAG, " RCD removed from list");
- displayIterator.remove();
- return;
- }
- }
- }
- }
- }
-
- /**
- * The remote control displays.
- * Access synchronized on mPRStack
- */
- private ArrayList<DisplayInfoForServer> mRcDisplays = new ArrayList<DisplayInfoForServer>(1);
-
- /**
- * Plug each registered display into the specified client
- * @param rcc, guaranteed non null
- */
- private void plugRemoteControlDisplaysIntoClient_syncPrs(IRemoteControlClient rcc) {
- final Iterator<DisplayInfoForServer> displayIterator = mRcDisplays.iterator();
- while (displayIterator.hasNext()) {
- final DisplayInfoForServer di = displayIterator.next();
- try {
- rcc.plugRemoteControlDisplay(di.mRcDisplay, di.mArtworkExpectedWidth,
- di.mArtworkExpectedHeight);
- if (di.mWantsPositionSync) {
- rcc.setWantsSyncForDisplay(di.mRcDisplay, true);
- }
- } catch (RemoteException e) {
- Log.e(TAG, "Error connecting RCD to RCC in RCC registration",e);
- }
- }
- }
-
- private void enableRemoteControlDisplayForClient_syncRcStack(IRemoteControlDisplay rcd,
- boolean enabled) {
- // let all the remote control clients know whether the given display is enabled
- // (so the remote control stack traversal order doesn't matter).
- final Iterator<PlayerRecord> stackIterator = mPRStack.iterator();
- while(stackIterator.hasNext()) {
- PlayerRecord prse = stackIterator.next();
- if(prse.getRcc() != null) {
- try {
- prse.getRcc().enableRemoteControlDisplay(rcd, enabled);
- } catch (RemoteException e) {
- Log.e(TAG, "Error connecting RCD to client: ", e);
- }
- }
- }
- }
-
- /**
- * Is the remote control display interface already registered
- * @param rcd
- * @return true if the IRemoteControlDisplay is already in the list of displays
- */
- private boolean rcDisplayIsPluggedIn_syncRcStack(IRemoteControlDisplay rcd) {
- final Iterator<DisplayInfoForServer> displayIterator = mRcDisplays.iterator();
- while (displayIterator.hasNext()) {
- final DisplayInfoForServer di = displayIterator.next();
- if (di.mRcDisplay.asBinder().equals(rcd.asBinder())) {
- return true;
- }
- }
- return false;
- }
-
- /**
- * Register an IRemoteControlDisplay.
- * Notify all IRemoteControlClient of the new display and cause the RemoteControlClient
- * at the top of the stack to update the new display with its information.
- * @see android.media.IAudioService#registerRemoteControlDisplay(android.media.IRemoteControlDisplay, int, int)
- * @param rcd the IRemoteControlDisplay to register. No effect if null.
- * @param w the maximum width of the expected bitmap. Negative or zero values indicate this
- * display doesn't need to receive artwork.
- * @param h the maximum height of the expected bitmap. Negative or zero values indicate this
- * display doesn't need to receive artwork.
- * @param listenerComp the component for the listener interface, may be null if it's not needed
- * to verify it belongs to one of the enabled notification listeners
- */
- private void registerRemoteControlDisplay_int(IRemoteControlDisplay rcd, int w, int h,
- ComponentName listenerComp) {
- if (DEBUG_RC) Log.d(TAG, ">>> registerRemoteControlDisplay("+rcd+")");
- synchronized(mAudioFocusLock) {
- synchronized(mPRStack) {
- if ((rcd == null) || rcDisplayIsPluggedIn_syncRcStack(rcd)) {
- return;
- }
- DisplayInfoForServer di = new DisplayInfoForServer(rcd, w, h);
- di.mEnabled = true;
- di.mClientNotifListComp = listenerComp;
- if (!di.init()) {
- if (DEBUG_RC) Log.e(TAG, " error registering RCD");
- return;
- }
- // add RCD to list of displays
- mRcDisplays.add(di);
-
- // let all the remote control clients know there is a new display (so the remote
- // control stack traversal order doesn't matter).
- Iterator<PlayerRecord> stackIterator = mPRStack.iterator();
- while(stackIterator.hasNext()) {
- PlayerRecord prse = stackIterator.next();
- if(prse.getRcc() != null) {
- try {
- prse.getRcc().plugRemoteControlDisplay(rcd, w, h);
- } catch (RemoteException e) {
- Log.e(TAG, "Error connecting RCD to client: ", e);
- }
- }
- }
-
- // we have a new display, of which all the clients are now aware: have it be
- // initialized wih the current gen ID and the current client info, do not
- // reset the information for the other (existing) displays
- sendMsg(mEventHandler, MSG_RCDISPLAY_INIT_INFO, SENDMSG_QUEUE,
- w /*arg1*/, h /*arg2*/,
- rcd /*obj*/, 0/*delay*/);
- }
- }
- }
-
- /**
- * Unregister an IRemoteControlDisplay.
- * No effect if the IRemoteControlDisplay hasn't been successfully registered.
- * @see android.media.IAudioService#unregisterRemoteControlDisplay(android.media.IRemoteControlDisplay)
- * @param rcd the IRemoteControlDisplay to unregister. No effect if null.
- */
- protected void unregisterRemoteControlDisplay(IRemoteControlDisplay rcd) {
- if (DEBUG_RC) Log.d(TAG, "<<< unregisterRemoteControlDisplay("+rcd+")");
- synchronized(mPRStack) {
- if (rcd == null) {
- return;
- }
-
- boolean displayWasPluggedIn = false;
- final Iterator<DisplayInfoForServer> displayIterator = mRcDisplays.iterator();
- while (displayIterator.hasNext() && !displayWasPluggedIn) {
- final DisplayInfoForServer di = displayIterator.next();
- if (di.mRcDisplay.asBinder().equals(rcd.asBinder())) {
- displayWasPluggedIn = true;
- di.release();
- displayIterator.remove();
- }
- }
-
- if (displayWasPluggedIn) {
- // disconnect this remote control display from all the clients, so the remote
- // control stack traversal order doesn't matter
- final Iterator<PlayerRecord> stackIterator = mPRStack.iterator();
- while(stackIterator.hasNext()) {
- final PlayerRecord prse = stackIterator.next();
- if(prse.getRcc() != null) {
- try {
- prse.getRcc().unplugRemoteControlDisplay(rcd);
- } catch (RemoteException e) {
- Log.e(TAG, "Error disconnecting remote control display to client: ", e);
- }
- }
- }
- } else {
- if (DEBUG_RC) Log.w(TAG, " trying to unregister unregistered RCD");
- }
- }
- }
-
- /**
- * Update the size of the artwork used by an IRemoteControlDisplay.
- * @see android.media.IAudioService#remoteControlDisplayUsesBitmapSize(android.media.IRemoteControlDisplay, int, int)
- * @param rcd the IRemoteControlDisplay with the new artwork size requirement
- * @param w the maximum width of the expected bitmap. Negative or zero values indicate this
- * display doesn't need to receive artwork.
- * @param h the maximum height of the expected bitmap. Negative or zero values indicate this
- * display doesn't need to receive artwork.
- */
- protected void remoteControlDisplayUsesBitmapSize(IRemoteControlDisplay rcd, int w, int h) {
- synchronized(mPRStack) {
- final Iterator<DisplayInfoForServer> displayIterator = mRcDisplays.iterator();
- boolean artworkSizeUpdate = false;
- while (displayIterator.hasNext() && !artworkSizeUpdate) {
- final DisplayInfoForServer di = displayIterator.next();
- if (di.mRcDisplay.asBinder().equals(rcd.asBinder())) {
- if ((di.mArtworkExpectedWidth != w) || (di.mArtworkExpectedHeight != h)) {
- di.mArtworkExpectedWidth = w;
- di.mArtworkExpectedHeight = h;
- artworkSizeUpdate = true;
- }
- }
- }
- if (artworkSizeUpdate) {
- // RCD is currently plugged in and its artwork size has changed, notify all RCCs,
- // stack traversal order doesn't matter
- final Iterator<PlayerRecord> stackIterator = mPRStack.iterator();
- while(stackIterator.hasNext()) {
- final PlayerRecord prse = stackIterator.next();
- if(prse.getRcc() != null) {
- try {
- prse.getRcc().setBitmapSizeForDisplay(rcd, w, h);
- } catch (RemoteException e) {
- Log.e(TAG, "Error setting bitmap size for RCD on RCC: ", e);
- }
- }
- }
- }
- }
- }
-
- /**
- * Controls whether a remote control display needs periodic checks of the RemoteControlClient
- * playback position to verify that the estimated position has not drifted from the actual
- * position. By default the check is not performed.
- * The IRemoteControlDisplay must have been previously registered for this to have any effect.
- * @param rcd the IRemoteControlDisplay for which the anti-drift mechanism will be enabled
- * or disabled. Not null.
- * @param wantsSync if true, RemoteControlClient instances which expose their playback position
- * to the framework will regularly compare the estimated playback position with the actual
- * position, and will update the IRemoteControlDisplay implementation whenever a drift is
- * detected.
- */
- protected void remoteControlDisplayWantsPlaybackPositionSync(IRemoteControlDisplay rcd,
- boolean wantsSync) {
- synchronized(mPRStack) {
- boolean rcdRegistered = false;
- // store the information about this display
- // (display stack traversal order doesn't matter).
- final Iterator<DisplayInfoForServer> displayIterator = mRcDisplays.iterator();
- while (displayIterator.hasNext()) {
- final DisplayInfoForServer di = displayIterator.next();
- if (di.mRcDisplay.asBinder().equals(rcd.asBinder())) {
- di.mWantsPositionSync = wantsSync;
- rcdRegistered = true;
- break;
- }
- }
- if (!rcdRegistered) {
- return;
- }
- // notify all current RemoteControlClients
- // (stack traversal order doesn't matter as we notify all RCCs)
- final Iterator<PlayerRecord> stackIterator = mPRStack.iterator();
- while (stackIterator.hasNext()) {
- final PlayerRecord prse = stackIterator.next();
- if (prse.getRcc() != null) {
- try {
- prse.getRcc().setWantsSyncForDisplay(rcd, wantsSync);
- } catch (RemoteException e) {
- Log.e(TAG, "Error setting position sync flag for RCD on RCC: ", e);
- }
- }
- }
- }
- }
-
- // handler for MSG_RCC_NEW_VOLUME_OBS
- private void onRegisterVolumeObserverForRcc(int rccId, IRemoteVolumeObserver rvo) {
- synchronized(mPRStack) {
- // The stack traversal order doesn't matter because there is only one stack entry
- // with this RCC ID, but the matching ID is more likely at the top of the stack, so
- // start iterating from the top.
- try {
- for (int index = mPRStack.size()-1; index >= 0; index--) {
- final PlayerRecord prse = mPRStack.elementAt(index);
- if (prse.getRccId() == rccId) {
- prse.mRemoteVolumeObs = rvo;
- break;
- }
- }
- } catch (ArrayIndexOutOfBoundsException e) {
- // not expected to happen, indicates improper concurrent modification
- Log.e(TAG, "Wrong index accessing media button stack, lock error? ", e);
- }
- }
- }
-
- /**
- * Checks if a remote client is active on the supplied stream type. Update the remote stream
- * volume state if found and playing
- * @param streamType
- * @return false if no remote playing is currently playing
- */
- protected boolean checkUpdateRemoteStateIfActive(int streamType) {
- synchronized(mPRStack) {
- // iterating from top of stack as active playback is more likely on entries at the top
- try {
- for (int index = mPRStack.size()-1; index >= 0; index--) {
- final PlayerRecord prse = mPRStack.elementAt(index);
- if ((prse.mPlaybackType == RemoteControlClient.PLAYBACK_TYPE_REMOTE)
- && isPlaystateActive(prse.mPlaybackState.mState)
- && (prse.mPlaybackStream == streamType)) {
- if (DEBUG_RC) Log.d(TAG, "remote playback active on stream " + streamType
- + ", vol =" + prse.mPlaybackVolume);
- synchronized (mMainRemote) {
- mMainRemote.mRccId = prse.getRccId();
- mMainRemote.mVolume = prse.mPlaybackVolume;
- mMainRemote.mVolumeMax = prse.mPlaybackVolumeMax;
- mMainRemote.mVolumeHandling = prse.mPlaybackVolumeHandling;
- mMainRemoteIsActive = true;
- }
- return true;
- }
- }
- } catch (ArrayIndexOutOfBoundsException e) {
- // not expected to happen, indicates improper concurrent modification
- Log.e(TAG, "Wrong index accessing RC stack, lock error? ", e);
- }
- }
- synchronized (mMainRemote) {
- mMainRemoteIsActive = false;
- }
- return false;
- }
-
- /**
- * Returns true if the given playback state is considered "active", i.e. it describes a state
- * where playback is happening, or about to
- * @param playState the playback state to evaluate
- * @return true if active, false otherwise (inactive or unknown)
- */
- protected static boolean isPlaystateActive(int playState) {
- switch (playState) {
- case RemoteControlClient.PLAYSTATE_PLAYING:
- case RemoteControlClient.PLAYSTATE_BUFFERING:
- case RemoteControlClient.PLAYSTATE_FAST_FORWARDING:
- case RemoteControlClient.PLAYSTATE_REWINDING:
- case RemoteControlClient.PLAYSTATE_SKIPPING_BACKWARDS:
- case RemoteControlClient.PLAYSTATE_SKIPPING_FORWARDS:
- return true;
- default:
- return false;
- }
- }
-
- private void sendVolumeUpdateToRemote(int rccId, int direction) {
- if (DEBUG_VOL) { Log.d(TAG, "sendVolumeUpdateToRemote(rccId="+rccId+" , dir="+direction); }
- if (direction == 0) {
- // only handling discrete events
- return;
- }
- IRemoteVolumeObserver rvo = null;
- synchronized (mPRStack) {
- // The stack traversal order doesn't matter because there is only one stack entry
- // with this RCC ID, but the matching ID is more likely at the top of the stack, so
- // start iterating from the top.
- try {
- for (int index = mPRStack.size()-1; index >= 0; index--) {
- final PlayerRecord prse = mPRStack.elementAt(index);
- //FIXME OPTIMIZE store this info in mMainRemote so we don't have to iterate?
- if (prse.getRccId() == rccId) {
- rvo = prse.mRemoteVolumeObs;
- break;
- }
- }
- } catch (ArrayIndexOutOfBoundsException e) {
- // not expected to happen, indicates improper concurrent modification
- Log.e(TAG, "Wrong index accessing media button stack, lock error? ", e);
- }
- }
- if (rvo != null) {
- try {
- rvo.dispatchRemoteVolumeUpdate(direction, -1);
- } catch (RemoteException e) {
- Log.e(TAG, "Error dispatching relative volume update", e);
- }
- }
- }
-
- protected int getRemoteStreamMaxVolume() {
- synchronized (mMainRemote) {
- if (mMainRemote.mRccId == RemoteControlClient.RCSE_ID_UNREGISTERED) {
- return 0;
- }
- return mMainRemote.mVolumeMax;
- }
- }
-
- protected int getRemoteStreamVolume() {
- synchronized (mMainRemote) {
- if (mMainRemote.mRccId == RemoteControlClient.RCSE_ID_UNREGISTERED) {
- return 0;
- }
- return mMainRemote.mVolume;
- }
- }
-
- protected void setRemoteStreamVolume(int vol) {
- if (DEBUG_VOL) { Log.d(TAG, "setRemoteStreamVolume(vol="+vol+")"); }
- int rccId = RemoteControlClient.RCSE_ID_UNREGISTERED;
- synchronized (mMainRemote) {
- if (mMainRemote.mRccId == RemoteControlClient.RCSE_ID_UNREGISTERED) {
- return;
- }
- rccId = mMainRemote.mRccId;
- }
- IRemoteVolumeObserver rvo = null;
- synchronized (mPRStack) {
- // The stack traversal order doesn't matter because there is only one stack entry
- // with this RCC ID, but the matching ID is more likely at the top of the stack, so
- // start iterating from the top.
- try {
- for (int index = mPRStack.size()-1; index >= 0; index--) {
- final PlayerRecord prse = mPRStack.elementAt(index);
- //FIXME OPTIMIZE store this info in mMainRemote so we don't have to iterate?
- if (prse.getRccId() == rccId) {
- rvo = prse.mRemoteVolumeObs;
- break;
- }
- }
- } catch (ArrayIndexOutOfBoundsException e) {
- // not expected to happen, indicates improper concurrent modification
- Log.e(TAG, "Wrong index accessing media button stack, lock error? ", e);
- }
- }
- if (rvo != null) {
- try {
- rvo.dispatchRemoteVolumeUpdate(0, vol);
- } catch (RemoteException e) {
- Log.e(TAG, "Error dispatching absolute volume update", e);
- }
- }
- }
-
- /**
- * Call to make AudioService reevaluate whether it's in a mode where remote players should
- * have their volume controlled. In this implementation this is only to reset whether
- * VolumePanel should display remote volumes
- */
- protected void postReevaluateRemote() {
- sendMsg(mEventHandler, MSG_REEVALUATE_REMOTE, SENDMSG_QUEUE, 0, 0, null, 0);
- }
-
- private void onReevaluateRemote() {
- // TODO This was used to notify VolumePanel if there was remote playback
- // in the stack. This is now in MediaSessionService. More code should be
- // removed.
- }
-
-}
diff --git a/media/java/android/media/MediaRouter.java b/media/java/android/media/MediaRouter.java
index 958ffab..5285074 100644
--- a/media/java/android/media/MediaRouter.java
+++ b/media/java/android/media/MediaRouter.java
@@ -171,15 +171,15 @@ public class MediaRouter {
}
void updateAudioRoutes(AudioRoutesInfo newRoutes) {
- if (newRoutes.mMainType != mCurAudioRoutesInfo.mMainType) {
- mCurAudioRoutesInfo.mMainType = newRoutes.mMainType;
+ if (newRoutes.mainType != mCurAudioRoutesInfo.mainType) {
+ mCurAudioRoutesInfo.mainType = newRoutes.mainType;
int name;
- if ((newRoutes.mMainType&AudioRoutesInfo.MAIN_HEADPHONES) != 0
- || (newRoutes.mMainType&AudioRoutesInfo.MAIN_HEADSET) != 0) {
+ if ((newRoutes.mainType&AudioRoutesInfo.MAIN_HEADPHONES) != 0
+ || (newRoutes.mainType&AudioRoutesInfo.MAIN_HEADSET) != 0) {
name = com.android.internal.R.string.default_audio_route_name_headphones;
- } else if ((newRoutes.mMainType&AudioRoutesInfo.MAIN_DOCK_SPEAKERS) != 0) {
+ } else if ((newRoutes.mainType&AudioRoutesInfo.MAIN_DOCK_SPEAKERS) != 0) {
name = com.android.internal.R.string.default_audio_route_name_dock_speakers;
- } else if ((newRoutes.mMainType&AudioRoutesInfo.MAIN_HDMI) != 0) {
+ } else if ((newRoutes.mainType&AudioRoutesInfo.MAIN_HDMI) != 0) {
name = com.android.internal.R.string.default_media_route_name_hdmi;
} else {
name = com.android.internal.R.string.default_audio_route_name;
@@ -188,21 +188,21 @@ public class MediaRouter {
dispatchRouteChanged(sStatic.mDefaultAudioVideo);
}
- final int mainType = mCurAudioRoutesInfo.mMainType;
+ final int mainType = mCurAudioRoutesInfo.mainType;
- if (!TextUtils.equals(newRoutes.mBluetoothName, mCurAudioRoutesInfo.mBluetoothName)) {
- mCurAudioRoutesInfo.mBluetoothName = newRoutes.mBluetoothName;
- if (mCurAudioRoutesInfo.mBluetoothName != null) {
+ if (!TextUtils.equals(newRoutes.bluetoothName, mCurAudioRoutesInfo.bluetoothName)) {
+ mCurAudioRoutesInfo.bluetoothName = newRoutes.bluetoothName;
+ if (mCurAudioRoutesInfo.bluetoothName != null) {
if (sStatic.mBluetoothA2dpRoute == null) {
final RouteInfo info = new RouteInfo(sStatic.mSystemCategory);
- info.mName = mCurAudioRoutesInfo.mBluetoothName;
+ info.mName = mCurAudioRoutesInfo.bluetoothName;
info.mDescription = sStatic.mResources.getText(
com.android.internal.R.string.bluetooth_a2dp_audio_route_name);
info.mSupportedTypes = ROUTE_TYPE_LIVE_AUDIO;
sStatic.mBluetoothA2dpRoute = info;
addRouteStatic(sStatic.mBluetoothA2dpRoute);
} else {
- sStatic.mBluetoothA2dpRoute.mName = mCurAudioRoutesInfo.mBluetoothName;
+ sStatic.mBluetoothA2dpRoute.mName = mCurAudioRoutesInfo.bluetoothName;
dispatchRouteChanged(sStatic.mBluetoothA2dpRoute);
}
} else if (sStatic.mBluetoothA2dpRoute != null) {
diff --git a/media/java/android/media/PlayerRecord.java b/media/java/android/media/PlayerRecord.java
deleted file mode 100644
index 664ddcf..0000000
--- a/media/java/android/media/PlayerRecord.java
+++ /dev/null
@@ -1,357 +0,0 @@
-/*
- * Copyright (C) 2014 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 android.media;
-
-import android.app.PendingIntent;
-import android.content.ComponentName;
-import android.os.Binder;
-import android.os.IBinder;
-import android.os.IBinder.DeathRecipient;
-import android.os.RemoteException;
-import android.util.Log;
-
-import java.io.PrintWriter;
-
-/**
- * @hide
- * Class to handle all the information about a media player, encapsulating information
- * about its use RemoteControlClient, playback type and volume... The lifecycle of each
- * instance is managed by android.media.MediaFocusControl, from its addition to the player stack
- * stack to its release.
- */
-class PlayerRecord implements DeathRecipient {
-
- // on purpose not using this classe's name, as it will only be used from MediaFocusControl
- private static final String TAG = "MediaFocusControl";
- private static final boolean DEBUG = false;
-
- /**
- * A global counter for RemoteControlClient identifiers
- */
- private static int sLastRccId = 0;
-
- public static MediaFocusControl sController;
-
- /**
- * The target for the ACTION_MEDIA_BUTTON events.
- * Always non null. //FIXME verify
- */
- final private PendingIntent mMediaIntent;
- /**
- * The registered media button event receiver.
- */
- final private ComponentName mReceiverComponent;
-
- private int mRccId = -1;
-
- /**
- * A non-null token implies this record tracks a "live" player whose death is being monitored.
- */
- private IBinder mToken;
- private String mCallingPackageName;
- private int mCallingUid;
- /**
- * Provides access to the information to display on the remote control.
- * May be null (when a media button event receiver is registered,
- * but no remote control client has been registered) */
- private IRemoteControlClient mRcClient;
- private RcClientDeathHandler mRcClientDeathHandler;
- /**
- * Information only used for non-local playback
- */
- //FIXME private?
- public int mPlaybackType;
- public int mPlaybackVolume;
- public int mPlaybackVolumeMax;
- public int mPlaybackVolumeHandling;
- public int mPlaybackStream;
- public RccPlaybackState mPlaybackState;
- public IRemoteVolumeObserver mRemoteVolumeObs;
-
-
- protected static class RccPlaybackState {
- public int mState;
- public long mPositionMs;
- public float mSpeed;
-
- public RccPlaybackState(int state, long positionMs, float speed) {
- mState = state;
- mPositionMs = positionMs;
- mSpeed = speed;
- }
-
- public void reset() {
- mState = RemoteControlClient.PLAYSTATE_STOPPED;
- mPositionMs = RemoteControlClient.PLAYBACK_POSITION_INVALID;
- mSpeed = RemoteControlClient.PLAYBACK_SPEED_1X;
- }
-
- @Override
- public String toString() {
- return stateToString() + ", " + posToString() + ", " + mSpeed + "X";
- }
-
- private String posToString() {
- if (mPositionMs == RemoteControlClient.PLAYBACK_POSITION_INVALID) {
- return "PLAYBACK_POSITION_INVALID";
- } else if (mPositionMs == RemoteControlClient.PLAYBACK_POSITION_ALWAYS_UNKNOWN) {
- return "PLAYBACK_POSITION_ALWAYS_UNKNOWN";
- } else {
- return (String.valueOf(mPositionMs) + "ms");
- }
- }
-
- private String stateToString() {
- switch (mState) {
- case RemoteControlClient.PLAYSTATE_NONE:
- return "PLAYSTATE_NONE";
- case RemoteControlClient.PLAYSTATE_STOPPED:
- return "PLAYSTATE_STOPPED";
- case RemoteControlClient.PLAYSTATE_PAUSED:
- return "PLAYSTATE_PAUSED";
- case RemoteControlClient.PLAYSTATE_PLAYING:
- return "PLAYSTATE_PLAYING";
- case RemoteControlClient.PLAYSTATE_FAST_FORWARDING:
- return "PLAYSTATE_FAST_FORWARDING";
- case RemoteControlClient.PLAYSTATE_REWINDING:
- return "PLAYSTATE_REWINDING";
- case RemoteControlClient.PLAYSTATE_SKIPPING_FORWARDS:
- return "PLAYSTATE_SKIPPING_FORWARDS";
- case RemoteControlClient.PLAYSTATE_SKIPPING_BACKWARDS:
- return "PLAYSTATE_SKIPPING_BACKWARDS";
- case RemoteControlClient.PLAYSTATE_BUFFERING:
- return "PLAYSTATE_BUFFERING";
- case RemoteControlClient.PLAYSTATE_ERROR:
- return "PLAYSTATE_ERROR";
- default:
- return "[invalid playstate]";
- }
- }
- }
-
-
- /**
- * Inner class to monitor remote control client deaths, and remove the client for the
- * remote control stack if necessary.
- */
- private class RcClientDeathHandler implements IBinder.DeathRecipient {
- final private IBinder mCb; // To be notified of client's death
- //FIXME needed?
- final private PendingIntent mMediaIntent;
-
- RcClientDeathHandler(IBinder cb, PendingIntent pi) {
- mCb = cb;
- mMediaIntent = pi;
- }
-
- public void binderDied() {
- Log.w(TAG, " RemoteControlClient died");
- // remote control client died, make sure the displays don't use it anymore
- // by setting its remote control client to null
- sController.registerRemoteControlClient(mMediaIntent, null/*rcClient*/, null/*ignored*/);
- // the dead client was maybe handling remote playback, the controller should reevaluate
- sController.postReevaluateRemote();
- }
-
- public IBinder getBinder() {
- return mCb;
- }
- }
-
-
- protected static class RemotePlaybackState {
- int mRccId;
- int mVolume;
- int mVolumeMax;
- int mVolumeHandling;
-
- protected RemotePlaybackState(int id, int vol, int volMax) {
- mRccId = id;
- mVolume = vol;
- mVolumeMax = volMax;
- mVolumeHandling = RemoteControlClient.DEFAULT_PLAYBACK_VOLUME_HANDLING;
- }
- }
-
-
- void dump(PrintWriter pw, boolean registrationInfo) {
- if (registrationInfo) {
- pw.println(" pi: " + mMediaIntent +
- " -- pack: " + mCallingPackageName +
- " -- ercvr: " + mReceiverComponent +
- " -- client: " + mRcClient +
- " -- uid: " + mCallingUid +
- " -- type: " + mPlaybackType +
- " state: " + mPlaybackState);
- } else {
- // emphasis on state
- pw.println(" uid: " + mCallingUid +
- " -- id: " + mRccId +
- " -- type: " + mPlaybackType +
- " -- state: " + mPlaybackState +
- " -- vol handling: " + mPlaybackVolumeHandling +
- " -- vol: " + mPlaybackVolume +
- " -- volMax: " + mPlaybackVolumeMax +
- " -- volObs: " + mRemoteVolumeObs);
- }
- }
-
-
- static protected void setMediaFocusControl(MediaFocusControl mfc) {
- sController = mfc;
- }
-
- /** precondition: mediaIntent != null */
- protected PlayerRecord(PendingIntent mediaIntent, ComponentName eventReceiver, IBinder token)
- {
- mMediaIntent = mediaIntent;
- mReceiverComponent = eventReceiver;
- mToken = token;
- mCallingUid = -1;
- mRcClient = null;
- mRccId = ++sLastRccId;
- mPlaybackState = new RccPlaybackState(
- RemoteControlClient.PLAYSTATE_STOPPED,
- RemoteControlClient.PLAYBACK_POSITION_INVALID,
- RemoteControlClient.PLAYBACK_SPEED_1X);
-
- resetPlaybackInfo();
- if (mToken != null) {
- try {
- mToken.linkToDeath(this, 0);
- } catch (RemoteException e) {
- sController.unregisterMediaButtonIntentAsync(mMediaIntent);
- }
- }
- }
-
- //---------------------------------------------
- // Accessors
- protected int getRccId() {
- return mRccId;
- }
-
- protected IRemoteControlClient getRcc() {
- return mRcClient;
- }
-
- protected ComponentName getMediaButtonReceiver() {
- return mReceiverComponent;
- }
-
- protected PendingIntent getMediaButtonIntent() {
- return mMediaIntent;
- }
-
- protected boolean hasMatchingMediaButtonIntent(PendingIntent pi) {
- if (mToken != null) {
- return mMediaIntent.equals(pi);
- } else {
- if (mReceiverComponent != null) {
- return mReceiverComponent.equals(pi.getIntent().getComponent());
- } else {
- return false;
- }
- }
- }
-
- protected boolean isPlaybackActive() {
- return MediaFocusControl.isPlaystateActive(mPlaybackState.mState);
- }
-
- //---------------------------------------------
- // Modify the records stored in the instance
- protected void resetControllerInfoForRcc(IRemoteControlClient rcClient,
- String callingPackageName, int uid) {
- // already had a remote control client?
- if (mRcClientDeathHandler != null) {
- // stop monitoring the old client's death
- unlinkToRcClientDeath();
- }
- // save the new remote control client
- mRcClient = rcClient;
- mCallingPackageName = callingPackageName;
- mCallingUid = uid;
- if (rcClient == null) {
- // here mcse.mRcClientDeathHandler is null;
- resetPlaybackInfo();
- } else {
- IBinder b = mRcClient.asBinder();
- RcClientDeathHandler rcdh =
- new RcClientDeathHandler(b, mMediaIntent);
- try {
- b.linkToDeath(rcdh, 0);
- } catch (RemoteException e) {
- // remote control client is DOA, disqualify it
- Log.w(TAG, "registerRemoteControlClient() has a dead client " + b);
- mRcClient = null;
- }
- mRcClientDeathHandler = rcdh;
- }
- }
-
- protected void resetControllerInfoForNoRcc() {
- // stop monitoring the RCC death
- unlinkToRcClientDeath();
- // reset the RCC-related fields
- mRcClient = null;
- mCallingPackageName = null;
- }
-
- public void resetPlaybackInfo() {
- mPlaybackType = RemoteControlClient.PLAYBACK_TYPE_LOCAL;
- mPlaybackVolume = RemoteControlClient.DEFAULT_PLAYBACK_VOLUME;
- mPlaybackVolumeMax = RemoteControlClient.DEFAULT_PLAYBACK_VOLUME;
- mPlaybackVolumeHandling = RemoteControlClient.DEFAULT_PLAYBACK_VOLUME_HANDLING;
- mPlaybackStream = AudioManager.STREAM_MUSIC;
- mPlaybackState.reset();
- mRemoteVolumeObs = null;
- }
-
- //---------------------------------------------
- public void unlinkToRcClientDeath() {
- if ((mRcClientDeathHandler != null) && (mRcClientDeathHandler.mCb != null)) {
- try {
- mRcClientDeathHandler.mCb.unlinkToDeath(mRcClientDeathHandler, 0);
- mRcClientDeathHandler = null;
- } catch (java.util.NoSuchElementException e) {
- // not much we can do here
- Log.e(TAG, "Error in unlinkToRcClientDeath()", e);
- }
- }
- }
-
- // FIXME rename to "release"? (as in FocusRequester class)
- public void destroy() {
- unlinkToRcClientDeath();
- if (mToken != null) {
- mToken.unlinkToDeath(this, 0);
- mToken = null;
- }
- }
-
- @Override
- public void binderDied() {
- sController.unregisterMediaButtonIntentAsync(mMediaIntent);
- }
-
- @Override
- protected void finalize() throws Throwable {
- destroy(); // unlink exception handled inside method
- super.finalize();
- }
-}