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