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