content: /*
   * Copyright (C) 2018 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.annotation.NonNull;
  import android.annotation.Nullable;
  import android.annotation.SystemApi;
  import android.media.AudioAttributes;
  import android.media.AudioSystem;
  import android.media.MediaRecorder;
  import android.os.Parcel;
  import android.os.Parcelable;
  import android.text.TextUtils;
  import android.util.Log;
  
  import com.android.internal.annotations.GuardedBy;
  import com.android.internal.util.Preconditions;
  
  import java.util.ArrayList;
  import java.util.List;
  
  /**
   * @hide
   * A class to encapsulate a collection of attributes associated to a given product strategy
   * (and for legacy reason, keep the association with the stream type).
   */
  @SystemApi
  public final class AudioProductStrategy implements Parcelable {
      /**
       * group value to use when introspection API fails.
       * @hide
       */
      public static final int DEFAULT_GROUP = -1;
  
  
      private static final String TAG = "AudioProductStrategy";
  
      private final AudioAttributesGroup[] mAudioAttributesGroups;
      private final String mName;
      /**
       * Unique identifier of a product strategy.
       * This Id can be assimilated to Car Audio Usage and even more generally to usage.
       * For legacy platforms, the product strategy id is the routing_strategy, which was hidden to
       * upper layer but was transpiring in the {@link AudioAttributes#getUsage()}.
       */
      private int mId;
  
      private static final Object sLock = new Object();
  
      @GuardedBy("sLock")
      private static List<AudioProductStrategy> sAudioProductStrategies;
  
      /**
       * @hide
       * @return the list of AudioProductStrategy discovered from platform configuration file.
       */
      @NonNull
      public static List<AudioProductStrategy> getAudioProductStrategies() {
          if (sAudioProductStrategies == null) {
              synchronized (sLock) {
                  if (sAudioProductStrategies == null) {
                      sAudioProductStrategies = initializeAudioProductStrategies();
                  }
              }
          }
          return sAudioProductStrategies;
      }
  
      /**
       * @hide
       * @param streamType to match against AudioProductStrategy
       * @return the AudioAttributes for the first strategy found with the associated stream type
       *          If no match is found, returns AudioAttributes with unknown content_type and usage
       */
      @NonNull
      public static AudioAttributes getAudioAttributesForStrategyWithLegacyStreamType(
              int streamType) {
          for (final AudioProductStrategy productStrategy :
                  AudioProductStrategy.getAudioProductStrategies()) {
              AudioAttributes aa = productStrategy.getAudioAttributesForLegacyStreamType(streamType);
              if (aa != null) {
                  return aa;
              }
          }
          return new AudioAttributes.Builder()
              .setContentType(AudioAttributes.CONTENT_TYPE_UNKNOWN)
              .setUsage(AudioAttributes.USAGE_UNKNOWN).build();
      }
  
      /**
       * @hide
       * @param audioAttributes to identify AudioProductStrategy with
       * @return legacy stream type associated with matched AudioProductStrategy
       *              Defaults to STREAM_MUSIC if no match is found, or if matches is STREAM_DEFAULT
       */
      public static int getLegacyStreamTypeForStrategyWithAudioAttributes(
              @NonNull AudioAttributes audioAttributes) {
          Preconditions.checkNotNull(audioAttributes, "AudioAttributes must not be null");
          for (final AudioProductStrategy productStrategy :
                  AudioProductStrategy.getAudioProductStrategies()) {
              if (productStrategy.supportsAudioAttributes(audioAttributes)) {
                  int streamType = productStrategy.getLegacyStreamTypeForAudioAttributes(
                          audioAttributes);
                  if (streamType == AudioSystem.STREAM_DEFAULT) {
                      Log.w(TAG, "Attributes " + audioAttributes.toString() + " ported by strategy "
                              + productStrategy.getId() + " has no stream type associated, "
                              + "DO NOT USE STREAM TO CONTROL THE VOLUME");
                      return AudioSystem.STREAM_MUSIC;
                  }
                  return streamType;
              }
          }
          return AudioSystem.STREAM_MUSIC;
      }
  
      private static List<AudioProductStrategy> initializeAudioProductStrategies() {
          ArrayList<AudioProductStrategy> apsList = new ArrayList<AudioProductStrategy>();
          int status = native_list_audio_product_strategies(apsList);
          if (status != AudioSystem.SUCCESS) {
              Log.w(TAG, ": initializeAudioProductStrategies failed");
          }
          return apsList;
      }
  
      private static native int native_list_audio_product_strategies(
              ArrayList<AudioProductStrategy> strategies);
  
      @Override
      public boolean equals(@Nullable Object o) {
          if (this == o) return true;
          if (o == null || getClass() != o.getClass()) return false;
  
          AudioProductStrategy thatStrategy = (AudioProductStrategy) o;
  
          return mName == thatStrategy.mName && mId == thatStrategy.mId
                  && mAudioAttributesGroups.equals(thatStrategy.mAudioAttributesGroups);
      }
  
      /**
       * @param name of the product strategy
       * @param id of the product strategy
       * @param aag {@link AudioAttributesGroup} associated to the given product strategy
       */
      private AudioProductStrategy(@NonNull String name, int id,
              @NonNull AudioAttributesGroup[] aag) {
          Preconditions.checkNotNull(name, "name must not be null");
          Preconditions.checkNotNull(aag, "AudioAttributesGroups must not be null");
          mName = name;
          mId = id;
          mAudioAttributesGroups = aag;
      }
  
      /**
       * @hide
       * @return the product strategy ID (which is the generalisation of Car Audio Usage / legacy
       *         routing_strategy linked to {@link AudioAttributes#getUsage()}).
       */
      @SystemApi
      public int getId() {
          return mId;
      }
  
      /**
       * @hide
       * @return first {@link AudioAttributes} associated to this product strategy.
       */
      @SystemApi
      public @NonNull AudioAttributes getAudioAttributes() {
          // We need a choice, so take the first one
          return mAudioAttributesGroups.length == 0 ? (new AudioAttributes.Builder().build())
                  : mAudioAttributesGroups[0].getAudioAttributes();
      }
  
      /**
       * @hide
       * @param streamType legacy stream type used for volume operation only
       * @return the {@link AudioAttributes} relevant for the given streamType.
       *         If none is found, it builds the default attributes.
       */
      public @Nullable AudioAttributes getAudioAttributesForLegacyStreamType(int streamType) {
          for (final AudioAttributesGroup aag : mAudioAttributesGroups) {
              if (aag.supportsStreamType(streamType)) {
                  return aag.getAudioAttributes();
              }
          }
          return null;
      }
  
      /**
       * @hide
       * @param aa the {@link AudioAttributes} to be considered
       * @return the legacy stream type relevant for the given {@link AudioAttributes}.
       *         If none is found, it return DEFAULT stream type.
       */
      public int getLegacyStreamTypeForAudioAttributes(@NonNull AudioAttributes aa) {
          Preconditions.checkNotNull(aa, "AudioAttributes must not be null");
          for (final AudioAttributesGroup aag : mAudioAttributesGroups) {
              if (aag.supportsAttributes(aa)) {
                  return aag.getStreamType();
              }
          }
          return AudioSystem.STREAM_DEFAULT;
      }
  
      /**
       * @hide
       * @param aa the {@link AudioAttributes} to be considered
       * @return true if the {@link AudioProductStrategy} supports the given {@link AudioAttributes},
       *         false otherwise.
       */
      public boolean supportsAudioAttributes(@NonNull AudioAttributes aa) {
          Preconditions.checkNotNull(aa, "AudioAttributes must not be null");
          for (final AudioAttributesGroup aag : mAudioAttributesGroups) {
              if (aag.supportsAttributes(aa)) {
                  return true;
              }
          }
          return false;
      }
  
      /**
       * @hide
       * @param streamType legacy stream type used for volume operation only
       * @return the volume group id relevant for the given streamType.
       *         If none is found, {@link AudioVolumeGroup#DEFAULT_VOLUME_GROUP} is returned.
       */
      public int getVolumeGroupIdForLegacyStreamType(int streamType) {
          for (final AudioAttributesGroup aag : mAudioAttributesGroups) {
              if (aag.supportsStreamType(streamType)) {
                  return aag.getVolumeGroupId();
              }
          }
          return AudioVolumeGroup.DEFAULT_VOLUME_GROUP;
      }
  
      /**
       * @hide
       * @param aa the {@link AudioAttributes} to be considered
       * @return the volume group id associated with the given audio attributes if found,
       *         {@link AudioVolumeGroup#DEFAULT_VOLUME_GROUP} otherwise.
       */
      public int getVolumeGroupIdForAudioAttributes(@NonNull AudioAttributes aa) {
          Preconditions.checkNotNull(aa, "AudioAttributes must not be null");
          for (final AudioAttributesGroup aag : mAudioAttributesGroups) {
              if (aag.supportsAttributes(aa)) {
                  return aag.getVolumeGroupId();
              }
          }
          return AudioVolumeGroup.DEFAULT_VOLUME_GROUP;
      }
  
      @Override
      public int describeContents() {
          return 0;
      }
  
      @Override
      public void writeToParcel(@NonNull Parcel dest, int flags) {
          dest.writeString(mName);
          dest.writeInt(mId);
          dest.writeInt(mAudioAttributesGroups.length);
          for (AudioAttributesGroup aag : mAudioAttributesGroups) {
              aag.writeToParcel(dest, flags);
          }
      }
  
      @NonNull
      public static final Parcelable.Creator<AudioProductStrategy> CREATOR =
              new Parcelable.Creator<AudioProductStrategy>() {
                  @Override
                  public AudioProductStrategy createFromParcel(@NonNull Parcel in) {
                      String name = in.readString();
                      int id = in.readInt();
                      int nbAttributesGroups = in.readInt();
                      AudioAttributesGroup[] aag = new AudioAttributesGroup[nbAttributesGroups];
                      for (int index = 0; index < nbAttributesGroups; index++) {
                          aag[index] = AudioAttributesGroup.CREATOR.createFromParcel(in);
                      }
                      return new AudioProductStrategy(name, id, aag);
                  }
  
                  @Override
                  public @NonNull AudioProductStrategy[] newArray(int size) {
                      return new AudioProductStrategy[size];
                  }
              };
  
      @Override
      public String toString() {
          StringBuilder s = new StringBuilder();
          s.append("\n Name: ");
          s.append(mName);
          s.append(" Id: ");
          s.append(Integer.toString(mId));
          for (AudioAttributesGroup aag : mAudioAttributesGroups) {
              s.append(aag.toString());
          }
          return s.toString();
      }
  
      /**
       * @hide
       * Default attributes, with default source to be aligned with native.
       */
      public static final @NonNull AudioAttributes sDefaultAttributes =
              new AudioAttributes.Builder().setCapturePreset(MediaRecorder.AudioSource.DEFAULT)
                                           .build();
  
      /**
       * To avoid duplicating the logic in java and native, we shall make use of
       * native API native_get_product_strategies_from_audio_attributes
       * @param refAttr {@link AudioAttributes} to be taken as the reference
       * @param attr {@link AudioAttributes} of the requester.
       */
      private static boolean attributesMatches(@NonNull AudioAttributes refAttr,
              @NonNull AudioAttributes attr) {
          Preconditions.checkNotNull(refAttr, "refAttr must not be null");
          Preconditions.checkNotNull(attr, "attr must not be null");
          String refFormattedTags = TextUtils.join(";", refAttr.getTags());
          String cliFormattedTags = TextUtils.join(";", attr.getTags());
          if (refAttr.equals(sDefaultAttributes)) {
              return false;
          }
          return ((refAttr.getUsage() == AudioAttributes.USAGE_UNKNOWN)
                  || (attr.getUsage() == refAttr.getUsage()))
              && ((refAttr.getContentType() == AudioAttributes.CONTENT_TYPE_UNKNOWN)
                  || (attr.getContentType() == refAttr.getContentType()))
              && ((refAttr.getAllFlags() == 0)
                  || (attr.getAllFlags() != 0
                  && (attr.getAllFlags() & refAttr.getAllFlags()) == refAttr.getAllFlags()))
              && ((refFormattedTags.length() == 0) || refFormattedTags.equals(cliFormattedTags));
      }
  
      private static final class AudioAttributesGroup implements Parcelable {
          private int mVolumeGroupId;
          private int mLegacyStreamType;
          private final AudioAttributes[] mAudioAttributes;
  
          AudioAttributesGroup(int volumeGroupId, int streamType,
                  @NonNull AudioAttributes[] audioAttributes) {
              mVolumeGroupId = volumeGroupId;
              mLegacyStreamType = streamType;
              mAudioAttributes = audioAttributes;
          }
  
          @Override
          public boolean equals(@Nullable Object o) {
              if (this == o) return true;
              if (o == null || getClass() != o.getClass()) return false;
  
              AudioAttributesGroup thatAag = (AudioAttributesGroup) o;
  
              return mVolumeGroupId == thatAag.mVolumeGroupId
                      && mLegacyStreamType == thatAag.mLegacyStreamType
                      && mAudioAttributes.equals(thatAag.mAudioAttributes);
          }
  
          public int getStreamType() {
              return mLegacyStreamType;
          }
  
          public int getVolumeGroupId() {
              return mVolumeGroupId;
          }
  
          public @NonNull AudioAttributes getAudioAttributes() {
              // We need a choice, so take the first one
              return mAudioAttributes.length == 0 ? (new AudioAttributes.Builder().build())
                      : mAudioAttributes[0];
          }
  
          /**
           * Checks if a {@link AudioAttributes} is supported by this product strategy.
           * @param {@link AudioAttributes} to check upon support
           * @return true if the {@link AudioAttributes} follows this product strategy,
                     false otherwise.
           */
          public boolean supportsAttributes(@NonNull AudioAttributes attributes) {
              for (final AudioAttributes refAa : mAudioAttributes) {
                  if (refAa.equals(attributes) || attributesMatches(refAa, attributes)) {
                      return true;
                  }
              }
              return false;
          }
  
          public boolean supportsStreamType(int streamType) {
              return mLegacyStreamType == streamType;
          }
  
          @Override
          public int describeContents() {
              return 0;
          }
  
          @Override
          public void writeToParcel(@NonNull Parcel dest, int flags) {
              dest.writeInt(mVolumeGroupId);
              dest.writeInt(mLegacyStreamType);
              dest.writeInt(mAudioAttributes.length);
              for (AudioAttributes attributes : mAudioAttributes) {
                  attributes.writeToParcel(dest, flags | AudioAttributes.FLATTEN_TAGS/*flags*/);
              }
          }
  
          public static final @android.annotation.NonNull Parcelable.Creator<AudioAttributesGroup> CREATOR =
                  new Parcelable.Creator<AudioAttributesGroup>() {
                      @Override
                      public AudioAttributesGroup createFromParcel(@NonNull Parcel in) {
                          int volumeGroupId = in.readInt();
                          int streamType = in.readInt();
                          int nbAttributes = in.readInt();
                          AudioAttributes[] aa = new AudioAttributes[nbAttributes];
                          for (int index = 0; index < nbAttributes; index++) {
                              aa[index] = AudioAttributes.CREATOR.createFromParcel(in);
                          }
                          return new AudioAttributesGroup(volumeGroupId, streamType, aa);
                      }
  
                      @Override
                      public @NonNull AudioAttributesGroup[] newArray(int size) {
                          return new AudioAttributesGroup[size];
                      }
                  };
  
  
          @Override
          public @NonNull String toString() {
              StringBuilder s = new StringBuilder();
              s.append("\n    Legacy Stream Type: ");
              s.append(Integer.toString(mLegacyStreamType));
              s.append(" Volume Group Id: ");
              s.append(Integer.toString(mVolumeGroupId));
  
              for (AudioAttributes attribute : mAudioAttributes) {
                  s.append("\n    -");
                  s.append(attribute.toString());
              }
              return s.toString();
          }
      }
  }
  