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