From 983bb24d3e1d218bfe596c0f873530c5d987631b Mon Sep 17 00:00:00 2001 From: Jindrich Kolman Date: Tue, 12 Jul 2022 23:19:29 +0200 Subject: [PATCH 1/4] Begin develop unifiedpush from a copy of gplay Signed-off-by: Jindrich Kolman --- app/src/unifiedpush/AndroidManifest.xml | 52 +++ .../talk/jobs/GetFirebasePushTokenWorker.kt | 73 ++++ .../firebase/ChatAndCallMessagingService.kt | 335 ++++++++++++++++++ .../talk/utils/ClosedInterfaceImpl.kt | 139 ++++++++ 4 files changed, 599 insertions(+) create mode 100644 app/src/unifiedpush/AndroidManifest.xml create mode 100644 app/src/unifiedpush/java/com/nextcloud/talk/jobs/GetFirebasePushTokenWorker.kt create mode 100644 app/src/unifiedpush/java/com/nextcloud/talk/services/firebase/ChatAndCallMessagingService.kt create mode 100644 app/src/unifiedpush/java/com/nextcloud/talk/utils/ClosedInterfaceImpl.kt diff --git a/app/src/unifiedpush/AndroidManifest.xml b/app/src/unifiedpush/AndroidManifest.xml new file mode 100644 index 00000000000..794a2c8a153 --- /dev/null +++ b/app/src/unifiedpush/AndroidManifest.xml @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + + diff --git a/app/src/unifiedpush/java/com/nextcloud/talk/jobs/GetFirebasePushTokenWorker.kt b/app/src/unifiedpush/java/com/nextcloud/talk/jobs/GetFirebasePushTokenWorker.kt new file mode 100644 index 00000000000..afc7c7bb781 --- /dev/null +++ b/app/src/unifiedpush/java/com/nextcloud/talk/jobs/GetFirebasePushTokenWorker.kt @@ -0,0 +1,73 @@ +/* + * Nextcloud Talk application + * + * @author Marcel Hibbe + * Copyright (C) 2022 Marcel Hibbe + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.nextcloud.talk.jobs + +import android.annotation.SuppressLint +import android.content.Context +import android.util.Log +import androidx.work.Data +import androidx.work.OneTimeWorkRequest +import androidx.work.WorkManager +import androidx.work.Worker +import androidx.work.WorkerParameters +import com.google.android.gms.tasks.OnCompleteListener +import com.google.firebase.messaging.FirebaseMessaging +import com.nextcloud.talk.utils.preferences.AppPreferences +import javax.inject.Inject + +class GetFirebasePushTokenWorker(val context: Context, workerParameters: WorkerParameters) : + Worker(context, workerParameters) { + + @JvmField + @Inject + var appPreferences: AppPreferences? = null + + @SuppressLint("LongLogTag") + override fun doWork(): Result { + FirebaseMessaging.getInstance().token.addOnCompleteListener( + OnCompleteListener { task -> + if (!task.isSuccessful) { + Log.w(TAG, "Fetching FCM registration token failed", task.exception) + return@OnCompleteListener + } + + val token = task.result + + appPreferences?.pushToken = token + + val data: Data = + Data.Builder() + .putString(PushRegistrationWorker.ORIGIN, "GetFirebasePushTokenWorker") + .build() + val pushRegistrationWork = OneTimeWorkRequest.Builder(PushRegistrationWorker::class.java) + .setInputData(data) + .build() + WorkManager.getInstance(context).enqueue(pushRegistrationWork) + } + ) + + return Result.success() + } + + companion object { + const val TAG = "GetFirebasePushTokenWorker" + } +} diff --git a/app/src/unifiedpush/java/com/nextcloud/talk/services/firebase/ChatAndCallMessagingService.kt b/app/src/unifiedpush/java/com/nextcloud/talk/services/firebase/ChatAndCallMessagingService.kt new file mode 100644 index 00000000000..b32c3ee7cca --- /dev/null +++ b/app/src/unifiedpush/java/com/nextcloud/talk/services/firebase/ChatAndCallMessagingService.kt @@ -0,0 +1,335 @@ +/* + * Nextcloud Talk application + * + * @author Mario Danic + * @author Tim Krüger + * Copyright (C) 2022 Tim Krüger + * Copyright (C) 2017-2019 Mario Danic + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.nextcloud.talk.services.firebase + +import android.annotation.SuppressLint +import android.app.Notification +import android.app.PendingIntent +import android.content.Intent +import android.net.Uri +import android.os.Build +import android.os.Bundle +import android.os.Handler +import android.util.Base64 +import android.util.Log +import androidx.core.app.NotificationCompat +import androidx.emoji.text.EmojiCompat +import androidx.work.Data +import androidx.work.OneTimeWorkRequest +import androidx.work.WorkManager +import autodagger.AutoInjector +import com.bluelinelabs.logansquare.LoganSquare +import com.google.firebase.messaging.FirebaseMessagingService +import com.google.firebase.messaging.RemoteMessage +import com.nextcloud.talk.R +import com.nextcloud.talk.activities.CallNotificationActivity +import com.nextcloud.talk.api.NcApi +import com.nextcloud.talk.application.NextcloudTalkApplication +import com.nextcloud.talk.application.NextcloudTalkApplication.Companion.sharedApplication +import com.nextcloud.talk.events.CallNotificationClick +import com.nextcloud.talk.jobs.NotificationWorker +import com.nextcloud.talk.jobs.PushRegistrationWorker +import com.nextcloud.talk.models.SignatureVerification +import com.nextcloud.talk.models.json.participants.Participant +import com.nextcloud.talk.models.json.participants.ParticipantsOverall +import com.nextcloud.talk.models.json.push.DecryptedPushMessage +import com.nextcloud.talk.utils.ApiUtils +import com.nextcloud.talk.utils.NotificationUtils +import com.nextcloud.talk.utils.NotificationUtils.cancelAllNotificationsForAccount +import com.nextcloud.talk.utils.NotificationUtils.cancelExistingNotificationWithId +import com.nextcloud.talk.utils.NotificationUtils.getCallRingtoneUri +import com.nextcloud.talk.utils.PushUtils +import com.nextcloud.talk.utils.bundle.BundleKeys +import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_FROM_NOTIFICATION_START_CALL +import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_USER_ENTITY +import com.nextcloud.talk.utils.preferences.AppPreferences +import io.reactivex.Observable +import io.reactivex.Observer +import io.reactivex.disposables.Disposable +import io.reactivex.schedulers.Schedulers +import okhttp3.JavaNetCookieJar +import okhttp3.OkHttpClient +import org.greenrobot.eventbus.EventBus +import org.greenrobot.eventbus.Subscribe +import org.greenrobot.eventbus.ThreadMode +import retrofit2.Retrofit +import java.net.CookieManager +import java.security.InvalidKeyException +import java.security.NoSuchAlgorithmException +import java.security.PrivateKey +import java.util.concurrent.TimeUnit +import javax.crypto.Cipher +import javax.crypto.NoSuchPaddingException +import javax.inject.Inject + +@SuppressLint("LongLogTag") +@AutoInjector(NextcloudTalkApplication::class) +class ChatAndCallMessagingService : FirebaseMessagingService() { + + @Inject + lateinit var appPreferences: AppPreferences + + private var isServiceInForeground: Boolean = false + private var decryptedPushMessage: DecryptedPushMessage? = null + private var signatureVerification: SignatureVerification? = null + private var handler: Handler = Handler() + + @Inject + lateinit var retrofit: Retrofit + + @Inject + lateinit var okHttpClient: OkHttpClient + + @Inject + lateinit var eventBus: EventBus + + override fun onCreate() { + super.onCreate() + sharedApplication!!.componentApplication.inject(this) + eventBus.register(this) + } + + @Subscribe(threadMode = ThreadMode.BACKGROUND) + fun onMessageEvent(event: CallNotificationClick) { + Log.d(TAG, "CallNotification was clicked") + isServiceInForeground = false + stopForeground(true) + } + + override fun onDestroy() { + Log.d(TAG, "onDestroy") + isServiceInForeground = false + eventBus.unregister(this) + stopForeground(true) + handler.removeCallbacksAndMessages(null) + super.onDestroy() + } + + override fun onNewToken(token: String) { + super.onNewToken(token) + sharedApplication!!.componentApplication.inject(this) + appPreferences.pushToken = token + Log.d(TAG, "onNewToken. token = $token") + + val data: Data = Data.Builder().putString(PushRegistrationWorker.ORIGIN, "onNewToken").build() + val pushRegistrationWork = OneTimeWorkRequest.Builder(PushRegistrationWorker::class.java) + .setInputData(data) + .build() + WorkManager.getInstance().enqueue(pushRegistrationWork) + } + + override fun onMessageReceived(remoteMessage: RemoteMessage) { + Log.d(TAG, "onMessageReceived") + sharedApplication!!.componentApplication.inject(this) + if (!remoteMessage.data["subject"].isNullOrEmpty() && !remoteMessage.data["signature"].isNullOrEmpty()) { + decryptMessage(remoteMessage.data["subject"]!!, remoteMessage.data["signature"]!!) + } + } + + @Suppress("Detekt.TooGenericExceptionCaught") + private fun decryptMessage(subject: String, signature: String) { + try { + val base64DecodedSubject = Base64.decode(subject, Base64.DEFAULT) + val base64DecodedSignature = Base64.decode(signature, Base64.DEFAULT) + val pushUtils = PushUtils() + val privateKey = pushUtils.readKeyFromFile(false) as PrivateKey + try { + signatureVerification = pushUtils.verifySignature( + base64DecodedSignature, + base64DecodedSubject + ) + if (signatureVerification!!.signatureValid) { + decryptMessage(privateKey, base64DecodedSubject, subject, signature) + } + } catch (e1: NoSuchAlgorithmException) { + Log.e(NotificationWorker.TAG, "No proper algorithm to decrypt the message.", e1) + } catch (e1: NoSuchPaddingException) { + Log.e(NotificationWorker.TAG, "No proper padding to decrypt the message.", e1) + } catch (e1: InvalidKeyException) { + Log.e(NotificationWorker.TAG, "Invalid private key.", e1) + } + } catch (exception: Exception) { + Log.e(NotificationWorker.TAG, "Something went very wrong!", exception) + } + } + + private fun decryptMessage( + privateKey: PrivateKey, + base64DecodedSubject: ByteArray?, + subject: String, + signature: String + ) { + val cipher = Cipher.getInstance("RSA/None/PKCS1Padding") + cipher.init(Cipher.DECRYPT_MODE, privateKey) + val decryptedSubject = cipher.doFinal(base64DecodedSubject) + decryptedPushMessage = LoganSquare.parse( + String(decryptedSubject), + DecryptedPushMessage::class.java + ) + decryptedPushMessage?.apply { + Log.d(TAG, this.toString()) + timestamp = System.currentTimeMillis() + if (delete) { + cancelExistingNotificationWithId( + applicationContext, + signatureVerification!!.userEntity!!, + notificationId + ) + } else if (deleteAll) { + cancelAllNotificationsForAccount(applicationContext, signatureVerification!!.userEntity!!) + } else if (deleteMultiple) { + notificationIds!!.forEach { + cancelExistingNotificationWithId( + applicationContext, + signatureVerification!!.userEntity!!, + it + ) + } + } else if (type == "call") { + val fullScreenIntent = Intent(applicationContext, CallNotificationActivity::class.java) + val bundle = Bundle() + bundle.putString(BundleKeys.KEY_ROOM_ID, decryptedPushMessage!!.id) + bundle.putParcelable(KEY_USER_ENTITY, signatureVerification!!.userEntity) + bundle.putBoolean(KEY_FROM_NOTIFICATION_START_CALL, true) + fullScreenIntent.putExtras(bundle) + + fullScreenIntent.flags = Intent.FLAG_ACTIVITY_SINGLE_TOP or Intent.FLAG_ACTIVITY_NEW_TASK + val fullScreenPendingIntent = PendingIntent.getActivity( + this@ChatAndCallMessagingService, + 0, + fullScreenIntent, + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT + } else { + PendingIntent.FLAG_UPDATE_CURRENT + } + ) + + val soundUri = getCallRingtoneUri(applicationContext!!, appPreferences) + val notificationChannelId = NotificationUtils.NOTIFICATION_CHANNEL_CALLS_V4 + val uri = Uri.parse(signatureVerification!!.userEntity!!.baseUrl) + val baseUrl = uri.host + + val notification = + NotificationCompat.Builder(this@ChatAndCallMessagingService, notificationChannelId) + .setPriority(NotificationCompat.PRIORITY_HIGH) + .setCategory(NotificationCompat.CATEGORY_CALL) + .setSmallIcon(R.drawable.ic_call_black_24dp) + .setSubText(baseUrl) + .setShowWhen(true) + .setWhen(decryptedPushMessage!!.timestamp) + .setContentTitle(EmojiCompat.get().process(decryptedPushMessage!!.subject)) + .setAutoCancel(true) + .setOngoing(true) + // .setTimeoutAfter(45000L) + .setContentIntent(fullScreenPendingIntent) + .setFullScreenIntent(fullScreenPendingIntent, true) + .setSound(soundUri) + .build() + notification.flags = notification.flags or Notification.FLAG_INSISTENT + isServiceInForeground = true + checkIfCallIsActive(signatureVerification!!, decryptedPushMessage!!) + startForeground(decryptedPushMessage!!.timestamp.toInt(), notification) + } else { + val messageData = Data.Builder() + .putString(BundleKeys.KEY_NOTIFICATION_SUBJECT, subject) + .putString(BundleKeys.KEY_NOTIFICATION_SIGNATURE, signature) + .build() + val pushNotificationWork = + OneTimeWorkRequest.Builder(NotificationWorker::class.java).setInputData(messageData) + .build() + WorkManager.getInstance().enqueue(pushNotificationWork) + } + } + } + + private fun checkIfCallIsActive( + signatureVerification: SignatureVerification, + decryptedPushMessage: DecryptedPushMessage + ) { + Log.d(TAG, "checkIfCallIsActive") + val ncApi = retrofit.newBuilder() + .client(okHttpClient.newBuilder().cookieJar(JavaNetCookieJar(CookieManager())).build()).build() + .create(NcApi::class.java) + var hasParticipantsInCall = true + var inCallOnDifferentDevice = false + + val apiVersion = ApiUtils.getConversationApiVersion( + signatureVerification.userEntity, + intArrayOf(ApiUtils.APIv4, 1) + ) + + ncApi.getPeersForCall( + ApiUtils.getCredentials( + signatureVerification.userEntity!!.username, + signatureVerification.userEntity!!.token + ), + ApiUtils.getUrlForCall( + apiVersion, + signatureVerification.userEntity!!.baseUrl, + decryptedPushMessage.id + ) + ) + .repeatWhen { completed -> + completed.zipWith(Observable.range(1, OBSERVABLE_COUNT), { _, i -> i }) + .flatMap { Observable.timer(OBSERVABLE_DELAY, TimeUnit.SECONDS) } + .takeWhile { isServiceInForeground && hasParticipantsInCall && !inCallOnDifferentDevice } + } + .subscribeOn(Schedulers.io()) + .subscribe(object : Observer { + override fun onSubscribe(d: Disposable) = Unit + + override fun onNext(participantsOverall: ParticipantsOverall) { + val participantList: List = participantsOverall.ocs!!.data!! + hasParticipantsInCall = participantList.isNotEmpty() + if (hasParticipantsInCall) { + for (participant in participantList) { + if (participant.actorId == signatureVerification.userEntity!!.userId && + participant.actorType == Participant.ActorType.USERS + ) { + inCallOnDifferentDevice = true + break + } + } + } + if (!hasParticipantsInCall || inCallOnDifferentDevice) { + Log.d(TAG, "no participants in call OR inCallOnDifferentDevice") + stopForeground(true) + handler.removeCallbacksAndMessages(null) + } + } + + override fun onError(e: Throwable) = Unit + + override fun onComplete() { + stopForeground(true) + handler.removeCallbacksAndMessages(null) + } + }) + } + + companion object { + private val TAG = ChatAndCallMessagingService::class.simpleName + private const val OBSERVABLE_COUNT = 12 + private const val OBSERVABLE_DELAY: Long = 5 + } +} diff --git a/app/src/unifiedpush/java/com/nextcloud/talk/utils/ClosedInterfaceImpl.kt b/app/src/unifiedpush/java/com/nextcloud/talk/utils/ClosedInterfaceImpl.kt new file mode 100644 index 00000000000..94f4abf067a --- /dev/null +++ b/app/src/unifiedpush/java/com/nextcloud/talk/utils/ClosedInterfaceImpl.kt @@ -0,0 +1,139 @@ +/* + * Nextcloud Talk application + * + * @author Mario Danic + * @author Andy Scherzinger + * @author Marcel Hibbe + * Copyright (C) 2017-2019 Mario Danic + * Copyright (C) 2022 Marcel Hibbe + * Copyright (C) 2022 Andy Scherzinger + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.nextcloud.talk.utils + +import android.content.Intent +import androidx.work.Data +import androidx.work.ExistingPeriodicWorkPolicy +import androidx.work.OneTimeWorkRequest +import androidx.work.PeriodicWorkRequest +import androidx.work.WorkManager +import autodagger.AutoInjector +import com.google.android.gms.common.ConnectionResult +import com.google.android.gms.common.GoogleApiAvailability +import com.google.android.gms.security.ProviderInstaller +import com.nextcloud.talk.application.NextcloudTalkApplication +import com.nextcloud.talk.interfaces.ClosedInterface +import com.nextcloud.talk.jobs.GetFirebasePushTokenWorker +import com.nextcloud.talk.jobs.PushRegistrationWorker +import java.util.concurrent.TimeUnit + +@AutoInjector(NextcloudTalkApplication::class) +class ClosedInterfaceImpl : ClosedInterface, ProviderInstaller.ProviderInstallListener { + + override val isGooglePlayServicesAvailable: Boolean = isGPlayServicesAvailable() + + override fun providerInstallerInstallIfNeededAsync() { + NextcloudTalkApplication.sharedApplication?.let { + ProviderInstaller.installIfNeededAsync( + it.applicationContext, + this + ) + } + } + + override fun onProviderInstalled() { + // unused atm + } + + override fun onProviderInstallFailed(p0: Int, p1: Intent?) { + // unused atm + } + + private fun isGPlayServicesAvailable(): Boolean { + val api = GoogleApiAvailability.getInstance() + val code = + NextcloudTalkApplication.sharedApplication?.let { + api.isGooglePlayServicesAvailable( + it.applicationContext + ) + } + return code == ConnectionResult.SUCCESS + } + + override fun setUpPushTokenRegistration() { + registerLocalToken() + setUpPeriodicLocalTokenRegistration() + setUpPeriodicTokenRefreshFromFCM() + } + + private fun registerLocalToken() { + val data: Data = Data.Builder().putString( + PushRegistrationWorker.ORIGIN, + "ClosedInterfaceImpl#registerLocalToken" + ) + .build() + val pushRegistrationWork = OneTimeWorkRequest.Builder(PushRegistrationWorker::class.java) + .setInputData(data) + .build() + WorkManager.getInstance().enqueue(pushRegistrationWork) + } + + private fun setUpPeriodicLocalTokenRegistration() { + val data: Data = Data.Builder().putString( + PushRegistrationWorker.ORIGIN, + "ClosedInterfaceImpl#setUpPeriodicLocalTokenRegistration" + ) + .build() + + val periodicTokenRegistration = PeriodicWorkRequest.Builder( + PushRegistrationWorker::class.java, + DAILY, + TimeUnit.HOURS, + FLEX_INTERVAL, + TimeUnit.HOURS + ) + .setInputData(data) + .build() + + WorkManager.getInstance() + .enqueueUniquePeriodicWork( + "periodicTokenRegistration", ExistingPeriodicWorkPolicy.REPLACE, + periodicTokenRegistration + ) + } + + private fun setUpPeriodicTokenRefreshFromFCM() { + val periodicTokenRefreshFromFCM = PeriodicWorkRequest.Builder( + GetFirebasePushTokenWorker::class.java, + MONTHLY, + TimeUnit.DAYS, + FLEX_INTERVAL, + TimeUnit.DAYS, + ) + .build() + + WorkManager.getInstance() + .enqueueUniquePeriodicWork( + "periodicTokenRefreshFromFCM", ExistingPeriodicWorkPolicy.REPLACE, + periodicTokenRefreshFromFCM + ) + } + + companion object { + const val DAILY: Long = 24 + const val MONTHLY: Long = 30 + const val FLEX_INTERVAL: Long = 10 + } +} From 730638d753f5e86ff91aba3f83c8876d7b1ebefd Mon Sep 17 00:00:00 2001 From: Jindrich Kolman Date: Tue, 12 Jul 2022 23:19:29 +0200 Subject: [PATCH 2/4] Remove firebase specific files Signed-off-by: Jindrich Kolman --- .../talk/jobs/GetFirebasePushTokenWorker.kt | 73 ------------------- 1 file changed, 73 deletions(-) delete mode 100644 app/src/unifiedpush/java/com/nextcloud/talk/jobs/GetFirebasePushTokenWorker.kt diff --git a/app/src/unifiedpush/java/com/nextcloud/talk/jobs/GetFirebasePushTokenWorker.kt b/app/src/unifiedpush/java/com/nextcloud/talk/jobs/GetFirebasePushTokenWorker.kt deleted file mode 100644 index afc7c7bb781..00000000000 --- a/app/src/unifiedpush/java/com/nextcloud/talk/jobs/GetFirebasePushTokenWorker.kt +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Nextcloud Talk application - * - * @author Marcel Hibbe - * Copyright (C) 2022 Marcel Hibbe - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package com.nextcloud.talk.jobs - -import android.annotation.SuppressLint -import android.content.Context -import android.util.Log -import androidx.work.Data -import androidx.work.OneTimeWorkRequest -import androidx.work.WorkManager -import androidx.work.Worker -import androidx.work.WorkerParameters -import com.google.android.gms.tasks.OnCompleteListener -import com.google.firebase.messaging.FirebaseMessaging -import com.nextcloud.talk.utils.preferences.AppPreferences -import javax.inject.Inject - -class GetFirebasePushTokenWorker(val context: Context, workerParameters: WorkerParameters) : - Worker(context, workerParameters) { - - @JvmField - @Inject - var appPreferences: AppPreferences? = null - - @SuppressLint("LongLogTag") - override fun doWork(): Result { - FirebaseMessaging.getInstance().token.addOnCompleteListener( - OnCompleteListener { task -> - if (!task.isSuccessful) { - Log.w(TAG, "Fetching FCM registration token failed", task.exception) - return@OnCompleteListener - } - - val token = task.result - - appPreferences?.pushToken = token - - val data: Data = - Data.Builder() - .putString(PushRegistrationWorker.ORIGIN, "GetFirebasePushTokenWorker") - .build() - val pushRegistrationWork = OneTimeWorkRequest.Builder(PushRegistrationWorker::class.java) - .setInputData(data) - .build() - WorkManager.getInstance(context).enqueue(pushRegistrationWork) - } - ) - - return Result.success() - } - - companion object { - const val TAG = "GetFirebasePushTokenWorker" - } -} From d1b5b6029e78d2fbda4095eb84d7c69777f1938d Mon Sep 17 00:00:00 2001 From: Jindrich Kolman Date: Tue, 12 Jul 2022 23:19:29 +0200 Subject: [PATCH 3/4] Use push notification server url from app preferences Signed-off-by: Jindrich Kolman --- .../main/java/com/nextcloud/talk/utils/ApiUtils.java | 2 +- .../main/java/com/nextcloud/talk/utils/PushUtils.java | 3 +-- .../talk/utils/preferences/AppPreferences.java | 11 +++++++++++ 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/com/nextcloud/talk/utils/ApiUtils.java b/app/src/main/java/com/nextcloud/talk/utils/ApiUtils.java index a493dc785ea..586328da9df 100644 --- a/app/src/main/java/com/nextcloud/talk/utils/ApiUtils.java +++ b/app/src/main/java/com/nextcloud/talk/utils/ApiUtils.java @@ -387,7 +387,7 @@ public static String getUrlNextcloudPush(String baseUrl) { public static String getUrlPushProxy() { return NextcloudTalkApplication.Companion.getSharedApplication(). - getApplicationContext().getResources().getString(R.string.nc_push_server_url) + "/devices"; + appPreferences.getPushServerUrl() + "/devices"; } public static String getUrlForNotificationWithId(String baseUrl, String notificationId) { diff --git a/app/src/main/java/com/nextcloud/talk/utils/PushUtils.java b/app/src/main/java/com/nextcloud/talk/utils/PushUtils.java index 4010ba9c49d..18835b713f1 100644 --- a/app/src/main/java/com/nextcloud/talk/utils/PushUtils.java +++ b/app/src/main/java/com/nextcloud/talk/utils/PushUtils.java @@ -98,8 +98,7 @@ public PushUtils() { String keyPath = NextcloudTalkApplication.Companion.getSharedApplication().getDir("PushKeystore", Context.MODE_PRIVATE).getAbsolutePath(); publicKeyFile = new File(keyPath, "push_key.pub"); privateKeyFile = new File(keyPath, "push_key.priv"); - proxyServer = NextcloudTalkApplication.Companion.getSharedApplication().getResources(). - getString(R.string.nc_push_server_url); + proxyServer = appPreferences.getPushServerUrl(); } public SignatureVerification verifySignature(byte[] signatureBytes, byte[] subjectBytes) { diff --git a/app/src/main/java/com/nextcloud/talk/utils/preferences/AppPreferences.java b/app/src/main/java/com/nextcloud/talk/utils/preferences/AppPreferences.java index 96b53631dac..2812fa28a74 100644 --- a/app/src/main/java/com/nextcloud/talk/utils/preferences/AppPreferences.java +++ b/app/src/main/java/com/nextcloud/talk/utils/preferences/AppPreferences.java @@ -126,6 +126,17 @@ public interface AppPreferences { @RemoveMethod void removePushToken(); + @KeyByString("push_server_url") + @DefaultValue(R.string.nc_push_server_url) + String getPushServerUrl(); + + @KeyByString("push_server_url") + void setPushServerUrl(String pushServerUrl); + + @KeyByString("push_server_url") + @RemoveMethod + void removePushServerUrl(); + @KeyByString("tempClientCertAlias") String getTemporaryClientCertAlias(); From 043fc6a8b46e8c4350aaa61937e78c2395fbf2e3 Mon Sep 17 00:00:00 2001 From: Jindrich Kolman Date: Tue, 12 Jul 2022 23:19:29 +0200 Subject: [PATCH 4/4] Add unifiedpush support Signed-off-by: Jindrich Kolman --- app/build.gradle | 6 + app/src/unifiedpush/AndroidManifest.xml | 20 +-- .../ChatAndCallMessagingService.kt | 14 +- .../talk/utils/ClosedInterfaceImpl.kt | 82 ++---------- .../utils/unifiedpush/ProviderInstaller.kt | 62 +++++++++ .../talk/utils/unifiedpush/RemoteMessage.kt | 39 ++++++ .../UnifiedPushMessagingReceiver.kt | 96 ++++++++++++++ .../UnifiedPushMessagingService.kt | 122 ++++++++++++++++++ 8 files changed, 357 insertions(+), 84 deletions(-) rename app/src/unifiedpush/java/com/nextcloud/talk/services/{firebase => unifiedpush}/ChatAndCallMessagingService.kt (97%) create mode 100644 app/src/unifiedpush/java/com/nextcloud/talk/utils/unifiedpush/ProviderInstaller.kt create mode 100644 app/src/unifiedpush/java/com/nextcloud/talk/utils/unifiedpush/RemoteMessage.kt create mode 100644 app/src/unifiedpush/java/com/nextcloud/talk/utils/unifiedpush/UnifiedPushMessagingReceiver.kt create mode 100644 app/src/unifiedpush/java/com/nextcloud/talk/utils/unifiedpush/UnifiedPushMessagingService.kt diff --git a/app/build.gradle b/app/build.gradle index 40f62082498..131caa85a36 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -62,6 +62,10 @@ android { applicationId 'com.nextcloud.talk2' dimension "default" } + unifiedpush { + applicationId 'com.nextcloud.talk2' + dimension "default" + } qa { applicationId "com.nextcloud.talk2.qa" dimension "default" @@ -336,6 +340,8 @@ dependencies { gplayImplementation 'com.google.android.gms:play-services-base:18.0.1' gplayImplementation "com.google.firebase:firebase-messaging:23.0.5" + unifiedpushImplementation 'com.github.UnifiedPush:android-connector:2.0.1' + // implementation 'androidx.activity:activity-ktx:1.4.0' } diff --git a/app/src/unifiedpush/AndroidManifest.xml b/app/src/unifiedpush/AndroidManifest.xml index 794a2c8a153..1bbaaa7e8bb 100644 --- a/app/src/unifiedpush/AndroidManifest.xml +++ b/app/src/unifiedpush/AndroidManifest.xml @@ -36,17 +36,21 @@ tools:replace="label, icon, theme, name, allowBackup" tools:ignore="UnusedAttribute, ExportedService"> - - - + android:foregroundServiceType="phoneCall" + android:permission="android.permission.BIND_JOB_SERVICE" /> + + - + + + + - - + diff --git a/app/src/unifiedpush/java/com/nextcloud/talk/services/firebase/ChatAndCallMessagingService.kt b/app/src/unifiedpush/java/com/nextcloud/talk/services/unifiedpush/ChatAndCallMessagingService.kt similarity index 97% rename from app/src/unifiedpush/java/com/nextcloud/talk/services/firebase/ChatAndCallMessagingService.kt rename to app/src/unifiedpush/java/com/nextcloud/talk/services/unifiedpush/ChatAndCallMessagingService.kt index b32c3ee7cca..0f503bfbb09 100644 --- a/app/src/unifiedpush/java/com/nextcloud/talk/services/firebase/ChatAndCallMessagingService.kt +++ b/app/src/unifiedpush/java/com/nextcloud/talk/services/unifiedpush/ChatAndCallMessagingService.kt @@ -19,7 +19,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package com.nextcloud.talk.services.firebase +package com.nextcloud.talk.services.unifiedpush import android.annotation.SuppressLint import android.app.Notification @@ -33,13 +33,14 @@ import android.util.Base64 import android.util.Log import androidx.core.app.NotificationCompat import androidx.emoji.text.EmojiCompat +import androidx.work.Configuration import androidx.work.Data import androidx.work.OneTimeWorkRequest import androidx.work.WorkManager import autodagger.AutoInjector import com.bluelinelabs.logansquare.LoganSquare -import com.google.firebase.messaging.FirebaseMessagingService -import com.google.firebase.messaging.RemoteMessage +import com.nextcloud.talk.utils.unifiedpush.UnifiedPushMessagingService +import com.nextcloud.talk.utils.unifiedpush.RemoteMessage import com.nextcloud.talk.R import com.nextcloud.talk.activities.CallNotificationActivity import com.nextcloud.talk.api.NcApi @@ -83,12 +84,11 @@ import javax.inject.Inject @SuppressLint("LongLogTag") @AutoInjector(NextcloudTalkApplication::class) -class ChatAndCallMessagingService : FirebaseMessagingService() { +class ChatAndCallMessagingService : UnifiedPushMessagingService() { @Inject lateinit var appPreferences: AppPreferences - private var isServiceInForeground: Boolean = false private var decryptedPushMessage: DecryptedPushMessage? = null private var signatureVerification: SignatureVerification? = null private var handler: Handler = Handler() @@ -102,6 +102,10 @@ class ChatAndCallMessagingService : FirebaseMessagingService() { @Inject lateinit var eventBus: EventBus + init { + Configuration.Builder().setJobSchedulerJobIdRange(0, 1000) + } + override fun onCreate() { super.onCreate() sharedApplication!!.componentApplication.inject(this) diff --git a/app/src/unifiedpush/java/com/nextcloud/talk/utils/ClosedInterfaceImpl.kt b/app/src/unifiedpush/java/com/nextcloud/talk/utils/ClosedInterfaceImpl.kt index 94f4abf067a..1174bd9c24a 100644 --- a/app/src/unifiedpush/java/com/nextcloud/talk/utils/ClosedInterfaceImpl.kt +++ b/app/src/unifiedpush/java/com/nextcloud/talk/utils/ClosedInterfaceImpl.kt @@ -4,9 +4,11 @@ * @author Mario Danic * @author Andy Scherzinger * @author Marcel Hibbe + * @author Jindrich Kolman * Copyright (C) 2017-2019 Mario Danic * Copyright (C) 2022 Marcel Hibbe * Copyright (C) 2022 Andy Scherzinger + * Copyright (C) 2022 Jindrich Kolman * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -30,19 +32,15 @@ import androidx.work.OneTimeWorkRequest import androidx.work.PeriodicWorkRequest import androidx.work.WorkManager import autodagger.AutoInjector -import com.google.android.gms.common.ConnectionResult -import com.google.android.gms.common.GoogleApiAvailability -import com.google.android.gms.security.ProviderInstaller +import com.nextcloud.talk.utils.unifiedpush.ProviderInstaller import com.nextcloud.talk.application.NextcloudTalkApplication import com.nextcloud.talk.interfaces.ClosedInterface -import com.nextcloud.talk.jobs.GetFirebasePushTokenWorker -import com.nextcloud.talk.jobs.PushRegistrationWorker -import java.util.concurrent.TimeUnit +import android.util.Log @AutoInjector(NextcloudTalkApplication::class) class ClosedInterfaceImpl : ClosedInterface, ProviderInstaller.ProviderInstallListener { - override val isGooglePlayServicesAvailable: Boolean = isGPlayServicesAvailable() + override val isGooglePlayServicesAvailable: Boolean = isUnifiedPushAvailable() override fun providerInstallerInstallIfNeededAsync() { NextcloudTalkApplication.sharedApplication?.let { @@ -61,79 +59,21 @@ class ClosedInterfaceImpl : ClosedInterface, ProviderInstaller.ProviderInstallLi // unused atm } - private fun isGPlayServicesAvailable(): Boolean { - val api = GoogleApiAvailability.getInstance() - val code = + private fun isUnifiedPushAvailable(): Boolean { + val unifiedPushAvailable = NextcloudTalkApplication.sharedApplication?.let { - api.isGooglePlayServicesAvailable( + ProviderInstaller.isUnifiedPushAvailable( it.applicationContext ) } - return code == ConnectionResult.SUCCESS + return unifiedPushAvailable == true } override fun setUpPushTokenRegistration() { - registerLocalToken() - setUpPeriodicLocalTokenRegistration() - setUpPeriodicTokenRefreshFromFCM() - } - - private fun registerLocalToken() { - val data: Data = Data.Builder().putString( - PushRegistrationWorker.ORIGIN, - "ClosedInterfaceImpl#registerLocalToken" - ) - .build() - val pushRegistrationWork = OneTimeWorkRequest.Builder(PushRegistrationWorker::class.java) - .setInputData(data) - .build() - WorkManager.getInstance().enqueue(pushRegistrationWork) - } - - private fun setUpPeriodicLocalTokenRegistration() { - val data: Data = Data.Builder().putString( - PushRegistrationWorker.ORIGIN, - "ClosedInterfaceImpl#setUpPeriodicLocalTokenRegistration" - ) - .build() - - val periodicTokenRegistration = PeriodicWorkRequest.Builder( - PushRegistrationWorker::class.java, - DAILY, - TimeUnit.HOURS, - FLEX_INTERVAL, - TimeUnit.HOURS - ) - .setInputData(data) - .build() - - WorkManager.getInstance() - .enqueueUniquePeriodicWork( - "periodicTokenRegistration", ExistingPeriodicWorkPolicy.REPLACE, - periodicTokenRegistration - ) - } - - private fun setUpPeriodicTokenRefreshFromFCM() { - val periodicTokenRefreshFromFCM = PeriodicWorkRequest.Builder( - GetFirebasePushTokenWorker::class.java, - MONTHLY, - TimeUnit.DAYS, - FLEX_INTERVAL, - TimeUnit.DAYS, - ) - .build() - - WorkManager.getInstance() - .enqueueUniquePeriodicWork( - "periodicTokenRefreshFromFCM", ExistingPeriodicWorkPolicy.REPLACE, - periodicTokenRefreshFromFCM - ) + android.util.Log.d(TAG, "setUpPushTokenRegistration") } companion object { - const val DAILY: Long = 24 - const val MONTHLY: Long = 30 - const val FLEX_INTERVAL: Long = 10 + const val TAG = "ClosedInterfaceImpl" } } diff --git a/app/src/unifiedpush/java/com/nextcloud/talk/utils/unifiedpush/ProviderInstaller.kt b/app/src/unifiedpush/java/com/nextcloud/talk/utils/unifiedpush/ProviderInstaller.kt new file mode 100644 index 00000000000..cb7ae86ecd8 --- /dev/null +++ b/app/src/unifiedpush/java/com/nextcloud/talk/utils/unifiedpush/ProviderInstaller.kt @@ -0,0 +1,62 @@ +/* + * Nextcloud Talk application + * + * @author Mario Danic + * @author Jindrich Kolman + * Copyright (C) 2017-2022 Mario Danic + * Copyright (C) 2022 Jindrich Kolman + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.nextcloud.talk.utils.unifiedpush + +import android.annotation.SuppressLint +import android.content.Context +import android.content.Intent +import android.util.Log +import org.unifiedpush.android.connector.UnifiedPush + +@SuppressLint("LongLogTag") +open class ProviderInstaller { + + interface ProviderInstallListener { + + fun onProviderInstalled() { + // unused atm + } + + fun onProviderInstallFailed(p0: Int, p1: Intent?) { + // unused atm + } + + companion object { + const val TAG = "ProviderInstaller.ProviderInstallListener" + } + } + + companion object { + lateinit var listener: ProviderInstallListener + fun installIfNeededAsync(context: Context, listener: ProviderInstallListener) { + this.listener = listener + UnifiedPush.registerAppWithDialog(context) + } + + fun isUnifiedPushAvailable(context: Context): Boolean { + return UnifiedPush.getDistributors(context).isNotEmpty() + } + + const val TAG = "ProviderInstaller" + } +} diff --git a/app/src/unifiedpush/java/com/nextcloud/talk/utils/unifiedpush/RemoteMessage.kt b/app/src/unifiedpush/java/com/nextcloud/talk/utils/unifiedpush/RemoteMessage.kt new file mode 100644 index 00000000000..36ec749e4df --- /dev/null +++ b/app/src/unifiedpush/java/com/nextcloud/talk/utils/unifiedpush/RemoteMessage.kt @@ -0,0 +1,39 @@ +/* + * Nextcloud Talk application + * + * @author Mario Danic + * Copyright (C) 2017-2022 Mario Danic + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.nextcloud.talk.utils.unifiedpush + +import com.google.gson.Gson +import com.google.gson.JsonParseException +import com.google.gson.reflect.TypeToken + +inline fun Gson.fromJson(json: String): T = fromJson(json, object : TypeToken() {}.type) + +typealias RemoteMessageJsonParseException = com.google.gson.JsonParseException + +open class RemoteMessage { + + var data: HashMap = HashMap() + + open fun fromJson(json: String): RemoteMessage { + this.data = Gson().fromJson(json) + return this + } +} diff --git a/app/src/unifiedpush/java/com/nextcloud/talk/utils/unifiedpush/UnifiedPushMessagingReceiver.kt b/app/src/unifiedpush/java/com/nextcloud/talk/utils/unifiedpush/UnifiedPushMessagingReceiver.kt new file mode 100644 index 00000000000..1c3c36e5040 --- /dev/null +++ b/app/src/unifiedpush/java/com/nextcloud/talk/utils/unifiedpush/UnifiedPushMessagingReceiver.kt @@ -0,0 +1,96 @@ +/* + * Nextcloud Talk application + * + * @author Jindrich Kolman + * Copyright (C) 2022 Jindrich Kolman + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.nextcloud.talk.utils.unifiedpush + +import android.annotation.SuppressLint +import android.app.job.JobInfo +import android.app.job.JobScheduler +import android.content.ComponentName +import android.content.Context +import android.content.Intent +import android.os.PersistableBundle +import android.util.Log +import com.nextcloud.talk.application.NextcloudTalkApplication +import com.nextcloud.talk.services.unifiedpush.ChatAndCallMessagingService +import org.unifiedpush.android.connector.EXTRA_MESSAGE +import org.unifiedpush.android.connector.ACTION_MESSAGE +import org.unifiedpush.android.connector.ACTION_NEW_ENDPOINT +import org.unifiedpush.android.connector.EXTRA_ENDPOINT +import org.unifiedpush.android.connector.MessagingReceiver +import javax.inject.Inject + +@SuppressLint("LongLogTag") +open class UnifiedPushMessagingReceiver : MessagingReceiver() { + @JvmField + @Inject + var context: Context = NextcloudTalkApplication.sharedApplication!!.applicationContext + + override fun onMessage(context: Context, message: ByteArray, instance: String) { + val strMessage = String(message) + Log.d(TAG, "New Message: $instance $strMessage") + scheduleJob( + PersistableBundle().apply { + putString("action", ACTION_MESSAGE) + putString(EXTRA_MESSAGE, strMessage) + } + ) + } + + override fun onNewEndpoint(context: Context, endpoint: String, instance: String) { + scheduleJob( + PersistableBundle().apply { + putString("action", ACTION_NEW_ENDPOINT) + putString(EXTRA_ENDPOINT, endpoint) + } + ) + } + + override fun onRegistrationFailed(context: Context, instance: String) { + Log.d(TAG, "Registration Failed: $instance") + } + + override fun onUnregistered(context: Context, instance: String) { + Log.d(TAG, "$instance is unregistered") + } + + override fun onReceive(context: Context, intent: Intent) { + Log.d(TAG, "event received $intent") + super.onReceive(context, intent) + } + + open fun scheduleJob(input: PersistableBundle) { + Log.d(TAG, "ScheduleJob") + val jobScheduler: JobScheduler = this.context.getSystemService(Context.JOB_SCHEDULER_SERVICE) as JobScheduler + jobScheduler.schedule( + JobInfo.Builder( + 0, + ComponentName(this.context, ChatAndCallMessagingService::class.java) + ).apply { + setOverrideDeadline((120 * 1000).toLong()) // Maximum delay 120s + setExtras(input) + }.build() + ) + } + + companion object { + const val TAG = "UnifiedPushMessagingReceiver" + } +} diff --git a/app/src/unifiedpush/java/com/nextcloud/talk/utils/unifiedpush/UnifiedPushMessagingService.kt b/app/src/unifiedpush/java/com/nextcloud/talk/utils/unifiedpush/UnifiedPushMessagingService.kt new file mode 100644 index 00000000000..c4a0d77e082 --- /dev/null +++ b/app/src/unifiedpush/java/com/nextcloud/talk/utils/unifiedpush/UnifiedPushMessagingService.kt @@ -0,0 +1,122 @@ +/* + * Nextcloud Talk application + * + * @author Jindrich Kolman + * Copyright (C) 2022 Jindrich Kolman + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.nextcloud.talk.utils.unifiedpush + +import android.annotation.SuppressLint +import android.app.job.JobParameters +import android.app.job.JobService +import android.net.Uri +import android.os.* +import android.util.Log +import androidx.work.Configuration +import com.nextcloud.talk.application.NextcloudTalkApplication +import org.unifiedpush.android.connector.ACTION_MESSAGE +import org.unifiedpush.android.connector.ACTION_NEW_ENDPOINT +import org.unifiedpush.android.connector.EXTRA_ENDPOINT +import org.unifiedpush.android.connector.EXTRA_MESSAGE + +@SuppressLint("LongLogTag") +open class UnifiedPushMessagingService : JobService() { + var isServiceInForeground: Boolean = false + private var params: JobParameters? = null + + open fun onNewToken(token: String) {} + open fun onMessageReceived(remoteMessage: RemoteMessage) {} + + override fun onStartJob(params: JobParameters?): Boolean { + Log.d(TAG, "onStartJob") + this.params = params + params?.let { + handleMessage( + it.extras.getString("action")!!, + it.extras.getString(EXTRA_MESSAGE), + it.extras.getString(EXTRA_ENDPOINT) + ) + } + return isServiceInForeground + } + + private fun handleMessage(action: String, message: String?, endpoint: String?) { + when (action) { + ACTION_NEW_ENDPOINT -> { + onNewEndpoint(endpoint!!) + } + ACTION_MESSAGE -> { + onMessage(message!!) + } + } + } + + private fun onMessage(message: String) { + try { + onMessageReceived( + RemoteMessage().fromJson(message) + ) + } catch (e: RemoteMessageJsonParseException) { + Log.d(TAG, "while parsing received message as JSON: $e") + } + checkForeground() + } + + private fun checkForeground() { + Log.d(TAG, "checkForeground") + val handler = Handler() + + if (isServiceInForeground) { + handler.postDelayed({ checkForeground() }, 10000) + } else { + jobFinished(this.params, false) + } + } + + override fun onStopJob(params: JobParameters?): Boolean { + Log.d(TAG, "Not yet implemented") + stopForeground(true) + isServiceInForeground = false + return true + } + + open fun onNewEndpoint(endpoint: String) { + Log.d(TAG, "New Endpoint: $endpoint") + val uri: Uri = Uri.parse(endpoint) + val protocol: String? = uri.scheme + val server: String? = uri.authority + val path: String? = uri.path + val args: Set = uri.queryParameterNames + Log.d(UnifiedPushMessagingReceiver.TAG, "Parsed Endpoint $protocol $server $path") + var token: String? = null + if (path == "/UP") { + setPushServerUrl("$protocol://$server$path") + token = uri.getQueryParameter("token") + } + if (token is String) { + onNewToken(token) + } + } + + private fun setPushServerUrl(url: String) { + NextcloudTalkApplication.sharedApplication!!.appPreferences.pushServerUrl = url + } + + companion object { + const val TAG = "UnifiedPushMessagingService" + } +}