Notifications & various other bug fixes

This commit is contained in:
Mario Danic 2019-12-18 14:00:57 +01:00
parent ee7cd4d60b
commit 5ddd3f8422
No known key found for this signature in database
GPG Key ID: CDE0BBD2738C4CC0
20 changed files with 364 additions and 445 deletions

View File

@ -333,4 +333,5 @@ dependencies {
findbugsPlugins 'com.mebigfatguy.fb-contrib:fb-contrib:7.4.7' findbugsPlugins 'com.mebigfatguy.fb-contrib:fb-contrib:7.4.7'
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.0.0' implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.0.0'
implementation 'com.github.Kennyc1012:BottomSheet:2.4.1' implementation 'com.github.Kennyc1012:BottomSheet:2.4.1'
implementation 'com.google.firebase:firebase-messaging:20.1.0'
} }

View File

@ -19,6 +19,6 @@
*/ */
dependencies { dependencies {
implementation "androidx.work:work-gcm:2.3.0-alpha02" implementation "androidx.work:work-gcm:2.3.0-beta01"
implementation "com.google.firebase:firebase-messaging:20.0.0" implementation "com.google.firebase:firebase-messaging:20.1.0"
} }

View File

@ -20,10 +20,8 @@
package com.nextcloud.talk.services.firebase package com.nextcloud.talk.services.firebase
import android.annotation.SuppressLint import android.annotation.SuppressLint
import autodagger.AutoInjector
import com.google.firebase.messaging.FirebaseMessagingService import com.google.firebase.messaging.FirebaseMessagingService
import com.google.firebase.messaging.RemoteMessage import com.google.firebase.messaging.RemoteMessage
import com.nextcloud.talk.application.NextcloudTalkApplication
import com.nextcloud.talk.jobs.NotificationWorker import com.nextcloud.talk.jobs.NotificationWorker
import com.nextcloud.talk.jobs.PushRegistrationWorker import com.nextcloud.talk.jobs.PushRegistrationWorker
import com.nextcloud.talk.utils.bundle.BundleKeys import com.nextcloud.talk.utils.bundle.BundleKeys
@ -31,33 +29,31 @@ import androidx.work.Data
import androidx.work.OneTimeWorkRequest import androidx.work.OneTimeWorkRequest
import androidx.work.WorkManager import androidx.work.WorkManager
import com.nextcloud.talk.utils.preferences.AppPreferences import com.nextcloud.talk.utils.preferences.AppPreferences
import org.koin.core.KoinComponent
import org.koin.core.inject
class MagicFirebaseMessagingService : FirebaseMessagingService(), KoinComponent { class MagicFirebaseMessagingService : FirebaseMessagingService(), KoinComponent {
val appPreferences: AppPreferences by inject() val appPreferences: AppPreferences by inject()
@Override override fun onNewToken(token: String) {
fun onNewToken(token: String?) {
super.onNewToken(token) super.onNewToken(token)
appPreferences.setPushToken(token) appPreferences.pushToken = token
val pushRegistrationWork: OneTimeWorkRequest = Builder(PushRegistrationWorker::class.java).build() val pushRegistrationWork: OneTimeWorkRequest = OneTimeWorkRequest.Builder(PushRegistrationWorker::class.java).build()
WorkManager.getInstance().enqueue(pushRegistrationWork) WorkManager.getInstance().enqueue(pushRegistrationWork)
} }
@SuppressLint("LongLogTag") @SuppressLint("LongLogTag")
@Override override fun onMessageReceived(remoteMessage: RemoteMessage) {
fun onMessageReceived(remoteMessage: RemoteMessage?) { remoteMessage.data.let {
if (remoteMessage == null) { val messageData: Data = Data.Builder()
return .putString(BundleKeys.KEY_NOTIFICATION_SUBJECT, it["subject"])
} .putString(BundleKeys.KEY_NOTIFICATION_SIGNATURE, it["signature"])
if (remoteMessage.getData() != null) {
val messageData: Data = Builder()
.putString(BundleKeys.INSTANCE.getKEY_NOTIFICATION_SUBJECT(), remoteMessage.getData().get("subject"))
.putString(BundleKeys.INSTANCE.getKEY_NOTIFICATION_SIGNATURE(), remoteMessage.getData().get("signature"))
.build() .build()
val pushNotificationWork: OneTimeWorkRequest = Builder(NotificationWorker::class.java) val pushNotificationWork: OneTimeWorkRequest = OneTimeWorkRequest.Builder(NotificationWorker::class.java)
.setInputData(messageData) .setInputData(messageData)
.build() .build()
WorkManager.getInstance().enqueue(pushNotificationWork) WorkManager.getInstance().enqueue(pushNotificationWork)
} }
} }
} }

View File

@ -162,8 +162,6 @@ class NextcloudTalkApplication : Application(), LifecycleObserver {
12, TimeUnit.HOURS 12, TimeUnit.HOURS
) )
.build() .build()
val capabilitiesUpdateWork = OneTimeWorkRequest.Builder(CapabilitiesWorker::class.java)
.build()
val signalingSettingsWork = OneTimeWorkRequest.Builder(SignalingSettingsWorker::class.java) val signalingSettingsWork = OneTimeWorkRequest.Builder(SignalingSettingsWorker::class.java)
.build() .build()
@ -171,8 +169,6 @@ class NextcloudTalkApplication : Application(), LifecycleObserver {
.enqueue(pushRegistrationWork) .enqueue(pushRegistrationWork)
WorkManager.getInstance() WorkManager.getInstance()
.enqueue(accountRemovalWork) .enqueue(accountRemovalWork)
WorkManager.getInstance()
.enqueue(capabilitiesUpdateWork)
WorkManager.getInstance() WorkManager.getInstance()
.enqueue(signalingSettingsWork) .enqueue(signalingSettingsWork)
WorkManager.getInstance() WorkManager.getInstance()

View File

@ -1007,22 +1007,12 @@ class CallController(args: Bundle) : BaseController() {
override fun onNext(capabilitiesOverall: CapabilitiesOverall) { override fun onNext(capabilitiesOverall: CapabilitiesOverall) {
isMultiSession = capabilitiesOverall.ocs.data isMultiSession = capabilitiesOverall.ocs.data
.capabilities != null && capabilitiesOverall.ocs.data
.capabilities.spreedCapability != null &&
capabilitiesOverall.ocs.data
.capabilities.spreedCapability .capabilities.spreedCapability
.features != null && capabilitiesOverall.ocs.data ?.features?.contains("multi-room-users") == true
.capabilities.spreedCapability
.features.contains("multi-room-users")
needsPing = !(capabilitiesOverall.ocs.data needsPing = capabilitiesOverall.ocs.data
.capabilities != null && capabilitiesOverall.ocs.data
.capabilities.spreedCapability != null &&
capabilitiesOverall.ocs.data
.capabilities.spreedCapability .capabilities.spreedCapability
.features != null && capabilitiesOverall.ocs.data ?.features?.contains("no-ping") == false
.capabilities.spreedCapability
.features.contains("no-ping"))
if (!hasExternalSignalingServer) { if (!hasExternalSignalingServer) {
joinRoomAndCall() joinRoomAndCall()

View File

@ -59,6 +59,8 @@ import com.nextcloud.talk.models.json.conversations.Conversation
import com.nextcloud.talk.models.json.conversations.RoomsOverall import com.nextcloud.talk.models.json.conversations.RoomsOverall
import com.nextcloud.talk.models.json.participants.Participant import com.nextcloud.talk.models.json.participants.Participant
import com.nextcloud.talk.models.json.participants.ParticipantsOverall import com.nextcloud.talk.models.json.participants.ParticipantsOverall
import com.nextcloud.talk.newarch.local.models.UserNgEntity
import com.nextcloud.talk.newarch.local.models.getCredentials
import com.nextcloud.talk.newarch.utils.getCredentials import com.nextcloud.talk.newarch.utils.getCredentials
import com.nextcloud.talk.utils.ApiUtils import com.nextcloud.talk.utils.ApiUtils
import com.nextcloud.talk.utils.DoNotDisturbUtils import com.nextcloud.talk.utils.DoNotDisturbUtils
@ -108,7 +110,7 @@ class CallNotificationController(private val originalBundle: Bundle) : BaseContr
@BindView(R.id.incomingTextRelativeLayout) @BindView(R.id.incomingTextRelativeLayout)
var incomingTextRelativeLayout: RelativeLayout? = null var incomingTextRelativeLayout: RelativeLayout? = null
private val roomId: String private val roomId: String
private val userBeingCalled: UserEntity? private val userBeingCalled: UserNgEntity?
private val credentials: String? private val credentials: String?
private var currentConversation: Conversation? = null private var currentConversation: Conversation? = null
private var mediaPlayer: MediaPlayer? = null private var mediaPlayer: MediaPlayer? = null
@ -282,7 +284,7 @@ class CallNotificationController(private val originalBundle: Bundle) : BaseContr
var importantConversation = false var importantConversation = false
val arbitraryStorageEntity: ArbitraryStorageEntity? = arbitraryStorageUtils.getStorageSetting( val arbitraryStorageEntity: ArbitraryStorageEntity? = arbitraryStorageUtils.getStorageSetting(
userBeingCalled!!.id, userBeingCalled!!.id!!,
"important_conversation", "important_conversation",
currentConversation!!.token currentConversation!!.token
) )

View File

@ -20,30 +20,151 @@
package com.nextcloud.talk.jobs package com.nextcloud.talk.jobs
import android.annotation.SuppressLint
import android.app.Application
import android.content.Context import android.content.Context
import android.text.TextUtils
import android.util.Base64
import android.util.Log
import androidx.work.CoroutineWorker
import androidx.work.ListenableWorker.Result import androidx.work.ListenableWorker.Result
import androidx.work.Worker
import androidx.work.WorkerParameters import androidx.work.WorkerParameters
import com.nextcloud.talk.R
import com.nextcloud.talk.api.NcApi
import com.nextcloud.talk.events.EventStatus
import com.nextcloud.talk.models.json.push.PushConfigurationState
import com.nextcloud.talk.models.json.push.PushRegistrationOverall
import com.nextcloud.talk.newarch.domain.repository.offline.UsersRepository import com.nextcloud.talk.newarch.domain.repository.offline.UsersRepository
import com.nextcloud.talk.newarch.local.models.getCredentials
import com.nextcloud.talk.newarch.utils.hashWithAlgorithm
import com.nextcloud.talk.utils.ApiUtils
import com.nextcloud.talk.utils.PushUtils import com.nextcloud.talk.utils.PushUtils
import com.nextcloud.talk.utils.preferences.AppPreferences
import io.reactivex.Observer
import io.reactivex.disposables.Disposable
import io.reactivex.schedulers.Schedulers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import org.greenrobot.eventbus.EventBus
import org.koin.core.KoinComponent import org.koin.core.KoinComponent
import org.koin.core.inject import org.koin.core.inject
import java.security.PublicKey
import java.util.*
class PushRegistrationWorker( class PushRegistrationWorker(
context: Context, context: Context,
workerParams: WorkerParameters workerParams: WorkerParameters
) : Worker(context, workerParams), KoinComponent { ) : CoroutineWorker(context, workerParams), KoinComponent {
val usersRepository: UsersRepository by inject() val usersRepository: UsersRepository by inject()
val eventBus: EventBus by inject()
val appPreferences: AppPreferences by inject()
val application: Application by inject()
val ncApi: NcApi by inject()
override fun doWork(): Result { override suspend fun doWork(): Result {
val pushUtils = PushUtils(usersRepository) val pushUtils = PushUtils(usersRepository)
pushUtils.generateRsa2048KeyPair() pushUtils.generateRsa2048KeyPair()
pushUtils.pushRegistrationToServer() pushRegistrationToServer()
return Result.success() return Result.success()
} }
private fun pushRegistrationToServer() {
val token: String = appPreferences.pushToken
if (!TextUtils.isEmpty(token)) {
var credentials: String
val pushUtils = PushUtils(usersRepository)
val pushTokenHash = token.hashWithAlgorithm("SHA-512")
val devicePublicKey = pushUtils.readKeyFromFile(true) as PublicKey?
if (devicePublicKey != null) {
val publicKeyBytes: ByteArray? =
Base64.encode(devicePublicKey.encoded, Base64.NO_WRAP)
var publicKey = String(publicKeyBytes!!)
publicKey = publicKey.replace("(.{64})".toRegex(), "$1\n")
publicKey = "-----BEGIN PUBLIC KEY-----\n$publicKey\n-----END PUBLIC KEY-----\n"
val users = usersRepository.getUsers()
if (users.count() > 0) {
var accountPushData: PushConfigurationState?
for (userEntityObject in users) {
accountPushData = userEntityObject.pushConfiguration
if (accountPushData == null || accountPushData.pushToken != token) {
val queryMap: MutableMap<String, String> =
HashMap()
queryMap["format"] = "json"
queryMap["pushTokenHash"] = pushTokenHash
queryMap["devicePublicKey"] = publicKey
queryMap["proxyServer"] = application.getString(R.string.nc_push_server_url)
credentials = userEntityObject.getCredentials()
ncApi.registerDeviceForNotificationsWithNextcloud(
credentials,
ApiUtils.getUrlNextcloudPush(userEntityObject.baseUrl),
queryMap
)
.blockingSubscribe(object : Observer<PushRegistrationOverall> {
override fun onSubscribe(d: Disposable) {}
@SuppressLint("CheckResult")
override fun onNext(pushRegistrationOverall: PushRegistrationOverall) {
val proxyMap: MutableMap<String, String> =
HashMap()
proxyMap["pushToken"] = token
proxyMap["deviceIdentifier"] =
pushRegistrationOverall.ocs.data.deviceIdentifier
proxyMap["deviceIdentifierSignature"] = pushRegistrationOverall.ocs
.data.signature
proxyMap["userPublicKey"] = pushRegistrationOverall.ocs
.data.publicKey
ncApi.registerDeviceForNotificationsWithProxy(
ApiUtils.getUrlPushProxy(), proxyMap
).subscribe({
val pushConfigurationState = PushConfigurationState()
pushConfigurationState.pushToken = token
pushConfigurationState.deviceIdentifier = proxyMap["deviceIdentifier"]
pushConfigurationState.deviceIdentifierSignature = proxyMap["deviceIdentifierSignature"]
pushConfigurationState.userPublicKey = proxyMap["userPublicKey"]
pushConfigurationState.usesRegularPass = false
GlobalScope.launch {
val user = usersRepository.getUserWithId(userEntityObject.id!!)
user.pushConfiguration = pushConfigurationState
usersRepository.updateUser(user)
}
eventBus.post(
EventStatus(
userEntityObject.id!!,
EventStatus.EventType.PUSH_REGISTRATION,
true
)
)
}, {
eventBus.post(
EventStatus(
userEntityObject.id!!,
EventStatus.EventType.PUSH_REGISTRATION,
false))
})
}
override fun onError(e: Throwable) {
eventBus.post(
EventStatus(
userEntityObject.id!!,
EventStatus.EventType.PUSH_REGISTRATION,
false
)
)
}
override fun onComplete() {}
})
}
}
}
}
}
}
companion object { companion object {
const val TAG = "PushRegistrationWorker" const val TAG = "PushRegistrationWorker"
} }
} }

View File

@ -36,8 +36,6 @@ data class ExternalSignalingServer(
var externalSignalingServer: String? = null, var externalSignalingServer: String? = null,
@JsonField(name = ["externalSignalingTicket"]) @JsonField(name = ["externalSignalingTicket"])
var externalSignalingTicket: String? = null var externalSignalingTicket: String? = null
) : Parcelable { ) : Parcelable {
override fun equals(other: Any?): Boolean { override fun equals(other: Any?): Boolean {
if (this === other) return true if (this === other) return true

View File

@ -1,64 +0,0 @@
/*
* Nextcloud Talk application
*
* @author Mario Danic
* Copyright (C) 2017-2018 Mario Danic <mario@lovelyhq.com>
*
* 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 <http://www.gnu.org/licenses/>.
*/
package com.nextcloud.talk.models.json.capabilities;
import com.bluelinelabs.logansquare.annotation.JsonField;
import com.bluelinelabs.logansquare.annotation.JsonObject;
import org.parceler.Parcel;
import java.util.HashMap;
import java.util.List;
import java.util.Objects;
import lombok.Data;
@Parcel
@Data
@JsonObject
public class Capabilities {
@JsonField(name = "spreed")
public SpreedCapability spreedCapability;
@JsonField(name = "notifications")
public NotificationsCapability notificationsCapability;
@JsonField(name = "theming")
public ThemingCapability themingCapability;
/*@JsonField(name = "external")
public HashMap<String, List<String>> externalCapability;*/
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Capabilities)) return false;
Capabilities that = (Capabilities) o;
return Objects.equals(spreedCapability, that.spreedCapability) &&
Objects.equals(notificationsCapability, that.notificationsCapability) &&
Objects.equals(themingCapability, that.themingCapability);
}
@Override
public int hashCode() {
return Objects.hash(spreedCapability, notificationsCapability, themingCapability);
}
}

View File

@ -0,0 +1,53 @@
/*
* Nextcloud Talk application
*
* @author Mario Danic
* Copyright (C) 2017-2018 Mario Danic <mario@lovelyhq.com>
*
* 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 <http://www.gnu.org/licenses/>.
*/
package com.nextcloud.talk.models.json.capabilities
import android.os.Parcelable
import com.bluelinelabs.logansquare.annotation.JsonField
import com.bluelinelabs.logansquare.annotation.JsonObject
import kotlinx.android.parcel.Parcelize
import lombok.Data
import org.parceler.Parcel
import java.util.*
@Data
@Parcel
@JsonObject
@Parcelize
data class Capabilities(
@JsonField(name = ["spreed"])
var spreedCapability: SpreedCapability? = null,
@JsonField(name = ["notifications"])
var notificationsCapability: NotificationsCapability? = null,
@JsonField(name = ["theming"])
var themingCapability: ThemingCapability? = null
): Parcelable {
override fun equals(o: Any?): Boolean {
if (this === o) return true
if (o !is Capabilities) return false
return (spreedCapability == o.spreedCapability &&
notificationsCapability == o.notificationsCapability &&
themingCapability == o.themingCapability)
}
override fun hashCode(): Int {
return Objects.hash(spreedCapability, notificationsCapability, themingCapability)
}
}

View File

@ -17,36 +17,32 @@
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
package com.nextcloud.talk.models.json.capabilities
package com.nextcloud.talk.models.json.capabilities; import android.os.Parcelable
import com.bluelinelabs.logansquare.annotation.JsonField
import com.bluelinelabs.logansquare.annotation.JsonField; import com.bluelinelabs.logansquare.annotation.JsonObject
import com.bluelinelabs.logansquare.annotation.JsonObject; import kotlinx.android.parcel.Parcelize
import lombok.Data
import org.parceler.Parcel; import org.parceler.Parcel
import java.util.*
import java.util.List;
import java.util.Objects;
import lombok.Data;
@Parcel @Parcel
@Data @Data
@JsonObject @JsonObject
public class NotificationsCapability { @Parcelize
@JsonField(name = "ocs-endpoints") data class NotificationsCapability(
public List<String> features; @JsonField(name = ["ocs-endpoints"])
var features: List<String>? = null
): Parcelable {
@Override override fun equals(o: Any?): Boolean {
public boolean equals(Object o) { if (this === o) return true
if (this == o) return true; if (o !is NotificationsCapability) return false
if (!(o instanceof NotificationsCapability)) return false; return features == o.features
NotificationsCapability that = (NotificationsCapability) o;
return Objects.equals(features, that.features);
} }
@Override override fun hashCode(): Int {
public int hashCode() { return Objects.hash(features)
return Objects.hash(features);
} }
} }

View File

@ -1,57 +0,0 @@
/*
* Nextcloud Talk application
*
* @author Mario Danic
* Copyright (C) 2017-2018 Mario Danic <mario@lovelyhq.com>
*
* 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 <http://www.gnu.org/licenses/>.
*/
package com.nextcloud.talk.models.json.capabilities;
import com.bluelinelabs.logansquare.annotation.JsonField;
import com.bluelinelabs.logansquare.annotation.JsonObject;
import org.parceler.Parcel;
import java.util.HashMap;
import java.util.List;
import java.util.Objects;
import lombok.Data;
@Parcel
@Data
@JsonObject
public class SpreedCapability {
@JsonField(name = "features")
public List<String> features;
@JsonField(name = "config")
public HashMap<String, HashMap<String, String>> config;
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof SpreedCapability)) return false;
SpreedCapability that = (SpreedCapability) o;
return Objects.equals(features, that.features) &&
Objects.equals(config, that.config);
}
@Override
public int hashCode() {
return Objects.hash(features, config);
}
}

View File

@ -0,0 +1,52 @@
/*
* Nextcloud Talk application
*
* @author Mario Danic
* Copyright (C) 2017-2018 Mario Danic <mario@lovelyhq.com>
*
* 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 <http://www.gnu.org/licenses/>.
*/
package com.nextcloud.talk.models.json.capabilities
import android.os.Parcelable
import com.bluelinelabs.logansquare.annotation.JsonField
import com.bluelinelabs.logansquare.annotation.JsonObject
import kotlinx.android.parcel.Parcelize
import lombok.Data
import org.parceler.Parcel
import java.util.*
@Parcel
@Data
@JsonObject
@Parcelize
data class SpreedCapability(
@JsonField(name = ["features"])
var features: List<String>? = null,
@JsonField(name = ["config"])
var config: HashMap<String, HashMap<String, String>>? = null
): Parcelable {
override fun equals(o: Any?): Boolean {
if (this === o) return true
if (o !is SpreedCapability) return false
val that = o
return features == that.features &&
config == that.config
}
override fun hashCode(): Int {
return Objects.hash(features, config)
}
}

View File

@ -1,87 +0,0 @@
/*
* Nextcloud Talk application
*
* @author Mario Danic
* Copyright (C) 2017-2019 Mario Danic <mario@lovelyhq.com>
*
* 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 <http://www.gnu.org/licenses/>.
*/
package com.nextcloud.talk.models.json.capabilities;
import com.bluelinelabs.logansquare.annotation.JsonField;
import com.bluelinelabs.logansquare.annotation.JsonObject;
import org.parceler.Parcel;
import java.util.Objects;
import lombok.Data;
@Parcel
@Data
@JsonObject
class ThemingCapability {
@JsonField(name = "name")
String name;
@JsonField(name = "url")
String url;
@JsonField(name = "slogan")
String slogan;
@JsonField(name = "color")
String color;
@JsonField(name = "color-text")
String colorText;
@JsonField(name = "color-element")
String colorElement;
@JsonField(name = "logo")
String logo;
@JsonField(name = "background")
String background;
@JsonField(name = "background-plain")
boolean backgroundPlain;
@JsonField(name = "background-default")
boolean backgroundDefault;
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof ThemingCapability)) return false;
ThemingCapability that = (ThemingCapability) o;
return backgroundPlain == that.backgroundPlain &&
backgroundDefault == that.backgroundDefault &&
Objects.equals(name, that.name) &&
Objects.equals(url, that.url) &&
Objects.equals(slogan, that.slogan) &&
Objects.equals(color, that.color) &&
Objects.equals(colorText, that.colorText) &&
Objects.equals(colorElement, that.colorElement) &&
Objects.equals(logo, that.logo) &&
Objects.equals(background, that.background);
}
@Override
public int hashCode() {
return Objects.hash(name, url, slogan, color, colorText, colorElement, logo, background, backgroundPlain, backgroundDefault);
}
}

View File

@ -0,0 +1,75 @@
/*
* Nextcloud Talk application
*
* @author Mario Danic
* Copyright (C) 2017-2019 Mario Danic <mario@lovelyhq.com>
*
* 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 <http://www.gnu.org/licenses/>.
*/
package com.nextcloud.talk.models.json.capabilities
import android.os.Parcelable
import com.bluelinelabs.logansquare.annotation.JsonField
import com.bluelinelabs.logansquare.annotation.JsonObject
import kotlinx.android.parcel.Parcelize
import lombok.Data
import org.parceler.Parcel
import java.util.*
@Parcel
@Data
@JsonObject
@Parcelize
data class ThemingCapability(
@JsonField(name = ["name"])
var name: String? = null,
@JsonField(name = ["url"])
var url: String? = null,
@JsonField(name = ["slogan"])
var slogan: String? = null,
@JsonField(name = ["color"])
var color: String? = null,
@JsonField(name = ["color-text"])
var colorText: String? = null,
@JsonField(name = ["color-element"])
var colorElement: String? = null,
@JsonField(name = ["logo"])
var logo: String? = null,
@JsonField(name = ["background"])
var background: String? = null,
@JsonField(name = ["background-plain"])
var backgroundPlain: Boolean = false,
@JsonField(name = ["background-default"])
var backgroundDefault: Boolean = false
): Parcelable {
override fun equals(o: Any?): Boolean {
if (this === o) return true
if (o !is ThemingCapability) return false
val that = o
return backgroundPlain == that.backgroundPlain && backgroundDefault == that.backgroundDefault &&
name == that.name &&
url == that.url &&
slogan == that.slogan &&
color == that.color &&
colorText == that.colorText &&
colorElement == that.colorElement &&
logo == that.logo &&
background == that.background
}
override fun hashCode(): Int {
return Objects.hash(name, url, slogan, color, colorText, colorElement, logo, background, backgroundPlain, backgroundDefault)
}
}

View File

@ -31,7 +31,7 @@ import org.parceler.Parcel
@Data @Data
@JsonObject @JsonObject
@Parcelize @Parcelize
class PushConfigurationState( data class PushConfigurationState(
@JsonField(name = ["pushToken"]) @JsonField(name = ["pushToken"])
var pushToken: String? = null, var pushToken: String? = null,
@JsonField(name = ["deviceIdentifier"]) @JsonField(name = ["deviceIdentifier"])

View File

@ -216,7 +216,6 @@ class ConversationsListView : BaseView(), OnQueryTextListener,
}) })
conversationsLiveData.observe(this@ConversationsListView, Observer { conversationsLiveData.observe(this@ConversationsListView, Observer {
Log.d("MARIO", "TRIGGER")
if (it.isEmpty()) { if (it.isEmpty()) {
if (viewState.value != LOADED_EMPTY) { if (viewState.value != LOADED_EMPTY) {
viewState.value = LOADED_EMPTY viewState.value = LOADED_EMPTY

View File

@ -20,7 +20,9 @@
package com.nextcloud.talk.newarch.local.models package com.nextcloud.talk.newarch.local.models
import android.os.Parcel
import android.os.Parcelable import android.os.Parcelable
import android.os.Parcelable.PARCELABLE_WRITE_RETURN_VALUE
import androidx.room.ColumnInfo import androidx.room.ColumnInfo
import androidx.room.Entity import androidx.room.Entity
import androidx.room.PrimaryKey import androidx.room.PrimaryKey
@ -29,8 +31,10 @@ import com.nextcloud.talk.models.json.capabilities.Capabilities
import com.nextcloud.talk.models.json.push.PushConfigurationState import com.nextcloud.talk.models.json.push.PushConfigurationState
import com.nextcloud.talk.newarch.local.models.other.UserStatus import com.nextcloud.talk.newarch.local.models.other.UserStatus
import com.nextcloud.talk.utils.ApiUtils import com.nextcloud.talk.utils.ApiUtils
import kotlinx.android.parcel.Parceler
import kotlinx.android.parcel.Parcelize import kotlinx.android.parcel.Parcelize
import kotlinx.android.parcel.RawValue import kotlinx.android.parcel.RawValue
import org.parceler.Parcels
@Parcelize @Parcelize
@Entity(tableName = "users") @Entity(tableName = "users")
@ -44,7 +48,7 @@ data class UserNgEntity(
@ColumnInfo( @ColumnInfo(
name = "push_configuration" name = "push_configuration"
) var pushConfiguration: PushConfigurationState? = null, ) var pushConfiguration: PushConfigurationState? = null,
@ColumnInfo(name = "capabilities") var capabilities: @RawValue Capabilities? = null, @ColumnInfo(name = "capabilities") var capabilities: Capabilities? = null,
@ColumnInfo(name = "client_auth_cert") var clientCertificate: String? = null, @ColumnInfo(name = "client_auth_cert") var clientCertificate: String? = null,
@ColumnInfo( @ColumnInfo(
name = "external_signaling" name = "external_signaling"

View File

@ -22,5 +22,11 @@ package com.nextcloud.talk.newarch.utils
import com.nextcloud.talk.models.database.UserEntity import com.nextcloud.talk.models.database.UserEntity
import com.nextcloud.talk.utils.ApiUtils import com.nextcloud.talk.utils.ApiUtils
import java.security.MessageDigest
fun UserEntity.getCredentials() = ApiUtils.getCredentials(username, token) fun UserEntity.getCredentials() = ApiUtils.getCredentials(username, token)
fun String.hashWithAlgorithm(algorithm: String): String {
val digest = MessageDigest.getInstance(algorithm)
val bytes = digest.digest(this.toByteArray(Charsets.UTF_8))
return bytes.fold("", { str, it -> str + "%02x".format(it) })
}

View File

@ -36,7 +36,6 @@ import com.nextcloud.talk.models.json.push.PushConfigurationState
import com.nextcloud.talk.models.json.push.PushRegistrationOverall import com.nextcloud.talk.models.json.push.PushRegistrationOverall
import com.nextcloud.talk.newarch.domain.repository.offline.UsersRepository import com.nextcloud.talk.newarch.domain.repository.offline.UsersRepository
import com.nextcloud.talk.newarch.local.models.UserNgEntity import com.nextcloud.talk.newarch.local.models.UserNgEntity
import com.nextcloud.talk.utils.database.user.UserUtils
import com.nextcloud.talk.utils.preferences.AppPreferences import com.nextcloud.talk.utils.preferences.AppPreferences
import io.reactivex.Observer import io.reactivex.Observer
import io.reactivex.disposables.Disposable import io.reactivex.disposables.Disposable
@ -53,14 +52,12 @@ import java.util.*
import kotlin.experimental.and import kotlin.experimental.and
class PushUtils(val usersRepository: UsersRepository) : KoinComponent { class PushUtils(val usersRepository: UsersRepository) : KoinComponent {
val userUtils: UserUtils by inject()
val appPreferences: AppPreferences by inject() val appPreferences: AppPreferences by inject()
val eventBus: EventBus by inject() val eventBus: EventBus by inject()
val ncApi: NcApi by inject() val ncApi: NcApi by inject()
private val keysFile: File private val keysFile: File
private val publicKeyFile: File private val publicKeyFile: File
private val privateKeyFile: File private val privateKeyFile: File
private val proxyServer: String
fun verifySignature( fun verifySignature(
signatureBytes: ByteArray?, signatureBytes: ByteArray?,
subjectBytes: ByteArray? subjectBytes: ByteArray?
@ -74,7 +71,7 @@ class PushUtils(val usersRepository: UsersRepository) : KoinComponent {
val userEntities: List<UserNgEntity> = usersRepository.getUsers() val userEntities: List<UserNgEntity> = usersRepository.getUsers()
try { try {
signature = Signature.getInstance("SHA512withRSA") signature = Signature.getInstance("SHA512withRSA")
if (userEntities.size > 0) { if (userEntities.isNotEmpty()) {
for (userEntity in userEntities) { for (userEntity in userEntities) {
pushConfigurationState = userEntity.pushConfiguration pushConfigurationState = userEntity.pushConfiguration
if (pushConfigurationState?.userPublicKey != null) { if (pushConfigurationState?.userPublicKey != null) {
@ -127,29 +124,6 @@ class PushUtils(val usersRepository: UsersRepository) : KoinComponent {
return -1 return -1
} }
private fun generateSHA512Hash(pushToken: String): String {
var messageDigest: MessageDigest? = null
try {
messageDigest = MessageDigest.getInstance("SHA-512")
messageDigest.update(pushToken.toByteArray())
return bytesToHex(messageDigest.digest())
} catch (e: NoSuchAlgorithmException) {
Log.d(TAG, "SHA-512 algorithm not supported")
}
return ""
}
private fun bytesToHex(bytes: ByteArray): String {
val result = StringBuilder()
for (individualByte in bytes) {
result.append(
((individualByte and 0xff.toByte()) + 0x100).toString(16)
.substring(1)
)
}
return result.toString()
}
fun generateRsa2048KeyPair(): Int { fun generateRsa2048KeyPair(): Int {
if (!publicKeyFile.exists() && !privateKeyFile.exists()) { if (!publicKeyFile.exists() && !privateKeyFile.exists()) {
if (!keysFile.exists()) { if (!keysFile.exists()) {
@ -186,139 +160,6 @@ class PushUtils(val usersRepository: UsersRepository) : KoinComponent {
return -2 return -2
} }
fun pushRegistrationToServer() {
val token: String = appPreferences.pushToken
if (!TextUtils.isEmpty(token)) {
var credentials: String
val pushTokenHash = generateSHA512Hash(token).toLowerCase()
val devicePublicKey =
readKeyFromFile(true) as PublicKey?
if (devicePublicKey != null) {
val publicKeyBytes: ByteArray? =
Base64.encode(devicePublicKey.encoded, Base64.NO_WRAP)
var publicKey = String(publicKeyBytes!!)
publicKey = publicKey.replace("(.{64})".toRegex(), "$1\n")
publicKey = "-----BEGIN PUBLIC KEY-----\n$publicKey\n-----END PUBLIC KEY-----\n"
if (userUtils.anyUserExists()) {
var accountPushData: PushConfigurationState? = null
for (userEntityObject in usersRepository.getUsers()) {
val userEntity = userEntityObject
accountPushData = userEntity.pushConfiguration
if (accountPushData == null || accountPushData.pushToken != token) {
val queryMap: MutableMap<String, String> =
HashMap()
queryMap["format"] = "json"
queryMap["pushTokenHash"] = pushTokenHash
queryMap["devicePublicKey"] = publicKey
queryMap["proxyServer"] = proxyServer
credentials = ApiUtils.getCredentials(
userEntity.username, userEntity.token
)
val finalCredentials = credentials
ncApi.registerDeviceForNotificationsWithNextcloud(
credentials,
ApiUtils.getUrlNextcloudPush(userEntity.baseUrl),
queryMap
)
.subscribe(object : Observer<PushRegistrationOverall> {
override fun onSubscribe(d: Disposable) {}
override fun onNext(pushRegistrationOverall: PushRegistrationOverall) {
val proxyMap: MutableMap<String, String> =
HashMap()
proxyMap["pushToken"] = token
proxyMap["deviceIdentifier"] =
pushRegistrationOverall.ocs.data.deviceIdentifier
proxyMap["deviceIdentifierSignature"] = pushRegistrationOverall.ocs
.data.signature
proxyMap["userPublicKey"] = pushRegistrationOverall.ocs
.data.publicKey
ncApi.registerDeviceForNotificationsWithProxy(
ApiUtils.getUrlPushProxy(), proxyMap
)
.subscribeOn(Schedulers.io())
.subscribe(object : Observer<Void> {
override fun onSubscribe(d: Disposable) {}
override fun onNext(aVoid: Void) {
val pushConfigurationState =
PushConfigurationState()
pushConfigurationState.pushToken = token
pushConfigurationState.deviceIdentifier = pushRegistrationOverall
.ocs.data.deviceIdentifier
pushConfigurationState.deviceIdentifierSignature =
pushRegistrationOverall.ocs.data.signature
pushConfigurationState.userPublicKey = pushRegistrationOverall.ocs
.data.publicKey
pushConfigurationState.usesRegularPass = false
try {
userUtils.createOrUpdateUser(
null,
null, null,
userEntity.displayName,
LoganSquare.serialize(
pushConfigurationState
), null,
null, userEntity.id, null, null, null
)
.subscribe(object : Observer<UserEntity> {
override fun onSubscribe(d: Disposable) {}
override fun onNext(userEntity: UserEntity) {
eventBus.post(
EventStatus(
userEntity.id,
PUSH_REGISTRATION,
true
)
)
}
override fun onError(e: Throwable) {
eventBus.post(
EventStatus(
userEntity.id!!,
PUSH_REGISTRATION, false
)
)
}
override fun onComplete() {}
})
} catch (e: IOException) {
Log.e(TAG, "IOException while updating user")
}
}
override fun onError(e: Throwable) {
eventBus.post(
EventStatus(
userEntity.id!!,
PUSH_REGISTRATION,
false
)
)
}
override fun onComplete() {}
})
}
override fun onError(e: Throwable) {
eventBus.post(
EventStatus(
userEntity.id!!,
PUSH_REGISTRATION,
false
)
)
}
override fun onComplete() {}
})
}
}
}
}
}
}
private fun readKeyFromString( private fun readKeyFromString(
readPublicKey: Boolean, readPublicKey: Boolean,
@ -417,8 +258,5 @@ class PushUtils(val usersRepository: UsersRepository) : KoinComponent {
Context.MODE_PRIVATE Context.MODE_PRIVATE
), "push_key.priv" ), "push_key.priv"
) )
proxyServer =
sharedApplication!!.resources
.getString(string.nc_push_server_url)
} }
} }