edu.mit.mobile.android.locast.casts.LocatableListWithMap.java Source code

Java tutorial

Introduction

Here is the source code for edu.mit.mobile.android.locast.casts.LocatableListWithMap.java

Source

package edu.mit.mobile.android.locast.casts;

/*
 * Copyright (C) 2011-2012 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 java.util.Set;

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.text.TextUtils;
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.bricolsoftconsulting.mapchange.MyMapView;
import com.google.android.maps.GeoPoint;
import com.google.android.maps.ItemizedOverlay;
import com.google.android.maps.ItemizedOverlay.OnFocusChangeListener;
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 com.google.android.maps.OverlayItem;

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.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.data.TaggableItem;
import edu.mit.mobile.android.locast.itineraries.LocatableItemOverlay;
import edu.mit.mobile.android.locast.maps.CastsOverlay;
import edu.mit.mobile.android.locast.memorytraces.R;
import edu.mit.mobile.android.locast.sync.LocastSync;
import edu.mit.mobile.android.locast.sync.LocastSyncObserver;
import edu.mit.mobile.android.locast.sync.LocastSyncStatusObserver;
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, LocastSyncObserver {

    private static final String TAG = LocatableListWithMap.class.getSimpleName();
    private CursorAdapter mAdapter;
    private ListView mListView;
    private Uri mContentNearLocation;
    private Uri mBaseContent;

    private LocatableItemOverlay mLocatableItemsOverlay;
    private MyMapView mMapView;
    private MapController mMapController;
    private MyMyLocationOverlay mMyLocationOverlay;
    private Location mLastLocation;
    private LoaderManager mLoaderManager;
    private long mLastUpdate;

    private boolean mUserPanned = false;

    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 mSearchRadius = 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 static int MSG_SET_GEOPOINT = 200;

    private final Handler mHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {

            case MSG_SET_GEOPOINT:
                setDataUriNear((GeoPoint) msg.obj, msg.arg1);
                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 = (MyMapView) 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;
        }

        mMapView.setOnChangeListener(new MyMapView.OnChangeListener() {

            @Override
            public void onChange(MapView view, GeoPoint newCenter, GeoPoint oldCenter, int newZoom, int oldZoom) {
                Log.d(TAG, "onChange triggered");
                if (!oldCenter.equals(newCenter)) {
                    mUserPanned = true;
                    Log.d(TAG, "centers are different, requesting a reload");
                    mHandler.obtainMessage(MSG_SET_GEOPOINT, mSearchRadius, 0, newCenter).sendToTarget();
                }
            }
        });

        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, mMapView));

            title = getString(R.string.title_casts);

            mSearchRadius = 1500;

        } 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);
        }

        try {
            final Set<String> tags = TaggableItem.getTagsFromUri(data);

            title = getString(R.string.cast_list_tags, TextUtils.join(", ", tags));
        } catch (final IllegalArgumentException e) {
            // that's fine, we didn't need to set it!
        }

        setTitle(title);
        if (actionSearchNearby) {
            updateLocation();
        }
        setRefreshing(true);
    }

    @Override
    protected void onResume() {
        super.onResume();
        if (actionSearchNearby) {
            mMyLocationOverlay.enableMyLocation();
        }
        mExpeditedSync = true;
        mSyncHandle = LocastSyncStatusObserver.registerSyncListener(this, mBaseContent, this);
    }

    @Override
    protected void onPause() {
        super.onPause();
        if (actionSearchNearby) {
            mMyLocationOverlay.disableMyLocation();
        }
        LocastSyncStatusObserver.unregisterSyncListener(this, 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);
        }
        mLastLocation = loc;
    }

    /**
     * Called when the location updates.
     *
     * @param loc
     */
    private void updateLocation(Location loc) {
        if (loc == null) {
            throw new NullPointerException();
        }

        if (actionSearchNearby) {
            setDataUriNear(loc, mSearchRadius);
        }

        mLastLocation = loc;
    }

    /**
     * Loads the data centered around the given point and radius.
     *
     * @param loc
     */
    private void setDataUriNear(Location loc, int searchRadius) {
        setDataUri(Locatable.toDistanceSearchUri(mBaseContent, loc, searchRadius));
    }

    /**
     * Loads the data centered around the given point and radius.
     *
     * @param loc
     */
    private void setDataUriNear(GeoPoint loc, int searchRadius) {
        setDataUri(Locatable.toDistanceSearchUri(mBaseContent, loc, searchRadius));
    }

    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;
        mLocatableItemsOverlay.setOnFocusChangeListener(new OnFocusChangeListener() {

            @Override
            public void onFocusChanged(@SuppressWarnings("rawtypes") ItemizedOverlay arg0, OverlayItem arg1) {
                final int index = arg0.getLastFocusedIndex();
                mListView.setSelection(index);

            }
        });
        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();
        LocastSync.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 (!mUserPanned
                    && (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);

        // When items are found
        if (c.moveToFirst()) {
            if (!mUserPanned) {
                mMapController.zoomToSpan(mLocatableItemsOverlay.getLatSpanE6(),
                        mLocatableItemsOverlay.getLonSpanE6());
                final GeoPoint center = mLocatableItemsOverlay.getCenter();

                mMapController.animateTo(center);
            }
        } else {

            if (!mUserPanned && mLastLocation != null) {
                mMapController.setZoom(15);
                final GeoPoint myPosition = new GeoPoint((int) (mLastLocation.getLatitude() * 1E6),
                        (int) (mLastLocation.getLongitude() * 1E6));

                mMapController.animateTo(myPosition);
            }
        }

        setRefreshing(false);

        if (mExpeditedSync) {
            mExpeditedSync = false;
            if (mListView.getAdapter().isEmpty()) {
                LocastSync.startExpeditedAutomaticSync(this, mContentNearLocation);
            }
        }
    }

    @Override
    public void onLoaderReset(Loader<Cursor> loader) {
        mAdapter.swapCursor(null);
        mLocatableItemsOverlay.swapCursor(null);

    }

    @Override
    public void onLocastSyncStarted(Uri uri) {
        if (Constants.DEBUG) {
            Log.d(TAG, "refreshing...");
        }
        mProgressBar.showProgressBar(true);
        mRefresh.setRefreshing(true);
    }

    @Override
    public void onLocastSyncStopped(Uri uri) {
        if (Constants.DEBUG) {
            Log.d(TAG, "done loading.");
        }
        mProgressBar.showProgressBar(false);
        mRefresh.setRefreshing(false);

    }
}