1/* 2 * Copyright (C) 2014 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package com.android.fmradio; 18 19import android.app.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), 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