content: /*
   * Copyright (C) 2016 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;
  
  import static android.media.AudioAttributes.ALLOW_CAPTURE_BY_ALL;
  import static android.media.AudioAttributes.ALLOW_CAPTURE_BY_NONE;
  
  import android.annotation.IntDef;
  import android.annotation.NonNull;
  import android.annotation.SystemApi;
  import android.os.Binder;
  import android.os.IBinder;
  import android.os.Parcel;
  import android.os.Parcelable;
  import android.os.RemoteException;
  import android.util.Log;
  
  import java.io.PrintWriter;
  import java.lang.annotation.Retention;
  import java.lang.annotation.RetentionPolicy;
  import java.util.Objects;
  
  /**
   * The AudioPlaybackConfiguration class collects the information describing an audio playback
   * session.
   */
  public final class AudioPlaybackConfiguration implements Parcelable {
      private static final String TAG = new String("AudioPlaybackConfiguration");
  
      private static final boolean DEBUG = false;
  
      /** @hide */
      public static final int PLAYER_PIID_INVALID = -1;
      /** @hide */
      public static final int PLAYER_UPID_INVALID = -1;
  
      // information about the implementation
      /**
       * @hide
       * An unknown type of player
       */
      @SystemApi
      public static final int PLAYER_TYPE_UNKNOWN = -1;
      /**
       * @hide
       * Player backed by a java android.media.AudioTrack player
       */
      @SystemApi
      public static final int PLAYER_TYPE_JAM_AUDIOTRACK = 1;
      /**
       * @hide
       * Player backed by a java android.media.MediaPlayer player
       */
      @SystemApi
      public static final int PLAYER_TYPE_JAM_MEDIAPLAYER = 2;
      /**
       * @hide
       * Player backed by a java android.media.SoundPool player
       */
      @SystemApi
      public static final int PLAYER_TYPE_JAM_SOUNDPOOL = 3;
      /**
       * @hide
       * Player backed by a C OpenSL ES AudioPlayer player with a BufferQueue source
       */
      @SystemApi
      public static final int PLAYER_TYPE_SLES_AUDIOPLAYER_BUFFERQUEUE = 11;
      /**
       * @hide
       * Player backed by a C OpenSL ES AudioPlayer player with a URI or FD source
       */
      @SystemApi
      public static final int PLAYER_TYPE_SLES_AUDIOPLAYER_URI_FD = 12;
  
      /**
       * @hide
       * Player backed an AAudio player.
       * Note this type is not in System API so it will not be returned in public API calls
       */
      // TODO unhide for SystemApi, update getPlayerType()
      public static final int PLAYER_TYPE_AAUDIO = 13;
  
      /**
       * @hide
       * Player backed a hardware source, whose state is visible in the Android audio policy manager.
       * Note this type is not in System API so it will not be returned in public API calls
       */
      // TODO unhide for SystemApi, update getPlayerType()
      public static final int PLAYER_TYPE_HW_SOURCE = 14;
  
      /**
       * @hide
       * Player is a proxy for an audio player whose audio and state doesn't go through the Android
       * audio framework.
       * Note this type is not in System API so it will not be returned in public API calls
       */
      // TODO unhide for SystemApi, update getPlayerType()
      public static final int PLAYER_TYPE_EXTERNAL_PROXY = 15;
  
      /** @hide */
      @IntDef({
          PLAYER_TYPE_UNKNOWN,
          PLAYER_TYPE_JAM_AUDIOTRACK,
          PLAYER_TYPE_JAM_MEDIAPLAYER,
          PLAYER_TYPE_JAM_SOUNDPOOL,
          PLAYER_TYPE_SLES_AUDIOPLAYER_BUFFERQUEUE,
          PLAYER_TYPE_SLES_AUDIOPLAYER_URI_FD,
      })
      @Retention(RetentionPolicy.SOURCE)
      public @interface PlayerType {}
  
      /**
       * @hide
       * An unknown player state
       */
      @SystemApi
      public static final int PLAYER_STATE_UNKNOWN = -1;
      /**
       * @hide
       * The resources of the player have been released, it cannot play anymore
       */
      @SystemApi
      public static final int PLAYER_STATE_RELEASED = 0;
      /**
       * @hide
       * The state of a player when it's created
       */
      @SystemApi
      public static final int PLAYER_STATE_IDLE = 1;
      /**
       * @hide
       * The state of a player that is actively playing
       */
      @SystemApi
      public static final int PLAYER_STATE_STARTED = 2;
      /**
       * @hide
       * The state of a player where playback is paused
       */
      @SystemApi
      public static final int PLAYER_STATE_PAUSED = 3;
      /**
       * @hide
       * The state of a player where playback is stopped
       */
      @SystemApi
      public static final int PLAYER_STATE_STOPPED = 4;
  
      /** @hide */
      @IntDef({
          PLAYER_STATE_UNKNOWN,
          PLAYER_STATE_RELEASED,
          PLAYER_STATE_IDLE,
          PLAYER_STATE_STARTED,
          PLAYER_STATE_PAUSED,
          PLAYER_STATE_STOPPED
      })
      @Retention(RetentionPolicy.SOURCE)
      public @interface PlayerState {}
  
      // immutable data
      private final int mPlayerIId;
  
      // not final due to anonymization step
      private int mPlayerType;
      private int mClientUid;
      private int mClientPid;
      // the IPlayer reference and death monitor
      private IPlayerShell mIPlayerShell;
  
      private int mPlayerState;
      private AudioAttributes mPlayerAttr; // never null
  
      /**
       * Never use without initializing parameters afterwards
       */
      private AudioPlaybackConfiguration(int piid) {
          mPlayerIId = piid;
          mIPlayerShell = null;
      }
  
      /**
       * @hide
       */
      public AudioPlaybackConfiguration(PlayerBase.PlayerIdCard pic, int piid, int uid, int pid) {
          if (DEBUG) { Log.d(TAG, "new: piid=" + piid + " iplayer=" + pic.mIPlayer); }
          mPlayerIId = piid;
          mPlayerType = pic.mPlayerType;
          mClientUid = uid;
          mClientPid = pid;
          mPlayerState = PLAYER_STATE_IDLE;
          mPlayerAttr = pic.mAttributes;
          if ((sPlayerDeathMonitor != null) && (pic.mIPlayer != null)) {
              mIPlayerShell = new IPlayerShell(this, pic.mIPlayer);
          } else {
              mIPlayerShell = null;
          }
      }
  
      /**
       * @hide
       */
      public void init() {
          synchronized (this) {
              if (mIPlayerShell != null) {
                  mIPlayerShell.monitorDeath();
              }
          }
      }
  
      // Note that this method is called server side, so no "privileged" information is ever sent
      // to a client that is not supposed to have access to it.
      /**
       * @hide
       * Creates a copy of the playback configuration that is stripped of any data enabling
       * identification of which application it is associated with ("anonymized").
       * @param toSanitize
       */
      public static AudioPlaybackConfiguration anonymizedCopy(AudioPlaybackConfiguration in) {
          final AudioPlaybackConfiguration anonymCopy = new AudioPlaybackConfiguration(in.mPlayerIId);
          anonymCopy.mPlayerState = in.mPlayerState;
          // do not reuse the full attributes: only usage, content type and public flags are allowed
          anonymCopy.mPlayerAttr = new AudioAttributes.Builder()
                  .setUsage(in.mPlayerAttr.getUsage())
                  .setContentType(in.mPlayerAttr.getContentType())
                  .setFlags(in.mPlayerAttr.getFlags())
                  .setAllowedCapturePolicy(
                          in.mPlayerAttr.getAllowedCapturePolicy() == ALLOW_CAPTURE_BY_ALL
                          ? ALLOW_CAPTURE_BY_ALL : ALLOW_CAPTURE_BY_NONE)
                  .build();
          // anonymized data
          anonymCopy.mPlayerType = PLAYER_TYPE_UNKNOWN;
          anonymCopy.mClientUid = PLAYER_UPID_INVALID;
          anonymCopy.mClientPid = PLAYER_UPID_INVALID;
          anonymCopy.mIPlayerShell = null;
          return anonymCopy;
      }
  
      /**
       * Return the {@link AudioAttributes} of the corresponding player.
       * @return the audio attributes of the player
       */
      public AudioAttributes getAudioAttributes() {
          return mPlayerAttr;
      }
  
      /**
       * @hide
       * Return the uid of the client application that created this player.
       * @return the uid of the client
       */
      @SystemApi
      public int getClientUid() {
          return mClientUid;
      }
  
      /**
       * @hide
       * Return the pid of the client application that created this player.
       * @return the pid of the client
       */
      @SystemApi
      public int getClientPid() {
          return mClientPid;
      }
  
      /**
       * @hide
       * Return the type of player linked to this configuration. The return value is one of
       * {@link #PLAYER_TYPE_JAM_AUDIOTRACK}, {@link #PLAYER_TYPE_JAM_MEDIAPLAYER},
       * {@link #PLAYER_TYPE_JAM_SOUNDPOOL}, {@link #PLAYER_TYPE_SLES_AUDIOPLAYER_BUFFERQUEUE},
       * {@link #PLAYER_TYPE_SLES_AUDIOPLAYER_URI_FD}, or {@link #PLAYER_TYPE_UNKNOWN}.
       * <br>Note that player types not exposed in the system API will be represented as
       * {@link #PLAYER_TYPE_UNKNOWN}.
       * @return the type of the player.
       */
      @SystemApi
      public @PlayerType int getPlayerType() {
          switch (mPlayerType) {
              case PLAYER_TYPE_AAUDIO:
              case PLAYER_TYPE_HW_SOURCE:
              case PLAYER_TYPE_EXTERNAL_PROXY:
                  return PLAYER_TYPE_UNKNOWN;
              default:
                  return mPlayerType;
          }
      }
  
      /**
       * @hide
       * Return the current state of the player linked to this configuration. The return value is one
       * of {@link #PLAYER_STATE_IDLE}, {@link #PLAYER_STATE_PAUSED}, {@link #PLAYER_STATE_STARTED},
       * {@link #PLAYER_STATE_STOPPED}, {@link #PLAYER_STATE_RELEASED} or
       * {@link #PLAYER_STATE_UNKNOWN}.
       * @return the state of the player.
       */
      @SystemApi
      public @PlayerState int getPlayerState() {
          return mPlayerState;
      }
  
      /**
       * @hide
       * Return an identifier unique for the lifetime of the player.
       * @return a player interface identifier
       */
      @SystemApi
      public int getPlayerInterfaceId() {
          return mPlayerIId;
      }
  
      /**
       * @hide
       * Return a proxy for the player associated with this playback configuration
       * @return a proxy player
       */
      @SystemApi
      public PlayerProxy getPlayerProxy() {
          final IPlayerShell ips;
          synchronized (this) {
              ips = mIPlayerShell;
          }
          return ips == null ? null : new PlayerProxy(this);
      }
  
      /**
       * @hide
       * @return the IPlayer interface for the associated player
       */
      IPlayer getIPlayer() {
          final IPlayerShell ips;
          synchronized (this) {
              ips = mIPlayerShell;
          }
          return ips == null ? null : ips.getIPlayer();
      }
  
      /**
       * @hide
       * Handle a change of audio attributes
       * @param attr
       */
      public boolean handleAudioAttributesEvent(@NonNull AudioAttributes attr) {
          final boolean changed = !attr.equals(mPlayerAttr);
          mPlayerAttr = attr;
          return changed;
      }
  
      /**
       * @hide
       * Handle a player state change
       * @param event
       * @return true if the state changed, false otherwise
       */
      public boolean handleStateEvent(int event) {
          final boolean changed;
          synchronized (this) {
              changed = (mPlayerState != event);
              mPlayerState = event;
              if (changed && (event == PLAYER_STATE_RELEASED) && (mIPlayerShell != null)) {
                  mIPlayerShell.release();
                  mIPlayerShell = null;
              }
          }
          return changed;
      }
  
      // To report IPlayer death from death recipient
      /** @hide */
      public interface PlayerDeathMonitor {
          public void playerDeath(int piid);
      }
      /** @hide */
      public static PlayerDeathMonitor sPlayerDeathMonitor;
  
      private void playerDied() {
          if (sPlayerDeathMonitor != null) {
              sPlayerDeathMonitor.playerDeath(mPlayerIId);
          }
      }
  
      /**
       * @hide
       * Returns true if the player is considered "active", i.e. actively playing, and thus
       * in a state that should make it considered for the list public (sanitized) active playback
       * configurations
       * @return true if active
       */
      public boolean isActive() {
          switch (mPlayerState) {
              case PLAYER_STATE_STARTED:
                  return true;
              case PLAYER_STATE_UNKNOWN:
              case PLAYER_STATE_RELEASED:
              case PLAYER_STATE_IDLE:
              case PLAYER_STATE_PAUSED:
              case PLAYER_STATE_STOPPED:
              default:
                  return false;
          }
      }
  
      /**
       * @hide
       * For AudioService dump
       * @param pw
       */
      public void dump(PrintWriter pw) {
          pw.println("  " + toLogFriendlyString(this));
      }
  
      /**
       * @hide
       */
      public static String toLogFriendlyString(AudioPlaybackConfiguration apc) {
          return new String("ID:" + apc.mPlayerIId
                  + " -- type:" + toLogFriendlyPlayerType(apc.mPlayerType)
                  + " -- u/pid:" + apc.mClientUid +"/" + apc.mClientPid
                  + " -- state:" + toLogFriendlyPlayerState(apc.mPlayerState)
                  + " -- attr:" + apc.mPlayerAttr);
      }
  
      public static final @android.annotation.NonNull Parcelable.Creator<AudioPlaybackConfiguration> CREATOR
              = new Parcelable.Creator<AudioPlaybackConfiguration>() {
          /**
           * Rebuilds an AudioPlaybackConfiguration previously stored with writeToParcel().
           * @param p Parcel object to read the AudioPlaybackConfiguration from
           * @return a new AudioPlaybackConfiguration created from the data in the parcel
           */
          public AudioPlaybackConfiguration createFromParcel(Parcel p) {
              return new AudioPlaybackConfiguration(p);
          }
          public AudioPlaybackConfiguration[] newArray(int size) {
              return new AudioPlaybackConfiguration[size];
          }
      };
  
      @Override
      public int hashCode() {
          return Objects.hash(mPlayerIId, mPlayerType, mClientUid, mClientPid);
      }
  
      @Override
      public int describeContents() {
          return 0;
      }
  
      @Override
      public void writeToParcel(Parcel dest, int flags) {
          dest.writeInt(mPlayerIId);
          dest.writeInt(mPlayerType);
          dest.writeInt(mClientUid);
          dest.writeInt(mClientPid);
          dest.writeInt(mPlayerState);
          mPlayerAttr.writeToParcel(dest, 0);
          final IPlayerShell ips;
          synchronized (this) {
              ips = mIPlayerShell;
          }
          dest.writeStrongInterface(ips == null ? null : ips.getIPlayer());
      }
  
      private AudioPlaybackConfiguration(Parcel in) {
          mPlayerIId = in.readInt();
          mPlayerType = in.readInt();
          mClientUid = in.readInt();
          mClientPid = in.readInt();
          mPlayerState = in.readInt();
          mPlayerAttr = AudioAttributes.CREATOR.createFromParcel(in);
          final IPlayer p = IPlayer.Stub.asInterface(in.readStrongBinder());
          mIPlayerShell = (p == null) ? null : new IPlayerShell(null, p);
      }
  
      @Override
      public boolean equals(Object o) {
          if (this == o) return true;
          if (o == null || !(o instanceof AudioPlaybackConfiguration)) return false;
  
          AudioPlaybackConfiguration that = (AudioPlaybackConfiguration) o;
  
          return ((mPlayerIId == that.mPlayerIId)
                  && (mPlayerType == that.mPlayerType)
                  && (mClientUid == that.mClientUid)
                  && (mClientPid == that.mClientPid));
      }
  
      //=====================================================================
      // Inner class for corresponding IPlayer and its death monitoring
      static final class IPlayerShell implements IBinder.DeathRecipient {
  
          final AudioPlaybackConfiguration mMonitor; // never null
          private volatile IPlayer mIPlayer;
  
          IPlayerShell(@NonNull AudioPlaybackConfiguration monitor, @NonNull IPlayer iplayer) {
              mMonitor = monitor;
              mIPlayer = iplayer;
          }
  
          synchronized void monitorDeath() {
              if (mIPlayer == null) {
                  return;
              }
              try {
                  mIPlayer.asBinder().linkToDeath(this, 0);
              } catch (RemoteException e) {
                  if (mMonitor != null) {
                      Log.w(TAG, "Could not link to client death for piid=" + mMonitor.mPlayerIId, e);
                  } else {
                      Log.w(TAG, "Could not link to client death", e);
                  }
              }
          }
  
          IPlayer getIPlayer() {
              return mIPlayer;
          }
  
          public void binderDied() {
              if (mMonitor != null) {
                  if (DEBUG) { Log.i(TAG, "IPlayerShell binderDied for piid=" + mMonitor.mPlayerIId);}
                  mMonitor.playerDied();
              } else if (DEBUG) { Log.i(TAG, "IPlayerShell binderDied"); }
          }
  
          synchronized void release() {
              if (mIPlayer == null) {
                  return;
              }
              mIPlayer.asBinder().unlinkToDeath(this, 0);
              mIPlayer = null;
              Binder.flushPendingCommands();
          }
      }
  
      //=====================================================================
      // Utilities
  
      /** @hide */
      public static String toLogFriendlyPlayerType(int type) {
          switch (type) {
              case PLAYER_TYPE_UNKNOWN: return "unknown";
              case PLAYER_TYPE_JAM_AUDIOTRACK: return "android.media.AudioTrack";
              case PLAYER_TYPE_JAM_MEDIAPLAYER: return "android.media.MediaPlayer";
              case PLAYER_TYPE_JAM_SOUNDPOOL:   return "android.media.SoundPool";
              case PLAYER_TYPE_SLES_AUDIOPLAYER_BUFFERQUEUE:
                  return "OpenSL ES AudioPlayer (Buffer Queue)";
              case PLAYER_TYPE_SLES_AUDIOPLAYER_URI_FD:
                  return "OpenSL ES AudioPlayer (URI/FD)";
              case PLAYER_TYPE_AAUDIO: return "AAudio";
              case PLAYER_TYPE_HW_SOURCE: return "hardware source";
              case PLAYER_TYPE_EXTERNAL_PROXY: return "external proxy";
              default:
                  return "unknown player type " + type + " - FIXME";
          }
      }
  
      /** @hide */
      public static String toLogFriendlyPlayerState(int state) {
          switch (state) {
              case PLAYER_STATE_UNKNOWN: return "unknown";
              case PLAYER_STATE_RELEASED: return "released";
              case PLAYER_STATE_IDLE: return "idle";
              case PLAYER_STATE_STARTED: return "started";
              case PLAYER_STATE_PAUSED: return "paused";
              case PLAYER_STATE_STOPPED: return "stopped";
              default:
                  return "unknown player state - FIXME";
          }
      }
  }
  