Java tutorial
/** * Copyright (c) 2015-2016 IBM Corporation. All rights reserved. * <p/> * 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 * <p/> * http://www.apache.org/licenses/LICENSE-2.0 * <p/> * 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.ibm.mf.geofence.demo; import android.content.Context; import android.content.IntentFilter; import android.graphics.Bitmap; import android.location.Location; import android.location.LocationManager; import android.os.Bundle; import android.support.v4.app.FragmentActivity; import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.widget.Button; import android.widget.ImageView; import com.google.android.gms.maps.CameraUpdateFactory; import com.google.android.gms.maps.GoogleMap; import com.google.android.gms.maps.SupportMapFragment; import com.google.android.gms.maps.model.BitmapDescriptor; import com.google.android.gms.maps.model.BitmapDescriptorFactory; 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.LatLngBounds; import com.google.android.gms.maps.model.Marker; import com.google.android.gms.maps.model.MarkerOptions; import com.google.maps.android.ui.IconGenerator; import com.ibm.mf.geofence.LoggingConfiguration; import com.ibm.mf.geofence.MFGeofence; import com.ibm.mf.geofence.MFGeofenceEvent; import com.ibm.mf.geofence.MFGeofencingManager; import com.ibm.mf.geofence.Settings; import com.ibm.pisdk.geofencing.demo.R; import org.apache.log4j.Logger; import java.util.HashMap; import java.util.List; import java.util.Map; /** * Handles a Google map where a number of geofences are highlighted as semi-transparent red circles. * As the current locations moves within a geofence, its circle becomes green and a marker with a * text label displays the name of the place. */ public class MapsActivity extends FragmentActivity { /** * Logger for this class. */ private static final Logger log = LoggingConfiguration.getLogger(MapsActivity.class.getSimpleName()); //private static Logger log; /** * Color for fences active on the map. */ private static final int ACTIVE_FENCE_COLOR = 0x6080E080; /** * Color for fences active on the map. */ private static final int INACTIVE_FENCE_COLOR = 0x40E08080; /** * Normal mode. */ private static final int MODE_NORMAL = 1; /** * In this mode, we are defining the location of a geofence. */ private static final int MODE_EDIT = 2; /** * Settings key for the tracking enabled flag. */ static final String TRACKING_ENABLED_KEY = "tracking.enabled"; static final String SERVER_URL = "http://geofencing-sample.mybluemix.net"; static final String USER = "demo"; static final String PWD = "demo"; /** * Might be null if Google Play services APK is not available. */ GoogleMap googleMap; /** * Marker on the map for the user's current position. */ private Marker currentMarker = null; Location currentLocation = null; float currentZoom = -1f; GeofenceInfo editedInfo = null; Settings settings; /** * */ private final GeofenceHolder geofenceHolder = new GeofenceHolder(); /** * Mapping of geofence uuids to the corresponding objects (marker, circle etc.) displayed on the map. */ Map<String, GeofenceInfo> geofenceInfoMap = new HashMap<>(); /** * Reference to the geofencing service used in this demo. */ MFGeofencingManager manager; CustomHttpService customHttpService; /** * Whether the db has already been deleted once. */ private static boolean dbDeleted = false; Button addFenceButton = null; /** * UI mode: MODE_NORMAL or MODE_EDIT when positioning a geofence. */ int mapMode = MODE_NORMAL; private ImageView mapCrossHair; /** * Whether to send Slack and local notifications upon geofence events. */ boolean trackingEnabled = true; private MyGeofenceReceiver receiver; @Override protected void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); if (currentLocation != null) { outState.putDoubleArray("currentLocation", new double[] { currentLocation.getLatitude(), currentLocation.getLongitude() }); } if (googleMap != null) { outState.putFloat("zoom", googleMap.getCameraPosition().zoom); } //log.debug("onSaveInstanceState()"); } @Override protected void onRestoreInstanceState(Bundle savedInstanceState) { super.onRestoreInstanceState(savedInstanceState); if (savedInstanceState != null) { double[] loc = savedInstanceState.getDoubleArray("currentLocation"); if (loc != null) { currentLocation = new Location(LocationManager.NETWORK_PROVIDER); currentLocation.setLatitude(loc[0]); currentLocation.setLongitude(loc[1]); currentLocation.setTime(System.currentTimeMillis()); } currentZoom = savedInstanceState.getFloat("zoom", -1f); //log.debug(String.format("restored currentLocation=%s; currentZoom=%f", currentLocation, currentZoom)); } } @Override protected void onStart() { super.onStart(); if (receiver == null) { receiver = new MyGeofenceReceiver(this); } IntentFilter filter = new IntentFilter(MFGeofenceEvent.ACTION_GEOFENCE_EVENT); registerReceiver(receiver, filter); } @Override protected void onStop() { super.onStop(); if (receiver != null) { unregisterReceiver(receiver); } } @Override protected void onCreate(Bundle savedInstanceState) { log.debug("***************************************************************************************"); super.onCreate(savedInstanceState); setContentView(R.layout.maps_activity); mapCrossHair = (ImageView) findViewById(R.id.map_cross_hair); log.debug("in onCreate() tracking is " + (trackingEnabled ? "enabled" : "disabled")); addFenceButton = (Button) findViewById(R.id.addFenceButton); addFenceButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { switchMode(); } }); if (savedInstanceState != null) { double[] loc = savedInstanceState.getDoubleArray("currentLocation"); if (loc != null) { currentLocation = new Location(LocationManager.NETWORK_PROVIDER); currentLocation.setLatitude(loc[0]); currentLocation.setLongitude(loc[1]); currentLocation.setTime(System.currentTimeMillis()); } currentZoom = savedInstanceState.getFloat("zoom", -1f); log.debug(String.format("restored currentLocation=%s; currentZoom=%f", currentLocation, currentZoom)); } if (currentLocation == null) { LocationManager lm = (LocationManager) getSystemService(Context.LOCATION_SERVICE); currentLocation = lm.getLastKnownLocation(LocationManager.NETWORK_PROVIDER); } log.debug("onCreate() : init of geofencing service"); /* if (!dbDeleted) { dbDeleted = true; DemoUtils.deleteGeofenceDB(this); } */ initManager(); /* // testing the loading from a zip resource manager.loadGeofencesFromResource("com/ibm/pisdk/geofencing/geofence_2016-03-18_14_38_04.zip"); */ customHttpService = new CustomHttpService(manager, this, SERVER_URL, USER, PWD); try { startSimulation(geofenceHolder.getFences()); } catch (Exception e) { log.error("error in startSimulation()", e); } } private void initManager() { settings = new Settings(this); manager = new MFGeofencingManager(this, SERVER_URL, USER, PWD, 10_000); manager.setIntervalBetweenDowloads(0); DemoUtils.updateSettingsIfNeeded(settings); trackingEnabled = settings.getBoolean(TRACKING_ENABLED_KEY, true); } /** * Start the simulation of a user walking between the geofences. */ void startSimulation(final List<MFGeofence> fences) { for (MFGeofence g : fences) { geofenceHolder.updateGeofence(g); } runOnUiThread(new Runnable() { @Override public void run() { try { setUpMapIfNeeded(); for (MFGeofence g : fences) { refreshGeofenceInfo(g, false); } } catch (Exception e) { log.error("error in startSimulation()", e); } } }); } /** * Sets up the map if it is possible to do so (i.e., the Google Play services APK is correctly installed) and the map has not already been instantiated. */ void setUpMapIfNeeded() { if (googleMap == null) { googleMap = ((SupportMapFragment) getSupportFragmentManager().findFragmentById(R.id.map)).getMap(); log.debug("setUpMapIfNeeded() : googleMap = " + googleMap); if (googleMap != null) { LocationManager locationManager = (LocationManager) this.getSystemService(Context.LOCATION_SERVICE); try { if (currentLocation == null) currentLocation = locationManager.getLastKnownLocation(LocationManager.NETWORK_PROVIDER); } catch (SecurityException e) { log.debug("missing permission to request get last location from NETWORK_PROVIDER"); } initGeofences(); // receive updates for th ecurrent location try { locationManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, 10000, 10f, new android.location.LocationListener() { @Override public void onLocationChanged(Location location) { currentLocation = location; refreshCurrentLocation(); } @Override public void onStatusChanged(String provider, int status, Bundle extras) { } @Override public void onProviderEnabled(String provider) { } @Override public void onProviderDisabled(String provider) { } }); } catch (SecurityException e) { log.debug("missing permission to request locations from NETWORK_PROVIDER"); } // set the map center and zoom level/bounds once it is loaded googleMap.setOnMapLoadedCallback(new GoogleMap.OnMapLoadedCallback() { @Override public void onMapLoaded() { runOnUiThread(new Runnable() { @Override public void run() { List<MFGeofence> fences = geofenceHolder.getFences(); LatLng latlng = currentLocation != null ? new LatLng(currentLocation.getLatitude(), currentLocation.getLongitude()) : googleMap.getCameraPosition().target; Map<String, Object> map = (fences != null) && !fences.isEmpty() ? DemoUtils.computeBounds(fences, latlng) : DemoUtils.computeBounds(latlng, 0.0005, 0.0005); log.debug("setUpMapIfNeeded() : bounds map = " + map + ", fences = " + fences); LatLngBounds bounds = (LatLngBounds) map.get("bounds"); LatLng loc = (LatLng) map.get("center"); if (currentZoom >= 0f) { log.debug("setUpMapIfNeeded() setting zoom"); googleMap.moveCamera(CameraUpdateFactory.zoomTo(currentZoom)); } else { log.debug("setUpMapIfNeeded() setting bounds"); googleMap.moveCamera(CameraUpdateFactory.newLatLngBounds(bounds, 100)); } refreshCurrentLocation(loc.latitude, loc.longitude); } }); } }); // respond to taps on the fences labels googleMap.setOnMarkerClickListener(new GoogleMap.OnMarkerClickListener() { @Override public boolean onMarkerClick(Marker marker) { if (mapMode != MODE_EDIT) { GeofenceInfo info = getGeofenceInfoForMarker(marker); log.debug(String.format("onMarkerClick(marker=%s) info=%s", marker, info)); EditGeofenceDialog dialog = new EditGeofenceDialog(); dialog.customInit(MapsActivity.this, EditGeofenceDialog.MODE_UPDATE_DELETE, info); dialog.show(getFragmentManager(), "geofences"); } return true; } }); } } } /** * Update the marker for the device's current location. */ void refreshCurrentLocation() { if (currentLocation != null) { refreshCurrentLocation(currentLocation.getLatitude(), currentLocation.getLongitude()); } } /** * Update the marker for the device's current location. */ private void refreshCurrentLocation(final double latitude, final double longitude) { runOnUiThread(new Runnable() { @Override public void run() { LatLng pos = new LatLng(latitude, longitude); if (currentMarker == null) { currentMarker = googleMap.addMarker(new MarkerOptions().position(pos).title("HappyUser")); } else { currentMarker.setPosition(pos); } updateCurrentMarker(); } }); } /** * Update the marker for the current location, based on whether the location is inside a geofence or not. */ void updateCurrentMarker() { if (currentMarker != null) { BitmapDescriptor btDesc = BitmapDescriptorFactory .defaultMarker(geofenceHolder.hasActiveFence() ? BitmapDescriptorFactory.HUE_AZURE : BitmapDescriptorFactory.HUE_RED); currentMarker.setIcon(btDesc); } } /** * Update the circle and label of a geofence depending on its active state. * @param fence the fence to update. * @param active whether the fence is active (current location is within the fence). */ void refreshGeofenceInfo(MFGeofence fence, boolean active) { String uuid = fence.getCode(); if (geofenceHolder.getGeofence(uuid) == null) { geofenceHolder.updateGeofence(fence); } if (active) { geofenceHolder.addActiveFence(uuid); } else { geofenceHolder.removeActiveFence(uuid); } GeofenceInfo info = geofenceInfoMap.get(uuid); if (info == null) { info = new GeofenceInfo(); info.uuid = uuid; info.name = fence.getName(); geofenceInfoMap.put(uuid, info); } info.active = active; int color = active ? ACTIVE_FENCE_COLOR : INACTIVE_FENCE_COLOR; LatLng pos = new LatLng(fence.getLatitude(), fence.getLongitude()); if (info.circle != null) { info.circle.setFillColor(color); } else { Circle c = googleMap.addCircle( new CircleOptions().center(pos).radius(fence.getRadius()).fillColor(color).strokeWidth(0f)); c.setVisible(true); info.circle = c; } BitmapDescriptor btDesc = BitmapDescriptorFactory.fromBitmap(getOrCreateBitmap(fence, info)); if (info.marker != null) { info.marker.setIcon(btDesc); } else { info.marker = googleMap.addMarker(new MarkerOptions().position(pos).icon(btDesc)); } info.name = fence.getName(); } void removeGeofence(MFGeofence fence) { String uuid = fence.getCode(); GeofenceInfo info = geofenceInfoMap.get(uuid); if (info != null) { info.marker.remove(); info.circle.remove(); geofenceInfoMap.remove(uuid); geofenceHolder.removeGeofence(fence); } } /** * Create a bitmap inside which the name of the geofence is drawn. * There is no other way to put text in a marker, as the Google map API does not provide any method for this. * @param fence the fence whose name to retrieve. * @return the generated bitmap with the geofence name inside. */ Bitmap getOrCreateBitmap(MFGeofence fence, GeofenceInfo info) { String key = "geofence.icon." + fence.getCode(); Bitmap bitmap = GenericCache.getInstance().get(key); if ((bitmap == null) || !fence.getName().equals(info.name)) { IconGenerator gen = new IconGenerator(this); gen.setColor(0x80C0C0FF); bitmap = gen.makeIcon(fence.getName()); // cache the bitmap for later reuse instead of re-generating it GenericCache.getInstance().put(key, bitmap); } return bitmap; } public GeofenceHolder getGeofenceHolder() { return geofenceHolder; } GeofenceInfo getGeofenceInfoForMarker(Marker marker) { for (Map.Entry<String, GeofenceInfo> entry : geofenceInfoMap.entrySet()) { GeofenceInfo info = entry.getValue(); if (marker.equals(info.marker)) { return info; } } return null; } void initGeofences() { try { final List<MFGeofence> fences = DemoUtils.getAllGeofencesFromDB(); log.debug("initGeofences() " + (fences == null ? 0 : fences.size()) + " fences in local DB"); geofenceHolder.clearFences(); geofenceHolder.addFences(fences); runOnUiThread(new Runnable() { @Override public void run() { try { for (MFGeofence g : fences) { boolean active = false; if (currentLocation != null) { Location loc = new Location(LocationManager.NETWORK_PROVIDER); loc.setLatitude(g.getLatitude()); loc.setLongitude(g.getLongitude()); loc.setTime(System.currentTimeMillis()); active = loc.distanceTo(currentLocation) <= g.getRadius(); } refreshGeofenceInfo(g, active); } } catch (Exception e) { log.error(e.getMessage(), e); } } }); } catch (Exception e) { log.error(e.getMessage(), e); } } @Override public boolean onOptionsItemSelected(final MenuItem item) { switch (item.getItemId()) { case R.id.action_tracking: trackingEnabled = !trackingEnabled; log.debug("tracking is now " + (trackingEnabled ? "enabled" : "disabled")); settings.putBoolean(TRACKING_ENABLED_KEY, trackingEnabled).commit(); item.setIcon(trackingEnabled ? R.mipmap.on : R.mipmap.off); log.debug(String.format("onOptionsItemSelected() tracking is now %s", trackingEnabled ? "enabled" : "disabled")); break; case R.id.action_mail_log: DemoUtils.sendLogByMail(this); break; } return super.onOptionsItemSelected(item); } @Override public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.menu, menu); MenuItem item = menu.findItem(R.id.action_tracking); item.setIcon(trackingEnabled ? R.mipmap.on : R.mipmap.off); return true; } void switchMode() { if (mapMode == MODE_NORMAL) { mapMode = MODE_EDIT; mapCrossHair.setAlpha(1.0f); addFenceButton.setCompoundDrawablesRelativeWithIntrinsicBounds(android.R.drawable.ic_menu_edit, 0, 0, 0); if (currentMarker != null) { currentMarker.remove(); currentMarker = null; } } else if (mapMode == MODE_EDIT) { mapMode = MODE_NORMAL; mapCrossHair.setAlpha(0.0f); addFenceButton.setCompoundDrawablesRelativeWithIntrinsicBounds(android.R.drawable.ic_input_add, 0, 0, 0); googleMap.setOnCameraChangeListener(null); EditGeofenceDialog dialog = new EditGeofenceDialog(); dialog.customInit(MapsActivity.this, editedInfo == null ? EditGeofenceDialog.MODE_NEW : EditGeofenceDialog.MODE_UPDATE_DELETE, editedInfo); dialog.show(getFragmentManager(), "geofences"); } } /** * Basic data structure holding the objects displayed on the map for an active geofence. */ static class GeofenceInfo { Circle circle; Marker marker; boolean active = false; String uuid; String name; } }