package edu.mit.mobile.android.locast.ver2.casts;
/*
* Copyright (C) 2011 MIT Mobile Experience Lab
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import java.util.List;
import android.content.ContentResolver;
import android.content.ContentUris;
import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
import android.location.Criteria;
import android.location.Location;
import android.location.LocationManager;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.support.v4.content.CursorLoader;
import android.support.v4.content.Loader;
import android.support.v4.widget.CursorAdapter;
import android.support.v4_map.app.LoaderManager;
import android.support.v4_map.app.MapFragmentActivity;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;
import com.google.android.maps.GeoPoint;
import com.google.android.maps.MapController;
import com.google.android.maps.MapView;
import com.google.android.maps.MyLocationOverlay;
import com.google.android.maps.Overlay;
import edu.mit.mobile.android.imagecache.ImageCache;
import edu.mit.mobile.android.imagecache.ImageLoaderAdapter;
import edu.mit.mobile.android.locast.Constants;
import edu.mit.mobile.android.locast.casts.CastCursorAdapter;
import edu.mit.mobile.android.locast.data.Cast;
import edu.mit.mobile.android.locast.data.Event;
import edu.mit.mobile.android.locast.data.Favoritable;
import edu.mit.mobile.android.locast.data.Locatable;
import edu.mit.mobile.android.locast.data.MediaProvider;
import edu.mit.mobile.android.locast.maps.CastsOverlay;
import edu.mit.mobile.android.locast.sync.LocastSyncService;
import edu.mit.mobile.android.locast.sync.LocastSyncStatusObserver;
import edu.mit.mobile.android.locast.ver2.R;
import edu.mit.mobile.android.locast.ver2.events.EventCursorAdapter;
import edu.mit.mobile.android.locast.ver2.itineraries.BasicLocatableOverlay;
import edu.mit.mobile.android.locast.ver2.itineraries.LocatableItemOverlay;
import edu.mit.mobile.android.widget.NotificationProgressBar;
import edu.mit.mobile.android.widget.RefreshButton;
public class LocatableListWithMap extends MapFragmentActivity implements LoaderManager.LoaderCallbacks<Cursor>, OnClickListener, OnItemClickListener {
@SuppressWarnings("unused")
private static final String TAG = LocatableListWithMap.class.getSimpleName();
private CursorAdapter mAdapter;
private ListView mListView;
private Uri mContentNearLocation;
private Uri mBaseContent;
private LocatableItemOverlay mLocatableItemsOverlay;
private MapView mMapView;
private MapController mMapController;
private MyMyLocationOverlay mMyLocationOverlay;
private Location mLastLocation;
private LoaderManager mLoaderManager;
private long mLastUpdate;
private ImageCache mImageCache;
// constants related to auto-refreshing
private static long AUTO_UPDATE_FREQUENCY = 15 * 1000 * 1000; // nano-seconds
private static float MIN_UPDATE_DISTANCE = 50; // meters
private int searchRadius = 500; // m
public static final String
ACTION_SEARCH_NEARBY = "edu.mit.mobile.android.locast.ACTION_SEARCH_NEARBY";
private boolean actionSearchNearby = false;
private boolean mExpeditedSync;
private RefreshButton mRefresh;
private Object mSyncHandle;
private NotificationProgressBar mProgressBar;
private final Handler mHandler = new Handler(){
@Override
public void handleMessage(Message msg) {
switch (msg.what){
case LocastSyncStatusObserver.MSG_SET_REFRESHING:
if (Constants.DEBUG){
Log.d(TAG, "refreshing...");
}
mProgressBar.showProgressBar(true);
mRefresh.setRefreshing(true);
break;
case LocastSyncStatusObserver.MSG_SET_NOT_REFRESHING:
if (Constants.DEBUG){
Log.d(TAG, "done loading.");
}
mProgressBar.showProgressBar(false);
mRefresh.setRefreshing(false);
break;
}
};
};
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.map_list_activity);
mProgressBar =(NotificationProgressBar) (findViewById(R.id.progressNotification));
findViewById(R.id.refresh).setOnClickListener(this);
findViewById(R.id.home).setOnClickListener(this);
mMapView = (MapView) findViewById(R.id.map);
mMapController = mMapView.getController();
mListView = (ListView) findViewById(android.R.id.list);
mListView.setOnItemClickListener(this);
mListView.addFooterView(getLayoutInflater().inflate(R.layout.list_footer, null), null, false);
mListView.setEmptyView(findViewById(R.id.progressNotification));
mRefresh = (RefreshButton) findViewById(R.id.refresh);
mRefresh.setOnClickListener(this);
mLoaderManager = getSupportLoaderManager();
final Intent intent = getIntent();
final String action = intent.getAction();
mImageCache = ImageCache.getInstance(this);
actionSearchNearby = ACTION_SEARCH_NEARBY.equals(action);
final boolean actionView = Intent.ACTION_VIEW.equals(action);
if (!actionView && !actionSearchNearby){
Log.e(TAG, "unhandled action " + action);
finish();
return;
}
CharSequence title;
final Uri data = intent.getData();
final String type = intent.resolveType(this);
if (MediaProvider.TYPE_CAST_DIR.equals(type)){
mAdapter = new CastCursorAdapter(this, null);
mListView.setAdapter(new ImageLoaderAdapter(this, mAdapter, mImageCache, new int[]{R.id.media_thumbnail}, 48, 48, ImageLoaderAdapter.UNIT_DIP));
initMapOverlays(new CastsOverlay(this));
title = getString(R.string.title_casts);
searchRadius = 1500;
}else if (MediaProvider.TYPE_EVENT_DIR.equals(type)){
title = getString(R.string.title_upcoming_events);
searchRadius = 10000;
mAdapter = new EventCursorAdapter(this,
R.layout.browse_content_item,
null,
new String[]{Event._TITLE, Event._START_DATE},
new int[]{android.R.id.text1, android.R.id.text2},
new int[]{}, 0);
mListView.setAdapter(mAdapter);
initMapOverlays(new BasicLocatableOverlay(LocatableItemOverlay.boundCenterBottom(getResources().getDrawable(R.drawable.ic_map_event))));
}else{
throw new IllegalArgumentException("Unhandled content type " + type);
}
mBaseContent = data;
setDataUri(data);
if (actionSearchNearby){
title = getString(R.string.title_nearby, title);
}
// if it's showing only favorited items, adjust the title's language accordingly.
final Boolean favorited = Favoritable.decodeFavoritedUri(data);
if (favorited != null){
title = getString(favorited ? R.string.title_favorited : R.string.title_unfavorited, title);
}
setTitle(title);
if (actionSearchNearby) {
updateLocation();
}
setRefreshing(true);
}
@Override
protected void onResume() {
super.onResume();
if (actionSearchNearby) {
mMyLocationOverlay.enableMyLocation();
}
mExpeditedSync = true;
mSyncHandle = ContentResolver.addStatusChangeListener(0xff, new LocastSyncStatusObserver(this, mHandler));
LocastSyncStatusObserver.notifySyncStatusToHandler(this, mHandler);
}
@Override
protected void onPause() {
super.onPause();
if (actionSearchNearby) {
mMyLocationOverlay.disableMyLocation();
}
if (mSyncHandle != null){
ContentResolver.removeStatusChangeListener(mSyncHandle);
}
}
/**
* Gets the last-known location and updates with that.
*/
private void updateLocation(){
final LocationManager lm = (LocationManager) getSystemService(LOCATION_SERVICE);
final String provider = lm.getBestProvider(new Criteria(), true);
if (provider == null){
Toast.makeText(this, getString(R.string.error_no_providers), Toast.LENGTH_LONG).show();
finish();
return;
}
final Location loc = lm.getLastKnownLocation(provider);
if (loc != null){
updateLocation(loc);
}else{
Toast.makeText(this, R.string.notice_finding_your_location, Toast.LENGTH_LONG).show();
setRefreshing(true);
mMapView.setVisibility(View.VISIBLE); // show the map, even without location being found
}
mLastLocation = loc;
}
/**
* Called when the location updates.
*
* @param loc
*/
private void updateLocation(Location loc){
if (loc == null){
throw new NullPointerException();
}
if (actionSearchNearby){
setDataUri(Locatable.toDistanceSearchUri(mBaseContent, loc, searchRadius));
}
mLastLocation = loc;
}
private void setDataUri(Uri data){
final Bundle args = new Bundle();
args.putParcelable(LOADER_ARG_DATA, data);
final String type = getContentResolver().getType(data);
if (MediaProvider.TYPE_EVENT_DIR.equals(type)){
mLoaderManager.restartLoader(LOADER_ID_EVENT, args, this);
}else if (MediaProvider.TYPE_CAST_DIR.equals(type)){
mLoaderManager.restartLoader(LOADER_ID_CAST, args, this);
}
setRefreshing(true);
mContentNearLocation = data;
if (data != null){
refresh(false);
}
}
@Override
public void onWindowFocusChanged(boolean hasFocus) {
super.onWindowFocusChanged(hasFocus);
if (hasFocus){
}
}
private void setRefreshing(boolean isRefreshing){
final RefreshButton refresh = (RefreshButton)findViewById(R.id.refresh);
refresh.setRefreshing(isRefreshing);
}
private void initMapOverlays(LocatableItemOverlay overlay){
mLocatableItemsOverlay = overlay;
final List<Overlay> overlays = mMapView.getOverlays();
mMyLocationOverlay = new MyMyLocationOverlay(this, mMapView);
if (actionSearchNearby) {
overlays.add(mMyLocationOverlay);
}
overlays.add(mLocatableItemsOverlay);
}
@Override
public void setTitle(CharSequence title){
super.setTitle(title);
((TextView)findViewById(android.R.id.title)).setText(title);
}
private void refresh(boolean explicitSync){
if ((System.nanoTime() - mLastUpdate) < AUTO_UPDATE_FREQUENCY && !explicitSync){
// not enough time has elapsed for a non-explicit sync to be allowed
return;
}
mLastUpdate = System.nanoTime();
LocastSyncService.startSync(this, mContentNearLocation, explicitSync);
}
@Override
public void onItemClick(AdapterView<?> adapter, View v, int position, long id) {
startActivity(new Intent(Intent.ACTION_VIEW, ContentUris.withAppendedId(mBaseContent, id)));
}
@Override
public void onClick(View v) {
switch (v.getId()){
case R.id.refresh:
refresh(true);
break;
case R.id.home:
startActivity(getPackageManager().getLaunchIntentForPackage(getPackageName()));
break;
}
}
@Override
protected boolean isRouteDisplayed() {
return false;
}
private class MyMyLocationOverlay extends MyLocationOverlay {
private Location mPrevLocation = null;
public MyMyLocationOverlay(Context context, MapView mapView) {
super(context, mapView);
}
@Override
public synchronized void onLocationChanged(Location location) {
super.onLocationChanged(location);
if (mPrevLocation == null || location.distanceTo(mPrevLocation) > MIN_UPDATE_DISTANCE){
updateLocation(location);
mPrevLocation = location;
}
}
}
private static String LOADER_ARG_DATA = "edu.mit.mobile.android.locast.LOADER_ARG_DATA";
private final static int
LOADER_ID_CAST = 0,
LOADER_ID_EVENT = 1;
@Override
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
switch (id){
case LOADER_ID_CAST:
return new CursorLoader(this, (Uri) args.getParcelable(LOADER_ARG_DATA), Cast.PROJECTION, null, null, Cast.SORT_ORDER_DEFAULT);
case LOADER_ID_EVENT:
// only show events that aren't already over
return new CursorLoader(this, (Uri) args.getParcelable(LOADER_ARG_DATA), Event.PROJECTION,
Event._END_DATE + " >= ?",
new String[]{String.valueOf(System.currentTimeMillis())}, Event.SORT_ORDER_DEFAULT);
default:
return null;
}
}
@Override
public void onLoadFinished(Loader<Cursor> loader, Cursor c) {
mAdapter.swapCursor(c);
mLocatableItemsOverlay.swapCursor(c);
if (c.moveToFirst()){
mMapController.zoomToSpan(mLocatableItemsOverlay.getLatSpanE6(), mLocatableItemsOverlay.getLonSpanE6());
final GeoPoint center = mLocatableItemsOverlay.getCenter();
if (mMapView.getVisibility()==View.INVISIBLE){
mMapController.setCenter(center);
}else{
mMapController.animateTo(center);
}
}else{
mMapController.setZoom(15);
if(mLastLocation != null){
final GeoPoint myPosition = new GeoPoint((int)(mLastLocation.getLatitude() * 1E6), (int)(mLastLocation.getLongitude() * 1E6));
if (mMapView.getVisibility()==View.INVISIBLE){
mMapController.setCenter(myPosition);
}else{
mMapController.animateTo(myPosition);
}
}
}
mMapView.setVisibility(View.VISIBLE);
setRefreshing(false);
if (mExpeditedSync){
mExpeditedSync = false;
if (mListView.getAdapter().isEmpty()){
LocastSyncService.startExpeditedAutomaticSync(this, mContentNearLocation);
}
}
}
@Override
public void onLoaderReset(Loader<Cursor> loader) {
mAdapter.swapCursor(null);
mLocatableItemsOverlay.swapCursor(null);
}
}