/*
* Copyright 2011 Jeremy Haberman
*
* 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.jeremyhaberman.playgrounds;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Timer;
import java.util.TimerTask;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import com.google.android.maps.GeoPoint;
import com.google.android.maps.MapActivity;
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 android.app.Activity;
import android.app.AlertDialog;
import android.app.ProgressDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.res.Resources;
import android.graphics.drawable.Drawable;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.preference.ListPreference;
import android.util.Log;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
/**
* Playgrounds is the entry and primary Activity. It displays the map, the
* user's current location and all playgrounds
*
* @author jeremyhaberman
*
*/
public class Playgrounds extends MapActivity {
private static Context context;
private MapView map;
private MapController controller;
private MyLocationOverlay overlay;
protected GeoPoint myLocation = null;
private List<Overlay> mapOverlays;
private PlaygroundsLayer playgroundsLayer;
private ProgressDialog progressDialog;
private AlertDialog alertDialog;
private static final String LOADING_NEARBY_PLAYGROUNDS = "Loading nearby playgrounds";
protected static final int ERROR_LOADING_PLAYGROUNDS = 1;
protected static final CharSequence ERROR_LOADING_PLAYGROUNDS_STRING = "Error loading playgrounds.";
protected static final int ERROR_FINDING_LOCATION = 0;
DownloadPlaygroundsTask downloadPlaygroundsTask;
Timer timer;
TimerTask getMyLocationTask;
/**
* Instantiate the playgroundDAO for retrieving playground data and display
* the map. The current location and playground data is loaded by
* onResume().
*/
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.map);
initMapView();
}
/**
* Display the map, allowing zoom controls
*/
private void initMapView() {
map = (MapView) findViewById(R.id.map);
controller = map.getController();
map.setBuiltInZoomControls(true);
}
/**
* During onResume(), show the current location and nearby playgrounds
*/
@Override
protected void onResume() {
super.onResume();
showMyLocationAndPlaygrounds();
}
/**
* Show the current location on the map, zoom in to level 13 and then
* display nearby playgrounds
*/
private void showMyLocationAndPlaygrounds() {
if (overlay == null) {
overlay = new MyLocationOverlay(this, map);
}
overlay.enableMyLocation();
// Once the location is known, zoom in to it
overlay.runOnFirstFix(new Runnable() {
@Override
public void run() {
controller.setZoom(getZoom());
myLocation = overlay.getMyLocation();
controller.animateTo(myLocation);
mHandler.post(showPlaygrounds);
}
});
map.getOverlays().add(overlay);
}
/**
* Gets the zoom level for the map based on the range preference
*
* @return
*/
protected int getZoom() {
int range = getRange();
switch (range) {
case 1: return 15;
case 2: return 14;
case 5: return 13;
case 10: return 12;
default: return 13;
}
}
/**
* Gets the range preference
* @return
*/
private int getRange() {
return Integer.parseInt(Settings.getRange(getApplicationContext()));
}
final Runnable showPlaygrounds = new Runnable() {
public void run() {
showNearbyPlaygrounds();
}
};
/**
* Handler for callbacks to the UI thread
*/
final Handler mHandler = new Handler() {
public void handleMessage(Message message) {
AlertDialog.Builder builder = new AlertDialog.Builder(getApplicationContext());
switch (message.what) {
case ERROR_FINDING_LOCATION:
progressDialog.dismiss();
builder.setMessage(getString(R.string.unable_to_get_current_location));
break;
case ERROR_LOADING_PLAYGROUNDS:
progressDialog.dismiss();
builder.setMessage(ERROR_LOADING_PLAYGROUNDS_STRING);
break;
}
progressDialog.dismiss();
}
};
private int range;
/**
* Get playground data via background task and add it to the map
*/
private void showNearbyPlaygrounds() {
downloadPlaygroundsTask = new DownloadPlaygroundsTask();
downloadPlaygroundsTask.execute(myLocation);
}
/**
* Add playground items to the map. If <code>playgrounds</code> is
* <code>null</code>, send a message to the UI handler to display the error
* and allow the user to try again.
*
* @param playgrounds
*/
public void addPlaygroundsToMap(Collection<Playground> playgrounds) {
if (playgrounds == null) {
mHandler.sendEmptyMessage(ERROR_LOADING_PLAYGROUNDS);
return;
}
mapOverlays = map.getOverlays();
Drawable drawable = this.getResources().getDrawable(R.drawable.pin2);
playgroundsLayer = new PlaygroundsLayer(this, drawable);
Collection<PlaygroundItem> playgroundItems = PlaygroundItemCreator.createItems(playgrounds);
for (PlaygroundItem item : playgroundItems) {
playgroundsLayer.addOverlayItem(item);
}
mapOverlays.add(playgroundsLayer);
map.postInvalidate();
}
@Override
protected void onPause() {
if (progressDialog != null && progressDialog.isShowing()) {
progressDialog.cancel();
}
if (alertDialog != null && alertDialog.isShowing()) {
alertDialog.cancel();
}
removePlaygroundsOnMap();
overlay.disableMyLocation();
overlay = null;
downloadPlaygroundsTask.cancel(true);
super.onPause();
}
/**
* Clear the playgrounds from the map
*/
private void removePlaygroundsOnMap() {
mapOverlays = map.getOverlays();
if (playgroundsLayer != null) {
mapOverlays.remove(playgroundsLayer);
}
}
/**
* Display a "Loading nearby playgrounds..." dialog while the background
* task is retrieving playground data
*/
private void displayLoadingPlaygroundsProgressDialog() {
progressDialog = ProgressDialog
.show(Playgrounds.this, "", LOADING_NEARBY_PLAYGROUNDS, true);
progressDialog.show();
}
/**
* Display an error about failing to load playground data. Called by
* displayErrorTask
*/
protected void displayLocationError() {
AlertDialog.Builder builder = new AlertDialog.Builder(this);
// builder.setTitle("ERROR");
builder.setMessage(getString(R.string.unable_to_get_current_location));
builder.setCancelable(false);
builder.setPositiveButton("Try Again", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
alertDialog.dismiss();
mHandler.post(showPlaygrounds);
}
});
builder.setNegativeButton("Close", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
alertDialog.dismiss();
}
});
alertDialog = builder.create();
alertDialog.show();
mHandler.sendEmptyMessage(0);
}
/**
* Displays an error if the playground data was not successfully loaded by
* the thread in showPlaygrounds().
*/
final Runnable displayPlaygroundLoadErrorTask = new Runnable() {
public void run() {
displayPlaygroundLoadError();
}
};
/**
* Display an error about failing to load playground data. Called by
* displayErrorTask
*/
protected void displayPlaygroundLoadError() {
AlertDialog.Builder builder = new AlertDialog.Builder(this);
// builder.setTitle("ERROR");
builder.setMessage("Error loading playgrounds");
builder.setCancelable(false);
builder.setPositiveButton("Try Again", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
alertDialog.dismiss();
mHandler.post(showPlaygrounds);
}
});
builder.setNegativeButton("Close", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
alertDialog.dismiss();
}
});
alertDialog = builder.create();
alertDialog.show();
mHandler.sendEmptyMessage(0);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
super.onCreateOptionsMenu(menu);
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.menu, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
super.onOptionsItemSelected(item);
switch (item.getItemId()) {
case R.id.add_current_location_button:
startActivity(new Intent(this, AddCurrentLocation.class));
return true;
case R.id.add_by_address_button:
startActivity(new Intent(this, AddByAddress.class));
return true;
case R.id.settings:
startActivity(new Intent(this, Settings.class));
return true;
case R.id.about:
startActivity(new Intent(this, About.class));
}
return false;
}
protected MapView getMap() {
return map;
}
protected Overlay getPlaygroundOverlay() {
return playgroundsLayer;
}
private class DownloadPlaygroundsTask extends AsyncTask<GeoPoint, Void, Collection<Playground>> {
private static final String TAG = "DownloadPlaygroundsTask";
private static final String LATITUDE_PARAM = "latitude";
private static final String LONGITUDE_PARAM = "longitude";
private static final String MAX_PARAM = "max";
private static final String TYPE_PARAM = "type";
private static final String NEARBY = "nearby";
private static final int MAX_QUANTITY = 5;
private static final String WITHIN = "within";
private static final String RANGE = "range";
private static final String RANGE_PARAM = "range";
protected void onPreExecute() {
displayLoadingPlaygroundsProgressDialog();
}
@Override
protected Collection<Playground> doInBackground(GeoPoint... params) {
GeoPoint location = params[0];
Collection<Playground> playgrounds = new ArrayList<Playground>();
HttpURLConnection httpConnection = null;
Log.d(TAG, "getPlaygrounds()");
try {
// Build query
URL url = new URL("http://swingsetweb.appspot.com/playground?" + TYPE_PARAM + "="
+ RANGE + "&" + LATITUDE_PARAM + "=" + location.getLatitudeE6() / 1E6 + "&"
+ LONGITUDE_PARAM + "=" + location.getLongitudeE6() / 1E6 + "&" + RANGE_PARAM + "="
+ getRange());
httpConnection = (HttpURLConnection) url.openConnection();
httpConnection.setConnectTimeout(10000);
httpConnection.setReadTimeout(10000);
StringBuilder response = new StringBuilder();
if (httpConnection.getResponseCode() == HttpURLConnection.HTTP_OK) {
// Read results from the query
BufferedReader input = new BufferedReader(new InputStreamReader(
httpConnection.getInputStream(), "UTF-8"));
String strLine = null;
while ((strLine = input.readLine()) != null) {
response.append(strLine);
}
input.close();
}
// Parse to get translated text
JSONArray jsonPlaygrounds = new JSONArray(response.toString());
int numOfPlaygrounds = jsonPlaygrounds.length();
JSONObject jsonPlayground = null;
for (int i = 0; i < numOfPlaygrounds; i++) {
jsonPlayground = jsonPlaygrounds.getJSONObject(i);
playgrounds.add(toPlayground(jsonPlayground));
}
} catch (Exception e) {
Log.e(TAG, "Exception", e);
} finally {
if (httpConnection != null) {
httpConnection.disconnect();
}
}
return playgrounds;
}
@Override
protected void onCancelled() {
super.onCancelled();
}
@Override
protected void onPostExecute(Collection<Playground> playgrounds) {
if (playgrounds.size() == 0) {
mHandler.post(displayPlaygroundLoadErrorTask);
} else {
addPlaygroundsToMap(playgrounds);
mHandler.sendEmptyMessage(0);
}
}
private Playground toPlayground(JSONObject jsonPlayground) throws JSONException {
String name = jsonPlayground.getString("name");
String description = jsonPlayground.getString("description");
int latitude = jsonPlayground.getInt("latitude");
int longitude = jsonPlayground.getInt("longitude");
return new Playground(name, description, latitude, longitude);
}
}
@Override
protected boolean isRouteDisplayed() {
return false;
}
protected static Context getContext() {
return context;
}
}