I am trying to do some work when my screen goes off. The screen on and off function is related to the proximity sensor, but this method is not working for some samsung devices, I am currently using PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK. With this I'm able to turn the screen on and off with proximity behaviour but the problem is I'm not receiving any broadcast for screenn_on and screen_off so that I can start and stop some background work.
here's My Activity
private lateinit var sensorText : TextView
private lateinit var accuText : TextView
private lateinit var lightBtn : TextView
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.sensor_test)
sensorText = findViewById(R.id.sensorInfo)
accuText = findViewById(R.id.accuracyInfo)
lightBtn = findViewById(R.id.lightBtn)
val proxMgr = ProximityMgr(this)
proxMgr.acquire()
val receiver = object : BroadcastReceiver() {
override fun onReceive(p0: Context?, p1: Intent?) {
MediaPlayer.create(this#SensorTestAct, R.raw.btnclick).start()
// do required task here
}
}
val iFilter = IntentFilter().apply {
addAction(Intent.ACTION_SCREEN_ON)
addAction(Intent.ACTION_SCREEN_OFF)
}
registerReceiver(receiver, iFilter)
}
#SuppressLint("InvalidWakeLockTag")
class ProximityMgr(context: Context) {
private val powerManager: PowerManager = (context.getSystemService(POWER_SERVICE) as PowerManager?)!!
private val wakeLock: PowerManager.WakeLock
init {
wakeLock = powerManager.newWakeLock(
PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK,
"proximity")
}
fun acquire() {
if (powerManager.isWakeLockLevelSupported(PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK)) {
if (wakeLock.isHeld) {
wakeLock.release()
}
wakeLock.acquire(WAKE_LOCK_TIMEOUT_MS)
} else {
Log.w(TAG, "not supported")
}
}
fun release() {
if (wakeLock.isHeld)
wakeLock.release()
}
companion object {
private const val TAG = "ProximitySensor"
private const val WAKE_LOCK_TIMEOUT_MS: Long = 2 * 3600 * 1000
}
}
}```
If you suggest me any alternate method then it will be very helpful
My Worker(for API call) starts from Service and I want to completion event send into Service class.
What should be best approach?
Calling from service:
PeriodicWorkRequest request = new PeriodicWorkRequest
.Builder(Worker.class, Constants.REPEAT_INTERVAL, TimeUnit.MINUTES)
.addTag(TAG)
.setConstraints(new Constraints.Builder().setRequiredNetworkType(NetworkType.CONNECTED).build())
.build();
WorkManager.getInstance(getApplicationContext()).enqueue(request);
Calling from WorkManager:
override fun doWork(): Result {
// API call
return Result.success()
}
Okay so what I would do is I would create common object for both Worker and Service class and utilize Observer pattern. This WorkObserver object would behave as a proxy between Service and Worker. Using Koin for example, it would look something like that:
class MyWorker: Worker(), KoinComponent {
private val workObserver: WorkObserver by inject()
override fun doWork(): Result {
val result = api.call().execute()
if(result.isSuccessful) {
workObserver.notifySuccess()
return Result.success()
} else {
workObserver.notifyError()
return Result.failure()
}
}
}
class MyService: Service(), KoinComponent {
private val workObserver: WorkObserver by inject()
override fun onCreate() {
workObserver.setOnResultListener { result ->
if(result) {
//do something
} else {
// do something else
}
}
override fun onDestroy() {
workObserver.setOnResultListener(null)
}
}
class WorkObserver {
private var onResultListener: ((Result) -> Unit)? = null
fun setOnResultListener(listener: ((Result) -> Unit)?) {
this.onResultListener = listener
}
fun notifySuccess() {
this.onResultListener?.invoke(true)
}
fun notifyError() {
this.onResultListener?.invoke(false)
}
}
Of course you can use other DI tools for that, you can have a list of listeners and remove particular ones, you can pass any other object through the listener in WorkObserver with the payload you need. I just created a simple boolean passing
For that simple case however if you don't want to use DI, simple Object would do the work. However when your codebase grows and you are dealing with multithreading issues, or even accessing this object in other parts of the application it may lead to problems. I am using it only to pass information between objects, I don't recommend using it for storing data etc:
class MyWorker: Worker() {
override fun doWork(): Result {
val result = api.call().execute()
if(result.isSuccessful) {
WorkObserver.notifySuccess()
return Result.success()
} else {
WorkObserver.notifyError()
return Result.failure()
}
}
}
class MyService: Service() {
override fun onCreate() {
WorkObserver.setOnResultListener { result ->
if(result) {
//do something
} else {
// do something else
}
}
override fun onDestroy() {
WorkObserver.setOnResultListener(null)
}
}
object WorkObserver {
private var onResultListener: ((Result) -> Unit)? = null
fun setOnResultListener(listener: ((Result) -> Unit)?) {
this.onResultListener = listener
}
fun notifySuccess() {
this.onResultListener?.invoke(true)
}
fun notifyError() {
this.onResultListener?.invoke(false)
}
}
I need to make an application where, while the user is authorized, it keeps the socket connection until it is logged out. For this purpose, a foreground service is created, which starts after the authorization of the user, and stops when it is logged out. It implements connection and reconnection on the socket.
All works well until you press the power button and turn off the charging. After this, the user stops receiving pongs from the server and the SocketTimeoutException is received on the OkHttp, and also stops receiving messages on the socket. On JavaWebsocket, The connection was closed because the other endpoint did not respond with a pong in time is received, after which you can successfully create a new socket connection, but it will repeat the same problem in the loop.
In the settings, the optimization of the battery for this application was disabled. What can I do to make a stable connection socket work in the background?
Implementation of activity:
class MainActivity : BaseFragmentPermissionActivity(), MainMvpView {
private var mIsSocketBound = false
private var mSocketBroadcastReceiver = SocketBroadcastReceiver(this)
private var mSocketConnection = SocketConnection(this)
private var mSocketService: SocketService? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
...
doBindService()
}
private fun doBindService() {
bindService(Intent(this, SocketService::class.java), mSocketConnection, Context.BIND_AUTO_CREATE)
mIsSocketBound = true
}
override fun onStart() {
super.onStart()
...
mSocketService?.doStopForeground()
}
override fun onStop() {
mSocketService?.doStartForeground()
...
super.onStop()
}
override fun onDestroy() {
doUnbindService()
...
super.onDestroy()
}
private fun doUnbindService() {
if (mIsSocketBound) {
unbindService(mSocketConnection)
mIsSocketBound = false
mSocketService = null
}
}
class SocketConnection(mainActivity: MainActivity) : ServiceConnection {
private val mMainActivity: WeakReference<MainActivity> = WeakReference(mainActivity)
override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
val socketService = (service as SocketService.LocalBinder).getService()
mMainActivity.get()?.mSocketService = socketService
if (socketService.isForeground()) {
socketService.doStopForeground()
}
}
override fun onServiceDisconnected(name: ComponentName?) {
mMainActivity.get()?.mSocketService = null
}
}
}
Implementation of service:
class SocketService : Service(), MvpErrorHandler {
private val mConnectingHandler = Handler()
private val mConnectingTask = ConnectingTask(this)
private var mIsRunningForeground = false
override fun onBind(intent: Intent?): IBinder {
startService(Intent(this, SocketService::class.java))
return mBinder
}
override fun onCreate() {
super.onCreate()
DaggerServiceComponent.builder()
.serviceModule(ServiceModule(this))
.applicationComponent(PatrolApplication.applicationComponent)
.build()
.inject(this)
startConnecting()
...
}
override fun onDestroy() {
...
stopConnecting()
super.onDestroy()
}
private fun startConnecting() {
if (!mIsConnecting) {
mIsConnecting = true
mConnectingHandler.post(mConnectingTask)
}
}
private fun stopConnecting() {
mConnectingHandler.removeCallbacks(mConnectingTask)
mIsConnecting = false
}
private fun openConnection() {
mCompositeDisposable.add(mDataManager.getSocketToken()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(false, this, {
stopConnecting()
mDataManager.openSocketConnection(it.token)
}, {
mConnectingHandler.postDelayed(mConnectingTask, RECONNECT_TIME.toLong())
return#subscribe ErrorHandlerUtil.handleGetSocketError(it, this)
}))
}
class ConnectingTask(socketService: SocketService) : Runnable {
private val mSocketService: WeakReference<SocketService> = WeakReference(socketService)
override fun run() {
mSocketService.get()?.openConnection()
}
}
}
Implementation of SocketHelper using JavaWebsocket:
class CustomApiSocketHelper #Inject constructor() : ApiSocketHelper {
private var mCustomSocketClient: WebSocketClient? = null
override fun openSocketConnection(token: String) {
mCustomSocketClient = CustomSocketClient(URI(CONNECTION_URL + token))
mCustomSocketClient?.connect()
}
override fun sendMessage(text: String) {
if (mCustomSocketClient?.isOpen == true) {
try {
mCustomSocketClient?.send(text)
} catch (t: Throwable) {
Log.e(TAG, Log.getStackTraceString(t))
Crashlytics.logException(t)
}
}
}
override fun closeSocketConnection() {
mCustomSocketClient?.close(CLOSE_REASON_OK)
}
class CustomSocketClient(uri: URI) : WebSocketClient(uri) {
init {
connectionLostTimeout = PING_TIMEOUT
}
override fun onOpen(handshakedata: ServerHandshake?) {
sendBroadcast(SocketActionType.OPEN.action)
}
override fun onMessage(message: String?) {
sendBroadcast(SocketActionType.MESSAGE.action, message)
}
override fun onClose(code: Int, reason: String?, remote: Boolean) {
if (code != CLOSE_REASON_OK) {
//call startConnecting() in service
sendBroadcast(SocketActionType.CLOSE.action)
}
}
override fun onError(ex: Exception?) {
sendBroadcast(SocketActionType.FAILURE.action)
}
private fun sendBroadcast(type: Int) {
val intent = Intent().apply { action = SOCKET_BROADCAST_ACTION }
intent.putExtra(SOCKET_MESSAGE_TYPE, type)
LocalBroadcastManager.getInstance(CustomApplication.application).sendBroadcast(intent)
}
private fun sendBroadcast(type: Int, text: String?) {
val intent = Intent().apply { action = SOCKET_BROADCAST_ACTION }
intent.putExtra(SOCKET_MESSAGE_TYPE, type)
intent.putExtra(SOCKET_MESSAGE, text)
LocalBroadcastManager.getInstance(CustomApplication.application).sendBroadcast(intent)
}
}
}
Implementation of SocketHelper using OkHttp:
class CustomApiSocketHelper #Inject constructor() : ApiSocketHelper {
private var mCustomSocketClient: WebSocket? = null
override fun openSocketConnection(token: String) {
val request = Request.Builder()
.url(CONNECTION_URL + token)
.build()
mCustomSocketClient = CustomApplication.applicationComponent.authorizedClient().newWebSocket(request, CustomSocketClient())
}
override fun sendMessage(text: String) {
mPatrolSocketClient?.send(text)
}
override fun closeSocketConnection() {
mCustomSocketClient?.close(CLOSE_REASON_OK, null)
}
class CustomSocketClient : WebSocketListener() {
override fun onOpen(webSocket: WebSocket, response: Response) {
super.onOpen(webSocket, response)
sendBroadcast(SocketActionType.OPEN.action)
}
override fun onMessage(webSocket: WebSocket, text: String) {
super.onMessage(webSocket, text)
sendBroadcast(SocketActionType.MESSAGE.action, text)
}
override fun onClosed(webSocket: WebSocket?, code: Int, reason: String?) {
super.onClosed(webSocket, code, reason)
if (code != CLOSE_REASON_OK) {
sendBroadcast(SocketActionType.CLOSE.action)
}
}
override fun onFailure(webSocket: WebSocket?, t: Throwable?, response: Response?) {
super.onFailure(webSocket, t, response)
sendBroadcast(SocketActionType.FAILURE.action)
}
private fun sendBroadcast(type: Int) {
val intent = Intent().apply { action = SOCKET_BROADCAST_ACTION }
intent.putExtra(SOCKET_MESSAGE_TYPE, type)
LocalBroadcastManager.getInstance(CustomApplication.application).sendBroadcast(intent)
}
private fun sendBroadcast(type: Int, text: String?) {
val intent = Intent().apply { action = SOCKET_BROADCAST_ACTION }
intent.putExtra(SOCKET_MESSAGE_TYPE, type)
intent.putExtra(SOCKET_MESSAGE, text)
LocalBroadcastManager.getInstance(CustomApplication.application).sendBroadcast(intent)
}
}
}
...
#Provides
#Singleton
#Named(AUTHORIZED_CLIENT)
fun provideAuthorizedClient(builder: OkHttpClient.Builder, interceptor: Interceptor, authenticator: Authenticator): OkHttpClient = builder
.addInterceptor(interceptor)
.authenticator(authenticator)
.pingInterval(PING_TIMEOUT.toLong(), TimeUnit.SECONDS)
.build()
#Provides
#Singleton
fun provideOkHttpBuilder() = CustomApiHelper.getOkHttpBuilder()
fun getOkHttpBuilder(): OkHttpClient.Builder {
val builder = OkHttpClient.Builder()
builder.readTimeout(NETWORK_CALL_TIMEOUT, TimeUnit.SECONDS)
builder.writeTimeout(NETWORK_CALL_TIMEOUT, TimeUnit.SECONDS)
if (BuildConfig.DEBUG) {
val logger = HttpLoggingInterceptor()
logger.level = HttpLoggingInterceptor.Level.BASIC
builder.addInterceptor(logger)
}
return builder
}
After some research and testing on different devices, it was found that for stable operation on the network, it is necessary that the device is charging or has a screen enabled. In the other case, neither PARTIAL_WAKE_LOCK nor the disabling of battery optimization in the settings itself can solve the problem.
The recommended way to solve this problem is to add this code to your activity:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
}
This prevents the screen from turning off and provides a stable socket connection. But we still have the situation that the user can press the power button. And, if at this moment the device is charging, everything will work as before, but otherwise, we will get the socket disconnect. To solve this problem, you need to periodically wake the device, in order to support the ping-pong process. This is not a recommended solution because it will lead to battery draining, and can not guarantee 100% performance, but if this moment is critical for you, then you can use this solution. You need to add this code, in a suitable place for you, in this example is used at the time of ping.
#Suppress("DEPRECATION")
override fun onWebsocketPing(conn: WebSocket?, f: Framedata?) {
if (mSocketWakeLock == null) {
mSocketWakeLock = mPowerManager.newWakeLock(PowerManager.FULL_WAKE_LOCK or PowerManager.SCREEN_BRIGHT_WAKE_LOCK or PowerManager.ACQUIRE_CAUSES_WAKEUP, TAG)
}
mSocketWakeLock?.takeIf { !it.isHeld }?.run { acquire(WAKE_TIMEOUT) }
super.onWebsocketPing(conn, f)
mSocketWakeLock?.takeIf { it.isHeld }?.run { release() }
}
Using this solution, on the test devices socket connection, with good Internet, stays stable for 2 hours or more. Without it, it is constantly disconnect.
Kotlin and Android rookie here...
I'm trying to create a job but I'm having some trobule gettint my async task to run. Here's my JobService:
class DbUpdaterJob: JobService(){
private var activityMessenger: Messenger? = null
private var isWorking = false
private var cancelled = false
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
//receive messenger used to get info back to the ui thread of activity
activityMessenger = intent?.getParcelableExtra(MESSENGER_DB_UPDATE_KEY)
return Service.START_NOT_STICKY
}
override fun onStartJob(params: JobParameters?): Boolean {
//asynctasks in order not to lock main thread
object: AsyncTask<Void, Void, Boolean>(){
override fun onPreExecute() {
super.onPreExecute()
}
override fun doInBackground(vararg params: Void?): Boolean {
isWorking = true
//do something
return true
}
override fun onPostExecute(result: Boolean?) {
isWorking = false
jobFinished(params, false)
//notify of update on main thread
if(result!!){
notifyActivity()
}
}
}.execute()
return true
}
override fun onStopJob(params: JobParameters?): Boolean {
cancelled = true
//if still working, must reschedule
jobFinished(params, isWorking)
return isWorking
}
private fun notifyActivity(){
val msg = Message.obtain()
msg.run {
what = MSG_DB_UPDATED
}
activityMessenger?.send(msg)
}
}
The idea is to put a couple of web services calls and local db code in the doInBackground. Unfortunately, it never gets called...Can someone please give some pointers on what I'm missing? I've also tried to rewrite the code with threads but still haven't got any luck with it. Not sure if it's important, but here's the code I'm using to schedule the job:
private fun scheduleJob(){
//NOTE: https://stackoverflow.com/questions/38344220/job-scheduler-not-running-on-android-n?utm_medium=organic&utm_source=google_rich_qa&utm_campaign=google_rich_qa
//less than 15m does not work!
var jobInfo = JobInfo.Builder(DB_UPDATER_JOB_ID, serviceComponent)
.setRequiredNetworkType(NETWORK_TYPE_NOT_ROAMING)
.setPeriodic(4 * 60 * 60 *1000)
.build()
val scheduler = getSystemService(Context.JOB_SCHEDULER_SERVICE) as? JobScheduler
val res = scheduler?.schedule(jobInfo)
Log.i("MainActivity", "Job scheduled with $res")
}
I have a service, where I manage music playing. Also I have activity sending intents with user's music. When I open activity, I want to get current status of playing.
I have specific player, what have only two events: playing started and playing ends. So if I use broadcast, I will get only next event.
I save events in variable lastAction when getting it. I can create new command ACTION_SEND_CURRENT_STATE. but it looks not good.
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
when (intent?.action) {
null -> {
player?.cancel()
}
ACTION_PLAY -> {
player?.cancel()
player = createPlayer(intent)
player?.start()
}
ACTION_STOP -> {
player?.cancel()
}
}
return START_STICKY
}
override fun onPlayingBegin(p0: player?) {
lastAction = BRODCAST_PLAYING_BEGIN
sendBroadcast(Intent(BRODCAST_PLAYING_BEGIN)
.putExtra(EXTRA_SONG, currentSong)
)
}
How to get current state from service correctly? as state I mean last action.
Use this method
public static boolean isServiceRunning(Context context, Class<?> serviceClass) {
ActivityManager manager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
for (ActivityManager.RunningServiceInfo service : manager.getRunningServices(Integer.MAX_VALUE)) {
if (serviceClass.getName().equals(service.service.getClassName())) {
return true;
}
}
return false;
}
Hope help you.
As pskink mentioned you need to bind your service to your activity to be able to get information from your service. If your service is working in a remote process then you need to use AIDL to communicate with your service, but I believe that if you do so you're able to find how to do that by yourself.
In your particular case the service communication might look like this(please note that the code may be not completely correct, I wrote it right from my head):
class LocalBinder(val service: MusicService) : Binder
class MusicService : Service() {
var lastAction: String? = null
private set
private val binder: IBinder = LocalBinder(this)
override fun onBind(intent: Intent) = binder
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
lastAction = intent?.action
when (intent?.action) {
null -> {
player?.cancel()
}
ACTION_PLAY -> {
player?.cancel()
player = createPlayer(intent)
player?.start()
}
ACTION_STOP -> {
player?.cancel()
}
}
return START_STICKY
}
}
class MusicPlayerActivity : Activity() {
private var musicService: MusicService? = null
private val connection = object : ServiceConnection {
override fun onServiceConnected(className: ComponentName, service: IBinder) {
val binder = (LocalBinder) service
musicService = binder.service
}
override fun onServiceDisconnected(className: ComponentName) {
musicService = null
}
}
override fun protected onStart() {
super.onStart()
val intent = Intent(this, MusicService::class.java)
bindService(intent, connection, Context.BIND_AUTO_CREATE)
}
override fun protected onStop() {
super.onStop()
if (musicService != null) {
unbind(connection)
musicService = null
}
}
fun onClick() {
if (musicService != null) {
musicService.lastAction // do something
}
}
}
No need to worry about the service just use foreground service as used by all music playing applications.