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