Mobile
Article

Using Android Sensors in Your App

By Paula Green

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.

More:
Comments
ChrisChinchilla

Have any of our readers tried integrating sensors with their apps yet?

Recommended
Sponsors
Because We Like You
Free Ebooks!

Grab SitePoint's top 10 web dev and design ebooks, completely free!

Get the latest in Mobile, once a week, for free.