Merge "NEW_API: Rework CameraSound into MediaActionSound, and unhide it."
diff --git a/api/current.txt b/api/current.txt
index 1c4190e..4110a20 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -10780,6 +10780,17 @@
     method public abstract void onJetUserIdUpdate(android.media.JetPlayer, int, int);
   }
 
+  public class MediaActionSound {
+    ctor public MediaActionSound();
+    method public void load(int);
+    method public void play(int);
+    method public void release();
+    field public static final int FOCUS_COMPLETE = 1; // 0x1
+    field public static final int SHUTTER_CLICK = 0; // 0x0
+    field public static final int START_VIDEO_RECORDING = 2; // 0x2
+    field public static final int STOP_VIDEO_RECORDING = 3; // 0x3
+  }
+
   public class MediaMetadataRetriever {
     ctor public MediaMetadataRetriever();
     method public java.lang.String extractMetadata(int);
diff --git a/core/java/android/hardware/Camera.java b/core/java/android/hardware/Camera.java
index cca208a..573e6ea 100644
--- a/core/java/android/hardware/Camera.java
+++ b/core/java/android/hardware/Camera.java
@@ -36,7 +36,6 @@
 import java.util.List;
 import java.util.StringTokenizer;
 
-
 /**
  * The Camera class is used to set image capture settings, start/stop preview,
  * snap pictures, and retrieve frames for encoding for video.  This class is a
diff --git a/core/java/android/hardware/CameraSound.java b/core/java/android/hardware/CameraSound.java
deleted file mode 100644
index dc97ff09..0000000
--- a/core/java/android/hardware/CameraSound.java
+++ /dev/null
@@ -1,217 +0,0 @@
-/*
- * Copyright (C) 2011 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.hardware;
-
-import android.media.AudioManager;
-import android.media.MediaPlayer;
-import android.os.SystemProperties;
-import android.util.Log;
-
-import java.io.IOException;
-
-/**
- * <p>Use this class to play an appropriate sound when implementing a custom
- * still or video recording mechanism through the preview callbacks.</p>
- *
- * <p>There is no need to play sounds when using {@link #android.hardware.Camera#takePicture}
- * or {@link android.media.MediaRecorder} for still images or video,
- * respectively, as these play their own sounds when needed.</p>
- *
- * @hide
- */
-public class CameraSound {
-    private static final String TAG = "CameraSound";
-    /**
-     * The sound used by {@link android.hardware.Camera#takePicture} to
-     * indicate still image capture.
-     */
-    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 Camera class.
-     */
-    public static final int FOCUS_COMPLETE        = 1;
-
-    /**
-     * The sound used by {@link android.media.MediaRecorder#start} to
-     * indicate the start of video recording.
-     */
-    public static final int START_VIDEO_RECORDING = 2;
-
-    /**
-     * The sound used by {@link android.media.MediaRecorder#stop} to
-     * indicate the end of video recording.
-     */
-    public static final int STOP_VIDEO_RECORDING  = 3;
-
-    private static final int NUM_SOUNDS           = 4;
-    private CameraSoundPlayer[] mCameraSoundPlayers;
-
-    public CameraSound() {
-    }
-
-    /**
-     * <p>Play one of the predefined platform sounds for camera actions.</p>
-     *
-     * <p>Use this method to play a platform-specific sound for various camera
-     * actions. The sound playing is done asynchronously, with the same behavior
-     * and content as the sounds played by {@link #takePicture takePicture},
-     * {@link android.media.MediaRecorder#start MediaRecorder.start}, and
-     * {@link android.media.MediaRecorder#stop MediaRecorder.stop}.</p>
-     *
-     * <p>Using this method makes it easy to match the default device sounds
-     * when recording or capturing data through the preview callbacks.</p>
-     *
-     * @param soundId The type of sound to play, selected from SHUTTER_CLICK,
-     *         FOCUS_COMPLETE, START_VIDEO_RECORDING, or STOP_VIDEO_RECORDING.
-     * @see android.hardware#takePicture
-     * @see android.media.MediaRecorder
-     * @see #SHUTTER_CLICK
-     * @see #FOCUS_COMPLETE
-     * @see #START_VIDEO_RECORDING
-     * @see #STOP_VIDEO_RECORDING
-     */
-    public void playSound(int soundId) {
-        if (mCameraSoundPlayers == null) {
-            mCameraSoundPlayers = new CameraSoundPlayer[NUM_SOUNDS];
-        }
-        if (mCameraSoundPlayers[soundId] == null) {
-            mCameraSoundPlayers[soundId] = new CameraSoundPlayer(soundId);
-        }
-        mCameraSoundPlayers[soundId].play();
-    }
-
-    public void release() {
-        if (mCameraSoundPlayers != null) {
-            for (CameraSoundPlayer csp: mCameraSoundPlayers) {
-                if (csp != null) {
-                    csp.release();
-                }
-            }
-            mCameraSoundPlayers = null;
-        }
-    }
-
-    private static class CameraSoundPlayer implements Runnable {
-        private int mSoundId;
-        private MediaPlayer mPlayer;
-        private Thread mThread;
-        private boolean mExit;
-        private int mPlayCount;
-
-        private static final String mShutterSound    =
-                "/system/media/audio/ui/camera_click.ogg";
-        private static final String mFocusSound      =
-                "/system/media/audio/ui/camera_focus.ogg";
-        private static final String mVideoStartSound =
-                "/system/media/audio/ui/VideoRecord.ogg";
-        private static final String mVideoStopSound  =
-                "/system/media/audio/ui/VideoRecord.ogg";
-
-        @Override
-        public void run() {
-            String soundFilePath;
-            switch (mSoundId) {
-                case SHUTTER_CLICK:
-                    soundFilePath = mShutterSound;
-                    break;
-                case FOCUS_COMPLETE:
-                    soundFilePath = mFocusSound;
-                    break;
-                case START_VIDEO_RECORDING:
-                    soundFilePath = mVideoStartSound;
-                    break;
-                case STOP_VIDEO_RECORDING:
-                    soundFilePath = mVideoStopSound;
-                    break;
-                default:
-                    Log.e(TAG, "Unknown sound " + mSoundId + " requested.");
-                    return;
-            }
-            mPlayer = new MediaPlayer();
-            try {
-                mPlayer.setAudioStreamType(AudioManager.STREAM_SYSTEM_ENFORCED);
-                mPlayer.setDataSource(soundFilePath);
-                mPlayer.setLooping(false);
-                mPlayer.prepare();
-            } catch(IOException e) {
-                Log.e(TAG, "Error setting up sound " + mSoundId, e);
-                return;
-            }
-
-            while(true) {
-                try {
-                    synchronized (this) {
-                        while(true) {
-                            if (mExit) {
-                                return;
-                            } else if (mPlayCount <= 0) {
-                                wait();
-                            } else {
-                                mPlayCount--;
-                                break;
-                            }
-                        }
-                    }
-                    mPlayer.start();
-                } catch (Exception e) {
-                    Log.e(TAG, "Error playing sound " + mSoundId, e);
-                }
-            }
-        }
-
-        public CameraSoundPlayer(int soundId) {
-            mSoundId = soundId;
-        }
-
-        public void play() {
-            if (mThread == null) {
-                mThread = new Thread(this);
-                mThread.start();
-            }
-            synchronized (this) {
-                mPlayCount++;
-                notifyAll();
-            }
-        }
-
-        public void release() {
-            if (mThread != null) {
-                synchronized (this) {
-                    mExit = true;
-                    notifyAll();
-                }
-                try {
-                    mThread.join();
-                } catch (InterruptedException e) {
-                }
-                mThread = null;
-            }
-            if (mPlayer != null) {
-                mPlayer.release();
-                mPlayer = null;
-            }
-        }
-
-        @Override
-        protected void finalize() {
-            release();
-        }
-    }
-}
\ No newline at end of file
diff --git a/media/java/android/media/MediaActionSound.java b/media/java/android/media/MediaActionSound.java
new file mode 100644
index 0000000..d0e6910
--- /dev/null
+++ b/media/java/android/media/MediaActionSound.java
@@ -0,0 +1,194 @@
+/*
+ * 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>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 int[]     mSoundIds;
+    private int       mSoundIdToPlay;
+
+    private static final String[] SOUND_FILES = {
+        "/system/media/audio/ui/camera_click.ogg",
+        "/system/media/audio/ui/camera_focus.ogg",
+        "/system/media/audio/ui/VideoRecord.ogg",
+        "/system/media/audio/ui/VideoRecord.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;
+
+    private static final int SOUND_NOT_LOADED = -1;
+
+    /**
+     * 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(NUM_MEDIA_SOUND_STREAMS,
+                AudioManager.STREAM_SYSTEM_ENFORCED, 0);
+        mSoundPool.setOnLoadCompleteListener(mLoadCompleteListener);
+        mSoundIds = new int[SOUND_FILES.length];
+        for (int i = 0; i < mSoundIds.length; i++) {
+            mSoundIds[i] = SOUND_NOT_LOADED;
+        }
+        mSoundIdToPlay = SOUND_NOT_LOADED;
+    }
+
+    /**
+     * 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 synchronized void load(int soundName) {
+        if (soundName < 0 || soundName >= SOUND_FILES.length) {
+            throw new RuntimeException("Unknown sound requested: " + soundName);
+        }
+        if (mSoundIds[soundName] == SOUND_NOT_LOADED) {
+            mSoundIds[soundName] =
+                    mSoundPool.load(SOUND_FILES[soundName], 1);
+        }
+    }
+
+    /**
+     * <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>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 synchronized void play(int soundName) {
+        if (soundName < 0 || soundName >= SOUND_FILES.length) {
+            throw new RuntimeException("Unknown sound requested: " + soundName);
+        }
+        if (mSoundIds[soundName] == SOUND_NOT_LOADED) {
+            mSoundIdToPlay =
+                    mSoundPool.load(SOUND_FILES[soundName], 1);
+            mSoundIds[soundName] = mSoundIdToPlay;
+        } else {
+            mSoundPool.play(mSoundIds[soundName], 1.0f, 1.0f, 0, 0, 1.0f);
+        }
+    }
+
+    private SoundPool.OnLoadCompleteListener mLoadCompleteListener =
+            new SoundPool.OnLoadCompleteListener() {
+        public void onLoadComplete(SoundPool soundPool,
+                int sampleId, int status) {
+            if (status == 0) {
+                if (mSoundIdToPlay == sampleId) {
+                    soundPool.play(sampleId, 1.0f, 1.0f, 0, 0, 1.0f);
+                    mSoundIdToPlay = SOUND_NOT_LOADED;
+                }
+            } else {
+                Log.e(TAG, "Unable to load sound for playback (status: " +
+                        status + ")");
+            }
+        }
+    };
+
+    /**
+     * Free up all audio resources used by this MediaActionSound instance
+     */
+    public void release() {
+        mSoundPool.release();
+        mSoundPool = null;
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java
index c4a92f7..9a76c14 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java
@@ -34,7 +34,7 @@
 import android.graphics.Matrix;
 import android.graphics.PixelFormat;
 import android.graphics.PointF;
-import android.hardware.CameraSound;
+import android.media.MediaActionSound;
 import android.net.Uri;
 import android.os.AsyncTask;
 import android.os.Environment;
@@ -256,7 +256,7 @@
     private float mBgPadding;
     private float mBgPaddingScale;
 
-    private CameraSound mCameraSound;
+    private MediaActionSound mCameraSound;
 
 
     /**
@@ -309,7 +309,8 @@
         mBgPaddingScale = mBgPadding /  mDisplayMetrics.widthPixels;
 
         // Setup the Camera shutter sound
-        mCameraSound = new CameraSound();
+        mCameraSound = new MediaActionSound();
+        mCameraSound.load(MediaActionSound.SHUTTER_CLICK);
     }
 
     /**
@@ -422,7 +423,7 @@
             @Override
             public void run() {
                 // Play the shutter sound to notify that we've taken a screenshot
-                mCameraSound.playSound(CameraSound.SHUTTER_CLICK);
+                mCameraSound.play(MediaActionSound.SHUTTER_CLICK);
 
                 mScreenshotView.setLayerType(View.LAYER_TYPE_HARDWARE, null);
                 mScreenshotView.buildLayer();