am 8638877f: Merge "Support collaborative audio focus handling" into lmp-mr1-dev
* commit '8638877f14f925ec8fc34a24ac4f239e6d5dfd6c': Support collaborative audio focus handling
This commit is contained in:
@@ -330,6 +330,7 @@ LOCAL_SRC_FILES += \
|
|||||||
media/java/android/media/IRemoteVolumeObserver.aidl \
|
media/java/android/media/IRemoteVolumeObserver.aidl \
|
||||||
media/java/android/media/IRingtonePlayer.aidl \
|
media/java/android/media/IRingtonePlayer.aidl \
|
||||||
media/java/android/media/IVolumeController.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/IMediaProjection.aidl \
|
||||||
media/java/android/media/projection/IMediaProjectionCallback.aidl \
|
media/java/android/media/projection/IMediaProjectionCallback.aidl \
|
||||||
media/java/android/media/projection/IMediaProjectionManager.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/MediaDescription.aidl \
|
||||||
frameworks/base/media/java/android/media/Rating.aidl \
|
frameworks/base/media/java/android/media/Rating.aidl \
|
||||||
frameworks/base/media/java/android/media/AudioAttributes.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/PlaybackState.aidl \
|
||||||
frameworks/base/media/java/android/media/session/MediaSession.aidl \
|
frameworks/base/media/java/android/media/session/MediaSession.aidl \
|
||||||
frameworks/base/media/java/android/media/tv/TvInputInfo.aidl \
|
frameworks/base/media/java/android/media/tv/TvInputInfo.aidl \
|
||||||
|
|||||||
18
media/java/android/media/AudioFocusInfo.aidl
Normal file
18
media/java/android/media/AudioFocusInfo.aidl
Normal file
@@ -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;
|
||||||
175
media/java/android/media/AudioFocusInfo.java
Normal file
175
media/java/android/media/AudioFocusInfo.java
Normal file
@@ -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];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -2385,17 +2385,42 @@ public class AudioManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// when adding new flags, add them to the relevant AUDIOFOCUS_FLAGS_APPS or SYSTEM masks
|
// 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
|
@SystemApi
|
||||||
public static final int AUDIOFOCUS_FLAG_DELAY_OK = 0x1 << 0;
|
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
|
@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 */
|
/** @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 */
|
/** @hide */
|
||||||
public static final int AUDIOFOCUS_FLAGS_SYSTEM = AUDIOFOCUS_FLAG_DELAY_OK
|
public static final int AUDIOFOCUS_FLAGS_SYSTEM = AUDIOFOCUS_FLAG_DELAY_OK
|
||||||
| AUDIOFOCUS_FLAG_LOCK;
|
| AUDIOFOCUS_FLAG_PAUSES_ON_DUCKABLE_LOSS | AUDIOFOCUS_FLAG_LOCK;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @hide
|
* @hide
|
||||||
@@ -2417,15 +2442,12 @@ public class AudioManager {
|
|||||||
* usecases such as voice memo recording, or speech recognition.
|
* usecases such as voice memo recording, or speech recognition.
|
||||||
* Use {@link #AUDIOFOCUS_GAIN} for a focus request of unknown duration such
|
* Use {@link #AUDIOFOCUS_GAIN} for a focus request of unknown duration such
|
||||||
* as the playback of a song or a video.
|
* 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
|
* <br>Use 0 when not using any flags for the request, which behaves like
|
||||||
* {@link #requestAudioFocus(OnAudioFocusChangeListener, int, int)}, where either audio
|
* {@link #requestAudioFocus(OnAudioFocusChangeListener, int, int)}, where either audio
|
||||||
* focus is granted immediately, or the grant request fails because the system is in a
|
* 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).
|
* 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}
|
* @return {@link #AUDIOFOCUS_REQUEST_FAILED}, {@link #AUDIOFOCUS_REQUEST_GRANTED}
|
||||||
* or {@link #AUDIOFOCUS_REQUEST_DELAYED}.
|
* or {@link #AUDIOFOCUS_REQUEST_DELAYED}.
|
||||||
* The return value is never {@link #AUDIOFOCUS_REQUEST_DELAYED} when focus is requested
|
* 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
|
* @param durationHint see the description of the same parameter in
|
||||||
* {@link #requestAudioFocus(OnAudioFocusChangeListener, AudioAttributes, int, int)}
|
* {@link #requestAudioFocus(OnAudioFocusChangeListener, AudioAttributes, int, int)}
|
||||||
* @param flags 0 or a combination of {link #AUDIOFOCUS_FLAG_DELAY_OK},
|
* @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
|
* <br>Use 0 when not using any flags for the request, which behaves like
|
||||||
* {@link #requestAudioFocus(OnAudioFocusChangeListener, int, int)}, where either audio
|
* {@link #requestAudioFocus(OnAudioFocusChangeListener, int, int)}, where either audio
|
||||||
* focus is granted immediately, or the grant request fails because the system is in a
|
* 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).
|
* 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
|
* @param ap a registered {@link android.media.audiopolicy.AudioPolicy} instance when locking
|
||||||
* focus, or null.
|
* focus, or null.
|
||||||
* @return see the description of the same return value in
|
* @return see the description of the same return value in
|
||||||
@@ -2510,7 +2526,7 @@ public class AudioManager {
|
|||||||
status = service.requestAudioFocus(requestAttributes, durationHint, mICallBack,
|
status = service.requestAudioFocus(requestAttributes, durationHint, mICallBack,
|
||||||
mAudioFocusDispatcher, getIdForAudioFocusListener(l),
|
mAudioFocusDispatcher, getIdForAudioFocusListener(l),
|
||||||
mContext.getOpPackageName() /* package name */, flags,
|
mContext.getOpPackageName() /* package name */, flags,
|
||||||
ap != null ? ap.token() : null);
|
ap != null ? ap.cb() : null);
|
||||||
} catch (RemoteException e) {
|
} catch (RemoteException e) {
|
||||||
Log.e(TAG, "Can't call requestAudioFocus() on AudioService:", e);
|
Log.e(TAG, "Can't call requestAudioFocus() on AudioService:", e);
|
||||||
}
|
}
|
||||||
@@ -2887,7 +2903,8 @@ public class AudioManager {
|
|||||||
}
|
}
|
||||||
IAudioService service = getService();
|
IAudioService service = getService();
|
||||||
try {
|
try {
|
||||||
String regId = service.registerAudioPolicy(policy.getConfig(), policy.token());
|
String regId = service.registerAudioPolicy(policy.getConfig(), policy.cb(),
|
||||||
|
policy.hasFocusListener());
|
||||||
if (regId == null) {
|
if (regId == null) {
|
||||||
return ERROR;
|
return ERROR;
|
||||||
} else {
|
} else {
|
||||||
@@ -2912,7 +2929,7 @@ public class AudioManager {
|
|||||||
}
|
}
|
||||||
IAudioService service = getService();
|
IAudioService service = getService();
|
||||||
try {
|
try {
|
||||||
service.unregisterAudioPolicyAsync(policy.token());
|
service.unregisterAudioPolicyAsync(policy.cb());
|
||||||
policy.setRegistration(null);
|
policy.setRegistration(null);
|
||||||
} catch (RemoteException e) {
|
} catch (RemoteException e) {
|
||||||
Log.e(TAG, "Dead object in unregisterAudioPolicyAsync()", e);
|
Log.e(TAG, "Dead object in unregisterAudioPolicyAsync()", e);
|
||||||
|
|||||||
@@ -51,6 +51,7 @@ import android.media.MediaPlayer.OnErrorListener;
|
|||||||
import android.media.audiopolicy.AudioMix;
|
import android.media.audiopolicy.AudioMix;
|
||||||
import android.media.audiopolicy.AudioPolicy;
|
import android.media.audiopolicy.AudioPolicy;
|
||||||
import android.media.audiopolicy.AudioPolicyConfig;
|
import android.media.audiopolicy.AudioPolicyConfig;
|
||||||
|
import android.media.audiopolicy.IAudioPolicyCallback;
|
||||||
import android.os.Binder;
|
import android.os.Binder;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.Environment;
|
import android.os.Environment;
|
||||||
@@ -5098,7 +5099,7 @@ public class AudioService extends IAudioService.Stub {
|
|||||||
//==========================================================================================
|
//==========================================================================================
|
||||||
public int requestAudioFocus(AudioAttributes aa, int durationHint, IBinder cb,
|
public int requestAudioFocus(AudioAttributes aa, int durationHint, IBinder cb,
|
||||||
IAudioFocusDispatcher fd, String clientId, String callingPackageName, int flags,
|
IAudioFocusDispatcher fd, String clientId, String callingPackageName, int flags,
|
||||||
IBinder policyToken) {
|
IAudioPolicyCallback pcb) {
|
||||||
// permission checks
|
// permission checks
|
||||||
if ((flags & AudioManager.AUDIOFOCUS_FLAG_LOCK) == AudioManager.AUDIOFOCUS_FLAG_LOCK) {
|
if ((flags & AudioManager.AUDIOFOCUS_FLAG_LOCK) == AudioManager.AUDIOFOCUS_FLAG_LOCK) {
|
||||||
if (mMediaFocusControl.IN_VOICE_COMM_FOCUS_ID.equals(clientId)) {
|
if (mMediaFocusControl.IN_VOICE_COMM_FOCUS_ID.equals(clientId)) {
|
||||||
@@ -5110,9 +5111,8 @@ public class AudioService extends IAudioService.Stub {
|
|||||||
} else {
|
} else {
|
||||||
// only a registered audio policy can be used to lock focus
|
// only a registered audio policy can be used to lock focus
|
||||||
synchronized (mAudioPolicies) {
|
synchronized (mAudioPolicies) {
|
||||||
if (!mAudioPolicies.containsKey(policyToken)) {
|
if (!mAudioPolicies.containsKey(pcb.asBinder())) {
|
||||||
Log.e(TAG, "Invalid unregistered AudioPolicy to (un)lock audio focus",
|
Log.e(TAG, "Invalid unregistered AudioPolicy to (un)lock audio focus");
|
||||||
new Exception());
|
|
||||||
return AudioManager.AUDIOFOCUS_REQUEST_FAILED;
|
return AudioManager.AUDIOFOCUS_REQUEST_FAILED;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -5812,30 +5812,34 @@ public class AudioService extends IAudioService.Stub {
|
|||||||
//==========================================================================================
|
//==========================================================================================
|
||||||
// Audio policy management
|
// Audio policy management
|
||||||
//==========================================================================================
|
//==========================================================================================
|
||||||
public String registerAudioPolicy(AudioPolicyConfig policyConfig, IBinder cb) {
|
public String registerAudioPolicy(AudioPolicyConfig policyConfig, IAudioPolicyCallback pcb,
|
||||||
//Log.v(TAG, "registerAudioPolicy for " + cb + " got policy:" + policyConfig);
|
boolean hasFocusListener) {
|
||||||
|
if (DEBUG_AP) Log.d(TAG, "registerAudioPolicy for " + pcb.asBinder()
|
||||||
|
+ " with config:" + policyConfig);
|
||||||
String regId = null;
|
String regId = null;
|
||||||
|
// error handling
|
||||||
boolean hasPermissionForPolicy =
|
boolean hasPermissionForPolicy =
|
||||||
(PackageManager.PERMISSION_GRANTED == mContext.checkCallingOrSelfPermission(
|
(PackageManager.PERMISSION_GRANTED == mContext.checkCallingPermission(
|
||||||
android.Manifest.permission.MODIFY_AUDIO_ROUTING));
|
android.Manifest.permission.MODIFY_AUDIO_ROUTING));
|
||||||
if (!hasPermissionForPolicy) {
|
if (!hasPermissionForPolicy) {
|
||||||
Slog.w(TAG, "Can't register audio policy for pid " + Binder.getCallingPid() + " / uid "
|
Slog.w(TAG, "Can't register audio policy for pid " + Binder.getCallingPid() + " / uid "
|
||||||
+ Binder.getCallingUid() + ", need MODIFY_AUDIO_ROUTING");
|
+ Binder.getCallingUid() + ", need MODIFY_AUDIO_ROUTING");
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
synchronized (mAudioPolicies) {
|
synchronized (mAudioPolicies) {
|
||||||
try {
|
try {
|
||||||
if (mAudioPolicies.containsKey(cb)) {
|
if (mAudioPolicies.containsKey(pcb.asBinder())) {
|
||||||
Slog.e(TAG, "Cannot re-register policy");
|
Slog.e(TAG, "Cannot re-register policy");
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
AudioPolicyProxy app = new AudioPolicyProxy(policyConfig, cb);
|
AudioPolicyProxy app = new AudioPolicyProxy(policyConfig, pcb, hasFocusListener);
|
||||||
cb.linkToDeath(app, 0/*flags*/);
|
pcb.asBinder().linkToDeath(app, 0/*flags*/);
|
||||||
regId = app.connectMixes();
|
regId = app.getRegistrationId();
|
||||||
mAudioPolicies.put(cb, app);
|
mAudioPolicies.put(pcb.asBinder(), app);
|
||||||
} catch (RemoteException e) {
|
} catch (RemoteException e) {
|
||||||
// audio policy owner has already died!
|
// 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);
|
" binder death", e);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -5843,21 +5847,58 @@ public class AudioService extends IAudioService.Stub {
|
|||||||
return regId;
|
return regId;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void unregisterAudioPolicyAsync(IBinder cb) {
|
public void unregisterAudioPolicyAsync(IAudioPolicyCallback pcb) {
|
||||||
|
if (DEBUG_AP) Log.d(TAG, "unregisterAudioPolicyAsync for " + pcb.asBinder());
|
||||||
synchronized (mAudioPolicies) {
|
synchronized (mAudioPolicies) {
|
||||||
AudioPolicyProxy app = mAudioPolicies.remove(cb);
|
AudioPolicyProxy app = mAudioPolicies.remove(pcb.asBinder());
|
||||||
if (app == null) {
|
if (app == null) {
|
||||||
Slog.w(TAG, "Trying to unregister unknown audio policy for pid "
|
Slog.w(TAG, "Trying to unregister unknown audio policy for pid "
|
||||||
+ Binder.getCallingPid() + " / uid " + Binder.getCallingUid());
|
+ Binder.getCallingPid() + " / uid " + Binder.getCallingUid());
|
||||||
return;
|
return;
|
||||||
} else {
|
} 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
|
// 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) {
|
private void dumpAudioPolicies(PrintWriter pw) {
|
||||||
pw.println("\nAudio policies:");
|
pw.println("\nAudio policies:");
|
||||||
synchronized (mAudioPolicies) {
|
synchronized (mAudioPolicies) {
|
||||||
@@ -5877,27 +5918,48 @@ public class AudioService extends IAudioService.Stub {
|
|||||||
public class AudioPolicyProxy extends AudioPolicyConfig implements IBinder.DeathRecipient {
|
public class AudioPolicyProxy extends AudioPolicyConfig implements IBinder.DeathRecipient {
|
||||||
private static final String TAG = "AudioPolicyProxy";
|
private static final String TAG = "AudioPolicyProxy";
|
||||||
AudioPolicyConfig mConfig;
|
AudioPolicyConfig mConfig;
|
||||||
IBinder mToken;
|
IAudioPolicyCallback mPolicyToken;
|
||||||
AudioPolicyProxy(AudioPolicyConfig config, IBinder token) {
|
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);
|
super(config);
|
||||||
setRegistration(new String(config.hashCode() + ":ap:" + mAudioPolicyCounter++));
|
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() {
|
public void binderDied() {
|
||||||
synchronized (mAudioPolicies) {
|
synchronized (mAudioPolicies) {
|
||||||
Log.i(TAG, "audio policy " + mToken + " died");
|
Log.i(TAG, "audio policy " + mPolicyToken + " died");
|
||||||
disconnectMixes();
|
release();
|
||||||
mAudioPolicies.remove(mToken);
|
mAudioPolicies.remove(mPolicyToken.asBinder());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
String connectMixes() {
|
String getRegistrationId() {
|
||||||
updateMixes(AudioSystem.DEVICE_STATE_AVAILABLE);
|
|
||||||
return getRegistration();
|
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);
|
updateMixes(AudioSystem.DEVICE_STATE_UNAVAILABLE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -16,6 +16,7 @@
|
|||||||
|
|
||||||
package android.media;
|
package android.media;
|
||||||
|
|
||||||
|
import android.annotation.NonNull;
|
||||||
import android.media.MediaFocusControl.AudioFocusDeathHandler;
|
import android.media.MediaFocusControl.AudioFocusDeathHandler;
|
||||||
import android.os.IBinder;
|
import android.os.IBinder;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
@@ -40,6 +41,7 @@ class FocusRequester {
|
|||||||
private final String mClientId;
|
private final String mClientId;
|
||||||
private final String mPackageName;
|
private final String mPackageName;
|
||||||
private final int mCallingUid;
|
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.
|
* 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;
|
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,
|
FocusRequester(AudioAttributes aa, int focusRequest, int grantFlags,
|
||||||
IAudioFocusDispatcher afl, IBinder source, String id, AudioFocusDeathHandler hdlr,
|
IAudioFocusDispatcher afl, IBinder source, String id, AudioFocusDeathHandler hdlr,
|
||||||
String pn, int uid) {
|
String pn, int uid, @NonNull MediaFocusControl ctlr) {
|
||||||
mAttributes = aa;
|
mAttributes = aa;
|
||||||
mFocusDispatcher = afl;
|
mFocusDispatcher = afl;
|
||||||
mSourceRef = source;
|
mSourceRef = source;
|
||||||
@@ -72,6 +87,7 @@ class FocusRequester {
|
|||||||
mFocusGainRequest = focusRequest;
|
mFocusGainRequest = focusRequest;
|
||||||
mGrantFlags = grantFlags;
|
mGrantFlags = grantFlags;
|
||||||
mFocusLossReceived = AudioManager.AUDIOFOCUS_NONE;
|
mFocusLossReceived = AudioManager.AUDIOFOCUS_NONE;
|
||||||
|
mFocusController = ctlr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -153,9 +169,17 @@ class FocusRequester {
|
|||||||
|
|
||||||
private static String flagsToString(int flags) {
|
private static String flagsToString(int flags) {
|
||||||
String msg = new String();
|
String msg = new String();
|
||||||
if ((flags & AudioManager.AUDIOFOCUS_FLAG_DELAY_OK) != 0) { msg += "DELAY_OK"; }
|
if ((flags & AudioManager.AUDIOFOCUS_FLAG_DELAY_OK) != 0) {
|
||||||
if (!msg.isEmpty()) { msg += "|"; }
|
msg += "DELAY_OK";
|
||||||
if ((flags & AudioManager.AUDIOFOCUS_FLAG_LOCK) != 0) { msg += "LOCK"; }
|
}
|
||||||
|
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;
|
return msg;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -230,13 +254,22 @@ class FocusRequester {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called synchronized on MediaFocusControl.mAudioFocusLock
|
||||||
|
*/
|
||||||
void handleExternalFocusGain(int focusGain) {
|
void handleExternalFocusGain(int focusGain) {
|
||||||
int focusLoss = focusLossForGainRequest(focusGain);
|
int focusLoss = focusLossForGainRequest(focusGain);
|
||||||
handleFocusLoss(focusLoss);
|
handleFocusLoss(focusLoss);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called synchronized on MediaFocusControl.mAudioFocusLock
|
||||||
|
*/
|
||||||
void handleFocusGain(int focusGain) {
|
void handleFocusGain(int focusGain) {
|
||||||
try {
|
try {
|
||||||
|
mFocusLossReceived = AudioManager.AUDIOFOCUS_NONE;
|
||||||
|
mFocusController.notifyExtPolicyFocusGrant_syncAf(toAudioFocusInfo(),
|
||||||
|
AudioManager.AUDIOFOCUS_REQUEST_GRANTED);
|
||||||
if (mFocusDispatcher != null) {
|
if (mFocusDispatcher != null) {
|
||||||
if (DEBUG) {
|
if (DEBUG) {
|
||||||
Log.v(TAG, "dispatching " + focusChangeToString(focusGain) + " to "
|
Log.v(TAG, "dispatching " + focusChangeToString(focusGain) + " to "
|
||||||
@@ -244,27 +277,52 @@ class FocusRequester {
|
|||||||
}
|
}
|
||||||
mFocusDispatcher.dispatchAudioFocusChange(focusGain, mClientId);
|
mFocusDispatcher.dispatchAudioFocusChange(focusGain, mClientId);
|
||||||
}
|
}
|
||||||
mFocusLossReceived = AudioManager.AUDIOFOCUS_NONE;
|
|
||||||
} catch (android.os.RemoteException e) {
|
} catch (android.os.RemoteException e) {
|
||||||
Log.e(TAG, "Failure to signal gain of audio focus due to: ", e);
|
Log.e(TAG, "Failure to signal gain of audio focus due to: ", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called synchronized on MediaFocusControl.mAudioFocusLock
|
||||||
|
*/
|
||||||
void handleFocusLoss(int focusLoss) {
|
void handleFocusLoss(int focusLoss) {
|
||||||
try {
|
try {
|
||||||
if (focusLoss != mFocusLossReceived) {
|
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 (mFocusDispatcher != null) {
|
||||||
if (DEBUG) {
|
if (DEBUG) {
|
||||||
Log.v(TAG, "dispatching " + focusChangeToString(focusLoss) + " to "
|
Log.v(TAG, "dispatching " + focusChangeToString(mFocusLossReceived) + " to "
|
||||||
+ mClientId);
|
+ mClientId);
|
||||||
}
|
}
|
||||||
mFocusDispatcher.dispatchAudioFocusChange(focusLoss, mClientId);
|
mFocusController.notifyExtPolicyFocusLoss_syncAf(
|
||||||
|
toAudioFocusInfo(), true /* wasDispatched */);
|
||||||
|
mFocusDispatcher.dispatchAudioFocusChange(mFocusLossReceived, mClientId);
|
||||||
}
|
}
|
||||||
mFocusLossReceived = focusLoss;
|
|
||||||
}
|
}
|
||||||
} catch (android.os.RemoteException e) {
|
} catch (android.os.RemoteException e) {
|
||||||
Log.e(TAG, "Failure to signal loss of audio focus due to:", 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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ import android.media.IRingtonePlayer;
|
|||||||
import android.media.IVolumeController;
|
import android.media.IVolumeController;
|
||||||
import android.media.Rating;
|
import android.media.Rating;
|
||||||
import android.media.audiopolicy.AudioPolicyConfig;
|
import android.media.audiopolicy.AudioPolicyConfig;
|
||||||
|
import android.media.audiopolicy.IAudioPolicyCallback;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.view.KeyEvent;
|
import android.view.KeyEvent;
|
||||||
|
|
||||||
@@ -123,7 +124,7 @@ interface IAudioService {
|
|||||||
|
|
||||||
int requestAudioFocus(in AudioAttributes aa, int durationHint, IBinder cb,
|
int requestAudioFocus(in AudioAttributes aa, int durationHint, IBinder cb,
|
||||||
IAudioFocusDispatcher fd, String clientId, String callingPackageName, int flags,
|
IAudioFocusDispatcher fd, String clientId, String callingPackageName, int flags,
|
||||||
IBinder policyToken);
|
IAudioPolicyCallback pcb);
|
||||||
|
|
||||||
int abandonAudioFocus(IAudioFocusDispatcher fd, String clientId, in AudioAttributes aa);
|
int abandonAudioFocus(IAudioFocusDispatcher fd, String clientId, in AudioAttributes aa);
|
||||||
|
|
||||||
@@ -213,6 +214,9 @@ interface IAudioService {
|
|||||||
|
|
||||||
boolean isHdmiSystemAudioSupported();
|
boolean isHdmiSystemAudioSupported();
|
||||||
|
|
||||||
String registerAudioPolicy(in AudioPolicyConfig policyConfig, IBinder cb);
|
String registerAudioPolicy(in AudioPolicyConfig policyConfig,
|
||||||
oneway void unregisterAudioPolicyAsync(in IBinder cb);
|
in IAudioPolicyCallback pcb, boolean hasFocusListener);
|
||||||
|
oneway void unregisterAudioPolicyAsync(in IAudioPolicyCallback pcb);
|
||||||
|
|
||||||
|
int setFocusPropertiesForPolicy(int duckingBehavior, in IAudioPolicyCallback pcb);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ import android.content.IntentFilter;
|
|||||||
import android.content.pm.PackageManager;
|
import android.content.pm.PackageManager;
|
||||||
import android.database.ContentObserver;
|
import android.database.ContentObserver;
|
||||||
import android.media.PlayerRecord.RemotePlaybackState;
|
import android.media.PlayerRecord.RemotePlaybackState;
|
||||||
|
import android.media.audiopolicy.IAudioPolicyCallback;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Binder;
|
import android.os.Binder;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
@@ -434,6 +435,9 @@ public class MediaFocusControl implements OnFinished {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called synchronized on mAudioFocusLock
|
||||||
|
*/
|
||||||
private void notifyTopOfAudioFocusStack() {
|
private void notifyTopOfAudioFocusStack() {
|
||||||
// notify the top of the stack it gained focus
|
// notify the top of the stack it gained focus
|
||||||
if (!mFocusStack.empty()) {
|
if (!mFocusStack.empty()) {
|
||||||
@@ -470,6 +474,7 @@ public class MediaFocusControl implements OnFinished {
|
|||||||
stackIterator.next().dump(pw);
|
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
|
* @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.
|
* 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)
|
// is the current top of the focus stack abandoning focus? (because of request, not death)
|
||||||
if (!mFocusStack.empty() && mFocusStack.peek().hasSameClient(clientToRemove))
|
if (!mFocusStack.empty() && mFocusStack.peek().hasSameClient(clientToRemove))
|
||||||
{
|
{
|
||||||
//Log.i(TAG, " removeFocusStackEntry() removing top of stack");
|
//Log.i(TAG, " removeFocusStackEntry() removing top of stack");
|
||||||
FocusRequester fr = mFocusStack.pop();
|
FocusRequester fr = mFocusStack.pop();
|
||||||
fr.release();
|
fr.release();
|
||||||
|
if (notifyFocusFollowers) {
|
||||||
|
final AudioFocusInfo afi = fr.toAudioFocusInfo();
|
||||||
|
afi.clearLossReceived();
|
||||||
|
notifyExtPolicyFocusLoss_syncAf(afi, false);
|
||||||
|
}
|
||||||
if (signal) {
|
if (signal) {
|
||||||
// notify the new top of the stack it gained focus
|
// notify the new top of the stack it gained focus
|
||||||
notifyTopOfAudioFocusStack();
|
notifyTopOfAudioFocusStack();
|
||||||
@@ -610,6 +621,86 @@ public class MediaFocusControl implements OnFinished {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates whether to notify an audio focus owner when it loses focus
|
||||||
|
* with {@link AudioManager#AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK} if it will only duck.
|
||||||
|
* This variable being false indicates an AudioPolicy has been registered and has signaled
|
||||||
|
* it will handle audio ducking.
|
||||||
|
*/
|
||||||
|
private boolean mNotifyFocusOwnerOnDuck = true;
|
||||||
|
|
||||||
|
protected void setDuckingInExtPolicyAvailable(boolean available) {
|
||||||
|
mNotifyFocusOwnerOnDuck = !available;
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean mustNotifyFocusOwnerOnDuck() { return mNotifyFocusOwnerOnDuck; }
|
||||||
|
|
||||||
|
private ArrayList<IAudioPolicyCallback> mFocusFollowers = new ArrayList<IAudioPolicyCallback>();
|
||||||
|
|
||||||
|
void addFocusFollower(IAudioPolicyCallback ff) {
|
||||||
|
if (ff == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
synchronized(mAudioFocusLock) {
|
||||||
|
boolean found = false;
|
||||||
|
for (IAudioPolicyCallback pcb : mFocusFollowers) {
|
||||||
|
if (pcb.asBinder().equals(ff.asBinder())) {
|
||||||
|
found = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (found) {
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
mFocusFollowers.add(ff);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void removeFocusFollower(IAudioPolicyCallback ff) {
|
||||||
|
if (ff == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
synchronized(mAudioFocusLock) {
|
||||||
|
for (IAudioPolicyCallback pcb : mFocusFollowers) {
|
||||||
|
if (pcb.asBinder().equals(ff.asBinder())) {
|
||||||
|
mFocusFollowers.remove(pcb);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called synchronized on mAudioFocusLock
|
||||||
|
*/
|
||||||
|
void notifyExtPolicyFocusGrant_syncAf(AudioFocusInfo afi, int requestResult) {
|
||||||
|
for (IAudioPolicyCallback pcb : mFocusFollowers) {
|
||||||
|
try {
|
||||||
|
// oneway
|
||||||
|
pcb.notifyAudioFocusGrant(afi, requestResult);
|
||||||
|
} catch (RemoteException e) {
|
||||||
|
Log.e(TAG, "Can't call newAudioFocusLoser() on IAudioPolicyCallback "
|
||||||
|
+ pcb.asBinder(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called synchronized on mAudioFocusLock
|
||||||
|
*/
|
||||||
|
void notifyExtPolicyFocusLoss_syncAf(AudioFocusInfo afi, boolean wasDispatched) {
|
||||||
|
for (IAudioPolicyCallback pcb : mFocusFollowers) {
|
||||||
|
try {
|
||||||
|
// oneway
|
||||||
|
pcb.notifyAudioFocusLoss(afi, wasDispatched);
|
||||||
|
} catch (RemoteException e) {
|
||||||
|
Log.e(TAG, "Can't call newAudioFocusLoser() on IAudioPolicyCallback "
|
||||||
|
+ pcb.asBinder(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
protected int getCurrentAudioFocus() {
|
protected int getCurrentAudioFocus() {
|
||||||
synchronized(mAudioFocusLock) {
|
synchronized(mAudioFocusLock) {
|
||||||
if (mFocusStack.empty()) {
|
if (mFocusStack.empty()) {
|
||||||
@@ -669,6 +760,8 @@ public class MediaFocusControl implements OnFinished {
|
|||||||
// unlink death handler so it can be gc'ed.
|
// unlink death handler so it can be gc'ed.
|
||||||
// linkToDeath() creates a JNI global reference preventing collection.
|
// linkToDeath() creates a JNI global reference preventing collection.
|
||||||
cb.unlinkToDeath(afdh, 0);
|
cb.unlinkToDeath(afdh, 0);
|
||||||
|
notifyExtPolicyFocusGrant_syncAf(fr.toAudioFocusInfo(),
|
||||||
|
AudioManager.AUDIOFOCUS_REQUEST_GRANTED);
|
||||||
return AudioManager.AUDIOFOCUS_REQUEST_GRANTED;
|
return AudioManager.AUDIOFOCUS_REQUEST_GRANTED;
|
||||||
}
|
}
|
||||||
// the reason for the audio focus request has changed: remove the current top of
|
// 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
|
// 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,
|
final FocusRequester nfr = new FocusRequester(aa, focusChangeHint, flags, fd, cb,
|
||||||
clientId, afdh, callingPackageName, Binder.getCallingUid());
|
clientId, afdh, callingPackageName, Binder.getCallingUid(), this);
|
||||||
if (focusGrantDelayed) {
|
if (focusGrantDelayed) {
|
||||||
// focusGrantDelayed being true implies we can't reassign focus right now
|
// focusGrantDelayed being true implies we can't reassign focus right now
|
||||||
// which implies the focus stack is not empty.
|
// 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 {
|
} else {
|
||||||
// propagate the focus change through the stack
|
// propagate the focus change through the stack
|
||||||
if (!mFocusStack.empty()) {
|
if (!mFocusStack.empty()) {
|
||||||
@@ -698,6 +795,8 @@ public class MediaFocusControl implements OnFinished {
|
|||||||
// push focus requester at the top of the audio focus stack
|
// push focus requester at the top of the audio focus stack
|
||||||
mFocusStack.push(nfr);
|
mFocusStack.push(nfr);
|
||||||
}
|
}
|
||||||
|
notifyExtPolicyFocusGrant_syncAf(nfr.toAudioFocusInfo(),
|
||||||
|
AudioManager.AUDIOFOCUS_REQUEST_GRANTED);
|
||||||
|
|
||||||
}//synchronized(mAudioFocusLock)
|
}//synchronized(mAudioFocusLock)
|
||||||
|
|
||||||
@@ -713,7 +812,7 @@ public class MediaFocusControl implements OnFinished {
|
|||||||
try {
|
try {
|
||||||
// this will take care of notifying the new focus owner if needed
|
// this will take care of notifying the new focus owner if needed
|
||||||
synchronized(mAudioFocusLock) {
|
synchronized(mAudioFocusLock) {
|
||||||
removeFocusStackEntry(clientId, true /*signal*/);
|
removeFocusStackEntry(clientId, true /*signal*/, true /*notifyFocusFollowers*/);
|
||||||
}
|
}
|
||||||
} catch (java.util.ConcurrentModificationException cme) {
|
} catch (java.util.ConcurrentModificationException cme) {
|
||||||
// Catching this exception here is temporary. It is here just to prevent
|
// 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) {
|
protected void unregisterAudioFocusClient(String clientId) {
|
||||||
synchronized(mAudioFocusLock) {
|
synchronized(mAudioFocusLock) {
|
||||||
removeFocusStackEntry(clientId, false);
|
removeFocusStackEntry(clientId, false, true /*notifyFocusFollowers*/);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -22,16 +22,20 @@ import android.annotation.SystemApi;
|
|||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.pm.PackageManager;
|
import android.content.pm.PackageManager;
|
||||||
import android.media.AudioAttributes;
|
import android.media.AudioAttributes;
|
||||||
|
import android.media.AudioFocusInfo;
|
||||||
import android.media.AudioFormat;
|
import android.media.AudioFormat;
|
||||||
import android.media.AudioManager;
|
import android.media.AudioManager;
|
||||||
import android.media.AudioRecord;
|
import android.media.AudioRecord;
|
||||||
import android.media.AudioTrack;
|
import android.media.AudioTrack;
|
||||||
|
import android.media.IAudioService;
|
||||||
import android.media.MediaRecorder;
|
import android.media.MediaRecorder;
|
||||||
import android.os.Binder;
|
import android.os.Binder;
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
import android.os.IBinder;
|
import android.os.IBinder;
|
||||||
import android.os.Looper;
|
import android.os.Looper;
|
||||||
import android.os.Message;
|
import android.os.Message;
|
||||||
|
import android.os.RemoteException;
|
||||||
|
import android.os.ServiceManager;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.util.Slog;
|
import android.util.Slog;
|
||||||
|
|
||||||
@@ -47,6 +51,8 @@ import java.util.ArrayList;
|
|||||||
public class AudioPolicy {
|
public class AudioPolicy {
|
||||||
|
|
||||||
private static final String TAG = "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.
|
* 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 String mRegistrationId;
|
||||||
private AudioPolicyStatusListener mStatusListener;
|
private AudioPolicyStatusListener mStatusListener;
|
||||||
|
|
||||||
private final IBinder mToken = new Binder();
|
/**
|
||||||
/** @hide */
|
* The behavior of a policy with regards to audio focus where it relies on the application
|
||||||
public IBinder token() { return mToken; }
|
* 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 Context mContext;
|
||||||
|
|
||||||
private AudioPolicyConfig mConfig;
|
private AudioPolicyConfig mConfig;
|
||||||
|
|
||||||
/** @hide */
|
/** @hide */
|
||||||
public AudioPolicyConfig getConfig() { return mConfig; }
|
public AudioPolicyConfig getConfig() { return mConfig; }
|
||||||
|
/** @hide */
|
||||||
|
public boolean hasFocusListener() { return mFocusListener != null; }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The parameter is guaranteed non-null through the Builder
|
* 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;
|
mConfig = config;
|
||||||
mStatus = POLICY_STATUS_UNREGISTERED;
|
mStatus = POLICY_STATUS_UNREGISTERED;
|
||||||
mContext = context;
|
mContext = context;
|
||||||
@@ -88,6 +114,8 @@ public class AudioPolicy {
|
|||||||
mEventHandler = null;
|
mEventHandler = null;
|
||||||
Log.e(TAG, "No event handler due to looper without a thread");
|
Log.e(TAG, "No event handler due to looper without a thread");
|
||||||
}
|
}
|
||||||
|
mFocusListener = fl;
|
||||||
|
mStatusListener = sl;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -98,11 +126,14 @@ public class AudioPolicy {
|
|||||||
private ArrayList<AudioMix> mMixes;
|
private ArrayList<AudioMix> mMixes;
|
||||||
private Context mContext;
|
private Context mContext;
|
||||||
private Looper mLooper;
|
private Looper mLooper;
|
||||||
|
private AudioPolicyFocusListener mFocusListener;
|
||||||
|
private AudioPolicyStatusListener mStatusListener;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructs a new Builder with no audio mixes.
|
* Constructs a new Builder with no audio mixes.
|
||||||
* @param context the context for the policy
|
* @param context the context for the policy
|
||||||
*/
|
*/
|
||||||
|
@SystemApi
|
||||||
public Builder(Context context) {
|
public Builder(Context context) {
|
||||||
mMixes = new ArrayList<AudioMix>();
|
mMixes = new ArrayList<AudioMix>();
|
||||||
mContext = context;
|
mContext = context;
|
||||||
@@ -114,6 +145,7 @@ public class AudioPolicy {
|
|||||||
* @return the same Builder instance.
|
* @return the same Builder instance.
|
||||||
* @throws IllegalArgumentException
|
* @throws IllegalArgumentException
|
||||||
*/
|
*/
|
||||||
|
@SystemApi
|
||||||
public Builder addMix(@NonNull AudioMix mix) throws IllegalArgumentException {
|
public Builder addMix(@NonNull AudioMix mix) throws IllegalArgumentException {
|
||||||
if (mix == null) {
|
if (mix == null) {
|
||||||
throw new IllegalArgumentException("Illegal null AudioMix argument");
|
throw new IllegalArgumentException("Illegal null AudioMix argument");
|
||||||
@@ -128,6 +160,7 @@ public class AudioPolicy {
|
|||||||
* @return the same Builder instance.
|
* @return the same Builder instance.
|
||||||
* @throws IllegalArgumentException
|
* @throws IllegalArgumentException
|
||||||
*/
|
*/
|
||||||
|
@SystemApi
|
||||||
public Builder setLooper(@NonNull Looper looper) throws IllegalArgumentException {
|
public Builder setLooper(@NonNull Looper looper) throws IllegalArgumentException {
|
||||||
if (looper == null) {
|
if (looper == null) {
|
||||||
throw new IllegalArgumentException("Illegal null Looper argument");
|
throw new IllegalArgumentException("Illegal null Looper argument");
|
||||||
@@ -136,34 +169,58 @@ public class AudioPolicy {
|
|||||||
return this;
|
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() {
|
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) {
|
public void setRegistration(String regId) {
|
||||||
mRegistrationId = regId;
|
synchronized (mLock) {
|
||||||
mConfig.setRegistration(regId);
|
mRegistrationId = regId;
|
||||||
if (regId != null) {
|
mConfig.setRegistration(regId);
|
||||||
mStatus = POLICY_STATUS_REGISTERED;
|
if (regId != null) {
|
||||||
} else {
|
mStatus = POLICY_STATUS_REGISTERED;
|
||||||
mStatus = POLICY_STATUS_UNREGISTERED;
|
} else {
|
||||||
|
mStatus = POLICY_STATUS_UNREGISTERED;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
sendMsg(mEventHandler, MSG_POLICY_STATUS_CHANGE);
|
sendMsg(MSG_POLICY_STATUS_CHANGE);
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean policyReadyToUse() {
|
private boolean policyReadyToUse() {
|
||||||
if (mStatus != POLICY_STATUS_REGISTERED) {
|
synchronized (mLock) {
|
||||||
Log.e(TAG, "Cannot use unregistered AudioPolicy");
|
if (mStatus != POLICY_STATUS_REGISTERED) {
|
||||||
return false;
|
Log.e(TAG, "Cannot use unregistered AudioPolicy");
|
||||||
}
|
return false;
|
||||||
if (mContext == null) {
|
}
|
||||||
Log.e(TAG, "Cannot use AudioPolicy without context");
|
if (mContext == null) {
|
||||||
return false;
|
Log.e(TAG, "Cannot use AudioPolicy without context");
|
||||||
}
|
return false;
|
||||||
if (mRegistrationId == null) {
|
}
|
||||||
Log.e(TAG, "Cannot use unregistered AudioPolicy");
|
if (mRegistrationId == null) {
|
||||||
return false;
|
Log.e(TAG, "Cannot use unregistered AudioPolicy");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (!(PackageManager.PERMISSION_GRANTED == mContext.checkCallingOrSelfPermission(
|
if (!(PackageManager.PERMISSION_GRANTED == mContext.checkCallingOrSelfPermission(
|
||||||
android.Manifest.permission.MODIFY_AUDIO_ROUTING))) {
|
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}.
|
* 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
|
* Audio buffers recorded through the created instance will contain the mix of the audio
|
||||||
@@ -282,21 +393,53 @@ public class AudioPolicy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@SystemApi
|
@SystemApi
|
||||||
synchronized public void setAudioPolicyStatusListener(AudioPolicyStatusListener l) {
|
public static abstract class AudioPolicyFocusListener {
|
||||||
mStatusListener = l;
|
public void onAudioFocusGrant(AudioFocusInfo afi, int requestResult) {}
|
||||||
|
public void onAudioFocusLoss(AudioFocusInfo afi, boolean wasNotified) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
synchronized private void onPolicyStatusChange() {
|
private void onPolicyStatusChange() {
|
||||||
if (mStatusListener == null) {
|
AudioPolicyStatusListener l;
|
||||||
return;
|
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
|
// Event handling
|
||||||
private final EventHandler mEventHandler;
|
private final EventHandler mEventHandler;
|
||||||
private final static int MSG_POLICY_STATUS_CHANGE = 0;
|
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 {
|
private class EventHandler extends Handler {
|
||||||
public EventHandler(AudioPolicy ap, Looper looper) {
|
public EventHandler(AudioPolicy ap, Looper looper) {
|
||||||
@@ -309,6 +452,18 @@ public class AudioPolicy {
|
|||||||
case MSG_POLICY_STATUS_CHANGE:
|
case MSG_POLICY_STATUS_CHANGE:
|
||||||
onPolicyStatusChange();
|
onPolicyStatusChange();
|
||||||
break;
|
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:
|
default:
|
||||||
Log.e(TAG, "Unknown event " + msg.what);
|
Log.e(TAG, "Unknown event " + msg.what);
|
||||||
}
|
}
|
||||||
@@ -321,12 +476,31 @@ public class AudioPolicy {
|
|||||||
return "addr=" + mix.getRegistration();
|
return "addr=" + mix.getRegistration();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void sendMsg(Handler handler, int msg) {
|
private void sendMsg(int msg) {
|
||||||
if (handler != null) {
|
if (mEventHandler != null) {
|
||||||
handler.sendEmptyMessage(msg);
|
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() {
|
public String toLogFriendlyString() {
|
||||||
String textDump = new String("android.media.audiopolicy.AudioPolicy:\n");
|
String textDump = new String("android.media.audiopolicy.AudioPolicy:\n");
|
||||||
textDump += "config=" + mConfig.toLogFriendlyString();
|
textDump += "config=" + mConfig.toLogFriendlyString();
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ public class AudioPolicyConfig implements Parcelable {
|
|||||||
private static final String TAG = "AudioPolicyConfig";
|
private static final String TAG = "AudioPolicyConfig";
|
||||||
|
|
||||||
protected ArrayList<AudioMix> mMixes;
|
protected ArrayList<AudioMix> mMixes;
|
||||||
|
protected int mDuckingPolicy = AudioPolicy.FOCUS_POLICY_DUCKING_IN_APP;
|
||||||
|
|
||||||
private String mRegistrationId = null;
|
private String mRegistrationId = null;
|
||||||
|
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user