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