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