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