| /* |
| * Copyright (C) 2014 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.fmradio; |
| |
| import android.app.ActionBar; |
| import android.app.Activity; |
| import android.content.ComponentName; |
| import android.content.Context; |
| import android.content.Intent; |
| import android.content.ServiceConnection; |
| import android.database.Cursor; |
| import android.graphics.Color; |
| import android.graphics.PorterDuff; |
| import android.location.Location; |
| import android.location.LocationManager; |
| import android.media.AudioManager; |
| import android.os.Bundle; |
| import android.os.Handler; |
| import android.os.IBinder; |
| import android.os.Looper; |
| import android.os.Message; |
| import android.text.SpannableString; |
| import android.text.style.ForegroundColorSpan; |
| import android.util.Log; |
| import android.view.LayoutInflater; |
| import android.view.Menu; |
| import android.view.MenuInflater; |
| import android.view.MenuItem; |
| import android.view.View; |
| import android.view.ViewGroup; |
| import android.view.Window; |
| import android.widget.AdapterView; |
| import android.widget.BaseAdapter; |
| import android.widget.ImageView; |
| import android.widget.LinearLayout; |
| import android.widget.ListView; |
| import android.widget.ProgressBar; |
| import android.widget.RelativeLayout; |
| import android.widget.TextView; |
| import android.widget.Toast; |
| |
| import com.android.fmradio.FmService.OnExitListener; |
| import com.android.fmradio.FmStation.Station; |
| |
| /** |
| * This class interact with user, provider edit station information, such as add |
| * to favorite, edit favorite, delete from favorite |
| */ |
| public class FmFavoriteActivity extends Activity { |
| // Logging |
| private static final String TAG = "FmFavoriteActivity"; |
| |
| public static final String ACTIVITY_RESULT = "ACTIVITY_RESULT"; |
| |
| private static final String SHOW_GPS_DIALOG = "SHOW_GPS_DIALOG"; |
| |
| private static final String GPS_NOT_LOCATED_DIALOG = "GPS_NOT_LOCATED_DIALOG"; |
| |
| private ListView mLvFavorites = null; // list view |
| |
| LinearLayout mSearchTips = null; |
| |
| private Context mContext = null; // application context |
| |
| private OnExitListener mExitListener = null; |
| |
| private MyFavoriteAdapter mMyAdapter; |
| |
| private ProgressBar mSearchProgress = null; |
| |
| private MenuItem mMenuRefresh = null; |
| |
| private LocationManager mLocationManager; |
| |
| private Location mCurLocation; |
| |
| private boolean mIsActivityForeground = true; |
| |
| /** |
| * on create |
| * |
| * @param savedInstanceState The save instance state |
| */ |
| @Override |
| public void onCreate(Bundle savedInstanceState) { |
| super.onCreate(savedInstanceState); |
| // Bind the activity to FM audio stream. |
| setVolumeControlStream(AudioManager.STREAM_MUSIC); |
| getWindow().requestFeature(Window.FEATURE_ACTION_BAR); |
| setContentView(R.layout.favorite); |
| // display action bar and navigation button |
| ActionBar actionBar = getActionBar(); |
| actionBar.setTitle(getString(R.string.station_title)); |
| actionBar.setDisplayHomeAsUpEnabled(true); |
| mLocationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE); |
| mContext = getApplicationContext(); |
| |
| mMyAdapter = new MyFavoriteAdapter(mContext); |
| mLvFavorites = (ListView) findViewById(R.id.station_list); |
| mSearchTips = (LinearLayout) findViewById(R.id.search_tips); |
| mSearchProgress = (ProgressBar) findViewById(R.id.search_progress); |
| mLvFavorites.setAdapter(mMyAdapter); // set adapter |
| mMyAdapter.swipResult(getData()); |
| |
| mLvFavorites.setOnItemClickListener(new AdapterView.OnItemClickListener() { |
| /** |
| * Click list item will finish activity and pass value to other activity |
| * |
| * @param parent adapter view |
| * @param view item view |
| * @param position current position |
| * @param id current id |
| */ |
| @Override |
| public void onItemClick(AdapterView<?> parent, View view, int position, long id) { |
| // Set the selected frequency to main UI and finish the |
| // favorite manager. |
| TextView textView = (TextView) view.findViewById(R.id.lv_station_freq); |
| float frequency = 0; |
| try { |
| frequency = Float.parseFloat(textView.getText().toString()); |
| } catch (NumberFormatException e) { |
| e.printStackTrace(); |
| } |
| Intent intentResult = new Intent(); |
| intentResult.putExtra(ACTIVITY_RESULT, FmUtils.computeStation(frequency)); |
| setResult(RESULT_OK, intentResult); |
| finish(); |
| } |
| }); |
| |
| // Finish favorite when exit FM |
| mExitListener = new FmService.OnExitListener() { |
| @Override |
| public void onExit() { |
| Handler mainHandler = new Handler(Looper.getMainLooper()); |
| mainHandler.post(new Runnable() { |
| @Override |
| public void run() { |
| FmFavoriteActivity.this.finish(); |
| } |
| }); |
| } |
| }; |
| FmService.registerExitListener(mExitListener); |
| if (!mIsServiceBinded) { |
| bindService(); |
| } |
| } |
| |
| /** |
| * When menu is selected |
| * |
| * @param item The selected menu item |
| * @return true to consume it, false to can handle other |
| */ |
| @Override |
| public boolean onOptionsItemSelected(MenuItem item) { |
| switch (item.getItemId()) { |
| case android.R.id.home: |
| onBackPressed(); |
| break; |
| case R.id.fm_station_list_refresh: |
| if (null != mService) { |
| refreshMenuItem(false); |
| |
| mMyAdapter.swipResult(null); |
| mLvFavorites.setEmptyView(mSearchTips); |
| mSearchProgress.setIndeterminate(true); |
| |
| // If current location and last location exceed defined distance, delete the RDS database |
| if (isGpsOpen()) { |
| mCurLocation = mLocationManager |
| .getLastKnownLocation(LocationManager.GPS_PROVIDER); |
| if (mCurLocation != null) { |
| double[] lastLocations = FmUtils.getLastSearchedLocation(mContext); |
| float distance[] = new float[2]; |
| Location.distanceBetween(lastLocations[0], lastLocations[1], |
| mCurLocation.getLatitude(), mCurLocation.getLongitude(), |
| distance); |
| float searchedDistance = distance[0]; |
| boolean exceed = |
| searchedDistance > FmUtils.LOCATION_DISTANCE_EXCEED; |
| mService.setDistanceExceed(exceed); |
| FmUtils.setLastSearchedLocation(mContext, mCurLocation.getLatitude(), |
| mCurLocation.getLongitude()); |
| } |
| } |
| |
| mService.startScanAsync(); |
| } |
| break; |
| default: |
| break; |
| } |
| return super.onOptionsItemSelected(item); |
| } |
| |
| @Override |
| public void onBackPressed() { |
| if (null == mService) { |
| Log.w(TAG, "onBackPressed, mService is null"); |
| } else { |
| boolean isScanning = mService.isScanning(); |
| if (isScanning) { |
| mService.stopScan(); |
| } |
| } |
| super.onBackPressed(); |
| } |
| |
| @Override |
| public boolean onCreateOptionsMenu(Menu menu) { |
| MenuInflater inflater = getMenuInflater(); |
| inflater.inflate(R.menu.fm_station_list_menu, menu); |
| mMenuRefresh = menu.findItem(R.id.fm_station_list_refresh); |
| resetMenuTitleColor(true); |
| return true; |
| } |
| |
| private void resetMenuTitleColor(boolean enabled) { |
| SpannableString ss = new SpannableString(mMenuRefresh.getTitle()); |
| int titleColor = enabled ? getResources().getColor(R.color.actionbar_overflow_title_color) |
| : getResources().getColor(R.color.actionbar_overflow_title_disabled_color); |
| ss.setSpan(new ForegroundColorSpan(titleColor), 0, ss.length(), 0); |
| mMenuRefresh.setTitle(ss); |
| } |
| |
| @Override |
| public boolean onPrepareOptionsMenu(Menu menu) { |
| if (null != mService) { |
| boolean isScan = mService.isScanning(); |
| refreshMenuItem(!isScan); |
| } |
| return super.onPrepareOptionsMenu(menu); |
| } |
| |
| static final class ViewHolder { |
| ImageView mStationTypeView; |
| TextView mStationFreqView; |
| TextView mStationNameView; |
| TextView mStationRdsView; |
| RelativeLayout mImgLayout; |
| } |
| |
| private Cursor getData() { |
| Cursor cursor = mContext.getContentResolver().query(Station.CONTENT_URI, |
| FmStation.COLUMNS, null, null, FmStation.Station.FREQUENCY); |
| return cursor; |
| } |
| |
| class MyFavoriteAdapter extends BaseAdapter { |
| private Cursor mCursor; |
| |
| private LayoutInflater mInflater; |
| |
| public MyFavoriteAdapter(Context context) { |
| mInflater = LayoutInflater.from(context); |
| } |
| |
| public void swipResult(Cursor cursor) { |
| if (null != mCursor) { |
| mCursor.close(); |
| } |
| mCursor = cursor; |
| notifyDataSetChanged(); |
| } |
| |
| @Override |
| public int getCount() { |
| if (null != mCursor) { |
| return mCursor.getCount(); |
| } |
| return 0; |
| } |
| |
| @Override |
| public Object getItem(int position) { |
| return null; |
| } |
| |
| @Override |
| public long getItemId(int position) { |
| return 0; |
| } |
| |
| @Override |
| public View getView(int position, View convertView, ViewGroup parent) { |
| ViewHolder viewHolder = null; |
| if (null == convertView) { |
| viewHolder = new ViewHolder(); |
| convertView = mInflater.inflate(R.layout.station_item, null); |
| viewHolder.mStationTypeView = (ImageView) convertView |
| .findViewById(R.id.lv_station_type); |
| viewHolder.mStationFreqView = (TextView) convertView |
| .findViewById(R.id.lv_station_freq); |
| viewHolder.mStationNameView = (TextView) convertView |
| .findViewById(R.id.lv_station_name); |
| viewHolder.mStationRdsView = (TextView) convertView |
| .findViewById(R.id.lv_station_rds); |
| viewHolder.mImgLayout = (RelativeLayout) convertView |
| .findViewById(R.id.list_item_right); |
| convertView.setTag(viewHolder); |
| } else { |
| viewHolder = (ViewHolder) convertView.getTag(); |
| } |
| |
| if (mCursor != null && mCursor.moveToFirst()) { |
| mCursor.moveToPosition(position); |
| final int stationFreq = mCursor.getInt(mCursor |
| .getColumnIndex(FmStation.Station.FREQUENCY)); |
| String name = mCursor.getString(mCursor |
| .getColumnIndex(FmStation.Station.STATION_NAME)); |
| String rds = mCursor.getString(mCursor |
| .getColumnIndex(FmStation.Station.RADIO_TEXT)); |
| final int isFavorite = mCursor.getInt(mCursor |
| .getColumnIndex(FmStation.Station.IS_FAVORITE)); |
| |
| if (null == name || "".equals(name)) { |
| name = mCursor.getString(mCursor |
| .getColumnIndex(FmStation.Station.PROGRAM_SERVICE)); |
| } |
| |
| if (rds == null || rds.equals("")) { |
| viewHolder.mStationRdsView.setVisibility(View.GONE); |
| } else { |
| viewHolder.mStationRdsView.setVisibility(View.VISIBLE); |
| } |
| |
| viewHolder.mStationFreqView.setText(FmUtils.formatStation(stationFreq)); |
| viewHolder.mStationNameView.setText(name); |
| viewHolder.mStationRdsView.setText(rds); |
| if (0 == isFavorite) { |
| viewHolder.mStationTypeView.setImageResource(R.drawable.btn_fm_favorite_off); |
| viewHolder.mStationTypeView.setColorFilter(Color.BLACK, |
| PorterDuff.Mode.SRC_ATOP); |
| viewHolder.mStationTypeView.setAlpha(0.54f); |
| } else { |
| viewHolder.mStationTypeView.setImageResource(R.drawable.btn_fm_favorite_on); |
| viewHolder.mStationTypeView.setColorFilter(null); |
| viewHolder.mStationTypeView.setAlpha(1.0f); |
| } |
| |
| viewHolder.mImgLayout.setOnClickListener(new View.OnClickListener() { |
| @Override |
| public void onClick(View v) { |
| if (0 == isFavorite) { |
| addFavorite(stationFreq); |
| } else { |
| deleteFavorite(stationFreq); |
| } |
| } |
| }); |
| } |
| return convertView; |
| } |
| } |
| |
| /** |
| * Add searched station as favorite station |
| */ |
| public void addFavorite(int stationFreq) { |
| // TODO it's on UI thread, change to sub thread |
| // update the station name and station type in database |
| // according the frequency |
| FmStation.addToFavorite(mContext, stationFreq); |
| mMyAdapter.swipResult(getData()); |
| } |
| |
| /** |
| * Delete favorite from favorite station list, make it as searched station |
| */ |
| public void deleteFavorite(int stationFreq) { |
| // TODO it's on UI thread, change to sub thread |
| // update the station type from favorite to searched. |
| FmStation.removeFromFavorite(mContext, stationFreq); |
| mMyAdapter.swipResult(getData()); |
| } |
| |
| @Override |
| protected void onResume() { |
| super.onResume(); |
| mIsActivityForeground = true; |
| if (null != mService) { |
| mService.setFmMainActivityForeground(mIsActivityForeground); |
| if (FmRecorder.STATE_RECORDING != mService.getRecorderState()) { |
| mService.removeNotification(); |
| } |
| } |
| } |
| |
| @Override |
| protected void onPause() { |
| mIsActivityForeground = false; |
| if (null != mService) { |
| mService.setFmMainActivityForeground(mIsActivityForeground); |
| } |
| super.onPause(); |
| } |
| |
| @Override |
| protected void onStop() { |
| // FmUtils.updateFrontActivity(mContext, ""); |
| if (null != mService) { |
| // home key pressed, show notification |
| mService.setNotificationClsName(FmFavoriteActivity.class.getName()); |
| mService.updatePlayingNotification(); |
| } |
| super.onStop(); |
| } |
| |
| @Override |
| protected void onDestroy() { |
| mMyAdapter.swipResult(null); |
| FmService.unregisterExitListener(mExitListener); |
| if (mService != null) { |
| mService.unregisterFmRadioListener(mFmRadioListener); |
| } |
| unbindService(); |
| super.onDestroy(); |
| } |
| |
| private FmService mService = null; |
| |
| private boolean mIsServiceBinded = false; |
| |
| private void bindService() { |
| mIsServiceBinded = bindService(new Intent(FmFavoriteActivity.this, FmService.class), |
| mServiceConnection, Context.BIND_AUTO_CREATE); |
| if (!mIsServiceBinded) { |
| Log.e(TAG, "bindService, mIsServiceBinded is false"); |
| finish(); |
| } |
| } |
| |
| private void unbindService() { |
| if (mIsServiceBinded) { |
| unbindService(mServiceConnection); |
| } |
| } |
| |
| // Service listener |
| private FmListener mFmRadioListener = new FmListener() { |
| @Override |
| public void onCallBack(Bundle bundle) { |
| int flag = bundle.getInt(FmListener.CALLBACK_FLAG); |
| if (flag == FmListener.MSGID_FM_EXIT) { |
| mHandler.removeCallbacksAndMessages(null); |
| } |
| |
| // remove tag message first, avoid too many same messages in queue. |
| Message msg = mHandler.obtainMessage(flag); |
| msg.setData(bundle); |
| mHandler.removeMessages(flag); |
| mHandler.sendMessage(msg); |
| } |
| }; |
| |
| /** |
| * Main thread handler to update UI |
| */ |
| private Handler mHandler = new Handler() { |
| @Override |
| public void handleMessage(Message msg) { |
| Log.d(TAG, |
| "handleMessage, what = " + msg.what + ",hashcode:" |
| + mHandler.hashCode()); |
| Bundle bundle; |
| switch (msg.what) { |
| case FmListener.MSGID_SCAN_FINISHED: |
| bundle = msg.getData(); |
| // cancel scan happen |
| boolean isScan = bundle.getBoolean(FmListener.KEY_IS_SCAN); |
| int searchedNum = bundle.getInt(FmListener.KEY_STATION_NUM); |
| refreshMenuItem(true); |
| mMyAdapter.swipResult(getData()); |
| mService.updatePlayingNotification(); |
| if (searchedNum == 0) { |
| Toast.makeText(mContext, getString(R.string.toast_cannot_search), |
| Toast.LENGTH_SHORT).show(); |
| // searched station is zero, if db has station, should not use empty. |
| if (mMyAdapter.getCount() == 0) { |
| View emptyView = (View) findViewById(R.id.empty_tips); |
| emptyView.setVisibility(View.VISIBLE); |
| View searchTips = (View) findViewById(R.id.search_tips); |
| searchTips.setVisibility(View.GONE); |
| } |
| return; |
| } |
| // Show toast to tell user how many stations have been searched |
| String text = getString(R.string.toast_station_searched) + " " |
| + String.valueOf(searchedNum); |
| Toast.makeText(mContext, text, Toast.LENGTH_SHORT).show(); |
| break; |
| case FmListener.MSGID_SWITCH_ANTENNA: |
| bundle = msg.getData(); |
| boolean isHeadset = bundle.getBoolean(FmListener.KEY_IS_SWITCH_ANTENNA); |
| // if receive headset plugout, need set headset mode on ui |
| if (!isHeadset) { |
| finish(); |
| } |
| break; |
| default: |
| break; |
| } |
| } |
| }; |
| |
| private void refreshMenuItem(boolean enabled) { |
| // action menu |
| if (mMenuRefresh != null) { |
| mMenuRefresh.setEnabled(enabled); |
| resetMenuTitleColor(enabled); |
| } |
| } |
| |
| // When call bind service, it will call service connect. register call back |
| // listener and initial device |
| private final ServiceConnection mServiceConnection = new ServiceConnection() { |
| |
| /** |
| * called by system when bind service |
| * |
| * @param className component name |
| * @param service service binder |
| */ |
| @Override |
| public void onServiceConnected(ComponentName className, IBinder service) { |
| mService = ((FmService.ServiceBinder) service).getService(); |
| if (null == mService) { |
| Log.e(TAG, "onServiceConnected, mService is null"); |
| finish(); |
| return; |
| } |
| mService.registerFmRadioListener(mFmRadioListener); |
| mService.setFmMainActivityForeground(mIsActivityForeground); |
| if (FmRecorder.STATE_RECORDING != mService.getRecorderState()) { |
| mService.removeNotification(); |
| } |
| // FmUtils.isFirstEnterStationList() must be called at the first time. |
| // After it is called, it will save status to SharedPreferences. |
| if (FmUtils.isFirstEnterStationList(mContext) || (0 == mMyAdapter.getCount())) { |
| refreshMenuItem(false); |
| mLvFavorites.setEmptyView(mSearchTips); |
| mSearchProgress.setIndeterminate(true); |
| mMyAdapter.swipResult(null); |
| mService.startScanAsync(); |
| } else { |
| boolean isScan = mService.isScanning(); |
| if (isScan) { |
| mMyAdapter.swipResult(null); |
| mLvFavorites.setEmptyView(mSearchTips); |
| mSearchProgress.setIndeterminate(true); |
| } else { |
| // TODO it's on UI thread, change to sub thread |
| mMyAdapter.swipResult(getData()); |
| } |
| refreshMenuItem(!isScan); |
| } |
| } |
| |
| /** |
| * When unbind service will call this method |
| * |
| * @param className The component name |
| */ |
| @Override |
| public void onServiceDisconnected(ComponentName className) { |
| } |
| }; |
| |
| /** |
| * check gps is open or not |
| * |
| * @return true is open |
| */ |
| private boolean isGpsOpen() { |
| return mLocationManager.isProviderEnabled(android.location.LocationManager.GPS_PROVIDER); |
| } |
| } |