Activity Recognition in Android
In this article, we are going to learn about an API called the Activity Recognition Transition API or Transition API which is intended for detecting the activity of a particular user such as driving, walking or running, etc. There are many applications that use this Activity Recognition Transition API. For example, a kilometer finder app starts running when you start driving a car or a bike and stops when you stop driving. Other examples of this Activity Recognition can be any Health and Fitness app that determines how many meters or kilometers you are running or walking on a particular day and after that, you can find the calories burnt on that day and much more.
Here we are going to make an application that will detect the user’s activity like still, running, walking, driving, or something else. A sample screenshot image is given below to get an idea about what we are going to do in this article.
Step by Step Implementation
Step 1: Create a New Project
To create a new project in Android Studio please refer to How to Create/Start a New Project in Android Studio. Note that select Kotlin as the programming language.
Step 2: Add dependencies
// Google Play Service’s Location
implementation ‘com.google.android.gms:play-services-location:18.0.0’
implementation “androidx.preference:preference-ktx:1.1.1”
// Timber
implementation ‘com.jakewharton.timber:timber:4.7.1’
// Notification Library
implementation “io.karn:notify:1.3.0”
// Easy Permissions
implementation ‘pub.devrel:easypermissions:3.0.0’
// Material Design
implementation ‘com.google.android.material:material:1.5.0-alpha02’
Step 3: Add a reference to the maven repository in the top-level build.gradle
allprojects {
repositories {
…
// For Notify library
maven { url ‘https://jitpack.io’ }
}
}
Step 4: Working with AndroidMenifest.xml
Make sure to add below permissions
<!– Required for 28 and below. –>
<uses-permission android:name=”com.google.android.gms.permission.ACTIVITY_RECOGNITION” />
<!– Required for 29+. –>
<uses-permission android:name=”android.permission.ACTIVITY_RECOGNITION” />
<application >
….
<activity >
….
</activity>
<receiver
android:name=”.ActivityTransitionReceiver”
android:exported=”false”
android:permission=”com.google.android.gms.permission.ACTIVITY_RECOGNITION”>
<intent-filter>
<action android:name=”action.TRANSITIONS_DATA” />
</intent-filter>
</receiver>
</application>
Step 5: Adding the UI of the application.
Navigate to the app > res > layout > activity_main.xml and add the below code to that file. Below is the code for the activity_main.xml file.
XML
<? xml version = "1.0" encoding = "utf-8" ?> < androidx.constraintlayout.widget.ConstraintLayout xmlns:android = "http://schemas.android.com/apk/res/android" xmlns:app = "http://schemas.android.com/apk/res-auto" xmlns:tools = "http://schemas.android.com/tools" android:layout_width = "match_parent" android:layout_height = "match_parent" tools:context = ".MainActivity" > < com.google.android.material.switchmaterial.SwitchMaterial android:id = "@+id/switchActivityTransition" android:layout_width = "wrap_content" android:layout_height = "wrap_content" android:text = "Activity Transition" app:layout_constraintBottom_toBottomOf = "parent" app:layout_constraintEnd_toEndOf = "parent" app:layout_constraintStart_toStartOf = "parent" app:layout_constraintTop_toTopOf = "parent" /> </ androidx.constraintlayout.widget.ConstraintLayout > |
Step 6: Create Constant.kt class and write all the required constants here
Kotlin
// declare all required constants at one place object Constants { const val REQUEST_CODE_ACTIVITY_TRANSITION = 123 const val REQUEST_CODE_INTENT_ACTIVITY_TRANSITION = 122 const val ACTIVITY_TRANSITION_NOTIFICATION_ID = 111 const val ACTIVITY_TRANSITION_STORAGE = "ACTIVITY_TRANSITION_STORAGE" } |
In Android, we have the Activity Recognition Client that wakes up your device at a regular interval and afterward gathers the information from the gadget’s sensor and after that this gathered information will be utilized to decide the Activities with the assistance of some Machine Learning calculation. You should simply utilize the Activity Recognition Client and the API will wrap up for you no compelling reason to learn ML.
Following are the activities that can be detected by the Activity Recognition Client:
- STILL: When the mobile device will be still when the user is either sitting at someplace or the mobile device is having no motion, then the Activity Recognition Client will detect the STILL activity.
- ON_FOOT: When the mobile device is moving at a normal speed i.e. the user carrying the mobile device is either walking or running then the Activity Recognition Client will detect the ON_FOOT activity.
- WALKING: This is a sub-activity of the ON_FOOT activity which is detected by the Activity Recognition Client when the user carrying the mobile device is walking.
- RUNNING: This is also a sub-activity of ON_FOOT activity which is detected by the Activity Recognition Client when the user carrying the mobile device is running.
- IN_VEHICLE: This activity is detected when the mobile device is in the bus or car or some other kind of vehicle or the user holding the mobile device is present in the vehicle.
- ON_BICYCLE: When the device is on the bicycle or the user carrying the mobile is on a bicycle then this activity will be detected.
- TILTING: When the mobile device is being lifted and is having some angle with the flat surface then the Activity Recognition Client will detect this activity.
- UNKNOWN: The Activity Recognition Client will show this result when the device is unable to detect any activity on the mobile device.
Step 7: Create ActivityTransitionsUtil.kt class that will contain a list of activities types and transition types
To start receiving notifications about activity transitions we must build an ActivityTransitionRequest object that determines the type of activity and transition. For this, we must create a list of ActivityTransition objects and pass it to the ActivityTransitionRequest class. Below is the complete code of the ActivityTransitionsUtil.kt class. Comments are added inside the code to understand each line of the code.
Kotlin
import android.Manifest import android.content.Context import android.os.Build import androidx.annotation.RequiresApi import com.google.android.gms.location.ActivityTransition import com.google.android.gms.location.ActivityTransitionRequest import com.google.android.gms.location.DetectedActivity import pub.devrel.easypermissions.EasyPermissions object ActivityTransitionsUtil { private fun getTransitions(): MutableList<ActivityTransition> { // List of activity transitions to track val transitions = mutableListOf<ActivityTransition>() transitions += ActivityTransition.Builder() .setActivityType(DetectedActivity.IN_VEHICLE) .setActivityTransition(ActivityTransition.ACTIVITY_TRANSITION_ENTER) .build() transitions += ActivityTransition.Builder() .setActivityType(DetectedActivity.IN_VEHICLE) .setActivityTransition(ActivityTransition.ACTIVITY_TRANSITION_EXIT) .build() transitions += ActivityTransition.Builder() .setActivityType(DetectedActivity.WALKING) .setActivityTransition(ActivityTransition.ACTIVITY_TRANSITION_ENTER) .build() transitions += ActivityTransition.Builder() .setActivityType(DetectedActivity.WALKING) .setActivityTransition(ActivityTransition.ACTIVITY_TRANSITION_EXIT) .build() transitions += ActivityTransition.Builder() .setActivityType(DetectedActivity.STILL) .setActivityTransition(ActivityTransition.ACTIVITY_TRANSITION_ENTER) .build() transitions += ActivityTransition.Builder() .setActivityType(DetectedActivity.STILL) .setActivityTransition(ActivityTransition.ACTIVITY_TRANSITION_EXIT) .build() transitions += ActivityTransition.Builder() .setActivityType(DetectedActivity.RUNNING) .setActivityTransition(ActivityTransition.ACTIVITY_TRANSITION_ENTER) .build() transitions += ActivityTransition.Builder() .setActivityType(DetectedActivity.RUNNING) .setActivityTransition(ActivityTransition.ACTIVITY_TRANSITION_EXIT) .build() return transitions } // pass the list of ActivityTransition objects to the ActivityTransitionRequest class fun getActivityTransitionRequest() = ActivityTransitionRequest(getTransitions()) // ask to allow permissions @RequiresApi (Build.VERSION_CODES.Q) fun hasActivityTransitionPermissions(context: Context): Boolean = EasyPermissions.hasPermissions( context, Manifest.permission.ACTIVITY_RECOGNITION ) // types of activities fun toActivityString(activity: Int): String { return when (activity) { DetectedActivity.STILL -> "STILL" DetectedActivity.WALKING -> "WALKING" DetectedActivity.IN_VEHICLE -> "IN VEHICLE" DetectedActivity.RUNNING -> "RUNNING" else -> "UNKNOWN" } } // type of transitions fun toTransitionType(transitionType: Int): String { return when (transitionType) { ActivityTransition.ACTIVITY_TRANSITION_ENTER -> "ENTER" ActivityTransition.ACTIVITY_TRANSITION_EXIT -> "EXIT" else -> "UNKNOWN" } } } |
Step 8: Create ActivityTransitionReceiver.kt class which receives the activities and notifies the user’s state
Below is the complete code of the ActivityTransitionReceiver.kt class. Comments are added inside the code to understand each line of the code.
Kotlin
import android.content.BroadcastReceiver import android.content.Context import android.content.Intent import android.widget.Toast import com.google.android.gms.location.ActivityTransitionResult import io.karn.notify.Notify import java.text.SimpleDateFormat import java.util.* class ActivityTransitionReceiver : BroadcastReceiver() { override fun onReceive(context: Context, intent: Intent) { if (ActivityTransitionResult.hasResult(intent)) { val result = ActivityTransitionResult.extractResult(intent) result?.let { result.transitionEvents.forEach { event -> // Info about activity val info = "Transition: " + ActivityTransitionsUtil.toActivityString(event.activityType) + " (" + ActivityTransitionsUtil.toTransitionType(event.transitionType) + ")" + " " + SimpleDateFormat( "HH:mm:ss" , Locale.US).format(Date()) // notification details Notify .with(context) .content { title = "Activity Detected" text = "I can see you are in ${ ActivityTransitionsUtil.toActivityString( event.activityType ) } state" } .show(id = Constants.ACTIVITY_TRANSITION_NOTIFICATION_ID) Toast.makeText(context, info, Toast.LENGTH_LONG).show() } } } } } |
Step 9: Working with MainActivity.kt.Below is the code for MainActivity.kt
Below is the complete code for the MainActivity.kt file. Comments are added inside the code to understand each line of the code.
Kotlin
import android.Manifest import android.app.PendingIntent import android.content.Intent import android.content.SharedPreferences import android.os.Build import android.os.Bundle import android.widget.Toast import androidx.annotation.RequiresApi import androidx.appcompat.app.AppCompatActivity import com.gfg.article.activityrecognition.Constants.ACTIVITY_TRANSITION_STORAGE import com.google.android.gms.location.ActivityRecognition import com.google.android.gms.location.ActivityRecognitionClient import kotlinx.android.synthetic.main.activity_main.* import pub.devrel.easypermissions.AppSettingsDialog import pub.devrel.easypermissions.EasyPermissions class MainActivity : AppCompatActivity(), EasyPermissions.PermissionCallbacks { lateinit var client: ActivityRecognitionClient lateinit var storage: SharedPreferences override fun onCreate(savedInstanceState: Bundle?) { super .onCreate(savedInstanceState) setContentView(R.layout.activity_main) // The Activity Recognition Client returns a // list of activities that a user might be doing client = ActivityRecognition.getClient( this ) // variable to check whether the user have already given the permissions storage = androidx.preference.PreferenceManager.getDefaultSharedPreferences( this ) // check switch is on/off switchActivityTransition.isChecked = getSwitchState() switchActivityTransition.setOnCheckedChangeListener { _, isChecked -> if (isChecked) { // check for devices with Android 10 (29+). if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q // check for permission && !ActivityTransitionsUtil.hasActivityTransitionPermissions( this ) ) { switchActivityTransition.isChecked = false // request for permission requestActivityTransitionPermission() } else { // when permission is already allowed requestForUpdates() } } else { saveSwitchState( false ) deregisterForUpdates() } } } // when permission is denied @RequiresApi (Build.VERSION_CODES.Q) override fun onPermissionsDenied(requestCode: Int, perms: MutableList<String>) { // permission is denied permanently if (EasyPermissions.somePermissionPermanentlyDenied( this , perms)) { AppSettingsDialog.Builder( this ).build().show() } else { requestActivityTransitionPermission() } } // after giving permission override fun onPermissionsGranted(requestCode: Int, perms: MutableList<String>) { switchActivityTransition.isChecked = true saveSwitchState( true ) requestForUpdates() } // request for permission override fun onRequestPermissionsResult( requestCode: Int, permissions: Array<out String>, grantResults: IntArray ) { super .onRequestPermissionsResult(requestCode, permissions, grantResults) EasyPermissions.onRequestPermissionsResult(requestCode, permissions, grantResults, this ) } // To register for changes we have to also supply the requestActivityTransitionUpdates() method // with the PendingIntent object that will contain an intent to the component // (i.e. IntentService, BroadcastReceiver etc.) that will receive and handle updates appropriately. private fun requestForUpdates() { client .requestActivityTransitionUpdates( ActivityTransitionsUtil.getActivityTransitionRequest(), getPendingIntent() ) .addOnSuccessListener { showToast( "successful registration" ) } .addOnFailureListener { showToast( "Unsuccessful registration" ) } } // Deregistering from updates // call the removeActivityTransitionUpdates() method // of the ActivityRecognitionClient and pass // ourPendingIntent object as a parameter private fun deregisterForUpdates() { client .removeActivityTransitionUpdates(getPendingIntent()) .addOnSuccessListener { getPendingIntent().cancel() showToast( "successful deregistration" ) } .addOnFailureListener { e: Exception -> showToast( "unsuccessful deregistration" ) } } // creates and returns the PendingIntent object which holds // an Intent to an BroadCastReceiver class private fun getPendingIntent(): PendingIntent { val intent = Intent( this , ActivityTransitionReceiver:: class .java) return PendingIntent.getBroadcast( this , Constants.REQUEST_CODE_INTENT_ACTIVITY_TRANSITION, intent, PendingIntent.FLAG_UPDATE_CURRENT ) } // requesting for permission @RequiresApi (Build.VERSION_CODES.Q) private fun requestActivityTransitionPermission() { EasyPermissions.requestPermissions( this , "You need to allow Activity Transition Permissions in order to recognize your activities" , Constants.REQUEST_CODE_ACTIVITY_TRANSITION, Manifest.permission.ACTIVITY_RECOGNITION ) } private fun showToast(message: String) { Toast.makeText( this , message, Toast.LENGTH_LONG) .show() } // save switch state private fun saveSwitchState(value: Boolean) { storage .edit() .putBoolean(ACTIVITY_TRANSITION_STORAGE, value) .apply() } // get the state of switch private fun getSwitchState() = storage.getBoolean(ACTIVITY_TRANSITION_STORAGE, false ) } |
Now, run the app
Output:
Source Code: Click Here
Contact Us