blob: 97fdfe2b3076daf00778ab0285881b2e8039a378 [file] [log] [blame]
Benson Huanga8b6afc2014-11-20 15:42:26 +08001/*
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
17package com.android.fmradio;
18
19import android.app.Activity;
20import android.app.FragmentManager;
21import android.content.ActivityNotFoundException;
22import android.content.ComponentName;
23import android.content.ContentResolver;
24import android.content.ContentValues;
25import android.content.Context;
26import android.content.Intent;
27import android.content.ServiceConnection;
28import android.content.res.Resources;
29import android.database.Cursor;
30import android.media.AudioManager;
31import android.net.Uri;
32import android.os.Bundle;
33import android.os.Handler;
34import android.os.IBinder;
35import android.os.Message;
36import android.text.TextUtils;
37import android.util.Log;
38import android.view.Menu;
39import android.view.MenuInflater;
40import android.view.MenuItem;
41import android.view.View;
42import android.view.ViewConfiguration;
43import android.view.animation.Animation;
44import android.view.animation.Animation.AnimationListener;
45import android.view.animation.AnimationUtils;
46import android.widget.ImageButton;
47import android.widget.ImageView;
48import android.widget.LinearLayout;
49import android.widget.RelativeLayout;
50import android.widget.TextView;
51import android.widget.Toast;
52import android.widget.Toolbar;
53
54import com.android.fmradio.FmStation.Station;
55import com.android.fmradio.dialogs.FmFavoriteEditDialog;
56import com.android.fmradio.views.FmScroller;
57import com.android.fmradio.views.FmSnackBar;
58import com.android.fmradio.views.FmScroller.EventListener;
59
60import java.lang.reflect.Field;
61
62/**
63 * This class interact with user, provide FM basic function.
64 */
65public 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);
Benson Huangeb6ec932014-12-11 11:38:53 +0800230 mCurrentStation = station;
Benson Huanga8b6afc2014-11-20 15:42:26 +0800231 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
Benson Huang6e47fef2014-12-11 20:37:01 +0800243 case FmListener.MSGID_SWITCH_ANTENNA:
Benson Huanga8b6afc2014-11-20 15:42:26 +0800244 bundle = msg.getData();
Benson Huang6e47fef2014-12-11 20:37:01 +0800245 boolean hasAntenna = bundle.getBoolean(FmListener.KEY_IS_SWITCH_ANTENNA);
Benson Huanga8b6afc2014-11-20 15:42:26 +0800246 // if receive headset plug out, need set headset mode on ui
Benson Huang6e47fef2014-12-11 20:37:01 +0800247 if (hasAntenna) {
Benson Huang6982b3a2014-12-17 18:14:18 +0800248 if (mIsActivityForeground) {
Benson Huangfc770ea2014-12-23 11:22:55 +0800249 cancelNoHeadsetAnimation();
Benson Huang6982b3a2014-12-17 18:14:18 +0800250 playMainAnimation();
251 } else {
252 changeToMainLayout();
253 }
Benson Huanga8b6afc2014-11-20 15:42:26 +0800254 } else {
255 mMenuItemHeadset.setIcon(R.drawable.btn_fm_headset_selector);
Benson Huang6982b3a2014-12-17 18:14:18 +0800256 if (mIsActivityForeground) {
Benson Huangfc770ea2014-12-23 11:22:55 +0800257 cancelMainAnimation();
Benson Huang6982b3a2014-12-17 18:14:18 +0800258 playNoHeadsetAnimation();
259 } else {
260 changeToNoHeadsetLayout();
261 }
Benson Huanga8b6afc2014-11-20 15:42:26 +0800262 }
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
Benson Huange2bd0202014-12-29 17:42:29 +0800327 case FmListener.LISTEN_RECORDSTATE_CHANGED:
328 if (mService != null) {
329 mService.updatePlayingNotification();
330 }
331 break;
332
Benson Huanga8b6afc2014-11-20 15:42:26 +0800333 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) {
Benson Huangfc770ea2014-12-23 11:22:55 +0800399 if (!isAntennaAvailable()) {
400 return;
401 }
Benson Huanga8b6afc2014-11-20 15:42:26 +0800402 changeToMainLayout();
Benson Huangfc770ea2014-12-23 11:22:55 +0800403 cancelMainAnimation();
Benson Huanga8b6afc2014-11-20 15:42:26 +0800404 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) {
Benson Huangfc770ea2014-12-23 11:22:55 +0800426 if (isAntennaAvailable()) {
427 return;
428 }
Benson Huanga8b6afc2014-11-20 15:42:26 +0800429 changeToNoHeadsetLayout();
Benson Huangfc770ea2014-12-23 11:22:55 +0800430 cancelNoHeadsetAnimation();
Benson Huanga8b6afc2014-11-20 15:42:26 +0800431 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();
Benson Huangc3939892015-01-05 21:04:40 +0800457 mTextStationName.setText(FmStation.getStationName(mContext, mCurrentStation));
Benson Huanga8b6afc2014-11-20 15:42:26 +0800458 } 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);
Benson Huang135af1d2014-12-25 17:29:38 +0800520 if (mCurrentStation == frequency) {
521 mTextStationName.setText(FmStation.getStationName(mContext, frequency));
522 }
Benson Huanga8b6afc2014-11-20 15:42:26 +0800523 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);
Benson Huanga8b6afc2014-11-20 15:42:26 +0800926 } else {
927 mButtonAddToFavorite.setImageResource(R.drawable.btn_fm_favorite_off_selector);
Benson Huanga8b6afc2014-11-20 15:42:26 +0800928 }
Benson Huangc520ec82014-12-24 18:07:35 +0800929 mTextStationName.setText(FmStation.getStationName(mContext, mCurrentStation));
Benson Huanga8b6afc2014-11-20 15:42:26 +0800930 }
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);
Benson Huang2816b932014-12-24 16:25:46 +08001006 // If BT headset is in use, need to disable speaker/earphone switching menu.
1007 mMenuItemHeadset.setEnabled(enabled && !mService.isBluetoothHeadsetInUse());
Benson Huanga8b6afc2014-11-20 15:42:26 +08001008 }
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);
Benson Huanga8b6afc2014-11-20 15:42:26 +08001027 }
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
Benson Huang62d4ffe2015-02-04 13:57:29 +08001161 /**
Benson Huanga8b6afc2014-11-20 15:42:26 +08001162 * play main animation
1163 */
1164 private void playMainAnimation() {
1165 if (null == mService) {
1166 Log.e(TAG, "playMainAnimation, mService is null");
1167 return;
1168 }
Benson Huanga8b6afc2014-11-20 15:42:26 +08001169 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
Benson Huang62d4ffe2015-02-04 13:57:29 +08001184 /**
Benson Huangfc770ea2014-12-23 11:22:55 +08001185 * clear main layout animation
1186 */
1187 private void cancelMainAnimation() {
1188 mNoEarPhoneTxt.clearAnimation();
1189 mNoHeadsetImgView.clearAnimation();
1190 mNoEarphoneTextLayout.clearAnimation();
1191 }
1192
Benson Huang62d4ffe2015-02-04 13:57:29 +08001193 /**
Benson Huanga8b6afc2014-11-20 15:42:26 +08001194 * 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 }
Benson Huanga8b6afc2014-11-20 15:42:26 +08001201 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
Benson Huang62d4ffe2015-02-04 13:57:29 +08001211 /**
Benson Huangfc770ea2014-12-23 11:22:55 +08001212 * clear no headset layout animation
1213 */
1214 private void cancelNoHeadsetAnimation() {
1215 mMainLayout.clearAnimation();
1216 mBtnPlayContainer.clearAnimation();
1217 }
1218
Benson Huanga8b6afc2014-11-20 15:42:26 +08001219 /**
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}