summaryrefslogtreecommitdiffstats
path: root/media/java
diff options
context:
space:
mode:
authorJean-Michel Trivi <jmtrivi@google.com>2014-11-25 12:53:41 -0800
committerJean-Michel Trivi <jmtrivi@google.com>2014-11-30 14:33:29 -0800
commit1b3541d5eedb332ea01066b4a78a2d06d5304044 (patch)
tree2920579a268983cae5fd35002a68f04a8eb235ad /media/java
parent563e61f5d06aca653894cecc2977bac70b08227c (diff)
downloadframeworks_base-1b3541d5eedb332ea01066b4a78a2d06d5304044.zip
frameworks_base-1b3541d5eedb332ea01066b4a78a2d06d5304044.tar.gz
frameworks_base-1b3541d5eedb332ea01066b4a78a2d06d5304044.tar.bz2
AudioMix address and type, rule exclusion API, dynamic source
- Simplify API for defining an exclusion-based rule: don't define an exclusion rule, add instead a way to exclude a rule. - API for defining rules for dynamic sources (rule match on capture preset). - Verify mix type when creating AudioRecord or AudioTrack for a mix. - Use hashcode of mix for generating the device address. - AudioService dump prints info about registered policies. - Annotate as SystemApi the audio policy-related APIs. - Express mixing match and exclude rule constants as flags for future-proofness Bug 16006090 Bug 16009464 Change-Id: I0dabe71204501acaffea7ef0ddbbab9700e1bd87
Diffstat (limited to 'media/java')
-rw-r--r--media/java/android/media/AudioAttributes.java3
-rw-r--r--media/java/android/media/AudioManager.java14
-rw-r--r--media/java/android/media/AudioService.java29
-rw-r--r--media/java/android/media/audiopolicy/AudioMix.java41
-rw-r--r--media/java/android/media/audiopolicy/AudioMixingRule.java187
-rw-r--r--media/java/android/media/audiopolicy/AudioPolicy.java130
-rw-r--r--media/java/android/media/audiopolicy/AudioPolicyConfig.java59
7 files changed, 397 insertions, 66 deletions
diff --git a/media/java/android/media/AudioAttributes.java b/media/java/android/media/AudioAttributes.java
index 20c4978..489f552 100644
--- a/media/java/android/media/AudioAttributes.java
+++ b/media/java/android/media/AudioAttributes.java
@@ -241,11 +241,11 @@ public final class AudioAttributes implements Parcelable {
/**
* @hide
- * CANDIDATE FOR PUBLIC API
* Return the capture preset.
* @return one of the values that can be set in {@link Builder#setCapturePreset(int)} or a
* negative value if none has been set.
*/
+ @SystemApi
public int getCapturePreset() {
return mSource;
}
@@ -508,6 +508,7 @@ public final class AudioAttributes implements Parcelable {
* {@link MediaRecorder.AudioSource#VOICE_COMMUNICATION}.
* @return the same Builder instance.
*/
+ @SystemApi
public Builder setCapturePreset(int preset) {
switch (preset) {
case MediaRecorder.AudioSource.DEFAULT:
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index e61b563..a764954 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -2448,6 +2448,7 @@ public class AudioManager {
* {@link #requestAudioFocus(OnAudioFocusChangeListener, AudioAttributes, int, int)}
* @throws IllegalArgumentException
*/
+ @SystemApi
public int requestAudioFocus(OnAudioFocusChangeListener l,
@NonNull AudioAttributes requestAttributes,
int durationHint,
@@ -2842,17 +2843,17 @@ public class AudioManager {
/**
* @hide
- * CANDIDATE FOR PUBLIC API
* Register the given {@link AudioPolicy}.
* This call is synchronous and blocks until the registration process successfully completed
* or failed to complete.
- * @param policy the {@link AudioPolicy} to register.
+ * @param policy the non-null {@link AudioPolicy} to register.
* @return {@link #ERROR} if there was an error communicating with the registration service
* or if the user doesn't have the required
* {@link android.Manifest.permission#MODIFY_AUDIO_ROUTING} permission,
* {@link #SUCCESS} otherwise.
*/
- public int registerAudioPolicy(AudioPolicy policy) {
+ @SystemApi
+ public int registerAudioPolicy(@NonNull AudioPolicy policy) {
if (policy == null) {
throw new IllegalArgumentException("Illegal null AudioPolicy argument");
}
@@ -2874,16 +2875,17 @@ public class AudioManager {
/**
* @hide
- * CANDIDATE FOR PUBLIC API
- * @param policy the {@link AudioPolicy} to unregister.
+ * @param policy the non-null {@link AudioPolicy} to unregister.
*/
- public void unregisterAudioPolicyAsync(AudioPolicy policy) {
+ @SystemApi
+ public void unregisterAudioPolicyAsync(@NonNull AudioPolicy policy) {
if (policy == null) {
throw new IllegalArgumentException("Illegal null AudioPolicy argument");
}
IAudioService service = getService();
try {
service.unregisterAudioPolicyAsync(policy.token());
+ policy.setRegistration(null);
} catch (RemoteException e) {
Log.e(TAG, "Dead object in unregisterAudioPolicyAsync()", e);
}
diff --git a/media/java/android/media/AudioService.java b/media/java/android/media/AudioService.java
index e5ae93c..73997af 100644
--- a/media/java/android/media/AudioService.java
+++ b/media/java/android/media/AudioService.java
@@ -5520,6 +5520,8 @@ public class AudioService extends IAudioService.Stub {
pw.print(" mMusicActiveMs="); pw.println(mMusicActiveMs);
pw.print(" mMcc="); pw.println(mMcc);
pw.print(" mHasVibrator="); pw.println(mHasVibrator);
+
+ dumpAudioPolicies(pw);
}
private static String safeMediaVolumeStateToString(Integer state) {
@@ -5789,6 +5791,10 @@ public class AudioService extends IAudioService.Stub {
}
synchronized (mAudioPolicies) {
try {
+ if (mAudioPolicies.containsKey(cb)) {
+ Slog.e(TAG, "Cannot re-register policy");
+ return null;
+ }
AudioPolicyProxy app = new AudioPolicyProxy(policyConfig, cb);
cb.linkToDeath(app, 0/*flags*/);
regId = app.connectMixes();
@@ -5809,6 +5815,7 @@ public class AudioService extends IAudioService.Stub {
if (app == null) {
Slog.w(TAG, "Trying to unregister unknown audio policy for pid "
+ Binder.getCallingPid() + " / uid " + Binder.getCallingUid());
+ return;
} else {
cb.unlinkToDeath(app, 0/*flags*/);
}
@@ -5817,12 +5824,21 @@ public class AudioService extends IAudioService.Stub {
// TODO implement clearing mix attribute matching info in native audio policy
}
+ private void dumpAudioPolicies(PrintWriter pw) {
+ pw.println("\nAudio policies:");
+ synchronized (mAudioPolicies) {
+ for(AudioPolicyProxy policy : mAudioPolicies.values()) {
+ pw.println(policy.toLogFriendlyString());
+ }
+ }
+ }
+
//======================
// Audio policy proxy
//======================
/**
- * This internal class inherits from AudioPolicyConfig which contains all the mixes and
- * their configurations.
+ * This internal class inherits from AudioPolicyConfig, each instance contains all the
+ * mixes of an AudioPolicy and their configurations.
*/
public class AudioPolicyProxy extends AudioPolicyConfig implements IBinder.DeathRecipient {
private static final String TAG = "AudioPolicyProxy";
@@ -5830,7 +5846,7 @@ public class AudioService extends IAudioService.Stub {
IBinder mToken;
AudioPolicyProxy(AudioPolicyConfig config, IBinder token) {
super(config);
- setRegistration(new String(config.toString() + ":ap:" + mAudioPolicyCounter++));
+ setRegistration(new String(config.hashCode() + ":ap:" + mAudioPolicyCounter++));
mToken = token;
}
@@ -5844,7 +5860,7 @@ public class AudioService extends IAudioService.Stub {
String connectMixes() {
updateMixes(AudioSystem.DEVICE_STATE_AVAILABLE);
- return mRegistrationId;
+ return getRegistration();
}
void disconnectMixes() {
@@ -5855,8 +5871,9 @@ public class AudioService extends IAudioService.Stub {
for (AudioMix mix : mMixes) {
// TODO implement sending the mix attribute matching info to native audio policy
if (DEBUG_AP) {
- Log.v(TAG, "AudioPolicyProxy connect mix state=" + connectionState
- + " addr=" + mix.getRegistration()); }
+ Log.v(TAG, "AudioPolicyProxy mix new connection state=" + connectionState
+ + " addr=" + mix.getRegistration());
+ }
AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_IN_REMOTE_SUBMIX,
connectionState,
mix.getRegistration());
diff --git a/media/java/android/media/audiopolicy/AudioMix.java b/media/java/android/media/audiopolicy/AudioMix.java
index bb52682..1806662 100644
--- a/media/java/android/media/audiopolicy/AudioMix.java
+++ b/media/java/android/media/audiopolicy/AudioMix.java
@@ -17,21 +17,25 @@
package android.media.audiopolicy;
import android.annotation.IntDef;
+import android.annotation.SystemApi;
import android.media.AudioFormat;
import android.media.AudioSystem;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.util.Objects;
/**
* @hide
*/
+@SystemApi
public class AudioMix {
private AudioMixingRule mRule;
private AudioFormat mFormat;
private int mRouteFlags;
private String mRegistrationId;
+ private int mMixType = MIX_TYPE_INVALID;
/**
* All parameters are guaranteed valid through the Builder.
@@ -41,20 +45,39 @@ public class AudioMix {
mFormat = format;
mRouteFlags = routeFlags;
mRegistrationId = null;
+ mMixType = rule.getTargetMixType();
}
/**
* An audio mix behavior where the output of the mix is sent to the original destination of
* the audio signal, i.e. an output device for an output mix, or a recording for an input mix.
*/
+ @SystemApi
public static final int ROUTE_FLAG_RENDER = 0x1;
/**
* An audio mix behavior where the output of the mix is rerouted back to the framework and
- * is accessible for injection or capture through the {@link Audiotrack} and {@link AudioRecord}
+ * is accessible for injection or capture through the {@link AudioTrack} and {@link AudioRecord}
* APIs.
*/
+ @SystemApi
public static final int ROUTE_FLAG_LOOP_BACK = 0x1 << 1;
+ /**
+ * @hide
+ * Invalid mix type, default value.
+ */
+ public static final int MIX_TYPE_INVALID = -1;
+ /**
+ * @hide
+ * Mix type indicating playback streams are mixed.
+ */
+ public static final int MIX_TYPE_PLAYERS = 0;
+ /**
+ * @hide
+ * Mix type indicating recording streams are mixed.
+ */
+ public static final int MIX_TYPE_RECORDERS = 1;
+
int getRouteFlags() {
return mRouteFlags;
}
@@ -67,6 +90,11 @@ public class AudioMix {
return mRule;
}
+ /** @hide */
+ public int getMixType() {
+ return mMixType;
+ }
+
void setRegistration(String regId) {
mRegistrationId = regId;
}
@@ -77,6 +105,12 @@ public class AudioMix {
}
/** @hide */
+ @Override
+ public int hashCode() {
+ return Objects.hash(mRouteFlags, mRule, mMixType, mFormat);
+ }
+
+ /** @hide */
@IntDef(flag = true,
value = { ROUTE_FLAG_RENDER, ROUTE_FLAG_LOOP_BACK } )
@Retention(RetentionPolicy.SOURCE)
@@ -86,6 +120,7 @@ public class AudioMix {
* Builder class for {@link AudioMix} objects
*
*/
+ @SystemApi
public static class Builder {
private AudioMixingRule mRule = null;
private AudioFormat mFormat = null;
@@ -102,6 +137,7 @@ public class AudioMix {
* @param rule a non-null {@link AudioMixingRule} instance.
* @throws IllegalArgumentException
*/
+ @SystemApi
public Builder(AudioMixingRule rule)
throws IllegalArgumentException {
if (rule == null) {
@@ -132,6 +168,7 @@ public class AudioMix {
* @return the same Builder instance.
* @throws IllegalArgumentException
*/
+ @SystemApi
public Builder setFormat(AudioFormat format)
throws IllegalArgumentException {
if (format == null) {
@@ -148,6 +185,7 @@ public class AudioMix {
* @return the same Builder instance.
* @throws IllegalArgumentException
*/
+ @SystemApi
public Builder setRouteFlags(@RouteFlags int routeFlags)
throws IllegalArgumentException {
if (routeFlags == 0) {
@@ -166,6 +204,7 @@ public class AudioMix {
* @return a new {@link AudioMix} object
* @throws IllegalArgumentException if no {@link AudioMixingRule} has been set.
*/
+ @SystemApi
public AudioMix build() throws IllegalArgumentException {
if (mRule == null) {
throw new IllegalArgumentException("Illegal null AudioMixingRule");
diff --git a/media/java/android/media/audiopolicy/AudioMixingRule.java b/media/java/android/media/audiopolicy/AudioMixingRule.java
index 2e06a80..02b03d2 100644
--- a/media/java/android/media/audiopolicy/AudioMixingRule.java
+++ b/media/java/android/media/audiopolicy/AudioMixingRule.java
@@ -16,10 +16,13 @@
package android.media.audiopolicy;
+import android.annotation.SystemApi;
import android.media.AudioAttributes;
+import android.os.Parcel;
import java.util.ArrayList;
import java.util.Iterator;
+import java.util.Objects;
/**
@@ -35,44 +38,114 @@ import java.util.Iterator;
* .build();
* </pre>
*/
+@SystemApi
public class AudioMixingRule {
- private AudioMixingRule(ArrayList<AttributeMatchCriterion> criteria) {
+ private AudioMixingRule(int mixType, ArrayList<AttributeMatchCriterion> criteria) {
mCriteria = criteria;
+ mTargetMixType = mixType;
}
/**
- * A rule requiring the usage information of the {@link AudioAttributes} to match
+ * A rule requiring the usage information of the {@link AudioAttributes} to match.
*/
+ @SystemApi
public static final int RULE_MATCH_ATTRIBUTE_USAGE = 0x1;
/**
- * A rule requiring the usage information of the {@link AudioAttributes} to differ
+ * A rule requiring the capture preset information of the {@link AudioAttributes} to match.
*/
- public static final int RULE_EXCLUDE_ATTRIBUTE_USAGE = 0x1 << 1;
+ @SystemApi
+ public static final int RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET = 0x1 << 1;
+
+ private final static int RULE_EXCLUSION_MASK = 0x8000;
+ /**
+ * @hide
+ * A rule requiring the usage information of the {@link AudioAttributes} to differ.
+ */
+ public static final int RULE_EXCLUDE_ATTRIBUTE_USAGE =
+ RULE_EXCLUSION_MASK | RULE_MATCH_ATTRIBUTE_USAGE;
+ /**
+ * @hide
+ * A rule requiring the capture preset information of the {@link AudioAttributes} to differ.
+ */
+ public static final int RULE_EXCLUDE_ATTRIBUTE_CAPTURE_PRESET =
+ RULE_EXCLUSION_MASK | RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET;
static final class AttributeMatchCriterion {
AudioAttributes mAttr;
int mRule;
+ /** input parameters must be valid */
AttributeMatchCriterion(AudioAttributes attributes, int rule) {
mAttr = attributes;
mRule = rule;
}
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mAttr, mRule);
+ }
+
+ void writeToParcel(Parcel dest) {
+ dest.writeInt(mRule);
+ if ((mRule == RULE_MATCH_ATTRIBUTE_USAGE) || (mRule == RULE_EXCLUDE_ATTRIBUTE_USAGE)) {
+ dest.writeInt(mAttr.getUsage());
+ } else {
+ // capture preset rule
+ dest.writeInt(mAttr.getCapturePreset());
+ }
+ }
}
- private ArrayList<AttributeMatchCriterion> mCriteria;
+ private final int mTargetMixType;
+ int getTargetMixType() { return mTargetMixType; }
+ private final ArrayList<AttributeMatchCriterion> mCriteria;
ArrayList<AttributeMatchCriterion> getCriteria() { return mCriteria; }
+ @Override
+ public int hashCode() {
+ return Objects.hash(mTargetMixType, mCriteria);
+ }
+
+ private static boolean isValidSystemApiRule(int rule) {
+ switch(rule) {
+ case RULE_MATCH_ATTRIBUTE_USAGE:
+ case RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET:
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ private static boolean isValidIntRule(int rule) {
+ switch(rule) {
+ case RULE_MATCH_ATTRIBUTE_USAGE:
+ case RULE_EXCLUDE_ATTRIBUTE_USAGE:
+ case RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET:
+ case RULE_EXCLUDE_ATTRIBUTE_CAPTURE_PRESET:
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ private static boolean isPlayerRule(int rule) {
+ return ((rule == RULE_MATCH_ATTRIBUTE_USAGE)
+ || (rule == RULE_EXCLUDE_ATTRIBUTE_USAGE));
+ }
+
/**
* Builder class for {@link AudioMixingRule} objects
- *
*/
+ @SystemApi
public static class Builder {
private ArrayList<AttributeMatchCriterion> mCriteria;
+ private int mTargetMixType = AudioMix.MIX_TYPE_INVALID;
/**
* Constructs a new Builder with no rules.
*/
+ @SystemApi
public Builder() {
mCriteria = new ArrayList<AttributeMatchCriterion>();
}
@@ -81,18 +154,80 @@ public class AudioMixingRule {
* Add a rule for the selection of which streams are mixed together.
* @param attrToMatch a non-null AudioAttributes instance for which a contradictory
* rule hasn't been set yet.
- * @param rule one of {@link AudioMixingRule#RULE_EXCLUDE_ATTRIBUTE_USAGE},
- * {@link AudioMixingRule#RULE_MATCH_ATTRIBUTE_USAGE}.
+ * @param rule {@link AudioMixingRule#RULE_MATCH_ATTRIBUTE_USAGE} or
+ * {@link AudioMixingRule#RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET}.
* @return the same Builder instance.
* @throws IllegalArgumentException
*/
+ @SystemApi
public Builder addRule(AudioAttributes attrToMatch, int rule)
throws IllegalArgumentException {
+ if (!isValidSystemApiRule(rule)) {
+ throw new IllegalArgumentException("Illegal rule value " + rule);
+ }
+ return addRuleInt(attrToMatch, rule);
+ }
+
+ /**
+ * Add a rule by exclusion for the selection of which streams are mixed together.
+ * <br>For instance the following code
+ * <br><pre>
+ * AudioAttributes mediaAttr = new AudioAttributes.Builder()
+ * .setUsage(AudioAttributes.USAGE_MEDIA)
+ * .build();
+ * AudioMixingRule noMediaRule = new AudioMixingRule.Builder()
+ * .excludeRule(mediaAttr, AudioMixingRule.RULE_MATCH_ATTRIBUTE_USAGE)
+ * .build();
+ * </pre>
+ * <br>will create a rule which maps to any usage value, except USAGE_MEDIA.
+ * @param attrToMatch a non-null AudioAttributes instance for which a contradictory
+ * rule hasn't been set yet.
+ * @param rule {@link AudioMixingRule#RULE_MATCH_ATTRIBUTE_USAGE} or
+ * {@link AudioMixingRule#RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET}.
+ * @return the same Builder instance.
+ * @throws IllegalArgumentException
+ */
+ @SystemApi
+ public Builder excludeRule(AudioAttributes attrToMatch, int rule)
+ throws IllegalArgumentException {
+ if (!isValidSystemApiRule(rule)) {
+ throw new IllegalArgumentException("Illegal rule value " + rule);
+ }
+ return addRuleInt(attrToMatch, rule | RULE_EXCLUSION_MASK);
+ }
+
+ /**
+ * Add or exclude a rule for the selection of which streams are mixed together.
+ * @param attrToMatch a non-null AudioAttributes instance for which a contradictory
+ * rule hasn't been set yet.
+ * @param rule one of {@link AudioMixingRule#RULE_EXCLUDE_ATTRIBUTE_USAGE},
+ * {@link AudioMixingRule#RULE_MATCH_ATTRIBUTE_USAGE},
+ * {@link AudioMixingRule#RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET} or
+ * {@link AudioMixingRule#RULE_EXCLUDE_ATTRIBUTE_CAPTURE_PRESET}.
+ * @return the same Builder instance.
+ * @throws IllegalArgumentException
+ */
+ Builder addRuleInt(AudioAttributes attrToMatch, int rule)
+ throws IllegalArgumentException {
if (attrToMatch == null) {
throw new IllegalArgumentException("Illegal null AudioAttributes argument");
}
- if ((rule != RULE_MATCH_ATTRIBUTE_USAGE) && (rule != RULE_EXCLUDE_ATTRIBUTE_USAGE)) {
+ if (!isValidIntRule(rule)) {
throw new IllegalArgumentException("Illegal rule value " + rule);
+ } else {
+ // as rules are added to the Builder, we verify they are consistent with the type
+ // of mix being built. When adding the first rule, the mix type is MIX_TYPE_INVALID.
+ if (mTargetMixType == AudioMix.MIX_TYPE_INVALID) {
+ if (isPlayerRule(rule)) {
+ mTargetMixType = AudioMix.MIX_TYPE_PLAYERS;
+ } else {
+ mTargetMixType = AudioMix.MIX_TYPE_RECORDERS;
+ }
+ } else if (((mTargetMixType == AudioMix.MIX_TYPE_PLAYERS) && !isPlayerRule(rule))
+ || ((mTargetMixType == AudioMix.MIX_TYPE_RECORDERS) && isPlayerRule(rule)))
+ {
+ throw new IllegalArgumentException("Incompatible rule for mix");
+ }
}
synchronized (mCriteria) {
Iterator<AttributeMatchCriterion> crIterator = mCriteria.iterator();
@@ -111,6 +246,19 @@ public class AudioMixingRule {
+ attrToMatch);
}
}
+ } else if ((rule == RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET)
+ || (rule == RULE_EXCLUDE_ATTRIBUTE_CAPTURE_PRESET)) {
+ // "capture preset"-base rule
+ if (criterion.mAttr.getCapturePreset() == attrToMatch.getCapturePreset()) {
+ if (criterion.mRule == rule) {
+ // rule already exists, we're done
+ return this;
+ } else {
+ // criterion already exists with a another rule, it is incompatible
+ throw new IllegalArgumentException("Contradictory rule exists for "
+ + attrToMatch);
+ }
+ }
}
}
// rule didn't exist, add it
@@ -119,13 +267,32 @@ public class AudioMixingRule {
return this;
}
+ Builder addRuleFromParcel(Parcel in) throws IllegalArgumentException {
+ int rule = in.readInt();
+ AudioAttributes attr;
+ if ((rule == RULE_MATCH_ATTRIBUTE_USAGE) || (rule == RULE_EXCLUDE_ATTRIBUTE_USAGE)) {
+ int usage = in.readInt();
+ attr = new AudioAttributes.Builder()
+ .setUsage(usage).build();
+ } else if ((rule == RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET)
+ || (rule == RULE_EXCLUDE_ATTRIBUTE_CAPTURE_PRESET)) {
+ int preset = in.readInt();
+ attr = new AudioAttributes.Builder()
+ .setInternalCapturePreset(preset).build();
+ } else {
+ in.readInt(); // assume there was in int value to read as for now they come in pair
+ throw new IllegalArgumentException("Illegal rule value " + rule + " in parcel");
+ }
+ return addRuleInt(attr, rule);
+ }
+
/**
* Combines all of the matching and exclusion rules that have been set and return a new
* {@link AudioMixingRule} object.
* @return a new {@link AudioMixingRule} object
*/
public AudioMixingRule build() {
- return new AudioMixingRule(mCriteria);
+ return new AudioMixingRule(mTargetMixType, mCriteria);
}
}
}
diff --git a/media/java/android/media/audiopolicy/AudioPolicy.java b/media/java/android/media/audiopolicy/AudioPolicy.java
index e9dc3af..44d2430 100644
--- a/media/java/android/media/audiopolicy/AudioPolicy.java
+++ b/media/java/android/media/audiopolicy/AudioPolicy.java
@@ -17,17 +17,21 @@
package android.media.audiopolicy;
import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
import android.content.Context;
import android.content.pm.PackageManager;
import android.media.AudioAttributes;
import android.media.AudioFormat;
import android.media.AudioManager;
import android.media.AudioRecord;
-import android.media.AudioSystem;
import android.media.AudioTrack;
import android.media.MediaRecorder;
import android.os.Binder;
+import android.os.Handler;
import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
import android.util.Log;
import android.util.Slog;
@@ -39,21 +43,20 @@ import java.util.ArrayList;
* @hide
* AudioPolicy provides access to the management of audio routing and audio focus.
*/
+@SystemApi
public class AudioPolicy {
private static final String TAG = "AudioPolicy";
/**
- * The status of an audio policy that cannot be used because it is invalid.
- */
- public static final int POLICY_STATUS_INVALID = 0;
- /**
* The status of an audio policy that is valid but cannot be used because it is not registered.
*/
+ @SystemApi
public static final int POLICY_STATUS_UNREGISTERED = 1;
/**
* The status of an audio policy that is valid, successfully registered and thus active.
*/
+ @SystemApi
public static final int POLICY_STATUS_REGISTERED = 2;
private int mStatus;
@@ -72,22 +75,29 @@ public class AudioPolicy {
/**
* The parameter is guaranteed non-null through the Builder
*/
- private AudioPolicy(AudioPolicyConfig config, Context context) {
+ private AudioPolicy(AudioPolicyConfig config, Context context, Looper looper) {
mConfig = config;
- if (mConfig.mMixes.isEmpty()) {
- mStatus = POLICY_STATUS_INVALID;
+ mStatus = POLICY_STATUS_UNREGISTERED;
+ mContext = context;
+ if (looper == null) {
+ looper = Looper.getMainLooper();
+ }
+ if (looper != null) {
+ mEventHandler = new EventHandler(this, looper);
} else {
- mStatus = POLICY_STATUS_UNREGISTERED;
+ mEventHandler = null;
+ Log.e(TAG, "No event handler due to looper without a thread");
}
- mContext = context;
}
/**
* Builder class for {@link AudioPolicy} objects
*/
+ @SystemApi
public static class Builder {
private ArrayList<AudioMix> mMixes;
private Context mContext;
+ private Looper mLooper;
/**
* Constructs a new Builder with no audio mixes.
@@ -104,7 +114,7 @@ public class AudioPolicy {
* @return the same Builder instance.
* @throws IllegalArgumentException
*/
- public Builder addMix(AudioMix mix) throws IllegalArgumentException {
+ public Builder addMix(@NonNull AudioMix mix) throws IllegalArgumentException {
if (mix == null) {
throw new IllegalArgumentException("Illegal null AudioMix argument");
}
@@ -112,18 +122,41 @@ public class AudioPolicy {
return this;
}
+ /**
+ * Sets the {@link Looper} on which to run the event loop.
+ * @param looper a non-null specific Looper.
+ * @return the same Builder instance.
+ * @throws IllegalArgumentException
+ */
+ public Builder setLooper(@NonNull Looper looper) throws IllegalArgumentException {
+ if (looper == null) {
+ throw new IllegalArgumentException("Illegal null Looper argument");
+ }
+ mLooper = looper;
+ return this;
+ }
+
public AudioPolicy build() {
- return new AudioPolicy(new AudioPolicyConfig(mMixes), mContext);
+ return new AudioPolicy(new AudioPolicyConfig(mMixes), mContext, mLooper);
}
}
- /** @hide */
public void setRegistration(String regId) {
mRegistrationId = regId;
mConfig.setRegistration(regId);
+ if (regId != null) {
+ mStatus = POLICY_STATUS_REGISTERED;
+ } else {
+ mStatus = POLICY_STATUS_UNREGISTERED;
+ }
+ sendMsg(mEventHandler, MSG_POLICY_STATUS_CHANGE);
}
private boolean policyReadyToUse() {
+ if (mStatus != POLICY_STATUS_REGISTERED) {
+ Log.e(TAG, "Cannot use unregistered AudioPolicy");
+ return false;
+ }
if (mContext == null) {
Log.e(TAG, "Cannot use AudioPolicy without context");
return false;
@@ -155,11 +188,17 @@ public class AudioPolicy {
{
throw new IllegalArgumentException("Invalid AudioMix: not defined for loop back");
}
- // TODO also check mix is defined for playback or recording, and matches forTrack argument
+ if (forTrack && (mix.getMixType() != AudioMix.MIX_TYPE_RECORDERS)) {
+ throw new IllegalArgumentException(
+ "Invalid AudioMix: not defined for being a recording source");
+ }
+ if (!forTrack && (mix.getMixType() != AudioMix.MIX_TYPE_PLAYERS)) {
+ throw new IllegalArgumentException(
+ "Invalid AudioMix: not defined for capturing playback");
+ }
}
/**
- * @hide
* Create an {@link AudioRecord} instance that is associated with the given {@link AudioMix}.
* Audio buffers recorded through the created instance will contain the mix of the audio
* streams that fed the given mixer.
@@ -170,6 +209,7 @@ public class AudioPolicy {
* with {@link AudioManager#registerAudioPolicy(AudioPolicy)}.
* @throws IllegalArgumentException
*/
+ @SystemApi
public AudioRecord createAudioRecordSink(AudioMix mix) throws IllegalArgumentException {
if (!policyReadyToUse()) {
Log.e(TAG, "Cannot create AudioRecord sink for AudioMix");
@@ -186,7 +226,7 @@ public class AudioPolicy {
AudioRecord ar = new AudioRecord(
new AudioAttributes.Builder()
.setInternalCapturePreset(MediaRecorder.AudioSource.REMOTE_SUBMIX)
- .addTag(mix.getRegistration())
+ .addTag(addressForTag(mix))
.build(),
mixFormat,
AudioRecord.getMinBufferSize(mix.getFormat().getSampleRate(),
@@ -198,17 +238,17 @@ public class AudioPolicy {
}
/**
- * @hide
* Create an {@link AudioTrack} instance that is associated with the given {@link AudioMix}.
* Audio buffers played through the created instance will be sent to the given mix
* to be recorded through the recording APIs.
* @param mix a non-null {@link AudioMix} instance whose routing flags was defined with
* {@link AudioMix#ROUTE_FLAG_LOOP_BACK}, previously added to this policy.
- * @returna new {@link AudioTrack} instance whose data format is the one defined in the
+ * @return a new {@link AudioTrack} instance whose data format is the one defined in the
* {@link AudioMix}, or null if this policy was not successfully registered
* with {@link AudioManager#registerAudioPolicy(AudioPolicy)}.
* @throws IllegalArgumentException
*/
+ @SystemApi
public AudioTrack createAudioTrackSource(AudioMix mix) throws IllegalArgumentException {
if (!policyReadyToUse()) {
Log.e(TAG, "Cannot create AudioTrack source for AudioMix");
@@ -219,7 +259,7 @@ public class AudioPolicy {
AudioTrack at = new AudioTrack(
new AudioAttributes.Builder()
.setUsage(AudioAttributes.USAGE_VIRTUAL_SOURCE)
- .addTag(mix.getRegistration())
+ .addTag(addressForTag(mix))
.build(),
mix.getFormat(),
AudioTrack.getMinBufferSize(mix.getFormat().getSampleRate(),
@@ -230,20 +270,63 @@ public class AudioPolicy {
return at;
}
+ @SystemApi
public int getStatus() {
return mStatus;
}
+ @SystemApi
public static abstract class AudioPolicyStatusListener {
- void onStatusChange() {}
- void onMixStateUpdate(AudioMix mix) {}
+ public void onStatusChange() {}
+ public void onMixStateUpdate(AudioMix mix) {}
}
- void setStatusListener(AudioPolicyStatusListener l) {
+ @SystemApi
+ synchronized public void setAudioPolicyStatusListener(AudioPolicyStatusListener l) {
mStatusListener = l;
}
- /** @hide */
+ synchronized private void onPolicyStatusChange() {
+ if (mStatusListener == null) {
+ return;
+ }
+ mStatusListener.onStatusChange();
+ }
+
+ //==================================================
+ // Event handling
+ private final EventHandler mEventHandler;
+ private final static int MSG_POLICY_STATUS_CHANGE = 0;
+
+ private class EventHandler extends Handler {
+ public EventHandler(AudioPolicy ap, Looper looper) {
+ super(looper);
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ switch(msg.what) {
+ case MSG_POLICY_STATUS_CHANGE:
+ onPolicyStatusChange();
+ break;
+ default:
+ Log.e(TAG, "Unknown event " + msg.what);
+ }
+ }
+ }
+
+ //==========================================================
+ // Utils
+ private static String addressForTag(AudioMix mix) {
+ return "addr=" + mix.getRegistration();
+ }
+
+ private static void sendMsg(Handler handler, int msg) {
+ if (handler != null) {
+ handler.sendEmptyMessage(msg);
+ }
+ }
+
public String toLogFriendlyString() {
String textDump = new String("android.media.audiopolicy.AudioPolicy:\n");
textDump += "config=" + mConfig.toLogFriendlyString();
@@ -252,7 +335,6 @@ public class AudioPolicy {
/** @hide */
@IntDef({
- POLICY_STATUS_INVALID,
POLICY_STATUS_REGISTERED,
POLICY_STATUS_UNREGISTERED
})
diff --git a/media/java/android/media/audiopolicy/AudioPolicyConfig.java b/media/java/android/media/audiopolicy/AudioPolicyConfig.java
index a9a4175..e2a20da 100644
--- a/media/java/android/media/audiopolicy/AudioPolicyConfig.java
+++ b/media/java/android/media/audiopolicy/AudioPolicyConfig.java
@@ -27,6 +27,7 @@ import android.os.Parcelable;
import android.util.Log;
import java.util.ArrayList;
+import java.util.Objects;
/**
* @hide
@@ -38,7 +39,7 @@ public class AudioPolicyConfig implements Parcelable {
protected ArrayList<AudioMix> mMixes;
- protected String mRegistrationId = null;
+ private String mRegistrationId = null;
protected AudioPolicyConfig(AudioPolicyConfig conf) {
mMixes = conf.mMixes;
@@ -62,6 +63,11 @@ public class AudioPolicyConfig implements Parcelable {
}
@Override
+ public int hashCode() {
+ return Objects.hash(mMixes);
+ }
+
+ @Override
public int describeContents() {
return 0;
}
@@ -80,8 +86,7 @@ public class AudioPolicyConfig implements Parcelable {
final ArrayList<AttributeMatchCriterion> criteria = mix.getRule().getCriteria();
dest.writeInt(criteria.size());
for (AttributeMatchCriterion criterion : criteria) {
- dest.writeInt(criterion.mRule);
- dest.writeInt(criterion.mAttr.getUsage());
+ criterion.writeToParcel(dest);
}
}
}
@@ -106,17 +111,7 @@ public class AudioPolicyConfig implements Parcelable {
AudioMixingRule.Builder ruleBuilder = new AudioMixingRule.Builder();
for (int j = 0 ; j < nbRules ; j++) {
// read the matching rules
- int matchRule = in.readInt();
- if ((matchRule == AudioMixingRule.RULE_EXCLUDE_ATTRIBUTE_USAGE)
- || (matchRule == AudioMixingRule.RULE_MATCH_ATTRIBUTE_USAGE)) {
- int usage = in.readInt();
- final AudioAttributes attr = new AudioAttributes.Builder()
- .setUsage(usage).build();
- ruleBuilder.addRule(attr, matchRule);
- } else {
- Log.w(TAG, "Encountered unsupported rule, skipping");
- in.readInt();
- }
+ ruleBuilder.addRuleFromParcel(in);
}
mixBuilder.setMixingRule(ruleBuilder.build());
mMixes.add(mixBuilder.build());
@@ -140,7 +135,7 @@ public class AudioPolicyConfig implements Parcelable {
public String toLogFriendlyString () {
String textDump = new String("android.media.audiopolicy.AudioPolicyConfig:\n");
- textDump += mMixes.size() + " AudioMix:\n";
+ textDump += mMixes.size() + " AudioMix: "+ mRegistrationId + "\n";
for(AudioMix mix : mMixes) {
// write mix route flags
textDump += "* route flags=0x" + Integer.toHexString(mix.getRouteFlags()) + "\n";
@@ -161,6 +156,14 @@ public class AudioPolicyConfig implements Parcelable {
textDump += " match usage ";
textDump += criterion.mAttr.usageToString();
break;
+ case AudioMixingRule.RULE_EXCLUDE_ATTRIBUTE_CAPTURE_PRESET:
+ textDump += " exclude capture preset ";
+ textDump += criterion.mAttr.getCapturePreset();
+ break;
+ case AudioMixingRule.RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET:
+ textDump += " match capture preset ";
+ textDump += criterion.mAttr.getCapturePreset();
+ break;
default:
textDump += "invalid rule!";
}
@@ -170,12 +173,32 @@ public class AudioPolicyConfig implements Parcelable {
return textDump;
}
- public void setRegistration(String regId) {
- mRegistrationId = regId;
+ protected void setRegistration(String regId) {
+ final boolean currentRegNull = (mRegistrationId == null) || mRegistrationId.isEmpty();
+ final boolean newRegNull = (regId == null) || regId.isEmpty();
+ if (!currentRegNull && !newRegNull && !mRegistrationId.equals(regId)) {
+ Log.e(TAG, "Invalid registration transition from " + mRegistrationId + " to " + regId);
+ return;
+ }
+ mRegistrationId = regId == null ? "" : regId;
int mixIndex = 0;
for (AudioMix mix : mMixes) {
- mix.setRegistration(mRegistrationId + "mix:" + mixIndex++);
+ if (!mRegistrationId.isEmpty()) {
+ mix.setRegistration(mRegistrationId + "mix" + mixTypeId(mix.getMixType()) + ":"
+ + mixIndex++);
+ } else {
+ mix.setRegistration("");
+ }
}
}
+ private static String mixTypeId(int type) {
+ if (type == AudioMix.MIX_TYPE_PLAYERS) return "p";
+ else if (type == AudioMix.MIX_TYPE_RECORDERS) return "r";
+ else return "i";
+ }
+
+ protected String getRegistration() {
+ return mRegistrationId;
+ }
}