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