content: /*
   * Copyright (C) 2012 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 android.media.AudioManager;
  import android.media.SoundPool;
  import android.util.Log;
  
  /**
   * <p>A class for producing sounds that match those produced by various actions
   * taken by the media and camera APIs.  </p>
   *
   * <p>This class is recommended for use with the {@link android.hardware.camera2} API, since the
   * camera2 API does not play any sounds on its own for any capture or video recording actions.</p>
   *
   * <p>With the older {@link android.hardware.Camera} API, use this class to play an appropriate
   * camera operation sound when implementing a custom still or video recording mechanism (through the
   * Camera preview callbacks with
   * {@link android.hardware.Camera#setPreviewCallback Camera.setPreviewCallback}, or through GPU
   * processing with {@link android.hardware.Camera#setPreviewTexture Camera.setPreviewTexture}, for
   * example), or when implementing some other camera-like function in your application.</p>
   *
   * <p>There is no need to play sounds when using
   * {@link android.hardware.Camera#takePicture Camera.takePicture} or
   * {@link android.media.MediaRecorder} for still images or video, respectively,
   * as the Android framework will play the appropriate sounds when needed for
   * these calls.</p>
   *
   */
  public class MediaActionSound {
      private static final int NUM_MEDIA_SOUND_STREAMS = 1;
  
      private SoundPool mSoundPool;
      private SoundState[] mSounds;
  
      private static final String[] SOUND_DIRS = {
          "/product/media/audio/ui/",
          "/system/media/audio/ui/",
      };
  
      private static final String[] SOUND_FILES = {
          "camera_click.ogg",
          "camera_focus.ogg",
          "VideoRecord.ogg",
          "VideoStop.ogg"
      };
  
      private static final String TAG = "MediaActionSound";
      /**
       * The sound used by
       * {@link android.hardware.Camera#takePicture Camera.takePicture} to
       * indicate still image capture.
       * @see #play
       */
      public static final int SHUTTER_CLICK         = 0;
  
      /**
       * A sound to indicate that focusing has completed. Because deciding
       * when this occurs is application-dependent, this sound is not used by
       * any methods in the media or camera APIs.
       * @see #play
       */
      public static final int FOCUS_COMPLETE        = 1;
  
      /**
       * The sound used by
       * {@link android.media.MediaRecorder#start MediaRecorder.start()} to
       * indicate the start of video recording.
       * @see #play
       */
      public static final int START_VIDEO_RECORDING = 2;
  
      /**
       * The sound used by
       * {@link android.media.MediaRecorder#stop MediaRecorder.stop()} to
       * indicate the end of video recording.
       * @see #play
       */
      public static final int STOP_VIDEO_RECORDING  = 3;
  
      /**
       * States for SoundState.
       * STATE_NOT_LOADED             : sample not loaded
       * STATE_LOADING                : sample being loaded: waiting for load completion callback
       * STATE_LOADING_PLAY_REQUESTED : sample being loaded and playback request received
       * STATE_LOADED                 : sample loaded, ready for playback
       */
      private static final int STATE_NOT_LOADED             = 0;
      private static final int STATE_LOADING                = 1;
      private static final int STATE_LOADING_PLAY_REQUESTED = 2;
      private static final int STATE_LOADED                 = 3;
  
      private class SoundState {
          public final int name;
          public int id;
          public int state;
  
          public SoundState(int name) {
              this.name = name;
              id = 0; // 0 is an invalid sample ID.
              state = STATE_NOT_LOADED;
          }
      }
      /**
       * Construct a new MediaActionSound instance. Only a single instance is
       * needed for playing any platform media action sound; you do not need a
       * separate instance for each sound type.
       */
      public MediaActionSound() {
          mSoundPool = new SoundPool.Builder()
                  .setMaxStreams(NUM_MEDIA_SOUND_STREAMS)
                  .setAudioAttributes(new AudioAttributes.Builder()
                      .setUsage(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION)
                      .setFlags(AudioAttributes.FLAG_AUDIBILITY_ENFORCED)
                      .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
                      .build())
                  .build();
          mSoundPool.setOnLoadCompleteListener(mLoadCompleteListener);
          mSounds = new SoundState[SOUND_FILES.length];
          for (int i = 0; i < mSounds.length; i++) {
              mSounds[i] = new SoundState(i);
          }
      }
  
      private int loadSound(SoundState sound) {
          final String soundFileName = SOUND_FILES[sound.name];
          for (String soundDir : SOUND_DIRS) {
              int id = mSoundPool.load(soundDir + soundFileName, 1);
              if (id > 0) {
                  sound.state = STATE_LOADING;
                  sound.id = id;
                  return id;
              }
          }
          return 0;
      }
  
      /**
       * Preload a predefined platform sound to minimize latency when the sound is
       * played later by {@link #play}.
       * @param soundName The type of sound to preload, selected from
       *         SHUTTER_CLICK, FOCUS_COMPLETE, START_VIDEO_RECORDING, or
       *         STOP_VIDEO_RECORDING.
       * @see #play
       * @see #SHUTTER_CLICK
       * @see #FOCUS_COMPLETE
       * @see #START_VIDEO_RECORDING
       * @see #STOP_VIDEO_RECORDING
       */
      public void load(int soundName) {
          if (soundName < 0 || soundName >= SOUND_FILES.length) {
              throw new RuntimeException("Unknown sound requested: " + soundName);
          }
          SoundState sound = mSounds[soundName];
          synchronized (sound) {
              switch (sound.state) {
              case STATE_NOT_LOADED:
                  if (loadSound(sound) <= 0) {
                      Log.e(TAG, "load() error loading sound: " + soundName);
                  }
                  break;
              default:
                  Log.e(TAG, "load() called in wrong state: " + sound + " for sound: "+ soundName);
                  break;
              }
          }
      }
  
      /**
       * <p>Play one of the predefined platform sounds for media actions.</p>
       *
       * <p>Use this method to play a platform-specific sound for various media
       * actions. The sound playback is done asynchronously, with the same
       * behavior and content as the sounds played by
       * {@link android.hardware.Camera#takePicture Camera.takePicture},
       * {@link android.media.MediaRecorder#start MediaRecorder.start}, and
       * {@link android.media.MediaRecorder#stop MediaRecorder.stop}.</p>
       *
       * <p>With the {@link android.hardware.camera2 camera2} API, this method can be used to play
       * standard camera operation sounds with the appropriate system behavior for such sounds.</p>
  
       * <p>With the older {@link android.hardware.Camera} API, using this method makes it easy to
       * match the default device sounds when recording or capturing data through the preview
       * callbacks, or when implementing custom camera-like features in your application.</p>
       *
       * <p>If the sound has not been loaded by {@link #load} before calling play,
       * play will load the sound at the cost of some additional latency before
       * sound playback begins. </p>
       *
       * @param soundName The type of sound to play, selected from
       *         SHUTTER_CLICK, FOCUS_COMPLETE, START_VIDEO_RECORDING, or
       *         STOP_VIDEO_RECORDING.
       * @see android.hardware.Camera#takePicture
       * @see android.media.MediaRecorder
       * @see #SHUTTER_CLICK
       * @see #FOCUS_COMPLETE
       * @see #START_VIDEO_RECORDING
       * @see #STOP_VIDEO_RECORDING
       */
      public void play(int soundName) {
          if (soundName < 0 || soundName >= SOUND_FILES.length) {
              throw new RuntimeException("Unknown sound requested: " + soundName);
          }
          SoundState sound = mSounds[soundName];
          synchronized (sound) {
              switch (sound.state) {
              case STATE_NOT_LOADED:
                  loadSound(sound);
                  if (loadSound(sound) <= 0) {
                      Log.e(TAG, "play() error loading sound: " + soundName);
                      break;
                  }
                  // FALL THROUGH
  
              case STATE_LOADING:
                  sound.state = STATE_LOADING_PLAY_REQUESTED;
                  break;
              case STATE_LOADED:
                  mSoundPool.play(sound.id, 1.0f, 1.0f, 0, 0, 1.0f);
                  break;
              default:
                  Log.e(TAG, "play() called in wrong state: " + sound.state + " for sound: "+ soundName);
                  break;
              }
          }
      }
  
      private SoundPool.OnLoadCompleteListener mLoadCompleteListener =
              new SoundPool.OnLoadCompleteListener() {
          public void onLoadComplete(SoundPool soundPool,
                  int sampleId, int status) {
              for (SoundState sound : mSounds) {
                  if (sound.id != sampleId) {
                      continue;
                  }
                  int playSoundId = 0;
                  synchronized (sound) {
                      if (status != 0) {
                          sound.state = STATE_NOT_LOADED;
                          sound.id = 0;
                          Log.e(TAG, "OnLoadCompleteListener() error: " + status +
                                  " loading sound: "+ sound.name);
                          return;
                      }
                      switch (sound.state) {
                      case STATE_LOADING:
                          sound.state = STATE_LOADED;
                          break;
                      case STATE_LOADING_PLAY_REQUESTED:
                          playSoundId = sound.id;
                          sound.state = STATE_LOADED;
                          break;
                      default:
                          Log.e(TAG, "OnLoadCompleteListener() called in wrong state: "
                                  + sound.state + " for sound: "+ sound.name);
                          break;
                      }
                  }
                  if (playSoundId != 0) {
                      soundPool.play(playSoundId, 1.0f, 1.0f, 0, 0, 1.0f);
                  }
                  break;
              }
          }
      };
  
      /**
       * Free up all audio resources used by this MediaActionSound instance. Do
       * not call any other methods on a MediaActionSound instance after calling
       * release().
       */
      public void release() {
          if (mSoundPool != null) {
              for (SoundState sound : mSounds) {
                  synchronized (sound) {
                      sound.state = STATE_NOT_LOADED;
                      sound.id = 0;
                  }
              }
              mSoundPool.release();
              mSoundPool = null;
          }
      }
  }
  