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