summaryrefslogtreecommitdiffstats
path: root/media
diff options
context:
space:
mode:
authorJean-Michel Trivi <jmtrivi@google.com>2014-11-24 14:43:10 -0800
committerJean-Michel Trivi <jmtrivi@google.com>2014-12-05 18:41:28 -0800
commit0212be5150fb9fb3c340f3c7e51f6126372cc6f9 (patch)
tree929f98806b4edb3591c5398881252fcd5c81b497 /media
parent7792b714b66e3af57bc243666b7843b0adf9c0b3 (diff)
downloadframeworks_base-0212be5150fb9fb3c340f3c7e51f6126372cc6f9.zip
frameworks_base-0212be5150fb9fb3c340f3c7e51f6126372cc6f9.tar.gz
frameworks_base-0212be5150fb9fb3c340f3c7e51f6126372cc6f9.tar.bz2
Support collaborative audio focus handling
Add new flag for an app to define it doesn't duck, but rather pauses when losing focus with AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK. This flag is to be used when requesting focus. Add support for AudioPolicy to specify whether it will implement ducking itself, rather than it being handled by an app. When ducking is handled by a policy, do not notify focus owners when they lose audio focus with LOSS_TRANSIENT_CAN_DUCK, unless they would have paused, as expressed with the AUDIOFOCUS_FLAG_PAUSES_ON_DUCKABLE_LOSS flag. Add a focus listener for a policy to be notified of focus changes so it can properly implement its own ducking. Bug 16010554 Change-Id: I11d7cdb85c52fd086128a44f4d938aaa44db5c25
Diffstat (limited to 'media')
-rw-r--r--media/java/android/media/AudioFocusInfo.aidl18
-rw-r--r--media/java/android/media/AudioFocusInfo.java175
-rw-r--r--media/java/android/media/AudioManager.java57
-rw-r--r--media/java/android/media/AudioService.java114
-rw-r--r--media/java/android/media/FocusRequester.java74
-rw-r--r--media/java/android/media/IAudioService.aidl10
-rw-r--r--media/java/android/media/MediaFocusControl.java111
-rw-r--r--media/java/android/media/audiopolicy/AudioPolicy.java238
-rw-r--r--media/java/android/media/audiopolicy/AudioPolicyConfig.java1
-rw-r--r--media/java/android/media/audiopolicy/IAudioPolicyCallback.aidl28
10 files changed, 731 insertions, 95 deletions
diff --git a/media/java/android/media/AudioFocusInfo.aidl b/media/java/android/media/AudioFocusInfo.aidl
new file mode 100644
index 0000000..f925fda
--- /dev/null
+++ b/media/java/android/media/AudioFocusInfo.aidl
@@ -0,0 +1,18 @@
+/* Copyright 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;
+
+parcelable AudioFocusInfo;
diff --git a/media/java/android/media/AudioFocusInfo.java b/media/java/android/media/AudioFocusInfo.java
new file mode 100644
index 0000000..fbdda3c
--- /dev/null
+++ b/media/java/android/media/AudioFocusInfo.java
@@ -0,0 +1,175 @@
+/*
+ * 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.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Objects;
+
+/**
+ * @hide
+ * A class to encapsulate information about an audio focus owner or request.
+ */
+@SystemApi
+public final class AudioFocusInfo implements Parcelable {
+
+ private AudioAttributes mAttributes;
+ private String mClientId;
+ private String mPackageName;
+ private int mGainRequest;
+ private int mLossReceived;
+ private int mFlags;
+
+
+ /**
+ * Class constructor
+ * @param aa
+ * @param clientId
+ * @param packageName
+ * @param gainRequest
+ * @param lossReceived
+ * @param flags
+ */
+ 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;
+ mPackageName = packageName == null ? "" : packageName;
+ mGainRequest = gainRequest;
+ mLossReceived = lossReceived;
+ mFlags = flags;
+ }
+
+
+ /**
+ * The audio attributes for the audio focus request.
+ * @return non-null {@link AudioAttributes}.
+ */
+ @SystemApi
+ public AudioAttributes getAttributes() { return mAttributes; }
+
+ @SystemApi
+ public String getClientId() { return mClientId; }
+
+ @SystemApi
+ public String getPackageName() { return mPackageName; }
+
+ /**
+ * The type of audio focus gain request.
+ * @return one of {@link AudioManager#AUDIOFOCUS_GAIN},
+ * {@link AudioManager#AUDIOFOCUS_GAIN_TRANSIENT},
+ * {@link AudioManager#AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK},
+ * {@link AudioManager#AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE}.
+ */
+ @SystemApi
+ public int getGainRequest() { return mGainRequest; }
+
+ /**
+ * The type of audio focus loss that was received by the
+ * {@link AudioManager.OnAudioFocusChangeListener} if one was set.
+ * @return 0 if focus wasn't lost, or one of {@link AudioManager#AUDIOFOCUS_LOSS},
+ * {@link AudioManager#AUDIOFOCUS_LOSS_TRANSIENT} or
+ * {@link AudioManager#AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK}.
+ */
+ @SystemApi
+ public int getLossReceived() { return mLossReceived; }
+
+ /** @hide */
+ void clearLossReceived() { mLossReceived = 0; }
+
+ /**
+ * The flags set in the audio focus request.
+ * @return 0 or a combination of {link AudioManager#AUDIOFOCUS_FLAG_DELAY_OK},
+ * {@link AudioManager#AUDIOFOCUS_FLAG_PAUSES_ON_DUCKABLE_LOSS}, and
+ * {@link AudioManager#AUDIOFOCUS_FLAG_LOCK}.
+ */
+ @SystemApi
+ public int getFlags() { return mFlags; }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ mAttributes.writeToParcel(dest, flags);
+ dest.writeString(mClientId);
+ dest.writeString(mPackageName);
+ dest.writeInt(mGainRequest);
+ dest.writeInt(mLossReceived);
+ dest.writeInt(mFlags);
+ }
+
+ @SystemApi
+ @Override
+ public int hashCode() {
+ return Objects.hash(mAttributes, mClientId, mPackageName, mGainRequest, mFlags);
+ }
+
+ @SystemApi
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (obj == null)
+ return false;
+ if (getClass() != obj.getClass())
+ return false;
+ AudioFocusInfo other = (AudioFocusInfo) obj;
+ if (!mAttributes.equals(other.mAttributes)) {
+ return false;
+ }
+ if (!mClientId.equals(other.mClientId)) {
+ return false;
+ }
+ if (!mPackageName.equals(other.mPackageName)) {
+ return false;
+ }
+ if (mGainRequest != other.mGainRequest) {
+ return false;
+ }
+ if (mLossReceived != other.mLossReceived) {
+ return false;
+ }
+ if (mFlags != other.mFlags) {
+ return false;
+ }
+ return true;
+ }
+
+ public static final Parcelable.Creator<AudioFocusInfo> CREATOR
+ = new Parcelable.Creator<AudioFocusInfo>() {
+
+ public AudioFocusInfo createFromParcel(Parcel in) {
+ return new AudioFocusInfo(
+ AudioAttributes.CREATOR.createFromParcel(in), //AudioAttributes aa
+ in.readString(), //String clientId
+ in.readString(), //String packageName
+ in.readInt(), //int gainRequest
+ in.readInt(), //int lossReceived
+ in.readInt() //int flags
+ );
+ }
+
+ public AudioFocusInfo[] newArray(int size) {
+ return new AudioFocusInfo[size];
+ }
+ };
+}
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index 543836b..a65aa72 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -2368,17 +2368,42 @@ public class AudioManager {
}
// when adding new flags, add them to the relevant AUDIOFOCUS_FLAGS_APPS or SYSTEM masks
- /** @hide */
+ /**
+ * @hide
+ * Use this flag when requesting audio focus to indicate 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.
+ */
@SystemApi
public static final int AUDIOFOCUS_FLAG_DELAY_OK = 0x1 << 0;
- /** @hide */
+ /**
+ * @hide
+ * Use this flag when requesting audio focus to indicate that the requester
+ * will pause its media playback (if applicable) when losing audio focus with
+ * {@link #AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK}, rather than ducking.
+ * <br>On some platforms, the ducking may be handled without the application being aware of it
+ * (i.e. it will not transiently lose focus). For applications that for instance play spoken
+ * content, such as audio book or podcast players, ducking may never be acceptable, and will
+ * thus always pause. This flag enables them to be declared as such whenever they request focus.
+ */
+ @SystemApi
+ public static final int AUDIOFOCUS_FLAG_PAUSES_ON_DUCKABLE_LOSS = 0x1 << 1;
+ /**
+ * @hide
+ * Use this flag to lock audio focus so granting is temporarily disabled.
+ * <br>This flag can only be used by owners of a registered
+ * {@link android.media.audiopolicy.AudioPolicy} in
+ * {@link #requestAudioFocus(OnAudioFocusChangeListener, AudioAttributes, int, int, AudioPolicy)}
+ */
@SystemApi
- public static final int AUDIOFOCUS_FLAG_LOCK = 0x1 << 1;
+ public static final int AUDIOFOCUS_FLAG_LOCK = 0x1 << 2;
/** @hide */
- public static final int AUDIOFOCUS_FLAGS_APPS = AUDIOFOCUS_FLAG_DELAY_OK;
+ public static final int AUDIOFOCUS_FLAGS_APPS = AUDIOFOCUS_FLAG_DELAY_OK
+ | AUDIOFOCUS_FLAG_PAUSES_ON_DUCKABLE_LOSS;
/** @hide */
public static final int AUDIOFOCUS_FLAGS_SYSTEM = AUDIOFOCUS_FLAG_DELAY_OK
- | AUDIOFOCUS_FLAG_LOCK;
+ | AUDIOFOCUS_FLAG_PAUSES_ON_DUCKABLE_LOSS | AUDIOFOCUS_FLAG_LOCK;
/**
* @hide
@@ -2400,15 +2425,12 @@ public class AudioManager {
* 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 0 or {link #AUDIOFOCUS_FLAG_DELAY_OK}.
+ * @param flags 0 or a combination of {link #AUDIOFOCUS_FLAG_DELAY_OK}
+ * and {@link #AUDIOFOCUS_FLAG_PAUSES_ON_DUCKABLE_LOSS}.
* <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
@@ -2442,17 +2464,11 @@ public class AudioManager {
* @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}
+ * {@link #AUDIOFOCUS_FLAG_PAUSES_ON_DUCKABLE_LOSS}, and {@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
@@ -2493,7 +2509,7 @@ public class AudioManager {
status = service.requestAudioFocus(requestAttributes, durationHint, mICallBack,
mAudioFocusDispatcher, getIdForAudioFocusListener(l),
mContext.getOpPackageName() /* package name */, flags,
- ap != null ? ap.token() : null);
+ ap != null ? ap.cb() : null);
} catch (RemoteException e) {
Log.e(TAG, "Can't call requestAudioFocus() on AudioService:", e);
}
@@ -2870,7 +2886,8 @@ public class AudioManager {
}
IAudioService service = getService();
try {
- String regId = service.registerAudioPolicy(policy.getConfig(), policy.token());
+ String regId = service.registerAudioPolicy(policy.getConfig(), policy.cb(),
+ policy.hasFocusListener());
if (regId == null) {
return ERROR;
} else {
@@ -2895,7 +2912,7 @@ public class AudioManager {
}
IAudioService service = getService();
try {
- service.unregisterAudioPolicyAsync(policy.token());
+ service.unregisterAudioPolicyAsync(policy.cb());
policy.setRegistration(null);
} catch (RemoteException e) {
Log.e(TAG, "Dead object in unregisterAudioPolicyAsync()", e);
diff --git a/media/java/android/media/AudioService.java b/media/java/android/media/AudioService.java
index 9a3ec42..8d664eb 100644
--- a/media/java/android/media/AudioService.java
+++ b/media/java/android/media/AudioService.java
@@ -51,6 +51,7 @@ 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;
@@ -5078,7 +5079,7 @@ public class AudioService extends IAudioService.Stub {
//==========================================================================================
public int requestAudioFocus(AudioAttributes aa, int durationHint, IBinder cb,
IAudioFocusDispatcher fd, String clientId, String callingPackageName, int flags,
- IBinder policyToken) {
+ IAudioPolicyCallback pcb) {
// permission checks
if ((flags & AudioManager.AUDIOFOCUS_FLAG_LOCK) == AudioManager.AUDIOFOCUS_FLAG_LOCK) {
if (mMediaFocusControl.IN_VOICE_COMM_FOCUS_ID.equals(clientId)) {
@@ -5090,9 +5091,8 @@ public class AudioService extends IAudioService.Stub {
} 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());
+ if (!mAudioPolicies.containsKey(pcb.asBinder())) {
+ Log.e(TAG, "Invalid unregistered AudioPolicy to (un)lock audio focus");
return AudioManager.AUDIOFOCUS_REQUEST_FAILED;
}
}
@@ -5786,30 +5786,34 @@ public class AudioService extends IAudioService.Stub {
//==========================================================================================
// Audio policy management
//==========================================================================================
- public String registerAudioPolicy(AudioPolicyConfig policyConfig, IBinder cb) {
- //Log.v(TAG, "registerAudioPolicy for " + cb + " got policy:" + policyConfig);
+ 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.checkCallingOrSelfPermission(
+ (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(cb)) {
+ if (mAudioPolicies.containsKey(pcb.asBinder())) {
Slog.e(TAG, "Cannot re-register policy");
return null;
}
- AudioPolicyProxy app = new AudioPolicyProxy(policyConfig, cb);
- cb.linkToDeath(app, 0/*flags*/);
- regId = app.connectMixes();
- mAudioPolicies.put(cb, app);
+ 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 " + cb +
+ Slog.w(TAG, "Audio policy registration failed, could not link to " + pcb +
" binder death", e);
return null;
}
@@ -5817,21 +5821,58 @@ public class AudioService extends IAudioService.Stub {
return regId;
}
- public void unregisterAudioPolicyAsync(IBinder cb) {
+ public void unregisterAudioPolicyAsync(IAudioPolicyCallback pcb) {
+ if (DEBUG_AP) Log.d(TAG, "unregisterAudioPolicyAsync for " + pcb.asBinder());
synchronized (mAudioPolicies) {
- AudioPolicyProxy app = mAudioPolicies.remove(cb);
+ 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 {
- cb.unlinkToDeath(app, 0/*flags*/);
+ pcb.asBinder().unlinkToDeath(app, 0/*flags*/);
}
- app.disconnectMixes();
+ 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) {
@@ -5851,27 +5892,48 @@ public class AudioService extends IAudioService.Stub {
public class AudioPolicyProxy extends AudioPolicyConfig implements IBinder.DeathRecipient {
private static final String TAG = "AudioPolicyProxy";
AudioPolicyConfig mConfig;
- IBinder mToken;
- AudioPolicyProxy(AudioPolicyConfig config, IBinder token) {
+ 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++));
- mToken = token;
+ mPolicyToken = token;
+ mHasFocusListener = hasFocusListener;
+ if (mHasFocusListener) {
+ mMediaFocusControl.addFocusFollower(mPolicyToken);
+ }
+ updateMixes(AudioSystem.DEVICE_STATE_AVAILABLE);
}
public void binderDied() {
synchronized (mAudioPolicies) {
- Log.i(TAG, "audio policy " + mToken + " died");
- disconnectMixes();
- mAudioPolicies.remove(mToken);
+ Log.i(TAG, "audio policy " + mPolicyToken + " died");
+ release();
+ mAudioPolicies.remove(mPolicyToken.asBinder());
}
}
- String connectMixes() {
- updateMixes(AudioSystem.DEVICE_STATE_AVAILABLE);
+ String getRegistrationId() {
return getRegistration();
}
- void disconnectMixes() {
+ void release() {
+ if (mFocusDuckBehavior == AudioPolicy.FOCUS_POLICY_DUCKING_IN_POLICY) {
+ mMediaFocusControl.setDuckingInExtPolicyAvailable(false);
+ }
+ if (mHasFocusListener) {
+ mMediaFocusControl.removeFocusFollower(mPolicyToken);
+ }
updateMixes(AudioSystem.DEVICE_STATE_UNAVAILABLE);
}
diff --git a/media/java/android/media/FocusRequester.java b/media/java/android/media/FocusRequester.java
index 0d675fc..bbe5fd2 100644
--- a/media/java/android/media/FocusRequester.java
+++ b/media/java/android/media/FocusRequester.java
@@ -16,6 +16,7 @@
package android.media;
+import android.annotation.NonNull;
import android.media.MediaFocusControl.AudioFocusDeathHandler;
import android.os.IBinder;
import android.util.Log;
@@ -40,6 +41,7 @@ class FocusRequester {
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.
*/
@@ -59,9 +61,22 @@ class FocusRequester {
*/
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) {
+ String pn, int uid, @NonNull MediaFocusControl ctlr) {
mAttributes = aa;
mFocusDispatcher = afl;
mSourceRef = source;
@@ -72,6 +87,7 @@ class FocusRequester {
mFocusGainRequest = focusRequest;
mGrantFlags = grantFlags;
mFocusLossReceived = AudioManager.AUDIOFOCUS_NONE;
+ mFocusController = ctlr;
}
@@ -153,9 +169,17 @@ class FocusRequester {
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"; }
+ 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;
}
@@ -230,13 +254,22 @@ class FocusRequester {
}
}
+ /**
+ * 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 "
@@ -244,27 +277,52 @@ class FocusRequester {
}
mFocusDispatcher.dispatchAudioFocusChange(focusGain, mClientId);
}
- mFocusLossReceived = AudioManager.AUDIOFOCUS_NONE;
} 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(focusLoss) + " to "
+ Log.v(TAG, "dispatching " + focusChangeToString(mFocusLossReceived) + " to "
+ mClientId);
}
- mFocusDispatcher.dispatchAudioFocusChange(focusLoss, mClientId);
+ mFocusController.notifyExtPolicyFocusLoss_syncAf(
+ toAudioFocusInfo(), true /* wasDispatched */);
+ mFocusDispatcher.dispatchAudioFocusChange(mFocusLossReceived, mClientId);
}
- mFocusLossReceived = focusLoss;
}
} 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/IAudioService.aidl b/media/java/android/media/IAudioService.aidl
index 4d8aa76..fad3cec 100644
--- a/media/java/android/media/IAudioService.aidl
+++ b/media/java/android/media/IAudioService.aidl
@@ -30,6 +30,7 @@ import android.media.IRingtonePlayer;
import android.media.IVolumeController;
import android.media.Rating;
import android.media.audiopolicy.AudioPolicyConfig;
+import android.media.audiopolicy.IAudioPolicyCallback;
import android.net.Uri;
import android.view.KeyEvent;
@@ -123,7 +124,7 @@ interface IAudioService {
int requestAudioFocus(in AudioAttributes aa, int durationHint, IBinder cb,
IAudioFocusDispatcher fd, String clientId, String callingPackageName, int flags,
- IBinder policyToken);
+ IAudioPolicyCallback pcb);
int abandonAudioFocus(IAudioFocusDispatcher fd, String clientId, in AudioAttributes aa);
@@ -213,6 +214,9 @@ interface IAudioService {
boolean isHdmiSystemAudioSupported();
- String registerAudioPolicy(in AudioPolicyConfig policyConfig, IBinder cb);
- oneway void unregisterAudioPolicyAsync(in IBinder cb);
+ String registerAudioPolicy(in AudioPolicyConfig policyConfig,
+ in IAudioPolicyCallback pcb, boolean hasFocusListener);
+ oneway void unregisterAudioPolicyAsync(in IAudioPolicyCallback pcb);
+
+ int setFocusPropertiesForPolicy(int duckingBehavior, in IAudioPolicyCallback pcb);
}
diff --git a/media/java/android/media/MediaFocusControl.java b/media/java/android/media/MediaFocusControl.java
index 8d96970..6518bd1 100644
--- a/media/java/android/media/MediaFocusControl.java
+++ b/media/java/android/media/MediaFocusControl.java
@@ -33,6 +33,7 @@ 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;
@@ -434,6 +435,9 @@ public class MediaFocusControl implements OnFinished {
}
}
+ /**
+ * Called synchronized on mAudioFocusLock
+ */
private void notifyTopOfAudioFocusStack() {
// notify the top of the stack it gained focus
if (!mFocusStack.empty()) {
@@ -470,6 +474,7 @@ public class MediaFocusControl implements OnFinished {
stackIterator.next().dump(pw);
}
}
+ pw.println("\n Notify on duck: " + mNotifyFocusOwnerOnDuck +"\n");
}
/**
@@ -480,13 +485,19 @@ public class MediaFocusControl implements OnFinished {
* @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) {
+ 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();
@@ -610,6 +621,86 @@ public class MediaFocusControl implements OnFinished {
}
}
+ /**
+ * 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()) {
@@ -669,6 +760,8 @@ public class MediaFocusControl implements OnFinished {
// 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
@@ -681,14 +774,18 @@ public class MediaFocusControl implements OnFinished {
}
// focus requester might already be somewhere below in the stack, remove it
- removeFocusStackEntry(clientId, false /* signal */);
+ removeFocusStackEntry(clientId, false /* signal */, false /*notifyFocusFollowers*/);
final FocusRequester nfr = new FocusRequester(aa, focusChangeHint, flags, fd, cb,
- clientId, afdh, callingPackageName, Binder.getCallingUid());
+ 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.
- return pushBelowLockedFocusOwners(nfr);
+ 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()) {
@@ -698,6 +795,8 @@ public class MediaFocusControl implements OnFinished {
// push focus requester at the top of the audio focus stack
mFocusStack.push(nfr);
}
+ notifyExtPolicyFocusGrant_syncAf(nfr.toAudioFocusInfo(),
+ AudioManager.AUDIOFOCUS_REQUEST_GRANTED);
}//synchronized(mAudioFocusLock)
@@ -713,7 +812,7 @@ public class MediaFocusControl implements OnFinished {
try {
// this will take care of notifying the new focus owner if needed
synchronized(mAudioFocusLock) {
- removeFocusStackEntry(clientId, true /*signal*/);
+ removeFocusStackEntry(clientId, true /*signal*/, true /*notifyFocusFollowers*/);
}
} catch (java.util.ConcurrentModificationException cme) {
// Catching this exception here is temporary. It is here just to prevent
@@ -729,7 +828,7 @@ public class MediaFocusControl implements OnFinished {
protected void unregisterAudioFocusClient(String clientId) {
synchronized(mAudioFocusLock) {
- removeFocusStackEntry(clientId, false);
+ removeFocusStackEntry(clientId, false, true /*notifyFocusFollowers*/);
}
}
diff --git a/media/java/android/media/audiopolicy/AudioPolicy.java b/media/java/android/media/audiopolicy/AudioPolicy.java
index 44d2430..f128044 100644
--- a/media/java/android/media/audiopolicy/AudioPolicy.java
+++ b/media/java/android/media/audiopolicy/AudioPolicy.java
@@ -22,16 +22,20 @@ import android.annotation.SystemApi;
import android.content.Context;
import android.content.pm.PackageManager;
import android.media.AudioAttributes;
+import android.media.AudioFocusInfo;
import android.media.AudioFormat;
import android.media.AudioManager;
import android.media.AudioRecord;
import android.media.AudioTrack;
+import android.media.IAudioService;
import android.media.MediaRecorder;
import android.os.Binder;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
+import android.os.RemoteException;
+import android.os.ServiceManager;
import android.util.Log;
import android.util.Slog;
@@ -47,6 +51,8 @@ import java.util.ArrayList;
public class AudioPolicy {
private static final String TAG = "AudioPolicy";
+ private static final boolean DEBUG = false;
+ private final Object mLock = new Object();
/**
* The status of an audio policy that is valid but cannot be used because it is not registered.
@@ -63,19 +69,39 @@ public class AudioPolicy {
private String mRegistrationId;
private AudioPolicyStatusListener mStatusListener;
- private final IBinder mToken = new Binder();
- /** @hide */
- public IBinder token() { return mToken; }
+ /**
+ * The behavior of a policy with regards to audio focus where it relies on the application
+ * to do the ducking, the is the legacy and default behavior.
+ */
+ @SystemApi
+ public static final int FOCUS_POLICY_DUCKING_IN_APP = 0;
+ public static final int FOCUS_POLICY_DUCKING_DEFAULT = FOCUS_POLICY_DUCKING_IN_APP;
+ /**
+ * The behavior of a policy with regards to audio focus where it handles ducking instead
+ * of the application losing focus and being signaled it can duck (as communicated by
+ * {@link android.media.AudioManager#AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK}).
+ * <br>Can only be used after having set a listener with
+ * {@link AudioPolicy#setAudioPolicyFocusListener(AudioPolicyFocusListener)}.
+ */
+ @SystemApi
+ public static final int FOCUS_POLICY_DUCKING_IN_POLICY = 1;
+
+ private AudioPolicyFocusListener mFocusListener;
+
private Context mContext;
private AudioPolicyConfig mConfig;
+
/** @hide */
public AudioPolicyConfig getConfig() { return mConfig; }
+ /** @hide */
+ public boolean hasFocusListener() { return mFocusListener != null; }
/**
* The parameter is guaranteed non-null through the Builder
*/
- private AudioPolicy(AudioPolicyConfig config, Context context, Looper looper) {
+ private AudioPolicy(AudioPolicyConfig config, Context context, Looper looper,
+ AudioPolicyFocusListener fl, AudioPolicyStatusListener sl) {
mConfig = config;
mStatus = POLICY_STATUS_UNREGISTERED;
mContext = context;
@@ -88,6 +114,8 @@ public class AudioPolicy {
mEventHandler = null;
Log.e(TAG, "No event handler due to looper without a thread");
}
+ mFocusListener = fl;
+ mStatusListener = sl;
}
/**
@@ -98,11 +126,14 @@ public class AudioPolicy {
private ArrayList<AudioMix> mMixes;
private Context mContext;
private Looper mLooper;
+ private AudioPolicyFocusListener mFocusListener;
+ private AudioPolicyStatusListener mStatusListener;
/**
* Constructs a new Builder with no audio mixes.
* @param context the context for the policy
*/
+ @SystemApi
public Builder(Context context) {
mMixes = new ArrayList<AudioMix>();
mContext = context;
@@ -114,6 +145,7 @@ public class AudioPolicy {
* @return the same Builder instance.
* @throws IllegalArgumentException
*/
+ @SystemApi
public Builder addMix(@NonNull AudioMix mix) throws IllegalArgumentException {
if (mix == null) {
throw new IllegalArgumentException("Illegal null AudioMix argument");
@@ -128,6 +160,7 @@ public class AudioPolicy {
* @return the same Builder instance.
* @throws IllegalArgumentException
*/
+ @SystemApi
public Builder setLooper(@NonNull Looper looper) throws IllegalArgumentException {
if (looper == null) {
throw new IllegalArgumentException("Illegal null Looper argument");
@@ -136,34 +169,58 @@ public class AudioPolicy {
return this;
}
+ /**
+ * Sets the audio focus listener for the policy.
+ * @param l a {@link AudioPolicy.AudioPolicyFocusListener}
+ */
+ @SystemApi
+ public void setAudioPolicyFocusListener(AudioPolicyFocusListener l) {
+ mFocusListener = l;
+ }
+
+ /**
+ * Sets the audio policy status listener.
+ * @param l a {@link AudioPolicy.AudioPolicyStatusListener}
+ */
+ @SystemApi
+ public void setAudioPolicyStatusListener(AudioPolicyStatusListener l) {
+ mStatusListener = l;
+ }
+
+ @SystemApi
public AudioPolicy build() {
- return new AudioPolicy(new AudioPolicyConfig(mMixes), mContext, mLooper);
+ return new AudioPolicy(new AudioPolicyConfig(mMixes), mContext, mLooper,
+ mFocusListener, mStatusListener);
}
}
public void setRegistration(String regId) {
- mRegistrationId = regId;
- mConfig.setRegistration(regId);
- if (regId != null) {
- mStatus = POLICY_STATUS_REGISTERED;
- } else {
- mStatus = POLICY_STATUS_UNREGISTERED;
+ synchronized (mLock) {
+ mRegistrationId = regId;
+ mConfig.setRegistration(regId);
+ if (regId != null) {
+ mStatus = POLICY_STATUS_REGISTERED;
+ } else {
+ mStatus = POLICY_STATUS_UNREGISTERED;
+ }
}
- sendMsg(mEventHandler, MSG_POLICY_STATUS_CHANGE);
+ sendMsg(MSG_POLICY_STATUS_CHANGE);
}
private boolean policyReadyToUse() {
- if (mStatus != POLICY_STATUS_REGISTERED) {
- Log.e(TAG, "Cannot use unregistered AudioPolicy");
- return false;
- }
- if (mContext == null) {
- Log.e(TAG, "Cannot use AudioPolicy without context");
- return false;
- }
- if (mRegistrationId == null) {
- Log.e(TAG, "Cannot use unregistered AudioPolicy");
- return false;
+ synchronized (mLock) {
+ if (mStatus != POLICY_STATUS_REGISTERED) {
+ Log.e(TAG, "Cannot use unregistered AudioPolicy");
+ return false;
+ }
+ if (mContext == null) {
+ Log.e(TAG, "Cannot use AudioPolicy without context");
+ return false;
+ }
+ if (mRegistrationId == null) {
+ Log.e(TAG, "Cannot use unregistered AudioPolicy");
+ return false;
+ }
}
if (!(PackageManager.PERMISSION_GRANTED == mContext.checkCallingOrSelfPermission(
android.Manifest.permission.MODIFY_AUDIO_ROUTING))) {
@@ -199,6 +256,60 @@ public class AudioPolicy {
}
/**
+ * Returns the current behavior for audio focus-related ducking.
+ * @return {@link #FOCUS_POLICY_DUCKING_IN_APP} or {@link #FOCUS_POLICY_DUCKING_IN_POLICY}
+ */
+ @SystemApi
+ public int getFocusDuckingBehavior() {
+ return mConfig.mDuckingPolicy;
+ }
+
+ // Note on implementation: not part of the Builder as there can be only one registered policy
+ // that handles ducking but there can be multiple policies
+ /**
+ * Sets the behavior for audio focus-related ducking.
+ * There must be a focus listener if this policy is to handle ducking.
+ * @param behavior {@link #FOCUS_POLICY_DUCKING_IN_APP} or
+ * {@link #FOCUS_POLICY_DUCKING_IN_POLICY}
+ * @return {@link AudioManager#SUCCESS} or {@link AudioManager#ERROR} (for instance if there
+ * is already an audio policy that handles ducking).
+ * @throws IllegalArgumentException
+ * @throws IllegalStateException
+ */
+ @SystemApi
+ public int setFocusDuckingBehavior(int behavior)
+ throws IllegalArgumentException, IllegalStateException {
+ if ((behavior != FOCUS_POLICY_DUCKING_IN_APP)
+ && (behavior != FOCUS_POLICY_DUCKING_IN_POLICY)) {
+ throw new IllegalArgumentException("Invalid ducking behavior " + behavior);
+ }
+ synchronized (mLock) {
+ if (mStatus != POLICY_STATUS_REGISTERED) {
+ throw new IllegalStateException(
+ "Cannot change ducking behavior for unregistered policy");
+ }
+ if ((behavior == FOCUS_POLICY_DUCKING_IN_POLICY)
+ && (mFocusListener == null)) {
+ // there must be a focus listener if the policy handles ducking
+ throw new IllegalStateException(
+ "Cannot handle ducking without an audio focus listener");
+ }
+ IAudioService service = getService();
+ try {
+ final int status = service.setFocusPropertiesForPolicy(behavior /*duckingBehavior*/,
+ this.cb());
+ if (status == AudioManager.SUCCESS) {
+ mConfig.mDuckingPolicy = behavior;
+ }
+ return status;
+ } catch (RemoteException e) {
+ Log.e(TAG, "Dead object in setFocusPropertiesForPolicy for behavior", e);
+ return AudioManager.ERROR;
+ }
+ }
+ }
+
+ /**
* Create an {@link AudioRecord} instance that is associated with the given {@link AudioMix}.
* Audio buffers recorded through the created instance will contain the mix of the audio
* streams that fed the given mixer.
@@ -282,21 +393,53 @@ public class AudioPolicy {
}
@SystemApi
- synchronized public void setAudioPolicyStatusListener(AudioPolicyStatusListener l) {
- mStatusListener = l;
+ public static abstract class AudioPolicyFocusListener {
+ public void onAudioFocusGrant(AudioFocusInfo afi, int requestResult) {}
+ public void onAudioFocusLoss(AudioFocusInfo afi, boolean wasNotified) {}
}
- synchronized private void onPolicyStatusChange() {
- if (mStatusListener == null) {
- return;
+ private void onPolicyStatusChange() {
+ AudioPolicyStatusListener l;
+ synchronized (mLock) {
+ if (mStatusListener == null) {
+ return;
+ }
+ l = mStatusListener;
}
- mStatusListener.onStatusChange();
+ l.onStatusChange();
}
//==================================================
+ // Callback interface
+
+ /** @hide */
+ public IAudioPolicyCallback cb() { return mPolicyCb; }
+
+ private final IAudioPolicyCallback mPolicyCb = new IAudioPolicyCallback.Stub() {
+
+ public void notifyAudioFocusGrant(AudioFocusInfo afi, int requestResult) {
+ sendMsg(MSG_FOCUS_GRANT, afi, requestResult);
+ if (DEBUG) {
+ Log.v(TAG, "notifyAudioFocusGrant: pack=" + afi.getPackageName() + " client="
+ + afi.getClientId() + "reqRes=" + requestResult);
+ }
+ }
+
+ public void notifyAudioFocusLoss(AudioFocusInfo afi, boolean wasNotified) {
+ sendMsg(MSG_FOCUS_LOSS, afi, wasNotified ? 1 : 0);
+ if (DEBUG) {
+ Log.v(TAG, "notifyAudioFocusLoss: pack=" + afi.getPackageName() + " client="
+ + afi.getClientId() + "wasNotified=" + wasNotified);
+ }
+ }
+ };
+
+ //==================================================
// Event handling
private final EventHandler mEventHandler;
private final static int MSG_POLICY_STATUS_CHANGE = 0;
+ private final static int MSG_FOCUS_GRANT = 1;
+ private final static int MSG_FOCUS_LOSS = 2;
private class EventHandler extends Handler {
public EventHandler(AudioPolicy ap, Looper looper) {
@@ -309,6 +452,18 @@ public class AudioPolicy {
case MSG_POLICY_STATUS_CHANGE:
onPolicyStatusChange();
break;
+ case MSG_FOCUS_GRANT:
+ if (mFocusListener != null) {
+ mFocusListener.onAudioFocusGrant(
+ (AudioFocusInfo) msg.obj, msg.arg1);
+ }
+ break;
+ case MSG_FOCUS_LOSS:
+ if (mFocusListener != null) {
+ mFocusListener.onAudioFocusLoss(
+ (AudioFocusInfo) msg.obj, msg.arg1 != 0);
+ }
+ break;
default:
Log.e(TAG, "Unknown event " + msg.what);
}
@@ -321,10 +476,29 @@ public class AudioPolicy {
return "addr=" + mix.getRegistration();
}
- private static void sendMsg(Handler handler, int msg) {
- if (handler != null) {
- handler.sendEmptyMessage(msg);
+ private void sendMsg(int msg) {
+ if (mEventHandler != null) {
+ mEventHandler.sendEmptyMessage(msg);
+ }
+ }
+
+ private void sendMsg(int msg, Object obj, int i) {
+ if (mEventHandler != null) {
+ mEventHandler.sendMessage(
+ mEventHandler.obtainMessage(msg, i /*arg1*/, 0 /*arg2, ignored*/, obj));
+ }
+ }
+
+ private static IAudioService sService;
+
+ private static IAudioService getService()
+ {
+ if (sService != null) {
+ return sService;
}
+ IBinder b = ServiceManager.getService(Context.AUDIO_SERVICE);
+ sService = IAudioService.Stub.asInterface(b);
+ return sService;
}
public String toLogFriendlyString() {
diff --git a/media/java/android/media/audiopolicy/AudioPolicyConfig.java b/media/java/android/media/audiopolicy/AudioPolicyConfig.java
index e2a20da..019309d 100644
--- a/media/java/android/media/audiopolicy/AudioPolicyConfig.java
+++ b/media/java/android/media/audiopolicy/AudioPolicyConfig.java
@@ -38,6 +38,7 @@ public class AudioPolicyConfig implements Parcelable {
private static final String TAG = "AudioPolicyConfig";
protected ArrayList<AudioMix> mMixes;
+ protected int mDuckingPolicy = AudioPolicy.FOCUS_POLICY_DUCKING_IN_APP;
private String mRegistrationId = null;
diff --git a/media/java/android/media/audiopolicy/IAudioPolicyCallback.aidl b/media/java/android/media/audiopolicy/IAudioPolicyCallback.aidl
new file mode 100644
index 0000000..c777c58
--- /dev/null
+++ b/media/java/android/media/audiopolicy/IAudioPolicyCallback.aidl
@@ -0,0 +1,28 @@
+/* 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.audiopolicy;
+
+import android.media.AudioFocusInfo;
+
+/**
+ * @hide
+ */
+oneway interface IAudioPolicyCallback {
+
+ // callbacks for audio focus
+ void notifyAudioFocusGrant(in AudioFocusInfo afi, int requestResult);
+ void notifyAudioFocusLoss(in AudioFocusInfo afi, boolean wasNotified);
+}