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
17package android.media;
18
19import java.io.File;
20import java.io.FileDescriptor;
21import java.lang.ref.WeakReference;
22
23import android.app.ActivityThread;
24import android.app.AppOpsManager;
25import android.content.Context;
26import android.content.res.AssetFileDescriptor;
27import android.os.Handler;
28import android.os.IBinder;
29import android.os.Looper;
30import android.os.Message;
31import android.os.ParcelFileDescriptor;
32import android.os.Process;
33import android.os.RemoteException;
34import android.os.ServiceManager;
35import android.os.SystemProperties;
36import android.util.AndroidRuntimeException;
37import android.util.Log;
38
39import 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 */
114public 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     */
132    public SoundPool(int maxStreams, int streamType, int srcQuality) {
133        this(maxStreams,
134                new AudioAttributes.Builder().setInternalLegacyStreamType(streamType).build());
135    }
136
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         */
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         */
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         */
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
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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         */
450        public void onLoadComplete(SoundPool soundPool, int sampleId, int status);
451    }
452
453    /**
454     * Sets the callback hook for the OnLoadCompleteListener.
455     */
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     */
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 {
482        public int load(String path, int priority);
483        public int load(Context context, int resId, int priority);
484        public int load(AssetFileDescriptor afd, int priority);
485        public int load(
486                FileDescriptor fd, long offset, long length, int priority);
487        public boolean unload(int soundID);
488        public int play(
489                int soundID, float leftVolume, float rightVolume,
490                int priority, int loop, float rate);
491        public void pause(int streamID);
492        public void resume(int streamID);
493        public void autoPause();
494        public void autoResume();
495        public void stop(int streamID);
496        public void setVolume(int streamID, float leftVolume, float rightVolume);
497        public void setVolume(int streamID, float volume);
498        public void setPriority(int streamID, int priority);
499        public void setLoop(int streamID, int loop);
500        public void setRate(int streamID, float rate);
501        public void setOnLoadCompleteListener(OnLoadCompleteListener listener);
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
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
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
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
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
591        public int load(FileDescriptor fd, long offset, long length, int priority) {
592            return _load(fd, offset, length, priority);
593        }
594
595        private native final int _load(String uri, int priority);
596
597        private native final int _load(FileDescriptor fd, long offset, long length, int priority);
598
599        public native final boolean unload(int soundID);
600
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
609        public native final int _play(int soundID, float leftVolume, float rightVolume,
610                int priority, int loop, float rate);
611
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
623        public native final void pause(int streamID);
624
625        public native final void resume(int streamID);
626
627        public native final void autoPause();
628
629        public native final void autoResume();
630
631        public native final void stop(int streamID);
632
633        public final void setVolume(int streamID, float leftVolume, float rightVolume) {
634            if (isRestricted()) {
635                return;
636            }
637            _setVolume(streamID, leftVolume, rightVolume);
638        }
639
640        private native final void _setVolume(int streamID, float leftVolume, float rightVolume);
641
642        public void setVolume(int streamID, float volume) {
643            setVolume(streamID, volume, volume);
644        }
645
646        public native final void setPriority(int streamID, int priority);
647
648        public native final void setLoop(int streamID, int loop);
649
650        public native final void setRate(int streamID, float rate);
651
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
676            public EventHandler(SoundPool soundPool, Looper looper) {
677                super(looper);
678                mSoundPool = soundPool;
679            }
680
681            @Override
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
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
712        public native final void release();
713
714        private native final int native_setup(Object weakRef, int maxStreams,
715                Object/*AudioAttributes*/ attributes);
716
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 {
726        public SoundPoolStub() { }
727
728        public int load(String path, int priority) {
729            return 0;
730        }
731
732        public int load(Context context, int resId, int priority) {
733            return 0;
734        }
735
736        public int load(AssetFileDescriptor afd, int priority) {
737            return 0;
738        }
739
740        public int load(FileDescriptor fd, long offset, long length, int priority) {
741            return 0;
742        }
743
744        public final boolean unload(int soundID) {
745            return true;
746        }
747
748        public final int play(int soundID, float leftVolume, float rightVolume,
749                int priority, int loop, float rate) {
750            return 0;
751        }
752
753        public final void pause(int streamID) { }
754
755        public final void resume(int streamID) { }
756
757        public final void autoPause() { }
758
759        public final void autoResume() { }
760
761        public final void stop(int streamID) { }
762
763        public final void setVolume(int streamID,
764                float leftVolume, float rightVolume) { }
765
766        public void setVolume(int streamID, float volume) {
767        }
768
769        public final void setPriority(int streamID, int priority) { }
770
771        public final void setLoop(int streamID, int loop) { }
772
773        public final void setRate(int streamID, float rate) { }
774
775        public void setOnLoadCompleteListener(SoundPool.OnLoadCompleteListener listener) {
776        }
777
778        public final void release() { }
779    }
780}
781