diff options
| -rw-r--r-- | api/system-current.txt | 1 | ||||
| -rw-r--r-- | media/java/android/media/AudioFocusInfo.java | 21 | ||||
| -rw-r--r-- | media/java/android/media/AudioManager.java | 182 | ||||
| -rw-r--r-- | media/java/android/media/IAudioFocusDispatcher.aidl | 2 | ||||
| -rw-r--r-- | media/java/android/media/IAudioService.aidl | 3 | ||||
| -rw-r--r-- | media/java/android/media/audiopolicy/AudioPolicy.java | 8 | ||||
| -rw-r--r-- | services/core/java/com/android/server/audio/AudioService.java | 23 | ||||
| -rw-r--r-- | services/core/java/com/android/server/audio/FocusRequester.java | 35 | ||||
| -rw-r--r-- | services/core/java/com/android/server/audio/MediaFocusControl.java | 47 |
9 files changed, 284 insertions, 38 deletions
diff --git a/api/system-current.txt b/api/system-current.txt index 70148d97e717..2d3b65a19b26 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -2516,6 +2516,7 @@ package android.media { method public int requestAudioFocus(android.media.AudioManager.OnAudioFocusChangeListener, android.media.AudioAttributes, int, int) throws java.lang.IllegalArgumentException; method public deprecated int requestAudioFocus(android.media.AudioManager.OnAudioFocusChangeListener, android.media.AudioAttributes, int, int, android.media.audiopolicy.AudioPolicy) throws java.lang.IllegalArgumentException; method public int requestAudioFocus(android.media.AudioFocusRequest, android.media.audiopolicy.AudioPolicy); + method public void setFocusRequestResult(android.media.AudioFocusInfo, int, android.media.audiopolicy.AudioPolicy); method public void unregisterAudioPolicyAsync(android.media.audiopolicy.AudioPolicy); field public static final int AUDIOFOCUS_FLAG_DELAY_OK = 1; // 0x1 field public static final int AUDIOFOCUS_FLAG_LOCK = 4; // 0x4 diff --git a/media/java/android/media/AudioFocusInfo.java b/media/java/android/media/AudioFocusInfo.java index 5d0c8e234d40..5467a69ea0bb 100644 --- a/media/java/android/media/AudioFocusInfo.java +++ b/media/java/android/media/AudioFocusInfo.java @@ -38,6 +38,10 @@ public final class AudioFocusInfo implements Parcelable { private int mLossReceived; private int mFlags; + // generation count for the validity of a request/response async exchange between + // external focus policy and MediaFocusControl + private long mGenCount = -1; + /** * Class constructor @@ -61,6 +65,16 @@ public final class AudioFocusInfo implements Parcelable { mSdkTarget = sdk; } + /** @hide */ + public void setGen(long g) { + mGenCount = g; + } + + /** @hide */ + public long getGen() { + return mGenCount; + } + /** * The audio attributes for the audio focus request. @@ -128,6 +142,7 @@ public final class AudioFocusInfo implements Parcelable { dest.writeInt(mLossReceived); dest.writeInt(mFlags); dest.writeInt(mSdkTarget); + dest.writeLong(mGenCount); } @Override @@ -168,6 +183,8 @@ public final class AudioFocusInfo implements Parcelable { if (mSdkTarget != other.mSdkTarget) { return false; } + // mGenCount is not used to verify equality between two focus holds as multiple requests + // (hence of different generations) could correspond to the same hold return true; } @@ -175,7 +192,7 @@ public final class AudioFocusInfo implements Parcelable { = new Parcelable.Creator<AudioFocusInfo>() { public AudioFocusInfo createFromParcel(Parcel in) { - return new AudioFocusInfo( + final AudioFocusInfo afi = new AudioFocusInfo( AudioAttributes.CREATOR.createFromParcel(in), //AudioAttributes aa in.readInt(), // int clientUid in.readString(), //String clientId @@ -185,6 +202,8 @@ public final class AudioFocusInfo implements Parcelable { in.readInt(), //int flags in.readInt() //int sdkTarget ); + afi.setGen(in.readLong()); + return afi; } public AudioFocusInfo[] newArray(int size) { diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java index bf51d97f6b2f..0be54ec2b2d9 100644 --- a/media/java/android/media/AudioManager.java +++ b/media/java/android/media/AudioManager.java @@ -32,6 +32,7 @@ import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.media.audiopolicy.AudioPolicy; +import android.media.audiopolicy.AudioPolicy.AudioPolicyFocusListener; import android.media.session.MediaController; import android.media.session.MediaSession; import android.media.session.MediaSessionLegacyHelper; @@ -54,10 +55,13 @@ import android.util.Log; import android.util.Slog; import android.view.KeyEvent; +import com.android.internal.annotations.GuardedBy; + import java.io.IOException; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; +import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.concurrent.ConcurrentHashMap; @@ -2338,6 +2342,20 @@ public class AudioManager { } } } + + @Override + public void dispatchFocusResultFromExtPolicy(int requestResult, String clientId) { + synchronized (mFocusRequestsLock) { + // TODO use generation counter as the key instead + final BlockingFocusResultReceiver focusReceiver = + mFocusRequestsAwaitingResult.remove(clientId); + if (focusReceiver != null) { + focusReceiver.notifyResult(requestResult); + } else { + Log.e(TAG, "dispatchFocusResultFromExtPolicy found no result receiver"); + } + } + } }; private String getIdForAudioFocusListener(OnAudioFocusChangeListener l) { @@ -2390,6 +2408,40 @@ public class AudioManager { */ public static final int AUDIOFOCUS_REQUEST_DELAYED = 2; + /** @hide */ + @IntDef(flag = false, prefix = "AUDIOFOCUS_REQUEST", value = { + AUDIOFOCUS_REQUEST_FAILED, + AUDIOFOCUS_REQUEST_GRANTED, + AUDIOFOCUS_REQUEST_DELAYED } + ) + @Retention(RetentionPolicy.SOURCE) + public @interface FocusRequestResult {} + + /** + * @hide + * code returned when a synchronous focus request on the client-side is to be blocked + * until the external audio focus policy decides on the response for the client + */ + public static final int AUDIOFOCUS_REQUEST_WAITING_FOR_EXT_POLICY = 100; + + /** + * Timeout duration in ms when waiting on an external focus policy for the result for a + * focus request + */ + private static final int EXT_FOCUS_POLICY_TIMEOUT_MS = 200; + + private static final String FOCUS_CLIENT_ID_STRING = "android_audio_focus_client_id"; + + private final Object mFocusRequestsLock = new Object(); + /** + * Map of all receivers of focus request results, one per unresolved focus request. + * Receivers are added before sending the request to the external focus policy, + * and are removed either after receiving the result, or after the timeout. + * This variable is lazily initialized. + */ + @GuardedBy("mFocusRequestsLock") + private HashMap<String, BlockingFocusResultReceiver> mFocusRequestsAwaitingResult; + /** * Request audio focus. @@ -2656,18 +2708,100 @@ public class AudioManager { // some tests don't have a Context sdk = Build.VERSION.SDK_INT; } - try { - status = service.requestAudioFocus(afr.getAudioAttributes(), - afr.getFocusGain(), mICallBack, - mAudioFocusDispatcher, - getIdForAudioFocusListener(afr.getOnAudioFocusChangeListener()), - getContext().getOpPackageName() /* package name */, afr.getFlags(), - ap != null ? ap.cb() : null, - sdk); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); + + final String clientId = getIdForAudioFocusListener(afr.getOnAudioFocusChangeListener()); + final BlockingFocusResultReceiver focusReceiver; + synchronized (mFocusRequestsLock) { + try { + // TODO status contains result and generation counter for ext policy + status = service.requestAudioFocus(afr.getAudioAttributes(), + afr.getFocusGain(), mICallBack, + mAudioFocusDispatcher, + clientId, + getContext().getOpPackageName() /* package name */, afr.getFlags(), + ap != null ? ap.cb() : null, + sdk); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + if (status != AudioManager.AUDIOFOCUS_REQUEST_WAITING_FOR_EXT_POLICY) { + // default path with no external focus policy + return status; + } + if (mFocusRequestsAwaitingResult == null) { + mFocusRequestsAwaitingResult = + new HashMap<String, BlockingFocusResultReceiver>(1); + } + focusReceiver = new BlockingFocusResultReceiver(clientId); + mFocusRequestsAwaitingResult.put(clientId, focusReceiver); + } + focusReceiver.waitForResult(EXT_FOCUS_POLICY_TIMEOUT_MS); + if (DEBUG && !focusReceiver.receivedResult()) { + Log.e(TAG, "requestAudio response from ext policy timed out, denying request"); + } + synchronized (mFocusRequestsLock) { + mFocusRequestsAwaitingResult.remove(clientId); + } + return focusReceiver.requestResult(); + } + + // helper class that abstracts out the handling of spurious wakeups in Object.wait() + private static final class SafeWaitObject { + private boolean mQuit = false; + + public void safeNotify() { + synchronized (this) { + mQuit = true; + this.notify(); + } + } + + public void safeWait(long millis) throws InterruptedException { + final long timeOutTime = java.lang.System.currentTimeMillis() + millis; + synchronized (this) { + while (!mQuit) { + final long timeToWait = timeOutTime - java.lang.System.currentTimeMillis(); + if (timeToWait < 0) { break; } + this.wait(timeToWait); + } + } + } + } + + private static final class BlockingFocusResultReceiver { + private final SafeWaitObject mLock = new SafeWaitObject(); + @GuardedBy("mLock") + private boolean mResultReceived = false; + // request denied by default (e.g. timeout) + private int mFocusRequestResult = AudioManager.AUDIOFOCUS_REQUEST_FAILED; + private final String mFocusClientId; + + BlockingFocusResultReceiver(String clientId) { + mFocusClientId = clientId; + } + + boolean receivedResult() { return mResultReceived; } + int requestResult() { return mFocusRequestResult; } + + void notifyResult(int requestResult) { + synchronized (mLock) { + mResultReceived = true; + mFocusRequestResult = requestResult; + mLock.safeNotify(); + } + } + + public void waitForResult(long timeOutMs) { + synchronized (mLock) { + if (mResultReceived) { + // the result was received before waiting + return; + } + try { + mLock.safeWait(timeOutMs); + } catch (InterruptedException e) { } + } } - return status; } /** @@ -2714,6 +2848,32 @@ public class AudioManager { /** * @hide + * Set the result to the audio focus request received through + * {@link AudioPolicyFocusListener#onAudioFocusRequest(AudioFocusInfo, int)}. + * @param afi the information about the focus requester + * @param requestResult the result to the focus request to be passed to the requester + * @param ap a valid registered {@link AudioPolicy} configured as a focus policy. + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) + public void setFocusRequestResult(@NonNull AudioFocusInfo afi, + @FocusRequestResult int requestResult, @NonNull AudioPolicy ap) { + if (afi == null) { + throw new IllegalArgumentException("Illegal null AudioFocusInfo"); + } + if (ap == null) { + throw new IllegalArgumentException("Illegal null AudioPolicy"); + } + final IAudioService service = getService(); + try { + service.setFocusRequestResultFromExtPolicy(afi, requestResult, ap.cb()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * @hide * Notifies an application with a focus listener of gain or loss of audio focus. * This method can only be used by owners of an {@link AudioPolicy} configured with * {@link AudioPolicy.Builder#setIsAudioFocusPolicy(boolean)} set to true. diff --git a/media/java/android/media/IAudioFocusDispatcher.aidl b/media/java/android/media/IAudioFocusDispatcher.aidl index 09575f733e32..3b33c5b7a46a 100644 --- a/media/java/android/media/IAudioFocusDispatcher.aidl +++ b/media/java/android/media/IAudioFocusDispatcher.aidl @@ -25,4 +25,6 @@ oneway interface IAudioFocusDispatcher { void dispatchAudioFocusChange(int focusChange, String clientId); + void dispatchFocusResultFromExtPolicy(int requestResult, String clientId); + } diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl index 88d0a6088026..cd4143cf3120 100644 --- a/media/java/android/media/IAudioService.aidl +++ b/media/java/android/media/IAudioService.aidl @@ -207,5 +207,8 @@ interface IAudioService { int setBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent(in BluetoothDevice device, int state, int profile, boolean suppressNoisyIntent); + oneway void setFocusRequestResultFromExtPolicy(in AudioFocusInfo afi, int requestResult, + in IAudioPolicyCallback pcb); + // WARNING: read warning at top of file, it is recommended to add new methods at the end } diff --git a/media/java/android/media/audiopolicy/AudioPolicy.java b/media/java/android/media/audiopolicy/AudioPolicy.java index 4de731a9a8a3..219063564132 100644 --- a/media/java/android/media/audiopolicy/AudioPolicy.java +++ b/media/java/android/media/audiopolicy/AudioPolicy.java @@ -463,9 +463,9 @@ public class AudioPolicy { * Only ever called if the {@link AudioPolicy} was built with * {@link AudioPolicy.Builder#setIsAudioFocusPolicy(boolean)} set to {@code true}. * @param afi information about the focus request and the requester - * @param requestResult the result that was returned synchronously by the framework to the - * application, {@link #AUDIOFOCUS_REQUEST_FAILED},or - * {@link #AUDIOFOCUS_REQUEST_DELAYED}. + * @param requestResult deprecated after the addition of + * {@link AudioManager#setFocusRequestResult(AudioFocusInfo, int, AudioPolicy)} + * in Android P, always equal to {@link #AUDIOFOCUS_REQUEST_GRANTED}. */ public void onAudioFocusRequest(AudioFocusInfo afi, int requestResult) {} /** @@ -534,7 +534,7 @@ public class AudioPolicy { sendMsg(MSG_FOCUS_REQUEST, afi, requestResult); if (DEBUG) { Log.v(TAG, "notifyAudioFocusRequest: pack=" + afi.getPackageName() + " client=" - + afi.getClientId() + "reqRes=" + requestResult); + + afi.getClientId() + " gen=" + afi.getGen()); } } diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java index 1825db8647bf..56d66de21f98 100644 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -7296,6 +7296,12 @@ public class AudioService extends IAudioService.Stub //====================== /** */ public int dispatchFocusChange(AudioFocusInfo afi, int focusChange, IAudioPolicyCallback pcb) { + if (afi == null) { + throw new IllegalArgumentException("Illegal null AudioFocusInfo"); + } + if (pcb == null) { + throw new IllegalArgumentException("Illegal null AudioPolicy callback"); + } synchronized (mAudioPolicies) { if (!mAudioPolicies.containsKey(pcb.asBinder())) { throw new IllegalStateException("Unregistered AudioPolicy for focus dispatch"); @@ -7304,6 +7310,23 @@ public class AudioService extends IAudioService.Stub } } + public void setFocusRequestResultFromExtPolicy(AudioFocusInfo afi, int requestResult, + IAudioPolicyCallback pcb) { + if (afi == null) { + throw new IllegalArgumentException("Illegal null AudioFocusInfo"); + } + if (pcb == null) { + throw new IllegalArgumentException("Illegal null AudioPolicy callback"); + } + synchronized (mAudioPolicies) { + if (!mAudioPolicies.containsKey(pcb.asBinder())) { + throw new IllegalStateException("Unregistered AudioPolicy for external focus"); + } + mMediaFocusControl.setFocusRequestResultFromExtPolicy(afi, requestResult); + } + } + + //====================== // misc //====================== diff --git a/services/core/java/com/android/server/audio/FocusRequester.java b/services/core/java/com/android/server/audio/FocusRequester.java index f2ef02fb2579..99f08405a375 100644 --- a/services/core/java/com/android/server/audio/FocusRequester.java +++ b/services/core/java/com/android/server/audio/FocusRequester.java @@ -241,15 +241,15 @@ public class FocusRequester { void release() { + final IBinder srcRef = mSourceRef; + final AudioFocusDeathHandler deathHdlr = mDeathHandler; try { - if (mSourceRef != null && mDeathHandler != null) { - mSourceRef.unlinkToDeath(mDeathHandler, 0); - mDeathHandler = null; - mFocusDispatcher = null; + if (srcRef != null && deathHdlr != null) { + srcRef.unlinkToDeath(deathHdlr, 0); } - } catch (java.util.NoSuchElementException e) { - Log.e(TAG, "FocusRequester.release() hit ", e); - } + } catch (java.util.NoSuchElementException e) { } + mDeathHandler = null; + mFocusDispatcher = null; } @Override @@ -424,7 +424,7 @@ public class FocusRequester { int dispatchFocusChange(int focusChange) { if (mFocusDispatcher == null) { - if (MediaFocusControl.DEBUG) { Log.v(TAG, "dispatchFocusChange: no focus dispatcher"); } + if (MediaFocusControl.DEBUG) { Log.e(TAG, "dispatchFocusChange: no focus dispatcher"); } return AudioManager.AUDIOFOCUS_REQUEST_FAILED; } if (focusChange == AudioManager.AUDIOFOCUS_NONE) { @@ -445,12 +445,29 @@ public class FocusRequester { try { mFocusDispatcher.dispatchAudioFocusChange(focusChange, mClientId); } catch (android.os.RemoteException e) { - Log.v(TAG, "dispatchFocusChange: error talking to focus listener", e); + Log.e(TAG, "dispatchFocusChange: error talking to focus listener " + mClientId, e); return AudioManager.AUDIOFOCUS_REQUEST_FAILED; } return AudioManager.AUDIOFOCUS_REQUEST_GRANTED; } + void dispatchFocusResultFromExtPolicy(int requestResult) { + if (mFocusDispatcher == null) { + if (MediaFocusControl.DEBUG) { + Log.e(TAG, "dispatchFocusResultFromExtPolicy: no focus dispatcher"); + } + } + if (DEBUG) { + Log.v(TAG, "dispatching result" + requestResult + " to " + mClientId); + } + try { + mFocusDispatcher.dispatchFocusResultFromExtPolicy(requestResult, mClientId); + } catch (android.os.RemoteException e) { + Log.e(TAG, "dispatchFocusResultFromExtPolicy: error talking to focus listener" + + mClientId, e); + } + } + AudioFocusInfo toAudioFocusInfo() { return new AudioFocusInfo(mAttributes, mCallingUid, mClientId, mPackageName, mFocusGainRequest, mFocusLossReceived, mGrantFlags, mSdkTarget); diff --git a/services/core/java/com/android/server/audio/MediaFocusControl.java b/services/core/java/com/android/server/audio/MediaFocusControl.java index 9ddc52a1826e..d023bd7827ff 100644 --- a/services/core/java/com/android/server/audio/MediaFocusControl.java +++ b/services/core/java/com/android/server/audio/MediaFocusControl.java @@ -83,6 +83,10 @@ public class MediaFocusControl implements PlayerFocusEnforcer { private boolean mRingOrCallActive = false; + private final Object mExtFocusChangeLock = new Object(); + @GuardedBy("mExtFocusChangeLock") + private long mExtFocusChangeCounter; + protected MediaFocusControl(Context cntxt, PlayerFocusEnforcer pfe) { mContext = cntxt; mAppOps = (AppOpsManager)mContext.getSystemService(Context.APP_OPS_SERVICE); @@ -521,7 +525,7 @@ public class MediaFocusControl implements PlayerFocusEnforcer { * @param requestResult * @return true if the external audio focus policy (if any) is handling the focus request */ - boolean notifyExtFocusPolicyFocusRequest_syncAf(AudioFocusInfo afi, int requestResult, + boolean notifyExtFocusPolicyFocusRequest_syncAf(AudioFocusInfo afi, IAudioFocusDispatcher fd, IBinder cb) { if (mFocusPolicy == null) { return false; @@ -530,6 +534,9 @@ public class MediaFocusControl implements PlayerFocusEnforcer { Log.v(TAG, "notifyExtFocusPolicyFocusRequest client="+afi.getClientId() + " dispatcher=" + fd); } + synchronized (mExtFocusChangeLock) { + afi.setGen(mExtFocusChangeCounter++); + } final FocusRequester existingFr = mFocusOwnersForFocusPolicy.get(afi.getClientId()); if (existingFr != null) { if (!existingFr.hasSameDispatcher(fd)) { @@ -538,8 +545,7 @@ public class MediaFocusControl implements PlayerFocusEnforcer { mFocusOwnersForFocusPolicy.put(afi.getClientId(), new FocusRequester(afi, fd, cb, hdlr, this)); } - } else if (requestResult == AudioManager.AUDIOFOCUS_REQUEST_GRANTED - || requestResult == AudioManager.AUDIOFOCUS_REQUEST_DELAYED) { + } else { // new focus (future) focus owner to keep track of final AudioFocusDeathHandler hdlr = new AudioFocusDeathHandler(cb); mFocusOwnersForFocusPolicy.put(afi.getClientId(), @@ -547,12 +553,25 @@ public class MediaFocusControl implements PlayerFocusEnforcer { } try { //oneway - mFocusPolicy.notifyAudioFocusRequest(afi, requestResult); + mFocusPolicy.notifyAudioFocusRequest(afi, AudioManager.AUDIOFOCUS_REQUEST_GRANTED); + return true; } catch (RemoteException e) { Log.e(TAG, "Can't call notifyAudioFocusRequest() on IAudioPolicyCallback " + mFocusPolicy.asBinder(), e); } - return true; + return false; + } + + void setFocusRequestResultFromExtPolicy(AudioFocusInfo afi, int requestResult) { + synchronized (mExtFocusChangeLock) { + if (afi.getGen() > mExtFocusChangeCounter) { + return; + } + } + final FocusRequester fr = mFocusOwnersForFocusPolicy.get(afi.getClientId()); + if (fr != null) { + fr.dispatchFocusResultFromExtPolicy(requestResult); + } } /** @@ -590,7 +609,12 @@ public class MediaFocusControl implements PlayerFocusEnforcer { if (DEBUG) { Log.v(TAG, "> failed: no focus policy" ); } return AudioManager.AUDIOFOCUS_REQUEST_FAILED; } - final FocusRequester fr = mFocusOwnersForFocusPolicy.get(afi.getClientId()); + final FocusRequester fr; + if (focusChange == AudioManager.AUDIOFOCUS_LOSS) { + fr = mFocusOwnersForFocusPolicy.remove(afi.getClientId()); + } else { + fr = mFocusOwnersForFocusPolicy.get(afi.getClientId()); + } if (fr == null) { if (DEBUG) { Log.v(TAG, "> failed: no such focus requester known" ); } return AudioManager.AUDIOFOCUS_REQUEST_FAILED; @@ -710,9 +734,7 @@ public class MediaFocusControl implements PlayerFocusEnforcer { boolean focusGrantDelayed = false; if (!canReassignAudioFocus()) { if ((flags & AudioManager.AUDIOFOCUS_FLAG_DELAY_OK) == 0) { - final int result = AudioManager.AUDIOFOCUS_REQUEST_FAILED; - notifyExtFocusPolicyFocusRequest_syncAf(afiForExtPolicy, result, fd, cb); - return result; + return AudioManager.AUDIOFOCUS_REQUEST_FAILED; } else { // request has AUDIOFOCUS_FLAG_DELAY_OK: focus can't be // granted right now, so the requester will be inserted in the focus stack @@ -721,12 +743,11 @@ public class MediaFocusControl implements PlayerFocusEnforcer { } } - // external focus policy: delay request for focus gain? - final int resultWithExtPolicy = AudioManager.AUDIOFOCUS_REQUEST_DELAYED; + // external focus policy? if (notifyExtFocusPolicyFocusRequest_syncAf( - afiForExtPolicy, resultWithExtPolicy, fd, cb)) { + afiForExtPolicy, fd, cb)) { // stop handling focus request here as it is handled by external audio focus policy - return resultWithExtPolicy; + return AudioManager.AUDIOFOCUS_REQUEST_WAITING_FOR_EXT_POLICY; } // handle the potential premature death of the new holder of the focus |