diff --git a/Android.mk b/Android.mk index 14164728bf55a..dd564557b1b31 100644 --- a/Android.mk +++ b/Android.mk @@ -330,6 +330,7 @@ LOCAL_SRC_FILES += \ 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 @@ aidl_files := \ 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 0000000000000..f925fda907fea --- /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 0000000000000..fbdda3ce4dee5 --- /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 CREATOR + = new Parcelable.Creator() { + + 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 ee9044e37b11e..360f764ace7c6 100644 --- a/media/java/android/media/AudioManager.java +++ b/media/java/android/media/AudioManager.java @@ -2385,17 +2385,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. + *
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. + *
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 @@ 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}. *
Use 0 when not using any flags for the request, which behaves like * {@link #requestAudioFocus(OnAudioFocusChangeListener, int, int)}, where either audio * focus is granted immediately, or the grant request fails because the system is in a * state where focus cannot change (e.g. a phone call). - *
Use {link #AUDIOFOCUS_FLAG_DELAY_OK} if it is ok for the requester to not be granted - * audio focus immediately (as indicated by {@link #AUDIOFOCUS_REQUEST_DELAYED}) when - * the system is in a state where focus cannot change, but be granted focus later when - * this condition ends. * @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 @@ 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}. *
Use 0 when not using any flags for the request, which behaves like * {@link #requestAudioFocus(OnAudioFocusChangeListener, int, int)}, where either audio * focus is granted immediately, or the grant request fails because the system is in a * state where focus cannot change (e.g. a phone call). - *
Use {link #AUDIOFOCUS_FLAG_DELAY_OK} if it is ok for the requester to not be granted - * audio focus immediately (as indicated by {@link #AUDIOFOCUS_REQUEST_DELAYED}) when - * the system is in a state where focus cannot change, but be granted focus later when - * this condition ends. - *
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 @@ 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); } @@ -2887,7 +2903,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 { @@ -2912,7 +2929,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 f1d6a0aaecec9..03ecad2f03c46 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; @@ -5098,7 +5099,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)) { @@ -5110,9 +5111,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; } } @@ -5812,30 +5812,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; } @@ -5843,21 +5847,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) { @@ -5877,27 +5918,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 0d675fc835b44..bbe5fd225c175 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 4d8aa762912e2..fad3cec78098b 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 8d96970e3bf2a..6518bd162e39f 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 mFocusFollowers = new ArrayList(); + + 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 44d2430ec0525..f128044d64d75 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}). + *
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 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(); 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))) { @@ -198,6 +255,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 @@ -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,12 +476,31 @@ 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() { 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 e2a20dabe41ef..019309d73ce1e 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 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 0000000000000..c777c582f3ab6 --- /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); +}