Ensure the RaiseToForeground service is bound before sending push msg
This commit is contained in:
parent
cce36d8151
commit
13ff0ec8a4
3 changed files with 122 additions and 38 deletions
|
|
@ -11,13 +11,9 @@ import io.heckel.ntfy.util.Log
|
|||
class Distributor(val context: Context) {
|
||||
fun sendMessage(app: String, connectorToken: String, message: ByteArray) {
|
||||
Log.d(TAG, "Sending MESSAGE to $app (token=$connectorToken): ${message.size} bytes")
|
||||
RaiseAppToForegroundFactory.getInstance(context, app).raise()
|
||||
val broadcastIntent = Intent()
|
||||
broadcastIntent.`package` = app
|
||||
broadcastIntent.action = ACTION_MESSAGE
|
||||
broadcastIntent.putExtra(EXTRA_TOKEN, connectorToken)
|
||||
broadcastIntent.putExtra(EXTRA_BYTES_MESSAGE, message)
|
||||
context.sendBroadcast(broadcastIntent)
|
||||
RaiseAppToForegroundFactory
|
||||
.getInstance(context, app)
|
||||
.raiseAndSend(connectorToken, message)
|
||||
}
|
||||
|
||||
fun sendEndpoint(app: String, connectorToken: String, endpoint: String) {
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import android.content.ComponentName
|
|||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.ServiceConnection
|
||||
import android.content.pm.PackageManager
|
||||
import android.os.Build
|
||||
import android.os.IBinder
|
||||
import android.util.Log
|
||||
|
|
@ -16,11 +17,20 @@ import java.util.concurrent.TimeUnit
|
|||
|
||||
class RaiseAppToForeground(private val context: Context, private val app: String, private val onUnbound: () -> Unit): ServiceConnection, Runnable {
|
||||
|
||||
class Message(val token: String, val content: ByteArray)
|
||||
|
||||
private enum class Bound {
|
||||
Binding,
|
||||
Bound,
|
||||
Unbound
|
||||
}
|
||||
|
||||
/**
|
||||
* Is the service bound ? This is a per service connection
|
||||
*/
|
||||
private var bound = false
|
||||
private var bound = Bound.Unbound
|
||||
private var scheduledFuture: ScheduledFuture<*>? = null
|
||||
private val msgsQueue = mutableListOf<Message>()
|
||||
|
||||
private val foregroundImportance = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
listOf(
|
||||
|
|
@ -47,51 +57,121 @@ class RaiseAppToForeground(private val context: Context, private val app: String
|
|||
return false
|
||||
}
|
||||
|
||||
|
||||
private fun hasRaiseToForegroundService(): Boolean {
|
||||
val intent = Intent(ACTION).apply {
|
||||
`package` = app
|
||||
}
|
||||
return (
|
||||
if (Build.VERSION.SDK_INT >= 33) {
|
||||
context.packageManager.queryIntentServices(
|
||||
intent,
|
||||
PackageManager.ResolveInfoFlags.of(
|
||||
PackageManager.GET_META_DATA.toLong() +
|
||||
PackageManager.GET_RESOLVED_FILTER.toLong(),
|
||||
),
|
||||
)
|
||||
} else {
|
||||
context.packageManager.queryIntentServices(
|
||||
Intent(ACTION_REGISTER),
|
||||
PackageManager.GET_RESOLVED_FILTER,
|
||||
)
|
||||
}
|
||||
).any {
|
||||
it.serviceInfo.exported
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private fun send(message: Message) {
|
||||
Log.d(TAG, "Sending msg for $app")
|
||||
val broadcastIntent = Intent()
|
||||
broadcastIntent.`package` = app
|
||||
broadcastIntent.action = ACTION_MESSAGE
|
||||
broadcastIntent.putExtra(EXTRA_TOKEN, message.token)
|
||||
broadcastIntent.putExtra(EXTRA_BYTES_MESSAGE, message.content)
|
||||
context.sendBroadcast(broadcastIntent)
|
||||
}
|
||||
|
||||
/**
|
||||
* Queue message when the service is binding
|
||||
*/
|
||||
private fun queue(message: Message) {
|
||||
msgsQueue.add(message)
|
||||
}
|
||||
|
||||
/**
|
||||
* If the service is already bound, we delay its unbinding
|
||||
*/
|
||||
private fun delayUnbinding() {
|
||||
/**
|
||||
* Close current scheduledFuture. We interrupt if it is running (mayInterruptIfRunning = true), so [run] won't
|
||||
* unbind this new connection after we release the lock.
|
||||
*/
|
||||
scheduledFuture?.cancel(true)
|
||||
/** Call [run] (unbind) in 5 seconds */
|
||||
scheduledFuture = unbindExecutor.schedule(this, 5L, TimeUnit.SECONDS)
|
||||
}
|
||||
|
||||
private fun bind() {
|
||||
Log.d(TAG, "Binding to ${this.app}")
|
||||
val intent = Intent().apply {
|
||||
`package` = this@RaiseAppToForeground.app
|
||||
action = ACTION
|
||||
}
|
||||
/** Bind to the target raise to the foreground service */
|
||||
context.bindService(intent, this, Context.BIND_AUTO_CREATE)
|
||||
/** Call [run] (unbind) in 5 seconds */
|
||||
scheduledFuture = unbindExecutor.schedule(this, 5L, TimeUnit.SECONDS)
|
||||
bound = Bound.Binding
|
||||
}
|
||||
|
||||
/**
|
||||
* Raise [app] to the foreground, to follow AND_3 specifications
|
||||
*
|
||||
* @return `true` if have successfully raised app to foreground
|
||||
*/
|
||||
fun raise(): Boolean {
|
||||
fun raiseAndSend(token: String, message: ByteArray): Boolean {
|
||||
val msg = Message(token, message)
|
||||
// Per instance lock
|
||||
synchronized(this) {
|
||||
if (bound) {
|
||||
Log.w(TAG, "This service connection is already bound to $app. Aborting.")
|
||||
/**
|
||||
* Close current scheduledFuture. We interrupt if it is running, so [run] won't
|
||||
* unbind this new connection after we release the lock.
|
||||
*/
|
||||
scheduledFuture?.cancel(/* mayInterruptIfRunning = */ true)
|
||||
/** Call [run] (unbind) in 5 seconds */
|
||||
scheduledFuture = unbindExecutor.schedule(this, 5L, TimeUnit.SECONDS)
|
||||
return true
|
||||
} else if (checkForeground()) {
|
||||
Log.d(TAG, "Binding to $app")
|
||||
val intent = Intent().apply {
|
||||
`package` = app
|
||||
action = ACTION
|
||||
when (bound) {
|
||||
Bound.Bound -> {
|
||||
Log.d(TAG, "Service connection already bound to ${this.app}")
|
||||
delayUnbinding()
|
||||
send(msg)
|
||||
}
|
||||
Bound.Binding -> {
|
||||
delayUnbinding()
|
||||
queue(msg)
|
||||
}
|
||||
Bound.Unbound -> {
|
||||
val isForeground = checkForeground()
|
||||
val targetHasService = hasRaiseToForegroundService()
|
||||
if (isForeground && targetHasService) {
|
||||
bind()
|
||||
queue(msg)
|
||||
} else {
|
||||
Log.d(
|
||||
TAG,
|
||||
"Cannot raise to foreground: isForeground=$isForeground, targetHasService=$targetHasService"
|
||||
)
|
||||
send(msg)
|
||||
return false
|
||||
}
|
||||
}
|
||||
//val sConnection = RaiseAppToForeground(context, app)
|
||||
/** Bind to the target raise to the foreground service */
|
||||
context.bindService(intent, this, Context.BIND_AUTO_CREATE)
|
||||
/** Call [run] (unbind) in 5 seconds */
|
||||
scheduledFuture = unbindExecutor.schedule(this, 5L, TimeUnit.SECONDS)
|
||||
Log.d(TAG, "Bound to $app")
|
||||
bound = true
|
||||
return true
|
||||
} else {
|
||||
Log.d(TAG, "We are not in foreground, can't raise $app to foreground")
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
private fun unbind() {
|
||||
// Per instance lock
|
||||
synchronized(this) {
|
||||
if (bound) {
|
||||
if (bound != Bound.Unbound) {
|
||||
msgsQueue.clear()
|
||||
context.unbindService(this)
|
||||
bound = false
|
||||
bound = Bound.Unbound
|
||||
onUnbound()
|
||||
Log.d(TAG, "Unbound")
|
||||
}
|
||||
|
|
@ -100,10 +180,18 @@ class RaiseAppToForeground(private val context: Context, private val app: String
|
|||
|
||||
override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
|
||||
Log.d(TAG, "onServiceConnected $name")
|
||||
synchronized(this) {
|
||||
bound = Bound.Bound
|
||||
}
|
||||
msgsQueue.forEach { msg ->
|
||||
send(msg)
|
||||
}
|
||||
msgsQueue.clear()
|
||||
}
|
||||
|
||||
override fun onServiceDisconnected(name: ComponentName?) {
|
||||
Log.d(TAG, "onServiceDisconnected $name")
|
||||
unbind()
|
||||
}
|
||||
|
||||
override fun onBindingDied(name: ComponentName?) {
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ import android.util.Log
|
|||
* We just want to avoid tens of it.
|
||||
*
|
||||
* \* When [getInstance] returns an existing instance, that runs [remove] before
|
||||
* [RaiseAppToForeground.raise] is called.
|
||||
* [RaiseAppToForeground.raiseAndSend] is called.
|
||||
*/
|
||||
object RaiseAppToForegroundFactory {
|
||||
fun getInstance(context: Context, app: String): RaiseAppToForeground {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue