/*
* Copyright (C) 2013 The Android Open Source Project
*
* 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.example.google.location;
import com.example.google.R;
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.ActivityRecognitionClient;
import com.google.android.gms.location.Geofence;
import com.google.android.gms.location.LocationClient;
import com.google.android.gms.location.LocationClient.OnAddGeofencesResultListener;
import com.google.android.gms.location.LocationListener;
import com.google.android.gms.location.LocationRequest;
import com.google.android.gms.location.LocationStatusCodes;
import com.google.android.gms.maps.CameraUpdateFactory;
import com.google.android.gms.maps.GoogleMap;
import com.google.android.gms.maps.GoogleMap.OnMapLongClickListener;
import com.google.android.gms.maps.SupportMapFragment;
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 android.app.Dialog;
import android.app.PendingIntent;
import android.content.ComponentName;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.res.Configuration;
import android.graphics.Color;
import android.location.Location;
import android.os.Bundle;
import android.support.v4.app.FragmentActivity;
import android.support.v4.app.FragmentManager;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.MenuItem.OnMenuItemClickListener;
import android.view.View;
import android.widget.CompoundButton;
import android.widget.CompoundButton.OnCheckedChangeListener;
import android.widget.Switch;
import android.widget.TextView;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class LocationActivity extends FragmentActivity {
public static String TAG = "LocationActivity";
public static boolean isAppForeground = false;
private static final int ERROR_DIALOG_ON_CREATE_REQUEST_CODE = 4055;
private static final int ERROR_DIALOG_ON_RESUME_REQUEST_CODE = 4056;
// Shared variables
private GoogleMap mMap;
private Dialog errorDialog;
// Location Request variables
private LocationClient mLocationClient;
private TextView mLocationStatus;
private LocationCallback mLocationCallback = new LocationCallback();
private Location mLastLocation;
private static final int LOCATION_UPDATES_INTERVAL = 10000; // Setting 10
// sec interval
// for location
// updates
// Activity Recognition variables
private ActivityRecognitionClient mActivityRecognitionClient;
private ActivityRecognitionCallback mActivityRecognitionCallback = new ActivityRecognitionCallback();
public static final String ACTION_ACTIVITY_RECOGNITION =
"com.example.google.location.LocationActivity.ACTIVITY_RECOGNITION";
private static final int ACTIVITY_UPDATES_INTERVAL = 4000;
private PendingIntent mActivityRecognitionPendingIntent;
private Switch mSwitch;
private ActivityRecognitionIntentReceiver mActivityRecognitionIntentReceiver;
// Geo Fencing variables
private GeoFenceCallback mGeoFenceCallback = new GeoFenceCallback();
private int id = 0;
private static final float GEOFENCE_RADIUS = 100;
private HashMap<String, Circle> mGeoFences;
private HashMap<String, Circle> mTriggeringFences;
public static final String ACTION_GEOFENCE =
"com.example.google.location.LocationActivity.GEOFENCE";
private TextView mGeoFenceStatus;
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
checkGooglePlayServiceAvailability(ERROR_DIALOG_ON_CREATE_REQUEST_CODE);
}
private void init() {
// Initialize map
if (mMap == null) {
FragmentManager myFragmentManager = getSupportFragmentManager();
SupportMapFragment myMapFragment = (SupportMapFragment) myFragmentManager
.findFragmentById(R.id.map);
mMap = myMapFragment.getMap();
}
// Initialize Location Client
mLocationStatus = (TextView) findViewById(R.id.location_status);
if (mLocationClient == null) {
mLocationClient = new LocationClient(this, mLocationCallback, mLocationCallback);
Log.v(LocationActivity.TAG, "Location Client connect");
if (!(mLocationClient.isConnected() || mLocationClient.isConnecting())) {
mLocationClient.connect();
}
}
// Initialize Action Recognition
if (mActivityRecognitionClient == null) {
mActivityRecognitionClient =
new ActivityRecognitionClient(this,
mActivityRecognitionCallback, mActivityRecognitionCallback);
}
mSwitch = (Switch) findViewById(R.id.swtich);
mSwitch.setOnCheckedChangeListener(new OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
if (isChecked) {
startActivityDetection(buttonView);
} else {
stopActivityDetection(buttonView);
}
}
});
if (mActivityRecognitionIntentReceiver == null) {
mActivityRecognitionIntentReceiver = new ActivityRecognitionIntentReceiver();
registerReceiver(mActivityRecognitionIntentReceiver,
new IntentFilter(LocationActivity.ACTION_ACTIVITY_RECOGNITION));
}
// Initialize Geo Fencing
mGeoFenceStatus = (TextView) findViewById(R.id.geo_fence_status);
if (mGeoFences == null) {
mGeoFences = new HashMap<String, Circle>();
}
if (mTriggeringFences == null) {
mTriggeringFences = new HashMap<String, Circle>();
}
// Setup map to allow adding Geo Fences
mMap.getUiSettings().setAllGesturesEnabled(true);
mMap.setOnMapLongClickListener(mGeoFenceCallback);
}
@Override
public void onPause() {
super.onPause();
// Indicate the application is in background
isAppForeground = false;
if (mLocationClient.isConnected()) {
mLocationClient.removeLocationUpdates(mLocationCallback);
mLocationClient.disconnect();
}
}
@Override
public void onResume() {
super.onResume();
// Indicate the application is in foreground
isAppForeground = true;
checkGooglePlayServiceAvailability(ERROR_DIALOG_ON_RESUME_REQUEST_CODE);
restartLocationClient();
}
@Override
public void onDestroy() {
super.onDestroy();
unregisterReceiver(mActivityRecognitionIntentReceiver);
mActivityRecognitionIntentReceiver = null;
}
private void checkGooglePlayServiceAvailability(int requestCode) {
// Query for the status of Google Play services on the device
int statusCode = GooglePlayServicesUtil
.isGooglePlayServicesAvailable(getBaseContext());
if (statusCode == ConnectionResult.SUCCESS) {
init();
} else {
if (GooglePlayServicesUtil.isUserRecoverableError(statusCode)) {
errorDialog = GooglePlayServicesUtil.getErrorDialog(statusCode,
this, requestCode);
errorDialog.show();
} else {
// Handle unrecoverable error
}
}
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (resultCode == RESULT_OK) {
switch (requestCode) {
case ERROR_DIALOG_ON_CREATE_REQUEST_CODE:
init();
break;
case ERROR_DIALOG_ON_RESUME_REQUEST_CODE:
restartLocationClient();
break;
}
}
}
private void restartLocationClient() {
if (!(mLocationClient.isConnected() || mLocationClient.isConnecting())) {
mLocationClient.connect(); // Somehow it becomes connected here
return;
}
LocationRequest request = LocationRequest.create();
request.setInterval(LOCATION_UPDATES_INTERVAL);
request.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);
mLocationClient.requestLocationUpdates(request, mLocationCallback);
}
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
MenuItem menuItem = menu.add(R.string.clear_map);
menuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
menuItem.setOnMenuItemClickListener(new OnMenuItemClickListener() {
@Override
public boolean onMenuItemClick(MenuItem item) {
clearMap();
return true;
}
});
return true;
}
public void clearMap() {
mMap.clear();
mLastLocation = null;
}
private class LocationCallback implements ConnectionCallbacks, OnConnectionFailedListener,
LocationListener {
@Override
public void onConnected(Bundle connectionHint) {
Log.v(LocationActivity.TAG, "Location Client Connected");
// Display last location
Location location = mLocationClient.getLastLocation();
if (location != null) {
handleLocation(location);
}
// Request for location updates
LocationRequest request = LocationRequest.create();
request.setInterval(LOCATION_UPDATES_INTERVAL);
request.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);
mLocationClient.requestLocationUpdates(request, mLocationCallback);
// Setup map to allow adding Geo Fences
}
@Override
public void onDisconnected() {
Log.v(LocationActivity.TAG, "Location Client disconnected by the system");
}
@Override
public void onConnectionFailed(ConnectionResult result) {
Log.v(LocationActivity.TAG, "Location Client connection failed");
}
@Override
public void onLocationChanged(Location location) {
if (location == null) {
Log.v(LocationActivity.TAG, "onLocationChanged: location == null");
return;
}
// Add a marker iff location has changed.
if (mLastLocation != null &&
mLastLocation.getLatitude() == location.getLatitude() &&
mLastLocation.getLongitude() == location.getLongitude()) {
return;
}
handleLocation(location);
}
private void handleLocation(Location location) {
// Update the mLocationStatus with the lat/lng of the location
Log.v(LocationActivity.TAG, "LocationChanged == @" +
location.getLatitude() + "," + location.getLongitude());
mLocationStatus.setText("Location changed @" + location.getLatitude() + "," +
location.getLongitude());
// Add a marker of that location to the map
LatLng latlongzoom = new LatLng(location.getLatitude(),
location.getLongitude());
String snippet = location.getLatitude() + "," + location.getLongitude();
Marker marker = mMap.addMarker(
new MarkerOptions().position(latlongzoom));
marker.setSnippet(snippet);
marker.setTitle(snippet);
// Center the map to the first marker
if (mLastLocation == null) {
mMap.moveCamera(CameraUpdateFactory.
newCameraPosition(CameraPosition.fromLatLngZoom(
new LatLng(location.getLatitude(), location.getLongitude()),
(float) 16.0)));
}
mLastLocation = location;
}
};
private class ActivityRecognitionCallback implements ConnectionCallbacks,
OnConnectionFailedListener {
@Override
public void onConnected(Bundle connectionHint) {
Log.v(LocationActivity.TAG, "Activity Recognition Client connected");
// Request activity updates
Intent intent = new Intent(LocationActivity.this,
ActivityRecognitionIntentService.class);
intent.setAction(LocationActivity.ACTION_ACTIVITY_RECOGNITION);
mActivityRecognitionPendingIntent = PendingIntent.getService(LocationActivity.this, 0,
intent,
PendingIntent.FLAG_UPDATE_CURRENT);
mActivityRecognitionClient.requestActivityUpdates(ACTIVITY_UPDATES_INTERVAL,
mActivityRecognitionPendingIntent);
}
@Override
public void onDisconnected() {
Log.v(LocationActivity.TAG, "Activity Recognition Client disconnected by the system");
}
@Override
public void onConnectionFailed(ConnectionResult result) {
Log.v(LocationActivity.TAG,
"Activity Recognition Client connection failed " + result.getErrorCode());
}
};
public void startActivityDetection(View v) {
if (!mActivityRecognitionClient.isConnected()) {
mActivityRecognitionClient.connect();
}
}
public void stopActivityDetection(View v) {
if (mActivityRecognitionClient.isConnected()) {
mActivityRecognitionClient.removeActivityUpdates(mActivityRecognitionPendingIntent);
mActivityRecognitionClient.disconnect();
}
}
private class GeoFenceCallback implements OnMapLongClickListener,
OnAddGeofencesResultListener {
@Override
public void onMapLongClick(LatLng point) {
Log.v(LocationActivity.TAG,
"onMapLongClick == " + point.latitude + "," + point.longitude);
CircleOptions circleOptions = new CircleOptions();
circleOptions.center(point).radius(GEOFENCE_RADIUS).strokeColor(
android.graphics.Color.BLUE).strokeWidth(2);
Circle circle = mMap.addCircle(circleOptions);
String key = Integer.toString(id);
id++;
mGeoFences.put(key, circle);
addGeoFences();
}
// Creates Geofence objects from all circles on the map and calls
// addGeofences API.
private void addGeoFences() {
List<Geofence> list = new ArrayList<Geofence>();
for (Map.Entry<String, Circle> entry : mGeoFences.entrySet()) {
Circle circle = entry.getValue();
Log.v(LocationActivity.TAG, "points == " +
circle.getCenter().latitude + "," +
circle.getCenter().longitude);
Geofence geofence = new Geofence.Builder()
.setRequestId(entry.getKey())
.setTransitionTypes(Geofence.GEOFENCE_TRANSITION_ENTER |
Geofence.GEOFENCE_TRANSITION_EXIT)
.setCircularRegion(circle.getCenter().latitude,
circle.getCenter().longitude,
(float) circle.getRadius())
.setExpirationDuration(Geofence.NEVER_EXPIRE).build();
list.add(geofence);
}
if (list.isEmpty()) {
return;
}
// Clear off all the currently triggering geo_fences before new
// fences
// are added.
for (Circle triggeringGeoFence : mTriggeringFences.values()) {
triggeringGeoFence.remove();
}
mTriggeringFences.clear();
Log.v(LocationActivity.TAG, "addingGeoFences size = " + list.size());
mLocationClient.addGeofences(list, getPendingIntent(), this);
}
private PendingIntent getPendingIntent() {
Intent intent = new Intent(ACTION_GEOFENCE);
intent.setComponent(new ComponentName(LocationActivity.this,
GeoFenceIntentReceiver.class));
return PendingIntent.getBroadcast(LocationActivity.this, 0, intent,
PendingIntent.FLAG_UPDATE_CURRENT);
}
@Override
public void onAddGeofencesResult(int statusCode,
String[] geofenceRequestIds) {
StringBuilder builder = new StringBuilder();
for (int i = 0; i < geofenceRequestIds.length - 1; ++i) {
builder.append(geofenceRequestIds[i]);
builder.append(",");
}
builder.append(geofenceRequestIds[geofenceRequestIds.length - 1]);
Log.v(LocationActivity.TAG, "Added Geofences == "
+ statusCodeToString(statusCode) + " " + builder.toString());
mGeoFenceStatus.setText("Added Geofences "
+ statusCodeToString(statusCode) + " " + builder.toString());
}
private String statusCodeToString(int statusCode) {
switch (statusCode) {
case LocationStatusCodes.SUCCESS:
return "SUCCESS";
case LocationStatusCodes.GEOFENCE_NOT_AVAILABLE:
return "GEOFENCE_NOT_AVAILABLE";
case LocationStatusCodes.GEOFENCE_TOO_MANY_GEOFENCES:
return "GEOFENCE_TOO_MANY_GEOFENCES";
case LocationStatusCodes.GEOFENCE_TOO_MANY_PENDING_INTENTS:
return "GEOFENCE_TOO_MANY_PENDING_INTENTS";
case LocationStatusCodes.ERROR:
return "ERROR";
}
return "UNKNOWN";
}
}
// Triggered when startAcitivity method is called in GeoFenceIntentReceiver.
// Updates UI as geofences are entered/exited.
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
// getIntent() should always return the most recent
setIntent(intent);
boolean receiverStarted =
intent.getBooleanExtra("RECEIVER_STARTED", false);
if (!receiverStarted) {
return;
}
Bundle bundle = intent.getParcelableExtra("geo_fences");
ArrayList<String> requestIds =
bundle.getStringArrayList("request_ids");
if (requestIds == null) {
Log.v(LocationActivity.TAG, "request_ids == null");
return;
}
int transition = intent.getIntExtra("transition", -2);
for (String requestId : requestIds) {
Log.v(LocationActivity.TAG, "Triggering Geo Fence requestId "
+ requestId);
if (transition == Geofence.GEOFENCE_TRANSITION_ENTER) {
Circle circle = mGeoFences.get(requestId);
if (circle == null) {
continue;
}
Log.v(LocationActivity.TAG, "triggering_geo_fences enter == "
+ requestId);
// Add a superimposed red circle when a geofence is entered and
// put the corresponding object in triggering_fences.
CircleOptions circleOptions = new CircleOptions();
circleOptions.center(circle.getCenter())
.radius(circle.getRadius())
.fillColor(Color.argb(100, 100, 0, 0));
Circle newCircle = mMap.addCircle(circleOptions);
mTriggeringFences.put(requestId, newCircle);
} else if (transition == Geofence.GEOFENCE_TRANSITION_EXIT) {
Log.v(LocationActivity.TAG, "triggering_geo_fences exit == "
+ requestId);
Circle circle = mTriggeringFences.get(requestId);
if (circle == null) {
continue;
}
// Remove the superimposed red circle from the map and the
// corresponding Circle object from triggering_fences hash_map.
circle.remove();
mTriggeringFences.remove(requestId);
}
}
return;
}
}