Sensors Tutorial for Android: Getting Started

In this sensors tutorial, you’ll learn about different types of sensors and how to use sensor fusion (accelerometer with magnetometer) to develop a compass. By Aaqib Hussain.

Leave a rating/review
Download materials
Save for later
Share
You are currently viewing page 3 of 3 of this article. Click here to view the first page.

Handling Events in the Background

When you implemented the Service, you enabled handling sensor events in the background. However, Android enforces a lot of restrictions on background processing. In its current implementation sensor events are handled when the app goes into the background. If, say, the system or user kills the app, no events will be processed.
This is not ideal when using a compass. To handle this case, you need to start your service as a foreground service and show a persistent notification.

To do so, you’ll need to add some more code to LocatyService:

  • Keep track of when the service is backgrounded
  • Create a notification
  • Start the service as Foreground Service

Start by opening LocatyService and adding the following variable:

private var background = false

Also, update your onStartCommand as below:

override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
  intent?.let {
     // 1
     background = it.getBooleanExtra(KEY_BACKGROUND, false)
   }
  return START_STICKY
}

Here’s what this does:

  1. Gets the application state from MainActivity, which you pass when you start the service.

Next, add the following constants in LocatyService:

private val notificationActivityRequestCode = 0
private val notificationId = 1
private val notificationStopRequestCode = 2

These are the request codes you use when creating a PendingIntent. Each PendingIntent should have a unique request code.

To create a notification when the app is in the background, first import androidx.core.app.NotificationCompat, then add the following function:

private fun createNotification(direction: String, angle: Double): Notification {
  // 1
  val notificationManager =
            getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager

  if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
      val notificationChannel = NotificationChannel(
                application.packageName,
                "Notifications", NotificationManager.IMPORTANCE_DEFAULT
            )

       // Configure the notification channel.
      notificationChannel.enableLights(false)
      notificationChannel.setSound(null, null)
      notificationChannel.enableVibration(false)
      notificationChannel.vibrationPattern = longArrayOf(0L)
      notificationChannel.setShowBadge(false)
      notificationManager.createNotificationChannel(notificationChannel)
  }
  
  val notificationBuilder = NotificationCompat.Builder(baseContext, application.packageName)
  // 2
  val contentIntent = PendingIntent.getActivity(
            this, notificationActivityRequestCode,
            Intent(this, MainActivity::class.java), PendingIntent.FLAG_UPDATE_CURRENT)
  // 3
  val stopNotificationIntent = Intent(this, ActionListener::class.java)
  stopNotificationIntent.action = KEY_NOTIFICATION_STOP_ACTION
  stopNotificationIntent.putExtra(KEY_NOTIFICATION_ID, notificationId)
  val pendingStopNotificationIntent =
            PendingIntent.getBroadcast(this, notificationStopRequestCode, stopNotificationIntent, PendingIntent.FLAG_UPDATE_CURRENT)

  notificationBuilder.setAutoCancel(true)
            .setDefaults(Notification.DEFAULT_ALL)
            .setContentTitle(resources.getString(R.string.app_name))
            .setContentText("You're currently facing $direction at an angle of $angle°")
            .setWhen(System.currentTimeMillis())
            .setDefaults(0)
            .setVibrate(longArrayOf(0L))
            .setSound(null)
            .setSmallIcon(R.mipmap.ic_launcher_round)
            .setContentIntent(contentIntent)
            .addAction(R.mipmap.ic_launcher_round, getString(R.string.stop_notifications), pendingStopNotificationIntent)


 return notificationBuilder.build()
}

Here’s a breakdown of what it does:

  1. Creates a NotificationManager.
  2. Opens the main screen of the app on a notification tap.
  3. Adds an intent to stop the notifications from appearing.

Now, you’ll create a BroadcastReceiver named ActionListener. This will listen to broadcast for stop action when you tap the Stop Notifications button from the notification.

Add the code block below inside LocatyService:

class ActionListener : BroadcastReceiver() {
  override fun onReceive(context: Context?, intent: Intent?) {

   if (intent != null && intent.action != null) {
       // 1
       if (intent.action.equals(KEY_NOTIFICATION_STOP_ACTION)) {
            context?.let {
               // 2
               val notificationManager =
                            context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
               val locatyIntent = Intent(context, LocatyService::class.java)
               // 3
               context.stopService(locatyIntent)
               val notificationId = intent.getIntExtra(KEY_NOTIFICATION_ID, -1)
               if (notificationId != -1) {
                  // 4
                  notificationManager.cancel(notificationId)
               }
            }
         }
      }
   }
}

Here’s what this code does:

  1. Checks if the broadcast’s action is same as for Stop Notifications.
  2. Gets a reference to NotificationManager.
  3. Stops the service.
  4. Removes the persistent notification from the Notification Drawer.

Now, add the ActionListener to AndroidManifest:

<receiver android:name=".LocatyService$ActionListener"/>

Here, you register the ActionListener in AndroidManifest. You could have invoked the registeration/deregiteration during runtime also inside the class.

When starting a foreground service, you need to register a notification if you want the service to keep running in the background. This applies to Android version Oreo and above.
In onCreate of LocatyService, add the following:

// 1
val notification = createNotification(getString(R.string.not_available), 0.0)
// 2
startForeground(notificationId, notification)

Here’s what this code block does:

  1. Create a notification
  2. Start the service with the notification as a Foreground Service

Finally, add the following snippet to the end of updateOrientationAngles:

if (background) {
   // 1
   val notification = createNotification(direction, angle)
   startForeground(notificationId, notification)
   } else {
   // 2
   stopForeground(true)
}

Here’s what this code does:

  1. Creates and shows a notification when the app goes into the background.
  2. Hides the notification as soon as the app comes into the foreground.

That’s it! Finally, it’s time to run the app to see how the compass works.

Now, build and run.

Home screen with a working compass

Press the Home button and you’ll see the notification.

Notification showing the user's position when the app is in the background

It works!

Happy Emoji

Where to Go From Here?

Congratulations, you’ve learned a lot about sensors and have a better understanding of their ins and outs. After following this tutorial to make a real compass, you must be yearning to use sensors in one of your next apps. :]

Feel free to download the completed project using the Download Materials button at the top or bottom of this tutorial.

If you want to learn more about sensors in-depth, go to the official Android documentation.

If you have any questions or queries please feel free to post them in the comments section below. :]