mirror of
https://github.com/TeamVanced/VancedMicroG
synced 2024-06-10 07:07:55 +02:00
349 lines
13 KiB
Kotlin
349 lines
13 KiB
Kotlin
/*
|
|
* SPDX-FileCopyrightText: 2020, microG Project Team
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
@file:Suppress("DEPRECATION")
|
|
|
|
package org.microg.gms.gcm
|
|
|
|
import android.app.Activity
|
|
import android.app.PendingIntent
|
|
import android.content.Context
|
|
import android.content.Intent
|
|
import android.os.*
|
|
import android.util.Log
|
|
import androidx.legacy.content.WakefulBroadcastReceiver
|
|
import androidx.lifecycle.Lifecycle
|
|
import androidx.lifecycle.LifecycleOwner
|
|
import androidx.lifecycle.LifecycleService
|
|
import androidx.lifecycle.lifecycleScope
|
|
import org.microg.gms.checkin.CheckinPrefs
|
|
import org.microg.gms.checkin.CheckinService
|
|
import org.microg.gms.checkin.LastCheckinInfo
|
|
import org.microg.gms.common.ForegroundServiceContext
|
|
import org.microg.gms.common.PackageUtils
|
|
import org.microg.gms.common.Utils
|
|
import org.microg.gms.gcm.GcmConstants.*
|
|
import java.util.concurrent.atomic.AtomicBoolean
|
|
import kotlin.coroutines.resume
|
|
import kotlin.coroutines.suspendCoroutine
|
|
|
|
private const val TAG = "GmsGcmRegister"
|
|
|
|
private suspend fun ensureCheckinIsUpToDate(context: Context) {
|
|
if (!CheckinPrefs.isEnabled(context)) throw RuntimeException("Checkin disabled")
|
|
val lastCheckin = LastCheckinInfo.read(context).lastCheckin
|
|
if (lastCheckin < System.currentTimeMillis() - CheckinService.MAX_VALID_CHECKIN_AGE) {
|
|
val resultData: Bundle = suspendCoroutine { continuation ->
|
|
val intent = Intent(context, CheckinService::class.java)
|
|
val continued = AtomicBoolean(false)
|
|
intent.putExtra(CheckinService.EXTRA_RESULT_RECEIVER, object : ResultReceiver(null) {
|
|
override fun onReceiveResult(resultCode: Int, resultData: Bundle?) {
|
|
if (continued.compareAndSet(false, true)) continuation.resume(resultData ?: Bundle.EMPTY)
|
|
}
|
|
})
|
|
ForegroundServiceContext(context).startService(intent)
|
|
Handler().postDelayed({
|
|
if (continued.compareAndSet(false, true)) continuation.resume(Bundle.EMPTY)
|
|
}, 10000L)
|
|
}
|
|
if (resultData.getLong(CheckinService.EXTRA_NEW_CHECKIN_TIME, 0L) + lastCheckin == 0L) {
|
|
throw RuntimeException("No checkin available")
|
|
}
|
|
}
|
|
}
|
|
|
|
private suspend fun ensureAppRegistrationAllowed(context: Context, database: GcmDatabase, packageName: String) {
|
|
if (!GcmPrefs.get(context).isEnabled) throw RuntimeException("GCM disabled")
|
|
val app = database.getApp(packageName)
|
|
if (app?.allowRegister == false) {
|
|
throw RuntimeException("Push permission not granted to $packageName")
|
|
}
|
|
}
|
|
|
|
suspend fun completeRegisterRequest(context: Context, database: GcmDatabase, request: RegisterRequest, requestId: String? = null): Bundle = suspendCoroutine { continuation ->
|
|
PushRegisterManager.completeRegisterRequest(context, database, requestId, request) { continuation.resume(it) }
|
|
}
|
|
|
|
private val Intent.requestId: String?
|
|
get() {
|
|
val kidString = getStringExtra(GcmConstants.EXTRA_KID) ?: return null
|
|
if (kidString.startsWith("|")) {
|
|
val kid = kidString.split("\\|".toRegex()).toTypedArray()
|
|
if (kid.size >= 3 && "ID" == kid[1]) {
|
|
return kid[2]
|
|
}
|
|
}
|
|
return null
|
|
}
|
|
|
|
private val Intent.app: PendingIntent?
|
|
get() = getParcelableExtra(EXTRA_APP)
|
|
|
|
private val Intent.appPackageName: String?
|
|
get() = PackageUtils.packageFromPendingIntent(app)
|
|
|
|
class PushRegisterService : LifecycleService() {
|
|
private lateinit var database: GcmDatabase
|
|
override fun onCreate() {
|
|
super.onCreate()
|
|
database = GcmDatabase(this)
|
|
}
|
|
|
|
override fun onDestroy() {
|
|
super.onDestroy()
|
|
database.close()
|
|
}
|
|
|
|
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
|
if (intent != null) {
|
|
WakefulBroadcastReceiver.completeWakefulIntent(intent)
|
|
Log.d(TAG, "onStartCommand: $intent")
|
|
lifecycleScope.launchWhenStarted {
|
|
handleIntent(intent)
|
|
}
|
|
}
|
|
return super.onStartCommand(intent, flags, startId)
|
|
}
|
|
|
|
private suspend fun handleIntent(intent: Intent) {
|
|
try {
|
|
ensureCheckinIsUpToDate(this)
|
|
if (ACTION_C2DM_UNREGISTER == intent.action || ACTION_C2DM_REGISTER == intent.action && "1" == intent.getStringExtra(EXTRA_DELETE)) {
|
|
unregister(intent)
|
|
} else if (ACTION_C2DM_REGISTER == intent.action) {
|
|
register(intent)
|
|
}
|
|
} catch (e: Exception) {
|
|
Log.w(TAG, e)
|
|
replyNotAvailable(intent)
|
|
}
|
|
}
|
|
|
|
private fun replyNotAvailable(intent: Intent) {
|
|
val outIntent = Intent(ACTION_C2DM_REGISTRATION)
|
|
outIntent.putExtra(EXTRA_ERROR, PushRegisterManager.attachRequestId(ERROR_SERVICE_NOT_AVAILABLE, intent.requestId))
|
|
sendReply(intent, intent.appPackageName, outIntent)
|
|
}
|
|
|
|
private suspend fun register(intent: Intent) {
|
|
val packageName = intent.appPackageName ?: throw RuntimeException("No package provided")
|
|
ensureAppRegistrationAllowed(this, database, packageName)
|
|
Log.d(TAG, "register[req]: " + intent.toString() + " extras=" + intent.extras)
|
|
val bundle = completeRegisterRequest(this, database,
|
|
RegisterRequest()
|
|
.build(Utils.getBuild(this))
|
|
.sender(intent.getStringExtra(EXTRA_SENDER))
|
|
.checkin(LastCheckinInfo.read(this))
|
|
.app(packageName)
|
|
.extraParams(intent.extras))
|
|
|
|
val outIntent = Intent(ACTION_C2DM_REGISTRATION)
|
|
outIntent.putExtras(bundle)
|
|
Log.d(TAG, "register[res]: " + outIntent.toString() + " extras=" + outIntent.extras)
|
|
sendReply(intent, packageName, outIntent)
|
|
}
|
|
|
|
private suspend fun unregister(intent: Intent) {
|
|
val packageName = intent.appPackageName ?: throw RuntimeException("No package provided")
|
|
Log.d(TAG, "unregister[req]: " + intent.toString() + " extras=" + intent.extras)
|
|
val bundle = completeRegisterRequest(this, database, RegisterRequest()
|
|
.build(Utils.getBuild(this))
|
|
.sender(intent.getStringExtra(EXTRA_SENDER))
|
|
.checkin(LastCheckinInfo.read(this))
|
|
.app(packageName)
|
|
.extraParams(intent.extras)
|
|
)
|
|
val outIntent = Intent(ACTION_C2DM_REGISTRATION)
|
|
outIntent.putExtras(bundle)
|
|
Log.d(TAG, "unregister[res]: " + outIntent.toString() + " extras=" + outIntent.extras)
|
|
sendReply(intent, packageName, outIntent)
|
|
}
|
|
|
|
private fun sendReply(intent: Intent, packageName: String?, outIntent: Intent) {
|
|
if (sendReplyToMessenger(intent, outIntent)) return
|
|
outIntent.setPackage(packageName)
|
|
sendOrderedBroadcast(outIntent, null)
|
|
}
|
|
|
|
private fun sendReplyToMessenger(intent: Intent, outIntent: Intent): Boolean {
|
|
try {
|
|
val messenger = intent.getParcelableExtra<Messenger>(EXTRA_MESSENGER) ?: return false
|
|
val message = Message.obtain()
|
|
message.obj = outIntent
|
|
messenger.send(message)
|
|
return true
|
|
} catch (e: Exception) {
|
|
Log.w(TAG, e)
|
|
return false
|
|
}
|
|
}
|
|
|
|
override fun onBind(intent: Intent): IBinder? {
|
|
Log.d(TAG, "onBind: $intent")
|
|
super.onBind(intent)
|
|
if (ACTION_C2DM_REGISTER == intent.action) {
|
|
val messenger = Messenger(PushRegisterHandler(this, database, lifecycle))
|
|
return messenger.binder
|
|
}
|
|
return null
|
|
}
|
|
}
|
|
|
|
internal class PushRegisterHandler(private val context: Context, private val database: GcmDatabase, private val lifecycle: Lifecycle) : Handler(), LifecycleOwner {
|
|
override fun getLifecycle(): Lifecycle = lifecycle
|
|
|
|
private var callingUid = 0
|
|
override fun sendMessageAtTime(msg: Message, uptimeMillis: Long): Boolean {
|
|
callingUid = Binder.getCallingUid()
|
|
return super.sendMessageAtTime(msg, uptimeMillis)
|
|
}
|
|
|
|
private fun sendReplyViaMessage(what: Int, id: Int, replyTo: Messenger, messageData: Bundle) {
|
|
val response = Message.obtain()
|
|
response.what = what
|
|
response.arg1 = id
|
|
response.data = messageData
|
|
try {
|
|
replyTo.send(response)
|
|
} catch (e: RemoteException) {
|
|
Log.w(TAG, e)
|
|
}
|
|
}
|
|
|
|
private fun sendReplyViaIntent(outIntent: Intent, replyTo: Messenger) {
|
|
val message = Message.obtain()
|
|
message.obj = outIntent
|
|
try {
|
|
replyTo.send(message)
|
|
} catch (e: RemoteException) {
|
|
Log.w(TAG, e)
|
|
}
|
|
}
|
|
|
|
private fun sendReply(what: Int, id: Int, replyTo: Messenger, data: Bundle) {
|
|
if (what == 0) {
|
|
val outIntent = Intent(ACTION_C2DM_REGISTRATION)
|
|
outIntent.putExtras(data)
|
|
sendReplyViaIntent(outIntent, replyTo)
|
|
return
|
|
}
|
|
val messageData = Bundle()
|
|
messageData.putBundle("data", data)
|
|
sendReplyViaMessage(what, id, replyTo, messageData)
|
|
}
|
|
|
|
private fun replyError(what: Int, id: Int, replyTo: Messenger, errorMessage: String) {
|
|
val bundle = Bundle()
|
|
bundle.putString(EXTRA_ERROR, errorMessage)
|
|
sendReply(what, id, replyTo, bundle)
|
|
}
|
|
|
|
private fun replyNotAvailable(what: Int, id: Int, replyTo: Messenger) {
|
|
replyError(what, id, replyTo, ERROR_SERVICE_NOT_AVAILABLE)
|
|
}
|
|
|
|
private val selfAuthIntent: PendingIntent
|
|
get() {
|
|
val intent = Intent()
|
|
intent.setPackage("com.google.example.invalidpackage")
|
|
return PendingIntent.getBroadcast(context, 0, intent, 0)
|
|
}
|
|
|
|
override fun handleMessage(msg: Message) {
|
|
var getmsg = msg
|
|
val obj = getmsg.obj
|
|
if (getmsg.what == 0) {
|
|
if (obj is Intent) {
|
|
val nuMsg = Message.obtain()
|
|
nuMsg.what = getmsg.what
|
|
nuMsg.arg1 = 0
|
|
nuMsg.replyTo = null
|
|
val packageName = obj.appPackageName
|
|
val data = Bundle()
|
|
data.putBoolean("oneWay", false)
|
|
data.putString("pkg", packageName)
|
|
data.putBundle("data", getmsg.data)
|
|
nuMsg.data = data
|
|
getmsg = nuMsg
|
|
} else {
|
|
return
|
|
}
|
|
}
|
|
val what = getmsg.what
|
|
val id = getmsg.arg1
|
|
val replyTo = getmsg.replyTo
|
|
if (replyTo == null) {
|
|
Log.w(TAG, "replyTo is null")
|
|
return
|
|
}
|
|
val data = getmsg.data
|
|
val packageName = data.getString("pkg") ?: return
|
|
val subdata = data.getBundle("data")
|
|
try {
|
|
PackageUtils.checkPackageUid(context, packageName, callingUid)
|
|
} catch (e: SecurityException) {
|
|
Log.w(TAG, e)
|
|
return
|
|
}
|
|
Log.d(TAG, "handleMessage: package=$packageName what=$what id=$id")
|
|
val oneWay = data.getBoolean("oneWay", false)
|
|
when (what) {
|
|
0, 1 -> {
|
|
lifecycleScope.launchWhenStarted {
|
|
try {
|
|
val sender = subdata?.getString("sender")
|
|
val delete = subdata?.get("delete") != null
|
|
ensureCheckinIsUpToDate(context)
|
|
if (!delete) ensureAppRegistrationAllowed(context, database, packageName)
|
|
val bundle = completeRegisterRequest(context, database,
|
|
RegisterRequest()
|
|
.build(Utils.getBuild(context))
|
|
.sender(sender)
|
|
.checkin(LastCheckinInfo.read(context))
|
|
.app(packageName)
|
|
.delete(delete)
|
|
.extraParams(subdata))
|
|
sendReply(what, id, replyTo, bundle)
|
|
} catch (e: Exception) {
|
|
Log.w(TAG, e)
|
|
replyNotAvailable(what, id, replyTo)
|
|
}
|
|
}
|
|
}
|
|
2 -> {
|
|
val messageId = subdata!!.getString("google.message_id")
|
|
Log.d(TAG, "Ack $messageId for $packageName")
|
|
val i = Intent(context, McsService::class.java)
|
|
i.action = McsConstants.ACTION_ACK
|
|
i.putExtra(EXTRA_APP, selfAuthIntent)
|
|
ForegroundServiceContext(context).startService(i)
|
|
}
|
|
else -> {
|
|
val bundle = Bundle()
|
|
bundle.putBoolean("unsupported", true)
|
|
sendReplyViaMessage(what, id, replyTo, bundle)
|
|
return
|
|
}
|
|
}
|
|
if (oneWay) {
|
|
val bundle = Bundle()
|
|
bundle.putBoolean("ack", true)
|
|
sendReplyViaMessage(what, id, replyTo, bundle)
|
|
}
|
|
}
|
|
}
|
|
|
|
class PushRegisterReceiver : WakefulBroadcastReceiver() {
|
|
override fun onReceive(context: Context, intent: Intent) {
|
|
val intent2 = Intent(context, PushRegisterService::class.java)
|
|
if (intent.extras!!["delete"] != null) {
|
|
intent2.action = ACTION_C2DM_UNREGISTER
|
|
} else {
|
|
intent2.action = ACTION_C2DM_REGISTER
|
|
}
|
|
intent2.putExtras(intent.extras!!)
|
|
startWakefulService(context, intent2)
|
|
}
|
|
}
|