Sanyam Saini
Sanyam Saini

Reputation: 41

Getting 'ActivityRecognitionResult.extractResult(intent)' as null while trying to initiate Activity Recognition in android

I've been trying to get the data from the 'ActivityRecognitionResult.extractResult(intent)' but always getting null. I'm using 'JobIntentService'. And I'm using Sampling API of the activity recognition for the continuous background data. Attaching the files and codes related to this issue.

Thank you in Advance.

Manifest File

<?xml version="1.0" encoding="utf-8"?>

<!-- Network related permissions -->
<!-- Required for making network/internet connections -->
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
<!-- Location related permissions -->
<!-- Required for location services, fencing -->
<uses-feature
    android:name="android.hardware.location"
    android:required="false" />
<uses-feature
    android:name="android.hardware.location.network"
    android:required="false" />
<uses-feature
    android:name="android.hardware.location.gps"
    android:required="false" />

<uses-permission android:name="com.google.android.gms.permission.ACTIVITY_RECOGNITION" />
<uses-permission android:name="android.permission.ACTIVITY_RECOGNITION" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>

<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_LOCATION_EXTRA_COMMANDS" />
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" /> <!-- Wifi related permissions -->
<!-- Required for wifi p2p jobs and device profiling -->
<uses-feature
    android:name="android.hardware.wifi"
    android:required="false" />

<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
<uses-permission android:name="android.permission.CHANGE_WIFI_MULTICAST_STATE" />

<application
    android:allowBackup="true"
    android:icon="@mipmap/ic_launcher"
    android:label="@string/app_name"
    android:roundIcon="@mipmap/ic_launcher_round"
    android:supportsRtl="true"
    android:theme="@style/Theme.ActivityRecognitionDemo">
    <activity android:name=".ui.TimelineActivity"/>

    <activity android:name=".ui.MainActivity">
        <intent-filter>
            <action android:name="android.intent.action.MAIN" />

            <category android:name="android.intent.category.LAUNCHER" />
        </intent-filter>
    </activity>

    <service
        android:name=".service.DetectedActivitiesIntentService"
        android:permission="android.permission.BIND_JOB_SERVICE"
        android:exported="false"/>

    <service
        android:name=".service.BackgroundDetectedActivitiesService"
        android:exported="false" />

</application>

BackgroundDetectedActivitiesService.class

    class BackgroundDetectedActivitiesService :  Service() {

        private val TAG = Constants.LOG_TAG +              BackgroundDetectedActivitiesService::class.java.simpleName

    private lateinit var mIntentService: Intent
    private lateinit var mPendingIntent: PendingIntent
    private lateinit var mActivityRecognitionClient: ActivityRecognitionClient

    private var mBinder: IBinder = LocalBinder()

    inner class LocalBinder : Binder() {
        val serverInstance: BackgroundDetectedActivitiesService
            get() = this@BackgroundDetectedActivitiesService
    }
    companion object {
        var isServiceStarted = false
        var mLocation: Location? = null
    }

    private val NOTIFICATION_CHANNEL_ID = "Foreground Activity Recognition"

    override fun onCreate() {
        super.onCreate()

        isServiceStarted = true
        val builder: NotificationCompat.Builder = NotificationCompat.Builder(this, NOTIFICATION_CHANNEL_ID)
            .setOngoing(false)
            .setContentTitle("Activity Recognition")
            .setContentText("Activity Recognition App is running in background.")
            .setSmallIcon(R.drawable.ic_inventa_logo)
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            val notificationManager: NotificationManager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager
            val notificationChannel = NotificationChannel(NOTIFICATION_CHANNEL_ID, NOTIFICATION_CHANNEL_ID, NotificationManager.IMPORTANCE_LOW)
            notificationChannel.description = NOTIFICATION_CHANNEL_ID
            notificationChannel.setSound(null, null)
            notificationManager.createNotificationChannel(notificationChannel)
            startForeground(1, builder.build())
        }



        mActivityRecognitionClient = ActivityRecognitionClient(this)
        mIntentService = Intent(this, DetectedActivitiesIntentService::class.java)
        mPendingIntent = PendingIntent.getService(this, 1, mIntentService, PendingIntent.FLAG_UPDATE_CURRENT)
        requestActivityUpdatesButtonHandler()

        Handler(mainLooper).postDelayed({
            DetectedActivitiesIntentService.enqueueWork(this, mIntentService)
        }, 3000)
    }

    override fun onBind(intent: Intent): IBinder {
        return mBinder
    }

    override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
        LocationHelper().startListeningUserLocation(
            this, object : MyLocationListener {
                override fun onLocationChanged(location: Location?) {
                    mLocation = location
                    mLocation?.let {
                        Log.d(TAG, "Lat from service : " + it.latitude)
                        Log.d(TAG, "Long from service : " + it.longitude)
                    }
                }
            })
        return START_STICKY
    }

    private fun requestActivityUpdatesButtonHandler() {
        Log.d(TAG, " requestActivityUpdatesButtonHandler Called")

        val task = mActivityRecognitionClient.requestActivityUpdates(MainActivity.DETECTION_INTERVAL_IN_MILLISECONDS, mPendingIntent)

        task.addOnSuccessListener {
            Log.d(TAG, " requestActivityUpdatesButtonHandler Success")
            //Log.d(TAG, " requestActivityUpdatesButtonHandler task : $it")
            Toast.makeText(applicationContext, "Successfully requested activity updates", Toast.LENGTH_SHORT).show()
        }

        task.addOnCompleteListener {
            Log.d(TAG, " requestActivityUpdatesButtonHandler Complete")
            //Log.d(TAG, " requestActivityUpdatesButtonHandler task : " + it.result)
            Toast.makeText(applicationContext, "Successfully requested activity updates", Toast.LENGTH_SHORT).show()
        }

        task.addOnFailureListener {
            Log.d(TAG, " requestActivityUpdatesButtonHandler Failure")
            Log.d(TAG, " requestActivityUpdatesButtonHandler Failure : ${it.localizedMessage}")
            Log.d(TAG, " requestActivityUpdatesButtonHandler Failure : ${it.cause}")
            Log.d(TAG, " requestActivityUpdatesButtonHandler Failure : ${it.message}")
            Toast.makeText(applicationContext, "Requesting activity updates failed to start", Toast.LENGTH_SHORT).show()
        }
    }

    private fun removeActivityUpdatesButtonHandler() {
        Log.d(TAG, " removeActivityUpdatesButtonHandler Called")

        val task = mActivityRecognitionClient.removeActivityUpdates(mPendingIntent)
        task.addOnSuccessListener {
            Log.d(TAG, " removeActivityUpdatesButtonHandler Success")
            Toast.makeText(applicationContext, "Removed activity updates successfully!", Toast.LENGTH_SHORT).show()
        }

        task.addOnCompleteListener {
            Log.d(TAG, " removeActivityUpdatesButtonHandler Complete")
            Toast.makeText(applicationContext, "Removed activity updates successfully!", Toast.LENGTH_SHORT).show()
        }

        task.addOnFailureListener {
            Log.d(TAG, " removeActivityUpdatesButtonHandler Failure")
            Log.d(TAG, " removeActivityUpdatesButtonHandler Failure : ${it.localizedMessage}")
            Log.d(TAG, " removeActivityUpdatesButtonHandler Failure : ${it.cause}")
            Log.d(TAG, " removeActivityUpdatesButtonHandler Failure : ${it.message}")
            Toast.makeText(applicationContext, "Failed to remove activity updates!", Toast.LENGTH_SHORT).show()
        }
    }

    override fun onDestroy() {
        super.onDestroy()
        //removeActivityUpdatesButtonHandler()
    }
}

DetectedActivitiesIntentService.class

class DetectedActivitiesIntentService: JobIntentService() {
    companion object {

        private val TAG = Constants.LOG_TAG + DetectedActivitiesIntentService::class.java.simpleName
        fun enqueueWork(context: Context, intent: Intent) {
            Log.d(TAG, " enqueueWork")
            enqueueWork(context, DetectedActivitiesIntentService::class.java, 1000, intent)
        }
    }

    override fun onCreate() {
        super.onCreate()

    }

    override fun onHandleWork(intent: Intent) {
        Log.d(TAG, " onHandleWork")
        //        val result = ActivityRecognitionResult.extractResult(intent)
        val result = ActivityRecognitionResult.extractResult(intent)

        Log.d(TAG, " hasResult : " + ActivityRecognitionResult.hasResult(intent))

        // Get the list of the probable activities associated with the current state of the
        // device. Each activity is associated with a confidence level, which is an int between
        // 0 and 100.
        if (result != null) {
            Log.d(TAG, " result not null")
            val detectedActivities = result.probableActivities as? ArrayList<*>

            for (activity in detectedActivities!!) {
                broadcastActivity(activity as DetectedActivity)
            }
        } else
            Log.d(TAG, " result null")
    }

    private fun broadcastActivity(activity: DetectedActivity) {
        Log.d(TAG, " broadcastActivity")

        val intent = Intent(MainActivity.BROADCAST_DETECTED_ACTIVITY)
        intent.putExtra("type", activity.type)
        intent.putExtra("confidence", activity.confidence)
        LocalBroadcastManager.getInstance(this).sendBroadcast(intent)
    }
}

MainActivity.class

class MainActivity : AppCompatActivity() {

    private val TAG = Constants.LOG_TAG + MainActivity::class.java.simpleName
    private lateinit var broadcastReceiver: BroadcastReceiver

    private lateinit var txtActivity: TextView
    private lateinit var txtConfidence: TextView
    private lateinit var btnStartTracking: Button
    private lateinit var btnStopTracking: Button
    private lateinit var btnTimeline: Button

    private var sessionManager:SessionManager?=null

    private var appDatabase:AppDatabase?=null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        sessionManager = SessionManager(this)
        appDatabase = DatabaseClient(this).getAppDatabase()

        txtActivity = findViewById(R.id.txt_activity)
        txtConfidence = findViewById(R.id.txt_confidence)
        btnStartTracking = findViewById(R.id.btn_start_tracking)
        btnStopTracking = findViewById(R.id.btn_stop_tracking)
        btnTimeline = findViewById(R.id.btnShowTimeline)

        btnStartTracking.setOnClickListener {
            Log.d(TAG, " btnStartTracking Clicked")
            startTracking()
        }

        btnStopTracking.setOnClickListener {
            Log.d(TAG, " btnStopTracking Clicked")
            stopTracking()
        }

        btnTimeline.setOnClickListener{
            startActivity(Intent(this, TimelineActivity::class.java))
        }

        broadcastReceiver = object : BroadcastReceiver() {
            override fun onReceive(context: Context, intent: Intent) {
                Log.d(TAG, " Login broadcastReceiver")

                if (intent.action == BROADCAST_DETECTED_ACTIVITY) {
                    val type = intent.getIntExtra("type", -1)
                    val confidence = intent.getIntExtra("confidence", 0)
                    handleUserActivity(type, confidence)

                    Log.d(TAG, " Activity Type : $type")
                    Log.d(TAG, " Activity Recognition : $confidence")
                }
            }
        }

            //startTracking()
    }

    private fun handleUserActivity(type: Int, confidence: Int) {
        var label = getString(R.string.activity_unknown)
        var typeName = getString(R.string.activity_unknown)

        when (type) {
            DetectedActivity.IN_VEHICLE -> {
                typeName = getString(R.string.activity_in_vehicle)
                label = "You are in Vehicle"
            }
            DetectedActivity.ON_BICYCLE -> {
                typeName = getString(R.string.activity_on_bicycle)
                label = "You are on Bicycle"
            }
            DetectedActivity.ON_FOOT -> {
                typeName = getString(R.string.activity_on_foot)
                label = "You are on Foot"
            }
            DetectedActivity.RUNNING -> {
                typeName = getString(R.string.activity_running)
                label = "You are Running"
            }
            DetectedActivity.STILL -> {
                typeName = getString(R.string.activity_still)
                label = "You are Still"
            }
            DetectedActivity.TILTING -> {
                typeName = getString(R.string.activity_tilting)
                label = "Your phone is Tilted"
            }
            DetectedActivity.WALKING -> {
                typeName = getString(R.string.activity_walking)
                label = "You are Walking"
            }
            DetectedActivity.UNKNOWN -> {
                typeName = getString(R.string.activity_unknown)
                label = "Unkown Activity"
            }
        }

        Log.d(TAG, "User activity: $label, Confidence: $confidence")

        if (confidence > CONFIDENCE) {
            sessionManager?.addPrefIntVal(Constants.PREF_CURRENT_ACTIVITY_TYPE, type)
            sessionManager?.addPrefStringVal(Constants.PREF_CURRENT_ACTIVITY_NAME, typeName)

            GlobalScope.launch(Dispatchers.Main) {
                databaseCall(confidence, type, typeName)
            }

            Toast.makeText(this, label, Toast.LENGTH_LONG).show()
            txtActivity.text = label
            txtConfidence.text = "Confidence: $confidence"
        }
    }

    private suspend fun databaseCall(confidence: Int, type: Int, typeName:String) {
        withContext(Dispatchers.IO) {
            val lastActivity:Activity? = appDatabase?.activityDao()?.getLastActivity()

            val activity = Activity()
            activity.startTime = System.currentTimeMillis().toString()
            activity.activityConfidence = confidence
            activity.activityType = type
            activity.activityTypeName = typeName

            when {
                lastActivity == null -> {
                    appDatabase?.activityDao()?.insertActivity(activity)

                }
                lastActivity.activityTypeName != sessionManager?.getPrefStringVal(Constants.PREF_CURRENT_ACTIVITY_NAME) -> {
                    activity.id = lastActivity.id?.plus(1)

                    appDatabase?.activityDao()?.insertActivity(activity)
                    appDatabase?.activityDao()?.updatePreviousActivityEndTime(activity.startTime!!, lastActivity.activityType!!, lastActivity.startTime!!)
                }
                else -> {

                }
            }
        }
    }

    override fun onResume() {
        super.onResume()

        LocalBroadcastManager.getInstance(this).registerReceiver(broadcastReceiver,
            IntentFilter(BROADCAST_DETECTED_ACTIVITY)
        )
    }

    override fun onPause() {
        super.onPause()

        LocalBroadcastManager.getInstance(this).unregisterReceiver(broadcastReceiver)
    }

    private fun startTracking() {
        Log.d(TAG, " startTracking called")

        if (!BackgroundDetectedActivitiesService.isServiceStarted) {
            val intent = Intent(this@MainActivity, BackgroundDetectedActivitiesService::class.java)
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
                startForegroundService(intent)
            } else {
                startService(intent)
            }
        }
    }

    private fun stopTracking() {
        Log.d(TAG, " stopTracking called")
        if (BackgroundDetectedActivitiesService.isServiceStarted) {
            val intent = Intent(this@MainActivity, BackgroundDetectedActivitiesService::class.java)
            stopService(intent)
            BackgroundDetectedActivitiesService.isServiceStarted = false
        }
    }

    companion object {

        const val BROADCAST_DETECTED_ACTIVITY = "activity_intent"

        internal const val DETECTION_INTERVAL_IN_MILLISECONDS: Long = 3000

        const val CONFIDENCE = 70
    }
}

activity_main.xml

 <?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@color/white"
    xmlns:android="http://schemas.android.com/apk/res/android">

    <Button
        android:id="@+id/btnShowTimeline"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginEnd="16dp"
        android:layout_marginTop="16dp"
        android:backgroundTint="@color/black"
        android:text="Timeline"
        android:paddingStart="16dp"
        android:paddingEnd="16dp"
        android:textAllCaps="false"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintEnd_toEndOf="parent" />

    <TextView
        android:id="@+id/txt_activity"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_margin="24dp"
        android:layout_marginStart="8dp"
        android:layout_marginLeft="8dp"
        android:layout_marginEnd="8dp"
        android:layout_marginRight="8dp"
        android:layout_marginBottom="48dp"
        android:textAllCaps="true"
        android:textColor="@color/black"
        android:textSize="18dp"
        android:textStyle="bold"
        app:layout_constraintBottom_toTopOf="@+id/txt_confidence"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent" />

    <TextView
        android:id="@+id/txt_confidence"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:layout_margin="24dp"
        android:layout_marginStart="8dp"
        android:layout_marginLeft="8dp"
        android:textAllCaps="true"
        android:textSize="14dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <Button
        android:id="@+id/btn_start_tracking"
        android:layout_width="240dp"
        android:layout_height="wrap_content"
        android:layout_marginStart="8dp"
        android:layout_marginLeft="8dp"
        android:layout_marginEnd="8dp"
        android:layout_marginRight="8dp"
        android:layout_marginBottom="8dp"
        android:text="Start Tracking"
        app:layout_constraintBottom_toTopOf="@+id/btn_stop_tracking"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent" />

    <Button
        android:id="@+id/btn_stop_tracking"
        android:layout_width="240dp"
        android:layout_height="wrap_content"
        android:layout_alignParentRight="true"
        android:layout_alignParentBottom="true"
        android:layout_marginStart="8dp"
        android:layout_marginLeft="8dp"
        android:layout_marginEnd="8dp"
        android:layout_marginRight="8dp"
        android:layout_marginBottom="8dp"
        android:text="Stop Tracking"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

App level Gradle

    plugins {
    id 'com.android.application'
    id 'kotlin-android'
    id 'kotlin-kapt'
}

android {
    compileSdkVersion 30
    buildToolsVersion "30.0.3"

    defaultConfig {
        applicationId "com.inventa.activity.recognition.demo"
        minSdkVersion 21
        targetSdkVersion 30
        versionCode 1
        versionName "1.0"

        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }
    kotlinOptions {
        jvmTarget = '1.8'
    }
}

dependencies {

    implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
    implementation 'androidx.core:core-ktx:1.6.0'
    implementation 'androidx.appcompat:appcompat:1.3.1'
    implementation 'com.google.android.material:material:1.4.0'
    implementation 'androidx.constraintlayout:constraintlayout:2.1.2'
    testImplementation 'junit:junit:4.13.2'
    androidTestImplementation 'androidx.test.ext:junit:1.1.3'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'

    //Activity recognition is dependent on play services
    implementation 'com.google.android.gms:play-services-location:18.0.0'

    //Room
    implementation "androidx.room:room-runtime:2.3.0"
    annotationProcessor "androidx.room:room-compiler:2.3.0"
    kapt "androidx.room:room-compiler:2.3.0"

    //Swipe Refresh
    implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.1.0"

    //Coroutine
    implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.9'
    implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.9"

    //Work Manager
//    implementation "androidx.work:work-runtime-ktx:2.7.1"
}

Upvotes: 4

Views: 1016

Answers (3)

Shubham Kumar Gupta
Shubham Kumar Gupta

Reputation: 1147

More info: https://developers.google.com/android/reference/com/google/android/gms/location/ActivityRecognitionClient

Beginning in API 21, activities may be received less frequently than the detectionIntervalMillis parameter if the device is in power save mode and the screen is off.

It takes much time to update the values.

This keeps calling the Google Play services connection active, so this is not a reliable option to work with.

This is the reason why it is showing null many times

Upvotes: 0

Sagar Patel
Sagar Patel

Reputation: 586

Why

Use Try-Catch block around the definition of the PendingIntent and when we register ActivityRecognitionClient.requestActivityUpdates with the same PendingIntent. If our target SDK is 31 or above, we get the error message saying we must use either PendingIntent.MUTABLE or PendingIntent.IMMUTABLE.

From Android 12, the PendingIntent must declare either the flag PendingIntent.IMMUTABLE or the flag PendingIntent.MUTABLE, and here we need PendingIntent.MUTABLE.

Ref: https://developer.android.com/reference/android/app/PendingIntent#constants_1

Hence, try after replacing your current mPendingIntent definition with the below one:

Solution


     mPendingIntent = PendingIntent.getService(this, 1, mIntentService, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.MUTABLE)

Upvotes: 1

czk
czk

Reputation: 1

I'm a little bit late, but I have run into simillar issue and what I've found is that based on this codelab you want to override onReceive function and process data here. The codelab is for Transition API but for me it works with both, for example:

        override fun onReceive(context: Context, intent: Intent) {
        if (ActivityRecognitionResult.hasResult(intent)){
            val resultRecognition = ActivityRecognitionResult.extractResult(intent)?.mostProbableActivity
            recognizedActivity.setValue(resultRecognition.toString())
        }

Upvotes: 0

Related Questions