1/*
2 * Copyright (C) 2012 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.camera;
18
19import android.annotation.TargetApi;
20import android.content.Context;
21import android.media.AudioManager;
22import android.media.MediaActionSound;
23import android.media.SoundPool;
24import android.util.Log;
25
26import com.android.gallery3d.common.ApiHelper;
27
28/*
29 * This class controls the sound playback according to the API level.
30 */
31public class SoundClips {
32    // Sound actions.
33    public static final int FOCUS_COMPLETE = 0;
34    public static final int START_VIDEO_RECORDING = 1;
35    public static final int STOP_VIDEO_RECORDING = 2;
36
37    public interface Player {
38        public void release();
39        public void play(int action);
40    }
41
42    public static Player getPlayer(Context context) {
43        if (ApiHelper.HAS_MEDIA_ACTION_SOUND) {
44            return new MediaActionSoundPlayer();
45        } else {
46            return new SoundPoolPlayer(context);
47        }
48    }
49
50    /**
51     * This class implements SoundClips.Player using MediaActionSound,
52     * which exists since API level 16.
53     */
54    @TargetApi(ApiHelper.VERSION_CODES.JELLY_BEAN)
55    private static class MediaActionSoundPlayer implements Player {
56        private static final String TAG = "MediaActionSoundPlayer";
57        private MediaActionSound mSound;
58
59        @Override
60        public void release() {
61            if (mSound != null) {
62                mSound.release();
63                mSound = null;
64            }
65        }
66
67        public MediaActionSoundPlayer() {
68            mSound = new MediaActionSound();
69            mSound.load(MediaActionSound.START_VIDEO_RECORDING);
70            mSound.load(MediaActionSound.STOP_VIDEO_RECORDING);
71            mSound.load(MediaActionSound.FOCUS_COMPLETE);
72        }
73
74        @Override
75        public synchronized void play(int action) {
76            switch(action) {
77                case FOCUS_COMPLETE:
78                    mSound.play(MediaActionSound.FOCUS_COMPLETE);
79                    break;
80                case START_VIDEO_RECORDING:
81                    mSound.play(MediaActionSound.START_VIDEO_RECORDING);
82                    break;
83                case STOP_VIDEO_RECORDING:
84                    mSound.play(MediaActionSound.STOP_VIDEO_RECORDING);
85                    break;
86                default:
87                    Log.w(TAG, "Unrecognized action:" + action);
88            }
89        }
90    }
91
92    /**
93     * This class implements SoundClips.Player using SoundPool, which
94     * exists since API level 1.
95     */
96    private static class SoundPoolPlayer implements
97            Player, SoundPool.OnLoadCompleteListener {
98
99        private static final String TAG = "SoundPoolPlayer";
100        private static final int NUM_SOUND_STREAMS = 1;
101        private static final int[] SOUND_RES = { // Soundtrack res IDs.
102            R.raw.focus_complete,
103            R.raw.video_record
104        };
105
106        // ID returned by load() should be non-zero.
107        private static final int ID_NOT_LOADED = 0;
108
109        // Maps a sound action to the id;
110        private final int[] mSoundRes = {0, 1, 1};
111        // Store the context for lazy loading.
112        private Context mContext;
113        // mSoundPool is created every time load() is called and cleared every
114        // time release() is called.
115        private SoundPool mSoundPool;
116        // Sound ID of each sound resources. Given when the sound is loaded.
117        private final int[] mSoundIDs;
118        private final boolean[] mSoundIDReady;
119        private int mSoundIDToPlay;
120
121        public SoundPoolPlayer(Context context) {
122            mContext = context;
123            int audioType = ApiHelper.getIntFieldIfExists(AudioManager.class,
124                    "STREAM_SYSTEM_ENFORCED", null, AudioManager.STREAM_RING);
125
126            mSoundIDToPlay = ID_NOT_LOADED;
127
128            mSoundPool = new SoundPool(NUM_SOUND_STREAMS, audioType, 0);
129            mSoundPool.setOnLoadCompleteListener(this);
130
131            mSoundIDs = new int[SOUND_RES.length];
132            mSoundIDReady = new boolean[SOUND_RES.length];
133            for (int i = 0; i < SOUND_RES.length; i++) {
134                mSoundIDs[i] = mSoundPool.load(mContext, SOUND_RES[i], 1);
135                mSoundIDReady[i] = false;
136            }
137        }
138
139        @Override
140        public synchronized void release() {
141            if (mSoundPool != null) {
142                mSoundPool.release();
143                mSoundPool = null;
144            }
145        }
146
147        @Override
148        public synchronized void play(int action) {
149            if (action < 0 || action >= mSoundRes.length) {
150                Log.e(TAG, "Resource ID not found for action:" + action + " in play().");
151                return;
152            }
153
154            int index = mSoundRes[action];
155            if (mSoundIDs[index] == ID_NOT_LOADED) {
156                // Not loaded yet, load first and then play when the loading is complete.
157                mSoundIDs[index] = mSoundPool.load(mContext, SOUND_RES[index], 1);
158                mSoundIDToPlay = mSoundIDs[index];
159            } else if (!mSoundIDReady[index]) {
160                // Loading and not ready yet.
161                mSoundIDToPlay = mSoundIDs[index];
162            } else {
163                mSoundPool.play(mSoundIDs[index], 1f, 1f, 0, 0, 1f);
164            }
165        }
166
167        @Override
168        public void onLoadComplete(SoundPool pool, int soundID, int status) {
169            if (status != 0) {
170                Log.e(TAG, "loading sound tracks failed (status=" + status + ")");
171                for (int i = 0; i < mSoundIDs.length; i++ ) {
172                    if (mSoundIDs[i] == soundID) {
173                        mSoundIDs[i] = ID_NOT_LOADED;
174                        break;
175                    }
176                }
177                return;
178            }
179
180            for (int i = 0; i < mSoundIDs.length; i++ ) {
181                if (mSoundIDs[i] == soundID) {
182                    mSoundIDReady[i] = true;
183                    break;
184                }
185            }
186
187            if (soundID == mSoundIDToPlay) {
188                mSoundIDToPlay = ID_NOT_LOADED;
189                mSoundPool.play(soundID, 1f, 1f, 0, 0, 1f);
190            }
191        }
192    }
193}
194