• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
onAnimationEnd(Animation animation)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
onAnimationRepeat(Animation animation)413         public void onAnimationRepeat(Animation animation) {
414         }
415 
416         @Override
onAnimationStart(Animation animation)417         public void onAnimationStart(Animation animation) {
418             mNoHeadsetImgViewWrap.setElevation(0);
419         }
420     }
421 
422     private class NoHeadsetAlpaInListener implements AnimationListener {
423 
424         @Override
onAnimationEnd(Animation animation)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
onAnimationRepeat(Animation animation)437         public void onAnimationRepeat(Animation animation) {
438         }
439 
440         @Override
onAnimationStart(Animation animation)441         public void onAnimationStart(Animation animation) {
442             mNoHeadsetImgViewWrap.setElevation(mMiddleShadowSize);
443         }
444 
445     }
446 
447     /**
448      * Update the favorite UI state
449      */
updateFavoriteStation()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
onCreate(Bundle savedInstanceState)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
editFavorite(int stationFreq, String name)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      */
showRenameDialog(int frequency)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      */
enterStationList()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      */
refreshStationUI(int station)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
onStart()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
onResume()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
onPause()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
onStop()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
onDestroy()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
onCreateOptionsMenu(Menu menu)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
onPrepareOptionsMenu(Menu menu)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
onOptionsItemSelected(MenuItem item)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      */
isAntennaAvailable()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
onActivityResult(int requestCode, int resultCode, Intent data)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      */
powerUpFm()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      */
powerDownFm()946     private void powerDownFm() {
947         refreshImageButton(false);
948         refreshActionMenuItem(false);
949         refreshPopupMenuItem(false);
950         refreshPlayButton(false);
951         mService.powerDownAsync();
952     }
953 
setSpeakerPhoneOn(boolean isSpeaker)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      */
tuneStation(final int station)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      */
seekStation(final int station, boolean direction)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 
refreshImageButton(boolean enabled)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
refreshActionMenuItem(boolean enabled)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
refreshPlayButton(boolean enabled)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 
refreshPopupMenuItem(boolean enabled)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
onBackPressed()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 
showToast(CharSequence text)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
onSaveInstanceState(Bundle outState)1056     protected void onSaveInstanceState(Bundle outState) {
1057         super.onSaveInstanceState(outState);
1058     }
1059 
1060     /**
1061      * Exit FM service
1062      */
exitService()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      */
updateCurrentStation()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      */
updateMenuStatus()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 
initUiComponent()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 
registerButtonClickListener()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      */
playMainAnimation()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      */
cancelMainAnimation()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      */
playNoHeadsetAnimation()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      */
cancelNoHeadsetAnimation()1214     private void cancelNoHeadsetAnimation() {
1215         mMainLayout.clearAnimation();
1216         mBtnPlayContainer.clearAnimation();
1217     }
1218 
1219     /**
1220      * change to main layout
1221      */
changeToMainLayout()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      */
changeToNoHeadsetLayout()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