Reputation: 41
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
Reputation: 1147
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
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
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