1 /*
2  * Copyright (C) 2007 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 
17 package android.media;
18 
19 import java.io.File;
20 import java.io.FileDescriptor;
21 import java.lang.ref.WeakReference;
22 
23 import android.app.ActivityThread;
24 import android.app.AppOpsManager;
25 import android.content.Context;
26 import android.content.res.AssetFileDescriptor;
27 import android.os.Handler;
28 import android.os.IBinder;
29 import android.os.Looper;
30 import android.os.Message;
31 import android.os.ParcelFileDescriptor;
32 import android.os.Process;
33 import android.os.RemoteException;
34 import android.os.ServiceManager;
35 import android.os.SystemProperties;
36 import android.util.AndroidRuntimeException;
37 import android.util.Log;
38 
39 import com.android.internal.app.IAppOpsService;
40 
41 
42 /**
43  * The SoundPool class manages and plays audio resources for applications.
44  *
45  * <p>A SoundPool is a collection of samples that can be loaded into memory
46  * from a resource inside the APK or from a file in the file system. The
47  * SoundPool library uses the MediaPlayer service to decode the audio
48  * into a raw 16-bit PCM mono or stereo stream. This allows applications
49  * to ship with compressed streams without having to suffer the CPU load
50  * and latency of decompressing during playback.</p>
51  *
52  * <p>In addition to low-latency playback, SoundPool can also manage the number
53  * of audio streams being rendered at once. When the SoundPool object is
54  * constructed, the maxStreams parameter sets the maximum number of streams
55  * that can be played at a time from this single SoundPool. SoundPool tracks
56  * the number of active streams. If the maximum number of streams is exceeded,
57  * SoundPool will automatically stop a previously playing stream based first
58  * on priority and then by age within that priority. Limiting the maximum
59  * number of streams helps to cap CPU loading and reducing the likelihood that
60  * audio mixing will impact visuals or UI performance.</p>
61  *
62  * <p>Sounds can be looped by setting a non-zero loop value. A value of -1
63  * causes the sound to loop forever. In this case, the application must
64  * explicitly call the stop() function to stop the sound. Any other non-zero
65  * value will cause the sound to repeat the specified number of times, e.g.
66  * a value of 3 causes the sound to play a total of 4 times.</p>
67  *
68  * <p>The playback rate can also be changed. A playback rate of 1.0 causes
69  * the sound to play at its original frequency (resampled, if necessary,
70  * to the hardware output frequency). A playback rate of 2.0 causes the
71  * sound to play at twice its original frequency, and a playback rate of
72  * 0.5 causes it to play at half its original frequency. The playback
73  * rate range is 0.5 to 2.0.</p>
74  *
75  * <p>Priority runs low to high, i.e. higher numbers are higher priority.
76  * Priority is used when a call to play() would cause the number of active
77  * streams to exceed the value established by the maxStreams parameter when
78  * the SoundPool was created. In this case, the stream allocator will stop
79  * the lowest priority stream. If there are multiple streams with the same
80  * low priority, it will choose the oldest stream to stop. In the case
81  * where the priority of the new stream is lower than all the active
82  * streams, the new sound will not play and the play() function will return
83  * a streamID of zero.</p>
84  *
85  * <p>Let's examine a typical use case: A game consists of several levels of
86  * play. For each level, there is a set of unique sounds that are used only
87  * by that level. In this case, the game logic should create a new SoundPool
88  * object when the first level is loaded. The level data itself might contain
89  * the list of sounds to be used by this level. The loading logic iterates
90  * through the list of sounds calling the appropriate SoundPool.load()
91  * function. This should typically be done early in the process to allow time
92  * for decompressing the audio to raw PCM format before they are needed for
93  * playback.</p>
94  *
95  * <p>Once the sounds are loaded and play has started, the application can
96  * trigger sounds by calling SoundPool.play(). Playing streams can be
97  * paused or resumed, and the application can also alter the pitch by
98  * adjusting the playback rate in real-time for doppler or synthesis
99  * effects.</p>
100  *
101  * <p>Note that since streams can be stopped due to resource constraints, the
102  * streamID is a reference to a particular instance of a stream. If the stream
103  * is stopped to allow a higher priority stream to play, the stream is no
104  * longer be valid. However, the application is allowed to call methods on
105  * the streamID without error. This may help simplify program logic since
106  * the application need not concern itself with the stream lifecycle.</p>
107  *
108  * <p>In our example, when the player has completed the level, the game
109  * logic should call SoundPool.release() to release all the native resources
110  * in use and then set the SoundPool reference to null. If the player starts
111  * another level, a new SoundPool is created, sounds are loaded, and play
112  * resumes.</p>
113  */
114 public class SoundPool {
115     private final SoundPoolDelegate mImpl;
116 
117     /**
118      * Constructor. Constructs a SoundPool object with the following
119      * characteristics:
120      *
121      * @param maxStreams the maximum number of simultaneous streams for this
122      *                   SoundPool object
123      * @param streamType the audio stream type as described in AudioManager
124      *                   For example, game applications will normally use
125      *                   {@link AudioManager#STREAM_MUSIC}.
126      * @param srcQuality the sample-rate converter quality. Currently has no
127      *                   effect. Use 0 for the default.
128      * @return a SoundPool object, or null if creation failed
129      * @deprecated use {@link SoundPool.Builder} instead to create and configure a
130      *     SoundPool instance
131      */
SoundPool(int maxStreams, int streamType, int srcQuality)132     public SoundPool(int maxStreams, int streamType, int srcQuality) {
133         this(maxStreams,
134                 new AudioAttributes.Builder().setInternalLegacyStreamType(streamType).build());
135     }
136 
SoundPool(int maxStreams, AudioAttributes attributes)137     private SoundPool(int maxStreams, AudioAttributes attributes) {
138         if (SystemProperties.getBoolean("config.disable_media", false)) {
139             mImpl = new SoundPoolStub();
140         } else {
141             mImpl = new SoundPoolImpl(this, maxStreams, attributes);
142         }
143     }
144 
145     /**
146      * Builder class for {@link SoundPool} objects.
147      */
148     public static class Builder {
149         private int mMaxStreams = 1;
150         private AudioAttributes mAudioAttributes;
151 
152         /**
153          * Constructs a new Builder with the defaults format values.
154          * If not provided, the maximum number of streams is 1 (see {@link #setMaxStreams(int)} to
155          * change it), and the audio attributes have a usage value of
156          * {@link AudioAttributes#USAGE_MEDIA} (see {@link #setAudioAttributes(AudioAttributes)} to
157          * change them).
158          */
Builder()159         public Builder() {
160         }
161 
162         /**
163          * Sets the maximum of number of simultaneous streams that can be played simultaneously.
164          * @param maxStreams a value equal to 1 or greater.
165          * @return the same Builder instance
166          * @throws IllegalArgumentException
167          */
setMaxStreams(int maxStreams)168         public Builder setMaxStreams(int maxStreams) throws IllegalArgumentException {
169             if (maxStreams <= 0) {
170                 throw new IllegalArgumentException(
171                         "Strictly positive value required for the maximum number of streams");
172             }
173             mMaxStreams = maxStreams;
174             return this;
175         }
176 
177         /**
178          * Sets the {@link AudioAttributes}. For examples, game applications will use attributes
179          * built with usage information set to {@link AudioAttributes#USAGE_GAME}.
180          * @param attributes a non-null
181          * @return
182          */
setAudioAttributes(AudioAttributes attributes)183         public Builder setAudioAttributes(AudioAttributes attributes)
184                 throws IllegalArgumentException {
185             if (attributes == null) {
186                 throw new IllegalArgumentException("Invalid null AudioAttributes");
187             }
188             mAudioAttributes = attributes;
189             return this;
190         }
191 
build()192         public SoundPool build() {
193             if (mAudioAttributes == null) {
194                 mAudioAttributes = new AudioAttributes.Builder()
195                         .setUsage(AudioAttributes.USAGE_MEDIA).build();
196             }
197             return new SoundPool(mMaxStreams, mAudioAttributes);
198         }
199     }
200 
201     /**
202      * Load the sound from the specified path.
203      *
204      * @param path the path to the audio file
205      * @param priority the priority of the sound. Currently has no effect. Use
206      *                 a value of 1 for future compatibility.
207      * @return a sound ID. This value can be used to play or unload the sound.
208      */
load(String path, int priority)209     public int load(String path, int priority) {
210         return mImpl.load(path, priority);
211     }
212 
213     /**
214      * Load the sound from the specified APK resource.
215      *
216      * Note that the extension is dropped. For example, if you want to load
217      * a sound from the raw resource file "explosion.mp3", you would specify
218      * "R.raw.explosion" as the resource ID. Note that this means you cannot
219      * have both an "explosion.wav" and an "explosion.mp3" in the res/raw
220      * directory.
221      *
222      * @param context the application context
223      * @param resId the resource ID
224      * @param priority the priority of the sound. Currently has no effect. Use
225      *                 a value of 1 for future compatibility.
226      * @return a sound ID. This value can be used to play or unload the sound.
227      */
load(Context context, int resId, int priority)228     public int load(Context context, int resId, int priority) {
229         return mImpl.load(context, resId, priority);
230     }
231 
232     /**
233      * Load the sound from an asset file descriptor.
234      *
235      * @param afd an asset file descriptor
236      * @param priority the priority of the sound. Currently has no effect. Use
237      *                 a value of 1 for future compatibility.
238      * @return a sound ID. This value can be used to play or unload the sound.
239      */
load(AssetFileDescriptor afd, int priority)240     public int load(AssetFileDescriptor afd, int priority) {
241         return mImpl.load(afd, priority);
242     }
243 
244     /**
245      * Load the sound from a FileDescriptor.
246      *
247      * This version is useful if you store multiple sounds in a single
248      * binary. The offset specifies the offset from the start of the file
249      * and the length specifies the length of the sound within the file.
250      *
251      * @param fd a FileDescriptor object
252      * @param offset offset to the start of the sound
253      * @param length length of the sound
254      * @param priority the priority of the sound. Currently has no effect. Use
255      *                 a value of 1 for future compatibility.
256      * @return a sound ID. This value can be used to play or unload the sound.
257      */
load(FileDescriptor fd, long offset, long length, int priority)258     public int load(FileDescriptor fd, long offset, long length, int priority) {
259         return mImpl.load(fd, offset, length, priority);
260     }
261 
262     /**
263      * Unload a sound from a sound ID.
264      *
265      * Unloads the sound specified by the soundID. This is the value
266      * returned by the load() function. Returns true if the sound is
267      * successfully unloaded, false if the sound was already unloaded.
268      *
269      * @param soundID a soundID returned by the load() function
270      * @return true if just unloaded, false if previously unloaded
271      */
unload(int soundID)272     public final boolean unload(int soundID) {
273         return mImpl.unload(soundID);
274     }
275 
276     /**
277      * Play a sound from a sound ID.
278      *
279      * Play the sound specified by the soundID. This is the value
280      * returned by the load() function. Returns a non-zero streamID
281      * if successful, zero if it fails. The streamID can be used to
282      * further control playback. Note that calling play() may cause
283      * another sound to stop playing if the maximum number of active
284      * streams is exceeded. A loop value of -1 means loop forever,
285      * a value of 0 means don't loop, other values indicate the
286      * number of repeats, e.g. a value of 1 plays the audio twice.
287      * The playback rate allows the application to vary the playback
288      * rate (pitch) of the sound. A value of 1.0 means play back at
289      * the original frequency. A value of 2.0 means play back twice
290      * as fast, and a value of 0.5 means playback at half speed.
291      *
292      * @param soundID a soundID returned by the load() function
293      * @param leftVolume left volume value (range = 0.0 to 1.0)
294      * @param rightVolume right volume value (range = 0.0 to 1.0)
295      * @param priority stream priority (0 = lowest priority)
296      * @param loop loop mode (0 = no loop, -1 = loop forever)
297      * @param rate playback rate (1.0 = normal playback, range 0.5 to 2.0)
298      * @return non-zero streamID if successful, zero if failed
299      */
play(int soundID, float leftVolume, float rightVolume, int priority, int loop, float rate)300     public final int play(int soundID, float leftVolume, float rightVolume,
301             int priority, int loop, float rate) {
302         return mImpl.play(
303             soundID, leftVolume, rightVolume, priority, loop, rate);
304     }
305 
306     /**
307      * Pause a playback stream.
308      *
309      * Pause the stream specified by the streamID. This is the
310      * value returned by the play() function. If the stream is
311      * playing, it will be paused. If the stream is not playing
312      * (e.g. is stopped or was previously paused), calling this
313      * function will have no effect.
314      *
315      * @param streamID a streamID returned by the play() function
316      */
pause(int streamID)317     public final void pause(int streamID) {
318         mImpl.pause(streamID);
319     }
320 
321     /**
322      * Resume a playback stream.
323      *
324      * Resume the stream specified by the streamID. This
325      * is the value returned by the play() function. If the stream
326      * is paused, this will resume playback. If the stream was not
327      * previously paused, calling this function will have no effect.
328      *
329      * @param streamID a streamID returned by the play() function
330      */
resume(int streamID)331     public final void resume(int streamID) {
332         mImpl.resume(streamID);
333     }
334 
335     /**
336      * Pause all active streams.
337      *
338      * Pause all streams that are currently playing. This function
339      * iterates through all the active streams and pauses any that
340      * are playing. It also sets a flag so that any streams that
341      * are playing can be resumed by calling autoResume().
342      */
autoPause()343     public final void autoPause() {
344         mImpl.autoPause();
345     }
346 
347     /**
348      * Resume all previously active streams.
349      *
350      * Automatically resumes all streams that were paused in previous
351      * calls to autoPause().
352      */
autoResume()353     public final void autoResume() {
354         mImpl.autoResume();
355     }
356 
357     /**
358      * Stop a playback stream.
359      *
360      * Stop the stream specified by the streamID. This
361      * is the value returned by the play() function. If the stream
362      * is playing, it will be stopped. It also releases any native
363      * resources associated with this stream. If the stream is not
364      * playing, it will have no effect.
365      *
366      * @param streamID a streamID returned by the play() function
367      */
stop(int streamID)368     public final void stop(int streamID) {
369         mImpl.stop(streamID);
370     }
371 
372     /**
373      * Set stream volume.
374      *
375      * Sets the volume on the stream specified by the streamID.
376      * This is the value returned by the play() function. The
377      * value must be in the range of 0.0 to 1.0. If the stream does
378      * not exist, it will have no effect.
379      *
380      * @param streamID a streamID returned by the play() function
381      * @param leftVolume left volume value (range = 0.0 to 1.0)
382      * @param rightVolume right volume value (range = 0.0 to 1.0)
383      */
setVolume(int streamID, float leftVolume, float rightVolume)384     public final void setVolume(int streamID,
385             float leftVolume, float rightVolume) {
386         mImpl.setVolume(streamID, leftVolume, rightVolume);
387     }
388 
389     /**
390      * Similar, except set volume of all channels to same value.
391      * @hide
392      */
setVolume(int streamID, float volume)393     public void setVolume(int streamID, float volume) {
394         setVolume(streamID, volume, volume);
395     }
396 
397     /**
398      * Change stream priority.
399      *
400      * Change the priority of the stream specified by the streamID.
401      * This is the value returned by the play() function. Affects the
402      * order in which streams are re-used to play new sounds. If the
403      * stream does not exist, it will have no effect.
404      *
405      * @param streamID a streamID returned by the play() function
406      */
setPriority(int streamID, int priority)407     public final void setPriority(int streamID, int priority) {
408         mImpl.setPriority(streamID, priority);
409     }
410 
411     /**
412      * Set loop mode.
413      *
414      * Change the loop mode. A loop value of -1 means loop forever,
415      * a value of 0 means don't loop, other values indicate the
416      * number of repeats, e.g. a value of 1 plays the audio twice.
417      * If the stream does not exist, it will have no effect.
418      *
419      * @param streamID a streamID returned by the play() function
420      * @param loop loop mode (0 = no loop, -1 = loop forever)
421      */
setLoop(int streamID, int loop)422     public final void setLoop(int streamID, int loop) {
423         mImpl.setLoop(streamID, loop);
424     }
425 
426     /**
427      * Change playback rate.
428      *
429      * The playback rate allows the application to vary the playback
430      * rate (pitch) of the sound. A value of 1.0 means playback at
431      * the original frequency. A value of 2.0 means playback twice
432      * as fast, and a value of 0.5 means playback at half speed.
433      * If the stream does not exist, it will have no effect.
434      *
435      * @param streamID a streamID returned by the play() function
436      * @param rate playback rate (1.0 = normal playback, range 0.5 to 2.0)
437      */
setRate(int streamID, float rate)438     public final void setRate(int streamID, float rate) {
439         mImpl.setRate(streamID, rate);
440     }
441 
442     public interface OnLoadCompleteListener {
443         /**
444          * Called when a sound has completed loading.
445          *
446          * @param soundPool SoundPool object from the load() method
447          * @param sampleId the sample ID of the sound loaded.
448          * @param status the status of the load operation (0 = success)
449          */
onLoadComplete(SoundPool soundPool, int sampleId, int status)450         public void onLoadComplete(SoundPool soundPool, int sampleId, int status);
451     }
452 
453     /**
454      * Sets the callback hook for the OnLoadCompleteListener.
455      */
setOnLoadCompleteListener(OnLoadCompleteListener listener)456     public void setOnLoadCompleteListener(OnLoadCompleteListener listener) {
457         mImpl.setOnLoadCompleteListener(listener);
458     }
459 
460     /**
461      * Release the SoundPool resources.
462      *
463      * Release all memory and native resources used by the SoundPool
464      * object. The SoundPool can no longer be used and the reference
465      * should be set to null.
466      */
release()467     public final void release() {
468         mImpl.release();
469     }
470 
471     /**
472      * Interface for SoundPool implementations.
473      * SoundPool is statically referenced and unconditionally called from all
474      * over the framework, so we can't simply omit the class or make it throw
475      * runtime exceptions, as doing so would break the framework. Instead we
476      * now select either a real or no-op impl object based on whether media is
477      * enabled.
478      *
479      * @hide
480      */
481     /* package */ interface SoundPoolDelegate {
load(String path, int priority)482         public int load(String path, int priority);
load(Context context, int resId, int priority)483         public int load(Context context, int resId, int priority);
load(AssetFileDescriptor afd, int priority)484         public int load(AssetFileDescriptor afd, int priority);
load( FileDescriptor fd, long offset, long length, int priority)485         public int load(
486                 FileDescriptor fd, long offset, long length, int priority);
unload(int soundID)487         public boolean unload(int soundID);
play( int soundID, float leftVolume, float rightVolume, int priority, int loop, float rate)488         public int play(
489                 int soundID, float leftVolume, float rightVolume,
490                 int priority, int loop, float rate);
pause(int streamID)491         public void pause(int streamID);
resume(int streamID)492         public void resume(int streamID);
autoPause()493         public void autoPause();
autoResume()494         public void autoResume();
stop(int streamID)495         public void stop(int streamID);
setVolume(int streamID, float leftVolume, float rightVolume)496         public void setVolume(int streamID, float leftVolume, float rightVolume);
setVolume(int streamID, float volume)497         public void setVolume(int streamID, float volume);
setPriority(int streamID, int priority)498         public void setPriority(int streamID, int priority);
setLoop(int streamID, int loop)499         public void setLoop(int streamID, int loop);
setRate(int streamID, float rate)500         public void setRate(int streamID, float rate);
setOnLoadCompleteListener(OnLoadCompleteListener listener)501         public void setOnLoadCompleteListener(OnLoadCompleteListener listener);
release()502         public void release();
503     }
504 
505 
506     /**
507      * Real implementation of the delegate interface. This was formerly the
508      * body of SoundPool itself.
509      */
510     /* package */ static class SoundPoolImpl implements SoundPoolDelegate {
511         static { System.loadLibrary("soundpool"); }
512 
513         private final static String TAG = "SoundPool";
514         private final static boolean DEBUG = false;
515 
516         private long mNativeContext; // accessed by native methods
517 
518         private EventHandler mEventHandler;
519         private SoundPool.OnLoadCompleteListener mOnLoadCompleteListener;
520         private SoundPool mProxy;
521 
522         private final Object mLock;
523         private final AudioAttributes mAttributes;
524         private final IAppOpsService mAppOps;
525 
526         // SoundPool messages
527         //
528         // must match SoundPool.h
529         private static final int SAMPLE_LOADED = 1;
530 
SoundPoolImpl(SoundPool proxy, int maxStreams, AudioAttributes attr)531         public SoundPoolImpl(SoundPool proxy, int maxStreams, AudioAttributes attr) {
532 
533             // do native setup
534             if (native_setup(new WeakReference(this), maxStreams, attr) != 0) {
535                 throw new RuntimeException("Native setup failed");
536             }
537             mLock = new Object();
538             mProxy = proxy;
539             mAttributes = attr;
540             IBinder b = ServiceManager.getService(Context.APP_OPS_SERVICE);
541             mAppOps = IAppOpsService.Stub.asInterface(b);
542         }
543 
load(String path, int priority)544         public int load(String path, int priority)
545         {
546             // pass network streams to player
547             if (path.startsWith("http:"))
548                 return _load(path, priority);
549 
550             // try local path
551             int id = 0;
552             try {
553                 File f = new File(path);
554                 ParcelFileDescriptor fd = ParcelFileDescriptor.open(f, ParcelFileDescriptor.MODE_READ_ONLY);
555                 if (fd != null) {
556                     id = _load(fd.getFileDescriptor(), 0, f.length(), priority);
557                     fd.close();
558                 }
559             } catch (java.io.IOException e) {
560                 Log.e(TAG, "error loading " + path);
561             }
562             return id;
563         }
564 
load(Context context, int resId, int priority)565         public int load(Context context, int resId, int priority) {
566             AssetFileDescriptor afd = context.getResources().openRawResourceFd(resId);
567             int id = 0;
568             if (afd != null) {
569                 id = _load(afd.getFileDescriptor(), afd.getStartOffset(), afd.getLength(), priority);
570                 try {
571                     afd.close();
572                 } catch (java.io.IOException ex) {
573                     //Log.d(TAG, "close failed:", ex);
574                 }
575             }
576             return id;
577         }
578 
load(AssetFileDescriptor afd, int priority)579         public int load(AssetFileDescriptor afd, int priority) {
580             if (afd != null) {
581                 long len = afd.getLength();
582                 if (len < 0) {
583                     throw new AndroidRuntimeException("no length for fd");
584                 }
585                 return _load(afd.getFileDescriptor(), afd.getStartOffset(), len, priority);
586             } else {
587                 return 0;
588             }
589         }
590 
load(FileDescriptor fd, long offset, long length, int priority)591         public int load(FileDescriptor fd, long offset, long length, int priority) {
592             return _load(fd, offset, length, priority);
593         }
594 
_load(String uri, int priority)595         private native final int _load(String uri, int priority);
596 
_load(FileDescriptor fd, long offset, long length, int priority)597         private native final int _load(FileDescriptor fd, long offset, long length, int priority);
598 
unload(int soundID)599         public native final boolean unload(int soundID);
600 
play(int soundID, float leftVolume, float rightVolume, int priority, int loop, float rate)601         public final int play(int soundID, float leftVolume, float rightVolume,
602                 int priority, int loop, float rate) {
603             if (isRestricted()) {
604                 leftVolume = rightVolume = 0;
605             }
606             return _play(soundID, leftVolume, rightVolume, priority, loop, rate);
607         }
608 
_play(int soundID, float leftVolume, float rightVolume, int priority, int loop, float rate)609         public native final int _play(int soundID, float leftVolume, float rightVolume,
610                 int priority, int loop, float rate);
611 
isRestricted()612         private boolean isRestricted() {
613             try {
614                 final int mode = mAppOps.checkAudioOperation(AppOpsManager.OP_PLAY_AUDIO,
615                         mAttributes.getUsage(),
616                         Process.myUid(), ActivityThread.currentPackageName());
617                 return mode != AppOpsManager.MODE_ALLOWED;
618             } catch (RemoteException e) {
619                 return false;
620             }
621         }
622 
pause(int streamID)623         public native final void pause(int streamID);
624 
resume(int streamID)625         public native final void resume(int streamID);
626 
autoPause()627         public native final void autoPause();
628 
autoResume()629         public native final void autoResume();
630 
stop(int streamID)631         public native final void stop(int streamID);
632 
setVolume(int streamID, float leftVolume, float rightVolume)633         public final void setVolume(int streamID, float leftVolume, float rightVolume) {
634             if (isRestricted()) {
635                 return;
636             }
637             _setVolume(streamID, leftVolume, rightVolume);
638         }
639 
_setVolume(int streamID, float leftVolume, float rightVolume)640         private native final void _setVolume(int streamID, float leftVolume, float rightVolume);
641 
setVolume(int streamID, float volume)642         public void setVolume(int streamID, float volume) {
643             setVolume(streamID, volume, volume);
644         }
645 
setPriority(int streamID, int priority)646         public native final void setPriority(int streamID, int priority);
647 
setLoop(int streamID, int loop)648         public native final void setLoop(int streamID, int loop);
649 
setRate(int streamID, float rate)650         public native final void setRate(int streamID, float rate);
651 
setOnLoadCompleteListener(SoundPool.OnLoadCompleteListener listener)652         public void setOnLoadCompleteListener(SoundPool.OnLoadCompleteListener listener)
653         {
654             synchronized(mLock) {
655                 if (listener != null) {
656                     // setup message handler
657                     Looper looper;
658                     if ((looper = Looper.myLooper()) != null) {
659                         mEventHandler = new EventHandler(mProxy, looper);
660                     } else if ((looper = Looper.getMainLooper()) != null) {
661                         mEventHandler = new EventHandler(mProxy, looper);
662                     } else {
663                         mEventHandler = null;
664                     }
665                 } else {
666                     mEventHandler = null;
667                 }
668                 mOnLoadCompleteListener = listener;
669             }
670         }
671 
672         private class EventHandler extends Handler
673         {
674             private SoundPool mSoundPool;
675 
EventHandler(SoundPool soundPool, Looper looper)676             public EventHandler(SoundPool soundPool, Looper looper) {
677                 super(looper);
678                 mSoundPool = soundPool;
679             }
680 
681             @Override
handleMessage(Message msg)682             public void handleMessage(Message msg) {
683                 switch(msg.what) {
684                 case SAMPLE_LOADED:
685                     if (DEBUG) Log.d(TAG, "Sample " + msg.arg1 + " loaded");
686                     synchronized(mLock) {
687                         if (mOnLoadCompleteListener != null) {
688                             mOnLoadCompleteListener.onLoadComplete(mSoundPool, msg.arg1, msg.arg2);
689                         }
690                     }
691                     break;
692                 default:
693                     Log.e(TAG, "Unknown message type " + msg.what);
694                     return;
695                 }
696             }
697         }
698 
699         // post event from native code to message handler
postEventFromNative(Object weakRef, int msg, int arg1, int arg2, Object obj)700         private static void postEventFromNative(Object weakRef, int msg, int arg1, int arg2, Object obj)
701         {
702             SoundPoolImpl soundPoolImpl = (SoundPoolImpl)((WeakReference)weakRef).get();
703             if (soundPoolImpl == null)
704                 return;
705 
706             if (soundPoolImpl.mEventHandler != null) {
707                 Message m = soundPoolImpl.mEventHandler.obtainMessage(msg, arg1, arg2, obj);
708                 soundPoolImpl.mEventHandler.sendMessage(m);
709             }
710         }
711 
release()712         public native final void release();
713 
native_setup(Object weakRef, int maxStreams, Object attributes)714         private native final int native_setup(Object weakRef, int maxStreams,
715                 Object/*AudioAttributes*/ attributes);
716 
finalize()717         protected void finalize() { release(); }
718     }
719 
720     /**
721      * No-op implementation of SoundPool.
722      * Used when media is disabled by the system.
723      * @hide
724      */
725     /* package */ static class SoundPoolStub implements SoundPoolDelegate {
SoundPoolStub()726         public SoundPoolStub() { }
727 
load(String path, int priority)728         public int load(String path, int priority) {
729             return 0;
730         }
731 
load(Context context, int resId, int priority)732         public int load(Context context, int resId, int priority) {
733             return 0;
734         }
735 
load(AssetFileDescriptor afd, int priority)736         public int load(AssetFileDescriptor afd, int priority) {
737             return 0;
738         }
739 
load(FileDescriptor fd, long offset, long length, int priority)740         public int load(FileDescriptor fd, long offset, long length, int priority) {
741             return 0;
742         }
743 
unload(int soundID)744         public final boolean unload(int soundID) {
745             return true;
746         }
747 
play(int soundID, float leftVolume, float rightVolume, int priority, int loop, float rate)748         public final int play(int soundID, float leftVolume, float rightVolume,
749                 int priority, int loop, float rate) {
750             return 0;
751         }
752 
pause(int streamID)753         public final void pause(int streamID) { }
754 
resume(int streamID)755         public final void resume(int streamID) { }
756 
autoPause()757         public final void autoPause() { }
758 
autoResume()759         public final void autoResume() { }
760 
stop(int streamID)761         public final void stop(int streamID) { }
762 
setVolume(int streamID, float leftVolume, float rightVolume)763         public final void setVolume(int streamID,
764                 float leftVolume, float rightVolume) { }
765 
setVolume(int streamID, float volume)766         public void setVolume(int streamID, float volume) {
767         }
768 
setPriority(int streamID, int priority)769         public final void setPriority(int streamID, int priority) { }
770 
setLoop(int streamID, int loop)771         public final void setLoop(int streamID, int loop) { }
772 
setRate(int streamID, float rate)773         public final void setRate(int streamID, float rate) { }
774 
setOnLoadCompleteListener(SoundPool.OnLoadCompleteListener listener)775         public void setOnLoadCompleteListener(SoundPool.OnLoadCompleteListener listener) {
776         }
777 
release()778         public final void release() { }
779     }
780 }
781