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 } 394 if (mAudioTrack != null) { 395 mAudioTrack.stop(); 396 } 397 initAudioRecordSink(); 398 399 mIsRender = true; 400 synchronized (mRenderLock) { 401 mRenderLock.notify(); 402 } 403 } 404 stopRender()405 private synchronized void stopRender() { 406 Log.d(TAG, "stopRender"); 407 mIsRender = false; 408 } 409 createRenderThread()410 private synchronized void createRenderThread() { 411 if (mRenderThread == null) { 412 mRenderThread = new RenderThread(); 413 mRenderThread.start(); 414 } 415 } 416 exitRenderThread()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 isRendering()436 private boolean isRendering() { 437 return mIsRender; 438 } 439 startAudioTrack()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 stopAudioTrack()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; isAudioFrameNeedIgnore()456 private boolean isAudioFrameNeedIgnore() { 457 return mCurrentFrame < AUDIO_FRAMES_TO_IGNORE_COUNT; 458 } 459 460 @Override run()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 isRender()526 private boolean isRender() { 527 return (mIsRender && (mPowerStatus == POWER_UP) && mIsAudioFocusHeld); 528 } 529 isSpeakerPhoneOn()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 */ openDevice()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 */ closeDevice()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 */ isDeviceOpen()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 */ powerUpAsync(float frequency)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 powerUp(float frequency)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 playFrequency(float frequency)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 */ powerDownAsync()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 */ powerDown()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 getPowerStatus()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 */ tuneStationAsync(float frequency)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 tuneStation(float frequency)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 */ seekStationAsync(float frequency, boolean isUp)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 seekStation(float frequency, boolean isUp)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 */ startScanAsync()791 public void startScanAsync() { 792 mFmServiceHandler.removeMessages(FmListener.MSGID_SCAN_FINISHED); 793 mFmServiceHandler.sendEmptyMessage(FmListener.MSGID_SCAN_FINISHED); 794 } 795 startScan()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 */ isScanning()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 */ stopScan()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 */ isSeeking()863 public boolean isSeeking() { 864 return mIsNativeSeeking; 865 } 866 867 /** 868 * Set RDS 869 * 870 * @param on true, enable RDS; false, disable RDS. 871 */ setRdsAsync(boolean on)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 setRds(boolean on)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 */ getPs()898 public String getPs() { 899 return mPsString; 900 } 901 902 /** 903 * Get RT information 904 * 905 * @return RT information 906 */ getRtText()907 public String getRtText() { 908 return mRtTextString; 909 } 910 911 /** 912 * Get AF frequency 913 * 914 * @return AF frequency 915 */ activeAfAsync()916 public void activeAfAsync() { 917 mFmServiceHandler.removeMessages(FmListener.MSGID_ACTIVE_AF_FINISHED); 918 mFmServiceHandler.sendEmptyMessage(FmListener.MSGID_ACTIVE_AF_FINISHED); 919 } 920 activeAf()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 */ setMuteAsync(boolean mute)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 */ setMute(boolean mute)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 */ isMuted()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 */ isRdsSupported()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 */ isSpeakerUsed()989 public boolean isSpeakerUsed() { 990 return mIsSpeakerUsed; 991 } 992 993 /** 994 * Initial service and current station 995 * 996 * @param iCurrentStation current station frequency 997 */ initService(int iCurrentStation)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 */ isServiceInited()1008 public boolean isServiceInited() { 1009 return mIsServiceInited; 1010 } 1011 1012 /** 1013 * Get FM service current station frequency 1014 * 1015 * @return Current station frequency 1016 */ getFrequency()1017 public int getFrequency() { 1018 return mCurrentStation; 1019 } 1020 1021 /** 1022 * Set FM service station frequency 1023 * 1024 * @param station Current station 1025 */ setFrequency(int station)1026 public void setFrequency(int station) { 1027 mCurrentStation = station; 1028 } 1029 1030 /** 1031 * resume FM audio 1032 */ resumeFmAudio()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 */ switchAntennaAsync(int antenna)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 */ switchAntenna(int antenna)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 */ startRecordingAsync()1079 public void startRecordingAsync() { 1080 mFmServiceHandler.removeMessages(FmListener.MSGID_STARTRECORDING_FINISHED); 1081 mFmServiceHandler.sendEmptyMessage(FmListener.MSGID_STARTRECORDING_FINISHED); 1082 } 1083 startRecording()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 isSdcardReady(String sdcardPath)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 */ stopRecordingAsync()1117 public void stopRecordingAsync() { 1118 mFmServiceHandler.removeMessages(FmListener.MSGID_STOPRECORDING_FINISHED); 1119 mFmServiceHandler.sendEmptyMessage(FmListener.MSGID_STOPRECORDING_FINISHED); 1120 } 1121 stopRecording()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 */ saveRecordingAsync(String newName)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 saveRecording(String newName)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 */ getRecordTime()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 */ setRecordingModeAsync(boolean isRecording)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 setRecordingMode(boolean isRecording)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 */ getRecordingMode()1207 public boolean getRecordingMode() { 1208 return mIsInRecordingMode; 1209 } 1210 1211 /** 1212 * Get record state 1213 * 1214 * @return record state 1215 */ getRecorderState()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 */ getRecordingName()1228 public String getRecordingName() { 1229 if (null != mFmRecorder) { 1230 return mFmRecorder.getRecordFileName(); 1231 } 1232 return null; 1233 } 1234 1235 @Override onCreate()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 registerAudioPortUpdateListener()1262 private void registerAudioPortUpdateListener() { 1263 if (mAudioPortUpdateListener == null) { 1264 mAudioPortUpdateListener = new FmOnAudioPortUpdateListener(); 1265 mAudioManager.registerAudioPortUpdateListener(mAudioPortUpdateListener); 1266 } 1267 } 1268 unregisterAudioPortUpdateListener()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() initAudioRecordSink()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 createAudioPatch()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 onAudioPortListUpdate(AudioPort[] portList)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 onAudioPatchListUpdate(AudioPatch[] patchList)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 onServiceDied()1383 public void onServiceDied() { 1384 enableFmAudio(false); 1385 } 1386 } 1387 releaseAudioPatch()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 registerFmBroadcastReceiver()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 unregisterFmBroadcastReceiver()1409 private void unregisterFmBroadcastReceiver() { 1410 if (null != mBroadcastReceiver) { 1411 unregisterReceiver(mBroadcastReceiver); 1412 mBroadcastReceiver = null; 1413 } 1414 } 1415 1416 @Override onDestroy()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 */ exitFm()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 onConfigurationChanged(Configuration newConfig)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 onStartCommand(Intent intent, int flags, int startId)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 */ startRdsThread()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 */ stopRdsThread()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 */ setPs(String ps)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 */ setLRText(String lrtText)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 */ enableFmAudio(boolean enable)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 isPatchMixerToEarphone(ArrayList<AudioPatch> patches)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. isPatchMixerToDeviceRemoved(ArrayList<AudioPatch> patches)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 */ showPlayingNotification()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 */ showRecordingNotification(Notification notification)1791 public void showRecordingNotification(Notification notification) { 1792 startForeground(NOTIFICATION_ID, notification); 1793 } 1794 1795 /** 1796 * Remove notification 1797 */ removeNotification()1798 public void removeNotification() { 1799 stopForeground(true); 1800 } 1801 1802 /** 1803 * Update notification 1804 */ updatePlayingNotification()1805 public void updatePlayingNotification() { 1806 if (mPowerStatus == POWER_UP) { 1807 showPlayingNotification(); 1808 } 1809 } 1810 1811 /** 1812 * Register sdcard listener for record 1813 */ registerSdcardReceiver()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 unregisterSdcardListener()1826 private void unregisterSdcardListener() { 1827 if (null != mSdcardListener) { 1828 unregisterReceiver(mSdcardListener); 1829 } 1830 } 1831 updateSdcardStateMap(Intent intent)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 onRecorderStateChanged(int state)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 onRecorderError(int error)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 */ checkState()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 */ isHeadSetIn()1908 private boolean isHeadSetIn() { 1909 return (0 == mValueHeadSetPlug); 1910 } 1911 focusChanged(int focusState)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 */ requestAudioFocus()1929 public boolean requestAudioFocus() { 1930 if (mIsAudioFocusHeld) { 1931 return true; 1932 } 1933 1934 int audioFocus = mAudioManager.requestAudioFocus(mAudioFocusChangeListener, 1935 AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN); 1936 mIsAudioFocusHeld = (AudioManager.AUDIOFOCUS_REQUEST_GRANTED == audioFocus); 1937 return mIsAudioFocusHeld; 1938 } 1939 1940 /** 1941 * Abandon audio focus 1942 */ abandonAudioFocus()1943 public void abandonAudioFocus() { 1944 mAudioManager.abandonAudioFocus(mAudioFocusChangeListener); 1945 mIsAudioFocusHeld = false; 1946 } 1947 1948 /** 1949 * Use to interact with other voice related app 1950 */ 1951 private final OnAudioFocusChangeListener mAudioFocusChangeListener = 1952 new OnAudioFocusChangeListener() { 1953 /** 1954 * Handle audio focus change ensure message FIFO 1955 * 1956 * @param focusChange audio focus change state 1957 */ 1958 @Override 1959 public void onAudioFocusChange(int focusChange) { 1960 Log.d(TAG, "onAudioFocusChange " + focusChange); 1961 switch (focusChange) { 1962 case AudioManager.AUDIOFOCUS_LOSS: 1963 synchronized (this) { 1964 mAudioManager.setParameters("AudioFmPreStop=1"); 1965 setMute(true); 1966 focusChanged(AudioManager.AUDIOFOCUS_LOSS); 1967 } 1968 break; 1969 1970 case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT: 1971 synchronized (this) { 1972 mAudioManager.setParameters("AudioFmPreStop=1"); 1973 setMute(true); 1974 focusChanged(AudioManager.AUDIOFOCUS_LOSS_TRANSIENT); 1975 } 1976 break; 1977 1978 case AudioManager.AUDIOFOCUS_GAIN: 1979 synchronized (this) { 1980 updateAudioFocusAync(AudioManager.AUDIOFOCUS_GAIN); 1981 } 1982 break; 1983 1984 case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK: 1985 synchronized (this) { 1986 updateAudioFocusAync( 1987 AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK); 1988 } 1989 break; 1990 1991 default: 1992 break; 1993 } 1994 } 1995 }; 1996 1997 /** 1998 * Audio focus changed, will send message to handler thread. synchronized to 1999 * ensure one message can go in this method. 2000 * 2001 * @param focusState AudioManager state 2002 */ updateAudioFocusAync(int focusState)2003 private synchronized void updateAudioFocusAync(int focusState) { 2004 final int bundleSize = 1; 2005 Bundle bundle = new Bundle(bundleSize); 2006 bundle.putInt(FmListener.KEY_AUDIOFOCUS_CHANGED, focusState); 2007 Message msg = mFmServiceHandler.obtainMessage(FmListener.MSGID_AUDIOFOCUS_CHANGED); 2008 msg.setData(bundle); 2009 mFmServiceHandler.sendMessage(msg); 2010 } 2011 2012 /** 2013 * Audio focus changed, update FM focus state. 2014 * 2015 * @param focusState AudioManager state 2016 */ updateAudioFocus(int focusState)2017 private void updateAudioFocus(int focusState) { 2018 switch (focusState) { 2019 case AudioManager.AUDIOFOCUS_LOSS: 2020 mPausedByTransientLossOfFocus = false; 2021 // play back audio will output with music audio 2022 // May be affect other recorder app, but the flow can not be 2023 // execute earlier, 2024 // It should ensure execute after start/stop record. 2025 if (mFmRecorder != null) { 2026 int fmState = mFmRecorder.getState(); 2027 // only handle recorder state, not handle playback state 2028 if (fmState == FmRecorder.STATE_RECORDING) { 2029 mFmServiceHandler.removeMessages( 2030 FmListener.MSGID_STARTRECORDING_FINISHED); 2031 mFmServiceHandler.removeMessages( 2032 FmListener.MSGID_STOPRECORDING_FINISHED); 2033 stopRecording(); 2034 } 2035 } 2036 handlePowerDown(); 2037 break; 2038 2039 case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT: 2040 if (mPowerStatus == POWER_UP) { 2041 mPausedByTransientLossOfFocus = true; 2042 } 2043 // play back audio will output with music audio 2044 // May be affect other recorder app, but the flow can not be 2045 // execute earlier, 2046 // It should ensure execute after start/stop record. 2047 if (mFmRecorder != null) { 2048 int fmState = mFmRecorder.getState(); 2049 if (fmState == FmRecorder.STATE_RECORDING) { 2050 mFmServiceHandler.removeMessages( 2051 FmListener.MSGID_STARTRECORDING_FINISHED); 2052 mFmServiceHandler.removeMessages( 2053 FmListener.MSGID_STOPRECORDING_FINISHED); 2054 stopRecording(); 2055 } 2056 } 2057 handlePowerDown(); 2058 break; 2059 2060 case AudioManager.AUDIOFOCUS_GAIN: 2061 if ((mPowerStatus != POWER_UP) && mPausedByTransientLossOfFocus) { 2062 final int bundleSize = 1; 2063 mFmServiceHandler.removeMessages(FmListener.MSGID_POWERUP_FINISHED); 2064 mFmServiceHandler.removeMessages(FmListener.MSGID_POWERDOWN_FINISHED); 2065 Bundle bundle = new Bundle(bundleSize); 2066 bundle.putFloat(FM_FREQUENCY, FmUtils.computeFrequency(mCurrentStation)); 2067 handlePowerUp(bundle); 2068 } 2069 setMute(false); 2070 break; 2071 2072 case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK: 2073 setMute(true); 2074 break; 2075 2076 default: 2077 break; 2078 } 2079 } 2080 2081 /** 2082 * FM Radio listener record 2083 */ 2084 private static class Record { 2085 int mHashCode; // hash code 2086 FmListener mCallback; // call back 2087 } 2088 2089 /** 2090 * Register FM Radio listener, activity get service state should call this 2091 * method register FM Radio listener 2092 * 2093 * @param callback FM Radio listener 2094 */ registerFmRadioListener(FmListener callback)2095 public void registerFmRadioListener(FmListener callback) { 2096 synchronized (mRecords) { 2097 // register callback in AudioProfileService, if the callback is 2098 // exist, just replace the event. 2099 Record record = null; 2100 int hashCode = callback.hashCode(); 2101 final int n = mRecords.size(); 2102 for (int i = 0; i < n; i++) { 2103 record = mRecords.get(i); 2104 if (hashCode == record.mHashCode) { 2105 return; 2106 } 2107 } 2108 record = new Record(); 2109 record.mHashCode = hashCode; 2110 record.mCallback = callback; 2111 mRecords.add(record); 2112 } 2113 } 2114 2115 /** 2116 * Call back from service to activity 2117 * 2118 * @param bundle The message to activity 2119 */ notifyActivityStateChanged(Bundle bundle)2120 private void notifyActivityStateChanged(Bundle bundle) { 2121 if (!mRecords.isEmpty()) { 2122 synchronized (mRecords) { 2123 Iterator<Record> iterator = mRecords.iterator(); 2124 while (iterator.hasNext()) { 2125 Record record = (Record) iterator.next(); 2126 2127 FmListener listener = record.mCallback; 2128 2129 if (listener == null) { 2130 iterator.remove(); 2131 return; 2132 } 2133 2134 listener.onCallBack(bundle); 2135 } 2136 } 2137 } 2138 } 2139 2140 /** 2141 * Call back from service to the current request activity 2142 * Scan need only notify FmFavoriteActivity if current is FmFavoriteActivity 2143 * 2144 * @param bundle The message to activity 2145 */ notifyCurrentActivityStateChanged(Bundle bundle)2146 private void notifyCurrentActivityStateChanged(Bundle bundle) { 2147 if (!mRecords.isEmpty()) { 2148 Log.d(TAG, "notifyCurrentActivityStateChanged = " + mRecords.size()); 2149 synchronized (mRecords) { 2150 if (mRecords.size() > 0) { 2151 Record record = mRecords.get(mRecords.size() - 1); 2152 FmListener listener = record.mCallback; 2153 if (listener == null) { 2154 mRecords.remove(record); 2155 return; 2156 } 2157 listener.onCallBack(bundle); 2158 } 2159 } 2160 } 2161 } 2162 2163 /** 2164 * Unregister FM Radio listener 2165 * 2166 * @param callback FM Radio listener 2167 */ unregisterFmRadioListener(FmListener callback)2168 public void unregisterFmRadioListener(FmListener callback) { 2169 remove(callback.hashCode()); 2170 } 2171 2172 /** 2173 * Remove call back according hash code 2174 * 2175 * @param hashCode The call back hash code 2176 */ remove(int hashCode)2177 private void remove(int hashCode) { 2178 synchronized (mRecords) { 2179 Iterator<Record> iterator = mRecords.iterator(); 2180 while (iterator.hasNext()) { 2181 Record record = (Record) iterator.next(); 2182 if (record.mHashCode == hashCode) { 2183 iterator.remove(); 2184 } 2185 } 2186 } 2187 } 2188 2189 /** 2190 * Check recording sd card is unmount 2191 * 2192 * @param intent The unmount sd card intent 2193 * 2194 * @return true or false indicate whether current recording sd card is 2195 * unmount or not 2196 */ isRecordingCardUnmount(Intent intent)2197 public boolean isRecordingCardUnmount(Intent intent) { 2198 String unmountSDCard = intent.getData().toString(); 2199 Log.d(TAG, "unmount sd card file path: " + unmountSDCard); 2200 return unmountSDCard.equalsIgnoreCase("file://" + sRecordingSdcard) ? true : false; 2201 } 2202 updateStations(int[] stations)2203 private int[] updateStations(int[] stations) { 2204 Log.d(TAG, "updateStations.firstValidstation:" + Arrays.toString(stations)); 2205 int firstValidstation = mCurrentStation; 2206 2207 int stationNum = 0; 2208 if (null != stations) { 2209 int searchedListSize = stations.length; 2210 if (mIsDistanceExceed) { 2211 FmStation.cleanSearchedStations(mContext); 2212 for (int j = 0; j < searchedListSize; j++) { 2213 int freqSearched = stations[j]; 2214 if (FmUtils.isValidStation(freqSearched) && 2215 !FmStation.isFavoriteStation(mContext, freqSearched)) { 2216 FmStation.insertStationToDb(mContext, freqSearched, null); 2217 } 2218 } 2219 } else { 2220 // get stations from db 2221 stationNum = updateDBInLocation(stations); 2222 } 2223 } 2224 2225 Log.d(TAG, "updateStations.firstValidstation:" + firstValidstation + 2226 ",stationNum:" + stationNum); 2227 return (new int[] { 2228 firstValidstation, stationNum 2229 }); 2230 } 2231 2232 /** 2233 * update DB, keep favorite and rds which is searched this time, 2234 * delete rds from db which is not searched this time. 2235 * @param stations 2236 * @return number of valid searched stations 2237 */ updateDBInLocation(int[] stations)2238 private int updateDBInLocation(int[] stations) { 2239 int stationNum = 0; 2240 int searchedListSize = stations.length; 2241 ArrayList<Integer> stationsInDB = new ArrayList<Integer>(); 2242 Cursor cursor = null; 2243 try { 2244 // get non favorite stations 2245 cursor = mContext.getContentResolver().query(Station.CONTENT_URI, 2246 new String[] { FmStation.Station.FREQUENCY }, 2247 FmStation.Station.IS_FAVORITE + "=0", 2248 null, FmStation.Station.FREQUENCY); 2249 if ((null != cursor) && cursor.moveToFirst()) { 2250 2251 do { 2252 int freqInDB = cursor.getInt(cursor.getColumnIndex( 2253 FmStation.Station.FREQUENCY)); 2254 stationsInDB.add(freqInDB); 2255 } while (cursor.moveToNext()); 2256 2257 } else { 2258 Log.d(TAG, "updateDBInLocation, insertSearchedStation cursor is null"); 2259 } 2260 } finally { 2261 if (null != cursor) { 2262 cursor.close(); 2263 } 2264 } 2265 2266 int listSizeInDB = stationsInDB.size(); 2267 // delete station if db frequency is not in searched list 2268 for (int i = 0; i < listSizeInDB; i++) { 2269 int freqInDB = stationsInDB.get(i); 2270 for (int j = 0; j < searchedListSize; j++) { 2271 int freqSearched = stations[j]; 2272 if (freqInDB == freqSearched) { 2273 break; 2274 } 2275 if (j == (searchedListSize - 1) && freqInDB != freqSearched) { 2276 // delete from db 2277 FmStation.deleteStationInDb(mContext, freqInDB); 2278 } 2279 } 2280 } 2281 2282 // add to db if station is not in db 2283 for (int j = 0; j < searchedListSize; j++) { 2284 int freqSearched = stations[j]; 2285 if (FmUtils.isValidStation(freqSearched)) { 2286 stationNum++; 2287 if (!stationsInDB.contains(freqSearched) 2288 && !FmStation.isFavoriteStation(mContext, freqSearched)) { 2289 // insert to db 2290 FmStation.insertStationToDb(mContext, freqSearched, ""); 2291 } 2292 } 2293 } 2294 return stationNum; 2295 } 2296 2297 /** 2298 * The background handler 2299 */ 2300 class FmRadioServiceHandler extends Handler { FmRadioServiceHandler(Looper looper)2301 public FmRadioServiceHandler(Looper looper) { 2302 super(looper); 2303 } 2304 2305 @Override handleMessage(Message msg)2306 public void handleMessage(Message msg) { 2307 Bundle bundle; 2308 boolean isPowerup = false; 2309 boolean isSwitch = true; 2310 2311 switch (msg.what) { 2312 2313 // power up 2314 case FmListener.MSGID_POWERUP_FINISHED: 2315 bundle = msg.getData(); 2316 handlePowerUp(bundle); 2317 break; 2318 2319 // power down 2320 case FmListener.MSGID_POWERDOWN_FINISHED: 2321 handlePowerDown(); 2322 break; 2323 2324 // fm exit 2325 case FmListener.MSGID_FM_EXIT: 2326 if (mIsSpeakerUsed) { 2327 setForceUse(false); 2328 } 2329 powerDown(); 2330 closeDevice(); 2331 2332 bundle = new Bundle(1); 2333 bundle.putInt(FmListener.CALLBACK_FLAG, FmListener.MSGID_FM_EXIT); 2334 notifyActivityStateChanged(bundle); 2335 // Finish favorite when exit FM 2336 if (sExitListener != null) { 2337 sExitListener.onExit(); 2338 } 2339 break; 2340 2341 // switch antenna 2342 case FmListener.MSGID_SWITCH_ANTENNA: 2343 bundle = msg.getData(); 2344 int value = bundle.getInt(FmListener.SWITCH_ANTENNA_VALUE); 2345 2346 // if ear phone insert, need dismiss plugin earphone 2347 // dialog 2348 // if earphone plug out and it is not play recorder 2349 // state, show plug dialog. 2350 if (0 == value) { 2351 // powerUpAsync(FMRadioUtils.computeFrequency(mCurrentStation)); 2352 bundle.putInt(FmListener.CALLBACK_FLAG, 2353 FmListener.MSGID_SWITCH_ANTENNA); 2354 bundle.putBoolean(FmListener.KEY_IS_SWITCH_ANTENNA, true); 2355 notifyActivityStateChanged(bundle); 2356 } else { 2357 // ear phone plug out, and recorder state is not 2358 // play recorder state, 2359 // show dialog. 2360 if (mRecordState != FmRecorder.STATE_PLAYBACK) { 2361 bundle.putInt(FmListener.CALLBACK_FLAG, 2362 FmListener.MSGID_SWITCH_ANTENNA); 2363 bundle.putBoolean(FmListener.KEY_IS_SWITCH_ANTENNA, false); 2364 notifyActivityStateChanged(bundle); 2365 } 2366 } 2367 break; 2368 2369 // tune to station 2370 case FmListener.MSGID_TUNE_FINISHED: 2371 bundle = msg.getData(); 2372 float tuneStation = bundle.getFloat(FM_FREQUENCY); 2373 boolean isTune = tuneStation(tuneStation); 2374 // if tune fail, pass current station to update ui 2375 if (!isTune) { 2376 tuneStation = FmUtils.computeFrequency(mCurrentStation); 2377 } 2378 bundle = new Bundle(3); 2379 bundle.putInt(FmListener.CALLBACK_FLAG, 2380 FmListener.MSGID_TUNE_FINISHED); 2381 bundle.putBoolean(FmListener.KEY_IS_TUNE, isTune); 2382 bundle.putFloat(FmListener.KEY_TUNE_TO_STATION, tuneStation); 2383 notifyActivityStateChanged(bundle); 2384 break; 2385 2386 // seek to station 2387 case FmListener.MSGID_SEEK_FINISHED: 2388 bundle = msg.getData(); 2389 mIsSeeking = true; 2390 float seekStation = seekStation(bundle.getFloat(FM_FREQUENCY), 2391 bundle.getBoolean(OPTION)); 2392 boolean isStationTunningSuccessed = false; 2393 int station = FmUtils.computeStation(seekStation); 2394 if (FmUtils.isValidStation(station)) { 2395 isStationTunningSuccessed = tuneStation(seekStation); 2396 } 2397 // if tune fail, pass current station to update ui 2398 if (!isStationTunningSuccessed) { 2399 seekStation = FmUtils.computeFrequency(mCurrentStation); 2400 } 2401 bundle = new Bundle(2); 2402 bundle.putInt(FmListener.CALLBACK_FLAG, 2403 FmListener.MSGID_TUNE_FINISHED); 2404 bundle.putBoolean(FmListener.KEY_IS_TUNE, isStationTunningSuccessed); 2405 bundle.putFloat(FmListener.KEY_TUNE_TO_STATION, seekStation); 2406 notifyActivityStateChanged(bundle); 2407 mIsSeeking = false; 2408 break; 2409 2410 // start scan 2411 case FmListener.MSGID_SCAN_FINISHED: 2412 int[] stations = null; 2413 int[] result = null; 2414 int scanTuneStation = 0; 2415 boolean isScan = true; 2416 mIsScanning = true; 2417 if (powerUp(FmUtils.DEFAULT_STATION_FLOAT)) { 2418 stations = startScan(); 2419 } 2420 2421 // check whether cancel scan 2422 if ((null != stations) && stations[0] == -100) { 2423 isScan = false; 2424 result = new int[] { 2425 -1, 0 2426 }; 2427 } else { 2428 result = updateStations(stations); 2429 scanTuneStation = result[0]; 2430 tuneStation(FmUtils.computeFrequency(mCurrentStation)); 2431 } 2432 2433 /* 2434 * if there is stop command when scan, so it needs to mute 2435 * fm avoid fm sound come out. 2436 */ 2437 if (mIsAudioFocusHeld) { 2438 setMute(false); 2439 } 2440 bundle = new Bundle(4); 2441 bundle.putInt(FmListener.CALLBACK_FLAG, 2442 FmListener.MSGID_SCAN_FINISHED); 2443 //bundle.putInt(FmListener.KEY_TUNE_TO_STATION, scanTuneStation); 2444 bundle.putInt(FmListener.KEY_STATION_NUM, result[1]); 2445 bundle.putBoolean(FmListener.KEY_IS_SCAN, isScan); 2446 2447 mIsScanning = false; 2448 // Only notify the newest request activity 2449 notifyCurrentActivityStateChanged(bundle); 2450 break; 2451 2452 // audio focus changed 2453 case FmListener.MSGID_AUDIOFOCUS_CHANGED: 2454 bundle = msg.getData(); 2455 int focusState = bundle.getInt(FmListener.KEY_AUDIOFOCUS_CHANGED); 2456 updateAudioFocus(focusState); 2457 break; 2458 2459 case FmListener.MSGID_SET_RDS_FINISHED: 2460 bundle = msg.getData(); 2461 setRds(bundle.getBoolean(OPTION)); 2462 break; 2463 2464 case FmListener.MSGID_SET_MUTE_FINISHED: 2465 bundle = msg.getData(); 2466 setMute(bundle.getBoolean(OPTION)); 2467 break; 2468 2469 case FmListener.MSGID_ACTIVE_AF_FINISHED: 2470 activeAf(); 2471 break; 2472 2473 /********** recording **********/ 2474 case FmListener.MSGID_STARTRECORDING_FINISHED: 2475 startRecording(); 2476 break; 2477 2478 case FmListener.MSGID_STOPRECORDING_FINISHED: 2479 stopRecording(); 2480 break; 2481 2482 case FmListener.MSGID_RECORD_MODE_CHANED: 2483 bundle = msg.getData(); 2484 setRecordingMode(bundle.getBoolean(OPTION)); 2485 break; 2486 2487 case FmListener.MSGID_SAVERECORDING_FINISHED: 2488 bundle = msg.getData(); 2489 saveRecording(bundle.getString(RECODING_FILE_NAME)); 2490 break; 2491 2492 default: 2493 break; 2494 } 2495 } 2496 2497 } 2498 2499 /** 2500 * handle power down, execute power down and call back to activity. 2501 */ handlePowerDown()2502 private void handlePowerDown() { 2503 Bundle bundle; 2504 boolean isPowerdown = powerDown(); 2505 bundle = new Bundle(1); 2506 bundle.putInt(FmListener.CALLBACK_FLAG, FmListener.MSGID_POWERDOWN_FINISHED); 2507 notifyActivityStateChanged(bundle); 2508 } 2509 2510 /** 2511 * handle power up, execute power up and call back to activity. 2512 * 2513 * @param bundle power up frequency 2514 */ handlePowerUp(Bundle bundle)2515 private void handlePowerUp(Bundle bundle) { 2516 boolean isPowerUp = false; 2517 boolean isSwitch = true; 2518 float curFrequency = bundle.getFloat(FM_FREQUENCY); 2519 2520 if (!isAntennaAvailable()) { 2521 Log.d(TAG, "handlePowerUp, earphone is not ready"); 2522 bundle = new Bundle(2); 2523 bundle.putInt(FmListener.CALLBACK_FLAG, FmListener.MSGID_SWITCH_ANTENNA); 2524 bundle.putBoolean(FmListener.KEY_IS_SWITCH_ANTENNA, false); 2525 notifyActivityStateChanged(bundle); 2526 return; 2527 } 2528 if (powerUp(curFrequency)) { 2529 if (FmUtils.isFirstTimePlayFm(mContext)) { 2530 isPowerUp = firstPlaying(curFrequency); 2531 FmUtils.setIsFirstTimePlayFm(mContext); 2532 } else { 2533 isPowerUp = playFrequency(curFrequency); 2534 } 2535 mPausedByTransientLossOfFocus = false; 2536 } 2537 bundle = new Bundle(2); 2538 bundle.putInt(FmListener.CALLBACK_FLAG, FmListener.MSGID_POWERUP_FINISHED); 2539 bundle.putInt(FmListener.KEY_TUNE_TO_STATION, mCurrentStation); 2540 notifyActivityStateChanged(bundle); 2541 } 2542 2543 /** 2544 * check FM is foreground or background 2545 */ isActivityForeground()2546 public boolean isActivityForeground() { 2547 return (mIsFmMainForeground || mIsFmFavoriteForeground || mIsFmRecordForeground); 2548 } 2549 2550 /** 2551 * mark FmMainActivity is foreground or not 2552 * @param isForeground 2553 */ setFmMainActivityForeground(boolean isForeground)2554 public void setFmMainActivityForeground(boolean isForeground) { 2555 mIsFmMainForeground = isForeground; 2556 } 2557 2558 /** 2559 * mark FmFavoriteActivity activity is foreground or not 2560 * @param isForeground 2561 */ setFmFavoriteForeground(boolean isForeground)2562 public void setFmFavoriteForeground(boolean isForeground) { 2563 mIsFmFavoriteForeground = isForeground; 2564 } 2565 2566 /** 2567 * mark FmRecordActivity activity is foreground or not 2568 * @param isForeground 2569 */ setFmRecordActivityForeground(boolean isForeground)2570 public void setFmRecordActivityForeground(boolean isForeground) { 2571 mIsFmRecordForeground = isForeground; 2572 } 2573 2574 /** 2575 * Get the recording sdcard path when staring record 2576 * 2577 * @return sdcard path like "/storage/sdcard0" 2578 */ getRecordingSdcard()2579 public static String getRecordingSdcard() { 2580 return sRecordingSdcard; 2581 } 2582 2583 /** 2584 * The listener interface for exit 2585 */ 2586 public interface OnExitListener { 2587 /** 2588 * When Service finish, should notify FmFavoriteActivity to finish 2589 */ onExit()2590 void onExit(); 2591 } 2592 2593 /** 2594 * Register the listener for exit 2595 * 2596 * @param listener The listener want to know the exit event 2597 */ registerExitListener(OnExitListener listener)2598 public static void registerExitListener(OnExitListener listener) { 2599 sExitListener = listener; 2600 } 2601 2602 /** 2603 * Unregister the listener for exit 2604 * 2605 * @param listener The listener want to know the exit event 2606 */ unregisterExitListener(OnExitListener listener)2607 public static void unregisterExitListener(OnExitListener listener) { 2608 sExitListener = null; 2609 } 2610 2611 /** 2612 * Get the latest recording name the show name in save dialog but saved in 2613 * service 2614 * 2615 * @return The latest recording name or null for not modified 2616 */ getModifiedRecordingName()2617 public String getModifiedRecordingName() { 2618 return mModifiedRecordingName; 2619 } 2620 2621 /** 2622 * Set the latest recording name if modify the default name 2623 * 2624 * @param name The latest recording name or null for not modified 2625 */ setModifiedRecordingName(String name)2626 public void setModifiedRecordingName(String name) { 2627 mModifiedRecordingName = name; 2628 } 2629 2630 @Override onTaskRemoved(Intent rootIntent)2631 public void onTaskRemoved(Intent rootIntent) { 2632 exitFm(); 2633 super.onTaskRemoved(rootIntent); 2634 } 2635 firstPlaying(float frequency)2636 private boolean firstPlaying(float frequency) { 2637 if (mPowerStatus != POWER_UP) { 2638 Log.w(TAG, "firstPlaying, FM is not powered up"); 2639 return false; 2640 } 2641 boolean isSeekTune = false; 2642 float seekStation = FmNative.seek(frequency, false); 2643 int station = FmUtils.computeStation(seekStation); 2644 if (FmUtils.isValidStation(station)) { 2645 isSeekTune = FmNative.tune(seekStation); 2646 if (isSeekTune) { 2647 playFrequency(seekStation); 2648 } 2649 } 2650 // if tune fail, pass current station to update ui 2651 if (!isSeekTune) { 2652 seekStation = FmUtils.computeFrequency(mCurrentStation); 2653 } 2654 return isSeekTune; 2655 } 2656 2657 /** 2658 * Set the mIsDistanceExceed 2659 * @param exceed true is exceed, false is not exceed 2660 */ setDistanceExceed(boolean exceed)2661 public void setDistanceExceed(boolean exceed) { 2662 mIsDistanceExceed = exceed; 2663 } 2664 2665 /** 2666 * Set notification class name 2667 * @param clsName The target class name of activity 2668 */ setNotificationClsName(String clsName)2669 public void setNotificationClsName(String clsName) { 2670 mTargetClassName = clsName; 2671 } 2672 } 2673