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