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     boolean isAffectingUsage(int usage) {
    139         for (AudioMixMatchCriterion criterion : mCriteria) {
    140             if ((criterion.mRule & RULE_MATCH_ATTRIBUTE_USAGE) != 0
    141                     && criterion.mAttr != null
    142                     && criterion.mAttr.getUsage() == usage) {
    143                 return true;
    144             }
    145         }
    146         return false;
    147     }
    148 
    149     private static boolean areCriteriaEquivalent(ArrayList<AudioMixMatchCriterion> cr1,
    150             ArrayList<AudioMixMatchCriterion> cr2) {
    151         if (cr1 == null || cr2 == null) return false;
    152         if (cr1 == cr2) return true;
    153         if (cr1.size() != cr2.size()) return false;
    154         //TODO iterate over rules to check they contain the same criterion
    155         return (cr1.hashCode() == cr2.hashCode());
    156     }
    157 
    158     private final int mTargetMixType;
    159     int getTargetMixType() { return mTargetMixType; }
    160     private final ArrayList<AudioMixMatchCriterion> mCriteria;
    161     ArrayList<AudioMixMatchCriterion> getCriteria() { return mCriteria; }
    162 
    163     /** @hide */
    164     @Override
    165     public boolean equals(Object o) {
    166         if (this == o) return true;
    167         if (o == null || getClass() != o.getClass()) return false;
    168 
    169         final AudioMixingRule that = (AudioMixingRule) o;
    170         return (this.mTargetMixType == that.mTargetMixType)
    171                 && (areCriteriaEquivalent(this.mCriteria, that.mCriteria));
    172     }
    173 
    174     @Override
    175     public int hashCode() {
    176         return Objects.hash(mTargetMixType, mCriteria);
    177     }
    178 
    179     private static boolean isValidSystemApiRule(int rule) {
    180         // API rules only expose the RULE_MATCH_* rules
    181         switch (rule) {
    182             case RULE_MATCH_ATTRIBUTE_USAGE:
    183             case RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET:
    184             case RULE_MATCH_UID:
    185                 return true;
    186             default:
    187                 return false;
    188         }
    189     }
    190     private static boolean isValidAttributesSystemApiRule(int rule) {
    191         // API rules only expose the RULE_MATCH_* rules
    192         switch (rule) {
    193             case RULE_MATCH_ATTRIBUTE_USAGE:
    194             case RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET:
    195                 return true;
    196             default:
    197                 return false;
    198         }
    199     }
    200 
    201     private static boolean isValidRule(int rule) {
    202         final int match_rule = rule & ~RULE_EXCLUSION_MASK;
    203         switch (match_rule) {
    204             case RULE_MATCH_ATTRIBUTE_USAGE:
    205             case RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET:
    206             case RULE_MATCH_UID:
    207                 return true;
    208             default:
    209                 return false;
    210         }
    211     }
    212 
    213     private static boolean isPlayerRule(int rule) {
    214         final int match_rule = rule & ~RULE_EXCLUSION_MASK;
    215         switch (match_rule) {
    216         case RULE_MATCH_ATTRIBUTE_USAGE:
    217         case RULE_MATCH_UID:
    218             return true;
    219         default:
    220             return false;
    221         }
    222     }
    223 
    224     private static boolean isAudioAttributeRule(int match_rule) {
    225         switch(match_rule) {
    226             case RULE_MATCH_ATTRIBUTE_USAGE:
    227             case RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET:
    228                 return true;
    229             default:
    230                 return false;
    231         }
    232     }
    233 
    234     /**
    235      * Builder class for {@link AudioMixingRule} objects
    236      */
    237     @SystemApi
    238     public static class Builder {
    239         private ArrayList<AudioMixMatchCriterion> mCriteria;
    240         private int mTargetMixType = AudioMix.MIX_TYPE_INVALID;
    241 
    242         /**
    243          * Constructs a new Builder with no rules.
    244          */
    245         @SystemApi
    246         public Builder() {
    247             mCriteria = new ArrayList<AudioMixMatchCriterion>();
    248         }
    249 
    250         /**
    251          * Add a rule for the selection of which streams are mixed together.
    252          * @param attrToMatch a non-null AudioAttributes instance for which a contradictory
    253          *     rule hasn't been set yet.
    254          * @param rule {@link AudioMixingRule#RULE_MATCH_ATTRIBUTE_USAGE} or
    255          *     {@link AudioMixingRule#RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET}.
    256          * @return the same Builder instance.
    257          * @throws IllegalArgumentException
    258          * @see #excludeRule(AudioAttributes, int)
    259          */
    260         @SystemApi
    261         public Builder addRule(AudioAttributes attrToMatch, int rule)
    262                 throws IllegalArgumentException {
    263             if (!isValidAttributesSystemApiRule(rule)) {
    264                 throw new IllegalArgumentException("Illegal rule value " + rule);
    265             }
    266             return checkAddRuleObjInternal(rule, attrToMatch);
    267         }
    268 
    269         /**
    270          * Add a rule by exclusion for the selection of which streams are mixed together.
    271          * <br>For instance the following code
    272          * <br><pre>
    273          * AudioAttributes mediaAttr = new AudioAttributes.Builder()
    274          *         .setUsage(AudioAttributes.USAGE_MEDIA)
    275          *         .build();
    276          * AudioMixingRule noMediaRule = new AudioMixingRule.Builder()
    277          *         .excludeRule(mediaAttr, AudioMixingRule.RULE_MATCH_ATTRIBUTE_USAGE)
    278          *         .build();
    279          * </pre>
    280          * <br>will create a rule which maps to any usage value, except USAGE_MEDIA.
    281          * @param attrToMatch a non-null AudioAttributes instance for which a contradictory
    282          *     rule hasn't been set yet.
    283          * @param rule {@link AudioMixingRule#RULE_MATCH_ATTRIBUTE_USAGE} or
    284          *     {@link AudioMixingRule#RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET}.
    285          * @return the same Builder instance.
    286          * @throws IllegalArgumentException
    287          * @see #addRule(AudioAttributes, int)
    288          */
    289         @SystemApi
    290         public Builder excludeRule(AudioAttributes attrToMatch, int rule)
    291                 throws IllegalArgumentException {
    292             if (!isValidAttributesSystemApiRule(rule)) {
    293                 throw new IllegalArgumentException("Illegal rule value " + rule);
    294             }
    295             return checkAddRuleObjInternal(rule | RULE_EXCLUSION_MASK, attrToMatch);
    296         }
    297 
    298         /**
    299          * Add a rule for the selection of which streams are mixed together.
    300          * The rule defines what the matching will be made on. It also determines the type of the
    301          * property to match against.
    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          * @see #excludeMixRule(int, Object)
    310          */
    311         @SystemApi
    312         public Builder addMixRule(int rule, Object property) throws IllegalArgumentException {
    313             if (!isValidSystemApiRule(rule)) {
    314                 throw new IllegalArgumentException("Illegal rule value " + rule);
    315             }
    316             return checkAddRuleObjInternal(rule, property);
    317         }
    318 
    319         /**
    320          * Add a rule by exclusion for the selection of which streams are mixed together.
    321          * <br>For instance the following code
    322          * <br><pre>
    323          * AudioAttributes mediaAttr = new AudioAttributes.Builder()
    324          *         .setUsage(AudioAttributes.USAGE_MEDIA)
    325          *         .build();
    326          * AudioMixingRule noMediaRule = new AudioMixingRule.Builder()
    327          *         .addMixRule(AudioMixingRule.RULE_MATCH_ATTRIBUTE_USAGE, mediaAttr)
    328          *         .excludeMixRule(AudioMixingRule.RULE_MATCH_UID, new Integer(uidToExclude)
    329          *         .build();
    330          * </pre>
    331          * <br>will create a rule which maps to usage USAGE_MEDIA, but excludes any stream
    332          * coming from the specified UID.
    333          * @param rule one of {@link AudioMixingRule#RULE_MATCH_ATTRIBUTE_USAGE},
    334          *     {@link AudioMixingRule#RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET} or
    335          *     {@link AudioMixingRule#RULE_MATCH_UID}.
    336          * @param property see the definition of each rule for the type to use (either an
    337          *     {@link AudioAttributes} or an {@link java.lang.Integer}).
    338          * @return the same Builder instance.
    339          * @throws IllegalArgumentException
    340          */
    341         @SystemApi
    342         public Builder excludeMixRule(int rule, Object property) throws IllegalArgumentException {
    343             if (!isValidSystemApiRule(rule)) {
    344                 throw new IllegalArgumentException("Illegal rule value " + rule);
    345             }
    346             return checkAddRuleObjInternal(rule | RULE_EXCLUSION_MASK, property);
    347         }
    348 
    349         /**
    350          * Add or exclude a rule for the selection of which streams are mixed together.
    351          * Does error checking on the parameters.
    352          * @param rule
    353          * @param property
    354          * @return the same Builder instance.
    355          * @throws IllegalArgumentException
    356          */
    357         private Builder checkAddRuleObjInternal(int rule, Object property)
    358                 throws IllegalArgumentException {
    359             if (property == null) {
    360                 throw new IllegalArgumentException("Illegal null argument for mixing rule");
    361             }
    362             if (!isValidRule(rule)) {
    363                 throw new IllegalArgumentException("Illegal rule value " + rule);
    364             }
    365             final int match_rule = rule & ~RULE_EXCLUSION_MASK;
    366             if (isAudioAttributeRule(match_rule)) {
    367                 if (!(property instanceof AudioAttributes)) {
    368                     throw new IllegalArgumentException("Invalid AudioAttributes argument");
    369                 }
    370                 return addRuleInternal((AudioAttributes) property, null, rule);
    371             } else {
    372                 // implies integer match rule
    373                 if (!(property instanceof Integer)) {
    374                     throw new IllegalArgumentException("Invalid Integer argument");
    375                 }
    376                 return addRuleInternal(null, (Integer) property, rule);
    377             }
    378         }
    379 
    380         /**
    381          * Add or exclude a rule on AudioAttributes or integer property for the selection of which
    382          * streams are mixed together.
    383          * No rule-to-parameter type check, all done in {@link #checkAddRuleObjInternal(int, Object)}.
    384          * Exceptions are thrown only when incompatible rules are added.
    385          * @param attrToMatch a non-null AudioAttributes instance for which a contradictory
    386          *     rule hasn't been set yet, null if not used.
    387          * @param intProp an integer property to match or exclude, null if not used.
    388          * @param rule one of {@link AudioMixingRule#RULE_EXCLUDE_ATTRIBUTE_USAGE},
    389          *     {@link AudioMixingRule#RULE_MATCH_ATTRIBUTE_USAGE},
    390          *     {@link AudioMixingRule#RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET} or
    391          *     {@link AudioMixingRule#RULE_EXCLUDE_ATTRIBUTE_CAPTURE_PRESET},
    392          *     {@link AudioMixingRule#RULE_MATCH_UID}, {@link AudioMixingRule#RULE_EXCLUDE_UID}.
    393          * @return the same Builder instance.
    394          * @throws IllegalArgumentException
    395          */
    396         private Builder addRuleInternal(AudioAttributes attrToMatch, Integer intProp, int rule)
    397                 throws IllegalArgumentException {
    398             // as rules are added to the Builder, we verify they are consistent with the type
    399             // of mix being built. When adding the first rule, the mix type is MIX_TYPE_INVALID.
    400             if (mTargetMixType == AudioMix.MIX_TYPE_INVALID) {
    401                 if (isPlayerRule(rule)) {
    402                     mTargetMixType = AudioMix.MIX_TYPE_PLAYERS;
    403                 } else {
    404                     mTargetMixType = AudioMix.MIX_TYPE_RECORDERS;
    405                 }
    406             } else if (((mTargetMixType == AudioMix.MIX_TYPE_PLAYERS) && !isPlayerRule(rule))
    407                     || ((mTargetMixType == AudioMix.MIX_TYPE_RECORDERS) && isPlayerRule(rule)))
    408             {
    409                 throw new IllegalArgumentException("Incompatible rule for mix");
    410             }
    411             synchronized (mCriteria) {
    412                 Iterator<AudioMixMatchCriterion> crIterator = mCriteria.iterator();
    413                 final int match_rule = rule & ~RULE_EXCLUSION_MASK;
    414                 while (crIterator.hasNext()) {
    415                     final AudioMixMatchCriterion criterion = crIterator.next();
    416                     switch (match_rule) {
    417                         case RULE_MATCH_ATTRIBUTE_USAGE:
    418                             // "usage"-based rule
    419                             if (criterion.mAttr.getUsage() == attrToMatch.getUsage()) {
    420                                 if (criterion.mRule == rule) {
    421                                     // rule already exists, we're done
    422                                     return this;
    423                                 } else {
    424                                     // criterion already exists with a another rule,
    425                                     // it is incompatible
    426                                     throw new IllegalArgumentException("Contradictory rule exists"
    427                                             + " for " + attrToMatch);
    428                                 }
    429                             }
    430                             break;
    431                         case RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET:
    432                             // "capture preset"-base rule
    433                             if (criterion.mAttr.getCapturePreset() == attrToMatch.getCapturePreset()) {
    434                                 if (criterion.mRule == rule) {
    435                                     // rule already exists, we're done
    436                                     return this;
    437                                 } else {
    438                                     // criterion already exists with a another rule,
    439                                     // it is incompatible
    440                                     throw new IllegalArgumentException("Contradictory rule exists"
    441                                             + " for " + attrToMatch);
    442                                 }
    443                             }
    444                             break;
    445                         case RULE_MATCH_UID:
    446                             // "usage"-based rule
    447                             if (criterion.mIntProp == intProp.intValue()) {
    448                                 if (criterion.mRule == rule) {
    449                                     // rule already exists, we're done
    450                                     return this;
    451                                 } else {
    452                                     // criterion already exists with a another rule,
    453                                     // it is incompatible
    454                                     throw new IllegalArgumentException("Contradictory rule exists"
    455                                             + " for UID " + intProp);
    456                                 }
    457                             }
    458                             break;
    459                     }
    460                 }
    461                 // rule didn't exist, add it
    462                 switch (match_rule) {
    463                     case RULE_MATCH_ATTRIBUTE_USAGE:
    464                     case RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET:
    465                         mCriteria.add(new AudioMixMatchCriterion(attrToMatch, rule));
    466                         break;
    467                     case RULE_MATCH_UID:
    468                         mCriteria.add(new AudioMixMatchCriterion(intProp, rule));
    469                         break;
    470                     default:
    471                         throw new IllegalStateException("Unreachable code in addRuleInternal()");
    472                 }
    473             }
    474             return this;
    475         }
    476 
    477         Builder addRuleFromParcel(Parcel in) throws IllegalArgumentException {
    478             final int rule = in.readInt();
    479             final int match_rule = rule & ~RULE_EXCLUSION_MASK;
    480             AudioAttributes attr = null;
    481             Integer intProp = null;
    482             switch (match_rule) {
    483                 case RULE_MATCH_ATTRIBUTE_USAGE:
    484                     int usage = in.readInt();
    485                     attr = new AudioAttributes.Builder()
    486                             .setUsage(usage).build();
    487                     break;
    488                 case RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET:
    489                     int preset = in.readInt();
    490                     attr = new AudioAttributes.Builder()
    491                             .setInternalCapturePreset(preset).build();
    492                     break;
    493                 case RULE_MATCH_UID:
    494                     intProp = new Integer(in.readInt());
    495                     break;
    496                 default:
    497                     // assume there was in int value to read as for now they come in pair
    498                     in.readInt();
    499                     throw new IllegalArgumentException("Illegal rule value " + rule + " in parcel");
    500             }
    501             return addRuleInternal(attr, intProp, rule);
    502         }
    503 
    504         /**
    505          * Combines all of the matching and exclusion rules that have been set and return a new
    506          * {@link AudioMixingRule} object.
    507          * @return a new {@link AudioMixingRule} object
    508          */
    509         public AudioMixingRule build() {
    510             return new AudioMixingRule(mTargetMixType, mCriteria);
    511         }
    512     }
    513 }
    514