Raise app to foreground on new message
This commit is contained in:
parent
5d6b3b887d
commit
41f4fabb33
@ -23,11 +23,12 @@ object Distributor {
|
|||||||
fun sendMessage(context: Context, appToken: String, message: ByteArray) {
|
fun sendMessage(context: Context, appToken: String, message: ByteArray) {
|
||||||
val db = getDb(context)
|
val db = getDb(context)
|
||||||
val connectorToken = db.getConnectorToken(appToken) ?: return
|
val connectorToken = db.getConnectorToken(appToken) ?: return
|
||||||
val application = getApp(context, connectorToken)
|
val application = getApp(context, connectorToken) ?: return
|
||||||
if (application == context.packageName) {
|
if (application == context.packageName) {
|
||||||
LocalNotification.showNotification(context, connectorToken, String(message))
|
LocalNotification.showNotification(context, connectorToken, String(message))
|
||||||
Log.d(TAG, "Local notif shown")
|
Log.d(TAG, "Local notification shown")
|
||||||
} else {
|
} else {
|
||||||
|
RaiseAppToForegroundFactory.getInstance(context, application).raise()
|
||||||
val broadcastIntent = Intent()
|
val broadcastIntent = Intent()
|
||||||
broadcastIntent.`package` = application
|
broadcastIntent.`package` = application
|
||||||
broadcastIntent.action = ACTION_MESSAGE
|
broadcastIntent.action = ACTION_MESSAGE
|
||||||
|
@ -0,0 +1,127 @@
|
|||||||
|
package org.unifiedpush.distributor.nextpush.distributor
|
||||||
|
|
||||||
|
import android.app.ActivityManager
|
||||||
|
import android.app.ActivityManager.RunningAppProcessInfo
|
||||||
|
import android.content.ComponentName
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.content.ServiceConnection
|
||||||
|
import android.os.IBinder
|
||||||
|
import android.util.Log
|
||||||
|
import kotlinx.coroutines.Runnable
|
||||||
|
import java.util.concurrent.Executors
|
||||||
|
import java.util.concurrent.ScheduledFuture
|
||||||
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
|
class RaiseAppToForeground(private val context: Context, private val app: String, private val onUnbound: () -> Unit): ServiceConnection, Runnable {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Is the service bound ? This is a per service connection
|
||||||
|
*/
|
||||||
|
private var bound = false
|
||||||
|
private var scheduledFuture: ScheduledFuture<*>? = null
|
||||||
|
|
||||||
|
private val foregroundImportance = listOf(
|
||||||
|
RunningAppProcessInfo.IMPORTANCE_FOREGROUND,
|
||||||
|
RunningAppProcessInfo.IMPORTANCE_FOREGROUND_SERVICE,
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return `true` if the app is in Foreground importance
|
||||||
|
*/
|
||||||
|
private fun checkForeground(): Boolean {
|
||||||
|
val appProcesses = (context.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager).runningAppProcesses
|
||||||
|
for (appProcess in appProcesses) {
|
||||||
|
if (appProcess.importance in foregroundImportance) {
|
||||||
|
Log.i(TAG, "Found foreground process: ${appProcess.processName}")
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Raise [app] to the foreground, to follow AND_3 specifications
|
||||||
|
*
|
||||||
|
* @return `true` if have successfully raised app to foreground
|
||||||
|
*/
|
||||||
|
fun raise(): Boolean {
|
||||||
|
// 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/$TARGET_CLASS")
|
||||||
|
val intent = Intent().apply {
|
||||||
|
`package` = app
|
||||||
|
action = ACTION
|
||||||
|
}
|
||||||
|
//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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun unbind() {
|
||||||
|
// Per instance lock
|
||||||
|
synchronized(this) {
|
||||||
|
if (bound) {
|
||||||
|
context.unbindService(this)
|
||||||
|
bound = false
|
||||||
|
onUnbound()
|
||||||
|
Log.d(TAG, "Unbound")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
|
||||||
|
Log.d(TAG, "onServiceConnected $name")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onServiceDisconnected(name: ComponentName?) {
|
||||||
|
Log.d(TAG, "onServiceDisconnected $name")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onBindingDied(name: ComponentName?) {
|
||||||
|
Log.d(TAG, "onBindingDied")
|
||||||
|
unbind()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onNullBinding(name: ComponentName?) {
|
||||||
|
Log.d(TAG, "onBindingDied")
|
||||||
|
unbind()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unbinding when the timeout passes.
|
||||||
|
*/
|
||||||
|
override fun run() {
|
||||||
|
Log.d(TAG, "Timeout expired, unbinding")
|
||||||
|
unbind()
|
||||||
|
}
|
||||||
|
|
||||||
|
private companion object {
|
||||||
|
private const val TAG = "RaiseAppToForeground"
|
||||||
|
private const val TARGET_CLASS = "org.unifiedpush.android.connector.RaiseToForegroundService"
|
||||||
|
private const val ACTION = "org.unifiedpush.android.distributor.RAISE_TO_FOREGROUND"
|
||||||
|
/** Executor to unbind 5 seconds later */
|
||||||
|
private val unbindExecutor = Executors.newSingleThreadScheduledExecutor()
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,39 @@
|
|||||||
|
package org.unifiedpush.distributor.nextpush.distributor
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.util.Log
|
||||||
|
|
||||||
|
/**
|
||||||
|
* [RaiseAppToForeground] Factory, to avoid having one service connection per push message
|
||||||
|
*
|
||||||
|
* There is a very small chance that 2 connections exist at the same time\*, but that's not important.
|
||||||
|
* We just want to avoid tens of it.
|
||||||
|
*
|
||||||
|
* \* When [getInstance] returns an existing instance, that runs [remove] before
|
||||||
|
* [RaiseAppToForeground.raise] is called.
|
||||||
|
*/
|
||||||
|
object RaiseAppToForegroundFactory {
|
||||||
|
fun getInstance(context: Context, app: String): RaiseAppToForeground {
|
||||||
|
synchronized(this) {
|
||||||
|
return instances[app] ?: new(context, app)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun new(context: Context, app: String): RaiseAppToForeground {
|
||||||
|
return RaiseAppToForeground(context, app, onUnbound = {
|
||||||
|
remove(app)
|
||||||
|
}).also {
|
||||||
|
Log.d(TAG, "New instance for $app")
|
||||||
|
instances[app] = it
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun remove(app: String) {
|
||||||
|
Log.d(TAG, "Removing instance for $app")
|
||||||
|
synchronized(this) {
|
||||||
|
instances.remove(app)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private val instances: MutableMap<String, RaiseAppToForeground> = mutableMapOf()
|
||||||
|
private const val TAG = "RaiseAppToForegroundFactory"
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user