Android FM process analysis

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();
        ......

    }

 

Guess you like

Origin blog.csdn.net/liu362732346/article/details/102665246