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.ActionBar;
     20 import android.app.Activity;
     21 import android.content.ComponentName;
     22 import android.content.Context;
     23 import android.content.Intent;
     24 import android.content.ServiceConnection;
     25 import android.database.Cursor;
     26 import android.graphics.Color;
     27 import android.graphics.PorterDuff;
     28 import android.location.Location;
     29 import android.location.LocationManager;
     30 import android.media.AudioManager;
     31 import android.os.Bundle;
     32 import android.os.Handler;
     33 import android.os.IBinder;
     34 import android.os.Looper;
     35 import android.os.Message;
     36 import android.text.SpannableString;
     37 import android.text.style.ForegroundColorSpan;
     38 import android.util.Log;
     39 import android.view.LayoutInflater;
     40 import android.view.Menu;
     41 import android.view.MenuInflater;
     42 import android.view.MenuItem;
     43 import android.view.View;
     44 import android.view.ViewGroup;
     45 import android.view.Window;
     46 import android.widget.AdapterView;
     47 import android.widget.BaseAdapter;
     48 import android.widget.ImageView;
     49 import android.widget.LinearLayout;
     50 import android.widget.ListView;
     51 import android.widget.ProgressBar;
     52 import android.widget.RelativeLayout;
     53 import android.widget.TextView;
     54 import android.widget.Toast;
     55 
     56 import com.android.fmradio.FmService.OnExitListener;
     57 import com.android.fmradio.FmStation.Station;
     58 
     59 /**
     60  * This class interact with user, provider edit station information, such as add
     61  * to favorite, edit favorite, delete from favorite
     62  */
     63 public class FmFavoriteActivity extends Activity {
     64     // Logging
     65     private static final String TAG = "FmFavoriteActivity";
     66 
     67     public static final String ACTIVITY_RESULT = "ACTIVITY_RESULT";
     68 
     69     private static final String SHOW_GPS_DIALOG = "SHOW_GPS_DIALOG";
     70 
     71     private static final String GPS_NOT_LOCATED_DIALOG = "GPS_NOT_LOCATED_DIALOG";
     72 
     73     private ListView mLvFavorites = null; // list view
     74 
     75     LinearLayout mSearchTips = null;
     76 
     77     private Context mContext = null; // application context
     78 
     79     private OnExitListener mExitListener = null;
     80 
     81     private MyFavoriteAdapter mMyAdapter;
     82 
     83     private ProgressBar mSearchProgress = null;
     84 
     85     private MenuItem mMenuRefresh = null;
     86 
     87     private LocationManager mLocationManager;
     88 
     89     private Location mCurLocation;
     90 
     91     private boolean mIsActivityForeground = true;
     92 
     93     /**
     94      * on create
     95      *
     96      * @param savedInstanceState The save instance state
     97      */
     98     @Override
     99     public void onCreate(Bundle savedInstanceState) {
    100         super.onCreate(savedInstanceState);
    101         // Bind the activity to FM audio stream.
    102         setVolumeControlStream(AudioManager.STREAM_MUSIC);
    103         getWindow().requestFeature(Window.FEATURE_ACTION_BAR);
    104         setContentView(R.layout.favorite);
    105         // display action bar and navigation button
    106         ActionBar actionBar = getActionBar();
    107         actionBar.setTitle(getString(R.string.station_title));
    108         actionBar.setDisplayHomeAsUpEnabled(true);
    109         mLocationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE);
    110         mContext = getApplicationContext();
    111 
    112         mMyAdapter = new MyFavoriteAdapter(mContext);
    113         mLvFavorites = (ListView) findViewById(R.id.station_list);
    114         mSearchTips = (LinearLayout) findViewById(R.id.search_tips);
    115         mSearchProgress = (ProgressBar) findViewById(R.id.search_progress);
    116         mLvFavorites.setAdapter(mMyAdapter); // set adapter
    117         mMyAdapter.swipResult(getData());
    118 
    119         mLvFavorites.setOnItemClickListener(new AdapterView.OnItemClickListener() {
    120             /**
    121              * Click list item will finish activity and pass value to other activity
    122              *
    123              * @param parent adapter view
    124              * @param view item view
    125              * @param position current position
    126              * @param id current id
    127              */
    128             @Override
    129             public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
    130                 // Set the selected frequency to main UI and finish the
    131                 // favorite manager.
    132                 TextView textView = (TextView) view.findViewById(R.id.lv_station_freq);
    133                 float frequency = 0;
    134                 try {
    135                     frequency = Float.parseFloat(textView.getText().toString());
    136                 } catch (NumberFormatException e) {
    137                     e.printStackTrace();
    138                 }
    139                 Intent intentResult = new Intent();
    140                 intentResult.putExtra(ACTIVITY_RESULT, FmUtils.computeStation(frequency));
    141                 setResult(RESULT_OK, intentResult);
    142                 finish();
    143             }
    144         });
    145 
    146         // Finish favorite when exit FM
    147         mExitListener = new FmService.OnExitListener() {
    148             @Override
    149             public void onExit() {
    150                 Handler mainHandler = new Handler(Looper.getMainLooper());
    151                 mainHandler.post(new Runnable() {
    152                     @Override
    153                     public void run() {
    154                         FmFavoriteActivity.this.finish();
    155                     }
    156                 });
    157             }
    158         };
    159         FmService.registerExitListener(mExitListener);
    160         if (!mIsServiceBinded) {
    161             bindService();
    162         }
    163     }
    164 
    165     /**
    166      * When menu is selected
    167      *
    168      * @param item The selected menu item
    169      * @return true to consume it, false to can handle other
    170      */
    171     @Override
    172     public boolean onOptionsItemSelected(MenuItem item) {
    173         switch (item.getItemId()) {
    174             case android.R.id.home:
    175                 onBackPressed();
    176                 break;
    177             case R.id.fm_station_list_refresh:
    178                 if (null != mService) {
    179                     refreshMenuItem(false);
    180 
    181                     mMyAdapter.swipResult(null);
    182                     mLvFavorites.setEmptyView(mSearchTips);
    183                     mSearchProgress.setIndeterminate(true);
    184 
    185                     // If current location and last location exceed defined distance, delete the RDS database
    186                     if (isGpsOpen()) {
    187                         mCurLocation = mLocationManager
    188                                 .getLastKnownLocation(LocationManager.GPS_PROVIDER);
    189                         if (mCurLocation != null) {
    190                             double[] lastLocations = FmUtils.getLastSearchedLocation(mContext);
    191                             float distance[] = new float[2];
    192                             Location.distanceBetween(lastLocations[0], lastLocations[1],
    193                                     mCurLocation.getLatitude(), mCurLocation.getLongitude(),
    194                                     distance);
    195                             float searchedDistance = distance[0];
    196                             boolean exceed =
    197                                     searchedDistance > FmUtils.LOCATION_DISTANCE_EXCEED;
    198                             mService.setDistanceExceed(exceed);
    199                             FmUtils.setLastSearchedLocation(mContext, mCurLocation.getLatitude(),
    200                                     mCurLocation.getLongitude());
    201                         }
    202                     }
    203 
    204                     mService.startScanAsync();
    205                 }
    206                 break;
    207             default:
    208                 break;
    209         }
    210         return super.onOptionsItemSelected(item);
    211     }
    212 
    213     @Override
    214     public void onBackPressed() {
    215         if (null == mService) {
    216             Log.w(TAG, "onBackPressed, mService is null");
    217         } else {
    218             boolean isScanning = mService.isScanning();
    219             if (isScanning) {
    220                 mService.stopScan();
    221             }
    222         }
    223         super.onBackPressed();
    224     }
    225 
    226     @Override
    227     public boolean onCreateOptionsMenu(Menu menu) {
    228         MenuInflater inflater = getMenuInflater();
    229         inflater.inflate(R.menu.fm_station_list_menu, menu);
    230         mMenuRefresh = menu.findItem(R.id.fm_station_list_refresh);
    231         resetMenuTitleColor(true);
    232         return true;
    233     }
    234 
    235     private void resetMenuTitleColor(boolean enabled) {
    236         SpannableString ss = new SpannableString(mMenuRefresh.getTitle());
    237         int titleColor = enabled ? getResources().getColor(R.color.actionbar_overflow_title_color)
    238                 : getResources().getColor(R.color.actionbar_overflow_title_disabled_color);
    239         ss.setSpan(new ForegroundColorSpan(titleColor), 0, ss.length(), 0);
    240         mMenuRefresh.setTitle(ss);
    241     }
    242 
    243     @Override
    244     public boolean onPrepareOptionsMenu(Menu menu) {
    245         if (null != mService) {
    246             boolean isScan = mService.isScanning();
    247             refreshMenuItem(!isScan);
    248         }
    249         return super.onPrepareOptionsMenu(menu);
    250     }
    251 
    252     static final class ViewHolder {
    253         ImageView mStationTypeView;
    254         TextView mStationFreqView;
    255         TextView mStationNameView;
    256         TextView mStationRdsView;
    257         RelativeLayout mImgLayout;
    258     }
    259 
    260     private Cursor getData() {
    261         Cursor cursor = mContext.getContentResolver().query(Station.CONTENT_URI,
    262                 FmStation.COLUMNS, null, null, FmStation.Station.FREQUENCY);
    263         return cursor;
    264     }
    265 
    266     class MyFavoriteAdapter extends BaseAdapter {
    267         private Cursor mCursor;
    268 
    269         private LayoutInflater mInflater;
    270 
    271         public MyFavoriteAdapter(Context context) {
    272             mInflater = LayoutInflater.from(context);
    273         }
    274 
    275         public void swipResult(Cursor cursor) {
    276             if (null != mCursor) {
    277                 mCursor.close();
    278             }
    279             mCursor = cursor;
    280             notifyDataSetChanged();
    281         }
    282 
    283         @Override
    284         public int getCount() {
    285             if (null != mCursor) {
    286                 return mCursor.getCount();
    287             }
    288             return 0;
    289         }
    290 
    291         @Override
    292         public Object getItem(int position) {
    293             return null;
    294         }
    295 
    296         @Override
    297         public long getItemId(int position) {
    298             return 0;
    299         }
    300 
    301         @Override
    302         public View getView(int position, View convertView, ViewGroup parent) {
    303             ViewHolder viewHolder = null;
    304             if (null == convertView) {
    305                 viewHolder = new ViewHolder();
    306                 convertView = mInflater.inflate(R.layout.station_item, null);
    307                 viewHolder.mStationTypeView = (ImageView) convertView
    308                         .findViewById(R.id.lv_station_type);
    309                 viewHolder.mStationFreqView = (TextView) convertView
    310                         .findViewById(R.id.lv_station_freq);
    311                 viewHolder.mStationNameView = (TextView) convertView
    312                         .findViewById(R.id.lv_station_name);
    313                 viewHolder.mStationRdsView = (TextView) convertView
    314                         .findViewById(R.id.lv_station_rds);
    315                 viewHolder.mImgLayout = (RelativeLayout) convertView
    316                         .findViewById(R.id.list_item_right);
    317                 convertView.setTag(viewHolder);
    318             } else {
    319                 viewHolder = (ViewHolder) convertView.getTag();
    320             }
    321 
    322             if (mCursor != null && mCursor.moveToFirst()) {
    323                 mCursor.moveToPosition(position);
    324                 final int stationFreq = mCursor.getInt(mCursor
    325                         .getColumnIndex(FmStation.Station.FREQUENCY));
    326                 String name = mCursor.getString(mCursor
    327                         .getColumnIndex(FmStation.Station.STATION_NAME));
    328                 String rds = mCursor.getString(mCursor
    329                         .getColumnIndex(FmStation.Station.RADIO_TEXT));
    330                 final int isFavorite = mCursor.getInt(mCursor
    331                         .getColumnIndex(FmStation.Station.IS_FAVORITE));
    332 
    333                 if (null == name || "".equals(name)) {
    334                     name = mCursor.getString(mCursor
    335                             .getColumnIndex(FmStation.Station.PROGRAM_SERVICE));
    336                 }
    337 
    338                 if (rds == null || rds.equals("")) {
    339                     viewHolder.mStationRdsView.setVisibility(View.GONE);
    340                 } else {
    341                     viewHolder.mStationRdsView.setVisibility(View.VISIBLE);
    342                 }
    343 
    344                 viewHolder.mStationFreqView.setText(FmUtils.formatStation(stationFreq));
    345                 viewHolder.mStationNameView.setText(name);
    346                 viewHolder.mStationRdsView.setText(rds);
    347                 if (0 == isFavorite) {
    348                     viewHolder.mStationTypeView.setImageResource(R.drawable.btn_fm_favorite_off);
    349                     viewHolder.mStationTypeView.setColorFilter(Color.BLACK,
    350                             PorterDuff.Mode.SRC_ATOP);
    351                     viewHolder.mStationTypeView.setAlpha(0.54f);
    352                 } else {
    353                     viewHolder.mStationTypeView.setImageResource(R.drawable.btn_fm_favorite_on);
    354                     viewHolder.mStationTypeView.setColorFilter(null);
    355                     viewHolder.mStationTypeView.setAlpha(1.0f);
    356                 }
    357 
    358                 viewHolder.mImgLayout.setOnClickListener(new View.OnClickListener() {
    359                     @Override
    360                     public void onClick(View v) {
    361                         if (0 == isFavorite) {
    362                             addFavorite(stationFreq);
    363                         } else {
    364                             deleteFavorite(stationFreq);
    365                         }
    366                     }
    367                 });
    368             }
    369             return convertView;
    370         }
    371     }
    372 
    373     /**
    374      * Add searched station as favorite station
    375      */
    376     public void addFavorite(int stationFreq) {
    377         // TODO it's on UI thread, change to sub thread
    378         // update the station name and station type in database
    379         // according the frequency
    380         FmStation.addToFavorite(mContext, stationFreq);
    381         mMyAdapter.swipResult(getData());
    382     }
    383 
    384     /**
    385      * Delete favorite from favorite station list, make it as searched station
    386      */
    387     public void deleteFavorite(int stationFreq) {
    388         // TODO it's on UI thread, change to sub thread
    389         // update the station type from favorite to searched.
    390         FmStation.removeFromFavorite(mContext, stationFreq);
    391         mMyAdapter.swipResult(getData());
    392     }
    393 
    394     @Override
    395     protected void onResume() {
    396         super.onResume();
    397         mIsActivityForeground = true;
    398         if (null != mService) {
    399             mService.setFmMainActivityForeground(mIsActivityForeground);
    400             if (FmRecorder.STATE_RECORDING != mService.getRecorderState()) {
    401                 mService.removeNotification();
    402             }
    403         }
    404     }
    405 
    406     @Override
    407     protected void onPause() {
    408         mIsActivityForeground = false;
    409         if (null != mService) {
    410             mService.setFmMainActivityForeground(mIsActivityForeground);
    411         }
    412         super.onPause();
    413     }
    414 
    415     @Override
    416     protected void onStop() {
    417        // FmUtils.updateFrontActivity(mContext, "");
    418         if (null != mService) {
    419             // home key pressed, show notification
    420             mService.setNotificationClsName(FmFavoriteActivity.class.getName());
    421             mService.updatePlayingNotification();
    422         }
    423         super.onStop();
    424     }
    425 
    426     @Override
    427     protected void onDestroy() {
    428         mMyAdapter.swipResult(null);
    429         FmService.unregisterExitListener(mExitListener);
    430         if (mService != null) {
    431             mService.unregisterFmRadioListener(mFmRadioListener);
    432         }
    433         unbindService();
    434         super.onDestroy();
    435     }
    436 
    437     private FmService mService = null;
    438 
    439     private boolean mIsServiceBinded = false;
    440 
    441     private void bindService() {
    442         mIsServiceBinded = bindService(new Intent(FmFavoriteActivity.this, FmService.class),
    443                 mServiceConnection, Context.BIND_AUTO_CREATE);
    444         if (!mIsServiceBinded) {
    445             Log.e(TAG, "bindService, mIsServiceBinded is false");
    446             finish();
    447         }
    448     }
    449 
    450     private void unbindService() {
    451         if (mIsServiceBinded) {
    452             unbindService(mServiceConnection);
    453         }
    454     }
    455 
    456     // Service listener
    457     private FmListener mFmRadioListener = new FmListener() {
    458         @Override
    459         public void onCallBack(Bundle bundle) {
    460             int flag = bundle.getInt(FmListener.CALLBACK_FLAG);
    461             if (flag == FmListener.MSGID_FM_EXIT) {
    462                 mHandler.removeCallbacksAndMessages(null);
    463             }
    464 
    465             // remove tag message first, avoid too many same messages in queue.
    466             Message msg = mHandler.obtainMessage(flag);
    467             msg.setData(bundle);
    468             mHandler.removeMessages(flag);
    469             mHandler.sendMessage(msg);
    470         }
    471     };
    472 
    473     /**
    474      * Main thread handler to update UI
    475      */
    476     private Handler mHandler = new Handler() {
    477         @Override
    478         public void handleMessage(Message msg) {
    479             Log.d(TAG,
    480                     "handleMessage, what = " + msg.what + ",hashcode:"
    481                             + mHandler.hashCode());
    482             Bundle bundle;
    483             switch (msg.what) {
    484                 case FmListener.MSGID_SCAN_FINISHED:
    485                     bundle = msg.getData();
    486                     // cancel scan happen
    487                     boolean isScan = bundle.getBoolean(FmListener.KEY_IS_SCAN);
    488                     int searchedNum = bundle.getInt(FmListener.KEY_STATION_NUM);
    489                     refreshMenuItem(true);
    490                     mMyAdapter.swipResult(getData());
    491                     mService.updatePlayingNotification();
    492                     if (searchedNum == 0) {
    493                         Toast.makeText(mContext, getString(R.string.toast_cannot_search),
    494                                 Toast.LENGTH_SHORT).show();
    495                         // searched station is zero, if db has station, should not use empty.
    496                         if (mMyAdapter.getCount() == 0) {
    497                             View emptyView = (View) findViewById(R.id.empty_tips);
    498                             emptyView.setVisibility(View.VISIBLE);
    499                             View searchTips = (View) findViewById(R.id.search_tips);
    500                             searchTips.setVisibility(View.GONE);
    501                         }
    502                         return;
    503                     }
    504                     // Show toast to tell user how many stations have been searched
    505                     String text = getString(R.string.toast_station_searched) + " "
    506                             + String.valueOf(searchedNum);
    507                     Toast.makeText(mContext, text, Toast.LENGTH_SHORT).show();
    508                     break;
    509                 case FmListener.MSGID_SWITCH_ANTENNA:
    510                     bundle = msg.getData();
    511                     boolean isHeadset = bundle.getBoolean(FmListener.KEY_IS_SWITCH_ANTENNA);
    512                     // if receive headset plugout, need set headset mode on ui
    513                     if (!isHeadset) {
    514                         finish();
    515                     }
    516                     break;
    517                 default:
    518                     break;
    519             }
    520         }
    521     };
    522 
    523     private void refreshMenuItem(boolean enabled) {
    524         // action menu
    525         if (mMenuRefresh != null) {
    526             mMenuRefresh.setEnabled(enabled);
    527             resetMenuTitleColor(enabled);
    528         }
    529     }
    530 
    531     // When call bind service, it will call service connect. register call back
    532     // listener and initial device
    533     private final ServiceConnection mServiceConnection = new ServiceConnection() {
    534 
    535         /**
    536          * called by system when bind service
    537          *
    538          * @param className component name
    539          * @param service service binder
    540          */
    541         @Override
    542         public void onServiceConnected(ComponentName className, IBinder service) {
    543             mService = ((FmService.ServiceBinder) service).getService();
    544             if (null == mService) {
    545                 Log.e(TAG, "onServiceConnected, mService is null");
    546                 finish();
    547                 return;
    548             }
    549             mService.registerFmRadioListener(mFmRadioListener);
    550             mService.setFmMainActivityForeground(mIsActivityForeground);
    551             if (FmRecorder.STATE_RECORDING != mService.getRecorderState()) {
    552                 mService.removeNotification();
    553             }
    554             // FmUtils.isFirstEnterStationList() must be called at the first time.
    555             // After it is called, it will save status to SharedPreferences.
    556             if (FmUtils.isFirstEnterStationList(mContext) || (0 == mMyAdapter.getCount())) {
    557                 refreshMenuItem(false);
    558                 mLvFavorites.setEmptyView(mSearchTips);
    559                 mSearchProgress.setIndeterminate(true);
    560                 mMyAdapter.swipResult(null);
    561                 mService.startScanAsync();
    562             } else {
    563                 boolean isScan = mService.isScanning();
    564                 if (isScan) {
    565                     mMyAdapter.swipResult(null);
    566                     mLvFavorites.setEmptyView(mSearchTips);
    567                     mSearchProgress.setIndeterminate(true);
    568                 } else {
    569                     // TODO it's on UI thread, change to sub thread
    570                     mMyAdapter.swipResult(getData());
    571                 }
    572                 refreshMenuItem(!isScan);
    573             }
    574         }
    575 
    576         /**
    577          * When unbind service will call this method
    578          *
    579          * @param className The component name
    580          */
    581         @Override
    582         public void onServiceDisconnected(ComponentName className) {
    583         }
    584     };
    585 
    586     /**
    587      * check gps is open or not
    588      *
    589      * @return true is open
    590      */
    591     private boolean isGpsOpen() {
    592         return mLocationManager.isProviderEnabled(android.location.LocationManager.GPS_PROVIDER);
    593     }
    594 }
    595