dimanche 22 juin 2014

Estimotes and monitoring, a practical example



In the last article, we saw what an estimote is, and how it works.
It's time, my friends, to get our hands dirty.

The main goal of this article is to esplain how to monitor if we get close to an estimote beacon in an android application. We don't especialy want the main app to be launched to do this, we just want a notification to pop if we are close to a beacon. No error handling here : that's not our point.
To do this, we will create a receiver, a service, and launch the beaconManager service, provided by the estimote api when required.

The sequence will be like this :
- When bluetooth is turned on, for example when the system boots or when the user decides to turn it on, then we start a service, which will start the monitoring activity.

- When bluetooth is turned off, then we stop monitoring estimote beacons, and stop the application service.

Step 1 : Reacting on bluetooth status changes.
First, add the following permissions to your AndroidManifest.xml file :
<uses-permission android:name="android.permission.BLUETOOTH"/>
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
<uses-feature android:name="android.hardware.bluetooth_le" android:required="true"/>
Then, in the application node, add the following :

<application
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme">
    <receiver android:name=".EstimoteReceiver" >
        <intent-filter>
            <action android:name="android.bluetooth.adapter.action.STATE_CHANGED" />
        </intent-filter>
    </receiver>
</application>
          
This will tell the Android system that we intend to launch a receiver, which class name is "EstimoteService", and which is expected to receive only system broadcasted events related to the bluethooth status changes (when bluetooth is turned on or off).

Step 2 : Create the receiver.
The code is pretty simple and self explainable :

package com.estimote.notificationstest;

import android.bluetooth.BluetoothAdapter;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;

public class EstimoteReceiver extends BroadcastReceiver {
 private Intent estimoteServiceIntent;

 // Method called when bluetooth is turned on or off.
 @Override
 public void onReceive(Context context, Intent intent) {
  final String action = intent.getAction();
  if (action.equals(BluetoothAdapter.ACTION_STATE_CHANGED)) {
   final int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE,
     BluetoothAdapter.ERROR);
   switch (state) {
   case BluetoothAdapter.STATE_TURNING_OFF:
    // When bluetooth is turning off, lets stop estimotes ranging
    if (estimoteServiceIntent != null) {
     context.stopService(estimoteServiceIntent);
     estimoteServiceIntent = null;
    }
    break;
   case BluetoothAdapter.STATE_ON:
    // When bluethooth is turned on, let's start estimotes monitoring
    if (estimoteServiceIntent == null) {
     estimoteServiceIntent = new Intent(context,
       EstimoteService.class);
     context.startService(estimoteServiceIntent);
    }
    break;
   }
  }
 }
}

Step 3 : Let's create a service.
We need to create a service, because we just can't launch the estimote monitoring from the receiver. The receiver's phylosophy is to perform quick tasks in reaction to broadcasted events. Tasks running for more than 10 seconds in a receiver are automatically killed by android itself.

package com.estimote.notificationstest;

import android.app.NotificationManager;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.os.IBinder;

public class EstimoteService extends Service {
 @Override
 public IBinder onBind(Intent arg0) {
  return null;
 }

 @Override
 public int onStartCommand(Intent intent, int flags, int startId) {
  try {
   EstimoteManager.Create((NotificationManager) this
     .getSystemService(Context.NOTIFICATION_SERVICE), this,
     intent);
  } catch (Exception e) {
  }
  return START_STICKY;
 }

 @Override
 public void onDestroy() {
  super.onDestroy();
  EstimoteManager.stop();
 }
}
Now we just need the service to be declared in our AndroidManifest.xml file :
<application />
<!-- ... -->
<service android:name=".EstimoteService" />
As you can see, we now need to go into the real thing : estimotes monitoring !

Step 4 : Let's start monitoring.

package com.estimote.notificationstest;

import java.util.List;
import java.util.concurrent.TimeUnit;

import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;

import com.estimote.sdk.Beacon;
import com.estimote.sdk.BeaconManager;
import com.estimote.sdk.Region;
import com.estimote.sdk.BeaconManager.MonitoringListener;

public class EstimoteManager {
 private static final int NOTIFICATION_ID = 123;
 private static BeaconManager beaconManager;
 private static NotificationManager notificationManager;
 public static final String EXTRAS_BEACON = "extrasBeacon";
 private static final String ESTIMOTE_PROXIMITY_UUID = "B9407F30-F5F8-466E-AFF9-25556B57FE6D";
 private static final Region ALL_ESTIMOTE_BEACONS = new Region("regionId",
   ESTIMOTE_PROXIMITY_UUID, null, null);

 private static Context currentContext;

 // Create everything we need to monitor the beacons
 public static void Create(NotificationManager notificationMngr,
   Context context, final Intent i) {
  try {
   notificationManager = notificationMngr;
   currentContext = context;

   // Create a beacon manager
   beaconManager = new BeaconManager(currentContext);

   // We want the beacons heartbeat to be set at one second.
   beaconManager.setBackgroundScanPeriod(TimeUnit.SECONDS.toMillis(1),
     0);

   // Method called when a beacon gets...
   beaconManager.setMonitoringListener(new MonitoringListener() {
    // ... close to us.
    @Override
    public void onEnteredRegion(Region region, List beacons) {
     postNotificationIntent("Estimote testing",
       "I have found an estimote !!!", i);
    }

    // ... far away from us.
    @Override
    public void onExitedRegion(Region region) {
     postNotificationIntent("Estimote testing",
       "I have lost my estimote !!!", i);
    }
   });
   
   // Connect to the beacon manager...
   beaconManager.connect(new BeaconManager.ServiceReadyCallback() {
    @Override
    public void onServiceReady() {
     try {
      // ... and start the monitoring
      beaconManager.startMonitoring(ALL_ESTIMOTE_BEACONS);
     } catch (Exception e) {
     }
    }
   });
  } catch (Exception e) {
  }
 }

 // Pops a notification in the task bar
 public static void postNotificationIntent(String title, String msg, Intent i) {
  i.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
  PendingIntent pendingIntent = PendingIntent.getActivities(
    currentContext, 0, new Intent[] { i },
    PendingIntent.FLAG_UPDATE_CURRENT);
  Notification notification = new Notification.Builder(currentContext)
    .setSmallIcon(R.drawable.ic_launcher).setContentTitle(title)
    .setContentText(msg).setAutoCancel(true)
    .setContentIntent(pendingIntent).build();
  notification.defaults |= Notification.DEFAULT_SOUND;
  notification.defaults |= Notification.DEFAULT_LIGHTS;
  notificationManager.notify(NOTIFICATION_ID, notification);
 }

 // Stop beacons monitoring, and closes the service
 public static void stop() {
  try {
   beaconManager.stopMonitoring(ALL_ESTIMOTE_BEACONS);
   beaconManager.disconnect();
  } catch (Exception e) {
  }
 }
}
The required xml stuff to be added to our AndroidManifest.xml file is this :
<application>
<!-- ... -->
    <service android:name="com.estimote.sdk.service.BeaconService" android:exported="false"/>
</application>

We also need this in order to use the beacons api, but we already included it earlier :
<uses-permission android:name="android.permission.BLUETOOTH"/>
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>

That's all we need !

Result is just this, when you get close to a beacon :


You can get your hands on the source code by following this link.


1 commentaire: