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