Home | History | Annotate | Download | only in audiopolicy
      1 /*
      2  * Copyright (C) 2014 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package android.media.audiopolicy;
     18 
     19 import android.annotation.SystemApi;
     20 import android.media.AudioAttributes;
     21 import android.os.Parcel;
     22 import android.util.Log;
     23 
     24 import java.util.ArrayList;
     25 import java.util.Iterator;
     26 import java.util.Objects;
     27 
     28 
     29 /**
     30  * @hide
     31  *
     32  * Here's an example of creating a mixing rule for all media playback:
     33  * <pre>
     34  * AudioAttributes mediaAttr = new AudioAttributes.Builder()
     35  *         .setUsage(AudioAttributes.USAGE_MEDIA)
     36  *         .build();
     37  * AudioMixingRule mediaRule = new AudioMixingRule.Builder()
     38  *         .addRule(mediaAttr, AudioMixingRule.RULE_MATCH_ATTRIBUTE_USAGE)
     39  *         .build();
     40  * </pre>
     41  */
     42 @SystemApi
     43 public class AudioMixingRule {
     44 
     45     private AudioMixingRule(int mixType, ArrayList<AudioMixMatchCriterion> criteria) {
     46         mCriteria = criteria;
     47         mTargetMixType = mixType;
     48     }
     49 
     50     /**
     51      * A rule requiring the usage information of the {@link AudioAttributes} to match.
     52      * This mixing rule can be added with {@link Builder#addRule(AudioAttributes, int)} or
     53      * {@link Builder#addMixRule(int, Object)} where the Object parameter is an instance of
     54      * {@link AudioAttributes}.
     55      */
     56     @SystemApi
     57     public static final int RULE_MATCH_ATTRIBUTE_USAGE = 0x1;
     58     /**
     59      * A rule requiring the capture preset information of the {@link AudioAttributes} to match.
     60      * This mixing rule can be added with {@link Builder#addRule(AudioAttributes, int)} or
     61      * {@link Builder#addMixRule(int, Object)} where the Object parameter is an instance of
     62      * {@link AudioAttributes}.
     63      */
     64     @SystemApi
     65     public static final int RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET = 0x1 << 1;
     66     /**
     67      * A rule requiring the UID of the audio stream to match that specified.
     68      * This mixing rule can be added with {@link Builder#addMixRule(int, Object)} where the Object
     69      * parameter is an instance of {@link java.lang.Integer}.
     70      */
     71     @SystemApi
     72     public static final int RULE_MATCH_UID = 0x1 << 2;
     73 
     74     private final static int RULE_EXCLUSION_MASK = 0x8000;
     75     /**
     76      * @hide
     77      * A rule requiring the usage information of the {@link AudioAttributes} to differ.
     78      */
     79     public static final int RULE_EXCLUDE_ATTRIBUTE_USAGE =
     80             RULE_EXCLUSION_MASK | RULE_MATCH_ATTRIBUTE_USAGE;
     81     /**
     82      * @hide
     83      * A rule requiring the capture preset information of the {@link AudioAttributes} to differ.
     84      */
     85     public static final int RULE_EXCLUDE_ATTRIBUTE_CAPTURE_PRESET =
     86             RULE_EXCLUSION_MASK | RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET;
     87     /**
     88      * @hide
     89      * A rule requiring the UID information to differ.
     90      */
     91     public static final int RULE_EXCLUDE_UID =
     92             RULE_EXCLUSION_MASK | RULE_MATCH_UID;
     93 
     94     static final class AudioMixMatchCriterion {
     95         final AudioAttributes mAttr;
     96         final int mIntProp;
     97         final int mRule;
     98 
     99         /** input parameters must be valid */
    100         AudioMixMatchCriterion(AudioAttributes attributes, int rule) {
    101             mAttr = attributes;
    102             mIntProp = Integer.MIN_VALUE;
    103             mRule = rule;
    104         }
    105         /** input parameters must be valid */
    106         AudioMixMatchCriterion(Integer intProp, int rule) {
    107             mAttr = null;
    108             mIntProp = intProp.intValue();
    109             mRule = rule;
    110         }
    111 
    112         @Override
    113         public int hashCode() {
    114             return Objects.hash(mAttr, mIntProp, mRule);
    115         }
    116 
    117         void writeToParcel(Parcel dest) {
    118             dest.writeInt(mRule);
    119             final int match_rule = mRule & ~RULE_EXCLUSION_MASK;
    120             switch (match_rule) {
    121             case RULE_MATCH_ATTRIBUTE_USAGE:
    122                 dest.writeInt(mAttr.getUsage());
    123                 break;
    124             case RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET:
    125                 dest.writeInt(mAttr.getCapturePreset());
    126                 break;
    127             case RULE_MATCH_UID:
    128                 dest.writeInt(mIntProp);
    129                 break;
    130             default:
    131                 Log.e("AudioMixMatchCriterion", "Unknown match rule" + match_rule
    132                         + " when writing to Parcel");
    133                 dest.writeInt(-1);
    134             }
    135         }
    136     }
    137 
    138     private final int mTargetMixType;
    139     int getTargetMixType() { return mTargetMixType; }
    140     private final ArrayList<AudioMixMatchCriterion> mCriteria;
    141     ArrayList<AudioMixMatchCriterion> getCriteria() { return mCriteria; }
    142 
    143     @Override
    144     public int hashCode() {
    145         return Objects.hash(mTargetMixType, mCriteria);
    146     }
    147 
    148     private static boolean isValidSystemApiRule(int rule) {
    149         // API rules only expose the RULE_MATCH_* rules
    150         switch (rule) {
    151             case RULE_MATCH_ATTRIBUTE_USAGE:
    152             case RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET:
    153             case RULE_MATCH_UID:
    154                 return true;
    155             default:
    156                 return false;
    157         }
    158     }
    159     private static boolean isValidAttributesSystemApiRule(int rule) {
    160         // API rules only expose the RULE_MATCH_* rules
    161         switch (rule) {
    162             case RULE_MATCH_ATTRIBUTE_USAGE:
    163             case RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET:
    164                 return true;
    165             default:
    166                 return false;
    167         }
    168     }
    169 
    170     private static boolean isValidRule(int rule) {
    171         final int match_rule = rule & ~RULE_EXCLUSION_MASK;
    172         switch (match_rule) {
    173             case RULE_MATCH_ATTRIBUTE_USAGE:
    174             case RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET:
    175             case RULE_MATCH_UID:
    176                 return true;
    177             default:
    178                 return false;
    179         }
    180     }
    181 
    182     private static boolean isPlayerRule(int rule) {
    183         final int match_rule = rule & ~RULE_EXCLUSION_MASK;
    184         switch (match_rule) {
    185         case RULE_MATCH_ATTRIBUTE_USAGE:
    186         case RULE_MATCH_UID:
    187             return true;
    188         default:
    189             return false;
    190         }
    191     }
    192 
    193     private static boolean isAudioAttributeRule(int match_rule) {
    194         switch(match_rule) {
    195             case RULE_MATCH_ATTRIBUTE_USAGE:
    196             case RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET:
    197                 return true;
    198             default:
    199                 return false;
    200         }
    201     }
    202 
    203     /**
    204      * Builder class for {@link AudioMixingRule} objects
    205      */
    206     @SystemApi
    207     public static class Builder {
    208         private ArrayList<AudioMixMatchCriterion> mCriteria;
    209         private int mTargetMixType = AudioMix.MIX_TYPE_INVALID;
    210 
    211         /**
    212          * Constructs a new Builder with no rules.
    213          */
    214         @SystemApi
    215         public Builder() {
    216             mCriteria = new ArrayList<AudioMixMatchCriterion>();
    217         }
    218 
    219         /**
    220          * Add a rule for the selection of which streams are mixed together.
    221          * @param attrToMatch a non-null AudioAttributes instance for which a contradictory
    222          *     rule hasn't been set yet.
    223          * @param rule {@link AudioMixingRule#RULE_MATCH_ATTRIBUTE_USAGE} or
    224          *     {@link AudioMixingRule#RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET}.
    225          * @return the same Builder instance.
    226          * @throws IllegalArgumentException
    227          * @see {@link #excludeRule(AudioAttributes, int)}
    228          */
    229         @SystemApi
    230         public Builder addRule(AudioAttributes attrToMatch, int rule)
    231                 throws IllegalArgumentException {
    232             if (!isValidAttributesSystemApiRule(rule)) {
    233                 throw new IllegalArgumentException("Illegal rule value " + rule);
    234             }
    235             return checkAddRuleObjInternal(rule, attrToMatch);
    236         }
    237 
    238         /**
    239          * Add a rule by exclusion for the selection of which streams are mixed together.
    240          * <br>For instance the following code
    241          * <br><pre>
    242          * AudioAttributes mediaAttr = new AudioAttributes.Builder()
    243          *         .setUsage(AudioAttributes.USAGE_MEDIA)
    244          *         .build();
    245          * AudioMixingRule noMediaRule = new AudioMixingRule.Builder()
    246          *         .excludeRule(mediaAttr, AudioMixingRule.RULE_MATCH_ATTRIBUTE_USAGE)
    247          *         .build();
    248          * </pre>
    249          * <br>will create a rule which maps to any usage value, except USAGE_MEDIA.
    250          * @param attrToMatch a non-null AudioAttributes instance for which a contradictory
    251          *     rule hasn't been set yet.
    252          * @param rule {@link AudioMixingRule#RULE_MATCH_ATTRIBUTE_USAGE} or
    253          *     {@link AudioMixingRule#RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET}.
    254          * @return the same Builder instance.
    255          * @throws IllegalArgumentException
    256          * @see {@link #addRule(AudioAttributes, int)}
    257          */
    258         @SystemApi
    259         public Builder excludeRule(AudioAttributes attrToMatch, int rule)
    260                 throws IllegalArgumentException {
    261             if (!isValidAttributesSystemApiRule(rule)) {
    262                 throw new IllegalArgumentException("Illegal rule value " + rule);
    263             }
    264             return checkAddRuleObjInternal(rule | RULE_EXCLUSION_MASK, attrToMatch);
    265         }
    266 
    267         /**
    268          * Add a rule for the selection of which streams are mixed together.
    269          * The rule defines what the matching will be made on. It also determines the type of the
    270          * property to match against.
    271          * @param rule one of {@link AudioMixingRule#RULE_MATCH_ATTRIBUTE_USAGE},
    272          *     {@link AudioMixingRule#RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET} or
    273          *     {@link AudioMixingRule#RULE_MATCH_UID}.
    274          * @param property see the definition of each rule for the type to use (either an
    275          *     {@link AudioAttributes} or an {@link java.lang.Integer}).
    276          * @return the same Builder instance.
    277          * @throws IllegalArgumentException
    278          * @see {@link #excludeMixRule(int, Object)}
    279          */
    280         @SystemApi
    281         public Builder addMixRule(int rule, Object property) throws IllegalArgumentException {
    282             if (!isValidSystemApiRule(rule)) {
    283                 throw new IllegalArgumentException("Illegal rule value " + rule);
    284             }
    285             return checkAddRuleObjInternal(rule, property);
    286         }
    287 
    288         /**
    289          * Add a rule by exclusion for the selection of which streams are mixed together.
    290          * <br>For instance the following code
    291          * <br><pre>
    292          * AudioAttributes mediaAttr = new AudioAttributes.Builder()
    293          *         .setUsage(AudioAttributes.USAGE_MEDIA)
    294          *         .build();
    295          * AudioMixingRule noMediaRule = new AudioMixingRule.Builder()
    296          *         .addMixRule(AudioMixingRule.RULE_MATCH_ATTRIBUTE_USAGE, mediaAttr)
    297          *         .excludeMixRule(AudioMixingRule.RULE_MATCH_UID, new Integer(uidToExclude)
    298          *         .build();
    299          * </pre>
    300          * <br>will create a rule which maps to usage USAGE_MEDIA, but excludes any stream
    301          * coming from the specified UID.
    302          * @param rule one of {@link AudioMixingRule#RULE_MATCH_ATTRIBUTE_USAGE},
    303          *     {@link AudioMixingRule#RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET} or
    304          *     {@link AudioMixingRule#RULE_MATCH_UID}.
    305          * @param property see the definition of each rule for the type to use (either an
    306          *     {@link AudioAttributes} or an {@link java.lang.Integer}).
    307          * @return the same Builder instance.
    308          * @throws IllegalArgumentException
    309          */
    310         @SystemApi
    311         public Builder excludeMixRule(int rule, Object property) throws IllegalArgumentException {
    312             if (!isValidSystemApiRule(rule)) {
    313                 throw new IllegalArgumentException("Illegal rule value " + rule);
    314             }
    315             return checkAddRuleObjInternal(rule | RULE_EXCLUSION_MASK, property);
    316         }
    317 
    318         /**
    319          * Add or exclude a rule for the selection of which streams are mixed together.
    320          * Does error checking on the parameters.
    321          * @param rule
    322          * @param property
    323          * @return the same Builder instance.
    324          * @throws IllegalArgumentException
    325          */
    326         private Builder checkAddRuleObjInternal(int rule, Object property)
    327                 throws IllegalArgumentException {
    328             if (property == null) {
    329                 throw new IllegalArgumentException("Illegal null argument for mixing rule");
    330             }
    331             if (!isValidRule(rule)) {
    332                 throw new IllegalArgumentException("Illegal rule value " + rule);
    333             }
    334             final int match_rule = rule & ~RULE_EXCLUSION_MASK;
    335             if (isAudioAttributeRule(match_rule)) {
    336                 if (!(property instanceof AudioAttributes)) {
    337                     throw new IllegalArgumentException("Invalid AudioAttributes argument");
    338                 }
    339                 return addRuleInternal((AudioAttributes) property, null, rule);
    340             } else {
    341                 // implies integer match rule
    342                 if (!(property instanceof Integer)) {
    343                     throw new IllegalArgumentException("Invalid Integer argument");
    344                 }
    345                 return addRuleInternal(null, (Integer) property, rule);
    346             }
    347         }
    348 
    349         /**
    350          * Add or exclude a rule on AudioAttributes or integer property for the selection of which
    351          * streams are mixed together.
    352          * No rule-to-parameter type check, all done in {@link #checkAddRuleObjInternal(int, Object)}.
    353          * Exceptions are thrown only when incompatible rules are added.
    354          * @param attrToMatch a non-null AudioAttributes instance for which a contradictory
    355          *     rule hasn't been set yet, null if not used.
    356          * @param intProp an integer property to match or exclude, null if not used.
    357          * @param rule one of {@link AudioMixingRule#RULE_EXCLUDE_ATTRIBUTE_USAGE},
    358          *     {@link AudioMixingRule#RULE_MATCH_ATTRIBUTE_USAGE},
    359          *     {@link AudioMixingRule#RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET} or
    360          *     {@link AudioMixingRule#RULE_EXCLUDE_ATTRIBUTE_CAPTURE_PRESET},
    361          *     {@link AudioMixingRule#RULE_MATCH_UID}, {@link AudioMixingRule#RULE_EXCLUDE_UID}.
    362          * @return the same Builder instance.
    363          * @throws IllegalArgumentException
    364          */
    365         private Builder addRuleInternal(AudioAttributes attrToMatch, Integer intProp, int rule)
    366                 throws IllegalArgumentException {
    367             // as rules are added to the Builder, we verify they are consistent with the type
    368             // of mix being built. When adding the first rule, the mix type is MIX_TYPE_INVALID.
    369             if (mTargetMixType == AudioMix.MIX_TYPE_INVALID) {
    370                 if (isPlayerRule(rule)) {
    371                     mTargetMixType = AudioMix.MIX_TYPE_PLAYERS;
    372                 } else {
    373                     mTargetMixType = AudioMix.MIX_TYPE_RECORDERS;
    374                 }
    375             } else if (((mTargetMixType == AudioMix.MIX_TYPE_PLAYERS) && !isPlayerRule(rule))
    376                     || ((mTargetMixType == AudioMix.MIX_TYPE_RECORDERS) && isPlayerRule(rule)))
    377             {
    378                 throw new IllegalArgumentException("Incompatible rule for mix");
    379             }
    380             synchronized (mCriteria) {
    381                 Iterator<AudioMixMatchCriterion> crIterator = mCriteria.iterator();
    382                 final int match_rule = rule & ~RULE_EXCLUSION_MASK;
    383                 while (crIterator.hasNext()) {
    384                     final AudioMixMatchCriterion criterion = crIterator.next();
    385                     switch (match_rule) {
    386                         case RULE_MATCH_ATTRIBUTE_USAGE:
    387                             // "usage"-based rule
    388                             if (criterion.mAttr.getUsage() == attrToMatch.getUsage()) {
    389                                 if (criterion.mRule == rule) {
    390                                     // rule already exists, we're done
    391                                     return this;
    392                                 } else {
    393                                     // criterion already exists with a another rule,
    394                                     // it is incompatible
    395                                     throw new IllegalArgumentException("Contradictory rule exists"
    396                                             + " for " + attrToMatch);
    397                                 }
    398                             }
    399                             break;
    400                         case RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET:
    401                             // "capture preset"-base rule
    402                             if (criterion.mAttr.getCapturePreset() == attrToMatch.getCapturePreset()) {
    403                                 if (criterion.mRule == rule) {
    404                                     // rule already exists, we're done
    405                                     return this;
    406                                 } else {
    407                                     // criterion already exists with a another rule,
    408                                     // it is incompatible
    409                                     throw new IllegalArgumentException("Contradictory rule exists"
    410                                             + " for " + attrToMatch);
    411                                 }
    412                             }
    413                             break;
    414                         case RULE_MATCH_UID:
    415                             // "usage"-based rule
    416                             if (criterion.mIntProp == intProp.intValue()) {
    417                                 if (criterion.mRule == rule) {
    418                                     // rule already exists, we're done
    419                                     return this;
    420                                 } else {
    421                                     // criterion already exists with a another rule,
    422                                     // it is incompatible
    423                                     throw new IllegalArgumentException("Contradictory rule exists"
    424                                             + " for UID " + intProp);
    425                                 }
    426                             }
    427                             break;
    428                     }
    429                 }
    430                 // rule didn't exist, add it
    431                 switch (match_rule) {
    432                     case RULE_MATCH_ATTRIBUTE_USAGE:
    433                     case RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET:
    434                         mCriteria.add(new AudioMixMatchCriterion(attrToMatch, rule));
    435                         break;
    436                     case RULE_MATCH_UID:
    437                         mCriteria.add(new AudioMixMatchCriterion(intProp, rule));
    438                         break;
    439                     default:
    440                         throw new IllegalStateException("Unreachable code in addRuleInternal()");
    441                 }
    442             }
    443             return this;
    444         }
    445 
    446         Builder addRuleFromParcel(Parcel in) throws IllegalArgumentException {
    447             final int rule = in.readInt();
    448             final int match_rule = rule & ~RULE_EXCLUSION_MASK;
    449             AudioAttributes attr = null;
    450             Integer intProp = null;
    451             switch (match_rule) {
    452                 case RULE_MATCH_ATTRIBUTE_USAGE:
    453                     int usage = in.readInt();
    454                     attr = new AudioAttributes.Builder()
    455                             .setUsage(usage).build();
    456                     break;
    457                 case RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET:
    458                     int preset = in.readInt();
    459                     attr = new AudioAttributes.Builder()
    460                             .setInternalCapturePreset(preset).build();
    461                     break;
    462                 case RULE_MATCH_UID:
    463                     intProp = new Integer(in.readInt());
    464                     break;
    465                 default:
    466                     // assume there was in int value to read as for now they come in pair
    467                     in.readInt();
    468                     throw new IllegalArgumentException("Illegal rule value " + rule + " in parcel");
    469             }
    470             return addRuleInternal(attr, intProp, rule);
    471         }
    472 
    473         /**
    474          * Combines all of the matching and exclusion rules that have been set and return a new
    475          * {@link AudioMixingRule} object.
    476          * @return a new {@link AudioMixingRule} object
    477          */
    478         public AudioMixingRule build() {
    479             return new AudioMixingRule(mTargetMixType, mCriteria);
    480         }
    481     }
    482 }
    483