Merge "Support collaborative audio focus handling" into lmp-mr1-dev
diff --git a/Android.mk b/Android.mk
index 1416472..dd564557 100644
--- a/Android.mk
+++ b/Android.mk
@@ -330,6 +330,7 @@
media/java/android/media/IRemoteVolumeObserver.aidl \
media/java/android/media/IRingtonePlayer.aidl \
media/java/android/media/IVolumeController.aidl \
+ media/java/android/media/audiopolicy/IAudioPolicyCallback.aidl \
media/java/android/media/projection/IMediaProjection.aidl \
media/java/android/media/projection/IMediaProjectionCallback.aidl \
media/java/android/media/projection/IMediaProjectionManager.aidl \
@@ -437,6 +438,7 @@
frameworks/base/media/java/android/media/MediaDescription.aidl \
frameworks/base/media/java/android/media/Rating.aidl \
frameworks/base/media/java/android/media/AudioAttributes.aidl \
+ frameworks/base/media/java/android/media/AudioFocusInfo.aidl \
frameworks/base/media/java/android/media/session/PlaybackState.aidl \
frameworks/base/media/java/android/media/session/MediaSession.aidl \
frameworks/base/media/java/android/media/tv/TvInputInfo.aidl \
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 ee9044e..360f764 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -2385,17 +2385,42 @@
}
// 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_LOCK = 0x1 << 1;
+ 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 << 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
@@ -2417,15 +2442,12 @@
* 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
@@ -2459,17 +2481,11 @@
* @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
@@ -2510,7 +2526,7 @@
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);
}
@@ -2887,7 +2903,8 @@
}
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 {
@@ -2912,7 +2929,7 @@
}
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 f1d6a0a..03ecad2 100644
--- a/media/java/android/media/AudioService.java
+++ b/media/java/android/media/AudioService.java
@@ -51,6 +51,7 @@
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;
@@ -5098,7 +5099,7 @@
//==========================================================================================
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)) {
@@ -5110,9 +5111,8 @@
} 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;
}
}
@@ -5812,30 +5812,34 @@
//==========================================================================================
// 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;
}
@@ -5843,21 +5847,58 @@
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) {
@@ -5877,27 +5918,48 @@
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 @@
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 @@
*/
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 @@
mFocusGainRequest = focusRequest;
mGrantFlags = grantFlags;
mFocusLossReceived = AudioManager.AUDIOFOCUS_NONE;
+ mFocusController = ctlr;
}
@@ -153,9 +169,17 @@
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 @@
}
}
+ /**
+ * 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 @@
}
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.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 @@
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 @@
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.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 @@
}
}
+ /**
+ * Called synchronized on mAudioFocusLock
+ */
private void notifyTopOfAudioFocusStack() {
// notify the top of the stack it gained focus
if (!mFocusStack.empty()) {
@@ -470,6 +474,7 @@
stackIterator.next().dump(pw);
}
}
+ pw.println("\n Notify on duck: " + mNotifyFocusOwnerOnDuck +"\n");
}
/**
@@ -480,13 +485,19 @@
* @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 @@
}
}
+ /**
+ * 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 @@
// 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 @@
}
// 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 @@
// 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 @@
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 @@
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.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 @@
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 @@
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 @@
mEventHandler = null;
Log.e(TAG, "No event handler due to looper without a thread");
}
+ mFocusListener = fl;
+ mStatusListener = sl;
}
/**
@@ -98,11 +126,14 @@
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 @@
* @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 @@
* @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 @@
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 @@
}
/**
+ * 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 @@
}
@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 @@
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,12 +476,31 @@
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() {
String textDump = new String("android.media.audiopolicy.AudioPolicy:\n");
textDump += "config=" + mConfig.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 @@
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);
+}