diff options
author | John Spurlock <jspurlock@google.com> | 2015-02-06 19:46:04 -0500 |
---|---|---|
committer | John Spurlock <jspurlock@google.com> | 2015-02-09 17:13:38 -0500 |
commit | 6156017c2217d0fbbbb03434986250ec6bbd69d8 (patch) | |
tree | 2bf29bdfad945edeeb161b9ef9bb6b8108093042 /media/java | |
parent | 7d85bc4c435bc37284a814fb8baf53980396ab5b (diff) | |
download | frameworks_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/java')
-rw-r--r-- | media/java/android/media/AudioFocusInfo.java | 5 | ||||
-rw-r--r-- | media/java/android/media/AudioManager.java | 7 | ||||
-rw-r--r-- | media/java/android/media/AudioRoutesInfo.java | 28 | ||||
-rw-r--r-- | media/java/android/media/AudioService.java | 5989 | ||||
-rw-r--r-- | media/java/android/media/AudioSystem.java | 104 | ||||
-rw-r--r-- | media/java/android/media/FocusRequester.java | 328 | ||||
-rw-r--r-- | media/java/android/media/MediaFocusControl.java | 2197 | ||||
-rw-r--r-- | media/java/android/media/MediaRouter.java | 24 | ||||
-rw-r--r-- | media/java/android/media/PlayerRecord.java | 357 |
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(); - } -} |