Merge "Add support for audio focus locking" into lmp-mr1-dev
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index 645681a..3f08305 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -17,6 +17,7 @@
package android.media;
import android.Manifest;
+import android.annotation.NonNull;
import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
import android.annotation.SystemApi;
@@ -2318,14 +2319,25 @@
return status;
}
- // when adding new flags, add them to AUDIOFOCUS_FLAGS_ALL
+ // when adding new flags, add them to the relevant AUDIOFOCUS_FLAGS_APPS or SYSTEM masks
/** @hide */
+ @SystemApi
public static final int AUDIOFOCUS_FLAG_DELAY_OK = 0x1 << 0;
/** @hide */
- public static final int AUDIOFOCUS_FLAGS_ALL = AUDIOFOCUS_FLAG_DELAY_OK;
+ @SystemApi
+ public static final int AUDIOFOCUS_FLAG_LOCK = 0x1 << 1;
+ /** @hide */
+ public static final int AUDIOFOCUS_FLAGS_APPS = AUDIOFOCUS_FLAG_DELAY_OK;
+ /** @hide */
+ public static final int AUDIOFOCUS_FLAGS_SYSTEM = AUDIOFOCUS_FLAG_DELAY_OK
+ | AUDIOFOCUS_FLAG_LOCK;
/**
* @hide
+ * Request audio focus.
+ * Send a request to obtain the audio focus. This method differs from
+ * {@link #requestAudioFocus(OnAudioFocusChangeListener, int, int)} in that it can express
+ * that the requester accepts delayed grants of audio focus.
* @param l the listener to be notified of audio focus changes. It is not allowed to be null
* when the request is flagged with {@link #AUDIOFOCUS_FLAG_DELAY_OK}.
* @param requestAttributes non null {@link AudioAttributes} describing the main reason for
@@ -2340,24 +2352,70 @@
* usecases such as voice memo recording, or speech recognition.
* Use {@link #AUDIOFOCUS_GAIN} for a focus request of unknown duration such
* as the playback of a song or a video.
- * @param flags use 0 when not using any flags for the request, which behaves like
- * {@link #requestAudioFocus(OnAudioFocusChangeListener, int, int)}, where either audio
- * focus is granted immediately, or the grant request fails because the system is in a
- * state where focus cannot change (e.g. a phone call).
- * Use {link #AUDIOFOCUS_FLAG_DELAY_OK} if it is ok for the requester to not be granted
- * audio focus immediately (as indicated by {@link #AUDIOFOCUS_REQUEST_DELAYED}) when
- * the system is in a state where focus cannot change, but be granted focus later when
- * this condition ends.
+ * @param flags 0 or {link #AUDIOFOCUS_FLAG_DELAY_OK}.
+ * <br>Use 0 when not using any flags for the request, which behaves like
+ * {@link #requestAudioFocus(OnAudioFocusChangeListener, int, int)}, where either audio
+ * focus is granted immediately, or the grant request fails because the system is in a
+ * state where focus cannot change (e.g. a phone call).
+ * <br>Use {link #AUDIOFOCUS_FLAG_DELAY_OK} if it is ok for the requester to not be granted
+ * audio focus immediately (as indicated by {@link #AUDIOFOCUS_REQUEST_DELAYED}) when
+ * the system is in a state where focus cannot change, but be granted focus later when
+ * this condition ends.
* @return {@link #AUDIOFOCUS_REQUEST_FAILED}, {@link #AUDIOFOCUS_REQUEST_GRANTED}
* or {@link #AUDIOFOCUS_REQUEST_DELAYED}.
* The return value is never {@link #AUDIOFOCUS_REQUEST_DELAYED} when focus is requested
* without the {@link #AUDIOFOCUS_FLAG_DELAY_OK} flag.
* @throws IllegalArgumentException
*/
+ @SystemApi
public int requestAudioFocus(OnAudioFocusChangeListener l,
- AudioAttributes requestAttributes,
+ @NonNull AudioAttributes requestAttributes,
int durationHint,
int flags) throws IllegalArgumentException {
+ if (flags != (flags & AUDIOFOCUS_FLAGS_APPS)) {
+ throw new IllegalArgumentException("Invalid flags 0x"
+ + Integer.toHexString(flags).toUpperCase());
+ }
+ return requestAudioFocus(l, requestAttributes, durationHint,
+ flags & AUDIOFOCUS_FLAGS_APPS,
+ null /* no AudioPolicy*/);
+ }
+
+ /**
+ * @hide
+ * Request or lock audio focus.
+ * This method is to be used by system components that have registered an
+ * {@link android.media.audiopolicy.AudioPolicy} to request audio focus, but also to "lock" it
+ * so focus granting is temporarily disabled.
+ * @param l see the description of the same parameter in
+ * {@link #requestAudioFocus(OnAudioFocusChangeListener, AudioAttributes, int, int)}
+ * @param requestAttributes non null {@link AudioAttributes} describing the main reason for
+ * requesting audio focus.
+ * @param durationHint see the description of the same parameter in
+ * {@link #requestAudioFocus(OnAudioFocusChangeListener, AudioAttributes, int, int)}
+ * @param flags 0 or a combination of {link #AUDIOFOCUS_FLAG_DELAY_OK},
+ * {@link #AUDIOFOCUS_FLAG_LOCK}
+ * <br>Use 0 when not using any flags for the request, which behaves like
+ * {@link #requestAudioFocus(OnAudioFocusChangeListener, int, int)}, where either audio
+ * focus is granted immediately, or the grant request fails because the system is in a
+ * state where focus cannot change (e.g. a phone call).
+ * <br>Use {link #AUDIOFOCUS_FLAG_DELAY_OK} if it is ok for the requester to not be granted
+ * audio focus immediately (as indicated by {@link #AUDIOFOCUS_REQUEST_DELAYED}) when
+ * the system is in a state where focus cannot change, but be granted focus later when
+ * this condition ends.
+ * <br>Use {@link #AUDIOFOCUS_FLAG_LOCK} when locking audio focus so granting is
+ * temporarily disabled.
+ * @param ap a registered {@link android.media.audiopolicy.AudioPolicy} instance when locking
+ * focus, or null.
+ * @return see the description of the same return value in
+ * {@link #requestAudioFocus(OnAudioFocusChangeListener, AudioAttributes, int, int)}
+ * @throws IllegalArgumentException
+ */
+ public int requestAudioFocus(OnAudioFocusChangeListener l,
+ @NonNull AudioAttributes requestAttributes,
+ int durationHint,
+ int flags,
+ AudioPolicy ap) throws IllegalArgumentException {
// parameter checking
if (requestAttributes == null) {
throw new IllegalArgumentException("Illegal null AudioAttributes argument");
@@ -2366,7 +2424,7 @@
(durationHint > AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE)) {
throw new IllegalArgumentException("Invalid duration hint");
}
- if (flags != (flags & AUDIOFOCUS_FLAGS_ALL)) {
+ if (flags != (flags & AUDIOFOCUS_FLAGS_SYSTEM)) {
throw new IllegalArgumentException("Illegal flags 0x"
+ Integer.toHexString(flags).toUpperCase());
}
@@ -2374,6 +2432,10 @@
throw new IllegalArgumentException(
"Illegal null focus listener when flagged as accepting delayed focus grant");
}
+ if (((flags & AUDIOFOCUS_FLAG_LOCK) == AUDIOFOCUS_FLAG_LOCK) && (ap == null)) {
+ throw new IllegalArgumentException(
+ "Illegal null audio policy when locking audio focus");
+ }
int status = AUDIOFOCUS_REQUEST_FAILED;
registerAudioFocusListener(l);
@@ -2381,9 +2443,10 @@
try {
status = service.requestAudioFocus(requestAttributes, durationHint, mICallBack,
mAudioFocusDispatcher, getIdForAudioFocusListener(l),
- mContext.getOpPackageName() /* package name */, flags);
+ mContext.getOpPackageName() /* package name */, flags,
+ ap != null ? ap.token() : null);
} catch (RemoteException e) {
- Log.e(TAG, "Can't call requestAudioFocus() on AudioService due to "+e);
+ Log.e(TAG, "Can't call requestAudioFocus() on AudioService:", e);
}
return status;
}
@@ -2405,9 +2468,11 @@
.setInternalLegacyStreamType(streamType).build(),
durationHint, mICallBack, null,
MediaFocusControl.IN_VOICE_COMM_FOCUS_ID,
- mContext.getOpPackageName(), 0 /* flags, legacy behavior*/ );
+ mContext.getOpPackageName(),
+ AUDIOFOCUS_FLAG_LOCK,
+ null /* policy token */);
} catch (RemoteException e) {
- Log.e(TAG, "Can't call requestAudioFocusForCall() on AudioService due to "+e);
+ Log.e(TAG, "Can't call requestAudioFocusForCall() on AudioService:", e);
}
}
@@ -2420,9 +2485,10 @@
public void abandonAudioFocusForCall() {
IAudioService service = getService();
try {
- service.abandonAudioFocus(null, MediaFocusControl.IN_VOICE_COMM_FOCUS_ID);
+ service.abandonAudioFocus(null, MediaFocusControl.IN_VOICE_COMM_FOCUS_ID,
+ null /*AudioAttributes, legacy behavior*/);
} catch (RemoteException e) {
- Log.e(TAG, "Can't call abandonAudioFocusForCall() on AudioService due to "+e);
+ Log.e(TAG, "Can't call abandonAudioFocusForCall() on AudioService:", e);
}
}
@@ -2432,19 +2498,30 @@
* @return {@link #AUDIOFOCUS_REQUEST_FAILED} or {@link #AUDIOFOCUS_REQUEST_GRANTED}
*/
public int abandonAudioFocus(OnAudioFocusChangeListener l) {
+ return abandonAudioFocus(l, null /*AudioAttributes, legacy behavior*/);
+ }
+
+ /**
+ * @hide
+ * Abandon audio focus. Causes the previous focus owner, if any, to receive focus.
+ * @param l the listener with which focus was requested.
+ * @param aa the {@link AudioAttributes} with which audio focus was requested
+ * @return {@link #AUDIOFOCUS_REQUEST_FAILED} or {@link #AUDIOFOCUS_REQUEST_GRANTED}
+ */
+ @SystemApi
+ public int abandonAudioFocus(OnAudioFocusChangeListener l, AudioAttributes aa) {
int status = AUDIOFOCUS_REQUEST_FAILED;
unregisterAudioFocusListener(l);
IAudioService service = getService();
try {
status = service.abandonAudioFocus(mAudioFocusDispatcher,
- getIdForAudioFocusListener(l));
+ getIdForAudioFocusListener(l), aa);
} catch (RemoteException e) {
- Log.e(TAG, "Can't call abandonAudioFocus() on AudioService due to "+e);
+ Log.e(TAG, "Can't call abandonAudioFocus() on AudioService:", e);
}
return status;
}
-
//====================================================================
// Remote Control
/**
diff --git a/media/java/android/media/AudioService.java b/media/java/android/media/AudioService.java
index c70ac55..b6b24a4 100644
--- a/media/java/android/media/AudioService.java
+++ b/media/java/android/media/AudioService.java
@@ -49,6 +49,7 @@
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.os.Binder;
import android.os.Build;
@@ -5018,13 +5019,34 @@
// Audio Focus
//==========================================================================================
public int requestAudioFocus(AudioAttributes aa, int durationHint, IBinder cb,
- IAudioFocusDispatcher fd, String clientId, String callingPackageName, int flags) {
+ IAudioFocusDispatcher fd, String clientId, String callingPackageName, int flags,
+ IBinder policyToken) {
+ // 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(policyToken)) {
+ Log.e(TAG, "Invalid unregistered AudioPolicy to (un)lock audio focus",
+ new Exception());
+ return AudioManager.AUDIOFOCUS_REQUEST_FAILED;
+ }
+ }
+ }
+ }
+
return mMediaFocusControl.requestAudioFocus(aa, durationHint, cb, fd,
clientId, callingPackageName, flags);
}
- public int abandonAudioFocus(IAudioFocusDispatcher fd, String clientId) {
- return mMediaFocusControl.abandonAudioFocus(fd, clientId);
+ public int abandonAudioFocus(IAudioFocusDispatcher fd, String clientId, AudioAttributes aa) {
+ return mMediaFocusControl.abandonAudioFocus(fd, clientId, aa);
}
public void unregisterAudioFocusClient(String clientId) {
@@ -5725,6 +5747,9 @@
// TODO implement clearing mix attribute matching info in native audio policy
}
+ //======================
+ // Audio policy proxy
+ //======================
/**
* This internal class inherits from AudioPolicyConfig which contains all the mixes and
* their configurations.
@@ -5742,8 +5767,8 @@
public void binderDied() {
synchronized (mAudioPolicies) {
Log.i(TAG, "audio policy " + mToken + " died");
- mAudioPolicies.remove(mToken);
disconnectMixes();
+ mAudioPolicies.remove(mToken);
}
}
diff --git a/media/java/android/media/FocusRequester.java b/media/java/android/media/FocusRequester.java
index 682d54c..0d675fc 100644
--- a/media/java/android/media/FocusRequester.java
+++ b/media/java/android/media/FocusRequester.java
@@ -83,6 +83,10 @@
}
}
+ boolean isLockedFocusOwner() {
+ return ((mGrantFlags & AudioManager.AUDIOFOCUS_FLAG_LOCK) != 0);
+ }
+
boolean hasSameBinder(IBinder ib) {
return (mSourceRef != null) && mSourceRef.equals(ib);
}
@@ -99,6 +103,9 @@
return mCallingUid == uid;
}
+ String getClientId() {
+ return mClientId;
+ }
int getGainRequest() {
return mFocusGainRequest;
@@ -144,12 +151,20 @@
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 (!msg.isEmpty()) { msg += "|"; }
+ if ((flags & AudioManager.AUDIOFOCUS_FLAG_LOCK) != 0) { msg += "LOCK"; }
+ return msg;
+ }
+
void dump(PrintWriter pw) {
pw.println(" source:" + mSourceRef
+ " -- pack: " + mPackageName
+ " -- client: " + mClientId
+ " -- gain: " + focusGainToString()
- + " -- grant: " + mGrantFlags
+ + " -- flags: " + flagsToString(mGrantFlags)
+ " -- loss: " + focusLossToString()
+ " -- uid: " + mCallingUid
+ " -- attr: " + mAttributes);
diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl
index 47a5291..b691447 100644
--- a/media/java/android/media/IAudioService.aidl
+++ b/media/java/android/media/IAudioService.aidl
@@ -118,9 +118,10 @@
boolean isBluetoothA2dpOn();
int requestAudioFocus(in AudioAttributes aa, int durationHint, IBinder cb,
- IAudioFocusDispatcher fd, String clientId, String callingPackageName, int flags);
+ IAudioFocusDispatcher fd, String clientId, String callingPackageName, int flags,
+ IBinder policyToken);
- int abandonAudioFocus(IAudioFocusDispatcher fd, String clientId);
+ int abandonAudioFocus(IAudioFocusDispatcher fd, String clientId, in AudioAttributes aa);
void unregisterAudioFocusClient(String clientId);
diff --git a/media/java/android/media/MediaFocusControl.java b/media/java/android/media/MediaFocusControl.java
index c495106..8d96970 100644
--- a/media/java/android/media/MediaFocusControl.java
+++ b/media/java/android/media/MediaFocusControl.java
@@ -390,7 +390,8 @@
// AudioFocus
//==========================================================================================
- /* constant to identify focus stack entry that is used to hold the focus while the phone
+ /**
+ * 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.
*/
@@ -539,40 +540,40 @@
* 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 "exclusive" focus owner is at the top of the focus stack.
+ * 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() && isExclusiveFocusOwner(mFocusStack.peek())) {
+ if (!mFocusStack.isEmpty() && isLockedFocusOwner(mFocusStack.peek())) {
return false;
}
return true;
}
- private boolean isExclusiveFocusOwner(FocusRequester fr) {
- return fr.hasSameClient(IN_VOICE_COMM_FOCUS_ID);
+ 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 exclusive focus owner
+ * 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 exclusive focus owners.
+ * following the locked focus owners.
* @return {@link AudioManager#AUDIOFOCUS_REQUEST_GRANTED} or
* {@link AudioManager#AUDIOFOCUS_REQUEST_DELAYED}
*/
- private int pushBelowExclusiveFocusOwners(FocusRequester nfr) {
- int lastExclusiveFocusOwnerIndex = mFocusStack.size();
+ private int pushBelowLockedFocusOwners(FocusRequester nfr) {
+ int lastLockedFocusOwnerIndex = mFocusStack.size();
for (int index = mFocusStack.size()-1; index >= 0; index--) {
- if (isExclusiveFocusOwner(mFocusStack.elementAt(index))) {
- lastExclusiveFocusOwnerIndex = index;
+ if (isLockedFocusOwner(mFocusStack.elementAt(index))) {
+ lastLockedFocusOwnerIndex = index;
}
}
- if (lastExclusiveFocusOwnerIndex == mFocusStack.size()) {
+ 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());
@@ -581,7 +582,7 @@
mFocusStack.push(nfr);
return AudioManager.AUDIOFOCUS_REQUEST_GRANTED;
} else {
- mFocusStack.insertElementAt(nfr, lastExclusiveFocusOwnerIndex);
+ mFocusStack.insertElementAt(nfr, lastLockedFocusOwnerIndex);
return AudioManager.AUDIOFOCUS_REQUEST_DELAYED;
}
}
@@ -687,7 +688,7 @@
if (focusGrantDelayed) {
// focusGrantDelayed being true implies we can't reassign focus right now
// which implies the focus stack is not empty.
- return pushBelowExclusiveFocusOwners(nfr);
+ return pushBelowLockedFocusOwners(nfr);
} else {
// propagate the focus change through the stack
if (!mFocusStack.empty()) {
@@ -703,8 +704,11 @@
return AudioManager.AUDIOFOCUS_REQUEST_GRANTED;
}
- /** @see AudioManager#abandonAudioFocus(AudioManager.OnAudioFocusChangeListener) */
- protected int abandonAudioFocus(IAudioFocusDispatcher fl, String clientId) {
+ /**
+ * @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