AudioService.java revision 9066cfe9886ac131c34d59ed0e2d287b0e3c0087
1/*
2 * Copyright (C) 2006 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 android.app.ActivityManagerNative;
20import android.content.ContentResolver;
21import android.content.Context;
22import android.content.Intent;
23import android.content.pm.PackageManager;
24import android.media.MediaPlayer.OnCompletionListener;
25import android.media.MediaPlayer.OnErrorListener;
26import android.os.Binder;
27import android.os.Environment;
28import android.os.Handler;
29import android.os.IBinder;
30import android.os.Looper;
31import android.os.Message;
32import android.os.RemoteException;
33import android.os.ServiceManager;
34import android.provider.Settings;
35import android.provider.Settings.System;
36import android.util.Log;
37import android.view.VolumePanel;
38
39import com.android.internal.telephony.ITelephony;
40
41import java.io.IOException;
42import java.util.ArrayList;
43
44
45/**
46 * The implementation of the volume manager service.
47 * <p>
48 * This implementation focuses on delivering a responsive UI. Most methods are
49 * asynchronous to external calls. For example, the task of setting a volume
50 * will update our internal state, but in a separate thread will set the system
51 * volume and later persist to the database. Similarly, setting the ringer mode
52 * will update the state and broadcast a change and in a separate thread later
53 * persist the ringer mode.
54 *
55 * @hide
56 */
57public class AudioService extends IAudioService.Stub {
58
59    private static final String TAG = "AudioService";
60
61    /** How long to delay before persisting a change in volume/ringer mode. */
62    private static final int PERSIST_DELAY = 3000;
63
64    private Context mContext;
65    private ContentResolver mContentResolver;
66
67    /** The UI */
68    private VolumePanel mVolumePanel;
69
70    // sendMsg() flags
71    /** Used when a message should be shared across all stream types. */
72    private static final int SHARED_MSG = -1;
73    /** If the msg is already queued, replace it with this one. */
74    private static final int SENDMSG_REPLACE = 0;
75    /** If the msg is already queued, ignore this one and leave the old. */
76    private static final int SENDMSG_NOOP = 1;
77    /** If the msg is already queued, queue this one and leave the old. */
78    private static final int SENDMSG_QUEUE = 2;
79
80    // AudioHandler message.whats
81    private static final int MSG_SET_SYSTEM_VOLUME = 0;
82    private static final int MSG_PERSIST_VOLUME = 1;
83    private static final int MSG_PERSIST_RINGER_MODE = 3;
84    private static final int MSG_PERSIST_VIBRATE_SETTING = 4;
85    private static final int MSG_MEDIA_SERVER_DIED = 5;
86    private static final int MSG_MEDIA_SERVER_STARTED = 6;
87    private static final int MSG_PLAY_SOUND_EFFECT = 7;
88
89    /** @see AudioSystemThread */
90    private AudioSystemThread mAudioSystemThread;
91    /** @see AudioHandler */
92    private AudioHandler mAudioHandler;
93    /** @see VolumeStreamState */
94    private VolumeStreamState[] mStreamStates;
95
96    private boolean mMicMute;
97    private int mMode;
98    private int[] mRoutes = new int[AudioSystem.NUM_MODES];
99    private Object mSettingsLock = new Object();
100    private boolean mMediaServerOk;
101
102    private SoundPool mSoundPool;
103    private Object mSoundEffectsLock = new Object();
104    private static final int NUM_SOUNDPOOL_CHANNELS = 4;
105    private static final int SOUND_EFFECT_VOLUME = 1000;
106
107    /* Sound effect file names  */
108    private static final String SOUND_EFFECTS_PATH = "/media/audio/ui/";
109    private static final String[] SOUND_EFFECT_FILES = new String[] {
110        "Effect_Tick.ogg",
111        "KeypressStandard.ogg",
112        "KeypressSpacebar.ogg",
113        "KeypressDelete.ogg",
114        "KeypressReturn.ogg"
115    };
116
117    /* Sound effect file name mapping sound effect id (AudioManager.FX_xxx) to
118     * file index in SOUND_EFFECT_FILES[] (first column) and indicating if effect
119     * uses soundpool (second column) */
120    private int[][] SOUND_EFFECT_FILES_MAP = new int[][] {
121        {0, -1},  // FX_KEY_CLICK
122        {0, -1},  // FX_FOCUS_NAVIGATION_UP
123        {0, -1},  // FX_FOCUS_NAVIGATION_DOWN
124        {0, -1},  // FX_FOCUS_NAVIGATION_LEFT
125        {0, -1},  // FX_FOCUS_NAVIGATION_RIGHT
126        {1, -1},  // FX_KEYPRESS_STANDARD
127        {2, -1},  // FX_KEYPRESS_SPACEBAR
128        {3, -1},  // FX_FOCUS_DELETE
129        {4, -1}   // FX_FOCUS_RETURN
130    };
131
132    private AudioSystem.ErrorCallback mAudioSystemCallback = new AudioSystem.ErrorCallback() {
133        public void onError(int error) {
134            switch (error) {
135            case AudioSystem.AUDIO_STATUS_SERVER_DIED:
136                if (mMediaServerOk) {
137                    sendMsg(mAudioHandler, MSG_MEDIA_SERVER_DIED, SHARED_MSG, SENDMSG_NOOP, 0, 0,
138                            null, 1500);
139                }
140                break;
141            case AudioSystem.AUDIO_STATUS_OK:
142                if (!mMediaServerOk) {
143                    sendMsg(mAudioHandler, MSG_MEDIA_SERVER_STARTED, SHARED_MSG, SENDMSG_NOOP, 0, 0,
144                            null, 0);
145                }
146                break;
147            default:
148                break;
149            }
150       }
151    };
152
153    /**
154     * Current ringer mode from one of {@link AudioManager#RINGER_MODE_NORMAL},
155     * {@link AudioManager#RINGER_MODE_SILENT}, or
156     * {@link AudioManager#RINGER_MODE_VIBRATE}.
157     */
158    private int mRingerMode;
159
160    /** @see System#MODE_RINGER_STREAMS_AFFECTED */
161    private int mRingerModeAffectedStreams;
162
163    /** @see System#MUTE_STREAMS_AFFECTED */
164    private int mMuteAffectedStreams;
165
166    /**
167     * Has multiple bits per vibrate type to indicate the type's vibrate
168     * setting. See {@link #setVibrateSetting(int, int)}.
169     * <p>
170     * NOTE: This is not the final decision of whether vibrate is on/off for the
171     * type since it depends on the ringer mode. See {@link #shouldVibrate(int)}.
172     */
173    private int mVibrateSetting;
174
175    ///////////////////////////////////////////////////////////////////////////
176    // Construction
177    ///////////////////////////////////////////////////////////////////////////
178
179    /** @hide */
180    public AudioService(Context context) {
181        mContext = context;
182        mContentResolver = context.getContentResolver();
183        mVolumePanel = new VolumePanel(context, this);
184
185        createAudioSystemThread();
186        createStreamStates();
187        readPersistedSettings();
188        readAudioSettings();
189        mMediaServerOk = true;
190        AudioSystem.setErrorCallback(mAudioSystemCallback);
191        loadSoundEffects();
192    }
193
194    private void createAudioSystemThread() {
195        mAudioSystemThread = new AudioSystemThread();
196        mAudioSystemThread.start();
197        waitForAudioHandlerCreation();
198    }
199
200    /** Waits for the volume handler to be created by the other thread. */
201    private void waitForAudioHandlerCreation() {
202        synchronized(this) {
203            while (mAudioHandler == null) {
204                try {
205                    // Wait for mAudioHandler to be set by the other thread
206                    wait();
207                } catch (InterruptedException e) {
208                    Log.e(TAG, "Interrupted while waiting on volume handler.");
209                }
210            }
211        }
212    }
213
214    private void createStreamStates() {
215        final int[] volumeLevelsPhone =
216            createVolumeLevels(0, AudioManager.MAX_STREAM_VOLUME[AudioManager.STREAM_VOICE_CALL]);
217        final int[] volumeLevelsCoarse =
218            createVolumeLevels(0, AudioManager.MAX_STREAM_VOLUME[AudioManager.STREAM_SYSTEM]);
219        final int[] volumeLevelsFine =
220            createVolumeLevels(0, AudioManager.MAX_STREAM_VOLUME[AudioManager.STREAM_MUSIC]);
221        final int[] volumeLevelsBtPhone =
222            createVolumeLevels(0,
223                    AudioManager.MAX_STREAM_VOLUME[AudioManager.STREAM_BLUETOOTH_SCO]);
224
225        int numStreamTypes = AudioSystem.getNumStreamTypes();
226        VolumeStreamState[] streams = mStreamStates = new VolumeStreamState[numStreamTypes];
227
228        for (int i = 0; i < numStreamTypes; i++) {
229            final int[] levels;
230
231            switch (i) {
232
233                case AudioSystem.STREAM_MUSIC:
234                    levels = volumeLevelsFine;
235                    break;
236
237                case AudioSystem.STREAM_VOICE_CALL:
238                    levels = volumeLevelsPhone;
239                    break;
240
241                case AudioSystem.STREAM_BLUETOOTH_SCO:
242                    levels = volumeLevelsBtPhone;
243                    break;
244
245                default:
246                    levels = volumeLevelsCoarse;
247                    break;
248            }
249
250            if (i == AudioSystem.STREAM_BLUETOOTH_SCO) {
251                streams[i] = new VolumeStreamState(AudioManager.DEFAULT_STREAM_VOLUME[i], i,levels);
252            } else {
253                streams[i] = new VolumeStreamState(System.VOLUME_SETTINGS[i], i, levels);
254            }
255        }
256    }
257
258    private static int[] createVolumeLevels(int offset, int numlevels) {
259        double curve = 1.0f; // 1.4f
260        int [] volumes = new int[numlevels + offset];
261        for (int i = 0; i < offset; i++) {
262            volumes[i] = 0;
263        }
264
265        double val = 0;
266        double max = Math.pow(numlevels - 1, curve);
267        for (int i = 0; i < numlevels; i++) {
268            val = Math.pow(i, curve) / max;
269            volumes[offset + i] = (int) (val * 100.0f);
270        }
271        return volumes;
272    }
273
274    private void readPersistedSettings() {
275        final ContentResolver cr = mContentResolver;
276
277        mRingerMode = System.getInt(cr, System.MODE_RINGER, AudioManager.RINGER_MODE_NORMAL);
278        mRingerModeAffectedStreams = System.getInt(mContentResolver,
279                System.MODE_RINGER_STREAMS_AFFECTED, 1 << AudioSystem.STREAM_RING);
280
281        mVibrateSetting = System.getInt(cr, System.VIBRATE_ON, 0);
282
283        mMuteAffectedStreams = System.getInt(cr,
284                System.MUTE_STREAMS_AFFECTED,
285                ((1 << AudioSystem.STREAM_MUSIC)|(1 << AudioSystem.STREAM_RING)|(1 << AudioSystem.STREAM_SYSTEM)));
286
287        // Each stream will read its own persisted settings
288
289        // Broadcast the sticky intent
290        broadcastRingerMode();
291
292        // Broadcast vibrate settings
293        broadcastVibrateSetting(AudioManager.VIBRATE_TYPE_RINGER);
294        broadcastVibrateSetting(AudioManager.VIBRATE_TYPE_NOTIFICATION);
295    }
296
297    private void readAudioSettings() {
298        synchronized (mSettingsLock) {
299            mMicMute = AudioSystem.isMicrophoneMuted();
300            mMode = AudioSystem.getMode();
301            for (int mode = 0; mode < AudioSystem.NUM_MODES; mode++) {
302                mRoutes[mode] = AudioSystem.getRouting(mode);
303            }
304        }
305    }
306
307    private void applyAudioSettings() {
308        synchronized (mSettingsLock) {
309            AudioSystem.muteMicrophone(mMicMute);
310            AudioSystem.setMode(mMode);
311            for (int mode = 0; mode < AudioSystem.NUM_MODES; mode++) {
312                AudioSystem.setRouting(mode, mRoutes[mode], AudioSystem.ROUTE_ALL);
313            }
314        }
315   }
316
317    ///////////////////////////////////////////////////////////////////////////
318    // IPC methods
319    ///////////////////////////////////////////////////////////////////////////
320
321    /** @see AudioManager#adjustVolume(int, int) */
322    public void adjustVolume(int direction, int flags) {
323        adjustSuggestedStreamVolume(direction, AudioManager.USE_DEFAULT_STREAM_TYPE, flags);
324    }
325
326    /** @see AudioManager#adjustVolume(int, int, int) */
327    public void adjustSuggestedStreamVolume(int direction, int suggestedStreamType, int flags) {
328
329        int streamType = getActiveStreamType(suggestedStreamType);
330
331        // Don't play sound on other streams
332        if (streamType != AudioSystem.STREAM_RING && (flags & AudioManager.FLAG_PLAY_SOUND) != 0) {
333            flags &= ~AudioManager.FLAG_PLAY_SOUND;
334        }
335
336        adjustStreamVolume(streamType, direction, flags);
337    }
338
339    /** @see AudioManager#adjustStreamVolume(int, int, int) */
340    public void adjustStreamVolume(int streamType, int direction, int flags) {
341        ensureValidDirection(direction);
342        ensureValidStreamType(streamType);
343
344        boolean notificationsUseRingVolume = Settings.System.getInt(mContentResolver,
345                Settings.System.NOTIFICATIONS_USE_RING_VOLUME, 1) == 1;
346        if (notificationsUseRingVolume && streamType == AudioManager.STREAM_NOTIFICATION) {
347            // Redirect the volume change to the ring stream
348            streamType = AudioManager.STREAM_RING;
349        }
350
351        VolumeStreamState streamState = mStreamStates[streamType];
352        final int oldIndex = streamState.mIndex;
353        boolean adjustVolume = true;
354
355        // If either the client forces allowing ringer modes for this adjustment,
356        // or the stream type is one that is affected by ringer modes
357        if ((flags & AudioManager.FLAG_ALLOW_RINGER_MODES) != 0
358                || streamType == AudioManager.STREAM_RING) {
359            // Check if the ringer mode changes with this volume adjustment. If
360            // it does, it will handle adjusting the volume, so we won't below
361            adjustVolume = checkForRingerModeChange(oldIndex, direction);
362        }
363
364        if (adjustVolume && streamState.adjustIndex(direction)) {
365
366            boolean alsoUpdateNotificationVolume =  notificationsUseRingVolume &&
367                    streamType == AudioManager.STREAM_RING;
368            if (alsoUpdateNotificationVolume) {
369                mStreamStates[AudioManager.STREAM_NOTIFICATION].adjustIndex(direction);
370            }
371
372            // Post message to set system volume (it in turn will post a message
373            // to persist). Do not change volume if stream is muted.
374            if (streamState.muteCount() == 0) {
375                sendMsg(mAudioHandler, MSG_SET_SYSTEM_VOLUME, streamType, SENDMSG_NOOP, 0, 0,
376                        streamState, 0);
377
378                if (alsoUpdateNotificationVolume) {
379                    sendMsg(mAudioHandler, MSG_SET_SYSTEM_VOLUME, AudioManager.STREAM_NOTIFICATION,
380                            SENDMSG_NOOP, 0, 0, mStreamStates[AudioManager.STREAM_NOTIFICATION], 0);
381                }
382            }
383        }
384
385        // UI
386        mVolumePanel.postVolumeChanged(streamType, flags);
387        // Broadcast Intent
388        sendVolumeUpdate(streamType);
389    }
390
391    /** @see AudioManager#setStreamVolume(int, int, int) */
392    public void setStreamVolume(int streamType, int index, int flags) {
393        ensureValidStreamType(streamType);
394        syncRingerAndNotificationStreamVolume(streamType, index, false);
395
396        setStreamVolumeInt(streamType, index, false);
397
398        // UI, etc.
399        mVolumePanel.postVolumeChanged(streamType, flags);
400        // Broadcast Intent
401        sendVolumeUpdate(streamType);
402    }
403
404    private void sendVolumeUpdate(int streamType) {
405        Intent intent = new Intent(AudioManager.VOLUME_CHANGED_ACTION);
406        intent.putExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, streamType);
407        intent.putExtra(AudioManager.EXTRA_VOLUME_STREAM_VALUE, getStreamVolume(streamType));
408
409        // Currently, sending the intent only when the stream is BLUETOOTH_SCO
410        if (streamType == AudioManager.STREAM_BLUETOOTH_SCO) {
411            mContext.sendBroadcast(intent);
412        }
413    }
414
415    /**
416     * Sync the STREAM_RING and STREAM_NOTIFICATION volumes if mandated by the
417     * value in Settings.
418     *
419     * @param streamType Type of the stream
420     * @param index Volume index for the stream
421     * @param force If true, set the volume even if the current and desired
422     * volume as same
423     */
424    private void syncRingerAndNotificationStreamVolume(int streamType, int index, boolean force) {
425        boolean notificationsUseRingVolume = Settings.System.getInt(mContentResolver,
426                Settings.System.NOTIFICATIONS_USE_RING_VOLUME, 1) == 1;
427        if (notificationsUseRingVolume) {
428            if (streamType == AudioManager.STREAM_NOTIFICATION) {
429                // Redirect the volume change to the ring stream
430                streamType = AudioManager.STREAM_RING;
431            }
432            if (streamType == AudioManager.STREAM_RING) {
433                // One-off to sync notification volume to ringer volume
434                setStreamVolumeInt(AudioManager.STREAM_NOTIFICATION, index, force);
435            }
436        }
437    }
438
439
440    /**
441     * Sets the stream state's index, and posts a message to set system volume.
442     * This will not call out to the UI. Assumes a valid stream type.
443     *
444     * @param streamType Type of the stream
445     * @param index Desired volume index of the stream
446     * @param force If true, set the volume even if the desired volume is same
447     * as the current volume.
448     */
449    private void setStreamVolumeInt(int streamType, int index, boolean force) {
450        VolumeStreamState streamState = mStreamStates[streamType];
451        if (streamState.setIndex(index) || force) {
452            // Post message to set system volume (it in turn will post a message
453            // to persist). Do not change volume if stream is muted.
454            if (streamState.muteCount() == 0) {
455                sendMsg(mAudioHandler, MSG_SET_SYSTEM_VOLUME, streamType, SENDMSG_NOOP, 0, 0,
456                        streamState, 0);
457            }
458        }
459    }
460
461    /** @see AudioManager#setStreamSolo(int, boolean) */
462    public void setStreamSolo(int streamType, boolean state, IBinder cb) {
463        for (int stream = 0; stream < mStreamStates.length; stream++) {
464            if (!isStreamAffectedByMute(stream) || stream == streamType) continue;
465            // Bring back last audible volume
466            mStreamStates[stream].mute(cb, state);
467         }
468    }
469
470    /** @see AudioManager#setStreamMute(int, boolean) */
471    public void setStreamMute(int streamType, boolean state, IBinder cb) {
472        if (isStreamAffectedByMute(streamType)) {
473            mStreamStates[streamType].mute(cb, state);
474        }
475    }
476
477    /** @see AudioManager#getStreamVolume(int) */
478    public int getStreamVolume(int streamType) {
479        ensureValidStreamType(streamType);
480        return mStreamStates[streamType].mIndex;
481    }
482
483    /** @see AudioManager#getStreamMaxVolume(int) */
484    public int getStreamMaxVolume(int streamType) {
485        ensureValidStreamType(streamType);
486        return mStreamStates[streamType].getMaxIndex();
487    }
488
489    /** @see AudioManager#getRingerMode() */
490    public int getRingerMode() {
491        return mRingerMode;
492    }
493
494    /** @see AudioManager#setRingerMode(int) */
495    public void setRingerMode(int ringerMode) {
496        if (ringerMode != mRingerMode) {
497            mRingerMode = ringerMode;
498
499            // Adjust volumes via posting message
500            int numStreamTypes = AudioSystem.getNumStreamTypes();
501            if (mRingerMode == AudioManager.RINGER_MODE_NORMAL) {
502                for (int streamType = numStreamTypes - 1; streamType >= 0; streamType--) {
503                    if (!isStreamAffectedByRingerMode(streamType)) continue;
504                    // Bring back last audible volume
505                    setStreamVolumeInt(streamType, mStreamStates[streamType].mLastAudibleIndex,
506                                       false);
507                }
508            } else {
509                for (int streamType = numStreamTypes - 1; streamType >= 0; streamType--) {
510                    if (!isStreamAffectedByRingerMode(streamType)) continue;
511                    // Either silent or vibrate, either way volume is 0
512                    setStreamVolumeInt(streamType, 0, false);
513                }
514            }
515
516            // Send sticky broadcast
517            broadcastRingerMode();
518
519            // Post a persist ringer mode msg
520            sendMsg(mAudioHandler, MSG_PERSIST_RINGER_MODE, SHARED_MSG,
521                    SENDMSG_REPLACE, 0, 0, null, PERSIST_DELAY);
522        }
523    }
524
525    /** @see AudioManager#shouldVibrate(int) */
526    public boolean shouldVibrate(int vibrateType) {
527
528        switch (getVibrateSetting(vibrateType)) {
529
530            case AudioManager.VIBRATE_SETTING_ON:
531                return mRingerMode != AudioManager.RINGER_MODE_SILENT;
532
533            case AudioManager.VIBRATE_SETTING_ONLY_SILENT:
534                return mRingerMode == AudioManager.RINGER_MODE_VIBRATE;
535
536            case AudioManager.VIBRATE_SETTING_OFF:
537                // Phone ringer should always vibrate in vibrate mode
538                if (vibrateType == AudioManager.VIBRATE_TYPE_RINGER) {
539                    return mRingerMode == AudioManager.RINGER_MODE_VIBRATE;
540                }
541
542            default:
543                return false;
544        }
545    }
546
547    /** @see AudioManager#getVibrateSetting(int) */
548    public int getVibrateSetting(int vibrateType) {
549        return (mVibrateSetting >> (vibrateType * 2)) & 3;
550    }
551
552    /** @see AudioManager#setVibrateSetting(int, int) */
553    public void setVibrateSetting(int vibrateType, int vibrateSetting) {
554
555        mVibrateSetting = getValueForVibrateSetting(mVibrateSetting, vibrateType, vibrateSetting);
556
557        // Broadcast change
558        broadcastVibrateSetting(vibrateType);
559
560        // Post message to set ringer mode (it in turn will post a message
561        // to persist)
562        sendMsg(mAudioHandler, MSG_PERSIST_VIBRATE_SETTING, SHARED_MSG, SENDMSG_NOOP, 0, 0,
563                null, 0);
564    }
565
566    /**
567     * @see #setVibrateSetting(int, int)
568     * @hide
569     */
570    public static int getValueForVibrateSetting(int existingValue, int vibrateType,
571            int vibrateSetting) {
572
573        // First clear the existing setting. Each vibrate type has two bits in
574        // the value. Note '3' is '11' in binary.
575        existingValue &= ~(3 << (vibrateType * 2));
576
577        // Set into the old value
578        existingValue |= (vibrateSetting & 3) << (vibrateType * 2);
579
580        return existingValue;
581    }
582
583    /** @see AudioManager#setMicrophoneMute(boolean) */
584    public void setMicrophoneMute(boolean on) {
585        if (!checkAudioSettingsPermission("setMicrophoneMute()")) {
586            return;
587        }
588        synchronized (mSettingsLock) {
589            if (on != mMicMute) {
590                AudioSystem.muteMicrophone(on);
591                mMicMute = on;
592            }
593        }
594    }
595
596    /** @see AudioManager#isMicrophoneMute() */
597    public boolean isMicrophoneMute() {
598        return mMicMute;
599    }
600
601    /** @see AudioManager#setMode(int) */
602    public void setMode(int mode) {
603        if (!checkAudioSettingsPermission("setMode()")) {
604            return;
605        }
606        synchronized (mSettingsLock) {
607            if (mode != mMode) {
608                AudioSystem.setMode(mode);
609                mMode = mode;
610            }
611            int streamType = getActiveStreamType(AudioManager.USE_DEFAULT_STREAM_TYPE);
612            int index = mStreamStates[streamType].mIndex;
613            syncRingerAndNotificationStreamVolume(streamType, index, true);
614            setStreamVolumeInt(streamType, index, true);
615        }
616    }
617
618    /** @see AudioManager#getMode() */
619    public int getMode() {
620        return mMode;
621    }
622
623    /** @see AudioManager#setRouting(int, int, int) */
624    public void setRouting(int mode, int routes, int mask) {
625        if (!checkAudioSettingsPermission("setRouting()")) {
626            return;
627        }
628        synchronized (mSettingsLock) {
629            if ((mRoutes[mode] & mask) != (routes & mask)) {
630                AudioSystem.setRouting(mode, routes, mask);
631                mRoutes[mode] = (mRoutes[mode] & ~mask) | (routes & mask);
632            }
633            int streamType = getActiveStreamType(AudioManager.USE_DEFAULT_STREAM_TYPE);
634            int index = mStreamStates[streamType].mIndex;
635            syncRingerAndNotificationStreamVolume(streamType, index, true);
636            setStreamVolumeInt(streamType, index, true);
637        }
638    }
639
640    /** @see AudioManager#getRouting(int) */
641    public int getRouting(int mode) {
642        return mRoutes[mode];
643    }
644
645    /** @see AudioManager#isMusicActive() */
646    public boolean isMusicActive() {
647        return AudioSystem.isMusicActive();
648    }
649
650    /** @see AudioManager#setParameter(String, String) */
651    public void setParameter(String key, String value) {
652        AudioSystem.setParameter(key, value);
653    }
654
655    /** @see AudioManager#playSoundEffect(int) */
656    public void playSoundEffect(int effectType) {
657        sendMsg(mAudioHandler, MSG_PLAY_SOUND_EFFECT, SHARED_MSG, SENDMSG_NOOP,
658                effectType, SOUND_EFFECT_VOLUME, null, 0);
659    }
660
661    /** @see AudioManager#playSoundEffect(int, float) */
662    /* @hide FIXME: unhide before release */
663    public void playSoundEffectVolume(int effectType, float volume) {
664        sendMsg(mAudioHandler, MSG_PLAY_SOUND_EFFECT, SHARED_MSG, SENDMSG_NOOP,
665                effectType, (int) (volume * 1000), null, 0);
666    }
667
668    /**
669     * Loads samples into the soundpool.
670     * This method must be called at when sound effects are enabled
671     */
672    public boolean loadSoundEffects() {
673        synchronized (mSoundEffectsLock) {
674            mSoundPool = new SoundPool(NUM_SOUNDPOOL_CHANNELS, AudioSystem.STREAM_SYSTEM, 0);
675            if (mSoundPool == null) {
676                return false;
677            }
678            /*
679             * poolId table: The value -1 in this table indicates that corresponding
680             * file (same index in SOUND_EFFECT_FILES[] has not been loaded.
681             * Once loaded, the value in poolId is the sample ID and the same
682             * sample can be reused for another effect using the same file.
683             */
684            int[] poolId = new int[SOUND_EFFECT_FILES.length];
685            for (int fileIdx = 0; fileIdx < SOUND_EFFECT_FILES.length; fileIdx++) {
686                poolId[fileIdx] = -1;
687            }
688            /*
689             * Effects whose value in SOUND_EFFECT_FILES_MAP[effect][1] is -1 must be loaded.
690             * If load succeeds, value in SOUND_EFFECT_FILES_MAP[effect][1] is > 0:
691             * this indicates we have a valid sample loaded for this effect.
692             */
693            for (int effect = 0; effect < AudioManager.NUM_SOUND_EFFECTS; effect++) {
694                // Do not load sample if this effect uses the MediaPlayer
695                if (SOUND_EFFECT_FILES_MAP[effect][1] == 0) {
696                    continue;
697                }
698                if (poolId[SOUND_EFFECT_FILES_MAP[effect][0]] == -1) {
699                    String filePath = Environment.getRootDirectory() + SOUND_EFFECTS_PATH + SOUND_EFFECT_FILES[SOUND_EFFECT_FILES_MAP[effect][0]];
700                    int sampleId = mSoundPool.load(filePath, 0);
701                    SOUND_EFFECT_FILES_MAP[effect][1] = sampleId;
702                    poolId[SOUND_EFFECT_FILES_MAP[effect][0]] = sampleId;
703                    if (sampleId <= 0) {
704                        Log.w(TAG, "Soundpool could not load file: "+filePath);
705                    }
706                } else {
707                    SOUND_EFFECT_FILES_MAP[effect][1] = poolId[SOUND_EFFECT_FILES_MAP[effect][0]];
708                }
709            }
710        }
711
712        return true;
713    }
714
715    /**
716     *  Unloads samples from the sound pool.
717     *  This method can be called to free some memory when
718     *  sound effects are disabled.
719     */
720    public void unloadSoundEffects() {
721        synchronized (mSoundEffectsLock) {
722            if (mSoundPool == null) {
723                return;
724            }
725            int[] poolId = new int[SOUND_EFFECT_FILES.length];
726            for (int fileIdx = 0; fileIdx < SOUND_EFFECT_FILES.length; fileIdx++) {
727                poolId[fileIdx] = 0;
728            }
729
730            for (int effect = 0; effect < AudioManager.NUM_SOUND_EFFECTS; effect++) {
731                if (SOUND_EFFECT_FILES_MAP[effect][1] <= 0) {
732                    continue;
733                }
734                if (poolId[SOUND_EFFECT_FILES_MAP[effect][0]] == 0) {
735                    mSoundPool.unload(SOUND_EFFECT_FILES_MAP[effect][1]);
736                    SOUND_EFFECT_FILES_MAP[effect][1] = -1;
737                    poolId[SOUND_EFFECT_FILES_MAP[effect][0]] = -1;
738                }
739            }
740            mSoundPool = null;
741        }
742    }
743
744    ///////////////////////////////////////////////////////////////////////////
745    // Internal methods
746    ///////////////////////////////////////////////////////////////////////////
747
748    /**
749     * Checks if the adjustment should change ringer mode instead of just
750     * adjusting volume. If so, this will set the proper ringer mode and volume
751     * indices on the stream states.
752     */
753    private boolean checkForRingerModeChange(int oldIndex, int direction) {
754        boolean adjustVolumeIndex = true;
755        int newRingerMode = mRingerMode;
756
757        if (mRingerMode == AudioManager.RINGER_MODE_NORMAL && oldIndex == 1
758                && direction == AudioManager.ADJUST_LOWER) {
759            newRingerMode = AudioManager.RINGER_MODE_VIBRATE;
760        } else if (mRingerMode == AudioManager.RINGER_MODE_VIBRATE) {
761            if (direction == AudioManager.ADJUST_RAISE) {
762                newRingerMode = AudioManager.RINGER_MODE_NORMAL;
763            } else if (direction == AudioManager.ADJUST_LOWER) {
764                newRingerMode = AudioManager.RINGER_MODE_SILENT;
765            }
766        } else if (direction == AudioManager.ADJUST_RAISE
767                && mRingerMode == AudioManager.RINGER_MODE_SILENT) {
768            newRingerMode = AudioManager.RINGER_MODE_VIBRATE;
769        }
770
771        if (newRingerMode != mRingerMode) {
772            setRingerMode(newRingerMode);
773
774            /*
775             * If we are changing ringer modes, do not increment/decrement the
776             * volume index. Instead, the handler for the message above will
777             * take care of changing the index.
778             */
779            adjustVolumeIndex = false;
780        }
781
782        return adjustVolumeIndex;
783    }
784
785    public boolean isStreamAffectedByRingerMode(int streamType) {
786        return (mRingerModeAffectedStreams & (1 << streamType)) != 0;
787    }
788
789    public boolean isStreamAffectedByMute(int streamType) {
790        return (mMuteAffectedStreams & (1 << streamType)) != 0;
791    }
792
793    private void ensureValidDirection(int direction) {
794        if (direction < AudioManager.ADJUST_LOWER || direction > AudioManager.ADJUST_RAISE) {
795            throw new IllegalArgumentException("Bad direction " + direction);
796        }
797    }
798
799    private void ensureValidStreamType(int streamType) {
800        if (streamType < 0 || streamType >= mStreamStates.length) {
801            throw new IllegalArgumentException("Bad stream type " + streamType);
802        }
803    }
804
805    private int getActiveStreamType(int suggestedStreamType) {
806        boolean isOffhook = false;
807        try {
808            ITelephony phone = ITelephony.Stub.asInterface(ServiceManager.checkService("phone"));
809            if (phone != null) isOffhook = phone.isOffhook();
810        } catch (RemoteException e) {
811            Log.w(TAG, "Couldn't connect to phone service", e);
812        }
813
814        if ((getRouting(AudioSystem.MODE_IN_CALL) & AudioSystem.ROUTE_BLUETOOTH_SCO) != 0) {
815            // Log.v(TAG, "getActiveStreamType: Forcing STREAM_BLUETOOTH_SCO...");
816            return AudioSystem.STREAM_BLUETOOTH_SCO;
817        } else if (isOffhook) {
818            // Log.v(TAG, "getActiveStreamType: Forcing STREAM_VOICE_CALL...");
819            return AudioSystem.STREAM_VOICE_CALL;
820        } else if (AudioSystem.isMusicActive()) {
821            // Log.v(TAG, "getActiveStreamType: Forcing STREAM_MUSIC...");
822            return AudioSystem.STREAM_MUSIC;
823        } else if (suggestedStreamType == AudioManager.USE_DEFAULT_STREAM_TYPE) {
824            // Log.v(TAG, "getActiveStreamType: Forcing STREAM_RING...");
825            return AudioSystem.STREAM_RING;
826        } else {
827            // Log.v(TAG, "getActiveStreamType: Returning suggested type " + suggestedStreamType);
828            return suggestedStreamType;
829        }
830    }
831
832    private void broadcastRingerMode() {
833        // Send sticky broadcast
834        if (ActivityManagerNative.isSystemReady()) {
835            Intent broadcast = new Intent(AudioManager.RINGER_MODE_CHANGED_ACTION);
836            broadcast.putExtra(AudioManager.EXTRA_RINGER_MODE, mRingerMode);
837            long origCallerIdentityToken = Binder.clearCallingIdentity();
838            mContext.sendStickyBroadcast(broadcast);
839            Binder.restoreCallingIdentity(origCallerIdentityToken);
840        }
841    }
842
843    private void broadcastVibrateSetting(int vibrateType) {
844        // Send broadcast
845        if (ActivityManagerNative.isSystemReady()) {
846            Intent broadcast = new Intent(AudioManager.VIBRATE_SETTING_CHANGED_ACTION);
847            broadcast.putExtra(AudioManager.EXTRA_VIBRATE_TYPE, vibrateType);
848            broadcast.putExtra(AudioManager.EXTRA_VIBRATE_SETTING, getVibrateSetting(vibrateType));
849            mContext.sendBroadcast(broadcast);
850        }
851    }
852
853    // Message helper methods
854    private static int getMsg(int baseMsg, int streamType) {
855        return (baseMsg & 0xffff) | streamType << 16;
856    }
857
858    private static int getMsgBase(int msg) {
859        return msg & 0xffff;
860    }
861
862    private static void sendMsg(Handler handler, int baseMsg, int streamType,
863            int existingMsgPolicy, int arg1, int arg2, Object obj, int delay) {
864        int msg = (streamType == SHARED_MSG) ? baseMsg : getMsg(baseMsg, streamType);
865
866        if (existingMsgPolicy == SENDMSG_REPLACE) {
867            handler.removeMessages(msg);
868        } else if (existingMsgPolicy == SENDMSG_NOOP && handler.hasMessages(msg)) {
869            return;
870        }
871
872        handler
873                .sendMessageDelayed(handler.obtainMessage(msg, arg1, arg2, obj), delay);
874    }
875
876    boolean checkAudioSettingsPermission(String method) {
877        if (mContext.checkCallingOrSelfPermission("android.permission.MODIFY_AUDIO_SETTINGS")
878                == PackageManager.PERMISSION_GRANTED) {
879            return true;
880        }
881        String msg = "Audio Settings Permission Denial: " + method + " from pid="
882                + Binder.getCallingPid()
883                + ", uid=" + Binder.getCallingUid();
884        Log.w(TAG, msg);
885        return false;
886    }
887
888
889    ///////////////////////////////////////////////////////////////////////////
890    // Inner classes
891    ///////////////////////////////////////////////////////////////////////////
892
893    public class VolumeStreamState {
894        private final String mVolumeIndexSettingName;
895        private final String mLastAudibleVolumeIndexSettingName;
896        private final int mStreamType;
897
898        private final int[] mVolumes;
899        private int mIndex;
900        private int mLastAudibleIndex;
901        private ArrayList<VolumeDeathHandler> mDeathHandlers; //handles mute/solo requests client death
902
903        private VolumeStreamState(String settingName, int streamType, int[] volumes) {
904
905            mVolumeIndexSettingName = settingName;
906            mLastAudibleVolumeIndexSettingName = settingName + System.APPEND_FOR_LAST_AUDIBLE;
907
908            mStreamType = streamType;
909            mVolumes = volumes;
910
911            final ContentResolver cr = mContentResolver;
912            mIndex = getValidIndex(Settings.System.getInt(cr, mVolumeIndexSettingName, AudioManager.DEFAULT_STREAM_VOLUME[streamType]));
913            mLastAudibleIndex = getValidIndex(Settings.System.getInt(cr,
914                    mLastAudibleVolumeIndexSettingName, mIndex > 0 ? mIndex : AudioManager.DEFAULT_STREAM_VOLUME[streamType]));
915
916            AudioSystem.setVolume(streamType, volumes[mIndex]);
917            mDeathHandlers = new ArrayList<VolumeDeathHandler>();
918        }
919
920        /**
921         * Constructor to be used when there is no setting associated with the VolumeStreamState.
922         *
923         * @param defaultVolume Default volume of the stream to use.
924         * @param streamType Type of the stream.
925         * @param volumes Volumes levels associated with this stream.
926         */
927        private VolumeStreamState(int defaultVolume, int streamType, int[] volumes) {
928            mVolumeIndexSettingName = null;
929            mLastAudibleVolumeIndexSettingName = null;
930            mIndex = mLastAudibleIndex = defaultVolume;
931            mStreamType = streamType;
932            mVolumes = volumes;
933            AudioSystem.setVolume(mStreamType, defaultVolume);
934            mDeathHandlers = new ArrayList<VolumeDeathHandler>();
935        }
936
937        public boolean adjustIndex(int deltaIndex) {
938            return setIndex(mIndex + deltaIndex);
939        }
940
941        public boolean setIndex(int index) {
942            int oldIndex = mIndex;
943            mIndex = getValidIndex(index);
944
945            if (oldIndex != mIndex) {
946                if (mIndex > 0) {
947                    mLastAudibleIndex = mIndex;
948                }
949                return true;
950            } else {
951                return false;
952            }
953        }
954
955        public int getMaxIndex() {
956            return mVolumes.length - 1;
957        }
958
959        public void mute(IBinder cb, boolean state) {
960            VolumeDeathHandler handler = getDeathHandler(cb, state);
961            if (handler == null) {
962                Log.e(TAG, "Could not get client death handler for stream: "+mStreamType);
963                return;
964            }
965            handler.mute(state);
966        }
967
968        private int getValidIndex(int index) {
969            if (index < 0) {
970                return 0;
971            } else if (index >= mVolumes.length) {
972                return mVolumes.length - 1;
973            }
974
975            return index;
976        }
977
978        private class VolumeDeathHandler implements IBinder.DeathRecipient {
979            private IBinder mICallback; // To be notified of client's death
980            private int mMuteCount; // Number of active mutes for this client
981
982            VolumeDeathHandler(IBinder cb) {
983                mICallback = cb;
984            }
985
986            public void mute(boolean state) {
987                synchronized(mDeathHandlers) {
988                    if (state) {
989                        if (mMuteCount == 0) {
990                            // Register for client death notification
991                            try {
992                                mICallback.linkToDeath(this, 0);
993                                mDeathHandlers.add(this);
994                                // If the stream is not yet muted by any client, set lvel to 0
995                                if (muteCount() == 0) {
996                                    setIndex(0);
997                                    sendMsg(mAudioHandler, MSG_SET_SYSTEM_VOLUME, mStreamType, SENDMSG_NOOP, 0, 0,
998                                            VolumeStreamState.this, 0);
999                                }
1000                            } catch (RemoteException e) {
1001                                // Client has died!
1002                                binderDied();
1003                                mDeathHandlers.notify();
1004                                return;
1005                            }
1006                        } else {
1007                            Log.w(TAG, "stream: "+mStreamType+" was already muted by this client");
1008                        }
1009                        mMuteCount++;
1010                    } else {
1011                        if (mMuteCount == 0) {
1012                            Log.e(TAG, "unexpected unmute for stream: "+mStreamType);
1013                        } else {
1014                            mMuteCount--;
1015                            if (mMuteCount == 0) {
1016                                // Unregistr from client death notification
1017                                mDeathHandlers.remove(this);
1018                                mICallback.unlinkToDeath(this, 0);
1019                                if (muteCount() == 0) {
1020                                    // If the stream is not mut any more, restore it's volume if
1021                                    // ringer mode allows it
1022                                    if (!isStreamAffectedByRingerMode(mStreamType) || mRingerMode == AudioManager.RINGER_MODE_NORMAL) {
1023                                        setIndex(mLastAudibleIndex);
1024                                        sendMsg(mAudioHandler, MSG_SET_SYSTEM_VOLUME, mStreamType, SENDMSG_NOOP, 0, 0,
1025                                                VolumeStreamState.this, 0);
1026                                    }
1027                                }
1028                            }
1029                        }
1030                    }
1031                    mDeathHandlers.notify();
1032                }
1033            }
1034
1035            public void binderDied() {
1036                Log.w(TAG, "Volume service client died for stream: "+mStreamType);
1037                if (mMuteCount != 0) {
1038                    // Reset all active mute requests from this client.
1039                    mMuteCount = 1;
1040                    mute(false);
1041                }
1042            }
1043        }
1044
1045        private int muteCount() {
1046            int count = 0;
1047            int size = mDeathHandlers.size();
1048            for (int i = 0; i < size; i++) {
1049                count += mDeathHandlers.get(i).mMuteCount;
1050            }
1051            return count;
1052        }
1053
1054        private VolumeDeathHandler getDeathHandler(IBinder cb, boolean state) {
1055            synchronized(mDeathHandlers) {
1056                VolumeDeathHandler handler;
1057                int size = mDeathHandlers.size();
1058                for (int i = 0; i < size; i++) {
1059                    handler = mDeathHandlers.get(i);
1060                    if (cb.equals(handler.mICallback)) {
1061                        return handler;
1062                    }
1063                }
1064                // If this is the first mute request for this client, create a new
1065                // client death handler. Otherwise, it is an out of sequence unmute request.
1066                if (state) {
1067                    handler = new VolumeDeathHandler(cb);
1068                } else {
1069                    Log.w(TAG, "stream was not muted by this client");
1070                    handler = null;
1071                }
1072                return handler;
1073            }
1074        }
1075    }
1076
1077    /** Thread that handles native AudioSystem control. */
1078    private class AudioSystemThread extends Thread {
1079        AudioSystemThread() {
1080            super("AudioService");
1081        }
1082
1083        @Override
1084        public void run() {
1085            // Set this thread up so the handler will work on it
1086            Looper.prepare();
1087
1088            synchronized(AudioService.this) {
1089                mAudioHandler = new AudioHandler();
1090
1091                // Notify that the handler has been created
1092                AudioService.this.notify();
1093            }
1094
1095            // Listen for volume change requests that are set by VolumePanel
1096            Looper.loop();
1097        }
1098    }
1099
1100    /** Handles internal volume messages in separate volume thread. */
1101    private class AudioHandler extends Handler {
1102
1103        private void setSystemVolume(VolumeStreamState streamState) {
1104
1105            // Adjust volume
1106            AudioSystem
1107                    .setVolume(streamState.mStreamType, streamState.mVolumes[streamState.mIndex]);
1108
1109            // Post a persist volume msg
1110            sendMsg(mAudioHandler, MSG_PERSIST_VOLUME, streamState.mStreamType,
1111                    SENDMSG_REPLACE, 0, 0, streamState, PERSIST_DELAY);
1112        }
1113
1114        private void persistVolume(VolumeStreamState streamState) {
1115            System.putInt(mContentResolver, streamState.mVolumeIndexSettingName,
1116                    streamState.mIndex);
1117            System.putInt(mContentResolver, streamState.mLastAudibleVolumeIndexSettingName,
1118                    streamState.mLastAudibleIndex);
1119        }
1120
1121        private void persistRingerMode() {
1122            System.putInt(mContentResolver, System.MODE_RINGER, mRingerMode);
1123        }
1124
1125        private void persistVibrateSetting() {
1126            System.putInt(mContentResolver, System.VIBRATE_ON, mVibrateSetting);
1127        }
1128
1129        private void playSoundEffect(int effectType, int volume) {
1130            synchronized (mSoundEffectsLock) {
1131                if (mSoundPool == null) {
1132                    return;
1133                }
1134
1135                if (SOUND_EFFECT_FILES_MAP[effectType][1] > 0) {
1136                    float v = (float) volume / 1000.0f;
1137                    mSoundPool.play(SOUND_EFFECT_FILES_MAP[effectType][1], v, v, 0, 0, 1.0f);
1138                } else {
1139                    MediaPlayer mediaPlayer = new MediaPlayer();
1140                    if (mediaPlayer != null) {
1141                        try {
1142                            String filePath = Environment.getRootDirectory() + SOUND_EFFECTS_PATH + SOUND_EFFECT_FILES[SOUND_EFFECT_FILES_MAP[effectType][0]];
1143                            mediaPlayer.setDataSource(filePath);
1144                            mediaPlayer.setAudioStreamType(AudioSystem.STREAM_SYSTEM);
1145                            mediaPlayer.prepare();
1146                            mediaPlayer.setOnCompletionListener(new OnCompletionListener() {
1147                                public void onCompletion(MediaPlayer mp) {
1148                                    cleanupPlayer(mp);
1149                                }
1150                            });
1151                            mediaPlayer.setOnErrorListener(new OnErrorListener() {
1152                                public boolean onError(MediaPlayer mp, int what, int extra) {
1153                                    cleanupPlayer(mp);
1154                                    return true;
1155                                }
1156                            });
1157                            mediaPlayer.start();
1158                        } catch (IOException ex) {
1159                            Log.w(TAG, "MediaPlayer IOException: "+ex);
1160                        } catch (IllegalArgumentException ex) {
1161                            Log.w(TAG, "MediaPlayer IllegalArgumentException: "+ex);
1162                        } catch (IllegalStateException ex) {
1163                            Log.w(TAG, "MediaPlayer IllegalStateException: "+ex);
1164                        }
1165                    }
1166                }
1167            }
1168        }
1169
1170        private void cleanupPlayer(MediaPlayer mp) {
1171            if (mp != null) {
1172                try {
1173                    mp.stop();
1174                    mp.release();
1175                } catch (IllegalStateException ex) {
1176                    Log.w(TAG, "MediaPlayer IllegalStateException: "+ex);
1177                }
1178            }
1179        }
1180
1181        @Override
1182        public void handleMessage(Message msg) {
1183            int baseMsgWhat = getMsgBase(msg.what);
1184
1185            switch (baseMsgWhat) {
1186
1187                case MSG_SET_SYSTEM_VOLUME:
1188                    setSystemVolume((VolumeStreamState) msg.obj);
1189                    break;
1190
1191                case MSG_PERSIST_VOLUME:
1192                    persistVolume((VolumeStreamState) msg.obj);
1193                    break;
1194
1195                case MSG_PERSIST_RINGER_MODE:
1196                    persistRingerMode();
1197                    break;
1198
1199                case MSG_PERSIST_VIBRATE_SETTING:
1200                    persistVibrateSetting();
1201                    break;
1202
1203                case MSG_MEDIA_SERVER_DIED:
1204                    Log.e(TAG, "Media server died.");
1205                    // Force creation of new IAudioflinger interface
1206                    mMediaServerOk = false;
1207                    AudioSystem.getMode();
1208                    break;
1209
1210                case MSG_MEDIA_SERVER_STARTED:
1211                    Log.e(TAG, "Media server started.");
1212                    // Restore audio routing and stream volumes
1213                    applyAudioSettings();
1214                    int numStreamTypes = AudioSystem.getNumStreamTypes();
1215                    for (int streamType = numStreamTypes - 1; streamType >= 0; streamType--) {
1216                        int volume;
1217                        VolumeStreamState streamState = mStreamStates[streamType];
1218                        if (streamState.muteCount() == 0) {
1219                            volume = streamState.mVolumes[streamState.mIndex];
1220                        } else {
1221                            volume = streamState.mVolumes[0];
1222                        }
1223                        AudioSystem.setVolume(streamType, volume);
1224                    }
1225                    setRingerMode(mRingerMode);
1226                    mMediaServerOk = true;
1227                    break;
1228
1229                case MSG_PLAY_SOUND_EFFECT:
1230                    playSoundEffect(msg.arg1, msg.arg2);
1231                    break;
1232            }
1233        }
1234    }
1235
1236}
1237