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.Activity; 20 import android.app.FragmentManager; 21 import android.content.ActivityNotFoundException; 22 import android.content.ComponentName; 23 import android.content.ContentResolver; 24 import android.content.ContentValues; 25 import android.content.Context; 26 import android.content.Intent; 27 import android.content.ServiceConnection; 28 import android.content.res.Resources; 29 import android.database.Cursor; 30 import android.media.AudioManager; 31 import android.net.Uri; 32 import android.os.Bundle; 33 import android.os.Handler; 34 import android.os.IBinder; 35 import android.os.Message; 36 import android.text.TextUtils; 37 import android.util.Log; 38 import android.view.Menu; 39 import android.view.MenuInflater; 40 import android.view.MenuItem; 41 import android.view.View; 42 import android.view.ViewConfiguration; 43 import android.view.animation.Animation; 44 import android.view.animation.Animation.AnimationListener; 45 import android.view.animation.AnimationUtils; 46 import android.widget.ImageButton; 47 import android.widget.ImageView; 48 import android.widget.LinearLayout; 49 import android.widget.RelativeLayout; 50 import android.widget.TextView; 51 import android.widget.Toast; 52 import android.widget.Toolbar; 53 54 import com.android.fmradio.FmStation.Station; 55 import com.android.fmradio.dialogs.FmFavoriteEditDialog; 56 import com.android.fmradio.views.FmScroller; 57 import com.android.fmradio.views.FmSnackBar; 58 import com.android.fmradio.views.FmScroller.EventListener; 59 60 import java.lang.reflect.Field; 61 62 /** 63 * This class interact with user, provide FM basic function. 64 */ 65 public class FmMainActivity extends Activity implements FmFavoriteEditDialog.EditFavoriteListener { 66 // Logging 67 private static final String TAG = "FmMainActivity"; 68 69 // Request code 70 private static final int REQUEST_CODE_FAVORITE = 1; 71 72 public static final int REQUEST_CODE_RECORDING = 2; 73 74 // Extra for result of request REQUEST_CODE_RECORDING 75 public static final String EXTRA_RESULT_STRING = "result_string"; 76 77 // FM 78 private static final String FM = "FM"; 79 80 // UI views 81 private TextView mTextStationName = null; 82 83 private TextView mTextStationValue = null; 84 85 // RDS text view 86 private TextView mTextRds = null; 87 88 private TextView mActionBarTitle = null; 89 90 private TextView mNoEarPhoneTxt = null; 91 92 private ImageButton mButtonDecrease = null; 93 94 private ImageButton mButtonPrevStation = null; 95 96 private ImageButton mButtonNextStation = null; 97 98 private ImageButton mButtonIncrease = null; 99 100 private ImageButton mButtonAddToFavorite = null; 101 102 private ImageButton mButtonPlay = null; 103 104 private ImageView mNoHeadsetImgView = null; 105 106 private View mNoHeadsetImgViewWrap = null; 107 108 private float mMiddleShadowSize; 109 110 private LinearLayout mMainLayout = null; 111 112 private RelativeLayout mNoHeadsetLayout = null; 113 114 private LinearLayout mNoEarphoneTextLayout = null; 115 116 private LinearLayout mBtnPlayInnerContainer = null; 117 118 private LinearLayout mBtnPlayContainer = null; 119 120 // Menu items 121 private MenuItem mMenuItemStationlList = null; 122 123 private MenuItem mMenuItemHeadset = null; 124 125 private MenuItem mMenuItemStartRecord = null; 126 127 private MenuItem mMenuItemRecordList = null; 128 129 // State variables 130 private boolean mIsServiceStarted = false; 131 132 private boolean mIsServiceBinded = false; 133 134 private boolean mIsTune = false; 135 136 private boolean mIsDisablePowerMenu = false; 137 138 private boolean mIsActivityForeground = true; 139 140 private int mCurrentStation = FmUtils.DEFAULT_STATION; 141 142 // Instance variables 143 private FmService mService = null; 144 145 private Context mContext = null; 146 147 private Toast mToast = null; 148 149 private FragmentManager mFragmentManager = null; 150 151 private AudioManager mAudioManager = null; 152 153 private FmScroller mScroller; 154 155 private FmScroller.EventListener mEventListener; 156 157 // Service listener 158 private FmListener mFmRadioListener = new FmListener() { 159 @Override 160 public void onCallBack(Bundle bundle) { 161 int flag = bundle.getInt(FmListener.CALLBACK_FLAG); 162 if (flag == FmListener.MSGID_FM_EXIT) { 163 mHandler.removeCallbacksAndMessages(null); 164 } 165 166 // remove tag message first, avoid too many same messages in queue. 167 Message msg = mHandler.obtainMessage(flag); 168 msg.setData(bundle); 169 mHandler.removeMessages(flag); 170 mHandler.sendMessage(msg); 171 } 172 }; 173 174 // Button click listeners on UI 175 private final View.OnClickListener mButtonClickListener = new View.OnClickListener() { 176 @Override 177 public void onClick(View v) { 178 switch (v.getId()) { 179 180 case R.id.button_add_to_favorite: 181 updateFavoriteStation(); 182 break; 183 184 case R.id.button_decrease: 185 tuneStation(FmUtils.computeDecreaseStation(mCurrentStation)); 186 break; 187 188 case R.id.button_increase: 189 tuneStation(FmUtils.computeIncreaseStation(mCurrentStation)); 190 break; 191 192 case R.id.button_prevstation: 193 seekStation(mCurrentStation, false); // false: previous station 194 break; 195 196 case R.id.button_nextstation: 197 seekStation(mCurrentStation, true); // true: previous station 198 break; 199 200 case R.id.play_button: 201 if (mService.getPowerStatus() == FmService.POWER_UP) { 202 powerDownFm(); 203 } else { 204 powerUpFm(); 205 } 206 break; 207 default: 208 Log.d(TAG, "mButtonClickListener.onClick, invalid view id"); 209 break; 210 } 211 } 212 }; 213 214 /** 215 * Main thread handler to update UI 216 */ 217 private Handler mHandler = new Handler() { 218 @Override 219 public void handleMessage(Message msg) { 220 Log.d(TAG, 221 "mHandler.handleMessage, what = " + msg.what + ",hashcode:" 222 + mHandler.hashCode()); 223 Bundle bundle; 224 switch (msg.what) { 225 226 case FmListener.MSGID_POWERUP_FINISHED: 227 bundle = msg.getData(); 228 boolean isPowerup = (mService.getPowerStatus() == FmService.POWER_UP); 229 int station = bundle.getInt(FmListener.KEY_TUNE_TO_STATION); 230 mCurrentStation = station; 231 refreshStationUI(station); 232 if (isPowerup) { 233 refreshImageButton(true); 234 refreshPopupMenuItem(true); 235 refreshActionMenuItem(true); 236 } else { 237 showToast(getString(R.string.not_available)); 238 } 239 // if not powerup success, refresh power to enable. 240 refreshPlayButton(true); 241 break; 242 243 case FmListener.MSGID_SWITCH_ANTENNA: 244 bundle = msg.getData(); 245 boolean hasAntenna = bundle.getBoolean(FmListener.KEY_IS_SWITCH_ANTENNA); 246 // if receive headset plug out, need set headset mode on ui 247 if (hasAntenna) { 248 if (mIsActivityForeground) { 249 cancelNoHeadsetAnimation(); 250 playMainAnimation(); 251 } else { 252 changeToMainLayout(); 253 } 254 } else { 255 mMenuItemHeadset.setIcon(R.drawable.btn_fm_headset_selector); 256 if (mIsActivityForeground) { 257 cancelMainAnimation(); 258 playNoHeadsetAnimation(); 259 } else { 260 changeToNoHeadsetLayout(); 261 } 262 } 263 break; 264 265 case FmListener.MSGID_POWERDOWN_FINISHED: 266 bundle = msg.getData(); 267 refreshImageButton(false); 268 refreshActionMenuItem(false); 269 refreshPopupMenuItem(false); 270 refreshPlayButton(true); 271 break; 272 273 case FmListener.MSGID_TUNE_FINISHED: 274 bundle = msg.getData(); 275 boolean isTune = bundle.getBoolean(FmListener.KEY_IS_TUNE); 276 boolean isPowerUp = (mService.getPowerStatus() == FmService.POWER_UP); 277 278 // tune finished, should make power enable 279 mIsDisablePowerMenu = false; 280 float frequency = bundle.getFloat(FmListener.KEY_TUNE_TO_STATION); 281 mCurrentStation = FmUtils.computeStation(frequency); 282 // After tune to station finished, refresh favorite button and 283 // other button status. 284 refreshStationUI(mCurrentStation); 285 // tune fail,should resume button status 286 if (!isTune) { 287 Log.d(TAG, "mHandler.tune: " + isTune); 288 refreshActionMenuItem(isPowerUp); 289 refreshImageButton(isPowerUp); 290 refreshPopupMenuItem(isPowerUp); 291 refreshPlayButton(true); 292 return; 293 } 294 refreshImageButton(true); 295 refreshActionMenuItem(true); 296 refreshPopupMenuItem(true); 297 refreshPlayButton(true); 298 break; 299 300 case FmListener.MSGID_FM_EXIT: 301 finish(); 302 break; 303 304 case FmListener.LISTEN_RDSSTATION_CHANGED: 305 bundle = msg.getData(); 306 int rdsStation = bundle.getInt(FmListener.KEY_RDS_STATION); 307 refreshStationUI(rdsStation); 308 break; 309 310 case FmListener.LISTEN_PS_CHANGED: 311 String stationName = FmStation.getStationName(mContext, mCurrentStation); 312 mTextStationName.setText(stationName); 313 mScroller.notifyAdatperChange(); 314 break; 315 316 case FmListener.LISTEN_RT_CHANGED: 317 bundle = msg.getData(); 318 String rtString = bundle.getString(FmListener.KEY_RT_INFO); 319 mTextRds.setText(rtString); 320 break; 321 322 case FmListener.LISTEN_SPEAKER_MODE_CHANGED: 323 bundle = msg.getData(); 324 boolean isSpeakerMode = bundle.getBoolean(FmListener.KEY_IS_SPEAKER_MODE); 325 break; 326 327 case FmListener.LISTEN_RECORDSTATE_CHANGED: 328 if (mService != null) { 329 mService.updatePlayingNotification(); 330 } 331 break; 332 333 default: 334 break; 335 } 336 } 337 }; 338 339 // When call bind service, it will call service connect. register call back 340 // listener and initial device 341 private final ServiceConnection mServiceConnection = new ServiceConnection() { 342 343 /** 344 * called by system when bind service 345 * 346 * @param className component name 347 * @param service service binder 348 */ 349 @Override 350 public void onServiceConnected(ComponentName className, IBinder service) { 351 mService = ((FmService.ServiceBinder) service).getService(); 352 if (null == mService) { 353 Log.e(TAG, "onServiceConnected, mService is null"); 354 finish(); 355 return; 356 } 357 358 mService.registerFmRadioListener(mFmRadioListener); 359 mService.setFmMainActivityForeground(mIsActivityForeground); 360 if (FmRecorder.STATE_RECORDING != mService.getRecorderState()) { 361 mService.removeNotification(); 362 } 363 if (!mService.isServiceInited()) { 364 mService.initService(mCurrentStation); 365 powerUpFm(); 366 } else { 367 if (mService.isDeviceOpen()) { 368 // tune to station during changing language,we need to tune 369 // again when service bind success 370 if (mIsTune) { 371 tuneStation(mCurrentStation); 372 mIsTune = false; 373 } 374 updateCurrentStation(); 375 updateMenuStatus(); 376 } else { 377 // Normal case will not come here 378 // Need to exit FM for this case 379 exitService(); 380 finish(); 381 } 382 } 383 } 384 385 /** 386 * When unbind service will call this method 387 * 388 * @param className The component name 389 */ 390 @Override 391 public void onServiceDisconnected(ComponentName className) { 392 } 393 }; 394 395 private class NoHeadsetAlpaOutListener implements AnimationListener { 396 397 @Override 398 public void onAnimationEnd(Animation animation) { 399 if (!isAntennaAvailable()) { 400 return; 401 } 402 changeToMainLayout(); 403 cancelMainAnimation(); 404 Animation anim = AnimationUtils.loadAnimation(mContext, 405 R.anim.main_alpha_in); 406 mMainLayout.startAnimation(anim); 407 anim = AnimationUtils.loadAnimation(mContext, R.anim.floatbtn_alpha_in); 408 409 mBtnPlayContainer.startAnimation(anim); 410 } 411 412 @Override 413 public void onAnimationRepeat(Animation animation) { 414 } 415 416 @Override 417 public void onAnimationStart(Animation animation) { 418 mNoHeadsetImgViewWrap.setElevation(0); 419 } 420 } 421 422 private class NoHeadsetAlpaInListener implements AnimationListener { 423 424 @Override 425 public void onAnimationEnd(Animation animation) { 426 if (isAntennaAvailable()) { 427 return; 428 } 429 changeToNoHeadsetLayout(); 430 cancelNoHeadsetAnimation(); 431 Animation anim = AnimationUtils.loadAnimation(mContext, 432 R.anim.noeaphone_alpha_in); 433 mNoHeadsetLayout.startAnimation(anim); 434 } 435 436 @Override 437 public void onAnimationRepeat(Animation animation) { 438 } 439 440 @Override 441 public void onAnimationStart(Animation animation) { 442 mNoHeadsetImgViewWrap.setElevation(mMiddleShadowSize); 443 } 444 445 } 446 447 /** 448 * Update the favorite UI state 449 */ 450 private void updateFavoriteStation() { 451 // Judge the current output and switch between the devices. 452 if (FmStation.isFavoriteStation(mContext, mCurrentStation)) { 453 FmStation.removeFromFavorite(mContext, mCurrentStation); 454 mButtonAddToFavorite.setImageResource(R.drawable.btn_fm_favorite_off_selector); 455 // Notify scroller 456 mScroller.onRemoveFavorite(); 457 mTextStationName.setText(FmStation.getStationName(mContext, mCurrentStation)); 458 } else { 459 // Add the station to favorite 460 if (FmStation.isStationExist(mContext, mCurrentStation)) { 461 FmStation.addToFavorite(mContext, mCurrentStation); 462 } else { 463 ContentValues values = new ContentValues(2); 464 values.put(Station.FREQUENCY, mCurrentStation); 465 values.put(Station.IS_FAVORITE, true); 466 FmStation.insertStationToDb(mContext, values); 467 } 468 mButtonAddToFavorite.setImageResource(R.drawable.btn_fm_favorite_on_selector); 469 // Notify scroller 470 mScroller.onAddFavorite(); 471 } 472 } 473 474 /** 475 * Called when the activity is first created, initial variables 476 * 477 * @param savedInstanceState The saved bundle in onSaveInstanceState 478 */ 479 @Override 480 public void onCreate(Bundle savedInstanceState) { 481 super.onCreate(savedInstanceState); 482 // Bind the activity to FM audio stream. 483 setVolumeControlStream(AudioManager.STREAM_MUSIC); 484 setContentView(R.layout.main); 485 try { 486 ViewConfiguration config = ViewConfiguration.get(this); 487 Field menuKeyField = ViewConfiguration.class.getDeclaredField("sHasPermanentMenuKey"); 488 if (menuKeyField != null) { 489 menuKeyField.setAccessible(true); 490 menuKeyField.setBoolean(config, false); 491 } 492 } catch (NoSuchFieldException e) { 493 e.printStackTrace(); 494 } catch (IllegalAccessException e) { 495 e.printStackTrace(); 496 } catch (IllegalArgumentException e) { 497 e.printStackTrace(); 498 } 499 500 mFragmentManager = getFragmentManager(); 501 mContext = getApplicationContext(); 502 503 initUiComponent(); 504 registerButtonClickListener(); 505 mAudioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE); 506 507 mScroller = (FmScroller) findViewById(R.id.multiscroller); 508 mScroller.initialize(); 509 mEventListener = new EventListener() { 510 @Override 511 public void onRename(int frequency) { 512 showRenameDialog(frequency); 513 } 514 515 @Override 516 public void onRemoveFavorite(int frequency) { 517 // TODO it's on UI thread, change to sub thread 518 if (FmStation.isFavoriteStation(mContext, frequency)) { 519 FmStation.removeFromFavorite(mContext, frequency); 520 if (mCurrentStation == frequency) { 521 mTextStationName.setText(FmStation.getStationName(mContext, frequency)); 522 } 523 mButtonAddToFavorite.setImageResource(R.drawable.btn_fm_favorite_off_selector); 524 // Notify scroller 525 mScroller.onRemoveFavorite(); 526 } 527 } 528 529 @Override 530 public void onPlay(int frequency) { 531 if (frequency != 0 && (mService.getPowerStatus() == FmService.POWER_UP)) { 532 tuneStation(frequency); 533 } 534 } 535 }; 536 mScroller.registerListener(mEventListener); 537 } 538 539 @Override 540 public void editFavorite(int stationFreq, String name) { 541 FmStation.updateStationToDb(mContext, stationFreq, name); 542 if (mCurrentStation == stationFreq) { 543 String stationName = FmStation.getStationName(mContext, mCurrentStation); 544 mTextStationName.setText(stationName); 545 } 546 mScroller.notifyAdatperChange(); 547 String title = getString(R.string.toast_station_renamed); 548 FmSnackBar.make(FmMainActivity.this, title, null, null, 549 FmSnackBar.DEFAULT_DURATION).show(); 550 } 551 552 /** 553 * Display the rename dialog 554 * 555 * @param frequency The display frequency 556 */ 557 public void showRenameDialog(int frequency) { 558 if (mService != null) { 559 String name = FmStation.getStationName(mContext, frequency); 560 FmFavoriteEditDialog newFragment = FmFavoriteEditDialog.newInstance(name, frequency); 561 newFragment.show(mFragmentManager, "TAG_EDIT_FAVORITE"); 562 mFragmentManager.executePendingTransactions(); 563 } 564 } 565 566 /** 567 * Go to station list activity 568 */ 569 private void enterStationList() { 570 if (mService != null) { 571 // AMS change the design for background start 572 // activity. need check app is background in app code 573 if (mService.isActivityForeground()) { 574 Intent intent = new Intent(); 575 intent.setClass(FmMainActivity.this, FmFavoriteActivity.class); 576 startActivityForResult(intent, REQUEST_CODE_FAVORITE); 577 } 578 } 579 } 580 581 /** 582 * Refresh the favorite button with the given station, if the station 583 * is favorite station, show favorite icon, else show non-favorite icon. 584 * 585 * @param station The station frequency 586 */ 587 private void refreshStationUI(int station) { 588 if (FmUtils.isFirstTimePlayFm(mContext)) { 589 Log.d(TAG, "refreshStationUI, set station value null when it is first time "); 590 return; 591 } 592 // TODO it's on UI thread, change to sub thread 593 // Change the station frequency displayed. 594 mTextStationValue.setText(FmUtils.formatStation(station)); 595 // Show or hide the favorite icon 596 if (FmStation.isFavoriteStation(mContext, station)) { 597 mButtonAddToFavorite.setImageResource(R.drawable.btn_fm_favorite_on_selector); 598 } else { 599 mButtonAddToFavorite.setImageResource(R.drawable.btn_fm_favorite_off_selector); 600 } 601 602 String stationName = ""; 603 String radioText = ""; 604 ContentResolver resolver = mContext.getContentResolver(); 605 Cursor cursor = null; 606 try { 607 cursor = resolver.query( 608 Station.CONTENT_URI, 609 FmStation.COLUMNS, 610 Station.FREQUENCY + "=?", 611 new String[] { String.valueOf(mCurrentStation) }, 612 null); 613 if (cursor != null && cursor.moveToFirst()) { 614 // If the station name is not exist, show program service(PS) instead 615 stationName = cursor.getString(cursor.getColumnIndex(Station.STATION_NAME)); 616 if (TextUtils.isEmpty(stationName)) { 617 stationName = cursor.getString(cursor.getColumnIndex(Station.PROGRAM_SERVICE)); 618 } 619 radioText = cursor.getString(cursor.getColumnIndex(Station.RADIO_TEXT)); 620 621 } else { 622 Log.d(TAG, "showPlayingNotification, cursor is null"); 623 } 624 } finally { 625 if (cursor != null) { 626 cursor.close(); 627 } 628 } 629 mTextStationName.setText(stationName); 630 mTextRds.setText(radioText); 631 } 632 633 /** 634 * Start and bind service, reduction variable values if configuration changed 635 */ 636 @Override 637 public void onStart() { 638 super.onStart(); 639 // check layout onstart 640 if (isAntennaAvailable()) { 641 changeToMainLayout(); 642 } else { 643 changeToNoHeadsetLayout(); 644 } 645 646 // Should start FM service first. 647 if (null == startService(new Intent(FmMainActivity.this, FmService.class))) { 648 Log.e(TAG, "onStart, cannot start FM service"); 649 return; 650 } 651 652 mIsServiceStarted = true; 653 mIsServiceBinded = bindService(new Intent(FmMainActivity.this, FmService.class), 654 mServiceConnection, Context.BIND_AUTO_CREATE); 655 656 if (!mIsServiceBinded) { 657 Log.e(TAG, "onStart, cannot bind FM service"); 658 finish(); 659 return; 660 } 661 } 662 663 /** 664 * Refresh UI, when stop search, dismiss search dialog, 665 * pop up recording dialog if FM stopped when recording in 666 * background 667 */ 668 @Override 669 public void onResume() { 670 super.onResume(); 671 mIsActivityForeground = true; 672 mScroller.onResume(); 673 if (null == mService) { 674 Log.d(TAG, "onResume, mService is null"); 675 return; 676 } 677 mService.setFmMainActivityForeground(mIsActivityForeground); 678 if (FmRecorder.STATE_RECORDING != mService.getRecorderState()) { 679 mService.removeNotification(); 680 } 681 updateMenuStatus(); 682 } 683 684 /** 685 * When activity is paused call this method, indicate activity 686 * enter background if press exit, power down FM 687 */ 688 @Override 689 public void onPause() { 690 mIsActivityForeground = false; 691 if (null != mService) { 692 mService.setFmMainActivityForeground(mIsActivityForeground); 693 } 694 mScroller.onPause(); 695 super.onPause(); 696 } 697 698 /** 699 * Called when activity enter stopped state, 700 * unbind service, if exit pressed, stop service 701 */ 702 @Override 703 public void onStop() { 704 if (null != mService) { 705 mService.setNotificationClsName(FmMainActivity.class.getName()); 706 mService.updatePlayingNotification(); 707 } 708 if (mIsServiceBinded) { 709 unbindService(mServiceConnection); 710 mIsServiceBinded = false; 711 } 712 super.onStop(); 713 } 714 715 /** 716 * W activity destroy, unregister broadcast receiver and remove handler message 717 */ 718 @Override 719 public void onDestroy() { 720 // need to call this function because if doesn't do this,after 721 // configuration change will have many instance and recording time 722 // or playing time will not refresh 723 // Remove all the handle message 724 mHandler.removeCallbacksAndMessages(null); 725 if (mService != null) { 726 mService.unregisterFmRadioListener(mFmRadioListener); 727 } 728 mFmRadioListener = null; 729 mScroller.closeAdapterCursor(); 730 mScroller.unregisterListener(mEventListener); 731 super.onDestroy(); 732 } 733 734 /** 735 * Create options menu 736 * 737 * @param menu The option menu 738 * @return true or false indicate need to handle other menu item 739 */ 740 @Override 741 public boolean onCreateOptionsMenu(Menu menu) { 742 MenuInflater inflater = getMenuInflater(); 743 inflater.inflate(R.menu.fm_action_bar, menu); 744 mMenuItemStationlList = menu.findItem(R.id.fm_station_list); 745 mMenuItemHeadset = menu.findItem(R.id.fm_headset); 746 mMenuItemStartRecord = menu.findItem(R.id.fm_start_record); 747 mMenuItemRecordList = menu.findItem(R.id.fm_record_list); 748 return true; 749 } 750 751 /** 752 * Prepare options menu 753 * 754 * @param menu The option menu 755 * @return true or false indicate need to handle other menu item 756 */ 757 @Override 758 public boolean onPrepareOptionsMenu(Menu menu) { 759 if (null == mService) { 760 Log.d(TAG, "onPrepareOptionsMenu, mService is null"); 761 return true; 762 } 763 int powerStatus = mService.getPowerStatus(); 764 boolean isPowerUp = (powerStatus == FmService.POWER_UP); 765 boolean isPowerdown = (powerStatus == FmService.POWER_DOWN); 766 boolean isSeeking = mService.isSeeking(); 767 boolean isSpeakerUsed = mService.isSpeakerUsed(); 768 // if fm power down by other app, should enable power menu, make it to 769 // powerup. 770 refreshActionMenuItem(isSeeking ? false : isPowerUp); 771 refreshPlayButton(isSeeking ? false 772 : (isPowerUp || (isPowerdown && !mIsDisablePowerMenu))); 773 mMenuItemHeadset.setIcon(isSpeakerUsed ? R.drawable.btn_fm_speaker_selector 774 : R.drawable.btn_fm_headset_selector); 775 return true; 776 } 777 778 /** 779 * Handle event when option item selected 780 * 781 * @param item The clicked item 782 * @return true or false indicate need to handle other menu item or not 783 */ 784 @Override 785 public boolean onOptionsItemSelected(MenuItem item) { 786 switch (item.getItemId()) { 787 case android.R.id.home: 788 onBackPressed(); 789 break; 790 791 case R.id.fm_station_list: 792 refreshImageButton(false); 793 refreshActionMenuItem(false); 794 refreshPopupMenuItem(false); 795 refreshPlayButton(false); 796 // Show favorite activity. 797 enterStationList(); 798 break; 799 800 case R.id.earphone_menu: 801 setSpeakerPhoneOn(false); 802 mMenuItemHeadset.setIcon(R.drawable.btn_fm_headset_selector); 803 invalidateOptionsMenu(); 804 break; 805 806 case R.id.speaker_menu: 807 setSpeakerPhoneOn(true); 808 mMenuItemHeadset.setIcon(R.drawable.btn_fm_speaker_selector); 809 invalidateOptionsMenu(); 810 break; 811 812 case R.id.fm_start_record: 813 Intent recordIntent = new Intent(this, FmRecordActivity.class); 814 recordIntent.putExtra(FmStation.CURRENT_STATION, mCurrentStation); 815 startActivityForResult(recordIntent, REQUEST_CODE_RECORDING); 816 break; 817 818 case R.id.fm_record_list: 819 Intent playMusicIntent = new Intent(Intent.ACTION_VIEW); 820 int playlistId = FmRecorder.getPlaylistId(mContext); 821 Bundle extras = new Bundle(); 822 extras.putInt("playlist", playlistId); 823 try { 824 playMusicIntent.putExtras(extras); 825 playMusicIntent.setClassName("com.google.android.music", 826 "com.google.android.music.ui.TrackContainerActivity"); 827 playMusicIntent.setType("vnd.android.cursor.dir/playlist"); 828 startActivity(playMusicIntent); 829 } catch (ActivityNotFoundException e1) { 830 try { 831 playMusicIntent = new Intent(Intent.ACTION_VIEW); 832 playMusicIntent.putExtras(extras); 833 playMusicIntent.setType("vnd.android.cursor.dir/playlist"); 834 startActivity(playMusicIntent); 835 } catch (ActivityNotFoundException e2) { 836 // No activity respond 837 Log.d(TAG, 838 "onOptionsItemSelected, No activity respond playlist view intent"); 839 } 840 } 841 break; 842 default: 843 Log.e(TAG, "onOptionsItemSelected, invalid options menu item."); 844 break; 845 } 846 return super.onOptionsItemSelected(item); 847 } 848 849 /** 850 * Check whether antenna is available 851 * 852 * @return true or false indicate antenna available or not 853 */ 854 private boolean isAntennaAvailable() { 855 return mAudioManager.isWiredHeadsetOn(); 856 } 857 858 /** 859 * When on activity result, tune to station which is from station list 860 * 861 * @param requestCode The request code 862 * @param resultCode The result code 863 * @param data The intent from station list 864 */ 865 @Override 866 protected void onActivityResult(int requestCode, int resultCode, Intent data) { 867 if (RESULT_OK == resultCode) { 868 if (REQUEST_CODE_RECORDING == requestCode) { 869 final Uri playUri = data.getData(); 870 boolean isSaved = playUri != null; 871 String title = data.getStringExtra(EXTRA_RESULT_STRING); 872 String action = null; 873 FmSnackBar.OnActionTriggerListener listener = null; 874 875 if (isSaved) { 876 action = FmMainActivity.this.getString(R.string.toast_listen); 877 listener = new FmSnackBar.OnActionTriggerListener() { 878 @Override 879 public void onActionTriggered() { 880 Intent playMusicIntent = new Intent(Intent.ACTION_VIEW); 881 try { 882 playMusicIntent.setClassName("com.google.android.music", 883 "com.google.android.music.AudioPreview"); 884 playMusicIntent.setDataAndType(playUri, "audio/3gpp"); 885 startActivity(playMusicIntent); 886 } catch (ActivityNotFoundException e1) { 887 try { 888 playMusicIntent = new Intent(Intent.ACTION_VIEW); 889 playMusicIntent.setDataAndType(playUri, "audio/3gpp"); 890 startActivity(playMusicIntent); 891 } catch (ActivityNotFoundException e2) { 892 // No activity respond 893 Log.d(TAG,"onActivityResult, no activity " 894 + "respond play record file intent"); 895 } 896 } 897 } 898 }; 899 } 900 FmSnackBar.make(FmMainActivity.this, title, action, listener, 901 FmSnackBar.DEFAULT_DURATION).show(); 902 } else if (REQUEST_CODE_FAVORITE == requestCode) { 903 int iStation = 904 data.getIntExtra(FmFavoriteActivity.ACTIVITY_RESULT, mCurrentStation); 905 // Tune to this station. 906 mCurrentStation = iStation; 907 // if tune from station list, we should disable power menu, 908 // especially for power down state 909 mIsDisablePowerMenu = true; 910 Log.d(TAG, "onActivityForReult:" + mIsDisablePowerMenu); 911 if (null == mService) { 912 Log.d(TAG, "onActivityResult, mService is null"); 913 mIsTune = true; 914 return; 915 } 916 tuneStation(iStation); 917 } else { 918 Log.e(TAG, "onActivityResult, invalid requestcode."); 919 return; 920 } 921 } 922 923 // TODO it's on UI thread, change to sub thread 924 if (FmStation.isFavoriteStation(mContext, mCurrentStation)) { 925 mButtonAddToFavorite.setImageResource(R.drawable.btn_fm_favorite_on_selector); 926 } else { 927 mButtonAddToFavorite.setImageResource(R.drawable.btn_fm_favorite_off_selector); 928 } 929 mTextStationName.setText(FmStation.getStationName(mContext, mCurrentStation)); 930 } 931 932 /** 933 * Power up FM 934 */ 935 private void powerUpFm() { 936 refreshImageButton(false); 937 refreshActionMenuItem(false); 938 refreshPopupMenuItem(false); 939 refreshPlayButton(false); 940 mService.powerUpAsync(FmUtils.computeFrequency(mCurrentStation)); 941 } 942 943 /** 944 * Power down FM 945 */ 946 private void powerDownFm() { 947 refreshImageButton(false); 948 refreshActionMenuItem(false); 949 refreshPopupMenuItem(false); 950 refreshPlayButton(false); 951 mService.powerDownAsync(); 952 } 953 954 private void setSpeakerPhoneOn(boolean isSpeaker) { 955 if (isSpeaker) { 956 mService.setSpeakerPhoneOn(true); 957 } else { 958 mService.setSpeakerPhoneOn(false); 959 } 960 } 961 962 /** 963 * Tune a station 964 * 965 * @param station The tune station 966 */ 967 private void tuneStation(final int station) { 968 refreshImageButton(false); 969 refreshActionMenuItem(false); 970 refreshPopupMenuItem(false); 971 refreshPlayButton(false); 972 mService.tuneStationAsync(FmUtils.computeFrequency(station)); 973 } 974 975 /** 976 * Seek station according current frequency and direction 977 * 978 * @param station The seek start station 979 * @param direction The seek direction 980 */ 981 private void seekStation(final int station, boolean direction) { 982 // If the seek AsyncTask has been executed and not canceled, cancel it 983 // before start new. 984 refreshImageButton(false); 985 refreshActionMenuItem(false); 986 refreshPopupMenuItem(false); 987 refreshPlayButton(false); 988 mService.seekStationAsync(FmUtils.computeFrequency(station), direction); 989 } 990 991 private void refreshImageButton(boolean enabled) { 992 mButtonDecrease.setEnabled(enabled); 993 mButtonPrevStation.setEnabled(enabled); 994 mButtonNextStation.setEnabled(enabled); 995 mButtonIncrease.setEnabled(enabled); 996 mButtonAddToFavorite.setEnabled(enabled); 997 } 998 999 // Refresh action menu except power menu 1000 private void refreshActionMenuItem(boolean enabled) { 1001 // action menu 1002 if (null != mMenuItemStationlList) { 1003 // if power down by other app, should disable station list, over 1004 // menu 1005 mMenuItemStationlList.setEnabled(enabled); 1006 // If BT headset is in use, need to disable speaker/earphone switching menu. 1007 mMenuItemHeadset.setEnabled(enabled && !mService.isBluetoothHeadsetInUse()); 1008 } 1009 } 1010 1011 // Refresh play/stop float button 1012 private void refreshPlayButton(boolean enabled) { 1013 // action menu 1014 boolean isPowerUp = (mService.getPowerStatus() == FmService.POWER_UP); 1015 mButtonPlay.setEnabled(enabled); 1016 mButtonPlay.setImageResource((isPowerUp 1017 ? R.drawable.btn_fm_stop_selector 1018 : R.drawable.btn_fm_start_selector)); 1019 Resources r = getResources(); 1020 mBtnPlayInnerContainer.setBackground(r.getDrawable(R.drawable.fb_red)); 1021 mScroller.refreshPlayIndicator(mCurrentStation, isPowerUp); 1022 } 1023 1024 private void refreshPopupMenuItem(boolean enabled) { 1025 if (null != mMenuItemStationlList) { 1026 mMenuItemStartRecord.setEnabled(enabled); 1027 } 1028 } 1029 1030 /** 1031 * Called when back pressed 1032 */ 1033 @Override 1034 public void onBackPressed() { 1035 // exit fm, disable all button 1036 if ((null != mService) && (mService.getPowerStatus() == FmService.POWER_DOWN)) { 1037 refreshImageButton(false); 1038 refreshActionMenuItem(false); 1039 refreshPopupMenuItem(false); 1040 refreshPlayButton(false); 1041 exitService(); 1042 return; 1043 } 1044 super.onBackPressed(); 1045 } 1046 1047 private void showToast(CharSequence text) { 1048 if (null == mToast) { 1049 mToast = Toast.makeText(mContext, text, Toast.LENGTH_SHORT); 1050 } 1051 mToast.setText(text); 1052 mToast.show(); 1053 } 1054 1055 @Override 1056 protected void onSaveInstanceState(Bundle outState) { 1057 super.onSaveInstanceState(outState); 1058 } 1059 1060 /** 1061 * Exit FM service 1062 */ 1063 private void exitService() { 1064 if (mIsServiceBinded) { 1065 unbindService(mServiceConnection); 1066 mIsServiceBinded = false; 1067 } 1068 1069 if (mIsServiceStarted) { 1070 stopService(new Intent(FmMainActivity.this, FmService.class)); 1071 mIsServiceStarted = false; 1072 } 1073 } 1074 1075 /** 1076 * Update current station according service station 1077 */ 1078 private void updateCurrentStation() { 1079 // get the frequency from service, set frequency in activity, UI, 1080 // database 1081 // same as the frequency in service 1082 int freq = mService.getFrequency(); 1083 if (FmUtils.isValidStation(freq)) { 1084 if (mCurrentStation != freq) { 1085 mCurrentStation = freq; 1086 FmStation.setCurrentStation(mContext, mCurrentStation); 1087 refreshStationUI(mCurrentStation); 1088 } 1089 } 1090 } 1091 1092 /** 1093 * Update menu status, and animation 1094 */ 1095 private void updateMenuStatus() { 1096 int powerStatus = mService.getPowerStatus(); 1097 boolean isPowerUp = (powerStatus == FmService.POWER_UP); 1098 boolean isDuringPowerup = (powerStatus == FmService.DURING_POWER_UP); 1099 boolean isSeeking = mService.isSeeking(); 1100 boolean isPowerdown = (powerStatus == FmService.POWER_DOWN); 1101 boolean isSpeakerUsed = mService.isSpeakerUsed(); 1102 boolean fmStatus = (isSeeking || isDuringPowerup); 1103 // when seeking, all button should disabled, 1104 // else should update as origin status 1105 refreshImageButton(fmStatus ? false : isPowerUp); 1106 refreshPopupMenuItem(fmStatus ? false : isPowerUp); 1107 refreshActionMenuItem(fmStatus ? false : isPowerUp); 1108 // if fm power down by other app, should enable power button 1109 // to powerup. 1110 Log.d(TAG, "updateMenuStatus.mIsDisablePowerMenu: " + mIsDisablePowerMenu); 1111 refreshPlayButton(fmStatus ? false 1112 : (isPowerUp || (isPowerdown && !mIsDisablePowerMenu))); 1113 if (null != mMenuItemHeadset) { 1114 mMenuItemHeadset.setIcon(isSpeakerUsed ? R.drawable.btn_fm_speaker_selector 1115 : R.drawable.btn_fm_headset_selector); 1116 } 1117 1118 } 1119 1120 private void initUiComponent() { 1121 mTextRds = (TextView) findViewById(R.id.station_rds); 1122 mTextStationValue = (TextView) findViewById(R.id.station_value); 1123 mButtonAddToFavorite = (ImageButton) findViewById(R.id.button_add_to_favorite); 1124 mTextStationName = (TextView) findViewById(R.id.station_name); 1125 mButtonDecrease = (ImageButton) findViewById(R.id.button_decrease); 1126 mButtonIncrease = (ImageButton) findViewById(R.id.button_increase); 1127 mButtonPrevStation = (ImageButton) findViewById(R.id.button_prevstation); 1128 mButtonNextStation = (ImageButton) findViewById(R.id.button_nextstation); 1129 1130 // put favorite button here since it might be used very early in 1131 // changing recording mode 1132 mCurrentStation = FmStation.getCurrentStation(mContext); 1133 refreshStationUI(mCurrentStation); 1134 1135 // l new 1136 mMainLayout = (LinearLayout) findViewById(R.id.main_view); 1137 mNoHeadsetLayout = (RelativeLayout) findViewById(R.id.no_headset); 1138 mNoEarphoneTextLayout = (LinearLayout) findViewById(R.id.no_bottom); 1139 mBtnPlayContainer = (LinearLayout) findViewById(R.id.play_button_container); 1140 mBtnPlayInnerContainer = (LinearLayout) findViewById(R.id.play_button_inner_container); 1141 mButtonPlay = (ImageButton) findViewById(R.id.play_button); 1142 mNoEarPhoneTxt = (TextView) findViewById(R.id.no_eaphone_text); 1143 mNoHeadsetImgView = (ImageView) findViewById(R.id.no_headset_img); 1144 mNoHeadsetImgViewWrap = findViewById(R.id.no_middle); 1145 mMiddleShadowSize = getResources().getDimension(R.dimen.fm_middle_shadow); 1146 // main ui layout params 1147 final Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); 1148 setActionBar(toolbar); 1149 getActionBar().setTitle(""); 1150 } 1151 1152 private void registerButtonClickListener() { 1153 mButtonAddToFavorite.setOnClickListener(mButtonClickListener); 1154 mButtonDecrease.setOnClickListener(mButtonClickListener); 1155 mButtonIncrease.setOnClickListener(mButtonClickListener); 1156 mButtonPrevStation.setOnClickListener(mButtonClickListener); 1157 mButtonNextStation.setOnClickListener(mButtonClickListener); 1158 mButtonPlay.setOnClickListener(mButtonClickListener); 1159 } 1160 1161 /** 1162 * play main animation 1163 */ 1164 private void playMainAnimation() { 1165 if (null == mService) { 1166 Log.e(TAG, "playMainAnimation, mService is null"); 1167 return; 1168 } 1169 if (mMainLayout.isShown()) { 1170 Log.w(TAG, "playMainAnimation, main layout has already shown"); 1171 return; 1172 } 1173 Animation animation = AnimationUtils.loadAnimation(mContext, 1174 R.anim.noeaphone_alpha_out); 1175 mNoEarPhoneTxt.startAnimation(animation); 1176 mNoHeadsetImgView.startAnimation(animation); 1177 1178 animation = AnimationUtils.loadAnimation(mContext, 1179 R.anim.noeaphone_translate_out); 1180 animation.setAnimationListener(new NoHeadsetAlpaOutListener()); 1181 mNoEarphoneTextLayout.startAnimation(animation); 1182 } 1183 1184 /** 1185 * clear main layout animation 1186 */ 1187 private void cancelMainAnimation() { 1188 mNoEarPhoneTxt.clearAnimation(); 1189 mNoHeadsetImgView.clearAnimation(); 1190 mNoEarphoneTextLayout.clearAnimation(); 1191 } 1192 1193 /** 1194 * play change to no headset layout animation 1195 */ 1196 private void playNoHeadsetAnimation() { 1197 if (null == mService) { 1198 Log.e(TAG, "playNoHeadsetAnimation, mService is null"); 1199 return; 1200 } 1201 if (mNoHeadsetLayout.isShown()) { 1202 Log.w(TAG,"playNoHeadsetAnimation, no headset layout has already shown"); 1203 return; 1204 } 1205 Animation animation = AnimationUtils.loadAnimation(mContext, R.anim.main_alpha_out); 1206 mMainLayout.startAnimation(animation); 1207 animation.setAnimationListener(new NoHeadsetAlpaInListener()); 1208 mBtnPlayContainer.startAnimation(animation); 1209 } 1210 1211 /** 1212 * clear no headset layout animation 1213 */ 1214 private void cancelNoHeadsetAnimation() { 1215 mMainLayout.clearAnimation(); 1216 mBtnPlayContainer.clearAnimation(); 1217 } 1218 1219 /** 1220 * change to main layout 1221 */ 1222 private void changeToMainLayout() { 1223 mNoEarphoneTextLayout.setVisibility(View.GONE); 1224 mNoHeadsetImgView.setVisibility(View.GONE); 1225 mNoHeadsetImgViewWrap.setVisibility(View.GONE); 1226 mNoHeadsetLayout.setVisibility(View.GONE); 1227 // change to main layout 1228 mMainLayout.setVisibility(View.VISIBLE); 1229 mBtnPlayContainer.setVisibility(View.VISIBLE); 1230 } 1231 1232 /** 1233 * change to no headset layout 1234 */ 1235 private void changeToNoHeadsetLayout() { 1236 mMainLayout.setVisibility(View.GONE); 1237 mBtnPlayContainer.setVisibility(View.GONE); 1238 mNoEarphoneTextLayout.setVisibility(View.VISIBLE); 1239 mNoHeadsetImgView.setVisibility(View.VISIBLE); 1240 mNoHeadsetImgViewWrap.setVisibility(View.VISIBLE); 1241 mNoHeadsetLayout.setVisibility(View.VISIBLE); 1242 mNoHeadsetImgViewWrap.setElevation(mMiddleShadowSize); 1243 } 1244 } 1245