Monitoring applications are gaining in popularity, from parents wanting to track their children’s location and online activity to spouses and employers wishing to monitor their respective spouses and employees. In this article, we will deviate from the moral and ethical issues and focus on the technical aspects. Let’s take the Android sensor as an example.
The Android sensor returns values that can be divided into three main categories: movement, location and environment.
An accelerometer is a device that measures proper acceleration (or ‘g-force’) and is commonly used to detect motion (shake and tilt). In mobile monitoring, an accelerometer could be used to monitor older people who live alone. With the accelerometer monitoring falling velocity to detect whether the person has fallen down. If a fall is detected, a caretaker receives a message alert.
According to Dan Morrill (Android Compatibility and Open Source Program Manager at Google), to use the API correctly you should follow the following three rules :
- The sensor coordinate system is the same as the OpenGL coordinate system and never changes if used with the same API for natural orientation.
- Natural orientation is not always portrait.
android.view.Display.getRotation()
should always be used by apps that match sensor data to on-screen display in order to match sensor coordinates to the screen.
Let’s see how it works
We’ll use SensorEventListener
to receive notifications from the SensorManager
when sensor values change. Since we’re going to make use of a shake gesture, it’s a good idea to lock the device’s orientation.
Create a new project in your IDE and add the following to your AndroidManifest.xml
file:
android:screenOrientation="portrait"
Replace the contents of MainActivity.java
with the following:
public class MainActivity extends Activity implements SensorEventListener {
private final String TAG = MainActivity.class.getSimpleName();
private SensorManager mSensorManager;
private Sensor mAccelerometer;
private long lastTime = 0;
private float lastX, lastY, lastZ;
private static final int THRESHOLD = 600; //used to see whether a shake gesture has been detected or not.
TextView coordinates;
TextView address;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mSensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE);
mAccelerometer = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
mSensorManager.registerListener(this, mAccelerometer, SensorManager.SENSOR_DELAY_NORMAL);
Button update = (Button) findViewById(R.id.update_button);
update.setOnClickListener(new UpdateLocationClick());
coordinates = (TextView) findViewById(R.id.location_points);
address = (TextView) findViewById(R.id.location_address);
if (SApplication.LOCATION != null) {
double lat = SApplication.LOCATION.getLatitude();
double lon = SApplication.LOCATION.getLongitude();
coordinates.setText(lat + " " + lon);
Geocoder geocoder = new Geocoder(getApplicationContext(), new Locale("en"));
try {
List<Address> addresses = geocoder.getFromLocation(lat, lon, 1);
if (addresses != null && addresses.size() != 0) {
StringBuilder builder = new StringBuilder();
Address returnAddress = addresses.get(0);
for (int i = 0; i < returnAddress.getMaxAddressLineIndex(); i++) {
builder.append(returnAddress.getAddressLine(i));
builder.append(" ");
}
address.setText(builder);
address.setVisibility(View.VISIBLE);
} else {
Log.e(TAG, "Addresses null");
}
} catch (IOException e) {
Log.e(TAG, "Geocoder exception " + e);
}
} else {
coordinates.setText("No location yet");
address.setVisibility(View.INVISIBLE);
}
}
}
The system’s sensors are sensitive. When holding a device, it is constantly in motion, no matter how steady your hand is. We don’t need to collect all this data. We store the system’s current time (in milliseconds) and check if more than 100 milliseconds have passed since the last time onSensorChanged
was invoked.
Add this to MainActivity.java
inside the MainActivity
class:
@Override
public void onSensorChanged(SensorEvent event) {
Sensor sensor = event.sensor;
if (sensor.getType() == Sensor.TYPE_ACCELEROMETER) {
float x = event.values[0];
float y = event.values[1];
float z = event.values[2];
long currentTime = System.currentTimeMillis();
if ((currentTime - lastTime) > 100) {
long diffTime = (currentTime - lastTime);
lastTime = currentTime;
float speed = Math.abs(x + y + z - lastX - lastY - lastZ)/ diffTime * 10000;
if (speed > THRESHOLD) {
getRandomNumber();
}
lastX = x;
lastY = y;
lastZ = z;
}
}
}
@Override
public void onAccuracyChanged(Sensor sensor, int accuracy) {
}
It’s good practice to unregister the sensor when the application hibernates to save battery power. Again add these functions inside the MainActivity
class:
protected void onPause() {
super.onPause();
mSensorManager.unregisterListener(this);
}
protected void onResume() {
super.onResume();
mSensorManager.registerListener(this, mAccelerometer, SensorManager.SENSOR_DELAY_NORMAL);
}
private void getRandomNumber() {
Random randNumber = new Random();
int iNumber = randNumber.nextInt(100);
TextView text = (TextView)findViewById(R.id.number);
text.setText("" + iNumber);
RelativeLayout ball = (RelativeLayout) findViewById(R.id.ball);
Animation a = AnimationUtils.loadAnimation(this, R.anim.move_down_ball_first);
ball.setVisibility(View.INVISIBLE);
ball.setVisibility(View.VISIBLE);
ball.clearAnimation();
ball.startAnimation(a);
}
public class UpdateLocationClick implements View.OnClickListener {
@Override
public void onClick(View v) {
if (SApplication.LOCATION != null) {
double lat = SApplication.LOCATION.getLatitude();
double lon = SApplication.LOCATION.getLongitude();
coordinates.setText(lat + " " + lon);
Geocoder geocoder = new Geocoder(getApplicationContext(), new Locale("en"));
try {
// get address from location
List<Address> addresses = geocoder.getFromLocation(lat, lon, 1);
if (addresses != null && addresses.size() != 0) {
StringBuilder builder = new StringBuilder();
Address returnAddress = addresses.get(0);
for (int i = 0; i < returnAddress.getMaxAddressLineIndex(); i++) {
builder.append(returnAddress.getAddressLine(i));
builder.append(" ");
}
address.setText(builder);
address.setVisibility(View.VISIBLE);
} else {
Log.e(TAG, "Addresses null");
}
} catch (IOException e) {
Log.e(TAG, "Geocoder exception " + e);
}
} else {
Toast.makeText(getApplicationContext(), "Check GPS status and internet connection", Toast.LENGTH_LONG).show();
coordinates.setText("No location yet");
address.setVisibility(View.INVISIBLE);
}
}
}
Android sensor value allows us to detect the real-time GPS location of the user in the main plane surfaces. This data is sent to the person who has access to the monitoring app anytime there is Wi-Fi connection or 3G/4G network.
Unlike an accelerometer, standalone GPS units depend on satellite radio indicators. In mobile monitoring the AGPS (assisted global positioning system) is chosen over GPS. This is because these network towers are equipped with GPS receivers to increase the signal’s speed. The receivers receive information from satellites and then send it to cell phones with all necessary computation handled. As a result you receive:
- Fast Location acquisition
- Less battery use
- Indoor location acquisition
First we add some permissions to the AndroidManifest.xml
file:
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.INTERNET" />
We’ll use the background service to detect a new location. Create a new class file called LocationService.java
and add the following to it:
public class LocationService extends Service {
private static final String TAG = LocationService.class.getSimpleName();
private LocationManager mLocationManager = null;
private static final int INTERVAL = 1000; // minimum time interval between location updates (milliseconds)
private static final float DISTANCE = 10f; // minimum distance between location updates (meters)
private class LocationListener implements android.location.LocationListener {
Location mLastLocation;
public LocationListener(String provider) {
Log.e(TAG, "LocationListener " + provider);
mLastLocation = new Location(provider);
}
@Override
public void onLocationChanged(Location location) {
mLastLocation.set(location);
SApplication.LOCATION = location;
}
@Override
public void onProviderDisabled(String provider) {
}
@Override
public void onProviderEnabled(String provider) {
}
@Override
public void onStatusChanged(String provider, int status, Bundle extras) {
}
}
LocationListener[] mLocationListeners = new LocationListener[]{
new LocationListener(LocationManager.GPS_PROVIDER),
new LocationListener(LocationManager.NETWORK_PROVIDER)
};
@Override
public IBinder onBind(Intent arg0) {
return null;
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
super.onStartCommand(intent, flags, startId);
return START_STICKY;
}
}
Now we request location updates from both providers, GPS and the network. Add the following to the LocationService
class:
@Override
public void onCreate()
{
if (mLocationManager == null) {
mLocationManager = (LocationManager) getApplicationContext().getSystemService(Context.LOCATION_SERVICE);
}try {
mLocationManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, INTERVAL, DISTANCE, mLocationListeners[1]);
} catch (java.lang.SecurityException ex) {
Log.i(TAG, "fail to request location update, ignore", ex);
} catch (IllegalArgumentException ex) {
Log.d(TAG, "network provider does not exist, " + ex.getMessage());
}
try {
mLocationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, INTERVAL, DISTANCE, mLocationListeners[0]);
} catch (java.lang.SecurityException ex) {
Log.i(TAG, "fail to request location update, ignore", ex);
} catch (IllegalArgumentException ex) {
Log.d(TAG, "gps provider does not exist " + ex.getMessage());
}
}
@Override
public void onDestroy()
{
super.onDestroy();
if (mLocationManager != null) {
for (int i = 0; i < mLocationListeners.length; i++) {
try {
mLocationManager.removeUpdates(mLocationListeners[i]);
} catch (Exception ex) {
Log.i(TAG, "fail to remove location listeners, ignore", ex);
}
}
}
}
Now we define our Application class, create a new class called SApplication
and add the following to it:
public class SApplication extends Application {
private final String TAG = SApplication.class.getSimpleName();
public static Location LOCATION = null;</p>
public void onCreate() {
Intent location = new Intent(getApplicationContext(), LocationService.class);
startService(location);
}
}
To finish the app there are a lot of user interface elements required, look in the repository on GitHub to see them all and import them into your own project. To achieve this quickly, you may find it easier to replace some of the contents of your ‘res’ folder with the ‘res’ folder in the demo project.
To test the app you will need to run it on a real device, not in the emulator. Shake the phone to see the accelerometer values and touch the
update button to get your location. If you can’t get the location to work, try enabling ‘high accuracy’ mode.
This was a simple example to illustrate the potential of some of Android’s sensors, hopefully you can see the potential uses in real-world applications.
Paula Green is an author of “Who is monitoring us or how does Android sensor value works”. She has recently graduated from NYU computer science department. She is passionate about mobile app development. Right now she works as a contributor for a parental control app at Pumpic.