content: /*
   * Copyright (C) 2010 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.audiofx;
  
  import android.annotation.SdkConstant;
  import android.annotation.SdkConstant.SdkConstantType;
  import android.annotation.TestApi;
  import android.annotation.UnsupportedAppUsage;
  import android.app.ActivityThread;
  import android.os.Build;
  import android.os.Handler;
  import android.os.Looper;
  import android.os.Message;
  import android.os.Parcel;
  import android.util.Log;
  
  import java.lang.ref.WeakReference;
  import java.nio.ByteBuffer;
  import java.nio.ByteOrder;
  import java.util.Objects;
  import java.util.UUID;
  
  /**
   * AudioEffect is the base class for controlling audio effects provided by the android audio
   * framework.
   * <p>Applications should not use the AudioEffect class directly but one of its derived classes to
   * control specific effects:
   * <ul>
   *   <li> {@link android.media.audiofx.Equalizer}</li>
   *   <li> {@link android.media.audiofx.Virtualizer}</li>
   *   <li> {@link android.media.audiofx.BassBoost}</li>
   *   <li> {@link android.media.audiofx.PresetReverb}</li>
   *   <li> {@link android.media.audiofx.EnvironmentalReverb}</li>
   *   <li> {@link android.media.audiofx.DynamicsProcessing}</li>
   * </ul>
   * <p>To apply the audio effect to a specific AudioTrack or MediaPlayer instance,
   * the application must specify the audio session ID of that instance when creating the AudioEffect.
   * (see {@link android.media.MediaPlayer#getAudioSessionId()} for details on audio sessions).
   * <p>NOTE: attaching insert effects (equalizer, bass boost, virtualizer) to the global audio output
   * mix by use of session 0 is deprecated.
   * <p>Creating an AudioEffect object will create the corresponding effect engine in the audio
   * framework if no instance of the same effect type exists in the specified audio session.
   * If one exists, this instance will be used.
   * <p>The application creating the AudioEffect object (or a derived class) will either receive
   * control of the effect engine or not depending on the priority parameter. If priority is higher
   * than the priority used by the current effect engine owner, the control will be transfered to the
   * new object. Otherwise control will remain with the previous object. In this case, the new
   * application will be notified of changes in effect engine state or control ownership by the
   * appropriate listener.
   */
  
  public class AudioEffect {
      static {
          System.loadLibrary("audioeffect_jni");
          native_init();
      }
  
      private final static String TAG = "AudioEffect-JAVA";
  
      // effect type UUIDs are taken from hardware/libhardware/include/hardware/audio_effect.h
  
      /**
       * The following UUIDs define effect types corresponding to standard audio
       * effects whose implementation and interface conform to the OpenSL ES
       * specification. The definitions match the corresponding interface IDs in
       * OpenSLES_IID.h
       */
      /**
       * UUID for environmental reverberation effect
       */
      public static final UUID EFFECT_TYPE_ENV_REVERB = UUID
              .fromString("c2e5d5f0-94bd-4763-9cac-4e234d06839e");
      /**
       * UUID for preset reverberation effect
       */
      public static final UUID EFFECT_TYPE_PRESET_REVERB = UUID
              .fromString("47382d60-ddd8-11db-bf3a-0002a5d5c51b");
      /**
       * UUID for equalizer effect
       */
      public static final UUID EFFECT_TYPE_EQUALIZER = UUID
              .fromString("0bed4300-ddd6-11db-8f34-0002a5d5c51b");
      /**
       * UUID for bass boost effect
       */
      public static final UUID EFFECT_TYPE_BASS_BOOST = UUID
              .fromString("0634f220-ddd4-11db-a0fc-0002a5d5c51b");
      /**
       * UUID for virtualizer effect
       */
      public static final UUID EFFECT_TYPE_VIRTUALIZER = UUID
              .fromString("37cc2c00-dddd-11db-8577-0002a5d5c51b");
  
      /**
       * UUIDs for effect types not covered by OpenSL ES.
       */
      /**
       * UUID for Automatic Gain Control (AGC)
       */
      public static final UUID EFFECT_TYPE_AGC = UUID
              .fromString("0a8abfe0-654c-11e0-ba26-0002a5d5c51b");
  
      /**
       * UUID for Acoustic Echo Canceler (AEC)
       */
      public static final UUID EFFECT_TYPE_AEC = UUID
              .fromString("7b491460-8d4d-11e0-bd61-0002a5d5c51b");
  
      /**
       * UUID for Noise Suppressor (NS)
       */
      public static final UUID EFFECT_TYPE_NS = UUID
              .fromString("58b4b260-8e06-11e0-aa8e-0002a5d5c51b");
  
      /**
       * UUID for Loudness Enhancer
       */
      public static final UUID EFFECT_TYPE_LOUDNESS_ENHANCER = UUID
                .fromString("fe3199be-aed0-413f-87bb-11260eb63cf1");
  
      /**
       * UUID for Dynamics Processing
       */
      public static final UUID EFFECT_TYPE_DYNAMICS_PROCESSING = UUID
                .fromString("7261676f-6d75-7369-6364-28e2fd3ac39e");
  
      /**
       * Null effect UUID. See {@link AudioEffect(UUID, UUID, int, int)} for use.
       * @hide
       */
      @TestApi
      public static final UUID EFFECT_TYPE_NULL = UUID
              .fromString("ec7178ec-e5e1-4432-a3f4-4657e6795210");
  
      /**
       * State of an AudioEffect object that was not successfully initialized upon
       * creation
       * @hide
       */
      public static final int STATE_UNINITIALIZED = 0;
      /**
       * State of an AudioEffect object that is ready to be used.
       * @hide
       */
      public static final int STATE_INITIALIZED = 1;
  
      // to keep in sync with
      // frameworks/base/include/media/AudioEffect.h
      /**
       * Event id for engine control ownership change notification.
       * @hide
       */
      public static final int NATIVE_EVENT_CONTROL_STATUS = 0;
      /**
       * Event id for engine state change notification.
       * @hide
       */
      public static final int NATIVE_EVENT_ENABLED_STATUS = 1;
      /**
       * Event id for engine parameter change notification.
       * @hide
       */
      public static final int NATIVE_EVENT_PARAMETER_CHANGED = 2;
  
      /**
       * Successful operation.
       */
      public static final int SUCCESS = 0;
      /**
       * Unspecified error.
       */
      public static final int ERROR = -1;
      /**
       * Internal operation status. Not returned by any method.
       */
      public static final int ALREADY_EXISTS = -2;
      /**
       * Operation failed due to bad object initialization.
       */
      public static final int ERROR_NO_INIT = -3;
      /**
       * Operation failed due to bad parameter value.
       */
      public static final int ERROR_BAD_VALUE = -4;
      /**
       * Operation failed because it was requested in wrong state.
       */
      public static final int ERROR_INVALID_OPERATION = -5;
      /**
       * Operation failed due to lack of memory.
       */
      public static final int ERROR_NO_MEMORY = -6;
      /**
       * Operation failed due to dead remote object.
       */
      public static final int ERROR_DEAD_OBJECT = -7;
  
      /**
       * The effect descriptor contains information on a particular effect implemented in the
       * audio framework:<br>
       * <ul>
       *  <li>type: UUID identifying the effect type. May be one of:
       * {@link AudioEffect#EFFECT_TYPE_AEC}, {@link AudioEffect#EFFECT_TYPE_AGC},
       * {@link AudioEffect#EFFECT_TYPE_BASS_BOOST}, {@link AudioEffect#EFFECT_TYPE_ENV_REVERB},
       * {@link AudioEffect#EFFECT_TYPE_EQUALIZER}, {@link AudioEffect#EFFECT_TYPE_NS},
       * {@link AudioEffect#EFFECT_TYPE_PRESET_REVERB}, {@link AudioEffect#EFFECT_TYPE_VIRTUALIZER},
       * {@link AudioEffect#EFFECT_TYPE_DYNAMICS_PROCESSING}.
       *  </li>
       *  <li>uuid: UUID for this particular implementation</li>
       *  <li>connectMode: {@link #EFFECT_INSERT} or {@link #EFFECT_AUXILIARY}</li>
       *  <li>name: human readable effect name</li>
       *  <li>implementor: human readable effect implementor name</li>
       * </ul>
       * The method {@link #queryEffects()} returns an array of Descriptors to facilitate effects
       * enumeration.
       */
      public static class Descriptor {
  
          public Descriptor() {
          }
  
          /**
           *  Indicates the generic type of the effect (Equalizer, Bass boost ...).
           *  One of {@link AudioEffect#EFFECT_TYPE_AEC},
           *  {@link AudioEffect#EFFECT_TYPE_AGC}, {@link AudioEffect#EFFECT_TYPE_BASS_BOOST},
           *  {@link AudioEffect#EFFECT_TYPE_ENV_REVERB}, {@link AudioEffect#EFFECT_TYPE_EQUALIZER},
           *  {@link AudioEffect#EFFECT_TYPE_NS}, {@link AudioEffect#EFFECT_TYPE_PRESET_REVERB}
           *  {@link AudioEffect#EFFECT_TYPE_VIRTUALIZER}
           *   or {@link AudioEffect#EFFECT_TYPE_DYNAMICS_PROCESSING}.<br>
           *  For reverberation, bass boost, EQ and virtualizer, the UUID
           *  corresponds to the OpenSL ES Interface ID.
           */
          public UUID type;
          /**
           *  Indicates the particular implementation of the effect in that type. Several effects
           *  can have the same type but this uuid is unique to a given implementation.
           */
          public UUID uuid;
          /**
           *  Indicates if the effect is of insert category {@link #EFFECT_INSERT} or auxiliary
           *  category {@link #EFFECT_AUXILIARY}.
           *  Insert effects (typically an {@link Equalizer}) are applied
           *  to the entire audio source and usually not shared by several sources. Auxiliary effects
           *  (typically a reverberator) are applied to part of the signal (wet) and the effect output
           *  is added to the original signal (dry).
           *  Audio pre processing are applied to audio captured on a particular
           * {@link android.media.AudioRecord}.
           */
          public String connectMode;
          /**
           * Human readable effect name
           */
          public String name;
          /**
           * Human readable effect implementor name
           */
          public String implementor;
  
          /**
           * @param type          UUID identifying the effect type. May be one of:
           * {@link AudioEffect#EFFECT_TYPE_AEC}, {@link AudioEffect#EFFECT_TYPE_AGC},
           * {@link AudioEffect#EFFECT_TYPE_BASS_BOOST}, {@link AudioEffect#EFFECT_TYPE_ENV_REVERB},
           * {@link AudioEffect#EFFECT_TYPE_EQUALIZER}, {@link AudioEffect#EFFECT_TYPE_NS},
           * {@link AudioEffect#EFFECT_TYPE_PRESET_REVERB},
           * {@link AudioEffect#EFFECT_TYPE_VIRTUALIZER},
           * {@link AudioEffect#EFFECT_TYPE_DYNAMICS_PROCESSING}.
           * @param uuid         UUID for this particular implementation
           * @param connectMode  {@link #EFFECT_INSERT} or {@link #EFFECT_AUXILIARY}
           * @param name         human readable effect name
           * @param implementor  human readable effect implementor name
          *
          */
          public Descriptor(String type, String uuid, String connectMode,
                  String name, String implementor) {
              this.type = UUID.fromString(type);
              this.uuid = UUID.fromString(uuid);
              this.connectMode = connectMode;
              this.name = name;
              this.implementor = implementor;
          }
  
          /** @hide */
          @TestApi
          public Descriptor(Parcel in) {
              type = UUID.fromString(in.readString());
              uuid = UUID.fromString(in.readString());
              connectMode = in.readString();
              name = in.readString();
              implementor = in.readString();
          }
  
          @Override
          public int hashCode() {
              return Objects.hash(type, uuid, connectMode, name, implementor);
          }
  
          /** @hide */
          @TestApi
          public void writeToParcel(Parcel dest) {
              dest.writeString(type.toString());
              dest.writeString(uuid.toString());
              dest.writeString(connectMode);
              dest.writeString(name);
              dest.writeString(implementor);
          }
  
          @Override
          public boolean equals(Object o) {
              if (this == o) return true;
              if (o == null || !(o instanceof Descriptor)) return false;
  
              Descriptor that = (Descriptor) o;
  
              return (type.equals(that.type)
                      && uuid.equals(that.uuid)
                      && connectMode.equals(that.connectMode)
                      && name.equals(that.name)
                      && implementor.equals(that.implementor));
          }
      }
  
      /**
       * Effect connection mode is insert. Specifying an audio session ID when creating the effect
       * will insert this effect after all players in the same audio session.
       */
      public static final String EFFECT_INSERT = "Insert";
      /**
       * Effect connection mode is auxiliary.
       * <p>Auxiliary effects must be created on session 0 (global output mix). In order for a
       * MediaPlayer or AudioTrack to be fed into this effect, they must be explicitely attached to
       * this effect and a send level must be specified.
       * <p>Use the effect ID returned by {@link #getId()} to designate this particular effect when
       * attaching it to the MediaPlayer or AudioTrack.
       */
      public static final String EFFECT_AUXILIARY = "Auxiliary";
      /**
       * Effect connection mode is pre processing.
       * The audio pre processing effects are attached to an audio input (AudioRecord).
       * @hide
       */
      public static final String EFFECT_PRE_PROCESSING = "Pre Processing";
  
      // --------------------------------------------------------------------------
      // Member variables
      // --------------------
      /**
       * Indicates the state of the AudioEffect instance
       */
      private int mState = STATE_UNINITIALIZED;
      /**
       * Lock to synchronize access to mState
       */
      private final Object mStateLock = new Object();
      /**
       * System wide unique effect ID
       */
      private int mId;
  
      // accessed by native methods
      private long mNativeAudioEffect;
      private long mJniData;
  
      /**
       * Effect descriptor
       */
      private Descriptor mDescriptor;
  
      /**
       * Listener for effect engine state change notifications.
       *
       * @see #setEnableStatusListener(OnEnableStatusChangeListener)
       */
      private OnEnableStatusChangeListener mEnableStatusChangeListener = null;
      /**
       * Listener for effect engine control ownership change notifications.
       *
       * @see #setControlStatusListener(OnControlStatusChangeListener)
       */
      private OnControlStatusChangeListener mControlChangeStatusListener = null;
      /**
       * Listener for effect engine control ownership change notifications.
       *
       * @see #setParameterListener(OnParameterChangeListener)
       */
      private OnParameterChangeListener mParameterChangeListener = null;
      /**
       * Lock to protect listeners updates against event notifications
       * @hide
       */
      public final Object mListenerLock = new Object();
      /**
       * Handler for events coming from the native code
       * @hide
       */
      public NativeEventHandler mNativeEventHandler = null;
  
      // --------------------------------------------------------------------------
      // Constructor, Finalize
      // --------------------
      /**
       * Class constructor.
       *
       * @param type type of effect engine created. See {@link #EFFECT_TYPE_ENV_REVERB},
       *            {@link #EFFECT_TYPE_EQUALIZER} ... Types corresponding to
       *            built-in effects are defined by AudioEffect class. Other types
       *            can be specified provided they correspond an existing OpenSL
       *            ES interface ID and the corresponsing effect is available on
       *            the platform. If an unspecified effect type is requested, the
       *            constructor with throw the IllegalArgumentException. This
       *            parameter can be set to {@link #EFFECT_TYPE_NULL} in which
       *            case only the uuid will be used to select the effect.
       * @param uuid unique identifier of a particular effect implementation.
       *            Must be specified if the caller wants to use a particular
       *            implementation of an effect type. This parameter can be set to
       *            {@link #EFFECT_TYPE_NULL} in which case only the type will
       *            be used to select the effect.
       * @param priority the priority level requested by the application for
       *            controlling the effect engine. As the same effect engine can
       *            be shared by several applications, this parameter indicates
       *            how much the requesting application needs control of effect
       *            parameters. The normal priority is 0, above normal is a
       *            positive number, below normal a negative number.
       * @param audioSession system wide unique audio session identifier.
       *            The effect will be attached to the MediaPlayer or AudioTrack in
       *            the same audio session.
       *
       * @throws java.lang.IllegalArgumentException
       * @throws java.lang.UnsupportedOperationException
       * @throws java.lang.RuntimeException
       * @hide
       */
  
      @UnsupportedAppUsage
      public AudioEffect(UUID type, UUID uuid, int priority, int audioSession)
              throws IllegalArgumentException, UnsupportedOperationException,
              RuntimeException {
          int[] id = new int[1];
          Descriptor[] desc = new Descriptor[1];
          // native initialization
          int initResult = native_setup(new WeakReference<AudioEffect>(this),
                  type.toString(), uuid.toString(), priority, audioSession, id,
                  desc, ActivityThread.currentOpPackageName());
          if (initResult != SUCCESS && initResult != ALREADY_EXISTS) {
              Log.e(TAG, "Error code " + initResult
                      + " when initializing AudioEffect.");
              switch (initResult) {
              case ERROR_BAD_VALUE:
                  throw (new IllegalArgumentException("Effect type: " + type
                          + " not supported."));
              case ERROR_INVALID_OPERATION:
                  throw (new UnsupportedOperationException(
                          "Effect library not loaded"));
              default:
                  throw (new RuntimeException(
                          "Cannot initialize effect engine for type: " + type
                                  + " Error: " + initResult));
              }
          }
          mId = id[0];
          mDescriptor = desc[0];
          synchronized (mStateLock) {
              mState = STATE_INITIALIZED;
          }
      }
  
      /**
       * Releases the native AudioEffect resources. It is a good practice to
       * release the effect engine when not in use as control can be returned to
       * other applications or the native resources released.
       */
      public void release() {
          synchronized (mStateLock) {
              native_release();
              mState = STATE_UNINITIALIZED;
          }
      }
  
      @Override
      protected void finalize() {
          native_finalize();
      }
  
      /**
       * Get the effect descriptor.
       *
       * @see android.media.audiofx.AudioEffect.Descriptor
       * @throws IllegalStateException
       */
      public Descriptor getDescriptor() throws IllegalStateException {
          checkState("getDescriptor()");
          return mDescriptor;
      }
  
      // --------------------------------------------------------------------------
      // Effects Enumeration
      // --------------------
  
      /**
       * Query all effects available on the platform. Returns an array of
       * {@link android.media.audiofx.AudioEffect.Descriptor} objects
       *
       * @throws IllegalStateException
       */
  
      static public Descriptor[] queryEffects() {
          return (Descriptor[]) native_query_effects();
      }
  
      /**
       * Query all audio pre-processing effects applied to the AudioRecord with the supplied
       * audio session ID. Returns an array of {@link android.media.audiofx.AudioEffect.Descriptor}
       * objects.
       * @param audioSession system wide unique audio session identifier.
       * @throws IllegalStateException
       * @hide
       */
  
      static public Descriptor[] queryPreProcessings(int audioSession) {
          return (Descriptor[]) native_query_pre_processing(audioSession);
      }
  
      /**
       * Checks if the device implements the specified effect type.
       * @param type the requested effect type.
       * @return true if the device implements the specified effect type, false otherwise.
       * @hide
       */
      @TestApi
      public static boolean isEffectTypeAvailable(UUID type) {
          AudioEffect.Descriptor[] desc = AudioEffect.queryEffects();
          if (desc == null) {
              return false;
          }
  
          for (int i = 0; i < desc.length; i++) {
              if (desc[i].type.equals(type)) {
                  return true;
              }
          }
          return false;
      }
  
      // --------------------------------------------------------------------------
      // Control methods
      // --------------------
  
      /**
       * Enable or disable the effect.
       * Creating an audio effect does not automatically apply this effect on the audio source. It
       * creates the resources necessary to process this effect but the audio signal is still bypassed
       * through the effect engine. Calling this method will make that the effect is actually applied
       * or not to the audio content being played in the corresponding audio session.
       *
       * @param enabled the requested enable state
       * @return {@link #SUCCESS} in case of success, {@link #ERROR_INVALID_OPERATION}
       *         or {@link #ERROR_DEAD_OBJECT} in case of failure.
       * @throws IllegalStateException
       */
      public int setEnabled(boolean enabled) throws IllegalStateException {
          checkState("setEnabled()");
          return native_setEnabled(enabled);
      }
  
      /**
       * Set effect parameter. The setParameter method is provided in several
       * forms addressing most common parameter formats. This form is the most
       * generic one where the parameter and its value are both specified as an
       * array of bytes. The parameter and value type and length are therefore
       * totally free. For standard effect defined by OpenSL ES, the parameter
       * format and values must match the definitions in the corresponding OpenSL
       * ES interface.
       *
       * @param param the identifier of the parameter to set
       * @param value the new value for the specified parameter
       * @return {@link #SUCCESS} in case of success, {@link #ERROR_BAD_VALUE},
       *         {@link #ERROR_NO_MEMORY}, {@link #ERROR_INVALID_OPERATION} or
       *         {@link #ERROR_DEAD_OBJECT} in case of failure
       * @throws IllegalStateException
       * @hide
       */
      @TestApi
      public int setParameter(byte[] param, byte[] value)
              throws IllegalStateException {
          checkState("setParameter()");
          return native_setParameter(param.length, param, value.length, value);
      }
  
      /**
       * Set effect parameter. The parameter and its value are integers.
       *
       * @see #setParameter(byte[], byte[])
       * @hide
       */
      @TestApi
      public int setParameter(int param, int value) throws IllegalStateException {
          byte[] p = intToByteArray(param);
          byte[] v = intToByteArray(value);
          return setParameter(p, v);
      }
  
      /**
       * Set effect parameter. The parameter is an integer and the value is a
       * short integer.
       *
       * @see #setParameter(byte[], byte[])
       * @hide
       */
      @TestApi
      public int setParameter(int param, short value)
              throws IllegalStateException {
          byte[] p = intToByteArray(param);
          byte[] v = shortToByteArray(value);
          return setParameter(p, v);
      }
  
      /**
       * Set effect parameter. The parameter is an integer and the value is an
       * array of bytes.
       *
       * @see #setParameter(byte[], byte[])
       * @hide
       */
      @TestApi
      public int setParameter(int param, byte[] value)
              throws IllegalStateException {
          byte[] p = intToByteArray(param);
          return setParameter(p, value);
      }
  
      /**
       * Set effect parameter. The parameter is an array of 1 or 2 integers and
       * the value is also an array of 1 or 2 integers
       *
       * @see #setParameter(byte[], byte[])
       * @hide
       */
      @TestApi
      public int setParameter(int[] param, int[] value)
              throws IllegalStateException {
          if (param.length > 2 || value.length > 2) {
              return ERROR_BAD_VALUE;
          }
          byte[] p = intToByteArray(param[0]);
          if (param.length > 1) {
              byte[] p2 = intToByteArray(param[1]);
              p = concatArrays(p, p2);
          }
          byte[] v = intToByteArray(value[0]);
          if (value.length > 1) {
              byte[] v2 = intToByteArray(value[1]);
              v = concatArrays(v, v2);
          }
          return setParameter(p, v);
      }
  
      /**
       * Set effect parameter. The parameter is an array of 1 or 2 integers and
       * the value is an array of 1 or 2 short integers
       *
       * @see #setParameter(byte[], byte[])
       * @hide
       */
      @UnsupportedAppUsage
      public int setParameter(int[] param, short[] value)
              throws IllegalStateException {
          if (param.length > 2 || value.length > 2) {
              return ERROR_BAD_VALUE;
          }
          byte[] p = intToByteArray(param[0]);
          if (param.length > 1) {
              byte[] p2 = intToByteArray(param[1]);
              p = concatArrays(p, p2);
          }
  
          byte[] v = shortToByteArray(value[0]);
          if (value.length > 1) {
              byte[] v2 = shortToByteArray(value[1]);
              v = concatArrays(v, v2);
          }
          return setParameter(p, v);
      }
  
      /**
       * Set effect parameter. The parameter is an array of 1 or 2 integers and
       * the value is an array of bytes
       *
       * @see #setParameter(byte[], byte[])
       * @hide
       */
      @TestApi
      public int setParameter(int[] param, byte[] value)
              throws IllegalStateException {
          if (param.length > 2) {
              return ERROR_BAD_VALUE;
          }
          byte[] p = intToByteArray(param[0]);
          if (param.length > 1) {
              byte[] p2 = intToByteArray(param[1]);
              p = concatArrays(p, p2);
          }
          return setParameter(p, value);
      }
  
      /**
       * Get effect parameter. The getParameter method is provided in several
       * forms addressing most common parameter formats. This form is the most
       * generic one where the parameter and its value are both specified as an
       * array of bytes. The parameter and value type and length are therefore
       * totally free.
       *
       * @param param the identifier of the parameter to set
       * @param value the new value for the specified parameter
       * @return the number of meaningful bytes in value array in case of success or
       *  {@link #ERROR_BAD_VALUE}, {@link #ERROR_NO_MEMORY}, {@link #ERROR_INVALID_OPERATION}
       *  or {@link #ERROR_DEAD_OBJECT} in case of failure.
       * @throws IllegalStateException
       * @hide
       */
      @TestApi
      public int getParameter(byte[] param, byte[] value)
              throws IllegalStateException {
          checkState("getParameter()");
          return native_getParameter(param.length, param, value.length, value);
      }
  
      /**
       * Get effect parameter. The parameter is an integer and the value is an
       * array of bytes.
       *
       * @see #getParameter(byte[], byte[])
       * @hide
       */
      @TestApi
      public int getParameter(int param, byte[] value)
              throws IllegalStateException {
          byte[] p = intToByteArray(param);
  
          return getParameter(p, value);
      }
  
      /**
       * Get effect parameter. The parameter is an integer and the value is an
       * array of 1 or 2 integers
       *
       * @see #getParameter(byte[], byte[])
       * In case of success, returns the number of meaningful integers in value array.
       * @hide
       */
      @TestApi
      public int getParameter(int param, int[] value)
              throws IllegalStateException {
          if (value.length > 2) {
              return ERROR_BAD_VALUE;
          }
          byte[] p = intToByteArray(param);
  
          byte[] v = new byte[value.length * 4];
  
          int status = getParameter(p, v);
  
          if (status == 4 || status == 8) {
              value[0] = byteArrayToInt(v);
              if (status == 8) {
                  value[1] = byteArrayToInt(v, 4);
              }
              status /= 4;
          } else {
              status = ERROR;
          }
          return status;
      }
  
      /**
       * Get effect parameter. The parameter is an integer and the value is an
       * array of 1 or 2 short integers
       *
       * @see #getParameter(byte[], byte[])
       * In case of success, returns the number of meaningful short integers in value array.
       * @hide
       */
      @TestApi
      public int getParameter(int param, short[] value)
              throws IllegalStateException {
          if (value.length > 2) {
              return ERROR_BAD_VALUE;
          }
          byte[] p = intToByteArray(param);
  
          byte[] v = new byte[value.length * 2];
  
          int status = getParameter(p, v);
  
          if (status == 2 || status == 4) {
              value[0] = byteArrayToShort(v);
              if (status == 4) {
                  value[1] = byteArrayToShort(v, 2);
              }
              status /= 2;
          } else {
              status = ERROR;
          }
          return status;
      }
  
      /**
       * Get effect parameter. The parameter is an array of 1 or 2 integers and
       * the value is also an array of 1 or 2 integers
       *
       * @see #getParameter(byte[], byte[])
       * In case of success, the returns the number of meaningful integers in value array.
       * @hide
       */
      @UnsupportedAppUsage
      public int getParameter(int[] param, int[] value)
              throws IllegalStateException {
          if (param.length > 2 || value.length > 2) {
              return ERROR_BAD_VALUE;
          }
          byte[] p = intToByteArray(param[0]);
          if (param.length > 1) {
              byte[] p2 = intToByteArray(param[1]);
              p = concatArrays(p, p2);
          }
          byte[] v = new byte[value.length * 4];
  
          int status = getParameter(p, v);
  
          if (status == 4 || status == 8) {
              value[0] = byteArrayToInt(v);
              if (status == 8) {
                  value[1] = byteArrayToInt(v, 4);
              }
              status /= 4;
          } else {
              status = ERROR;
          }
          return status;
      }
  
      /**
       * Get effect parameter. The parameter is an array of 1 or 2 integers and
       * the value is an array of 1 or 2 short integers
       *
       * @see #getParameter(byte[], byte[])
       * In case of success, returns the number of meaningful short integers in value array.
       * @hide
       */
      @TestApi
      public int getParameter(int[] param, short[] value)
              throws IllegalStateException {
          if (param.length > 2 || value.length > 2) {
              return ERROR_BAD_VALUE;
          }
          byte[] p = intToByteArray(param[0]);
          if (param.length > 1) {
              byte[] p2 = intToByteArray(param[1]);
              p = concatArrays(p, p2);
          }
          byte[] v = new byte[value.length * 2];
  
          int status = getParameter(p, v);
  
          if (status == 2 || status == 4) {
              value[0] = byteArrayToShort(v);
              if (status == 4) {
                  value[1] = byteArrayToShort(v, 2);
              }
              status /= 2;
          } else {
              status = ERROR;
          }
          return status;
      }
  
      /**
       * Get effect parameter. The parameter is an array of 1 or 2 integers and
       * the value is an array of bytes
       *
       * @see #getParameter(byte[], byte[])
       * @hide
       */
      @UnsupportedAppUsage
      public int getParameter(int[] param, byte[] value)
              throws IllegalStateException {
          if (param.length > 2) {
              return ERROR_BAD_VALUE;
          }
          byte[] p = intToByteArray(param[0]);
          if (param.length > 1) {
              byte[] p2 = intToByteArray(param[1]);
              p = concatArrays(p, p2);
          }
  
          return getParameter(p, value);
      }
  
      /**
       * Send a command to the effect engine. This method is intended to send
       * proprietary commands to a particular effect implementation.
       * In case of success, returns the number of meaningful bytes in reply array.
       * In case of failure, the returned value is negative and implementation specific.
       * @hide
       */
      @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
      public int command(int cmdCode, byte[] command, byte[] reply)
              throws IllegalStateException {
          checkState("command()");
          return native_command(cmdCode, command.length, command, reply.length, reply);
      }
  
      // --------------------------------------------------------------------------
      // Getters
      // --------------------
  
      /**
       * Returns effect unique identifier. This system wide unique identifier can
       * be used to attach this effect to a MediaPlayer or an AudioTrack when the
       * effect is an auxiliary effect (Reverb)
       *
       * @return the effect identifier.
       * @throws IllegalStateException
       */
      public int getId() throws IllegalStateException {
          checkState("getId()");
          return mId;
      }
  
      /**
       * Returns effect enabled state
       *
       * @return true if the effect is enabled, false otherwise.
       * @throws IllegalStateException
       */
      public boolean getEnabled() throws IllegalStateException {
          checkState("getEnabled()");
          return native_getEnabled();
      }
  
      /**
       * Checks if this AudioEffect object is controlling the effect engine.
       *
       * @return true if this instance has control of effect engine, false
       *         otherwise.
       * @throws IllegalStateException
       */
      public boolean hasControl() throws IllegalStateException {
          checkState("hasControl()");
          return native_hasControl();
      }
  
      // --------------------------------------------------------------------------
      // Initialization / configuration
      // --------------------
      /**
       * Sets the listener AudioEffect notifies when the effect engine is enabled
       * or disabled.
       *
       * @param listener
       */
      public void setEnableStatusListener(OnEnableStatusChangeListener listener) {
          synchronized (mListenerLock) {
              mEnableStatusChangeListener = listener;
          }
          if ((listener != null) && (mNativeEventHandler == null)) {
              createNativeEventHandler();
          }
      }
  
      /**
       * Sets the listener AudioEffect notifies when the effect engine control is
       * taken or returned.
       *
       * @param listener
       */
      public void setControlStatusListener(OnControlStatusChangeListener listener) {
          synchronized (mListenerLock) {
              mControlChangeStatusListener = listener;
          }
          if ((listener != null) && (mNativeEventHandler == null)) {
              createNativeEventHandler();
          }
      }
  
      /**
       * Sets the listener AudioEffect notifies when a parameter is changed.
       *
       * @param listener
       * @hide
       */
      @TestApi
      public void setParameterListener(OnParameterChangeListener listener) {
          synchronized (mListenerLock) {
              mParameterChangeListener = listener;
          }
          if ((listener != null) && (mNativeEventHandler == null)) {
              createNativeEventHandler();
          }
      }
  
      // Convenience method for the creation of the native event handler
      // It is called only when a non-null event listener is set.
      // precondition:
      // mNativeEventHandler is null
      private void createNativeEventHandler() {
          Looper looper;
          if ((looper = Looper.myLooper()) != null) {
              mNativeEventHandler = new NativeEventHandler(this, looper);
          } else if ((looper = Looper.getMainLooper()) != null) {
              mNativeEventHandler = new NativeEventHandler(this, looper);
          } else {
              mNativeEventHandler = null;
          }
      }
  
      // ---------------------------------------------------------
      // Interface definitions
      // --------------------
      /**
       * The OnEnableStatusChangeListener interface defines a method called by the AudioEffect
       * when the enabled state of the effect engine was changed by the controlling application.
       */
      public interface OnEnableStatusChangeListener {
          /**
           * Called on the listener to notify it that the effect engine has been
           * enabled or disabled.
           * @param effect the effect on which the interface is registered.
           * @param enabled new effect state.
           */
          void onEnableStatusChange(AudioEffect effect, boolean enabled);
      }
  
      /**
       * The OnControlStatusChangeListener interface defines a method called by the AudioEffect
       * when control of the effect engine is gained or lost by the application
       */
      public interface OnControlStatusChangeListener {
          /**
           * Called on the listener to notify it that the effect engine control
           * has been taken or returned.
           * @param effect the effect on which the interface is registered.
           * @param controlGranted true if the application has been granted control of the effect
           * engine, false otherwise.
           */
          void onControlStatusChange(AudioEffect effect, boolean controlGranted);
      }
  
      /**
       * The OnParameterChangeListener interface defines a method called by the AudioEffect
       * when a parameter is changed in the effect engine by the controlling application.
       * @hide
       */
      @TestApi
      public interface OnParameterChangeListener {
          /**
           * Called on the listener to notify it that a parameter value has changed.
           * @param effect the effect on which the interface is registered.
           * @param status status of the set parameter operation.
           * @param param ID of the modified parameter.
           * @param value the new parameter value.
           */
          void onParameterChange(AudioEffect effect, int status, byte[] param,
                  byte[] value);
      }
  
  
      // -------------------------------------------------------------------------
      // Audio Effect Control panel intents
      // -------------------------------------------------------------------------
  
      /**
       *  Intent to launch an audio effect control panel UI.
       *  <p>The goal of this intent is to enable separate implementations of music/media player
       *  applications and audio effect control application or services.
       *  This will allow platform vendors to offer more advanced control options for standard effects
       *  or control for platform specific effects.
       *  <p>The intent carries a number of extras used by the player application to communicate
       *  necessary pieces of information to the control panel application.
       *  <p>The calling application must use the
       *  {@link android.app.Activity#startActivityForResult(Intent, int)} method to launch the
       *  control panel so that its package name is indicated and used by the control panel
       *  application to keep track of changes for this particular application.
       *  <p>The {@link #EXTRA_AUDIO_SESSION} extra will indicate an audio session to which the
       *  audio effects should be applied. If no audio session is specified, either one of the
       *  follownig will happen:
       *  <p>- If an audio session was previously opened by the calling application with
       *  {@link #ACTION_OPEN_AUDIO_EFFECT_CONTROL_SESSION} intent, the effect changes will
       *  be applied to that session.
       *  <p>- If no audio session is opened, the changes will be stored in the package specific
       *  storage area and applied whenever a new audio session is opened by this application.
       *  <p>The {@link #EXTRA_CONTENT_TYPE} extra will help the control panel application
       *  customize both the UI layout and the default audio effect settings if none are already
       *  stored for the calling application.
       */
      @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
      public static final String ACTION_DISPLAY_AUDIO_EFFECT_CONTROL_PANEL =
          "android.media.action.DISPLAY_AUDIO_EFFECT_CONTROL_PANEL";
  
      /**
       *  Intent to signal to the effect control application or service that a new audio session
       *  is opened and requires audio effects to be applied.
       *  <p>This is different from {@link #ACTION_DISPLAY_AUDIO_EFFECT_CONTROL_PANEL} in that no
       *  UI should be displayed in this case. Music player applications can broadcast this intent
       *  before starting playback to make sure that any audio effect settings previously selected
       *  by the user are applied.
       *  <p>The effect control application receiving this intent will look for previously stored
       *  settings for the calling application, create all required audio effects and apply the
       *  effect settings to the specified audio session.
       *  <p>The calling package name is indicated by the {@link #EXTRA_PACKAGE_NAME} extra and the
       *  audio session ID by the {@link #EXTRA_AUDIO_SESSION} extra. Both extras are mandatory.
       *  <p>If no stored settings are found for the calling application, default settings for the
       *  content type indicated by {@link #EXTRA_CONTENT_TYPE} will be applied. The default settings
       *  for a given content type are platform specific.
       */
      @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
      public static final String ACTION_OPEN_AUDIO_EFFECT_CONTROL_SESSION =
          "android.media.action.OPEN_AUDIO_EFFECT_CONTROL_SESSION";
  
      /**
       *  Intent to signal to the effect control application or service that an audio session
       *  is closed and that effects should not be applied anymore.
       *  <p>The effect control application receiving this intent will delete all effects on
       *  this session and store current settings in package specific storage.
       *  <p>The calling package name is indicated by the {@link #EXTRA_PACKAGE_NAME} extra and the
       *  audio session ID by the {@link #EXTRA_AUDIO_SESSION} extra. Both extras are mandatory.
       *  <p>It is good practice for applications to broadcast this intent when music playback stops
       *  and/or when exiting to free system resources consumed by audio effect engines.
       */
      @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
      public static final String ACTION_CLOSE_AUDIO_EFFECT_CONTROL_SESSION =
          "android.media.action.CLOSE_AUDIO_EFFECT_CONTROL_SESSION";
  
      /**
       * Contains the ID of the audio session the effects should be applied to.
       * <p>This extra is for use with {@link #ACTION_DISPLAY_AUDIO_EFFECT_CONTROL_PANEL},
       * {@link #ACTION_OPEN_AUDIO_EFFECT_CONTROL_SESSION} and
       * {@link #ACTION_CLOSE_AUDIO_EFFECT_CONTROL_SESSION} intents.
       * <p>The extra value is of type int and is the audio session ID.
       *  @see android.media.MediaPlayer#getAudioSessionId() for details on audio sessions.
       */
       public static final String EXTRA_AUDIO_SESSION = "android.media.extra.AUDIO_SESSION";
  
      /**
       * Contains the package name of the calling application.
       * <p>This extra is for use with {@link #ACTION_OPEN_AUDIO_EFFECT_CONTROL_SESSION} and
       * {@link #ACTION_CLOSE_AUDIO_EFFECT_CONTROL_SESSION} intents.
       * <p>The extra value is a string containing the full package name.
       */
      public static final String EXTRA_PACKAGE_NAME = "android.media.extra.PACKAGE_NAME";
  
      /**
       * Indicates which type of content is played by the application.
       * <p>This extra is for use with {@link #ACTION_DISPLAY_AUDIO_EFFECT_CONTROL_PANEL} and
       * {@link #ACTION_OPEN_AUDIO_EFFECT_CONTROL_SESSION} intents.
       * <p>This information is used by the effect control application to customize UI and select
       * appropriate default effect settings. The content type is one of the following:
       * <ul>
       *   <li>{@link #CONTENT_TYPE_MUSIC}</li>
       *   <li>{@link #CONTENT_TYPE_MOVIE}</li>
       *   <li>{@link #CONTENT_TYPE_GAME}</li>
       *   <li>{@link #CONTENT_TYPE_VOICE}</li>
       * </ul>
       * If omitted, the content type defaults to {@link #CONTENT_TYPE_MUSIC}.
       */
      public static final String EXTRA_CONTENT_TYPE = "android.media.extra.CONTENT_TYPE";
  
      /**
       * Value for {@link #EXTRA_CONTENT_TYPE} when the type of content played is music
       */
      public static final int  CONTENT_TYPE_MUSIC = 0;
      /**
       * Value for {@link #EXTRA_CONTENT_TYPE} when the type of content played is video or movie
       */
      public static final int  CONTENT_TYPE_MOVIE = 1;
      /**
       * Value for {@link #EXTRA_CONTENT_TYPE} when the type of content played is game audio
       */
      public static final int  CONTENT_TYPE_GAME = 2;
      /**
       * Value for {@link #EXTRA_CONTENT_TYPE} when the type of content played is voice audio
       */
      public static final int  CONTENT_TYPE_VOICE = 3;
  
  
      // ---------------------------------------------------------
      // Inner classes
      // --------------------
      /**
       * Helper class to handle the forwarding of native events to the appropriate
       * listeners
       */
      private class NativeEventHandler extends Handler {
          private AudioEffect mAudioEffect;
  
          public NativeEventHandler(AudioEffect ae, Looper looper) {
              super(looper);
              mAudioEffect = ae;
          }
  
          @Override
          public void handleMessage(Message msg) {
              if (mAudioEffect == null) {
                  return;
              }
              switch (msg.what) {
              case NATIVE_EVENT_ENABLED_STATUS:
                  OnEnableStatusChangeListener enableStatusChangeListener = null;
                  synchronized (mListenerLock) {
                      enableStatusChangeListener = mAudioEffect.mEnableStatusChangeListener;
                  }
                  if (enableStatusChangeListener != null) {
                      enableStatusChangeListener.onEnableStatusChange(
                              mAudioEffect, (boolean) (msg.arg1 != 0));
                  }
                  break;
              case NATIVE_EVENT_CONTROL_STATUS:
                  OnControlStatusChangeListener controlStatusChangeListener = null;
                  synchronized (mListenerLock) {
                      controlStatusChangeListener = mAudioEffect.mControlChangeStatusListener;
                  }
                  if (controlStatusChangeListener != null) {
                      controlStatusChangeListener.onControlStatusChange(
                              mAudioEffect, (boolean) (msg.arg1 != 0));
                  }
                  break;
              case NATIVE_EVENT_PARAMETER_CHANGED:
                  OnParameterChangeListener parameterChangeListener = null;
                  synchronized (mListenerLock) {
                      parameterChangeListener = mAudioEffect.mParameterChangeListener;
                  }
                  if (parameterChangeListener != null) {
                      // arg1 contains offset of parameter value from start of
                      // byte array
                      int vOffset = msg.arg1;
                      byte[] p = (byte[]) msg.obj;
                      // See effect_param_t in EffectApi.h for psize and vsize
                      // fields offsets
                      int status = byteArrayToInt(p, 0);
                      int psize = byteArrayToInt(p, 4);
                      int vsize = byteArrayToInt(p, 8);
                      byte[] param = new byte[psize];
                      byte[] value = new byte[vsize];
                      System.arraycopy(p, 12, param, 0, psize);
                      System.arraycopy(p, vOffset, value, 0, vsize);
  
                      parameterChangeListener.onParameterChange(mAudioEffect,
                              status, param, value);
                  }
                  break;
  
              default:
                  Log.e(TAG, "handleMessage() Unknown event type: " + msg.what);
                  break;
              }
          }
      }
  
      // ---------------------------------------------------------
      // Java methods called from the native side
      // --------------------
      @SuppressWarnings("unused")
      private static void postEventFromNative(Object effect_ref, int what,
              int arg1, int arg2, Object obj) {
          AudioEffect effect = (AudioEffect) ((WeakReference) effect_ref).get();
          if (effect == null) {
              return;
          }
          if (effect.mNativeEventHandler != null) {
              Message m = effect.mNativeEventHandler.obtainMessage(what, arg1,
                      arg2, obj);
              effect.mNativeEventHandler.sendMessage(m);
          }
  
      }
  
      // ---------------------------------------------------------
      // Native methods called from the Java side
      // --------------------
  
      private static native final void native_init();
  
      private native final int native_setup(Object audioeffect_this, String type,
              String uuid, int priority, int audioSession, int[] id, Object[] desc,
              String opPackageName);
  
      private native final void native_finalize();
  
      private native final void native_release();
  
      private native final int native_setEnabled(boolean enabled);
  
      private native final boolean native_getEnabled();
  
      private native final boolean native_hasControl();
  
      private native final int native_setParameter(int psize, byte[] param,
              int vsize, byte[] value);
  
      private native final int native_getParameter(int psize, byte[] param,
              int vsize, byte[] value);
  
      private native final int native_command(int cmdCode, int cmdSize,
              byte[] cmdData, int repSize, byte[] repData);
  
      private static native Object[] native_query_effects();
  
      private static native Object[] native_query_pre_processing(int audioSession);
  
      // ---------------------------------------------------------
      // Utility methods
      // ------------------
  
      /**
      * @hide
      */
      @UnsupportedAppUsage
      public void checkState(String methodName) throws IllegalStateException {
          synchronized (mStateLock) {
              if (mState != STATE_INITIALIZED) {
                  throw (new IllegalStateException(methodName
                          + " called on uninitialized AudioEffect."));
              }
          }
      }
  
      /**
       * @hide
       */
      public void checkStatus(int status) {
          if (isError(status)) {
              switch (status) {
              case AudioEffect.ERROR_BAD_VALUE:
                  throw (new IllegalArgumentException(
                          "AudioEffect: bad parameter value"));
              case AudioEffect.ERROR_INVALID_OPERATION:
                  throw (new UnsupportedOperationException(
                          "AudioEffect: invalid parameter operation"));
              default:
                  throw (new RuntimeException("AudioEffect: set/get parameter error"));
              }
          }
      }
  
      /**
       * @hide
       */
      @TestApi
      public static boolean isError(int status) {
          return (status < 0);
      }
  
      /**
       * @hide
       */
      @TestApi
      public static int byteArrayToInt(byte[] valueBuf) {
          return byteArrayToInt(valueBuf, 0);
  
      }
  
      /**
       * @hide
       */
      public static int byteArrayToInt(byte[] valueBuf, int offset) {
          ByteBuffer converter = ByteBuffer.wrap(valueBuf);
          converter.order(ByteOrder.nativeOrder());
          return converter.getInt(offset);
  
      }
  
      /**
       * @hide
       */
      @TestApi
      public static byte[] intToByteArray(int value) {
          ByteBuffer converter = ByteBuffer.allocate(4);
          converter.order(ByteOrder.nativeOrder());
          converter.putInt(value);
          return converter.array();
      }
  
      /**
       * @hide
       */
      @TestApi
      public static short byteArrayToShort(byte[] valueBuf) {
          return byteArrayToShort(valueBuf, 0);
      }
  
      /**
       * @hide
       */
      public static short byteArrayToShort(byte[] valueBuf, int offset) {
          ByteBuffer converter = ByteBuffer.wrap(valueBuf);
          converter.order(ByteOrder.nativeOrder());
          return converter.getShort(offset);
  
      }
  
      /**
       * @hide
       */
      @TestApi
      public static byte[] shortToByteArray(short value) {
          ByteBuffer converter = ByteBuffer.allocate(2);
          converter.order(ByteOrder.nativeOrder());
          short sValue = (short) value;
          converter.putShort(sValue);
          return converter.array();
      }
  
      /**
       * @hide
       */
      public static float byteArrayToFloat(byte[] valueBuf) {
          return byteArrayToFloat(valueBuf, 0);
  
      }
  
      /**
       * @hide
       */
      public static float byteArrayToFloat(byte[] valueBuf, int offset) {
          ByteBuffer converter = ByteBuffer.wrap(valueBuf);
          converter.order(ByteOrder.nativeOrder());
          return converter.getFloat(offset);
  
      }
  
      /**
       * @hide
       */
      public static byte[] floatToByteArray(float value) {
          ByteBuffer converter = ByteBuffer.allocate(4);
          converter.order(ByteOrder.nativeOrder());
          converter.putFloat(value);
          return converter.array();
      }
  
      /**
       * @hide
       */
      public static byte[] concatArrays(byte[]... arrays) {
          int len = 0;
          for (byte[] a : arrays) {
              len += a.length;
          }
          byte[] b = new byte[len];
  
          int offs = 0;
          for (byte[] a : arrays) {
              System.arraycopy(a, 0, b, offs, a.length);
              offs += a.length;
          }
          return b;
      }
  }
  