Skip to main content

Section II Q-3: Location APIs / Geofencing (5 Marks)

Question

a) Describe the Android Location APIs and their functionality.

OR

b) Explain the concept of geofencing in Android and its use cases


Answer A: Android Location APIs

Overview of Location APIs

Android provides comprehensive Location APIs through Google Play Services and the Android framework to help developers access device location information and build location-aware applications.

Main Location API Components

1. FusedLocationProviderClient

The primary API for location services, combining GPS, Wi-Fi, and cell tower data for optimal accuracy and battery efficiency.

package com.example.locationapi;

import android.Manifest;
import android.content.pm.PackageManager;
import android.location.Location;
import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;

import com.google.android.gms.location.FusedLocationProviderClient;
import com.google.android.gms.location.LocationCallback;
import com.google.android.gms.location.LocationRequest;
import com.google.android.gms.location.LocationResult;
import com.google.android.gms.location.LocationServices;
import com.google.android.gms.tasks.OnSuccessListener;

public class LocationActivity extends AppCompatActivity {
private static final int PERMISSION_REQUEST_CODE = 1000;
private FusedLocationProviderClient fusedLocationClient;
private LocationCallback locationCallback;
private LocationRequest locationRequest;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_location);

// Initialize location client
fusedLocationClient = LocationServices.getFusedLocationProviderClient(this);

createLocationRequest();
createLocationCallback();

if (checkLocationPermissions()) {
startLocationUpdates();
} else {
requestLocationPermissions();
}
}

private void createLocationRequest() {
locationRequest = LocationRequest.create();
locationRequest.setInterval(10000); // 10 seconds
locationRequest.setFastestInterval(5000); // 5 seconds
locationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);
locationRequest.setMaxWaitTime(15000); // 15 seconds
}

private void createLocationCallback() {
locationCallback = new LocationCallback() {
@Override
public void onLocationResult(@NonNull LocationResult locationResult) {
if (locationResult == null) {
return;
}

for (Location location : locationResult.getLocations()) {
updateLocationUI(location);
}
}
};
}

private void startLocationUpdates() {
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION)
!= PackageManager.PERMISSION_GRANTED) {
return;
}

fusedLocationClient.requestLocationUpdates(locationRequest,
locationCallback, getMainLooper());
}

private void stopLocationUpdates() {
fusedLocationClient.removeLocationUpdates(locationCallback);
}

// Get last known location
private void getLastLocation() {
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION)
!= PackageManager.PERMISSION_GRANTED) {
return;
}

fusedLocationClient.getLastLocation()
.addOnSuccessListener(this, new OnSuccessListener<Location>() {
@Override
public void onSuccess(Location location) {
if (location != null) {
updateLocationUI(location);
}
}
});
}

private void updateLocationUI(Location location) {
double latitude = location.getLatitude();
double longitude = location.getLongitude();
float accuracy = location.getAccuracy();
long timestamp = location.getTime();

// Update UI with location information
String locationText = String.format(
"Lat: %.6f\nLng: %.6f\nAccuracy: %.2f meters\nTime: %d",
latitude, longitude, accuracy, timestamp
);

// Update TextView or other UI elements
// textViewLocation.setText(locationText);
}

private boolean checkLocationPermissions() {
return ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION)
== PackageManager.PERMISSION_GRANTED;
}

private void requestLocationPermissions() {
ActivityCompat.requestPermissions(this,
new String[]{Manifest.permission.ACCESS_FINE_LOCATION},
PERMISSION_REQUEST_CODE);
}

@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
@NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);

if (requestCode == PERMISSION_REQUEST_CODE) {
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
startLocationUpdates();
}
}
}

@Override
protected void onResume() {
super.onResume();
if (checkLocationPermissions()) {
startLocationUpdates();
}
}

@Override
protected void onPause() {
super.onPause();
stopLocationUpdates();
}
}

2. LocationManager (Traditional API)

The original Android location API, still useful for certain scenarios.

import android.location.LocationManager;
import android.location.LocationListener;
import android.location.Criteria;

public class TraditionalLocationManager {
private LocationManager locationManager;
private LocationListener locationListener;

public void initializeLocationManager(Context context) {
locationManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE);

locationListener = new LocationListener() {
@Override
public void onLocationChanged(@NonNull Location location) {
// Handle location updates
handleLocationUpdate(location);
}

@Override
public void onStatusChanged(String provider, int status, Bundle extras) {
// Handle provider status changes
}

@Override
public void onProviderEnabled(@NonNull String provider) {
// Handle provider enabled
}

@Override
public void onProviderDisabled(@NonNull String provider) {
// Handle provider disabled
}
};
}

public void requestLocationUpdates() {
// Get best provider
Criteria criteria = new Criteria();
criteria.setAccuracy(Criteria.ACCURACY_FINE);
criteria.setPowerRequirement(Criteria.POWER_MEDIUM);

String bestProvider = locationManager.getBestProvider(criteria, true);

if (bestProvider != null) {
// Check permissions
if (ActivityCompat.checkSelfPermission(context,
Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED) {

locationManager.requestLocationUpdates(
bestProvider,
10000, // minimum time interval (ms)
10, // minimum distance (meters)
locationListener
);
}
}
}

private void handleLocationUpdate(Location location) {
// Process location data
double lat = location.getLatitude();
double lng = location.getLongitude();
float accuracy = location.getAccuracy();
String provider = location.getProvider();
}

public void stopLocationUpdates() {
if (locationManager != null && locationListener != null) {
locationManager.removeUpdates(locationListener);
}
}
}

Location API Features and Functionality

1. Location Requests Configuration

public class LocationRequestBuilder {

public static LocationRequest createHighAccuracyRequest() {
return LocationRequest.create()
.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY)
.setInterval(5000)
.setFastestInterval(2000);
}

public static LocationRequest createBalancedRequest() {
return LocationRequest.create()
.setPriority(LocationRequest.PRIORITY_BALANCED_POWER_ACCURACY)
.setInterval(15000)
.setFastestInterval(10000);
}

public static LocationRequest createLowPowerRequest() {
return LocationRequest.create()
.setPriority(LocationRequest.PRIORITY_LOW_POWER)
.setInterval(60000)
.setFastestInterval(30000);
}

public static LocationRequest createPassiveRequest() {
return LocationRequest.create()
.setPriority(LocationRequest.PRIORITY_NO_POWER)
.setInterval(300000); // 5 minutes
}
}

2. Geocoding and Reverse Geocoding

import android.location.Geocoder;
import android.location.Address;

public class GeocodingHelper {
private Geocoder geocoder;

public GeocodingHelper(Context context) {
geocoder = new Geocoder(context, Locale.getDefault());
}

// Convert address to coordinates
public Location getLocationFromAddress(String strAddress) {
try {
List<Address> addresses = geocoder.getFromLocationName(strAddress, 1);
if (addresses != null && !addresses.isEmpty()) {
Address address = addresses.get(0);
Location location = new Location("geocoder");
location.setLatitude(address.getLatitude());
location.setLongitude(address.getLongitude());
return location;
}
} catch (IOException e) {
e.printStackTrace();
}
return null;
}

// Convert coordinates to address
public String getAddressFromLocation(double latitude, double longitude) {
try {
List<Address> addresses = geocoder.getFromLocation(latitude, longitude, 1);
if (addresses != null && !addresses.isEmpty()) {
Address address = addresses.get(0);

StringBuilder addressString = new StringBuilder();
for (int i = 0; i <= address.getMaxAddressLineIndex(); i++) {
addressString.append(address.getAddressLine(i));
if (i < address.getMaxAddressLineIndex()) {
addressString.append(", ");
}
}
return addressString.toString();
}
} catch (IOException e) {
e.printStackTrace();
}
return "Address not found";
}
}

3. Location Settings and Availability

import com.google.android.gms.location.LocationSettingsRequest;
import com.google.android.gms.location.LocationSettingsResponse;
import com.google.android.gms.location.SettingsClient;
import com.google.android.gms.tasks.Task;

public class LocationSettingsChecker {

public void checkLocationSettings(Context context, LocationRequest locationRequest) {
LocationSettingsRequest.Builder builder = new LocationSettingsRequest.Builder()
.addLocationRequest(locationRequest)
.setAlwaysShow(true);

SettingsClient settingsClient = LocationServices.getSettingsClient(context);
Task<LocationSettingsResponse> task = settingsClient.checkLocationSettings(builder.build());

task.addOnSuccessListener(locationSettingsResponse -> {
// Location settings are satisfied
startLocationUpdates();
});

task.addOnFailureListener(exception -> {
if (exception instanceof ResolvableApiException) {
try {
// Show resolution dialog
ResolvableApiException resolvable = (ResolvableApiException) exception;
resolvable.startResolutionForResult((Activity) context, REQUEST_CHECK_SETTINGS);
} catch (IntentSender.SendIntentException sendEx) {
// Ignore the error
}
}
});
}
}

Location API Summary Table

API ComponentPurposeUse CaseBattery Impact
FusedLocationProviderClientModern location APIReal-time location trackingOptimized
LocationManagerTraditional APILegacy apps, specific providersHigher
GeocoderAddress conversionMaps, navigationMinimal
LocationSettingsRequestSettings validationUser experienceNone

Answer B: Geofencing in Android

Understanding Geofencing

Geofencing is a location-based service that triggers actions when a device enters or exits a predefined geographical area (virtual fence). It combines GPS, Wi-Fi, and cellular data to monitor location changes efficiently.

Core Concepts

1. Geofence Definition

  • Center Point: Latitude and longitude coordinates
  • Radius: Distance from center point (in meters)
  • Expiration: How long the geofence remains active
  • Transition Types: Enter, exit, or dwell events

2. Geofence Lifecycle

Create Geofence → Add to Monitoring → Trigger Events → Remove Geofence

Complete Geofencing Implementation

1. Geofence Creation and Management

package com.example.geofencing;

import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import androidx.annotation.NonNull;
import com.google.android.gms.location.Geofence;
import com.google.android.gms.location.GeofencingClient;
import com.google.android.gms.location.GeofencingRequest;
import com.google.android.gms.location.LocationServices;
import com.google.android.gms.tasks.OnFailureListener;
import com.google.android.gms.tasks.OnSuccessListener;

import java.util.ArrayList;
import java.util.List;

public class GeofenceManager {
private GeofencingClient geofencingClient;
private List<Geofence> geofenceList;
private PendingIntent geofencePendingIntent;
private Context context;

public GeofenceManager(Context context) {
this.context = context;
geofencingClient = LocationServices.getGeofencingClient(context);
geofenceList = new ArrayList<>();
}

// Create a geofence
public Geofence createGeofence(String requestId, double latitude, double longitude,
float radius, long expiration) {
return new Geofence.Builder()
.setRequestId(requestId)
.setCircularRegion(latitude, longitude, radius)
.setExpirationDuration(expiration)
.setTransitionTypes(Geofence.GEOFENCE_TRANSITION_ENTER |
Geofence.GEOFENCE_TRANSITION_EXIT |
Geofence.GEOFENCE_TRANSITION_DWELL)
.setLoiteringDelay(30000) // 30 seconds for dwell
.build();
}

// Add multiple geofences
public void addGeofences(List<GeofenceData> geofenceDataList) {
geofenceList.clear();

for (GeofenceData data : geofenceDataList) {
Geofence geofence = createGeofence(
data.getId(),
data.getLatitude(),
data.getLongitude(),
data.getRadius(),
data.getExpiration()
);
geofenceList.add(geofence);
}

startGeofenceMonitoring();
}

// Create geofencing request
private GeofencingRequest getGeofencingRequest() {
GeofencingRequest.Builder builder = new GeofencingRequest.Builder();
builder.setInitialTrigger(GeofencingRequest.INITIAL_TRIGGER_ENTER);
builder.addGeofences(geofenceList);
return builder.build();
}

// Get pending intent for geofence transitions
private PendingIntent getGeofencePendingIntent() {
if (geofencePendingIntent != null) {
return geofencePendingIntent;
}

Intent intent = new Intent(context, GeofenceBroadcastReceiver.class);
geofencePendingIntent = PendingIntent.getBroadcast(
context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE
);
return geofencePendingIntent;
}

// Start monitoring geofences
@SuppressWarnings("MissingPermission")
public void startGeofenceMonitoring() {
geofencingClient.addGeofences(getGeofencingRequest(), getGeofencePendingIntent())
.addOnSuccessListener(new OnSuccessListener<Void>() {
@Override
public void onSuccess(Void aVoid) {
// Geofences added successfully
onGeofenceAdded();
}
})
.addOnFailureListener(new OnFailureListener() {
@Override
public void onFailure(@NonNull Exception e) {
// Failed to add geofences
onGeofenceError(e.getMessage());
}
});
}

// Stop monitoring geofences
public void stopGeofenceMonitoring() {
geofencingClient.removeGeofences(getGeofencePendingIntent())
.addOnSuccessListener(new OnSuccessListener<Void>() {
@Override
public void onSuccess(Void aVoid) {
// Geofences removed successfully
onGeofenceRemoved();
}
})
.addOnFailureListener(new OnFailureListener() {
@Override
public void onFailure(@NonNull Exception e) {
// Failed to remove geofences
onGeofenceError(e.getMessage());
}
});
}

// Remove specific geofences by ID
public void removeGeofences(List<String> geofenceIds) {
geofencingClient.removeGeofences(geofenceIds)
.addOnSuccessListener(aVoid -> onGeofenceRemoved())
.addOnFailureListener(e -> onGeofenceError(e.getMessage()));
}

private void onGeofenceAdded() {
// Handle successful geofence addition
}

private void onGeofenceRemoved() {
// Handle successful geofence removal
}

private void onGeofenceError(String error) {
// Handle geofence errors
}
}

2. Geofence Data Model

public class GeofenceData {
private String id;
private String name;
private double latitude;
private double longitude;
private float radius;
private long expiration;
private boolean isActive;

public GeofenceData(String id, String name, double latitude, double longitude, float radius) {
this.id = id;
this.name = name;
this.latitude = latitude;
this.longitude = longitude;
this.radius = radius;
this.expiration = Geofence.NEVER_EXPIRE;
this.isActive = true;
}

// Getters and setters
public String getId() { return id; }
public String getName() { return name; }
public double getLatitude() { return latitude; }
public double getLongitude() { return longitude; }
public float getRadius() { return radius; }
public long getExpiration() { return expiration; }
public boolean isActive() { return isActive; }

public void setExpiration(long expiration) { this.expiration = expiration; }
public void setActive(boolean active) { isActive = active; }
}

3. Geofence Broadcast Receiver

public class GeofenceBroadcastReceiver extends BroadcastReceiver {
private static final String TAG = "GeofenceReceiver";

@Override
public void onReceive(Context context, Intent intent) {
GeofencingEvent geofencingEvent = GeofencingEvent.fromIntent(intent);

if (geofencingEvent.hasError()) {
String errorMessage = GeofenceStatusCodes.getStatusCodeString(geofencingEvent.getErrorCode());
Log.e(TAG, "Geofence error: " + errorMessage);
return;
}

// Get the transition type
int geofenceTransition = geofencingEvent.getGeofenceTransition();

// Get the triggered geofences
List<Geofence> triggeringGeofences = geofencingEvent.getTriggeringGeofences();

// Handle different transition types
switch (geofenceTransition) {
case Geofence.GEOFENCE_TRANSITION_ENTER:
handleGeofenceEnter(context, triggeringGeofences);
break;

case Geofence.GEOFENCE_TRANSITION_EXIT:
handleGeofenceExit(context, triggeringGeofences);
break;

case Geofence.GEOFENCE_TRANSITION_DWELL:
handleGeofenceDwell(context, triggeringGeofences);
break;

default:
Log.e(TAG, "Invalid transition type: " + geofenceTransition);
}
}

private void handleGeofenceEnter(Context context, List<Geofence> geofences) {
for (Geofence geofence : geofences) {
String geofenceId = geofence.getRequestId();

// Send notification
sendNotification(context, "Entered " + geofenceId,
"You have entered the geofenced area");

// Log event
GeofenceEventLogger.logEvent(context, geofenceId, "ENTER");

// Trigger custom actions
triggerEnterActions(context, geofenceId);
}
}

private void handleGeofenceExit(Context context, List<Geofence> geofences) {
for (Geofence geofence : geofences) {
String geofenceId = geofence.getRequestId();

// Send notification
sendNotification(context, "Exited " + geofenceId,
"You have left the geofenced area");

// Log event
GeofenceEventLogger.logEvent(context, geofenceId, "EXIT");

// Trigger custom actions
triggerExitActions(context, geofenceId);
}
}

private void handleGeofenceDwell(Context context, List<Geofence> geofences) {
for (Geofence geofence : geofences) {
String geofenceId = geofence.getRequestId();

// Send notification
sendNotification(context, "Dwelling in " + geofenceId,
"You have been in this area for a while");

// Log event
GeofenceEventLogger.logEvent(context, geofenceId, "DWELL");
}
}

private void sendNotification(Context context, String title, String message) {
NotificationHelper.showNotification(context, title, message);
}

private void triggerEnterActions(Context context, String geofenceId) {
// Custom actions when entering geofence
switch (geofenceId) {
case "home":
// Turn on Wi-Fi, adjust settings
break;
case "work":
// Set to silent mode
break;
case "store":
// Show shopping list
break;
}
}

private void triggerExitActions(Context context, String geofenceId) {
// Custom actions when exiting geofence
switch (geofenceId) {
case "home":
// Turn off Wi-Fi
break;
case "work":
// Restore normal mode
break;
}
}
}

4. Notification Helper

public class NotificationHelper {
private static final String CHANNEL_ID = "geofence_channel";
private static final int NOTIFICATION_ID = 1000;

public static void createNotificationChannel(Context context) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
CharSequence name = "Geofence Notifications";
String description = "Notifications for geofence events";
int importance = NotificationManager.IMPORTANCE_DEFAULT;

NotificationChannel channel = new NotificationChannel(CHANNEL_ID, name, importance);
channel.setDescription(description);

NotificationManager notificationManager = context.getSystemService(NotificationManager.class);
notificationManager.createNotificationChannel(channel);
}
}

public static void showNotification(Context context, String title, String message) {
NotificationCompat.Builder builder = new NotificationCompat.Builder(context, CHANNEL_ID)
.setSmallIcon(R.drawable.ic_location)
.setContentTitle(title)
.setContentText(message)
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
.setAutoCancel(true);

NotificationManagerCompat notificationManager = NotificationManagerCompat.from(context);
notificationManager.notify(NOTIFICATION_ID, builder.build());
}
}

Geofencing Use Cases

1. Retail and Marketing

  • Store Proximity: Send promotions when customers approach stores
  • Competitor Analysis: Track foot traffic near competitor locations
  • Customer Insights: Understand shopping patterns and preferences
public class RetailGeofenceManager {
public void setupStoreGeofences() {
List<GeofenceData> stores = Arrays.asList(
new GeofenceData("store_downtown", "Downtown Store", 40.7589, -73.9851, 100),
new GeofenceData("store_mall", "Mall Location", 40.7505, -73.9934, 150),
new GeofenceData("competitor_nearby", "Competitor Store", 40.7614, -73.9776, 50)
);

geofenceManager.addGeofences(stores);
}
}

2. Home Automation

  • Smart Home Control: Adjust temperature, lighting when arriving home
  • Security: Enable/disable alarms based on presence
  • Energy Management: Turn off devices when leaving
public class HomeAutomationGeofence {
public void handleHomeGeofence(String transition) {
switch (transition) {
case "ENTER":
// Turn on lights, adjust temperature
SmartHomeController.turnOnLights();
SmartHomeController.setTemperature(72);
SmartHomeController.disableAlarm();
break;

case "EXIT":
// Turn off devices, enable security
SmartHomeController.turnOffNonEssentialDevices();
SmartHomeController.enableAlarm();
break;
}
}
}

3. Transportation and Logistics

  • Fleet Management: Track vehicle movements and delivery zones
  • Public Transit: Provide real-time updates based on location
  • Ride Sharing: Optimize pickup and drop-off points

4. Healthcare and Safety

  • Patient Monitoring: Alert caregivers when patients leave safe zones
  • Emergency Services: Trigger alerts in dangerous areas
  • Child Safety: Monitor children's location and send alerts

5. Workplace Management

  • Attendance Tracking: Automatic check-in/check-out
  • Asset Management: Track equipment and inventory
  • Security: Control access to restricted areas

Best Practices and Considerations

1. Battery Optimization

public class BatteryOptimizedGeofencing {
// Use larger radii for better battery life
private static final float MIN_RADIUS = 100; // meters

// Limit number of active geofences
private static final int MAX_GEOFENCES = 10;

// Use appropriate expiration times
private static final long EXPIRATION_TIME = 24 * 60 * 60 * 1000; // 24 hours
}

2. Error Handling

public void handleGeofenceErrors(int errorCode) {
switch (errorCode) {
case GeofenceStatusCodes.GEOFENCE_NOT_AVAILABLE:
// Location services not available
break;
case GeofenceStatusCodes.GEOFENCE_TOO_MANY_GEOFENCES:
// Too many geofences registered
break;
case GeofenceStatusCodes.GEOFENCE_TOO_MANY_PENDING_INTENTS:
// Too many pending intents
break;
}
}

3. Privacy Considerations

  • Always request appropriate permissions
  • Inform users about location tracking
  • Provide opt-out mechanisms
  • Follow data protection regulations

Geofencing Summary

AspectDetails
Maximum Geofences100 per app
Minimum Radius100 meters (recommended)
Battery ImpactLow (when properly configured)
AccuracyTypically 100-200 meters
Permissions RequiredACCESS_FINE_LOCATION, ACCESS_BACKGROUND_LOCATION

← Back to RETEST 2024