• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2014 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.fmradio;
18 
19 import android.app.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
onCreate(Bundle savedInstanceState)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
onOptionsItemSelected(MenuItem item)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
onBackPressed()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
onCreateOptionsMenu(Menu menu)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 
resetMenuTitleColor(boolean enabled)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
onPrepareOptionsMenu(Menu menu)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 
getData()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 
MyFavoriteAdapter(Context context)271         public MyFavoriteAdapter(Context context) {
272             mInflater = LayoutInflater.from(context);
273         }
274 
swipResult(Cursor cursor)275         public void swipResult(Cursor cursor) {
276             if (null != mCursor) {
277                 mCursor.close();
278             }
279             mCursor = cursor;
280             notifyDataSetChanged();
281         }
282 
283         @Override
getCount()284         public int getCount() {
285             if (null != mCursor) {
286                 return mCursor.getCount();
287             }
288             return 0;
289         }
290 
291         @Override
getItem(int position)292         public Object getItem(int position) {
293             return null;
294         }
295 
296         @Override
getItemId(int position)297         public long getItemId(int position) {
298             return 0;
299         }
300 
301         @Override
getView(int position, View convertView, ViewGroup parent)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      */
addFavorite(int stationFreq)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      */
deleteFavorite(int stationFreq)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
onResume()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
onPause()407     protected void onPause() {
408         mIsActivityForeground = false;
409         if (null != mService) {
410             mService.setFmMainActivityForeground(mIsActivityForeground);
411         }
412         super.onPause();
413     }
414 
415     @Override
onStop()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
onDestroy()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 
bindService()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 
unbindService()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 
refreshMenuItem(boolean enabled)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      */
isGpsOpen()591     private boolean isGpsOpen() {
592         return mLocationManager.isProviderEnabled(android.location.LocationManager.GPS_PROVIDER);
593     }
594 }
595