package edu.washington.geopost;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Set;
import android.content.Context;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.Color;
import android.location.Location;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.os.AsyncTask;
import android.os.Bundle;
import android.support.v4.app.DialogFragment;
import android.support.v4.app.FragmentActivity;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemSelectedListener;
import android.widget.ArrayAdapter;
import android.widget.ProgressBar;
import android.widget.Spinner;
import android.widget.Toast;
import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.GooglePlayServicesClient.ConnectionCallbacks;
import com.google.android.gms.common.GooglePlayServicesClient.OnConnectionFailedListener;
import com.google.android.gms.common.GooglePlayServicesUtil;
import com.google.android.gms.location.LocationClient;
import com.google.android.gms.location.LocationRequest;
import com.google.android.gms.maps.CameraUpdateFactory;
import com.google.android.gms.maps.GoogleMap;
import com.google.android.gms.maps.GoogleMap.OnCameraChangeListener;
import com.google.android.gms.maps.GoogleMap.OnMarkerClickListener;
import com.google.android.gms.maps.MapFragment;
import com.google.android.gms.maps.model.BitmapDescriptorFactory;
import com.google.android.gms.maps.model.CameraPosition;
import com.google.android.gms.maps.model.Circle;
import com.google.android.gms.maps.model.CircleOptions;
import com.google.android.gms.maps.model.LatLng;
import com.google.android.gms.maps.model.Marker;
import com.google.android.gms.maps.model.MarkerOptions;
import com.google.android.gms.maps.model.VisibleRegion;
/**
* This is the activity of GeoPost that displays the map of pins to the user. It
* also displays controls to them for posting and filtering pins as well as
* viewing their profile
*
* @author Matt, Mike, Ethan
*/
public class MainActivity extends FragmentActivity implements
OnMarkerClickListener,
PostFragment.PostDialogListener,
EnableLocationFragment.EnableLocationDialogListener,
OnItemSelectedListener, OnCameraChangeListener, ConnectionCallbacks,
OnConnectionFailedListener,
com.google.android.gms.location.LocationListener {
// Zoom level upon opening app, 16 is on the order of UW campus size
// No units are specified, range of zoom is 2.0 to 21.0 (bigger => closer)
public static final float INIT_ZOOM = 16;
// Tag for the app
public static final String TAG = "GeoPost";
// Thickness of the unlocking circle border line
public static final float BORDER_THICKNESS = 4;
// Scale of the unlocking circle in lat/long coord difference
public static final double RANGE_RADIUS = 0.0015;
// The meters between two lat/lng lines
public static final double COORD_IN_METERS = 111319.9;
// The radius of the earth in meters
public static final int EARTH_RADIUS = 6366000;
// The number of milliseconds in a second
public static final int SEC_TO_MILLIS = 1000;
// The update interval for location in seconds
public static final int UPDATE_INTERVAL = 5;
// The fastest possible update interval in seconds
public static final int FASTEST_UPDATE = 1;
// Location related fields
// The main map that is shown to the user
private GoogleMap map;
// Check to see if marker window is open
private boolean markerWindowShown;
// The background thread that handles getting pins from database
private RefreshMapTask refreshThread;
// The circle drawn on the map
private Circle unlockedRadius;
// The location client that handles location
private LocationClient locationClient;
// The location request which has parameters about location updates
private LocationRequest locationRequest;
// Database interfaces
private DBQuery dbq;
private DBStore dbs;
// Sorting option
private enum SortingOption {
ALL, FB_FRIENDS, LOCKED,
VIEWED, MY_POSTS
}
SortingOption sortingOption;
// Current user and their facebook friends
private User currentUser;
private Set<String> friends;
// A map of all pins currently drawn in the app
private HashMap<Marker, Pin> geoposts;
// Window for pin being displayed
private ViewPinWindow vpw;
private ProgressBar loadingWheel;
/**
* Called upon opening of the activity. Initializes all of the UI
* components, location, database interfaces, and makes initial call to zoom
* in on location and get pins to put on map.
*
* @param Bundle
* The saved instance state of the app
*/
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
final int result = GooglePlayServicesUtil
.isGooglePlayServicesAvailable(this);
if (result != ConnectionResult.SUCCESS) {
Log.d("DEBUG", "MainActivity - Google Play messed up, result = "
+ result);
Toast toast = Toast.makeText(this,
"Google Play service is not available", Toast.LENGTH_LONG);
toast.show();
finish();
}
locationClient = new LocationClient(this, this, this);
locationClient.connect();
setUpMapIfNeeded();
// Set filters to show all posts
sortingOption = SortingOption.ALL;
// Setup collection of markers on map to actual pins
geoposts = new HashMap<Marker, Pin>();
// Initialize the database interfaces
dbq = new DBQuery();
dbs = new DBStore();
Log.d("LoadWheel", "Before creating");
loadingWheel = (ProgressBar)findViewById(R.id.progressBar1);
Log.d("LoadWheel", "findViewById done");
loadingWheel.setVisibility(View.GONE);
Log.d("LoadWheel", "Initially gone");
Log.d("DEBUG", "MainActivity - onCreate, just before network stuff");
if (isNetworkAvailable()) {
new GetUserTask().execute();
Log.d("DEBUG", "MainActivity - onCreate, grabbed current user");
new GetFriendsTask().execute();
Log.d("DEBUG", "MainActivity - onCreate, grabbed friends");
} else {
Toast toast = Toast.makeText(getApplicationContext(),
"Network unavailable", Toast.LENGTH_LONG);
toast.show();
}
Log.d("DEBUG",
"MainActivity - onCreate, successful network setup/handling");
// Set the pin pop up windows to use the ViewPinWindow class
vpw = new ViewPinWindow(this);
if (vpw == null)
Log.d("LC", "VPW IS NULL");
if (map == null)
Log.d("LC", "MAP IS NULL");
map.setInfoWindowAdapter(vpw);
markerWindowShown = false;
Log.d("DEBUG", "MainActivity - before map stuff");
map.setMyLocationEnabled(true);
map.setOnMarkerClickListener(this);
map.setOnCameraChangeListener(this);
map.getUiSettings().setRotateGesturesEnabled(false);
Log.d("DEBUG", "MainActivity - after map stuff");
// Create the Async Task that will be used to refresh
// pins on the screen
refreshThread = new RefreshMapTask();
Log.d("DEBUG", "MainActivity - end of onCreate");
// Check for first time users and start help fragment
Bundle extras = getIntent().getExtras();
if (extras != null) {
boolean isNewUser = extras.getBoolean("NewUser");
if (isNewUser) {
openHelpFragment();
}
}
}
/**
*
* @author Matt
* For this given user, starts a background task that queries
* the database for a set of the user's Facebook friends which
* we store for using later
*/
private class GetFriendsTask extends AsyncTask<Void, Void, Set<String>> {
@Override
protected Set<String> doInBackground(Void... params) {
Log.d("DEBUG", "Friends background start");
Set<String> results = dbq.getFriends();
Log.d("DEBUG", "found Friends");
return results;
}
protected void onPostExecute(Set<String> results) {
Log.d("DEBUG", "Friends background end");
friends = results;
}
}
/**
*
* @author Matt
* Starts a background task that will query the database for
* information on the current user of this app
*/
private class GetUserTask extends AsyncTask<Void, Void, User> {
@Override
protected User doInBackground(Void... params) {
Log.d("DEBUG", "User background start");
User user = dbq.getCurrentUser();
Log.d("DEBUG", "got user");
return user;
}
protected void onPostExecute(User user) {
Log.d("DEBUG", "User background end");
currentUser = user;
}
}
/**
* Called on Activity stop, stop location updates and disconnect location
* client.
*/
@Override
protected void onStop() {
Log.d("OnStop", "Stop Periodic Updates");
// If the client is connected
if (locationClient.isConnected()) {
stopPeriodicUpdates();
}
/*
* After disconnect() is called, the client is considered "dead".
*/
locationClient.disconnect();
super.onStop();
}
/**
* Stops the location client from updating location
*/
private void stopPeriodicUpdates() {
locationClient.removeLocationUpdates(this);
}
/**
* Starts the location client to continually update location
*/
private void startPeriodicUpdates() {
locationClient.requestLocationUpdates(locationRequest, this);
}
/**
* Sends the app to the top of the background app stack.
* User is brought back to main screen of phone.
*/
@Override
public void onBackPressed() {
moveTaskToBack(true);
}
/**
* Request updates for location and check map setup
*/
@Override
protected void onResume() {
super.onResume();
locationClient.connect();
setUpMapIfNeeded();
}
/**
* Remove the locationlistener updates when Activity is paused
*/
@Override
protected void onPause() {
super.onPause();
locationClient.disconnect();
}
/**
* Called when the location client is connected. It sets up the location
* request settings, zooms into the current location, and draws the
* unlocking radius around the user's current location.
*
* @param Bundle of data provided to clients by Google Play services.
* May be null if no content is provided by the service.
*/
@Override
public void onConnected(Bundle arg0) {
Log.d("DEBUG", "onConnected begin");
locationRequest = LocationRequest.create();
locationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);
// Set the update interval to 5 seconds
locationRequest.setInterval(UPDATE_INTERVAL * SEC_TO_MILLIS);
// Set the fastest update interval to 1 second
locationRequest.setFastestInterval(FASTEST_UPDATE * SEC_TO_MILLIS);
// Make the app open up to your current location
Location currentLocation = locationClient.getLastLocation();
if (currentLocation != null) {
LatLng myLatLng = new LatLng(currentLocation.getLatitude(),
currentLocation.getLongitude());
map.animateCamera(CameraUpdateFactory.newLatLngZoom(myLatLng,
INIT_ZOOM));
if (unlockedRadius != null) {
unlockedRadius.remove();
}
drawCircle(new LatLng(currentLocation.getLatitude(),
currentLocation.getLongitude()));
} else {
DialogFragment newFragment = new EnableLocationFragment();
newFragment.show(getSupportFragmentManager(), "enableLocation");
}
Log.d("DEBUG", "onConnected end");
startPeriodicUpdates();
}
/**
* Called when the user clicks to enable the GPS on their phone, opens up
* settings page on phone where they can make this change
*
* @param dialog The DialogFragment that the button was clicked from
*/
public void onEnableLocationPositiveClick(DialogFragment dialog) {
Intent gpsOptionsIntent = new Intent(
android.provider.Settings.ACTION_LOCATION_SOURCE_SETTINGS);
startActivity(gpsOptionsIntent);
}
/**
* Called whenever the connection to LocationClient fails
* @param arg0 Contains information about what kind of connection error occurred
*/
@Override
public void onConnectionFailed(ConnectionResult arg0) {
Log.d("DEBUG", "onConnectedFailed");
Toast.makeText(this, "Connection Failed", Toast.LENGTH_LONG).show();
}
/**
* Called whenever the connection to LocationClient is disconnected
*/
@Override
public void onDisconnected() {
Log.d("DEBUG", "onDisconnected");
Toast.makeText(this, "Disconnected", Toast.LENGTH_LONG).show();
}
/**
* Add a pin to the map, determines visibility and color details for
* specific pin and adds it to main collection as well as draws it on the
* map.
*
* @param pin the pin to be added
*/
private void addPin(Pin pin) {
if (friends == null) {
friends = dbq.getFriends();
}
float color = BitmapDescriptorFactory.HUE_RED;
Log.d("addPin", pin.getUser());
if (currentUser == null) {
if (!isNetworkAvailable()) {
Toast toast = Toast.makeText(getApplicationContext(),
"Network unavailable", Toast.LENGTH_LONG);
toast.show();
return;
}
currentUser = dbq.getCurrentUser();
}
if (wasPostedByUser(pin)) {
// pin is user's posted pin
color = BitmapDescriptorFactory.HUE_VIOLET;
} else if (!pin.isLocked()) {
// pin is unlocked
color = (float) 220.0; // blue
}
Marker m = map.addMarker(new MarkerOptions().title(pin.getMessage())
.snippet(pin.getUser()).position(pin.getLocation())
.icon(BitmapDescriptorFactory.defaultMarker(color))
.visible(getVisible(pin)));
geoposts.put(m, pin);
}
/**
* Initialize the map if it is not already, ensures map is avaliable to
* activity
*/
private void setUpMapIfNeeded() {
// Do a null check to confirm that we have not already instantiated the
// map.
if (map == null) {
map = ((MapFragment) getFragmentManager()
.findFragmentById(R.id.map)).getMap();
}
}
/************ Options Menu ***************/
/**
* Creates the options menu on start up of the activity. Currently, always
* returns true.
*
* @param menu
* , menu to place items in
* @return true to have menu displayed
*/
@Override
public boolean onCreateOptionsMenu(Menu menu) {
Log.d("DEBUG", "onCreateOptionsMenu");
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.main, menu);
// Create a drop down menu in the menu bar
Spinner spinner = (Spinner) menu.findItem(R.id.sort_options)
.getActionView();
// Create an ArrayAdapter using the string array and a default spinner
// layout
ArrayAdapter<CharSequence> adapter = ArrayAdapter.createFromResource(
this, R.array.sorting_array, R.layout.spinner);
// Specify the layout to use when the list of choices appears
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
// Apply the adapter to the spinner
spinner.setAdapter(adapter);
// set listener on spinner
spinner.setOnItemSelectedListener(this);
return true;
}
/**
* Handle menu item selections
*
* @param item the clicked menu item
* @return true if event was handled, false to have default
* onOptionsItemSelected happen.
*/
@Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle item selection
switch (item.getItemId()) {
case R.id.action_help:
openHelpFragment();
return true;
case R.id.action_profile:
openProfileActivity();
return true;
default:
return super.onOptionsItemSelected(item);
}
}
/**
* Called when the user selects an item from the sorting spinner
* @param parent The AdapterView where the selection happened
* @param view The view within the AdapterView that was clicked
* @param pos The position of the view in the adapter
* @param id The row id of the item that is selected
*/
public void onItemSelected(AdapterView<?> parent, View view, int pos,
long id) {
String option = (String) parent.getItemAtPosition(pos);
if (option.equals("All Posts")) {
sortingOption = SortingOption.ALL;
} else if (option.equals("Viewed")) {
sortingOption = SortingOption.VIEWED;
} else if (option.equals("Locked")) {
sortingOption = SortingOption.LOCKED;
} else if (option.equals("My Posts")) {
sortingOption = SortingOption.MY_POSTS;
} else if (option.equals("Friends")) {
sortingOption = SortingOption.FB_FRIENDS;
}
// refresh the current pins
refreshLocalPins();
}
/**
* Loop through all pins stored locally and set the correct ones to be
* visible
*/
private void refreshLocalPins() {
for (Marker m : geoposts.keySet()) {
Pin pin = geoposts.get(m);
m.setVisible(getVisible(pin));
}
}
/**
* Return whether the given pin should be displayed given
* the current filter option.
*
* @param p the pin to be displayed or not
* @return true if the pin should be visible, false otherwise
*/
private boolean getVisible(Pin p) {
switch (sortingOption) {
case LOCKED:
return p.isLocked();
case VIEWED:
return !p.isLocked() &&
!wasPostedByUser(p);
case MY_POSTS:
return wasPostedByUser(p);
case FB_FRIENDS:
return friends.contains(p.getFacebookID());
default:
return true;
}
}
/**
* Return whether the given pin was posted by the current user
*
* @param p the pin to check
* @return true if the current user posted p, false otherwise
*/
private boolean wasPostedByUser(Pin p) {
return p.getFacebookID() != null
&& p.getFacebookID().equals(currentUser.getFacebookID());
}
/**
* Called from the sorting spinner when nothing is selected
*/
public void onNothingSelected(AdapterView<?> parent) {
// Required for interface, but not needed
}
/**
* Open the help fragment
*/
private void openHelpFragment() {
DialogFragment newFragment = new HelpFragment();
newFragment.show(getSupportFragmentManager(), "help");
}
/**
* Open the profile activity Pass it the user's name, number of posts, and
* number of viewed posts
*/
private void openProfileActivity() {
Intent intent = new Intent(this, ProfileActivity.class);
// get the user again to update posts and views
currentUser = dbq.getCurrentUser();
assert (currentUser != null);
intent.putExtra("edu.washington.geopost.USERNAME",
currentUser.getName());
intent.putExtra("edu.washington.geopost.FACEBOOKID",
currentUser.getFacebookID());
intent.putExtra("edu.washington.geopost.NUM_POSTED",
currentUser.getNumPosted());
intent.putExtra("edu.washington.geopost.NUM_VIEWED",
currentUser.getNumViewed());
startActivity(intent);
}
/************ View pin logic ***************/
/**
* On clicking a marker, show the marker window if there is not already one
* shown. Otherwise, hide the marker window.
*
* @param marker the clicked marker (or pin)
* @return true if event was handled, returning false causes default
* onMarkerClick to run, which will incorrectly display the window
* here.
*/
@Override
public boolean onMarkerClick(Marker marker) {
Log.d("onMarkerClick", "marker clicked");
assert (marker != null);
Pin pin = geoposts.get(marker);
if (pin == null) {
Log.d("onMarkerClick", "clicked on marker not found in map");
return true;
}
if (markerWindowShown) { // window is showing, hide it
hidePinWindow(marker);
} else { // window not showing, see if we should show it
if (isInRange(marker) && pin.isLocked()) {
Log.d("onMarkerClick", "attempting to unlock in-range pin");
if (!isNetworkAvailable()) {
Toast toast = Toast.makeText(getApplicationContext(),
"Network unavailable", Toast.LENGTH_LONG);
toast.show();
return true;
}
Pin p = dbs.unlockPin(pin);
if (p != null) { // unlocked new pin
geoposts.put(marker, p);
showPinWindow(p, marker);
} else { // unlocking failed
hidePinWindow(marker);
Log.d("onMarkerClick", "Failed to unlock pin");
}
} else if (!pin.isLocked()) { // pin already unlocked
showPinWindow(pin, marker);
Log.d("onMarkerClick", "viewed previously unlocked pin");
} else { // pin is locked
Log.d("onMarkerClick", "clicked on locked/out of range pin");
hidePinWindow(marker);
Toast toast = Toast.makeText(getApplicationContext(), "Locked",
Toast.LENGTH_SHORT);
toast.show();
}
}
return true;
}
/**
* Brings up the pin window associated with the marker to show
* the pin's message and photo
*
* @param pin The pin associated with given marker that contains
* message, photo, and name
* @param marker The marker the user clicked on
*/
private void showPinWindow(Pin pin, Marker marker) {
Bitmap photo = pin.getPhoto();
if (photo != null) {
vpw.setPhoto(photo);
}
marker.showInfoWindow();
markerWindowShown = true;
}
/**
* Closes the pin window associated with the marker
*
* @param marker The marker the user clicked on
*/
private void hidePinWindow(Marker marker) {
vpw.closePhoto();
marker.hideInfoWindow();
markerWindowShown = false;
}
/**
* Returns whether the marker is in range of the user's GPS position. The
* user must be RANGE_RADIUS coordinates or less away from the marker to be
* in range. If the user's location cannot be found, displays a message
* saying so.
*
* @param marker the marker to verify
* @return true if the marker is in range, false otherwise
*/
private boolean isInRange(Marker marker) {
Location loc = locationClient.getLastLocation();
if (loc == null) {
Toast toast = Toast.makeText(getApplicationContext(),
"Could not find your location", Toast.LENGTH_SHORT);
toast.show();
return false;
}
// Get the user's lat/lng coordinates
double userLat = loc.getLatitude();
double userLng = loc.getLongitude();
// Get the pin's lat/lng coordinates
Pin p = geoposts.get(marker);
double pinLat = p.getLocation().latitude;
double pinLng = p.getLocation().longitude;
// Return if the distance between points is within unlocked radius
double distance = distance(userLat, userLng, pinLat, pinLng);
return distance <= coordToMeters(RANGE_RADIUS);
}
/**
* Gets distance between two latlng points
*
* @param startLat The latitude of the initial point
* @param startLng The longitude of the initial point
* @param endLat The latitude of the end point
* @param endLng The latitude of the end point
* @return Uses the haversine formula to calculate and return the distance
* between two lat/lng points on the earth in meters
*/
private double distance(double startLat, double startLng, double endLat,
double endLng) {
double dLat = Math.toRadians(endLat - startLat);
double dLon = Math.toRadians(endLng - startLng);
double a = Math.sin(dLat / 2) * Math.sin(dLat / 2)
+ Math.cos(Math.toRadians(startLat))
* Math.cos(Math.toRadians(endLat)) * Math.sin(dLon / 2)
* Math.sin(dLon / 2);
double c = 2 * Math.asin(Math.sqrt(a));
return EARTH_RADIUS * c;
}
/**************** Post pin logic ****************/
/**
* Method called when the post button is clicked Creates and displays a new
* PostFragment, passing it the current latitude and longitude of the user's
* location. It the current location cannot be found, displays a message
* saying so.
*
* @param view
* the clicked post button
*/
public void onPostButtonClick(View view) {
Log.d("LC", "Post Clicked");
Location loc = locationClient.getLastLocation();
if (loc == null) {
Toast toast = Toast.makeText(getApplicationContext(),
"Unable to find your location", Toast.LENGTH_SHORT);
toast.show();
} else if (!isNetworkAvailable()) {
Toast toast = Toast.makeText(getApplicationContext(),
"Network unavailable", Toast.LENGTH_SHORT);
toast.show();
} else {
DialogFragment newFragment = new PostFragment();
newFragment.show(getSupportFragmentManager(), "post");
}
}
@Override
/**
* This has to be overridden so PostFragment's camera intent is
* sent to the correct activity
* @param requestCode The integer request code originally supplied to
* startActivityForResult(), allowing you to identify
* who this result came from.
* @param resultCode The integer result code returned by the
* child activity through its setResult()
* @param data An Intent, which can return result data to the caller
*/
public void onActivityResult(int requestCode, int resultCode, Intent data) {
Log.d("DEBUG", "onActivityResult");
Log.d("CAM", "Inside main onActivityResult");
super.onActivityResult(requestCode, resultCode, data);
}
/**
* The dialog fragment receives a reference to this Activity through the
* Fragment.onAttach() callback, which it uses to call the following methods
* defined by the PostFragment.PostDialogListener interface This method is
* called on a click of the "Post" button from a PostFragment Adds a pin to
* the map at the coordinates given with the given message
*
* @param dialog
* a reference to the fragment this is listening on
* @param coord
* the coordinates to create a pin at
* @param message
* the message for the new pin
*/
@Override
public void onDialogPositiveClick(DialogFragment dialog, String message,
Bitmap photo) {
Log.d("PHOTO", "onDialogPositiveClick start");
Location loc = locationClient.getLastLocation();
// check for no location
if (loc == null) {
Toast toast = Toast.makeText(getApplicationContext(),
"Unable to post: cannot find your location",
Toast.LENGTH_SHORT);
toast.show();
return;
}
if (!isNetworkAvailable()) {
Toast toast = Toast.makeText(getApplicationContext(),
"Unable to post: Network unavailable", Toast.LENGTH_LONG);
toast.show();
return;
}
// check for empty message
if (message.length() == 0) {
Toast toast = Toast.makeText(getApplicationContext(),
"Sorry, cannot post an empty message", Toast.LENGTH_SHORT);
toast.show();
return;
}
LatLng coord = new LatLng(loc.getLatitude(), loc.getLongitude());
Pin pin = dbs.postPin(coord, message, photo);
if (pin == null) {
Toast toast = Toast.makeText(getApplicationContext(),
"Posting failed. Please try again.",
Toast.LENGTH_LONG);
toast.show();
return;
}
addPin(pin);
}
/**************** location listener ****************/
/**
* Redraws the user's unlocking radius to center around the new location
*
* @param Location
* The new location the user has moved to
*/
@Override
public void onLocationChanged(Location location) {
// Remove the old radius
if (unlockedRadius != null) {
unlockedRadius.remove();
}
// Draw the new radius
if (location != null) {
drawCircle(new LatLng(location.getLatitude(),
location.getLongitude()));
} else {
Toast toast = Toast.makeText(getApplicationContext(),
"Unable to find your location", Toast.LENGTH_SHORT);
toast.show();
}
}
/**
* Draw the unlock range circle on the map
*
* @param center The coordinate center where the user is located on the map
* which serves as the epicenter of the circle to draw
*/
public void drawCircle(LatLng center) {
CircleOptions circleOptions = new CircleOptions();
circleOptions.center(center);
circleOptions.radius(coordToMeters(RANGE_RADIUS));
circleOptions.strokeColor(Color.RED);
circleOptions.strokeWidth(BORDER_THICKNESS);
// Add the circle to the map
unlockedRadius = map.addCircle(circleOptions);
}
/**
* Changes the given difference in coordinates to a difference
* in meters
*
* @param difference The lat/lng difference in distance between two points
* @return The same distance in meters
*/
private double coordToMeters(double difference) {
return difference * COORD_IN_METERS;
}
/**
* Check if we have network
*
* @return True if the phone is connected to any network, false otherwise
*/
private boolean isNetworkAvailable() {
ConnectivityManager connectivityManager = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo activeNetworkInfo = connectivityManager
.getActiveNetworkInfo();
return activeNetworkInfo != null && activeNetworkInfo.isConnected();
}
/**************** Map refresh logic ****************/
/**
* Activated when camera is changed, panning or zooming. This method will
* trigger a call to updateMap() to redraw the relevant pins
*
* @param CameraPosition The position of the user's camera
*/
@Override
public void onCameraChange(CameraPosition cp) {
Log.d("Event", "onCameraChange fired");
refreshThread.cancel(true); // If another query to onCameraChange is
// still
// running, stop this so that this new
// change is seen
VisibleRegion vr = map.getProjection().getVisibleRegion();
if (vr != null) {
LatLng sw = vr.latLngBounds.southwest;
LatLng ne = vr.latLngBounds.northeast;
Log.d("updateMap", " sw,lat " + sw.latitude + " sw,lng "
+ sw.longitude + " ne,lat " + ne.latitude + " ne,lng "
+ ne.longitude);
// Create background task that will query the database
// and upon return, draw the updated pin/markers on the map
refreshThread = new RefreshMapTask();
loadingWheel.setVisibility(View.VISIBLE);
refreshThread.execute(sw, ne);
}
}
/**
*
* @author Matt Asynchronous task used to refresh the pins on the map. The
* querying to the database is done in the background and draws the
* results once it gets resulting pins Extends from AsyncTask which
* is an asynchronous task handler
*/
private class RefreshMapTask extends AsyncTask<Object, Void, Set<Pin>> {
/**
* Queries the database for pins in view as background task
*
* @param LatLng sw The southwest corner of the user's view
* @param LatLng ne The northeast corner of the user's view
* @return Set<Pin> The resulting pins from the database that are within
* the bounding box from the two points
*/
@Override
protected Set<Pin> doInBackground(Object... params) {
Log.d("Background!", "Background start!");
assert (params.length >= 2);
LatLng sw = (LatLng) params[0];
LatLng ne = (LatLng) params[1];
Set<Pin> p = dbq.getPins(sw, ne);
if (p == null) {
Log.d("doInBackground", "null query");
}
return p;
}
/**
* This is called after doInBackground returns, with its return value
* Draws the pins onto the map
*
* @param Set <Pin> The pins from the background task that need to be
* drawn onto the map
*/
protected void onPostExecute(Set<Pin> pins) {
Log.d("onPostExecute", "executing, pin drawing");
drawMarkers(pins);
Log.d("LoadWheel", "onPostExecute setVisible before");
loadingWheel.setVisibility(View.GONE);
Log.d("LoadWheel", "onPostExecute setVisible before");
}
}
/**
* Takes a set of Pin objects and ensures that they are displayed on the
* map, removes any pins that are currently displayed if they are not also
* in the supplied set.
*
* @param pins
* , set of pins to draw onto the map, passing null causes map to
* be cleared
*/
public void drawMarkers(Set<Pin> pins) {
assert (geoposts != null);
if (pins == null) {
geoposts.clear();
map.clear();
Toast toast = Toast.makeText(getApplicationContext(),
"Unable to load posts", Toast.LENGTH_SHORT);
toast.show();
return;
}
/*
* First remove old pins that aren't in view now
*/
HashSet<Marker> currentMarkers = new HashSet<Marker>(geoposts.keySet());
for (Marker m : currentMarkers) {
Pin p = geoposts.get(m);
if (!pins.contains(p)) {
// m is no longer in our scope
m.remove();
geoposts.remove(m);
}
}
/*
* Now add new pins that weren't drawn before
*/
HashSet<Pin> currentPins = new HashSet<Pin>(geoposts.values());
for (Pin p : pins) {
if (!currentPins.contains(p)) {
// this will add p to geoposts
Log.d("drawMarkers", "added pin to map");
addPin(p);
}
}
Log.d("drawMarkers", "drew markers");
}
/* For test */
/**
* @return the value of includeViewed
*/
public boolean isIncludeViewed() {
return sortingOption == SortingOption.ALL ||
sortingOption == SortingOption.VIEWED ||
sortingOption == SortingOption.FB_FRIENDS;
}
/**
* @return the value of includeLocked
*/
public boolean isIncludeLocked() {
return sortingOption == SortingOption.ALL ||
sortingOption == SortingOption.LOCKED ||
sortingOption == SortingOption.FB_FRIENDS;
}
/**
* @return the value of includePosted
*/
public boolean isIncludePosted() {
return sortingOption == SortingOption.ALL ||
sortingOption == SortingOption.MY_POSTS;
}
/**
* @return the value of includeFriends
*/
public boolean isIncludeFriends() {
return sortingOption == SortingOption.FB_FRIENDS;
}
}