1. Introduction of FM main categories
FmMainActivity.java main interface
FmService.java core
FmNative.java calls JNI and the underlying communication
2. Introduction to the main functions of FM
1. Radio playback:
1) Search Taiwan
2) Collection
3) Headphone/outside playback
4) Cutting table
5) Timing off
6) Flight mode
2. Recording and playback
Three, process analysis
1. Don't insert headphones
If you start FM without inserting the earphones, you cannot play FM, and it will prompt "Please insert earphones"
Code flow analysis:
The layout will be loaded in FmMainActivity.java-->onCreate()
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
......
initUiComponent();
......
}
FmMainActivity.java-->initUiComponent()
private void initUiComponent() {
......
mMainLayout = (LinearLayout) findViewById(R.id.main_view);
mNoHeadsetLayout = (RelativeLayout) findViewById(R.id.no_headset);
......
}
Two layouts are loaded here, no_headset and main_view, main_view is the interface when headphones are plugged in for normal listening, and no_headset is the prompt interface when headphones are not plugged in. Let’s analyze no_headset first.
In FmMainActivity.java-->changeToNoHeadsetLayout(), mMainLayout will be hidden, and mNoHeadsetLayout will be set to visible
/**
* change to no headset layout
*/
private void changeToNoHeadsetLayout() {
......
mMainLayout.setVisibility(View.GONE);
......
mNoHeadsetLayout.setVisibility(View.VISIBLE);
......
}
Next, look at where changeToNoHeadsetLayout() is called
@Override
public void onStart() {
super.onStart();
......
} else if (isAntennaAvailable()) {
changeToMainLayout();
} else {
changeToNoHeadsetLayout();
}
......
}
It can be seen that isAntennaAvailable() is used to determine whether there is a headset
FmMainActivity.java-->isAntennaAvailable()
private boolean isAntennaAvailable() {
if (FmUtils.supportShortAntenna) {
return true;
} else {
return isWiredHeadsetOn();
//mAudioManager.isWiredHeadsetOn();
}
}
FmMainActivity.java-->isWiredHeadsetOn()
private boolean isWiredHeadsetOn() {
//bug939562 return true ,Only wired headset is plugged in
AudioDeviceInfo[] audioDeviceInfo = mAudioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS);
for (AudioDeviceInfo info : audioDeviceInfo) {
//wired headset(with mic or not) is pluged in
if ((info.getType() == AudioDeviceInfo.TYPE_WIRED_HEADSET)||(info.getType() == AudioDeviceInfo.TYPE_WIRED_HEADPHONES)){
Log.d(TAG,"Wired headset is exist");
return true;
}
}
return false;
//return true ,type-c or wired headset is plugged in
//mAudioManager.isWiredHeadsetOn();
}
It can be seen that when the app is started, the tpye of AudioDeviceInfo is used to determine whether the headset is inserted
2. Plug in headphones
When the headset is plugged in, it will switch to the main_view interface, let’s look at the process below
Before looking at the headset plug-in monitoring process, you need to talk about the startup process of FmService.
@Override
public void onStart() {
super.onStart();
......
if (null == startForegroundService(new Intent(FmMainActivity.this, FmService.class))) {
Log.e(TAG, "onStart, cannot start FM service");
return;
}
mIsServiceStarted = true;
mIsServiceBinded = bindService(new Intent(FmMainActivity.this, FmService.class),
mServiceConnection, Context.BIND_AUTO_CREATE);
......
}
After FmService onBind() is successful, the onServiceConnected() method in ServiceConnection will be called
private final ServiceConnection mServiceConnection = new ServiceConnection() {
/**
* called by system when bind service
*
* @param className component name
* @param service service binder
*/
@Override
public void onServiceConnected(ComponentName className, IBinder service) {
mService = ((FmService.ServiceBinder) service).getService();
if (null == mService) {
Log.e(TAG, "onServiceConnected, mService is null");
finish();
return;
}
Log.d(TAG, "onServiceConnected, mService is not null");
refreshRDSVisiable();
mService.registerFmRadioListener(mFmRadioListener);
mService.setFmActivityForground(mIsActivityForeground);
// bug568587, new feature FM new UI
mService.setTimerListenr(new MyTimeCountListener());
if (!mService.isServiceInited()) {
mService.initService();
powerUpFm();
} else {
if (mService.isDeviceOpen()) {
updateMenuStatus();
} else {
// Normal case will not come here
// Need to exit FM for this case
exitService();
finish();
}
}
}
/**
* When unbind service will call this method
*
* @param className The component name
*/
@Override
public void onServiceDisconnected(ComponentName className) {
}
};
Here will call FmService.java-->registerFmRadioListener() to monitor mFmRadioListener
private FmListener mFmRadioListener = new FmListener() {
@Override
public void onCallBack(Bundle bundle) {
int flag = bundle.getInt(FmListener.CALLBACK_FLAG);
if (flag == FmListener.MSGID_FM_EXIT) {
mHandler.removeCallbacksAndMessages(null);
}
// remove tag message first, avoid too many same messages in queue.
Message msg = mHandler.obtainMessage(flag);
msg.setData(bundle);
mHandler.removeMessages(flag);
mHandler.sendMessage(msg);
}
};
FmListener is an interface, FmMainActivity.java implements this interface
Look at FmService.java-->registerFmRadioListener() in reverse
public void registerFmRadioListener(FmListener callback) {
synchronized (mRecords) {
// register callback in AudioProfileService, if the callback is
// exist, just replace the event.
Record record = null;
int hashCode = callback.hashCode();
final int n = mRecords.size();
for (int i = 0; i < n; i++) {
record = mRecords.get(i);
if (hashCode == record.mHashCode) {
return;
}
}
record = new Record();
record.mHashCode = hashCode;
record.mCallback = callback;
mRecords.add(record);
}
}
Here will add FmListener to a list, because in FmFavoriteActivity FmMainActivity FmRecordActivity will register to monitor this interface
After the introduction of this monitoring mechanism, we will specifically talk about how FmService.java monitors the earphone plug-in event to notify FmMainActivity and let it update the interface.
There is an internal broadcast receiver FmServiceBroadcastReceiver extends BroadcastReceiver in FmService.java, and the Intent.ACTION_HEADSET_PLUG event will be monitored in FmServiceBroadcastReceiver
After listening, call switchAntennaAsync()
public void switchAntennaAsync(int antenna) {
final int bundleSize = 1;
mFmServiceHandler.removeMessages(FmListener.MSGID_SWITCH_ANTENNA);
Bundle bundle = new Bundle(bundleSize);
bundle.putInt(FmListener.SWITCH_ANTENNA_VALUE, antenna);
Message msg = mFmServiceHandler.obtainMessage(FmListener.MSGID_SWITCH_ANTENNA);
msg.setData(bundle);
mFmServiceHandler.sendMessage(msg);
}
case FmListener.MSGID_SWITCH_ANTENNA:
bundle = msg.getData();
int value = bundle.getInt(FmListener.SWITCH_ANTENNA_VALUE);
// if ear phone insert, need dismiss plugin earphone
// dialog
// if earphone plug out and it is not play recorder
// state, show plug dialog.
bundle.putInt(FmListener.CALLBACK_FLAG,
FmListener.MSGID_SWITCH_ANTENNA);
bundle.putBoolean(FmListener.KEY_IS_SWITCH_ANTENNA, (0 == value));
notifyActivityStateChanged(bundle);
break;
private void notifyActivityStateChanged(Bundle bundle) {
if (!mRecords.isEmpty()) {
synchronized (mRecords) {
Iterator<Record> iterator = mRecords.iterator();
while (iterator.hasNext()) {
Record record = (Record) iterator.next();
FmListener listener = record.mCallback;
if (listener == null) {
iterator.remove();
return;
}
listener.onCallBack(bundle);
}
}
}
}
Here it is back to FmMainActivity.java-->FmListener .java-->onCallBack()
private FmListener mFmRadioListener = new FmListener() {
@Override
public void onCallBack(Bundle bundle) {
int flag = bundle.getInt(FmListener.CALLBACK_FLAG);
if (flag == FmListener.MSGID_FM_EXIT) {
mHandler.removeCallbacksAndMessages(null);
}
// remove tag message first, avoid too many same messages in queue.
Message msg = mHandler.obtainMessage(flag);
msg.setData(bundle);
mHandler.removeMessages(flag);
mHandler.sendMessage(msg);
}
};
Passed from the bundle is MSGID_SWITCH_ANTENNA
case FmListener.MSGID_SWITCH_ANTENNA:
......
if (hasAntenna) {
cancelNoHeadsetAnimation();
if (mIsActivityForeground) {
playMainAnimation();
} else {
changeToMainLayout();
}
......
2. Channel search function
Click the menu menu of the FmFavoriteActivity.java interface, and click refresh
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.fm_station_list_refresh:
if ((null != mService) && (!mService.isSeeking())) {
onOptionsItemSelectedScan(false);
}
break;
}
return super.onOptionsItemSelected(item);
}
FmFavoriteActivity.java-->onOptionsItemSelectedScan()
private void onOptionsItemSelectedScan(boolean fromCurrent) {
if (null != mService) {
showTipView(true);
if (fromCurrent) {
mService.startScanAsyncFromCurrent(mCurrentStationItem.stationFreq);
} else {
mService.startScanAsync();
}
}
}
FmService.java-->startScanAsync()
/**
* Scan stations
*/
public void startScanAsync() {
mFmServiceHandler.removeMessages(FmListener.MSGID_SCAN_FINISHED);
mFmServiceHandler.sendEmptyMessage(FmListener.MSGID_SCAN_FINISHED);
}
// start scan
case FmListener.MSGID_SCAN_FINISHED:
......
stations = startScan(start_freq);
/**
* @}
*/
}
private int[] startScan(int start_freq) {
......
stations = mFmManager.autoScan(start_freq);
......
return stations;
}
FmManagerSelect.java -->autoScan()
public int[] autoScan(int start_freq) {
if (mIsUseBrcmFmChip) {
return mFmManagerForBrcm.autoScanStation(start_freq);
} else {
short[] stationsInShort = null;
int[] stations = null;
stationsInShort = FmNative.autoScan(start_freq);
if (null != stationsInShort) {
int size = stationsInShort.length;
stations = new int[size];
for (int i = 0; i < size; i++) {
stations[i] = stationsInShort[i];
}
}
return stations;
}
}
Call FmNative.java-->autoScan() here
static native short[] autoScan(int start_freq);
After the scan is successful, the scanned data will be inserted into the database
private int[] updateStations(int[] stations) {
Log.d(TAG, "updateStations.firstValidstation:" + Arrays.toString(stations));
int firstValidstation = mCurrentStationItem.stationFreq;
FmStation.cleanSearchedStations(mContext);
int stationNum = batchInsertStationToDb(stations, null);
int searchedNum = (stations == null ? 0 : stations.length);
Log.d(TAG, "updateStations.firstValidstation:" + firstValidstation +
",searchedNum:" + searchedNum);
return (new int[]{
firstValidstation, searchedNum
});
}
private int batchInsertStationToDb(int[] stations, List<FmStationItem> favoriteList) {
if (null == stations) return 0;
ArrayList<ContentProviderOperation> ops = new ArrayList<>();
ContentResolver resolver = mContext.getContentResolver();
for (int i = 0; i < stations.length; i++) {
if (!FmUtils.isValidStation(stations[i]) || FmStation.isFavoriteStation(mContext, stations[i])) {
Log.d(TAG, "station is favorite : " + stations[i]);
continue;
}
ContentProviderOperation.Builder op = ContentProviderOperation.newInsert(Station.CONTENT_URI);
op.withYieldAllowed(false);
Log.d(TAG, "station : " + stations[i]);
ContentValues values = new ContentValues();
values.clear();
values.put(Station.FREQUENCY, stations[i]);
values.put(Station.IS_SHOW, Station.IS_SCAN);
op.withValues(values);
ops.add(op.build());
}
Log.d(TAG, "ops size : " + ops.size());
int stationNum = ops.size();
if (stationNum > 0) {
try {
ContentProviderResult[] result = resolver.applyBatch(FmStation.AUTHORITY, ops);
ops.clear();
Log.d(TAG, "batch opreate db result count : " + result.length);
} catch (Exception e) {
Log.d(TAG, "Batch operate exception");
e.printStackTrace();
}
} else {
mContext.getContentResolver().notifyChange(FmStation.Station.CONTENT_URI, null);
}
return stationNum;
}
After the search is successful, call FmService.java-->notifyCurrentActivityStateChanged() to notify FmFavoriteActivity.java to update the interface
private void refreshFmFavorityList(Cursor cursor) {
if (cursor == null) {
return;
}
mFavoriteList.clear();
mOtherList.clear();
mAllStationSet.clear();
for (int i = 0; i < cursor.getCount(); i++) {
FmStationItem item = new FmStationItem();
if (cursor.moveToFirst()) {
cursor.moveToPosition(i);
item.stationFreq = cursor.getInt(cursor
.getColumnIndex(FmStation.Station.FREQUENCY));
item.stationName = cursor.getString(cursor
.getColumnIndex(FmStation.Station.STATION_NAME));
item.rt = cursor.getString(cursor
.getColumnIndex(FmStation.Station.RADIO_TEXT));
item.isFavorite = cursor.getInt(cursor
.getColumnIndex(FmStation.Station.IS_FAVORITE));
item.isShow = cursor.getInt(cursor
.getColumnIndex(FmStation.Station.IS_SHOW));
item.ps = cursor.getString(cursor
.getColumnIndex(FmStation.Station.PROGRAM_SERVICE));
if (item.isFavorite == 0) {
mOtherList.add(item);
} else {
mFavoriteList.add(item);
}
mAllStationSet.add(item.stationFreq);
}
}
checkSelectedStationSet();
mMyAdapter.notifyDataSetChanged();
Log.d(TAG, "refreshFmFavorityList mAllStationSet.size:" + mAllStationSet.size());
}
Refresh the interface through the notifyDataSetChanged() of MyFavoriteAdapter
3. Collection
viewHolder.mImgLayout.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
/* bug568587, new feature FM new UI @{ */
if (0 == isFavorite) {
showToast(getString(R.string.toast_channel_added));
addFavorite(stationFreq);
} else {
showToast(getString(R.string.toast_channel_deleted));
deleteFromFavorite(stationFreq);
}
/* @} */
}
});
/**
* Add searched station as favorite station
*/
public void addFavorite(int stationFreq) {
// TODO it's on UI thread, change to sub thread
// update the station name and station type in database
// according the frequency
operateFmFavoriteStation(stationFreq, ACTION_ADD_FAVORITE_TYPE);
}
private void operateFmFavoriteStation(int stationFreq, int type) {
MyAsyncTask operateTask = new MyAsyncTask();
operateTask.execute(ACTION_OPERATE_TYPE, stationFreq, type);
}
class MyAsyncTask extends AsyncTask<Integer, Void, Cursor> {
@Override
protected Cursor doInBackground(Integer... params) {
Log.d(TAG, "operate database type:" + params[0]);
if (params[0] == ACTION_QUERY_TYPE) {
Cursor cursor = getData();
return cursor;
} else {
Log.d(TAG, "stationFreq=" + params[1]);
operateData(params[1], params[2]);
return null;
}
}
@Override
protected void onPostExecute(Cursor cursor) {
refreshFmFavorityList(cursor);
if (cursor != null) {
cursor.close();
}
}
}
private void operateData(int stationFreq, int type) {
switch (type) {
case ACTION_ADD_FAVORITE_TYPE:
FmStation.addToFavorite(mContext, stationFreq);
break;
case ACTION_REMOVE_FROM_FAVORITE_TYPE:
FmStation.removeFromFavorite(mContext, stationFreq);
break;
}
/**
* update db to mark it is a favorite frequency
*
* @param context The context
* @param frequency The target frequency
*/
public static void addToFavorite(Context context, int frequency) {
ContentValues values = new ContentValues(1);
values.put(Station.IS_FAVORITE, true);
values.put(Station.IS_SHOW,Station.IS_SCAN);
context.getContentResolver().update(
Station.CONTENT_URI,
values,
Station.FREQUENCY + "=?",
new String[] {
String.valueOf(frequency)
});
}
4. Play process
FmMainActivity.java
case R.id.play_button:
/**
* bug500046, for monkey test.
*
* @{
*/
if (null == mService) {
Log.e(TAG, "onClick case playbutton, mService is null");
return;
}
/**
* @}
*/
if (mService.getPowerStatus() == FmService.POWER_UP) {
if(mService.isMuted()){
mService.setMuteAsync(false,false);
}else{
powerDownFm();
}
} else {
powerUpFm();
}
break;
default:
Log.d(TAG, "mButtonClickListener.onClick, invalid view id");
break;
FmMainActivity.java -- >powerUpFm()
/**
* Power up FM
*/
private void powerUpFm() {
if(FmUtils.isAirplane(this)){
changeToAirPlaneLayout();
}else{
refreshImageButton(false);
refreshActionMenuItem(false);
refreshPopupMenuItem(false);
refreshPlayButton(false);
mService.powerUpAsync(FmUtils.computeFrequency(mCurrentStationItem.stationFreq));
}
}
Send MSGID_POWERUP_FINISHED
public void powerUpAsync(float frequency) {
final int bundleSize = 1;
mFmServiceHandler.removeMessages(FmListener.MSGID_POWERUP_FINISHED);
mFmServiceHandler.removeMessages(FmListener.MSGID_POWERDOWN_FINISHED);
Bundle bundle = new Bundle(bundleSize);
bundle.putFloat(FM_FREQUENCY, frequency);
Message msg = mFmServiceHandler.obtainMessage(FmListener.MSGID_POWERUP_FINISHED);
msg.setData(bundle);
mFmServiceHandler.sendMessage(msg);
}
FmService.java -->powerUpAsync()
// power up
case FmListener.MSGID_POWERUP_FINISHED:
bundle = msg.getData();
handlePowerUp(bundle);
break;
FmService.java -->handlePowerUp()
private void handlePowerUp(Bundle bundle) {
boolean isPowerUp = false;
boolean isSwitch = true;
float curFrequency = bundle.getFloat(FM_FREQUENCY);
if (FmUtils.isAirplane(this)) {
Log.d(TAG, "handlePowerUp, airplane is on");
return;
}
boolean isAntennaAvailable = isAntennaAvailable();
if (!FmUtils.supportShortAntenna) {
if (!isAntennaAvailable) {
Log.d(TAG, "handlePowerUp, earphone is not ready");
bundle = new Bundle(2);
bundle.putInt(FmListener.CALLBACK_FLAG, FmListener.MSGID_SWITCH_ANTENNA);
bundle.putBoolean(FmListener.KEY_IS_SWITCH_ANTENNA, false);
notifyActivityStateChanged(bundle);
return;
}
} else {
Log.d(TAG, "handlePowerUp: wether earphone is plugged in -->" + isAntennaAvailable);
switchAntenna(isAntennaAvailable ? 0 : 1);
}
if (powerUp(curFrequency)) {
if (FmUtils.isFirstTimePlayFm(mContext)) {
isPowerUp = firstPlaying(curFrequency);
FmUtils.setIsFirstTimePlayFm(mContext);
} else {
/**
* Bug546461 Tune radio again after power down then power up for brcm. Orig:
* isPowerUp = playFrequency(curFrequency);
* @{
*/
if (mFmManager.tuneRadioAgain(curFrequency)) {
isPowerUp = playFrequency(curFrequency);
}
/**
* @}
*/
}
mPausedByTransientLossOfFocus = false;
}
bundle = new Bundle(1);
bundle.putInt(FmListener.CALLBACK_FLAG, FmListener.MSGID_POWERUP_FINISHED);
notifyActivityStateChanged(bundle);
}
FmService.java -->playFrequency()
private boolean playFrequency(float frequency) {
Log.d(TAG, "playFrequency");
updatePlayingNotification();
// Start the RDS thread if RDS is supported.
// RDS function should be open status.
if (isRdsSupported() && FmUtils.isRDSOpen(mContext)) {
startRdsThread();
}
//bug492835, FM audio route change. if (!mWakeLock.isHeld()) { mWakeLock.acquire(); }
if (mIsSpeakerUsed) {
setForceUse(mIsSpeakerUsed);
}
enableFmAudio(true);
setRds(true);
setMute(false);
return (mPowerStatus == POWER_UP);
}
FmService.java -->enableFmAudio()
private void enableFmAudio(boolean enable) {
Log.d(TAG, "enableFmAudio:" + enable);
if (enable) {
if ((mPowerStatus != POWER_UP) || !mIsAudioFocusHeld) {
Log.d(TAG, "enableFmAudio, current not available return.mIsAudioFocusHeld:"
+ mIsAudioFocusHeld);
return;
}
ArrayList<AudioPatch> patches = new ArrayList<AudioPatch>();
mAudioManager.listAudioPatches(patches);
if (mAudioPatch == null) {
Log.d(TAG, "mAudioPatch == null");
if (isPatchMixerToEarphone(patches)) {
int status;
stopRender();
status = createAudioPatch();
if (status != AudioManager.SUCCESS) {
Log.d(TAG, "enableFmAudio: fallback as createAudioPatch failed");
startRender();
}
} else {
startRender();
}
}
} else {
releaseAudioPatch();
stopRender();
}
}
FmService.java -> startRender ()
private synchronized void startRender() {
//bug492835, FM audio route change.
mFmManager.setAudioPathEnable(AudioPath.FM_AUDIO_PATH_HEADSET, true);
mMediaSessionManager.setOnMediaKeyListener(mMediaKeyListener, null);
Log.d(TAG, "startRender,setOnMediaKeyListener");
mIsRender = true;
synchronized (mRenderLock) {
mRenderLock.notify();
}
}
FmManagerSelect.java -->setAudioPathEnable()
public boolean setAudioPathEnable(AudioPath path, boolean enable) {
if (mIsUseBrcmFmChip) {
if (enable) {
mFmManagerForBrcm.setAudioMode(FmAudioModeForBrcm.FM_AUDIO_MODE_BLEND);
//AudioSystem.setDeviceConnectionState(AudioManager.DEVICE_OUT_FM_HEADSET,
// AudioSystem.DEVICE_STATE_AVAILABLE, "", "");
mAudioManager.setDeviceConnectionStateForFM(AudioManager.DEVICE_OUT_FM_HEADSET,
AudioSystem.DEVICE_STATE_AVAILABLE, "", "");
} else {
//AudioSystem.setDeviceConnectionState(AudioManager.DEVICE_OUT_FM_HEADSET,
// AudioSystem.DEVICE_STATE_UNAVAILABLE, "", "");
mAudioManager.setDeviceConnectionStateForFM(AudioManager.DEVICE_OUT_FM_HEADSET,
AudioSystem.DEVICE_STATE_UNAVAILABLE, "", "");
}
return mFmManagerForBrcm.setAudioPath(convertAudioPathForBrcm(path));
} else {
if (enable) {
//AudioSystem.setDeviceConnectionState(AudioManager.DEVICE_OUT_FM_HEADSET,
mAudioManager.setDeviceConnectionStateForFM(AudioManager.DEVICE_OUT_FM_HEADSET,
AudioSystem.DEVICE_STATE_AVAILABLE, "", "");
return true;
} else {
//AudioSystem.setDeviceConnectionState(AudioManager.DEVICE_OUT_FM_HEADSET,
mAudioManager.setDeviceConnectionStateForFM(AudioManager.DEVICE_OUT_FM_HEADSET,
AudioSystem.DEVICE_STATE_UNAVAILABLE, "", "");
return true;
}
}
}
AudioManager.java-->setDeviceConnectionStateForFM()
public void setDeviceConnectionStateForFM(int device, int state,
String device_address, String device_name) {
final IAudioService service = getService();
try {
service.setDeviceConnectionStateForFM(device, state, device_address, device_name,mICallBack);
} catch (RemoteException e) {
Log.e(TAG, "Dead object in setDeviceConnectionStateForFM ", e);
}
}
Second, recording
The recording is FmRecordActivity
Click start and call mService.startRecordingAsync();
/**
* Start recording
*/
public void startRecordingAsync() {
mFmServiceHandler.removeMessages(FmListener.MSGID_STARTRECORDING_FINISHED);
mFmServiceHandler.sendEmptyMessage(FmListener.MSGID_STARTRECORDING_FINISHED);
}
case FmListener.MSGID_STARTRECORDING_FINISHED:
startRecording();
break;
public void startRecording(Context context) {
mRecordTime = 0;
Uri dir = null;
FileDescriptor fd = null;
Uri externalStorageUri=null;
/**
* Bug529776 Check the main card state Original Android code:
*
* @{ // Check external storage if
* (!Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())) {
* Log.e(TAG, "startRecording, no external storage available");
* setError(ERROR_SDCARD_NOT_PRESENT); return; } /** @}
*/
if (FmUtils.isExternalStorage()) {
externalStorageUri =FmUtils.getCurrentAccessUri(FmUtils.FM_RECORD_STORAGE_PATH_NAME);
}
String recordingSdcard = FmUtils.getDefaultStoragePath();
Log.d(TAG,"externalStorageUri="+externalStorageUri+" recordingSdcard="+recordingSdcard);
/**
* bug474747, recording path selection.
*
* @{
*/
if (recordingSdcard == null
|| recordingSdcard.isEmpty()
|| (FmUtils.FM_RECORD_STORAGE_PATH_SDCARD.equals(FmUtils.FM_RECORD_STORAGE_PATH_NAME) && !Environment.MEDIA_MOUNTED
.equals(EnvironmentEx.getExternalStoragePathState()))) {
Log.e(TAG, "startRecording, no sdcard storage available");
setError(ERROR_SDCARD_NOT_PRESENT);
return;
}
/**
* @}
*/
// check whether have sufficient storage space, if not will notify
// caller error message
if (!FmUtils.hasEnoughSpace(recordingSdcard)) {
setError(ERROR_SDCARD_INSUFFICIENT_SPACE);
Log.e(TAG, "startRecording, SD card does not have sufficient space!!");
return;
}
// get external storage directory
File sdDir = new File(recordingSdcard);
File recordingDir = new File(sdDir, FM_RECORD_FOLDER);
// exist a file named FM Recording, so can't create FM recording folder
if (recordingDir.exists() && !recordingDir.isDirectory()) {
Log.e(TAG, "startRecording, a file with name \"" + FM_RECORD_FOLDER + "\" already exists!!");
setError(ERROR_SDCARD_WRITE_FAILED);
return;
} else if (!recordingDir.exists()) { // try to create recording folder
if (FmUtils.isExternalStorage()) {
dir = getPathUri(context,recordingDir,externalStorageUri);
} else {
boolean mkdirResult = recordingDir.mkdir();
if (!mkdirResult) { // create recording file failed
setError(ERROR_RECORDER_INTERNAL);
return;
}
}
} else {
if (FmUtils.isExternalStorage()) {
dir = getPathUri(context,recordingDir,externalStorageUri);
}
}
// create recording temporary file
long curTime = System.currentTimeMillis();
Date date = new Date(curTime);
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("MMddyyyy_HHmmss",
Locale.ENGLISH);
String time = simpleDateFormat.format(date);
StringBuilder stringBuilder = new StringBuilder();
/**
* bug474741, recording format selection.
*
* @{
*/
if (GLOBAL_RECORD_FORMAT_FLAG == 1) {
stringBuilder.append(".").append(time).append(RECORDING_FILE_EXTENSION_3GPP).append(".tmp");
} else if(GLOBAL_RECORD_FORMAT_FLAG == 2) {
stringBuilder.append(".").append(time).append(RECORDING_FILE_EXTENSION_MP3).append(".tmp");
} else {
stringBuilder.append(".").append(time).append(RECORDING_FILE_EXTENSION_AMR).append(".tmp");
}
/**
* @}
*/
String name = stringBuilder.toString();
mRecordFile = new File(recordingDir, name);
if (FmUtils.isExternalStorage()) {
try {
Uri fileDoc = DocumentsContract.createDocument(context.getContentResolver(),dir
,DocumentsContract.Document.COLUMN_MIME_TYPE,name);
FmUtils.saveTmpFileUri(fileDoc.toString());
mPfd = context.getContentResolver().openFileDescriptor(fileDoc,"w");
fd = mPfd.getFileDescriptor();
if(fd == null) {
Log.e(TAG,"fd is NULL");
throw new IllegalArgumentException("Memory related error");
}
} catch(FileNotFoundException e) {
Log.d(TAG,""+e);
}
} else {
try {
if (mRecordFile.createNewFile()) {
Log.d(TAG, "startRecording, createNewFile success with path "
+ mRecordFile.getPath());
}
} catch (IOException e) {
Log.e(TAG, "startRecording, IOException while createTempFile: " + e);
e.printStackTrace();
setError(ERROR_SDCARD_WRITE_FAILED);
return;
}
}
// set record parameter and start recording
try {
mRecorder = new MediaRecorder();
mRecorder.setOnErrorListener(this);
mRecorder.setOnInfoListener(this);
// mRecorder.setAudioSource(MediaRecorder.AudioSource.RADIO_TUNER);
mRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
/**
* bug474741, recording format selection. Original Android code:
* mRecorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);
* mRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC); final int samplingRate =
* 44100; mRecorder.setAudioSamplingRate(samplingRate); final int bitRate = 128000;
* mRecorder.setAudioEncodingBitRate(bitRate); final int audiochannels = 2;
* mRecorder.setAudioChannels(audiochannels);
*
* @{
*/
if (1 == GLOBAL_RECORD_FORMAT_FLAG) {
Log.d(TAG, "global_record_format: 3gpp");
mRecorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);
mRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC);
mRecorder.setAudioSamplingRate(44100);
mRecorder.setAudioEncodingBitRate(128000);
} else if(2 == GLOBAL_RECORD_FORMAT_FLAG) {
Log.d(TAG, "global_record_format: mp3");
mRecorder.setOutputFormat(11);
mRecorder.setAudioEncoder(7);
mRecorder.setAudioSamplingRate(44100);
mRecorder.setAudioEncodingBitRate(320000);
mRecorder.setAudioChannels(2);
} else {
Log.d(TAG, "global_record_format: amr_nb");
mRecorder.setOutputFormat(MediaRecorder.OutputFormat.AMR_NB);
mRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);
mRecorder.setAudioSamplingRate(8000);
mRecorder.setAudioEncodingBitRate(5900);
}
/**
* @}
*/
if (FmUtils.isExternalStorage()) {
mRecorder.setOutputFile(fd);
} else {
mRecorder.setOutputFile(mRecordFile.getAbsolutePath());
}
mRecorder.prepare();
mRecordStartTime = SystemClock.elapsedRealtime();
mRecorder.start();
mIsRecordingFileSaved = false;
mFileListener= new FileListener(recordingDir.getPath());
mFileListener.startWatching();
} catch (IllegalStateException e) {
Log.e(TAG, "startRecording, IllegalStateException while starting recording!", e);
setError(ERROR_RECORDER_INTERNAL);
return;
} catch (IOException e) {
Log.e(TAG, "startRecording, IOException while starting recording!", e);
setError(ERROR_RECORDER_INTERNAL);
return;
}
setState(STATE_RECORDING);
}
/**
*Listening whether recording file is delete or not.
*@}
*/
private class FileListener extends FileObserver {
public FileListener(String path) {
super(path, FileObserver.MOVED_FROM | FileObserver.DELETE);
Log.d(TAG, "FileListener path="+path);
}
@Override
public void onEvent(int event, String path) {
Log.d(TAG, "onEvent: event = "+event+"; path = "+path);
switch (event) {
case FileObserver.MOVED_FROM:
case FileObserver.DELETE:
if (path == null) {
return ;
}
if ( getTmpFileName().equals(path)) {
Log.d(TAG, "recording tmp file is deleted");
discardRecording();
}
break;
default:
break;
}
}
}
End of recording
private void stopRecorder() {
......
mRecorder.stop();
......
}