blob: 6abef980e88ee450823acdb456e9c288a2cc0150 [file] [log] [blame]
Benson Huanga8b6afc2014-11-20 15:42:26 +08001/*
2 * Copyright (C) 2014 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.fmradio;
18
19import android.app.ActionBar;
20import android.app.Activity;
21import android.content.ComponentName;
22import android.content.Context;
23import android.content.Intent;
24import android.content.ServiceConnection;
25import android.database.Cursor;
26import android.graphics.Color;
27import android.graphics.PorterDuff;
28import android.location.Location;
29import android.location.LocationManager;
30import android.media.AudioManager;
31import android.os.Bundle;
32import android.os.Handler;
33import android.os.IBinder;
34import android.os.Looper;
35import android.os.Message;
36import android.text.SpannableString;
37import android.text.style.ForegroundColorSpan;
38import android.util.Log;
39import android.view.LayoutInflater;
40import android.view.Menu;
41import android.view.MenuInflater;
42import android.view.MenuItem;
43import android.view.View;
44import android.view.ViewGroup;
45import android.view.Window;
46import android.widget.AdapterView;
47import android.widget.BaseAdapter;
48import android.widget.ImageView;
49import android.widget.LinearLayout;
50import android.widget.ListView;
51import android.widget.ProgressBar;
52import android.widget.RelativeLayout;
53import android.widget.TextView;
54import android.widget.Toast;
55
56import com.android.fmradio.FmService.OnExitListener;
57import 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 */
63public 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),
Benson Huangd1e9a152014-12-17 18:24:31 +0800494 Toast.LENGTH_SHORT).show();
Benson Huanga8b6afc2014-11-20 15:42:26 +0800495 // 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;
Benson Huang6e47fef2014-12-11 20:37:01 +0800509 case FmListener.MSGID_SWITCH_ANTENNA:
Benson Huanga8b6afc2014-11-20 15:42:26 +0800510 bundle = msg.getData();
Benson Huang6e47fef2014-12-11 20:37:01 +0800511 boolean isHeadset = bundle.getBoolean(FmListener.KEY_IS_SWITCH_ANTENNA);
Benson Huanga8b6afc2014-11-20 15:42:26 +0800512 // 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 }
Benson Huang42601802014-12-15 10:47:15 +0800554 // 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())) {
Benson Huanga8b6afc2014-11-20 15:42:26 +0800557 refreshMenuItem(false);
558 mLvFavorites.setEmptyView(mSearchTips);
559 mSearchProgress.setIndeterminate(true);
Benson Huang8e9f3832015-01-14 10:46:25 +0800560 mMyAdapter.swipResult(null);
Benson Huanga8b6afc2014-11-20 15:42:26 +0800561 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}