1/*
2 * Copyright (C) 2014 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.fmradio;
18
19import android.app.ActivityManager;
20import android.app.Notification;
21import android.app.Notification.BigTextStyle;
22import android.app.PendingIntent;
23import android.app.Service;
24import android.bluetooth.BluetoothAdapter;
25import android.bluetooth.BluetoothProfile;
26import android.content.BroadcastReceiver;
27import android.content.ContentResolver;
28import android.content.ContentValues;
29import android.content.Context;
30import android.content.Intent;
31import android.content.IntentFilter;
32import android.content.res.Configuration;
33import android.database.Cursor;
34import android.graphics.Bitmap;
35import android.media.AudioDevicePort;
36import android.media.AudioDevicePortConfig;
37import android.media.AudioFormat;
38import android.media.AudioManager;
39import android.media.AudioManager.OnAudioFocusChangeListener;
40import android.media.AudioManager.OnAudioPortUpdateListener;
41import android.media.AudioMixPort;
42import android.media.AudioPatch;
43import android.media.AudioPort;
44import android.media.AudioPortConfig;
45import android.media.AudioRecord;
46import android.media.AudioSystem;
47import android.media.AudioTrack;
48import android.media.MediaRecorder;
49import android.net.Uri;
50import android.os.Binder;
51import android.os.Bundle;
52import android.os.Handler;
53import android.os.HandlerThread;
54import android.os.IBinder;
55import android.os.Looper;
56import android.os.Message;
57import android.os.PowerManager;
58import android.os.PowerManager.WakeLock;
59import android.text.TextUtils;
60import android.util.Log;
61
62import com.android.fmradio.FmStation.Station;
63
64import java.util.ArrayList;
65import java.util.Arrays;
66import java.util.HashMap;
67import java.util.Iterator;
68
69/**
70 * Background service to control FM or do background tasks.
71 */
72public class FmService extends Service implements FmRecorder.OnRecorderStateChangedListener {
73    // Logging
74    private static final String TAG = "FmService";
75
76    // Broadcast messages from other sounder APP to FM service
77    private static final String SOUND_POWER_DOWN_MSG = "com.android.music.musicservicecommand";
78    private static final String FM_SEEK_PREVIOUS = "fmradio.seek.previous";
79    private static final String FM_SEEK_NEXT = "fmradio.seek.next";
80    private static final String FM_TURN_OFF = "fmradio.turnoff";
81    private static final String CMDPAUSE = "pause";
82
83    // HandlerThread Keys
84    private static final String FM_FREQUENCY = "frequency";
85    private static final String OPTION = "option";
86    private static final String RECODING_FILE_NAME = "name";
87
88    // RDS events
89    // PS
90    private static final int RDS_EVENT_PROGRAMNAME = 0x0008;
91    // RT
92    private static final int RDS_EVENT_LAST_RADIOTEXT = 0x0040;
93    // AF
94    private static final int RDS_EVENT_AF = 0x0080;
95
96    // Headset
97    private static final int HEADSET_PLUG_IN = 1;
98
99    // Notification id
100    private static final int NOTIFICATION_ID = 1;
101
102    // ignore audio data
103    private static final int AUDIO_FRAMES_TO_IGNORE_COUNT = 3;
104
105    // Set audio policy for FM
106    // should check AUDIO_POLICY_FORCE_FOR_MEDIA in audio_policy.h
107    private static final int FOR_PROPRIETARY = 1;
108    // Forced Use value
109    private int mForcedUseForMedia;
110
111    // FM recorder
112    FmRecorder mFmRecorder = null;
113    private BroadcastReceiver mSdcardListener = null;
114    private int mRecordState = FmRecorder.STATE_INVALID;
115    private int mRecorderErrorType = -1;
116    // If eject record sdcard, should set Value false to not record.
117    // Key is sdcard path(like "/storage/sdcard0"), V is to enable record or
118    // not.
119    private HashMap<String, Boolean> mSdcardStateMap = new HashMap<String, Boolean>();
120    // The show name in save dialog but saved in service
121    // If modify the save title it will be not null, otherwise it will be null
122    private String mModifiedRecordingName = null;
123    // record the listener list, will notify all listener in list
124    private ArrayList<Record> mRecords = new ArrayList<Record>();
125    // record FM whether in recording mode
126    private boolean mIsInRecordingMode = false;
127    // record sd card path when start recording
128    private static String sRecordingSdcard = FmUtils.getDefaultStoragePath();
129
130    // RDS
131    // PS String
132    private String mPsString = "";
133    // RT String
134    private String mRtTextString = "";
135    // Notification target class name
136    private String mTargetClassName = FmMainActivity.class.getName();
137    // RDS thread use to receive the information send by station
138    private Thread mRdsThread = null;
139    // record whether RDS thread exit
140    private boolean mIsRdsThreadExit = false;
141
142    // State variables
143    // Record whether FM is in native scan state
144    private boolean mIsNativeScanning = false;
145    // Record whether FM is in scan thread
146    private boolean mIsScanning = false;
147    // Record whether FM is in seeking state
148    private boolean mIsNativeSeeking = false;
149    // Record whether FM is in native seek
150    private boolean mIsSeeking = false;
151    // Record whether searching progress is canceled
152    private boolean mIsStopScanCalled = false;
153    // Record whether is speaker used
154    private boolean mIsSpeakerUsed = false;
155    // Record whether device is open
156    private boolean mIsDeviceOpen = false;
157    // Record Power Status
158    private int mPowerStatus = POWER_DOWN;
159
160    public static int POWER_UP = 0;
161    public static int DURING_POWER_UP = 1;
162    public static int POWER_DOWN = 2;
163    // Record whether service is init
164    private boolean mIsServiceInited = false;
165    // Fm power down by loss audio focus,should make power down menu item can
166    // click
167    private boolean mIsPowerDown = false;
168    // distance is over 100 miles(160934.4m)
169    private boolean mIsDistanceExceed = false;
170    // FmMainActivity foreground
171    private boolean mIsFmMainForeground = true;
172    // FmFavoriteActivity foreground
173    private boolean mIsFmFavoriteForeground = false;
174    // FmRecordActivity foreground
175    private boolean mIsFmRecordForeground = false;
176    // Instance variables
177    private Context mContext = null;
178    private AudioManager mAudioManager = null;
179    private ActivityManager mActivityManager = null;
180    //private MediaPlayer mFmPlayer = null;
181    private WakeLock mWakeLock = null;
182    // Audio focus is held or not
183    private boolean mIsAudioFocusHeld = false;
184    // Focus transient lost
185    private boolean mPausedByTransientLossOfFocus = false;
186    private int mCurrentStation = FmUtils.DEFAULT_STATION;
187    // Headset plug state (0:long antenna plug in, 1:long antenna plug out)
188    private int mValueHeadSetPlug = 1;
189    // For bind service
190    private final IBinder mBinder = new ServiceBinder();
191    // Broadcast to receive the external event
192    private FmServiceBroadcastReceiver mBroadcastReceiver = null;
193    // Async handler
194    private FmRadioServiceHandler mFmServiceHandler;
195    // Lock for lose audio focus and receive SOUND_POWER_DOWN_MSG
196    // at the same time
197    // while recording call stop recording not finished(status is still
198    // RECORDING), but
199    // SOUND_POWER_DOWN_MSG will exitFm(), if it is RECORDING will discard the
200    // record.
201    // 1. lose audio focus -> stop recording(lock) -> set to IDLE and show save
202    // dialog
203    // 2. exitFm() -> check the record status, discard it if it is recording
204    // status(lock)
205    // Add this lock the exitFm() while stopRecording()
206    private Object mStopRecordingLock = new Object();
207    // The listener for exit, should finish favorite when exit FM
208    private static OnExitListener sExitListener = null;
209    // The latest status for mute/unmute
210    private boolean mIsMuted = false;
211
212    // Audio Patch
213    private AudioPatch mAudioPatch = null;
214    private Object mRenderLock = new Object();
215
216    private Notification.Builder mNotificationBuilder = null;
217    private BigTextStyle mNotificationStyle = null;
218
219    @Override
220    public IBinder onBind(Intent intent) {
221        return mBinder;
222    }
223
224    /**
225     * class use to return service instance
226     */
227    public class ServiceBinder extends Binder {
228        /**
229         * get FM service instance
230         *
231         * @return service instance
232         */
233        FmService getService() {
234            return FmService.this;
235        }
236    }
237
238    /**
239     * Broadcast monitor external event, Other app want FM stop, Phone shut
240     * down, screen state, headset state
241     */
242    private class FmServiceBroadcastReceiver extends BroadcastReceiver {
243
244        @Override
245        public void onReceive(Context context, Intent intent) {
246            String action = intent.getAction();
247            String command = intent.getStringExtra("command");
248            Log.d(TAG, "onReceive, action = " + action + " / command = " + command);
249            // other app want FM stop, stop FM
250            if ((SOUND_POWER_DOWN_MSG.equals(action) && CMDPAUSE.equals(command))) {
251                // need remove all messages, make power down will be execute
252                mFmServiceHandler.removeCallbacksAndMessages(null);
253                exitFm();
254                stopSelf();
255                // phone shut down, so exit FM
256            } else if (Intent.ACTION_SHUTDOWN.equals(action)) {
257                /**
258                 * here exitFm, system will send broadcast, system will shut
259                 * down, so fm does not need call back to activity
260                 */
261                mFmServiceHandler.removeCallbacksAndMessages(null);
262                exitFm();
263                // screen on, if FM play, open rds
264            } else if (Intent.ACTION_SCREEN_ON.equals(action)) {
265                setRdsAsync(true);
266                // screen off, if FM play, close rds
267            } else if (Intent.ACTION_SCREEN_OFF.equals(action)) {
268                setRdsAsync(false);
269                // switch antenna when headset plug in or plug out
270            } else if (Intent.ACTION_HEADSET_PLUG.equals(action)) {
271                // switch antenna should not impact audio focus status
272                mValueHeadSetPlug = (intent.getIntExtra("state", -1) == HEADSET_PLUG_IN) ? 0 : 1;
273                switchAntennaAsync(mValueHeadSetPlug);
274
275                // Avoid Service is killed,and receive headset plug in
276                // broadcast again
277                if (!mIsServiceInited) {
278                    Log.d(TAG, "onReceive, mIsServiceInited is false");
279                    return;
280                }
281                /*
282                 * If ear phone insert and activity is
283                 * foreground. power up FM automatic
284                 */
285                if ((0 == mValueHeadSetPlug) && isActivityForeground()) {
286                    powerUpAsync(FmUtils.computeFrequency(mCurrentStation));
287                } else if (1 == mValueHeadSetPlug) {
288                    mFmServiceHandler.removeMessages(FmListener.MSGID_SCAN_FINISHED);
289                    mFmServiceHandler.removeMessages(FmListener.MSGID_SEEK_FINISHED);
290                    mFmServiceHandler.removeMessages(FmListener.MSGID_TUNE_FINISHED);
291                    mFmServiceHandler.removeMessages(
292                            FmListener.MSGID_POWERDOWN_FINISHED);
293                    mFmServiceHandler.removeMessages(
294                            FmListener.MSGID_POWERUP_FINISHED);
295                    focusChanged(AudioManager.AUDIOFOCUS_LOSS);
296
297                    // Need check to switch to earphone mode for audio will
298                    // change to AudioSystem.FORCE_NONE
299                    setForceUse(false);
300
301                    // Notify UI change to earphone mode, false means not speaker mode
302                    Bundle bundle = new Bundle(2);
303                    bundle.putInt(FmListener.CALLBACK_FLAG,
304                            FmListener.LISTEN_SPEAKER_MODE_CHANGED);
305                    bundle.putBoolean(FmListener.KEY_IS_SPEAKER_MODE, false);
306                    notifyActivityStateChanged(bundle);
307                }
308            }
309        }
310    }
311
312    /**
313     * Handle sdcard mount/unmount event. 1. Update the sdcard state map 2. If
314     * the recording sdcard is unmounted, need to stop and notify
315     */
316    private class SdcardListener extends BroadcastReceiver {
317        @Override
318        public void onReceive(Context context, Intent intent) {
319            // If eject record sdcard, should set this false to not
320            // record.
321            updateSdcardStateMap(intent);
322
323            if (mFmRecorder == null) {
324                Log.w(TAG, "SdcardListener.onReceive, mFmRecorder is null");
325                return;
326            }
327
328            String action = intent.getAction();
329            if (Intent.ACTION_MEDIA_EJECT.equals(action) ||
330                    Intent.ACTION_MEDIA_UNMOUNTED.equals(action)) {
331                // If not unmount recording sd card, do nothing;
332                if (isRecordingCardUnmount(intent)) {
333                    if (mFmRecorder.getState() == FmRecorder.STATE_RECORDING) {
334                        onRecorderError(FmRecorder.ERROR_SDCARD_NOT_PRESENT);
335                        mFmRecorder.discardRecording();
336                    } else {
337                        Bundle bundle = new Bundle(2);
338                        bundle.putInt(FmListener.CALLBACK_FLAG,
339                                FmListener.LISTEN_RECORDSTATE_CHANGED);
340                        bundle.putInt(FmListener.KEY_RECORDING_STATE,
341                                FmRecorder.STATE_IDLE);
342                        notifyActivityStateChanged(bundle);
343                    }
344                }
345                return;
346            }
347        }
348    }
349
350    /**
351     * whether antenna available
352     *
353     * @return true, antenna available; false, antenna not available
354     */
355    public boolean isAntennaAvailable() {
356        return mAudioManager.isWiredHeadsetOn();
357    }
358
359    private void setForceUse(boolean isSpeaker) {
360        mForcedUseForMedia = isSpeaker ? AudioSystem.FORCE_SPEAKER : AudioSystem.FORCE_NONE;
361        AudioSystem.setForceUse(FOR_PROPRIETARY, mForcedUseForMedia);
362        mIsSpeakerUsed = isSpeaker;
363    }
364
365    /**
366     * Set FM audio from speaker or not
367     *
368     * @param isSpeaker true if set FM audio from speaker
369     */
370    public void setSpeakerPhoneOn(boolean isSpeaker) {
371        Log.d(TAG, "setSpeakerPhoneOn " + isSpeaker);
372        setForceUse(isSpeaker);
373    }
374
375    /**
376     * Check if BT headset is connected
377     * @return true if current is playing with BT headset
378     */
379    public boolean isBluetoothHeadsetInUse() {
380        BluetoothAdapter btAdapter = BluetoothAdapter.getDefaultAdapter();
381        int a2dpState = btAdapter.getProfileConnectionState(BluetoothProfile.HEADSET);
382        return (BluetoothProfile.STATE_CONNECTED == a2dpState
383                || BluetoothProfile.STATE_CONNECTING == a2dpState);
384    }
385
386    private synchronized void startRender() {
387        Log.d(TAG, "startRender " + AudioSystem.getForceUse(FOR_PROPRIETARY));
388
389       // need to create new audio record and audio play back track,
390       // because input/output device may be changed.
391       if (mAudioRecord != null) {
392           mAudioRecord.stop();
393           mAudioRecord.release();
394           mAudioRecord = null;
395       }
396       if (mAudioTrack != null) {
397           mAudioTrack.stop();
398           mAudioTrack.release();
399           mAudioTrack = null;
400       }
401       initAudioRecordSink();
402
403        mIsRender = true;
404        synchronized (mRenderLock) {
405            mRenderLock.notify();
406        }
407    }
408
409    private synchronized void stopRender() {
410        Log.d(TAG, "stopRender");
411        mIsRender = false;
412    }
413
414    private synchronized void createRenderThread() {
415        if (mRenderThread == null) {
416            mRenderThread = new RenderThread();
417            mRenderThread.start();
418        }
419    }
420
421    private synchronized void exitRenderThread() {
422        stopRender();
423        mRenderThread.interrupt();
424        mRenderThread = null;
425    }
426
427    private Thread mRenderThread = null;
428    private AudioRecord mAudioRecord = null;
429    private AudioTrack mAudioTrack = null;
430    private static final int SAMPLE_RATE = 44100;
431    private static final int CHANNEL_CONFIG = AudioFormat.CHANNEL_CONFIGURATION_STEREO;
432    private static final int AUDIO_FORMAT = AudioFormat.ENCODING_PCM_16BIT;
433    private static final int RECORD_BUF_SIZE = AudioRecord.getMinBufferSize(SAMPLE_RATE,
434            CHANNEL_CONFIG, AUDIO_FORMAT);
435    private boolean mIsRender = false;
436
437    AudioDevicePort mAudioSource = null;
438    AudioDevicePort mAudioSink = null;
439
440    private boolean isRendering() {
441        return mIsRender;
442    }
443
444    private void startAudioTrack() {
445        if (mAudioTrack.getPlayState() == AudioTrack.PLAYSTATE_STOPPED) {
446            ArrayList<AudioPatch> patches = new ArrayList<AudioPatch>();
447            mAudioManager.listAudioPatches(patches);
448            mAudioTrack.play();
449        }
450    }
451
452    private void stopAudioTrack() {
453        if (mAudioTrack.getPlayState() == AudioTrack.PLAYSTATE_PLAYING) {
454            mAudioTrack.stop();
455        }
456    }
457
458    class RenderThread extends Thread {
459        private int mCurrentFrame = 0;
460        private boolean isAudioFrameNeedIgnore() {
461            return mCurrentFrame < AUDIO_FRAMES_TO_IGNORE_COUNT;
462        }
463
464        @Override
465        public void run() {
466            try {
467                byte[] buffer = new byte[RECORD_BUF_SIZE];
468                while (!Thread.interrupted()) {
469                    if (isRender()) {
470                        // Speaker mode or BT a2dp mode will come here and keep reading and writing.
471                        // If we want FM sound output from speaker or BT a2dp, we must record data
472                        // to AudioRecrd and write data to AudioTrack.
473                        if (mAudioRecord.getRecordingState() == AudioRecord.RECORDSTATE_STOPPED) {
474                            mAudioRecord.startRecording();
475                        }
476
477                        if (mAudioTrack.getPlayState() == AudioTrack.PLAYSTATE_STOPPED) {
478                            mAudioTrack.play();
479                        }
480                        int size = mAudioRecord.read(buffer, 0, RECORD_BUF_SIZE);
481                        // check whether need to ignore first 3 frames audio data from AudioRecord
482                        // to avoid pop noise.
483                        if (isAudioFrameNeedIgnore()) {
484                            mCurrentFrame += 1;
485                            continue ;
486                        }
487                        if (size <= 0) {
488                            Log.e(TAG, "RenderThread read data from AudioRecord "
489                                    + "error size: " + size);
490                            continue;
491                        }
492                        byte[] tmpBuf = new byte[size];
493                        System.arraycopy(buffer, 0, tmpBuf, 0, size);
494                        // Check again to avoid noises, because mIsRender may be changed
495                        // while AudioRecord is reading.
496                        if (isRender()) {
497                            mAudioTrack.write(tmpBuf, 0, tmpBuf.length);
498                        }
499                    } else {
500                        // Earphone mode will come here and wait.
501                        mCurrentFrame = 0;
502
503                        if (mAudioTrack.getPlayState() == AudioTrack.PLAYSTATE_PLAYING) {
504                            mAudioTrack.stop();
505                        }
506
507                        if (mAudioRecord.getRecordingState() == AudioRecord.RECORDSTATE_RECORDING) {
508                            mAudioRecord.stop();
509                        }
510
511                        synchronized (mRenderLock) {
512                            mRenderLock.wait();
513                        }
514                    }
515                }
516            } catch (InterruptedException e) {
517                Log.d(TAG, "RenderThread.run, thread is interrupted, need exit thread");
518            } finally {
519                if (mAudioRecord.getRecordingState() == AudioRecord.RECORDSTATE_RECORDING) {
520                    mAudioRecord.stop();
521                }
522                if (mAudioTrack.getPlayState() == AudioTrack.PLAYSTATE_PLAYING) {
523                    mAudioTrack.stop();
524                }
525            }
526        }
527    }
528
529    // A2dp or speaker mode should render
530    private boolean isRender() {
531        return (mIsRender && (mPowerStatus == POWER_UP) && mIsAudioFocusHeld);
532    }
533
534    private boolean isSpeakerPhoneOn() {
535        return (mForcedUseForMedia == AudioSystem.FORCE_SPEAKER);
536    }
537
538    /**
539     * open FM device, should be call before power up
540     *
541     * @return true if FM device open, false FM device not open
542     */
543    private boolean openDevice() {
544        if (!mIsDeviceOpen) {
545            mIsDeviceOpen = FmNative.openDev();
546        }
547        return mIsDeviceOpen;
548    }
549
550    /**
551     * close FM device
552     *
553     * @return true if close FM device success, false close FM device failed
554     */
555    private boolean closeDevice() {
556        boolean isDeviceClose = false;
557        if (mIsDeviceOpen) {
558            isDeviceClose = FmNative.closeDev();
559            mIsDeviceOpen = !isDeviceClose;
560        }
561        // quit looper
562        mFmServiceHandler.getLooper().quit();
563        return isDeviceClose;
564    }
565
566    /**
567     * get FM device opened or not
568     *
569     * @return true FM device opened, false FM device closed
570     */
571    public boolean isDeviceOpen() {
572        return mIsDeviceOpen;
573    }
574
575    /**
576     * power up FM, and make FM voice output from earphone
577     *
578     * @param frequency
579     */
580    public void powerUpAsync(float frequency) {
581        final int bundleSize = 1;
582        mFmServiceHandler.removeMessages(FmListener.MSGID_POWERUP_FINISHED);
583        mFmServiceHandler.removeMessages(FmListener.MSGID_POWERDOWN_FINISHED);
584        Bundle bundle = new Bundle(bundleSize);
585        bundle.putFloat(FM_FREQUENCY, frequency);
586        Message msg = mFmServiceHandler.obtainMessage(FmListener.MSGID_POWERUP_FINISHED);
587        msg.setData(bundle);
588        mFmServiceHandler.sendMessage(msg);
589    }
590
591    private boolean powerUp(float frequency) {
592        if (mPowerStatus == POWER_UP) {
593            return true;
594        }
595        if (!mWakeLock.isHeld()) {
596            mWakeLock.acquire();
597        }
598        if (!requestAudioFocus()) {
599            // activity used for update powerdown menu
600            mPowerStatus = POWER_DOWN;
601            return false;
602        }
603
604        mPowerStatus = DURING_POWER_UP;
605
606        // if device open fail when chip reset, it need open device again before
607        // power up
608        if (!mIsDeviceOpen) {
609            openDevice();
610        }
611
612        if (!FmNative.powerUp(frequency)) {
613            mPowerStatus = POWER_DOWN;
614            return false;
615        }
616        mPowerStatus = POWER_UP;
617        // need mute after power up
618        setMute(true);
619
620        return (mPowerStatus == POWER_UP);
621    }
622
623    private boolean playFrequency(float frequency) {
624        mCurrentStation = FmUtils.computeStation(frequency);
625        FmStation.setCurrentStation(mContext, mCurrentStation);
626        // Add notification to the title bar.
627        updatePlayingNotification();
628
629        // Start the RDS thread if RDS is supported.
630        if (isRdsSupported()) {
631            startRdsThread();
632        }
633
634        if (!mWakeLock.isHeld()) {
635            mWakeLock.acquire();
636        }
637        if (mIsSpeakerUsed != isSpeakerPhoneOn()) {
638            setForceUse(mIsSpeakerUsed);
639        }
640        if (mRecordState != FmRecorder.STATE_PLAYBACK) {
641            enableFmAudio(true);
642        }
643
644        setRds(true);
645        setMute(false);
646
647        return (mPowerStatus == POWER_UP);
648    }
649
650    /**
651     * power down FM
652     */
653    public void powerDownAsync() {
654        // if power down Fm, should remove message first.
655        // not remove all messages, because such as recorder message need
656        // to execute after or before power down
657        mFmServiceHandler.removeMessages(FmListener.MSGID_SCAN_FINISHED);
658        mFmServiceHandler.removeMessages(FmListener.MSGID_SEEK_FINISHED);
659        mFmServiceHandler.removeMessages(FmListener.MSGID_TUNE_FINISHED);
660        mFmServiceHandler.removeMessages(FmListener.MSGID_POWERDOWN_FINISHED);
661        mFmServiceHandler.removeMessages(FmListener.MSGID_POWERUP_FINISHED);
662        mFmServiceHandler.sendEmptyMessage(FmListener.MSGID_POWERDOWN_FINISHED);
663    }
664
665    /**
666     * Power down FM
667     *
668     * @return true if power down success
669     */
670    private boolean powerDown() {
671        if (mPowerStatus == POWER_DOWN) {
672            return true;
673        }
674
675        setMute(true);
676        setRds(false);
677        enableFmAudio(false);
678
679        if (!FmNative.powerDown(0)) {
680
681            if (isRdsSupported()) {
682                stopRdsThread();
683            }
684
685            if (mWakeLock.isHeld()) {
686                mWakeLock.release();
687            }
688            // Remove the notification in the title bar.
689            removeNotification();
690            return false;
691        }
692        // activity used for update powerdown menu
693        mPowerStatus = POWER_DOWN;
694
695        if (isRdsSupported()) {
696            stopRdsThread();
697        }
698
699        if (mWakeLock.isHeld()) {
700            mWakeLock.release();
701        }
702
703        // Remove the notification in the title bar.
704        removeNotification();
705        return true;
706    }
707
708    public int getPowerStatus() {
709        return mPowerStatus;
710    }
711
712    /**
713     * Tune to a station
714     *
715     * @param frequency The frequency to tune
716     *
717     * @return true, success; false, fail.
718     */
719    public void tuneStationAsync(float frequency) {
720        mFmServiceHandler.removeMessages(FmListener.MSGID_TUNE_FINISHED);
721        final int bundleSize = 1;
722        Bundle bundle = new Bundle(bundleSize);
723        bundle.putFloat(FM_FREQUENCY, frequency);
724        Message msg = mFmServiceHandler.obtainMessage(FmListener.MSGID_TUNE_FINISHED);
725        msg.setData(bundle);
726        mFmServiceHandler.sendMessage(msg);
727    }
728
729    private boolean tuneStation(float frequency) {
730        if (mPowerStatus == POWER_UP) {
731            setRds(false);
732            boolean bRet = FmNative.tune(frequency);
733            if (bRet) {
734                setRds(true);
735                mCurrentStation = FmUtils.computeStation(frequency);
736                FmStation.setCurrentStation(mContext, mCurrentStation);
737                updatePlayingNotification();
738            }
739            setMute(false);
740            return bRet;
741        }
742
743        // if earphone is not insert, not power up
744        if (!isAntennaAvailable()) {
745            return false;
746        }
747
748        // if not power up yet, should powerup first
749        boolean tune = false;
750
751        if (powerUp(frequency)) {
752            tune = playFrequency(frequency);
753        }
754
755        return tune;
756    }
757
758    /**
759     * Seek station according frequency and direction
760     *
761     * @param frequency start frequency(100KHZ, 87.5)
762     * @param isUp direction(true, next station; false, previous station)
763     *
764     * @return the frequency after seek
765     */
766    public void seekStationAsync(float frequency, boolean isUp) {
767        mFmServiceHandler.removeMessages(FmListener.MSGID_SEEK_FINISHED);
768        final int bundleSize = 2;
769        Bundle bundle = new Bundle(bundleSize);
770        bundle.putFloat(FM_FREQUENCY, frequency);
771        bundle.putBoolean(OPTION, isUp);
772        Message msg = mFmServiceHandler.obtainMessage(FmListener.MSGID_SEEK_FINISHED);
773        msg.setData(bundle);
774        mFmServiceHandler.sendMessage(msg);
775    }
776
777    private float seekStation(float frequency, boolean isUp) {
778        if (mPowerStatus != POWER_UP) {
779            return -1;
780        }
781
782        setRds(false);
783        mIsNativeSeeking = true;
784        float fRet = FmNative.seek(frequency, isUp);
785        mIsNativeSeeking = false;
786        // make mIsStopScanCalled false, avoid stop scan make this true,
787        // when start scan, it will return null.
788        mIsStopScanCalled = false;
789        return fRet;
790    }
791
792    /**
793     * Scan stations
794     */
795    public void startScanAsync() {
796        mFmServiceHandler.removeMessages(FmListener.MSGID_SCAN_FINISHED);
797        mFmServiceHandler.sendEmptyMessage(FmListener.MSGID_SCAN_FINISHED);
798    }
799
800    private int[] startScan() {
801        int[] stations = null;
802
803        setRds(false);
804        setMute(true);
805        short[] stationsInShort = null;
806        if (!mIsStopScanCalled) {
807            mIsNativeScanning = true;
808            stationsInShort = FmNative.autoScan();
809            mIsNativeScanning = false;
810        }
811
812        setRds(true);
813        if (mIsStopScanCalled) {
814            // Received a message to power down FM, or interrupted by a phone
815            // call. Do not return any stations. stationsInShort = null;
816            // if cancel scan, return invalid station -100
817            stationsInShort = new short[] {
818                -100
819            };
820            mIsStopScanCalled = false;
821        }
822
823        if (null != stationsInShort) {
824            int size = stationsInShort.length;
825            stations = new int[size];
826            for (int i = 0; i < size; i++) {
827                stations[i] = stationsInShort[i];
828            }
829        }
830        return stations;
831    }
832
833    /**
834     * Check FM Radio is in scan progress or not
835     *
836     * @return if in scan progress return true, otherwise return false.
837     */
838    public boolean isScanning() {
839        return mIsScanning;
840    }
841
842    /**
843     * Stop scan progress
844     *
845     * @return true if can stop scan, otherwise return false.
846     */
847    public boolean stopScan() {
848        if (mPowerStatus != POWER_UP) {
849            return false;
850        }
851
852        boolean bRet = false;
853        mFmServiceHandler.removeMessages(FmListener.MSGID_SCAN_FINISHED);
854        mFmServiceHandler.removeMessages(FmListener.MSGID_SEEK_FINISHED);
855        if (mIsNativeScanning || mIsNativeSeeking) {
856            mIsStopScanCalled = true;
857            bRet = FmNative.stopScan();
858        }
859        return bRet;
860    }
861
862    /**
863     * Check FM is in seek progress or not
864     *
865     * @return true if in seek progress, otherwise return false.
866     */
867    public boolean isSeeking() {
868        return mIsNativeSeeking;
869    }
870
871    /**
872     * Set RDS
873     *
874     * @param on true, enable RDS; false, disable RDS.
875     */
876    public void setRdsAsync(boolean on) {
877        final int bundleSize = 1;
878        mFmServiceHandler.removeMessages(FmListener.MSGID_SET_RDS_FINISHED);
879        Bundle bundle = new Bundle(bundleSize);
880        bundle.putBoolean(OPTION, on);
881        Message msg = mFmServiceHandler.obtainMessage(FmListener.MSGID_SET_RDS_FINISHED);
882        msg.setData(bundle);
883        mFmServiceHandler.sendMessage(msg);
884    }
885
886    private int setRds(boolean on) {
887        if (mPowerStatus != POWER_UP) {
888            return -1;
889        }
890        int ret = -1;
891        if (isRdsSupported()) {
892            ret = FmNative.setRds(on);
893        }
894        return ret;
895    }
896
897    /**
898     * Get PS information
899     *
900     * @return PS information
901     */
902    public String getPs() {
903        return mPsString;
904    }
905
906    /**
907     * Get RT information
908     *
909     * @return RT information
910     */
911    public String getRtText() {
912        return mRtTextString;
913    }
914
915    /**
916     * Get AF frequency
917     *
918     * @return AF frequency
919     */
920    public void activeAfAsync() {
921        mFmServiceHandler.removeMessages(FmListener.MSGID_ACTIVE_AF_FINISHED);
922        mFmServiceHandler.sendEmptyMessage(FmListener.MSGID_ACTIVE_AF_FINISHED);
923    }
924
925    private int activeAf() {
926        if (mPowerStatus != POWER_UP) {
927            Log.w(TAG, "activeAf, FM is not powered up");
928            return -1;
929        }
930
931        int frequency = FmNative.activeAf();
932        return frequency;
933    }
934
935    /**
936     * Mute or unmute FM voice
937     *
938     * @param mute true for mute, false for unmute
939     *
940     * @return (true, success; false, failed)
941     */
942    public void setMuteAsync(boolean mute) {
943        mFmServiceHandler.removeMessages(FmListener.MSGID_SET_MUTE_FINISHED);
944        final int bundleSize = 1;
945        Bundle bundle = new Bundle(bundleSize);
946        bundle.putBoolean(OPTION, mute);
947        Message msg = mFmServiceHandler.obtainMessage(FmListener.MSGID_SET_MUTE_FINISHED);
948        msg.setData(bundle);
949        mFmServiceHandler.sendMessage(msg);
950    }
951
952    /**
953     * Mute or unmute FM voice
954     *
955     * @param mute true for mute, false for unmute
956     *
957     * @return (1, success; other, failed)
958     */
959    public int setMute(boolean mute) {
960        if (mPowerStatus != POWER_UP) {
961            Log.w(TAG, "setMute, FM is not powered up");
962            return -1;
963        }
964        int iRet = FmNative.setMute(mute);
965        mIsMuted = mute;
966        return iRet;
967    }
968
969    /**
970     * Check the latest status is mute or not
971     *
972     * @return (true, mute; false, unmute)
973     */
974    public boolean isMuted() {
975        return mIsMuted;
976    }
977
978    /**
979     * Check whether RDS is support in driver
980     *
981     * @return (true, support; false, not support)
982     */
983    public boolean isRdsSupported() {
984        boolean isRdsSupported = (FmNative.isRdsSupport() == 1);
985        return isRdsSupported;
986    }
987
988    /**
989     * Check whether speaker used or not
990     *
991     * @return true if use speaker, otherwise return false
992     */
993    public boolean isSpeakerUsed() {
994        return mIsSpeakerUsed;
995    }
996
997    /**
998     * Initial service and current station
999     *
1000     * @param iCurrentStation current station frequency
1001     */
1002    public void initService(int iCurrentStation) {
1003        mIsServiceInited = true;
1004        mCurrentStation = iCurrentStation;
1005    }
1006
1007    /**
1008     * Check service is initialed or not
1009     *
1010     * @return true if initialed, otherwise return false
1011     */
1012    public boolean isServiceInited() {
1013        return mIsServiceInited;
1014    }
1015
1016    /**
1017     * Get FM service current station frequency
1018     *
1019     * @return Current station frequency
1020     */
1021    public int getFrequency() {
1022        return mCurrentStation;
1023    }
1024
1025    /**
1026     * Set FM service station frequency
1027     *
1028     * @param station Current station
1029     */
1030    public void setFrequency(int station) {
1031        mCurrentStation = station;
1032    }
1033
1034    /**
1035     * resume FM audio
1036     */
1037    private void resumeFmAudio() {
1038        // If not check mIsAudioFocusHeld && power up, when scan canceled,
1039        // this will be resume first, then execute power down. it will cause
1040        // nosise.
1041        if (mIsAudioFocusHeld && (mPowerStatus == POWER_UP)) {
1042            enableFmAudio(true);
1043        }
1044    }
1045
1046    /**
1047     * Switch antenna There are two types of antenna(long and short) If long
1048     * antenna(most is this type), must plug in earphone as antenna to receive
1049     * FM. If short antenna, means there is a short antenna if phone already,
1050     * can receive FM without earphone.
1051     *
1052     * @param antenna antenna (0, long antenna, 1 short antenna)
1053     *
1054     * @return (0, success; 1 failed; 2 not support)
1055     */
1056    public void switchAntennaAsync(int antenna) {
1057        final int bundleSize = 1;
1058        mFmServiceHandler.removeMessages(FmListener.MSGID_SWITCH_ANTENNA);
1059
1060        Bundle bundle = new Bundle(bundleSize);
1061        bundle.putInt(FmListener.SWITCH_ANTENNA_VALUE, antenna);
1062        Message msg = mFmServiceHandler.obtainMessage(FmListener.MSGID_SWITCH_ANTENNA);
1063        msg.setData(bundle);
1064        mFmServiceHandler.sendMessage(msg);
1065    }
1066
1067    /**
1068     * Need native support whether antenna support interface.
1069     *
1070     * @param antenna antenna (0, long antenna, 1 short antenna)
1071     *
1072     * @return (0, success; 1 failed; 2 not support)
1073     */
1074    private int switchAntenna(int antenna) {
1075        // if fm not powerup, switchAntenna will flag whether has earphone
1076        int ret = FmNative.switchAntenna(antenna);
1077        return ret;
1078    }
1079
1080    /**
1081     * Start recording
1082     */
1083    public void startRecordingAsync() {
1084        mFmServiceHandler.removeMessages(FmListener.MSGID_STARTRECORDING_FINISHED);
1085        mFmServiceHandler.sendEmptyMessage(FmListener.MSGID_STARTRECORDING_FINISHED);
1086    }
1087
1088    private void startRecording() {
1089        sRecordingSdcard = FmUtils.getDefaultStoragePath();
1090        if (sRecordingSdcard == null || sRecordingSdcard.isEmpty()) {
1091            Log.d(TAG, "startRecording, may be no sdcard");
1092            onRecorderError(FmRecorder.ERROR_SDCARD_NOT_PRESENT);
1093            return;
1094        }
1095
1096        if (mFmRecorder == null) {
1097            mFmRecorder = new FmRecorder();
1098            mFmRecorder.registerRecorderStateListener(FmService.this);
1099        }
1100
1101        if (isSdcardReady(sRecordingSdcard)) {
1102            mFmRecorder.startRecording(mContext);
1103        } else {
1104            onRecorderError(FmRecorder.ERROR_SDCARD_NOT_PRESENT);
1105        }
1106    }
1107
1108    private boolean isSdcardReady(String sdcardPath) {
1109        if (!mSdcardStateMap.isEmpty()) {
1110            if (mSdcardStateMap.get(sdcardPath) != null && !mSdcardStateMap.get(sdcardPath)) {
1111                Log.d(TAG, "isSdcardReady, return false");
1112                return false;
1113            }
1114        }
1115        return true;
1116    }
1117
1118    /**
1119     * stop recording
1120     */
1121    public void stopRecordingAsync() {
1122        mFmServiceHandler.removeMessages(FmListener.MSGID_STOPRECORDING_FINISHED);
1123        mFmServiceHandler.sendEmptyMessage(FmListener.MSGID_STOPRECORDING_FINISHED);
1124    }
1125
1126    private boolean stopRecording() {
1127        if (mFmRecorder == null) {
1128            Log.e(TAG, "stopRecording, called without a valid recorder!!");
1129            return false;
1130        }
1131        synchronized (mStopRecordingLock) {
1132            mFmRecorder.stopRecording();
1133        }
1134        return true;
1135    }
1136
1137    /**
1138     * Save recording file according name or discard recording file if name is
1139     * null
1140     *
1141     * @param newName New recording file name
1142     */
1143    public void saveRecordingAsync(String newName) {
1144        mFmServiceHandler.removeMessages(FmListener.MSGID_SAVERECORDING_FINISHED);
1145        final int bundleSize = 1;
1146        Bundle bundle = new Bundle(bundleSize);
1147        bundle.putString(RECODING_FILE_NAME, newName);
1148        Message msg = mFmServiceHandler.obtainMessage(FmListener.MSGID_SAVERECORDING_FINISHED);
1149        msg.setData(bundle);
1150        mFmServiceHandler.sendMessage(msg);
1151    }
1152
1153    private void saveRecording(String newName) {
1154        if (mFmRecorder != null) {
1155            if (newName != null) {
1156                mFmRecorder.saveRecording(FmService.this, newName);
1157                return;
1158            }
1159            mFmRecorder.discardRecording();
1160        }
1161    }
1162
1163    /**
1164     * Get record time
1165     *
1166     * @return Record time
1167     */
1168    public long getRecordTime() {
1169        if (mFmRecorder != null) {
1170            return mFmRecorder.getRecordTime();
1171        }
1172        return 0;
1173    }
1174
1175    /**
1176     * Set recording mode
1177     *
1178     * @param isRecording true, enter recoding mode; false, exit recording mode
1179     */
1180    public void setRecordingModeAsync(boolean isRecording) {
1181        mFmServiceHandler.removeMessages(FmListener.MSGID_RECORD_MODE_CHANED);
1182        final int bundleSize = 1;
1183        Bundle bundle = new Bundle(bundleSize);
1184        bundle.putBoolean(OPTION, isRecording);
1185        Message msg = mFmServiceHandler.obtainMessage(FmListener.MSGID_RECORD_MODE_CHANED);
1186        msg.setData(bundle);
1187        mFmServiceHandler.sendMessage(msg);
1188    }
1189
1190    private void setRecordingMode(boolean isRecording) {
1191        mIsInRecordingMode = isRecording;
1192        if (mFmRecorder != null) {
1193            if (!isRecording) {
1194                if (mFmRecorder.getState() != FmRecorder.STATE_IDLE) {
1195                    mFmRecorder.stopRecording();
1196                }
1197                resumeFmAudio();
1198                setMute(false);
1199                return;
1200            }
1201            // reset recorder to unused status
1202            mFmRecorder.resetRecorder();
1203        }
1204    }
1205
1206    /**
1207     * Get current recording mode
1208     *
1209     * @return if in recording mode return true, otherwise return false;
1210     */
1211    public boolean getRecordingMode() {
1212        return mIsInRecordingMode;
1213    }
1214
1215    /**
1216     * Get record state
1217     *
1218     * @return record state
1219     */
1220    public int getRecorderState() {
1221        if (null != mFmRecorder) {
1222            return mFmRecorder.getState();
1223        }
1224        return FmRecorder.STATE_INVALID;
1225    }
1226
1227    /**
1228     * Get recording file name
1229     *
1230     * @return recording file name
1231     */
1232    public String getRecordingName() {
1233        if (null != mFmRecorder) {
1234            return mFmRecorder.getRecordFileName();
1235        }
1236        return null;
1237    }
1238
1239    @Override
1240    public void onCreate() {
1241        super.onCreate();
1242        mContext = getApplicationContext();
1243        mAudioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
1244        mActivityManager = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
1245        PowerManager powerManager = (PowerManager) getSystemService(Context.POWER_SERVICE);
1246        mWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
1247        mWakeLock.setReferenceCounted(false);
1248        sRecordingSdcard = FmUtils.getDefaultStoragePath();
1249
1250        registerFmBroadcastReceiver();
1251        registerSdcardReceiver();
1252        registerAudioPortUpdateListener();
1253
1254        HandlerThread handlerThread = new HandlerThread("FmRadioServiceThread");
1255        handlerThread.start();
1256        mFmServiceHandler = new FmRadioServiceHandler(handlerThread.getLooper());
1257
1258        openDevice();
1259        // set speaker to default status, avoid setting->clear data.
1260        setForceUse(mIsSpeakerUsed);
1261
1262        initAudioRecordSink();
1263        createRenderThread();
1264    }
1265
1266    private void registerAudioPortUpdateListener() {
1267        if (mAudioPortUpdateListener == null) {
1268            mAudioPortUpdateListener = new FmOnAudioPortUpdateListener();
1269            mAudioManager.registerAudioPortUpdateListener(mAudioPortUpdateListener);
1270        }
1271    }
1272
1273    private void unregisterAudioPortUpdateListener() {
1274        if (mAudioPortUpdateListener != null) {
1275            mAudioManager.unregisterAudioPortUpdateListener(mAudioPortUpdateListener);
1276            mAudioPortUpdateListener = null;
1277        }
1278    }
1279
1280    // This function may be called in different threads.
1281    // Need to add "synchronized" to make sure mAudioRecord and mAudioTrack are the newest.
1282    // Thread 1: onCreate() or startRender()
1283    // Thread 2: onAudioPatchListUpdate() or startRender()
1284    private synchronized void initAudioRecordSink() {
1285        mAudioRecord = new AudioRecord(MediaRecorder.AudioSource.RADIO_TUNER,
1286                SAMPLE_RATE, CHANNEL_CONFIG, AUDIO_FORMAT, RECORD_BUF_SIZE);
1287        mAudioTrack = new AudioTrack(AudioManager.STREAM_MUSIC,
1288                SAMPLE_RATE, CHANNEL_CONFIG, AUDIO_FORMAT, RECORD_BUF_SIZE, AudioTrack.MODE_STREAM);
1289    }
1290
1291    private synchronized int createAudioPatch() {
1292        Log.d(TAG, "createAudioPatch");
1293        int status = AudioManager.SUCCESS;
1294        if (mAudioPatch != null) {
1295            Log.d(TAG, "createAudioPatch, mAudioPatch is not null, return");
1296            return status;
1297        }
1298
1299        mAudioSource = null;
1300        mAudioSink = null;
1301        ArrayList<AudioPort> ports = new ArrayList<AudioPort>();
1302        mAudioManager.listAudioPorts(ports);
1303        for (AudioPort port : ports) {
1304            if (port instanceof AudioDevicePort) {
1305                int type = ((AudioDevicePort) port).type();
1306                String name = AudioSystem.getOutputDeviceName(type);
1307                if (type == AudioSystem.DEVICE_IN_FM_TUNER) {
1308                    mAudioSource = (AudioDevicePort) port;
1309                } else if (type == AudioSystem.DEVICE_OUT_WIRED_HEADSET ||
1310                        type == AudioSystem.DEVICE_OUT_WIRED_HEADPHONE) {
1311                    mAudioSink = (AudioDevicePort) port;
1312                }
1313            }
1314        }
1315        if (mAudioSource != null && mAudioSink != null) {
1316            AudioDevicePortConfig sourceConfig = (AudioDevicePortConfig) mAudioSource
1317                    .activeConfig();
1318            AudioDevicePortConfig sinkConfig = (AudioDevicePortConfig) mAudioSink.activeConfig();
1319            AudioPatch[] audioPatchArray = new AudioPatch[] {null};
1320            status = mAudioManager.createAudioPatch(audioPatchArray,
1321                    new AudioPortConfig[] {sourceConfig},
1322                    new AudioPortConfig[] {sinkConfig});
1323            mAudioPatch = audioPatchArray[0];
1324        }
1325        return status;
1326    }
1327
1328    private FmOnAudioPortUpdateListener mAudioPortUpdateListener = null;
1329
1330    private class FmOnAudioPortUpdateListener implements OnAudioPortUpdateListener {
1331        /**
1332         * Callback method called upon audio port list update.
1333         * @param portList the updated list of audio ports
1334         */
1335        @Override
1336        public void onAudioPortListUpdate(AudioPort[] portList) {
1337            // Ingore audio port update
1338        }
1339
1340        /**
1341         * Callback method called upon audio patch list update.
1342         *
1343         * @param patchList the updated list of audio patches
1344         */
1345        @Override
1346        public void onAudioPatchListUpdate(AudioPatch[] patchList) {
1347            if (mPowerStatus != POWER_UP) {
1348                Log.d(TAG, "onAudioPatchListUpdate, not power up");
1349                return;
1350            }
1351
1352            if (!mIsAudioFocusHeld) {
1353                Log.d(TAG, "onAudioPatchListUpdate no audio focus");
1354                return;
1355            }
1356
1357            if (mAudioPatch != null) {
1358                ArrayList<AudioPatch> patches = new ArrayList<AudioPatch>();
1359                mAudioManager.listAudioPatches(patches);
1360                // When BT or WFD is connected, native will remove the patch (mixer -> device).
1361                // Need to recreate AudioRecord and AudioTrack for this case.
1362                if (isPatchMixerToDeviceRemoved(patches)) {
1363                    Log.d(TAG, "onAudioPatchListUpdate reinit for BT or WFD connected");
1364                    initAudioRecordSink();
1365                    startRender();
1366                    return;
1367                }
1368                if (isPatchMixerToEarphone(patches)) {
1369                    stopRender();
1370                } else {
1371                    releaseAudioPatch();
1372                    startRender();
1373                }
1374            } else if (mIsRender) {
1375                ArrayList<AudioPatch> patches = new ArrayList<AudioPatch>();
1376                mAudioManager.listAudioPatches(patches);
1377                if (isPatchMixerToEarphone(patches)) {
1378                    int status;
1379                    stopAudioTrack();
1380                    stopRender();
1381                    status = createAudioPatch();
1382                    if (status != AudioManager.SUCCESS){
1383                       Log.d(TAG, "onAudioPatchListUpdate: fallback as createAudioPatch failed");
1384                       startRender();
1385                    }
1386                }
1387            }
1388        }
1389
1390        /**
1391         * Callback method called when the mediaserver dies
1392         */
1393        @Override
1394        public void onServiceDied() {
1395            enableFmAudio(false);
1396        }
1397    }
1398
1399    private synchronized void releaseAudioPatch() {
1400        if (mAudioPatch != null) {
1401            Log.d(TAG, "releaseAudioPatch");
1402            mAudioManager.releaseAudioPatch(mAudioPatch);
1403            mAudioPatch = null;
1404        }
1405        mAudioSource = null;
1406        mAudioSink = null;
1407    }
1408
1409    private void registerFmBroadcastReceiver() {
1410        IntentFilter filter = new IntentFilter();
1411        filter.addAction(SOUND_POWER_DOWN_MSG);
1412        filter.addAction(Intent.ACTION_SHUTDOWN);
1413        filter.addAction(Intent.ACTION_SCREEN_ON);
1414        filter.addAction(Intent.ACTION_SCREEN_OFF);
1415        filter.addAction(Intent.ACTION_HEADSET_PLUG);
1416        mBroadcastReceiver = new FmServiceBroadcastReceiver();
1417        registerReceiver(mBroadcastReceiver, filter);
1418    }
1419
1420    private void unregisterFmBroadcastReceiver() {
1421        if (null != mBroadcastReceiver) {
1422            unregisterReceiver(mBroadcastReceiver);
1423            mBroadcastReceiver = null;
1424        }
1425    }
1426
1427    @Override
1428    public void onDestroy() {
1429        mAudioManager.setParameters("AudioFmPreStop=1");
1430        setMute(true);
1431        // stop rds first, avoid blocking other native method
1432        if (isRdsSupported()) {
1433            stopRdsThread();
1434        }
1435        unregisterFmBroadcastReceiver();
1436        unregisterSdcardListener();
1437        abandonAudioFocus();
1438        exitFm();
1439        if (null != mFmRecorder) {
1440            mFmRecorder = null;
1441        }
1442        exitRenderThread();
1443        releaseAudioPatch();
1444        unregisterAudioPortUpdateListener();
1445        super.onDestroy();
1446    }
1447
1448    /**
1449     * Exit FMRadio application
1450     */
1451    private void exitFm() {
1452        mIsAudioFocusHeld = false;
1453        // Stop FM recorder if it is working
1454        if (null != mFmRecorder) {
1455            synchronized (mStopRecordingLock) {
1456                int fmState = mFmRecorder.getState();
1457                if (FmRecorder.STATE_RECORDING == fmState) {
1458                    mFmRecorder.stopRecording();
1459                }
1460            }
1461        }
1462
1463        // When exit, we set the audio path back to earphone.
1464        if (mIsNativeScanning || mIsNativeSeeking) {
1465            stopScan();
1466        }
1467
1468        mFmServiceHandler.removeCallbacksAndMessages(null);
1469        mFmServiceHandler.removeMessages(FmListener.MSGID_FM_EXIT);
1470        mFmServiceHandler.sendEmptyMessage(FmListener.MSGID_FM_EXIT);
1471    }
1472
1473    @Override
1474    public void onConfigurationChanged(Configuration newConfig) {
1475        super.onConfigurationChanged(newConfig);
1476        // Change the notification string.
1477        if (mPowerStatus == POWER_UP) {
1478            showPlayingNotification();
1479        }
1480    }
1481
1482    @Override
1483    public int onStartCommand(Intent intent, int flags, int startId) {
1484        int ret = super.onStartCommand(intent, flags, startId);
1485
1486        if (intent != null) {
1487            String action = intent.getAction();
1488            if (FM_SEEK_PREVIOUS.equals(action)) {
1489                seekStationAsync(FmUtils.computeFrequency(mCurrentStation), false);
1490            } else if (FM_SEEK_NEXT.equals(action)) {
1491                seekStationAsync(FmUtils.computeFrequency(mCurrentStation), true);
1492            } else if (FM_TURN_OFF.equals(action)) {
1493                powerDownAsync();
1494            }
1495        }
1496        return START_NOT_STICKY;
1497    }
1498
1499    /**
1500     * Start RDS thread to update RDS information
1501     */
1502    private void startRdsThread() {
1503        mIsRdsThreadExit = false;
1504        if (null != mRdsThread) {
1505            return;
1506        }
1507        mRdsThread = new Thread() {
1508            public void run() {
1509                while (true) {
1510                    if (mIsRdsThreadExit) {
1511                        break;
1512                    }
1513
1514                    int iRdsEvents = FmNative.readRds();
1515                    if (iRdsEvents != 0) {
1516                        Log.d(TAG, "startRdsThread, is rds events: " + iRdsEvents);
1517                    }
1518
1519                    if (RDS_EVENT_PROGRAMNAME == (RDS_EVENT_PROGRAMNAME & iRdsEvents)) {
1520                        byte[] bytePS = FmNative.getPs();
1521                        if (null != bytePS) {
1522                            String ps = new String(bytePS).trim();
1523                            if (!mPsString.equals(ps)) {
1524                                updatePlayingNotification();
1525                            }
1526                            ContentValues values = null;
1527                            if (FmStation.isStationExist(mContext, mCurrentStation)) {
1528                                values = new ContentValues(1);
1529                                values.put(Station.PROGRAM_SERVICE, ps);
1530                                FmStation.updateStationToDb(mContext, mCurrentStation, values);
1531                            } else {
1532                                values = new ContentValues(2);
1533                                values.put(Station.FREQUENCY, mCurrentStation);
1534                                values.put(Station.PROGRAM_SERVICE, ps);
1535                                FmStation.insertStationToDb(mContext, values);
1536                            }
1537                            setPs(ps);
1538                        }
1539                    }
1540
1541                    if (RDS_EVENT_LAST_RADIOTEXT == (RDS_EVENT_LAST_RADIOTEXT & iRdsEvents)) {
1542                        byte[] byteLRText = FmNative.getLrText();
1543                        if (null != byteLRText) {
1544                            String rds = new String(byteLRText).trim();
1545                            if (!mRtTextString.equals(rds)) {
1546                                updatePlayingNotification();
1547                            }
1548                            setLRText(rds);
1549                            ContentValues values = null;
1550                            if (FmStation.isStationExist(mContext, mCurrentStation)) {
1551                                values = new ContentValues(1);
1552                                values.put(Station.RADIO_TEXT, rds);
1553                                FmStation.updateStationToDb(mContext, mCurrentStation, values);
1554                            } else {
1555                                values = new ContentValues(2);
1556                                values.put(Station.FREQUENCY, mCurrentStation);
1557                                values.put(Station.RADIO_TEXT, rds);
1558                                FmStation.insertStationToDb(mContext, values);
1559                            }
1560                        }
1561                    }
1562
1563                    if (RDS_EVENT_AF == (RDS_EVENT_AF & iRdsEvents)) {
1564                        /*
1565                         * add for rds AF
1566                         */
1567                        if (mIsScanning || mIsSeeking) {
1568                            Log.d(TAG, "startRdsThread, seek or scan going, no need to tune here");
1569                        } else if (mPowerStatus == POWER_DOWN) {
1570                            Log.d(TAG, "startRdsThread, fm is power down, do nothing.");
1571                        } else {
1572                            int iFreq = FmNative.activeAf();
1573                            if (FmUtils.isValidStation(iFreq)) {
1574                                // if the new frequency is not equal to current
1575                                // frequency.
1576                                if (mCurrentStation != iFreq) {
1577                                    if (!mIsScanning && !mIsSeeking) {
1578                                        Log.d(TAG, "startRdsThread, seek or scan not going,"
1579                                                + "need to tune here");
1580                                        tuneStationAsync(FmUtils.computeFrequency(iFreq));
1581                                    }
1582                                }
1583                            }
1584                        }
1585                    }
1586                    // Do not handle other events.
1587                    // Sleep 500ms to reduce inquiry frequency
1588                    try {
1589                        final int hundredMillisecond = 500;
1590                        Thread.sleep(hundredMillisecond);
1591                    } catch (InterruptedException e) {
1592                        e.printStackTrace();
1593                    }
1594                }
1595            }
1596        };
1597        mRdsThread.start();
1598    }
1599
1600    /**
1601     * Stop RDS thread to stop listen station RDS change
1602     */
1603    private void stopRdsThread() {
1604        if (null != mRdsThread) {
1605            // Must call closedev after stopRDSThread.
1606            mIsRdsThreadExit = true;
1607            mRdsThread = null;
1608        }
1609    }
1610
1611    /**
1612     * Set PS information
1613     *
1614     * @param ps The ps information
1615     */
1616    private void setPs(String ps) {
1617        if (0 != mPsString.compareTo(ps)) {
1618            mPsString = ps;
1619            Bundle bundle = new Bundle(3);
1620            bundle.putInt(FmListener.CALLBACK_FLAG, FmListener.LISTEN_PS_CHANGED);
1621            bundle.putString(FmListener.KEY_PS_INFO, mPsString);
1622            notifyActivityStateChanged(bundle);
1623        } // else New PS is the same as current
1624    }
1625
1626    /**
1627     * Set RT information
1628     *
1629     * @param lrtText The RT information
1630     */
1631    private void setLRText(String lrtText) {
1632        if (0 != mRtTextString.compareTo(lrtText)) {
1633            mRtTextString = lrtText;
1634            Bundle bundle = new Bundle(3);
1635            bundle.putInt(FmListener.CALLBACK_FLAG, FmListener.LISTEN_RT_CHANGED);
1636            bundle.putString(FmListener.KEY_RT_INFO, mRtTextString);
1637            notifyActivityStateChanged(bundle);
1638        } // else New RT is the same as current
1639    }
1640
1641    /**
1642     * Open or close FM Radio audio
1643     *
1644     * @param enable true, open FM audio; false, close FM audio;
1645     */
1646    private void enableFmAudio(boolean enable) {
1647        if (enable) {
1648            if ((mPowerStatus != POWER_UP) || !mIsAudioFocusHeld) {
1649                Log.d(TAG, "enableFmAudio, current not available return.mIsAudioFocusHeld:"
1650                    + mIsAudioFocusHeld);
1651                return;
1652            }
1653
1654            startAudioTrack();
1655            ArrayList<AudioPatch> patches = new ArrayList<AudioPatch>();
1656            mAudioManager.listAudioPatches(patches);
1657            if (mAudioPatch == null) {
1658                if (isPatchMixerToEarphone(patches)) {
1659                    int status;
1660                    stopAudioTrack();
1661                    stopRender();
1662                    status = createAudioPatch();
1663                    if (status != AudioManager.SUCCESS){
1664                       Log.d(TAG, "enableFmAudio: fallback as createAudioPatch failed");
1665                       startRender();
1666                    }
1667                } else {
1668                    startRender();
1669                }
1670            }
1671        } else {
1672            releaseAudioPatch();
1673            stopRender();
1674        }
1675    }
1676
1677    // Make sure patches count will not be 0
1678    private boolean isPatchMixerToEarphone(ArrayList<AudioPatch> patches) {
1679        int deviceCount = 0;
1680        int deviceEarphoneCount = 0;
1681        for (AudioPatch patch : patches) {
1682            AudioPortConfig[] sources = patch.sources();
1683            AudioPortConfig[] sinks = patch.sinks();
1684            AudioPortConfig sourceConfig = sources[0];
1685            AudioPortConfig sinkConfig = sinks[0];
1686            AudioPort sourcePort = sourceConfig.port();
1687            AudioPort sinkPort = sinkConfig.port();
1688            Log.d(TAG, "isPatchMixerToEarphone " + sourcePort + " ====> " + sinkPort);
1689            if (sourcePort instanceof AudioMixPort && sinkPort instanceof AudioDevicePort) {
1690                deviceCount++;
1691                int type = ((AudioDevicePort) sinkPort).type();
1692                if (type == AudioSystem.DEVICE_OUT_WIRED_HEADSET ||
1693                        type == AudioSystem.DEVICE_OUT_WIRED_HEADPHONE) {
1694                    deviceEarphoneCount++;
1695                }
1696            }
1697        }
1698        if (deviceEarphoneCount == 1 && deviceCount == deviceEarphoneCount) {
1699            return true;
1700        }
1701        return false;
1702    }
1703
1704    // Check whether the patch (mixer -> device) is removed by native.
1705    // If no patch (mixer -> device), return true.
1706    private boolean isPatchMixerToDeviceRemoved(ArrayList<AudioPatch> patches) {
1707        boolean noMixerToDevice = true;
1708        for (AudioPatch patch : patches) {
1709            AudioPortConfig[] sources = patch.sources();
1710            AudioPortConfig[] sinks = patch.sinks();
1711            AudioPortConfig sourceConfig = sources[0];
1712            AudioPortConfig sinkConfig = sinks[0];
1713            AudioPort sourcePort = sourceConfig.port();
1714            AudioPort sinkPort = sinkConfig.port();
1715
1716            if (sourcePort instanceof AudioMixPort && sinkPort instanceof AudioDevicePort) {
1717                noMixerToDevice = false;
1718                break;
1719            }
1720        }
1721        return noMixerToDevice;
1722    }
1723
1724    /**
1725     * Show notification
1726     */
1727    private void showPlayingNotification() {
1728        if (isActivityForeground() || mIsScanning
1729                || (getRecorderState() == FmRecorder.STATE_RECORDING)) {
1730            Log.w(TAG, "showPlayingNotification, do not show main notification.");
1731            return;
1732        }
1733        String stationName = "";
1734        String radioText = "";
1735        ContentResolver resolver = mContext.getContentResolver();
1736        Cursor cursor = null;
1737        try {
1738            cursor = resolver.query(
1739                    Station.CONTENT_URI,
1740                    FmStation.COLUMNS,
1741                    Station.FREQUENCY + "=?",
1742                    new String[] { String.valueOf(mCurrentStation) },
1743                    null);
1744            if (cursor != null && cursor.moveToFirst()) {
1745                // If the station name is not exist, show program service(PS) instead
1746                stationName = cursor.getString(cursor.getColumnIndex(Station.STATION_NAME));
1747                if (TextUtils.isEmpty(stationName)) {
1748                    stationName = cursor.getString(cursor.getColumnIndex(Station.PROGRAM_SERVICE));
1749                }
1750                radioText = cursor.getString(cursor.getColumnIndex(Station.RADIO_TEXT));
1751
1752            } else {
1753                Log.d(TAG, "showPlayingNotification, cursor is null");
1754            }
1755        } finally {
1756            if (cursor != null) {
1757                cursor.close();
1758            }
1759        }
1760
1761        Intent aIntent = new Intent(Intent.ACTION_MAIN);
1762        aIntent.addCategory(Intent.CATEGORY_LAUNCHER);
1763        aIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
1764        aIntent.setClassName(getPackageName(), mTargetClassName);
1765        PendingIntent pAIntent = PendingIntent.getActivity(mContext, 0, aIntent, 0);
1766
1767        if (null == mNotificationBuilder) {
1768            mNotificationBuilder = new Notification.Builder(mContext);
1769            mNotificationBuilder.setSmallIcon(R.drawable.ic_launcher);
1770            mNotificationBuilder.setShowWhen(false);
1771            mNotificationBuilder.setAutoCancel(true);
1772
1773            Intent intent = new Intent(FM_SEEK_PREVIOUS);
1774            intent.setClass(mContext, FmService.class);
1775            PendingIntent pIntent = PendingIntent.getService(mContext, 0, intent, 0);
1776            mNotificationBuilder.addAction(R.drawable.btn_fm_prevstation, "", pIntent);
1777            intent = new Intent(FM_TURN_OFF);
1778            intent.setClass(mContext, FmService.class);
1779            pIntent = PendingIntent.getService(mContext, 0, intent, 0);
1780            mNotificationBuilder.addAction(R.drawable.btn_fm_rec_stop_enabled, "", pIntent);
1781            intent = new Intent(FM_SEEK_NEXT);
1782            intent.setClass(mContext, FmService.class);
1783            pIntent = PendingIntent.getService(mContext, 0, intent, 0);
1784            mNotificationBuilder.addAction(R.drawable.btn_fm_nextstation, "", pIntent);
1785        }
1786        mNotificationBuilder.setContentIntent(pAIntent);
1787        Bitmap largeIcon = FmUtils.createNotificationLargeIcon(mContext,
1788                FmUtils.formatStation(mCurrentStation));
1789        mNotificationBuilder.setLargeIcon(largeIcon);
1790        // Show FM Radio if empty
1791        if (TextUtils.isEmpty(stationName)) {
1792            stationName = getString(R.string.app_name);
1793        }
1794        mNotificationBuilder.setContentTitle(stationName);
1795        // If radio text is "" or null, we also need to update notification.
1796        mNotificationBuilder.setContentText(radioText);
1797        Log.d(TAG, "showPlayingNotification PS:" + stationName + ", RT:" + radioText);
1798
1799        Notification n = mNotificationBuilder.build();
1800        n.flags &= ~Notification.FLAG_NO_CLEAR;
1801        startForeground(NOTIFICATION_ID, n);
1802    }
1803
1804    /**
1805     * Show notification
1806     */
1807    public void showRecordingNotification(Notification notification) {
1808        startForeground(NOTIFICATION_ID, notification);
1809    }
1810
1811    /**
1812     * Remove notification
1813     */
1814    public void removeNotification() {
1815        stopForeground(true);
1816    }
1817
1818    /**
1819     * Update notification
1820     */
1821    public void updatePlayingNotification() {
1822        if (mPowerStatus == POWER_UP) {
1823            showPlayingNotification();
1824        }
1825    }
1826
1827    /**
1828     * Register sdcard listener for record
1829     */
1830    private void registerSdcardReceiver() {
1831        if (mSdcardListener == null) {
1832            mSdcardListener = new SdcardListener();
1833        }
1834        IntentFilter filter = new IntentFilter();
1835        filter.addDataScheme("file");
1836        filter.addAction(Intent.ACTION_MEDIA_MOUNTED);
1837        filter.addAction(Intent.ACTION_MEDIA_UNMOUNTED);
1838        filter.addAction(Intent.ACTION_MEDIA_EJECT);
1839        registerReceiver(mSdcardListener, filter);
1840    }
1841
1842    private void unregisterSdcardListener() {
1843        if (null != mSdcardListener) {
1844            unregisterReceiver(mSdcardListener);
1845        }
1846    }
1847
1848    private void updateSdcardStateMap(Intent intent) {
1849        String action = intent.getAction();
1850        String sdcardPath = null;
1851        Uri mountPointUri = intent.getData();
1852        if (mountPointUri != null) {
1853            sdcardPath = mountPointUri.getPath();
1854            if (sdcardPath != null) {
1855                if (Intent.ACTION_MEDIA_EJECT.equals(action)) {
1856                    mSdcardStateMap.put(sdcardPath, false);
1857                } else if (Intent.ACTION_MEDIA_UNMOUNTED.equals(action)) {
1858                    mSdcardStateMap.put(sdcardPath, false);
1859                } else if (Intent.ACTION_MEDIA_MOUNTED.equals(action)) {
1860                    mSdcardStateMap.put(sdcardPath, true);
1861                }
1862            }
1863        }
1864    }
1865
1866    /**
1867     * Notify FM recorder state
1868     *
1869     * @param state The current FM recorder state
1870     */
1871    @Override
1872    public void onRecorderStateChanged(int state) {
1873        mRecordState = state;
1874        Bundle bundle = new Bundle(2);
1875        bundle.putInt(FmListener.CALLBACK_FLAG, FmListener.LISTEN_RECORDSTATE_CHANGED);
1876        bundle.putInt(FmListener.KEY_RECORDING_STATE, state);
1877        notifyActivityStateChanged(bundle);
1878    }
1879
1880    /**
1881     * Notify FM recorder error message
1882     *
1883     * @param error The recorder error type
1884     */
1885    @Override
1886    public void onRecorderError(int error) {
1887        // if media server die, will not enable FM audio, and convert to
1888        // ERROR_PLAYER_INATERNAL, call back to activity showing toast.
1889        mRecorderErrorType = error;
1890
1891        Bundle bundle = new Bundle(2);
1892        bundle.putInt(FmListener.CALLBACK_FLAG, FmListener.LISTEN_RECORDERROR);
1893        bundle.putInt(FmListener.KEY_RECORDING_ERROR_TYPE, mRecorderErrorType);
1894        notifyActivityStateChanged(bundle);
1895    }
1896
1897    /**
1898     * Check and go next(play or show tips) after recorder file play
1899     * back finish.
1900     * Two cases:
1901     * 1. With headset  -> play FM
1902     * 2. Without headset -> show plug in earphone tips
1903     */
1904    private void checkState() {
1905        if (isHeadSetIn()) {
1906            // with headset
1907            if (mPowerStatus == POWER_UP) {
1908                resumeFmAudio();
1909                setMute(false);
1910            } else {
1911                powerUpAsync(FmUtils.computeFrequency(mCurrentStation));
1912            }
1913        } else {
1914            // without headset need show plug in earphone tips
1915            switchAntennaAsync(mValueHeadSetPlug);
1916        }
1917    }
1918
1919    /**
1920     * Check the headset is plug in or plug out
1921     *
1922     * @return true for plug in; false for plug out
1923     */
1924    private boolean isHeadSetIn() {
1925        return (0 == mValueHeadSetPlug);
1926    }
1927
1928    private void focusChanged(int focusState) {
1929        mIsAudioFocusHeld = false;
1930        if (mIsNativeScanning || mIsNativeSeeking) {
1931            // make stop scan from activity call to service.
1932            // notifyActivityStateChanged(FMRadioListener.LISTEN_SCAN_CANCELED);
1933            stopScan();
1934        }
1935
1936        // using handler thread to update audio focus state
1937        updateAudioFocusAync(focusState);
1938    }
1939
1940    /**
1941     * Request audio focus
1942     *
1943     * @return true, success; false, fail;
1944     */
1945    public boolean requestAudioFocus() {
1946        if (FmUtils.getIsSpeakerModeOnFocusLost(mContext)) {
1947            setForceUse(true);
1948            FmUtils.setIsSpeakerModeOnFocusLost(mContext, false);
1949        }
1950        if (mIsAudioFocusHeld) {
1951            return true;
1952        }
1953
1954        int audioFocus = mAudioManager.requestAudioFocus(mAudioFocusChangeListener,
1955                AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN);
1956        mIsAudioFocusHeld = (AudioManager.AUDIOFOCUS_REQUEST_GRANTED == audioFocus);
1957        return mIsAudioFocusHeld;
1958    }
1959
1960    /**
1961     * Abandon audio focus
1962     */
1963    public void abandonAudioFocus() {
1964        mAudioManager.abandonAudioFocus(mAudioFocusChangeListener);
1965        mIsAudioFocusHeld = false;
1966    }
1967
1968    /**
1969     * Use to interact with other voice related app
1970     */
1971    private final OnAudioFocusChangeListener mAudioFocusChangeListener =
1972            new OnAudioFocusChangeListener() {
1973                /**
1974                 * Handle audio focus change ensure message FIFO
1975                 *
1976                 * @param focusChange audio focus change state
1977                 */
1978                @Override
1979                public void onAudioFocusChange(int focusChange) {
1980                    Log.d(TAG, "onAudioFocusChange " + focusChange);
1981                    switch (focusChange) {
1982                        case AudioManager.AUDIOFOCUS_LOSS:
1983                            synchronized (this) {
1984                                mAudioManager.setParameters("AudioFmPreStop=1");
1985                                setMute(true);
1986                                focusChanged(AudioManager.AUDIOFOCUS_LOSS);
1987                            }
1988                            break;
1989
1990                        case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
1991                            synchronized (this) {
1992                                mAudioManager.setParameters("AudioFmPreStop=1");
1993                                setMute(true);
1994                                focusChanged(AudioManager.AUDIOFOCUS_LOSS_TRANSIENT);
1995                            }
1996                            break;
1997
1998                        case AudioManager.AUDIOFOCUS_GAIN:
1999                            synchronized (this) {
2000                                updateAudioFocusAync(AudioManager.AUDIOFOCUS_GAIN);
2001                            }
2002                            break;
2003
2004                        case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
2005                            synchronized (this) {
2006                                updateAudioFocusAync(
2007                                        AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK);
2008                            }
2009                            break;
2010
2011                        default:
2012                            break;
2013                    }
2014                }
2015            };
2016
2017    /**
2018     * Audio focus changed, will send message to handler thread. synchronized to
2019     * ensure one message can go in this method.
2020     *
2021     * @param focusState AudioManager state
2022     */
2023    private synchronized void updateAudioFocusAync(int focusState) {
2024        final int bundleSize = 1;
2025        Bundle bundle = new Bundle(bundleSize);
2026        bundle.putInt(FmListener.KEY_AUDIOFOCUS_CHANGED, focusState);
2027        Message msg = mFmServiceHandler.obtainMessage(FmListener.MSGID_AUDIOFOCUS_CHANGED);
2028        msg.setData(bundle);
2029        mFmServiceHandler.sendMessage(msg);
2030    }
2031
2032    /**
2033     * Audio focus changed, update FM focus state.
2034     *
2035     * @param focusState AudioManager state
2036     */
2037    private void updateAudioFocus(int focusState) {
2038        switch (focusState) {
2039            case AudioManager.AUDIOFOCUS_LOSS:
2040                mPausedByTransientLossOfFocus = false;
2041                // play back audio will output with music audio
2042                // May be affect other recorder app, but the flow can not be
2043                // execute earlier,
2044                // It should ensure execute after start/stop record.
2045                if (mFmRecorder != null) {
2046                    int fmState = mFmRecorder.getState();
2047                    // only handle recorder state, not handle playback state
2048                    if (fmState == FmRecorder.STATE_RECORDING) {
2049                        mFmServiceHandler.removeMessages(
2050                                FmListener.MSGID_STARTRECORDING_FINISHED);
2051                        mFmServiceHandler.removeMessages(
2052                                FmListener.MSGID_STOPRECORDING_FINISHED);
2053                        stopRecording();
2054                    }
2055                }
2056                handlePowerDown();
2057                forceToHeadsetMode();
2058                break;
2059
2060            case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
2061                if (mPowerStatus == POWER_UP) {
2062                    mPausedByTransientLossOfFocus = true;
2063                }
2064                // play back audio will output with music audio
2065                // May be affect other recorder app, but the flow can not be
2066                // execute earlier,
2067                // It should ensure execute after start/stop record.
2068                if (mFmRecorder != null) {
2069                    int fmState = mFmRecorder.getState();
2070                    if (fmState == FmRecorder.STATE_RECORDING) {
2071                        mFmServiceHandler.removeMessages(
2072                                FmListener.MSGID_STARTRECORDING_FINISHED);
2073                        mFmServiceHandler.removeMessages(
2074                                FmListener.MSGID_STOPRECORDING_FINISHED);
2075                        stopRecording();
2076                    }
2077                }
2078                handlePowerDown();
2079                forceToHeadsetMode();
2080                break;
2081
2082            case AudioManager.AUDIOFOCUS_GAIN:
2083                if (FmUtils.getIsSpeakerModeOnFocusLost(mContext)) {
2084                    setForceUse(true);
2085                    FmUtils.setIsSpeakerModeOnFocusLost(mContext, false);
2086                }
2087                if ((mPowerStatus != POWER_UP) && mPausedByTransientLossOfFocus) {
2088                    final int bundleSize = 1;
2089                    mFmServiceHandler.removeMessages(FmListener.MSGID_POWERUP_FINISHED);
2090                    mFmServiceHandler.removeMessages(FmListener.MSGID_POWERDOWN_FINISHED);
2091                    Bundle bundle = new Bundle(bundleSize);
2092                    bundle.putFloat(FM_FREQUENCY, FmUtils.computeFrequency(mCurrentStation));
2093                    handlePowerUp(bundle);
2094                }
2095                setMute(false);
2096                break;
2097
2098            case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
2099                setMute(true);
2100                break;
2101
2102            default:
2103                break;
2104        }
2105    }
2106
2107    private void forceToHeadsetMode() {
2108        if (mIsSpeakerUsed && isHeadSetIn()) {
2109            AudioSystem.setForceUse(FOR_PROPRIETARY, AudioSystem.FORCE_NONE);
2110            // save user's option to shared preferences.
2111            FmUtils.setIsSpeakerModeOnFocusLost(mContext, true);
2112        }
2113    }
2114
2115    /**
2116     * FM Radio listener record
2117     */
2118    private static class Record {
2119        int mHashCode; // hash code
2120        FmListener mCallback; // call back
2121    }
2122
2123    /**
2124     * Register FM Radio listener, activity get service state should call this
2125     * method register FM Radio listener
2126     *
2127     * @param callback FM Radio listener
2128     */
2129    public void registerFmRadioListener(FmListener callback) {
2130        synchronized (mRecords) {
2131            // register callback in AudioProfileService, if the callback is
2132            // exist, just replace the event.
2133            Record record = null;
2134            int hashCode = callback.hashCode();
2135            final int n = mRecords.size();
2136            for (int i = 0; i < n; i++) {
2137                record = mRecords.get(i);
2138                if (hashCode == record.mHashCode) {
2139                    return;
2140                }
2141            }
2142            record = new Record();
2143            record.mHashCode = hashCode;
2144            record.mCallback = callback;
2145            mRecords.add(record);
2146        }
2147    }
2148
2149    /**
2150     * Call back from service to activity
2151     *
2152     * @param bundle The message to activity
2153     */
2154    private void notifyActivityStateChanged(Bundle bundle) {
2155        if (!mRecords.isEmpty()) {
2156            synchronized (mRecords) {
2157                Iterator<Record> iterator = mRecords.iterator();
2158                while (iterator.hasNext()) {
2159                    Record record = (Record) iterator.next();
2160
2161                    FmListener listener = record.mCallback;
2162
2163                    if (listener == null) {
2164                        iterator.remove();
2165                        return;
2166                    }
2167
2168                    listener.onCallBack(bundle);
2169                }
2170            }
2171        }
2172    }
2173
2174    /**
2175     * Call back from service to the current request activity
2176     * Scan need only notify FmFavoriteActivity if current is FmFavoriteActivity
2177     *
2178     * @param bundle The message to activity
2179     */
2180    private void notifyCurrentActivityStateChanged(Bundle bundle) {
2181        if (!mRecords.isEmpty()) {
2182            Log.d(TAG, "notifyCurrentActivityStateChanged = " + mRecords.size());
2183            synchronized (mRecords) {
2184                if (mRecords.size() > 0) {
2185                    Record record  = mRecords.get(mRecords.size() - 1);
2186                    FmListener listener = record.mCallback;
2187                    if (listener == null) {
2188                        mRecords.remove(record);
2189                        return;
2190                    }
2191                    listener.onCallBack(bundle);
2192                }
2193            }
2194        }
2195    }
2196
2197    /**
2198     * Unregister FM Radio listener
2199     *
2200     * @param callback FM Radio listener
2201     */
2202    public void unregisterFmRadioListener(FmListener callback) {
2203        remove(callback.hashCode());
2204    }
2205
2206    /**
2207     * Remove call back according hash code
2208     *
2209     * @param hashCode The call back hash code
2210     */
2211    private void remove(int hashCode) {
2212        synchronized (mRecords) {
2213            Iterator<Record> iterator = mRecords.iterator();
2214            while (iterator.hasNext()) {
2215                Record record = (Record) iterator.next();
2216                if (record.mHashCode == hashCode) {
2217                    iterator.remove();
2218                }
2219            }
2220        }
2221    }
2222
2223    /**
2224     * Check recording sd card is unmount
2225     *
2226     * @param intent The unmount sd card intent
2227     *
2228     * @return true or false indicate whether current recording sd card is
2229     *         unmount or not
2230     */
2231    public boolean isRecordingCardUnmount(Intent intent) {
2232        String unmountSDCard = intent.getData().toString();
2233        Log.d(TAG, "unmount sd card file path: " + unmountSDCard);
2234        return unmountSDCard.equalsIgnoreCase("file://" + sRecordingSdcard) ? true : false;
2235    }
2236
2237    private int[] updateStations(int[] stations) {
2238        Log.d(TAG, "updateStations.firstValidstation:" + Arrays.toString(stations));
2239        int firstValidstation = mCurrentStation;
2240
2241        int stationNum = 0;
2242        if (null != stations) {
2243            int searchedListSize = stations.length;
2244            if (mIsDistanceExceed) {
2245                FmStation.cleanSearchedStations(mContext);
2246                for (int j = 0; j < searchedListSize; j++) {
2247                    int freqSearched = stations[j];
2248                    if (FmUtils.isValidStation(freqSearched) &&
2249                            !FmStation.isFavoriteStation(mContext, freqSearched)) {
2250                        FmStation.insertStationToDb(mContext, freqSearched, null);
2251                    }
2252                }
2253            } else {
2254                // get stations from db
2255                stationNum = updateDBInLocation(stations);
2256            }
2257        }
2258
2259        Log.d(TAG, "updateStations.firstValidstation:" + firstValidstation +
2260                ",stationNum:" + stationNum);
2261        return (new int[] {
2262                firstValidstation, stationNum
2263        });
2264    }
2265
2266    /**
2267     * update DB, keep favorite and rds which is searched this time,
2268     * delete rds from db which is not searched this time.
2269     * @param stations
2270     * @return number of valid searched stations
2271     */
2272    private int updateDBInLocation(int[] stations) {
2273        int stationNum = 0;
2274        int searchedListSize = stations.length;
2275        ArrayList<Integer> stationsInDB = new ArrayList<Integer>();
2276        Cursor cursor = null;
2277        try {
2278            // get non favorite stations
2279            cursor = mContext.getContentResolver().query(Station.CONTENT_URI,
2280                    new String[] { FmStation.Station.FREQUENCY },
2281                    FmStation.Station.IS_FAVORITE + "=0",
2282                    null, FmStation.Station.FREQUENCY);
2283            if ((null != cursor) && cursor.moveToFirst()) {
2284
2285                do {
2286                    int freqInDB = cursor.getInt(cursor.getColumnIndex(
2287                            FmStation.Station.FREQUENCY));
2288                    stationsInDB.add(freqInDB);
2289                } while (cursor.moveToNext());
2290
2291            } else {
2292                Log.d(TAG, "updateDBInLocation, insertSearchedStation cursor is null");
2293            }
2294        } finally {
2295            if (null != cursor) {
2296                cursor.close();
2297            }
2298        }
2299
2300        int listSizeInDB = stationsInDB.size();
2301        // delete station if db frequency is not in searched list
2302        for (int i = 0; i < listSizeInDB; i++) {
2303            int freqInDB = stationsInDB.get(i);
2304            for (int j = 0; j < searchedListSize; j++) {
2305                int freqSearched = stations[j];
2306                if (freqInDB == freqSearched) {
2307                    break;
2308                }
2309                if (j == (searchedListSize - 1) && freqInDB != freqSearched) {
2310                    // delete from db
2311                    FmStation.deleteStationInDb(mContext, freqInDB);
2312                }
2313            }
2314        }
2315
2316        // add to db if station is not in db
2317        for (int j = 0; j < searchedListSize; j++) {
2318            int freqSearched = stations[j];
2319            if (FmUtils.isValidStation(freqSearched)) {
2320                stationNum++;
2321                if (!stationsInDB.contains(freqSearched)
2322                        && !FmStation.isFavoriteStation(mContext, freqSearched)) {
2323                    // insert to db
2324                    FmStation.insertStationToDb(mContext, freqSearched, "");
2325                }
2326            }
2327        }
2328        return stationNum;
2329    }
2330
2331    /**
2332     * The background handler
2333     */
2334    class FmRadioServiceHandler extends Handler {
2335        public FmRadioServiceHandler(Looper looper) {
2336            super(looper);
2337        }
2338
2339        @Override
2340        public void handleMessage(Message msg) {
2341            Bundle bundle;
2342            boolean isPowerup = false;
2343            boolean isSwitch = true;
2344
2345            switch (msg.what) {
2346
2347                // power up
2348                case FmListener.MSGID_POWERUP_FINISHED:
2349                    bundle = msg.getData();
2350                    handlePowerUp(bundle);
2351                    break;
2352
2353                // power down
2354                case FmListener.MSGID_POWERDOWN_FINISHED:
2355                    handlePowerDown();
2356                    break;
2357
2358                // fm exit
2359                case FmListener.MSGID_FM_EXIT:
2360                    if (mIsSpeakerUsed) {
2361                        setForceUse(false);
2362                    }
2363                    powerDown();
2364                    closeDevice();
2365
2366                    bundle = new Bundle(1);
2367                    bundle.putInt(FmListener.CALLBACK_FLAG, FmListener.MSGID_FM_EXIT);
2368                    notifyActivityStateChanged(bundle);
2369                    // Finish favorite when exit FM
2370                    if (sExitListener != null) {
2371                        sExitListener.onExit();
2372                    }
2373                    break;
2374
2375                // switch antenna
2376                case FmListener.MSGID_SWITCH_ANTENNA:
2377                    bundle = msg.getData();
2378                    int value = bundle.getInt(FmListener.SWITCH_ANTENNA_VALUE);
2379
2380                    // if ear phone insert, need dismiss plugin earphone
2381                    // dialog
2382                    // if earphone plug out and it is not play recorder
2383                    // state, show plug dialog.
2384                    if (0 == value) {
2385                        // powerUpAsync(FMRadioUtils.computeFrequency(mCurrentStation));
2386                        bundle.putInt(FmListener.CALLBACK_FLAG,
2387                                FmListener.MSGID_SWITCH_ANTENNA);
2388                        bundle.putBoolean(FmListener.KEY_IS_SWITCH_ANTENNA, true);
2389                        notifyActivityStateChanged(bundle);
2390                    } else {
2391                        // ear phone plug out, and recorder state is not
2392                        // play recorder state,
2393                        // show dialog.
2394                        if (mRecordState != FmRecorder.STATE_PLAYBACK) {
2395                            bundle.putInt(FmListener.CALLBACK_FLAG,
2396                                    FmListener.MSGID_SWITCH_ANTENNA);
2397                            bundle.putBoolean(FmListener.KEY_IS_SWITCH_ANTENNA, false);
2398                            notifyActivityStateChanged(bundle);
2399                        }
2400                    }
2401                    break;
2402
2403                // tune to station
2404                case FmListener.MSGID_TUNE_FINISHED:
2405                    bundle = msg.getData();
2406                    float tuneStation = bundle.getFloat(FM_FREQUENCY);
2407                    boolean isTune = tuneStation(tuneStation);
2408                    // if tune fail, pass current station to update ui
2409                    if (!isTune) {
2410                        tuneStation = FmUtils.computeFrequency(mCurrentStation);
2411                    }
2412                    bundle = new Bundle(3);
2413                    bundle.putInt(FmListener.CALLBACK_FLAG,
2414                            FmListener.MSGID_TUNE_FINISHED);
2415                    bundle.putBoolean(FmListener.KEY_IS_TUNE, isTune);
2416                    bundle.putFloat(FmListener.KEY_TUNE_TO_STATION, tuneStation);
2417                    notifyActivityStateChanged(bundle);
2418                    break;
2419
2420                // seek to station
2421                case FmListener.MSGID_SEEK_FINISHED:
2422                    bundle = msg.getData();
2423                    mIsSeeking = true;
2424                    float seekStation = seekStation(bundle.getFloat(FM_FREQUENCY),
2425                            bundle.getBoolean(OPTION));
2426                    boolean isStationTunningSuccessed = false;
2427                    int station = FmUtils.computeStation(seekStation);
2428                    if (FmUtils.isValidStation(station)) {
2429                        isStationTunningSuccessed = tuneStation(seekStation);
2430                    }
2431                    // if tune fail, pass current station to update ui
2432                    if (!isStationTunningSuccessed) {
2433                        seekStation = FmUtils.computeFrequency(mCurrentStation);
2434                    }
2435                    bundle = new Bundle(2);
2436                    bundle.putInt(FmListener.CALLBACK_FLAG,
2437                            FmListener.MSGID_TUNE_FINISHED);
2438                    bundle.putBoolean(FmListener.KEY_IS_TUNE, isStationTunningSuccessed);
2439                    bundle.putFloat(FmListener.KEY_TUNE_TO_STATION, seekStation);
2440                    notifyActivityStateChanged(bundle);
2441                    mIsSeeking = false;
2442                    break;
2443
2444                // start scan
2445                case FmListener.MSGID_SCAN_FINISHED:
2446                    int[] stations = null;
2447                    int[] result = null;
2448                    int scanTuneStation = 0;
2449                    boolean isScan = true;
2450                    mIsScanning = true;
2451                    if (powerUp(FmUtils.DEFAULT_STATION_FLOAT)) {
2452                        stations = startScan();
2453                    }
2454
2455                    // check whether cancel scan
2456                    if ((null != stations) && stations[0] == -100) {
2457                        isScan = false;
2458                        result = new int[] {
2459                                -1, 0
2460                        };
2461                    } else {
2462                        result = updateStations(stations);
2463                        scanTuneStation = result[0];
2464                        tuneStation(FmUtils.computeFrequency(mCurrentStation));
2465                    }
2466
2467                    /*
2468                     * if there is stop command when scan, so it needs to mute
2469                     * fm avoid fm sound come out.
2470                     */
2471                    if (mIsAudioFocusHeld) {
2472                        setMute(false);
2473                    }
2474                    bundle = new Bundle(4);
2475                    bundle.putInt(FmListener.CALLBACK_FLAG,
2476                            FmListener.MSGID_SCAN_FINISHED);
2477                    //bundle.putInt(FmListener.KEY_TUNE_TO_STATION, scanTuneStation);
2478                    bundle.putInt(FmListener.KEY_STATION_NUM, result[1]);
2479                    bundle.putBoolean(FmListener.KEY_IS_SCAN, isScan);
2480
2481                    mIsScanning = false;
2482                    // Only notify the newest request activity
2483                    notifyCurrentActivityStateChanged(bundle);
2484                    break;
2485
2486                // audio focus changed
2487                case FmListener.MSGID_AUDIOFOCUS_CHANGED:
2488                    bundle = msg.getData();
2489                    int focusState = bundle.getInt(FmListener.KEY_AUDIOFOCUS_CHANGED);
2490                    updateAudioFocus(focusState);
2491                    break;
2492
2493                case FmListener.MSGID_SET_RDS_FINISHED:
2494                    bundle = msg.getData();
2495                    setRds(bundle.getBoolean(OPTION));
2496                    break;
2497
2498                case FmListener.MSGID_SET_MUTE_FINISHED:
2499                    bundle = msg.getData();
2500                    setMute(bundle.getBoolean(OPTION));
2501                    break;
2502
2503                case FmListener.MSGID_ACTIVE_AF_FINISHED:
2504                    activeAf();
2505                    break;
2506
2507                /********** recording **********/
2508                case FmListener.MSGID_STARTRECORDING_FINISHED:
2509                    startRecording();
2510                    break;
2511
2512                case FmListener.MSGID_STOPRECORDING_FINISHED:
2513                    stopRecording();
2514                    break;
2515
2516                case FmListener.MSGID_RECORD_MODE_CHANED:
2517                    bundle = msg.getData();
2518                    setRecordingMode(bundle.getBoolean(OPTION));
2519                    break;
2520
2521                case FmListener.MSGID_SAVERECORDING_FINISHED:
2522                    bundle = msg.getData();
2523                    saveRecording(bundle.getString(RECODING_FILE_NAME));
2524                    break;
2525
2526                default:
2527                    break;
2528            }
2529        }
2530
2531    }
2532
2533    /**
2534     * handle power down, execute power down and call back to activity.
2535     */
2536    private void handlePowerDown() {
2537        Bundle bundle;
2538        boolean isPowerdown = powerDown();
2539        bundle = new Bundle(1);
2540        bundle.putInt(FmListener.CALLBACK_FLAG, FmListener.MSGID_POWERDOWN_FINISHED);
2541        notifyActivityStateChanged(bundle);
2542    }
2543
2544    /**
2545     * handle power up, execute power up and call back to activity.
2546     *
2547     * @param bundle power up frequency
2548     */
2549    private void handlePowerUp(Bundle bundle) {
2550        boolean isPowerUp = false;
2551        boolean isSwitch = true;
2552        float curFrequency = bundle.getFloat(FM_FREQUENCY);
2553
2554        if (!isAntennaAvailable()) {
2555            Log.d(TAG, "handlePowerUp, earphone is not ready");
2556            bundle = new Bundle(2);
2557            bundle.putInt(FmListener.CALLBACK_FLAG, FmListener.MSGID_SWITCH_ANTENNA);
2558            bundle.putBoolean(FmListener.KEY_IS_SWITCH_ANTENNA, false);
2559            notifyActivityStateChanged(bundle);
2560            return;
2561        }
2562        if (powerUp(curFrequency)) {
2563            if (FmUtils.isFirstTimePlayFm(mContext)) {
2564                isPowerUp = firstPlaying(curFrequency);
2565                FmUtils.setIsFirstTimePlayFm(mContext);
2566            } else {
2567                isPowerUp = playFrequency(curFrequency);
2568            }
2569            mPausedByTransientLossOfFocus = false;
2570        }
2571        bundle = new Bundle(2);
2572        bundle.putInt(FmListener.CALLBACK_FLAG, FmListener.MSGID_POWERUP_FINISHED);
2573        bundle.putInt(FmListener.KEY_TUNE_TO_STATION, mCurrentStation);
2574        notifyActivityStateChanged(bundle);
2575    }
2576
2577    /**
2578     * check FM is foreground or background
2579     */
2580    public boolean isActivityForeground() {
2581        return (mIsFmMainForeground || mIsFmFavoriteForeground || mIsFmRecordForeground);
2582    }
2583
2584    /**
2585     * mark FmMainActivity is foreground or not
2586     * @param isForeground
2587     */
2588    public void setFmMainActivityForeground(boolean isForeground) {
2589        mIsFmMainForeground = isForeground;
2590    }
2591
2592    /**
2593     * mark FmFavoriteActivity activity is foreground or not
2594     * @param isForeground
2595     */
2596    public void setFmFavoriteForeground(boolean isForeground) {
2597        mIsFmFavoriteForeground = isForeground;
2598    }
2599
2600    /**
2601     * mark FmRecordActivity activity is foreground or not
2602     * @param isForeground
2603     */
2604    public void setFmRecordActivityForeground(boolean isForeground) {
2605        mIsFmRecordForeground = isForeground;
2606    }
2607
2608    /**
2609     * Get the recording sdcard path when staring record
2610     *
2611     * @return sdcard path like "/storage/sdcard0"
2612     */
2613    public static String getRecordingSdcard() {
2614        return sRecordingSdcard;
2615    }
2616
2617    /**
2618     * The listener interface for exit
2619     */
2620    public interface OnExitListener {
2621        /**
2622         * When Service finish, should notify FmFavoriteActivity to finish
2623         */
2624        void onExit();
2625    }
2626
2627    /**
2628     * Register the listener for exit
2629     *
2630     * @param listener The listener want to know the exit event
2631     */
2632    public static void registerExitListener(OnExitListener listener) {
2633        sExitListener = listener;
2634    }
2635
2636    /**
2637     * Unregister the listener for exit
2638     *
2639     * @param listener The listener want to know the exit event
2640     */
2641    public static void unregisterExitListener(OnExitListener listener) {
2642        sExitListener = null;
2643    }
2644
2645    /**
2646     * Get the latest recording name the show name in save dialog but saved in
2647     * service
2648     *
2649     * @return The latest recording name or null for not modified
2650     */
2651    public String getModifiedRecordingName() {
2652        return mModifiedRecordingName;
2653    }
2654
2655    /**
2656     * Set the latest recording name if modify the default name
2657     *
2658     * @param name The latest recording name or null for not modified
2659     */
2660    public void setModifiedRecordingName(String name) {
2661        mModifiedRecordingName = name;
2662    }
2663
2664    @Override
2665    public void onTaskRemoved(Intent rootIntent) {
2666        exitFm();
2667        super.onTaskRemoved(rootIntent);
2668    }
2669
2670    private boolean firstPlaying(float frequency) {
2671        if (mPowerStatus != POWER_UP) {
2672            Log.w(TAG, "firstPlaying, FM is not powered up");
2673            return false;
2674        }
2675        boolean isSeekTune = false;
2676        float seekStation = FmNative.seek(frequency, false);
2677        int station = FmUtils.computeStation(seekStation);
2678        if (FmUtils.isValidStation(station)) {
2679            isSeekTune = FmNative.tune(seekStation);
2680            if (isSeekTune) {
2681                playFrequency(seekStation);
2682            }
2683        }
2684        // if tune fail, pass current station to update ui
2685        if (!isSeekTune) {
2686            seekStation = FmUtils.computeFrequency(mCurrentStation);
2687        }
2688        return isSeekTune;
2689    }
2690
2691    /**
2692     * Set the mIsDistanceExceed
2693     * @param exceed true is exceed, false is not exceed
2694     */
2695    public void setDistanceExceed(boolean exceed) {
2696        mIsDistanceExceed = exceed;
2697    }
2698
2699    /**
2700     * Set notification class name
2701     * @param clsName The target class name of activity
2702     */
2703    public void setNotificationClsName(String clsName) {
2704        mTargetClassName = clsName;
2705    }
2706}
2707