I'm currently working on an android application to monitor the incoming and outgoing calls from a phone and register the call info into a file, and from what I've read PhoneStateListener seems to do what I need.
The thing is I need the application to run on background and I was thinking of using a service for that, but every example I've found that uses the listener declares it in the main activity, so I'm not sure if I need to create a service for it to run on background.
For a little more context, I have specific instructions that I can't create an application to "replace" the default calling app, so there's not much use in creating a GUI (I know the app needs a main activity, but it's only functionality should be starting the monitor).
The idea I have at the moment looks something like:
class CallMonitorService : Service() {
private lateinit var serviceLooper: Looper
private lateinit var serviceHandler: ServiceHandler
private lateinit var monitor: StateMonitor
private inner class ServiceHandler(looper: Looper) : Handler(looper) {
override fun handleMessage(msg: Message) {...}
}
override fun onCreate() {
Log.d(serviceName, "Created")
HandlerThread("ServiceStartArguments", Process.THREAD_PRIORITY_BACKGROUND).apply {
start()
// Get the HandlerThread's Looper and use it for our Handler
serviceLooper = looper
serviceHandler = ServiceHandler(looper)
val manager = getSystemService(TELEPHONY_SERVICE) as TelephonyManager
manager.listen(StateMonitor(), PhoneStateListener.LISTEN_CALL_STATE | ...)
}
}
...
}
class StateMonitor : PhoneStateListener() {
// Handler methods
}
So, as a summary, I need the PhoneStateListener to be running at all moments, without the need of an app to be running on foreground.
Should I create a service to run the listener on background or the listener runs on background by itself?
You need to move your listener into Service that will be running standalone. The service is already in "background", so you don't need to create extra thread. Moreover, from what you have posted there is no code that is blocking code, all your events will be sent in callback manner.
Ok, so I've solved the issue.
The short answer is no, the PhoneStateListener doesn't run in background.
By default the listener runs only when the application is in foreground for what I've seen.
Maybe there's a way to run it in a service but I couldn't get it to work.
Instead I solved the problem with a BroadcastReceiver.
So, for the solution
The CallStateMonitor stayed the same as it was before, I just moved it from where it was.
For the service, as I said, I replaced it for a broadcast receiver:
class CallBroadcastReceiver : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
val telephonyManager = context?.getSystemService(TELEPHONY_SERVICE) as TelephonyManager
val monitor = StateMonitor()
telephonyManager.listen(monitor, LISTEN_CALL_STATE)
}
private val tag = "STATE_MONITOR"
private inner class StateMonitor : PhoneStateListener() {
override fun onCallStateChanged(state: Int, phoneNumber: String?) {
when (state) {
TelephonyManager.CALL_STATE_IDLE -> Log.d(tag, "IDLE")
TelephonyManager.CALL_STATE_OFFHOOK -> Log.d(tag, "OFF-HOOK")
TelephonyManager.CALL_STATE_RINGING -> Log.d(tag, "RINGING")
}
}
}
}
The rest is just starting the receiver from the main activity:
class CallMonitorActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
registerReceiver(CallBroadcastReceiver(), IntentFilter(ACTION_CONFIGURATION_CHANGED))
Log.d(ACTIVITY_TAG, "Registered call receiver.")
}
}
And that's it, hope it can help someone else ^-^
Related
I have a Repository(context: Context) class (accepts a context) that must be singleton.
In normal cases it is easy to do. But in my app, I have a Foreground Service that will be running even when the app is removed from the recent apps.
And I have to use the Repository object inside this Foreground Service and as well as inside other Fragments in the app.
What is the best way to make the Repository singleton?
Currently I am using dagger-hilt to inject the Repository inside the Service class. And I am not sure if it is the right way to do it.
Here are the code samples:
MainApplication.kt
#HiltAndroidApp
class MainApplication: Application() {}
HiltModule.kt
#Module
#InstallIn(SingletonComponent::class)
object HiltModule {
#Singleton
#Provides
fun getDataStore(#ApplicationContext mContext: Context) = Repository(mContext)
}
ForegroundService.kt
#AndroidEntryPoint
class ForegroundService : Service() {
#Inject
lateinit var dataRepo: Repository
}
I don't know anything about Hilt, but you can use a bound service if you're okay with foregoing constructor injection. You could setup a binding between your activity and the foreground service, and then pass in your shared Repository instance once the service is live and has registered itself. As I understand it, this is the recommended way to communicate between an activity and service.
I think this will also help with the lifecycle issue you bring up in the comments. I'm using this approach on a foreground service that has a different lifecycle than my MainActivity, the activity gets destroyed and the service keeps running in the background, no problems. It also starts back up and syncs with the foreground process without issue. Hilt may be inadvertently causing lifecycle issues.
Edit: Just came across this comment
Common Android DI solutions do not work across processes. If you
elected to have 2+ processes (e.g., UI process and foreground service
process), you cannot inject the same object into both processes using,
say, Hilt.
class MainActivity : AppCompatActivity(), ForegroundServiceConnection {
private lateinit var mService: ForegroundService
private var mBound: Boolean = false
private val mConnection = object : ServiceConnection {
override fun onServiceConnected(className: ComponentName, service: IBinder) {
Log.d("SERVICE CONNECTED", "FOREGROUND RUNNING")
val binder = service as ForegroundService.ForegroundServiceBinder
mService = binder.getService()
mBound = true
mService.setCallbacks(this#MainActivity)
}
override fun onServiceDisconnected(arg0: ComponentName){
mBound = false
}
}
}
You will also need to implement the binding in the service:
class ForegroundService: LifecycleService() {
private val binder = ForegroundServiceBinder()
inner class ForegroundServiceBinder : Binder() {
fun getService(): ForegroundService = this#ForegroundService
}
override fun onBind(intent: Intent): IBinder {
return binder
}
private lateinit var serviceConnection: ForegroundServiceConnection
}
Next, create an interface that defines the methods you want available to the foreground service, like this:
interface ForegroundServiceConnection {
fun getRepositorySingleton () : RepositoryClass
}
Now, go back to the activity you added the binding to, and implement the interface methods.
fun getRepositorySingleton () : RepositoryClass {
return repo
}
Inside the foreground service, create the setCallbacks method that will receive the activity/app as an argument, and hold a reference to it, like this:
fun setCallbacks(app: MainActivity) {
serviceConnection = app
// Now you have access to the MainActivity
val repo = serviceConnection.getRepositorySingleton()
// Done
}
Remember to check for null values if you attempt to use the serviceConnection outside of this block, it may not have been created yet.
Enjoy! :)
The following code gives me error as registering occurs after onResume:
class TempActivity: AppCompatActivity(){
private lateinit var binding: ActivityTempBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityTempBinding.inflate(layoutInflater)
setContentView(binding.root)
binding.tempBtn.setOnClickListener {
val a = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
//SomeCode
}
a.launch(
//SomeIntent
)
}
}
However, if I use activityResultRegistry, I am not getting any errors. The code is
class TempActivity: AppCompatActivity(){
private lateinit var binding: ActivityTempBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityTempBinding.inflate(layoutInflater)
setContentView(binding.root)
binding.tempBtn.setOnClickListener {
val a = activityResultRegistry.register("key", ActivityResultContracts.StartActivityForResult()){
// SomeCode
}
a.launch(
//Some Intent
)
}
}
The latter code run without any problem and launches the corresponding intent. I just want to know how safe is latter one and is there any unwanted behaviors I should be aware of?
It gives you an error because you are registering the contract conditionally after the Activity is well into its lifecycle.
The guide says:
You must always call registerForActivityResult() in the same order for each creation of your fragment or activity to ensure that the inflight results are delivered to the correct callback.
It's clear that if you register something after the Activity is created and it only happens when a condition (click event in this case) is met, the order of registration cannot be ensured.
A better solution would be to register the contract before the Activity is created and just call launch() when you need it. The guide, once again, says it is completely safe:
registerForActivityResult() is safe to call before your fragment or activity is created, allowing it to be used directly when declaring member variables for the returned ActivityResultLauncher instances.
So in your case, the Activity would look like this:
class TempActivity: AppCompatActivity() {
private lateinit var binding: ActivityTempBinding
// registering the contract here
private val a = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
//SomeCode
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityTempBinding.inflate(layoutInflater)
setContentView(binding.root)
binding.tempBtn.setOnClickListener {
// launching the registered contract
a.launch(
//SomeIntent
)
}
}
}
Further explanation:
The registerForActivityResult() is a convenience method that internally calls the registry's register method with an automatically created key. The key is derived from an internal AtomicInteger that is retrieved and incremented every time you call registerForActivityResult(). Since this key is used to look up the callback that will handle the result, every call to the registerForActivityResult must be in the same order, otherwise it might happen that you once call it in the order of A (key=0), B (key=1) but then you call it B (key=0), A (key=1), or not even call the register method for one of the contracts (this is exactly what happens when you register in the OnClickListener).
In your specific case if the Activity gets recreated while you're waiting for the launched contract to return (for example, configuration change happens or the system simply kills the app), the callback will be removed from the registry (the key remains there though), meaning that it will not be called with the results.
So, to summarize the whole thing: you can (should) safely register any contract as a member field in your Activity or in the onCreate(...), and you should never register a contract on-the-fly (a.k.a. conditionally). Registering the contract will do nothing special, the real deal happens when you launch it.
I want to create a Service that makes a network operation but i want it to run as long as an activity is open. So i want to bind it in the activity's lifecycle. If the user navigates to another activity and back i want it to restart. If the screen goes off and the user reopens it i want it to start again if its not possible to keep it
class PushService: Service() {
override fun onBind(intent: Intent?): IBinder? {
return null
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
// ToDo Create the request that i want
}
}
So i have to start and stop the service in the onResume and onStop of the Activity?
override fun onResume() {
super.onResume()
Intent(this, PushService::class.java).also { intent ->
startService(intent)
}
}
override fun onStop() {
super.onStop()
stopService(Intent(this, PushService::class.java))
}
Im not sure how to do that. Does anybody know the correct way?
Perhaps it would be a good idea to just create the proccess that i want inside the ViewModel instead of start a Service for it?
You are mostly doing it correctly, except you should either be using onResume/onPause or onStart/onStop, not mixing the two pairs. onStart and onStop are only called when your activity is going out of view entirely. So in your example, if a dialog from another app appeared in front of yours, onStop would not get called, but onResume would get called so your already started service will get multiple onStartCommand calls.
However, the whole point of Services is to run operations that continue when your app is not visible. If you're not doing that, it would be simpler to write your own class (maybe that implements LifecycleObserver or borrows lifecycleScope from the Activity) to handle the background work. Then you wouldn't have to deal with registering it in the manifest and handling intents.
Example of a LifecycleObserver:
// lifecycle is a property of AppCompatActivity. You can instantiate this class
// from your activity.onCreate()
class MyNeworkTaskManager(lifecycle: Lifecycle): LifecycleObserver, CoroutineScope by lifecycle.coroutineScope {
init {
lifecycle.addObserver(this)
}
#OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
private fun onResume() {
startMyRequest()
}
#OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
private fun onPause() {
pauseOrCancelMyRequest()
}
// Alternatively, if you want to expose suspend functions so your activity can request
// and respond to data in a coroutine without callbacks:
suspend fun getMyData(args: String): MyData {
val results = someNetworkRequestSuspendFunction(args)
return MyData(results)
}
// Or if you want to use coroutines for your network request, but still want
// your activity to use callbacks so it doesn't have to use coroutines to call
// these functions:
fun getMyDataAsync(args: String, callback: (MyData) -> Unit) = launch {
val results = someNetworkRequestSuspendFunction(args)
callback(MyData(results))
}
}
I don't do much with networking myself. But whatever library you're using, you can usually convert callbacks to coroutines using suspendCancellableCoroutine. There are tutorials for that you can look up.
I have an Inherited class which all of my Activities inherit. In that Base/Parent Activity class I would like to create a Handler and Runnable Object that runs every 5 seconds in all Child Activities without having to add code in each Child Activity. I have it working, where the Runnable Object is in the Inherited Activity but I can't seem to get around having to add code for the Handler in each Child Activity.
open class BaseActivity : AppCompatActivity(), NavigationView.OnNavigationItemSelectedListener {
val handlerBaseActivity = Handler()
val runnable = object : Runnable {
override fun run() {
Log.d("icepts","runnable from base...")
}
}
class MainActivity : BaseActivity () {
val handlerLocalActivity = Handler()
override fun onCreate(savedInstanceState: Bundle?) {
...
handlerLocalActivity.postDelayed(runnable, 100)
}
override fun onPause() {
super.onPause()
handlerBaseActivity.removeCallbacks(runnable)
}
Anyone have any ideas that they'd be interested in sharing? Thanks so much!
You can use Activity's lifecycle in the BaseActivity as well so that every child Activity does exactly the same work in their lifecycle callback. You need to post your Runable in the onResume() or onCreate() of the BaseActivity and remove it in onPause() or onDestroy(). Please see the example below,
BaseActivity
class BaseActivity : AppCompatActivity(), NavigationView.OnNavigationItemSelectedListener {
val handlerBaseActivity = Handler()
val runnable = object : Runnable {
override fun run() {
Log.d("icepts","runnable from base...")
// TODO perform your repetitive operation
// call the handler again after 5 sec to perform above operation again
handlerBaseActivity.postDelayed(this, 5000)
}
}
override fun onResume() {
super.onResume()
handlerBaseActivity.postDelayed(runnable, 100)
}
override fun onPause() {
super.onPause()
handlerBaseActivity.removeCallbacks(runnable)
}
}
After that, you do not have to call postDelayed() function manually in each Activity that inherits BaseActivity. So, to make MainActivity do the same operation that is written in run() function in BaseActivity you just simply inherit BaseActivity.
class MainActivity : BaseActivity () {
// no need to declare local handler
override fun onCreate(savedInstanceState: Bundle?) {
...
// No need to call postDelayed() of localHandler as it's already called in BaseActiviy
}
// No need to remove callbacks in onPause() as it's already defined in BaseActivity
}
Update
I want to clear your confusion of the following comment,
If I put the removeCallBacks in the onPause of the BaseActivity then
the runnable wouldn't run at all because the onPause of the
BaseActivity gets executed before the Child Activity starts.
If you also have BaseActivity registered in the AndroidMannifest and it runs as an independent Activity, removing the runnable from it will not prevent any child from executing their runnable at all. Basically, all the childs will have their own instance of runnables and handlers that will be tied to their own lifecycles.
You can use a Thread to perform this task. I am aware that in Kotlin you have Coroutines which are cheaper to instantiate and easier to manage, so you might want to go that path. Make sure you create your handler outside of this thread, that way when you perform UI changes you don't get errors, however, if you want to perform background tasks then don't use a handler at all.
You can keep a reference to this thread so that you can pause it, by setting running to false and interrupting it, then setting it to null, for good measure, when you completely stop using it.
fun loop() {
handler.post(runnable)
//or
runOnUIThread(runnable)
//background task
}
Thread(Runnable{
while(running){
loop()
try{
Thread.sleep(1000 * 5)
}catch(e: InterruptedException){
//some weird stuff happened
}
}
}).start()
I am new with Kotlin and little bit stack with intentService. Manifest shows me an error that my service doesn't contain default constructor, but inside service it looks ok and there are no errors.
Here is my intentService:
class MyService : IntentService {
constructor(name:String?) : super(name) {
}
override fun onCreate() {
super.onCreate()
}
override fun onHandleIntent(intent: Intent?) {
}
}
I was also try another variant:
class MyService(name: String?) : IntentService(name) {
but when I try to run this service I still get an error:
java.lang.Class<com.test.test.MyService> has no zero argument constructor
Any ideas how to fix default constructor in Kotlin?
Thanks!
As explained here your service class needs to have parameterless consturctor. Change your implementation to in example:
class MyService : IntentService("MyService") {
override fun onCreate() {
super.onCreate()
}
override fun onHandleIntent(intent: Intent?) {
}
}
The Android documentation on IntentService states that this name is only used for debugging:
name String: Used to name the worker thread, important only for debugging.
While not stated explicitly, on the mentioned documentation page, the framework needs to be able to instantiate your service class and expects there will be a parameterless constructor.