This commit is contained in:
gavine99 2025-06-13 07:55:21 +00:00 committed by GitHub
commit 1351b20950
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
23 changed files with 518 additions and 114 deletions

View File

@ -13,7 +13,8 @@
alt="Get it on F-Droid" alt="Get it on F-Droid"
height="80">](https://f-droid.org/packages/com.nextcloud.talk2/) height="80">](https://f-droid.org/packages/com.nextcloud.talk2/)
Please note that Notifications won't work with the F-Droid version due to missing Google Play Services. Please note that the F-Droid version uses UnifiedPush notifications and the Play Store version uses Google Play
Services notifications.
||||||| |||||||
|---|---|---|---|---|---| |---|---|---|---|---|---|
@ -63,7 +64,8 @@ Easy starting points are also reviewing [pull requests](https://github.com/nextc
So you would like to contribute by testing? Awesome, we appreciate that very much. So you would like to contribute by testing? Awesome, we appreciate that very much.
To report a bug for the alpha or beta version, just create an issue on github like you would for the stable version and To report a bug for the alpha or beta version, just create an issue on github like you would for the stable version and
provide the version number. Please remember that Google Services are necessary to receive push notifications. provide the version number. Please remember that Google Services are necessary to receive push notifications in the
Play Store version whereas the F-Droid version uses UnifiedPush notifications.
#### Beta versions (Release Candidates) :package: #### Beta versions (Release Candidates) :package:

View File

@ -306,6 +306,10 @@ dependencies {
implementation 'com.github.nextcloud.android-common:ui:0.23.2' implementation 'com.github.nextcloud.android-common:ui:0.23.2'
implementation 'com.github.nextcloud-deps:android-talk-webrtc:132.6834.0' implementation 'com.github.nextcloud-deps:android-talk-webrtc:132.6834.0'
// unified push library for generic flavour
genericImplementation 'org.unifiedpush.android:connector:3.0.7'
genericImplementation 'org.unifiedpush.android:connector-ui:1.1.0'
gplayImplementation 'com.google.android.gms:play-services-base:18.6.0' gplayImplementation 'com.google.android.gms:play-services-base:18.6.0'
gplayImplementation "com.google.firebase:firebase-messaging:24.1.1" gplayImplementation "com.google.firebase:firebase-messaging:24.1.1"

View File

@ -0,0 +1,21 @@
<!--
~ Nextcloud Talk - Android Client
~
~ SPDX-FileCopyrightText: 2017-2024 Nextcloud GmbH and Nextcloud contributors
~ SPDX-FileCopyrightText: 2021-2023 Marcel Hibbe <dev@mhibbe.de>
~ SPDX-FileCopyrightText: 2017-2019 Mario Danic <mario@lovelyhq.com>
~ SPDX-License-Identifier: GPL-3.0-or-later
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" />
<application>
<service android:name=".UnifiedPush"
android:exported="false">
<intent-filter>
<action android:name="org.unifiedpush.android.connector.PUSH_EVENT"/>
</intent-filter>
</service>
</application>
</manifest>

View File

@ -0,0 +1,143 @@
/*
* Nextcloud Talk - Android Client
*
* SPDX-FileCopyrightText: 2025 Your Name <your@email.com>
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package com.nextcloud.talk
import android.content.Context
import android.util.Log
import androidx.work.Data
import androidx.work.OneTimeWorkRequest
import androidx.work.OutOfQuotaPolicy
import androidx.work.WorkManager
import com.nextcloud.talk.activities.MainActivity
import com.nextcloud.talk.jobs.NotificationWorker
import com.nextcloud.talk.utils.bundle.BundleKeys
import com.nextcloud.talk.utils.power.PowerManagerUtils
import org.greenrobot.eventbus.EventBus
import org.unifiedpush.android.connector.FailedReason
import org.unifiedpush.android.connector.PushService
import org.unifiedpush.android.connector.UnifiedPush
import org.unifiedpush.android.connector.data.PushEndpoint
import org.unifiedpush.android.connector.data.PushMessage
import org.unifiedpush.android.connector.ui.SelectDistributorDialogsBuilder
import org.unifiedpush.android.connector.ui.UnifiedPushFunctions
class UnifiedPush : PushService() {
companion object {
private val TAG: String? = UnifiedPush::class.java.simpleName
private const val MESSAGE_RECEIVED_WAKE_LOCK_TIMEOUT = (60 * 1000L)
fun getNumberOfDistributorsAvailable(context: Context) =
UnifiedPush.getDistributors(context).size
fun registerForPushMessaging(context: Context, accountName: String, forceChoose: Boolean): Boolean {
var retVal = false
object : SelectDistributorDialogsBuilder(
context,
object : UnifiedPushFunctions {
override fun tryUseDefaultDistributor(callback: (Boolean) -> Unit) =
UnifiedPush.tryUseDefaultDistributor(context, callback).also {
Log.d(TAG, "tryUseDefaultDistributor()")
}
override fun getAckDistributor(): String? =
UnifiedPush.getAckDistributor(context).also {
Log.d(TAG, "getAckDistributor() = $it")
}
override fun getDistributors(): List<String> =
UnifiedPush.getDistributors(context).also {
Log.d(TAG, "getDistributors() = $it")
}
override fun register(instance: String) =
UnifiedPush.register(context, instance).also {
Log.d(TAG, "register($instance)")
}
override fun saveDistributor(distributor: String) =
UnifiedPush.saveDistributor(context, distributor).also {
Log.d(TAG, "saveDistributor($distributor)")
}
}
) {
override fun onManyDistributorsFound(distributors: List<String>) =
Log.d(TAG, "onManyDistributorsFound($distributors)").run {
// true return indicates to calling activity that it should wait whilst dialog is shown
retVal = true
super.onManyDistributorsFound(distributors)
}
override fun onDistributorSelected(distributor: String) =
super.onDistributorSelected(distributor).also {
// send message to main activity that it can move on after waiting
EventBus.getDefault().post(MainActivity.ProceedToConversationsListMessageEvent())
}
}.apply {
instances = listOf(accountName)
mayUseCurrent = !forceChoose
mayUseDefault = !forceChoose
}.run()
return retVal
}
fun unregisterForPushMessaging(context: Context, accountName: String) =
// try and unregister with unified push distributor
UnifiedPush.unregister(context, accountName).also {
Log.d(TAG, "unregisterForPushMessaging($accountName)")
}
}
override fun onMessage(message: PushMessage, instance: String) {
// wake lock to get the notification background job to execute more promptly since it will take eons to run
// if phone is dozing. default client ring time is 45 seconds so it should be more than that
PowerManagerUtils().acquireTimedPartialLock(MESSAGE_RECEIVED_WAKE_LOCK_TIMEOUT)
Log.d(TAG, "onMessage()")
val messageString = message.content.toString(Charsets.UTF_8)
if (messageString.isNotEmpty() && instance.isNotEmpty()) {
val messageData = Data.Builder()
.putString(BundleKeys.KEY_NOTIFICATION_SUBJECT, messageString)
.putString(BundleKeys.KEY_NOTIFICATION_SIGNATURE, instance)
.putInt(
BundleKeys.KEY_NOTIFICATION_BACKEND_TYPE,
NotificationWorker.Companion.BackendType.UNIFIED_PUSH.value
)
.build()
val notificationWork =
OneTimeWorkRequest.Builder(NotificationWorker::class.java).setInputData(messageData)
.setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST)
.build()
WorkManager.getInstance(this).enqueue(notificationWork)
Log.d(TAG, "expedited NotificationWorker queued")
}
}
override fun onNewEndpoint(endpoint: PushEndpoint, instance: String) {
Log.d(TAG, "onNewEndpoint(${endpoint.url}, $instance)")
}
override fun onRegistrationFailed(reason: FailedReason, instance: String) =
// the registration is not possible, eg. no network
// force unregister to make sure cleaned up. re-register will be re-attempted next time
UnifiedPush.unregister(this, instance).also {
Log.d(TAG, "onRegistrationFailed(${reason.name}, $instance)")
}
override fun onUnregistered(instance: String) =
// this application is unregistered by the distributor from receiving push messages
// force unregister to make sure cleaned up. re-register will be re-attempted next time
UnifiedPush.unregister(this, instance).also {
Log.d(TAG, "onUnregistered($instance)")
}
}

View File

@ -1,28 +0,0 @@
/*
* Nextcloud Talk - Android Client
*
* SPDX-FileCopyrightText: 2022 Marcel Hibbe <dev@mhibbe.de>
* SPDX-FileCopyrightText: 2017-2018 Mario Danic <mario@lovelyhq.com>
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package com.nextcloud.talk.utils;
import com.nextcloud.talk.interfaces.ClosedInterface;
public class ClosedInterfaceImpl implements ClosedInterface {
@Override
public void providerInstallerInstallIfNeededAsync() {
// does absolutely nothing :)
}
@Override
public boolean isGooglePlayServicesAvailable() {
return false;
}
@Override
public void setUpPushTokenRegistration() {
// no push notifications for generic build variant
}
}

View File

@ -0,0 +1,39 @@
/*
* Nextcloud Talk - Android Client
*
* SPDX-FileCopyrightText: 2022 Marcel Hibbe <dev@mhibbe.de>
* SPDX-FileCopyrightText: 2017-2018 Mario Danic <mario@lovelyhq.com>
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package com.nextcloud.talk.utils
import android.content.Context
import com.nextcloud.talk.UnifiedPush.Companion.getNumberOfDistributorsAvailable
import com.nextcloud.talk.UnifiedPush.Companion.registerForPushMessaging
import com.nextcloud.talk.UnifiedPush.Companion.unregisterForPushMessaging
import com.nextcloud.talk.interfaces.ClosedInterface
class ClosedInterfaceImpl : ClosedInterface {
override fun providerInstallerInstallIfNeededAsync() { /* nothing */
}
override fun isPushMessagingServiceAvailable(context: Context): Boolean {
return (getNumberOfDistributorsAvailable(context) > 0)
}
override fun pushMessagingProvider(): String {
return "unifiedpush"
}
override fun registerWithServer(context: Context, username: String?, forceChoose: Boolean): Boolean {
// unified push available in generic build
if (username == null) return false
return registerForPushMessaging(context, username, forceChoose)
}
override fun unregisterWithServer(context: Context, username: String?) {
// unified push available in generic build
if (username == null) return
unregisterForPushMessaging(context, username)
}
}

View File

@ -50,6 +50,10 @@ class NCFirebaseMessagingService : FirebaseMessagingService() {
val messageData = Data.Builder() val messageData = Data.Builder()
.putString(BundleKeys.KEY_NOTIFICATION_SUBJECT, subject) .putString(BundleKeys.KEY_NOTIFICATION_SUBJECT, subject)
.putString(BundleKeys.KEY_NOTIFICATION_SIGNATURE, signature) .putString(BundleKeys.KEY_NOTIFICATION_SIGNATURE, signature)
.putInt(
BundleKeys.KEY_NOTIFICATION_BACKEND_TYPE,
NotificationWorker.Companion.BackendType.FIREBASE_CLOUD_MESSAGING.value
)
.build() .build()
val notificationWork = val notificationWork =
OneTimeWorkRequest.Builder(NotificationWorker::class.java).setInputData(messageData) OneTimeWorkRequest.Builder(NotificationWorker::class.java).setInputData(messageData)

View File

@ -8,6 +8,7 @@
*/ */
package com.nextcloud.talk.utils package com.nextcloud.talk.utils
import android.content.Context
import android.content.Intent import android.content.Intent
import android.util.Log import android.util.Log
import androidx.work.ExistingPeriodicWorkPolicy import androidx.work.ExistingPeriodicWorkPolicy
@ -26,7 +27,13 @@ import java.util.concurrent.TimeUnit
@AutoInjector(NextcloudTalkApplication::class) @AutoInjector(NextcloudTalkApplication::class)
class ClosedInterfaceImpl : ClosedInterface, ProviderInstaller.ProviderInstallListener { class ClosedInterfaceImpl : ClosedInterface, ProviderInstaller.ProviderInstallListener {
override val isGooglePlayServicesAvailable: Boolean = isGPlayServicesAvailable() override fun isPushMessagingServiceAvailable(context: Context): Boolean {
return isGPlayServicesAvailable()
}
override fun pushMessagingProvider(): String {
return "gplay"
}
override fun providerInstallerInstallIfNeededAsync() { override fun providerInstallerInstallIfNeededAsync() {
NextcloudTalkApplication.sharedApplication?.let { NextcloudTalkApplication.sharedApplication?.let {
@ -49,7 +56,7 @@ class ClosedInterfaceImpl : ClosedInterface, ProviderInstaller.ProviderInstallLi
val api = GoogleApiAvailability.getInstance() val api = GoogleApiAvailability.getInstance()
val code = val code =
NextcloudTalkApplication.sharedApplication?.let { NextcloudTalkApplication.sharedApplication?.let {
api.isGooglePlayServicesAvailable(it.applicationContext) api.isPushMessagingAvailable(it.applicationContext)
} }
return if (code == ConnectionResult.SUCCESS) { return if (code == ConnectionResult.SUCCESS) {
true true
@ -59,11 +66,17 @@ class ClosedInterfaceImpl : ClosedInterface, ProviderInstaller.ProviderInstallLi
} }
} }
override fun setUpPushTokenRegistration() { override fun registerWithServer(context: Context, username: String?, forceChoose: Boolean): Boolean {
val firebasePushTokenWorker = OneTimeWorkRequest.Builder(GetFirebasePushTokenWorker::class.java).build() val firebasePushTokenWorker = OneTimeWorkRequest.Builder(GetFirebasePushTokenWorker::class.java).build()
WorkManager.getInstance().enqueue(firebasePushTokenWorker) WorkManager.getInstance().enqueue(firebasePushTokenWorker)
setUpPeriodicTokenRefreshFromFCM() setUpPeriodicTokenRefreshFromFCM()
return false
}
override fun unregisterWithServer(context: Context, username: String?) {
// do nothing
} }
private fun setUpPeriodicTokenRefreshFromFCM() { private fun setUpPeriodicTokenRefreshFromFCM() {

View File

@ -9,6 +9,7 @@
package com.nextcloud.talk.account package com.nextcloud.talk.account
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.content.Context
import android.content.Intent import android.content.Intent
import android.content.pm.ActivityInfo import android.content.pm.ActivityInfo
import android.os.Bundle import android.os.Bundle
@ -235,6 +236,9 @@ class AccountVerificationActivity : BaseActivity() {
} }
private fun storeProfile(displayName: String?, userId: String, capabilitiesOverall: CapabilitiesOverall) { private fun storeProfile(displayName: String?, userId: String, capabilitiesOverall: CapabilitiesOverall) {
// for capture by lambda used by subscribe() below
val activityContext: Context = this
userManager.storeProfile( userManager.storeProfile(
username, username,
UserManager.UserAttributes( UserManager.UserAttributes(
@ -260,8 +264,8 @@ class AccountVerificationActivity : BaseActivity() {
@SuppressLint("SetTextI18n") @SuppressLint("SetTextI18n")
override fun onSuccess(user: User) { override fun onSuccess(user: User) {
internalAccountId = user.id!! internalAccountId = user.id!!
if (ClosedInterfaceImpl().isGooglePlayServicesAvailable) { if (ClosedInterfaceImpl().isPushMessagingServiceAvailable(context)) {
ClosedInterfaceImpl().setUpPushTokenRegistration() ClosedInterfaceImpl().registerWithServer(activityContext, user.username, false)
} else { } else {
Log.w(TAG, "Skipping push registration.") Log.w(TAG, "Skipping push registration.")
runOnUiThread { runOnUiThread {

View File

@ -46,10 +46,14 @@ import io.reactivex.SingleObserver
import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.Disposable import io.reactivex.disposables.Disposable
import io.reactivex.schedulers.Schedulers import io.reactivex.schedulers.Schedulers
import org.greenrobot.eventbus.ThreadMode
import org.greenrobot.eventbus.Subscribe
import javax.inject.Inject import javax.inject.Inject
@AutoInjector(NextcloudTalkApplication::class) @AutoInjector(NextcloudTalkApplication::class)
class MainActivity : BaseActivity(), ActionBarProvider { class MainActivity : BaseActivity(), ActionBarProvider {
class ProceedToConversationsListMessageEvent
lateinit var binding: ActivityMainBinding lateinit var binding: ActivityMainBinding
@Inject @Inject
@ -76,9 +80,7 @@ class MainActivity : BaseActivity(), ActionBarProvider {
}) })
// Set the default theme to replace the launch screen theme. // Set the default theme to replace the launch screen theme.
setTheme(R.style.AppTheme)
binding = ActivityMainBinding.inflate(layoutInflater) binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
NextcloudTalkApplication.sharedApplication!!.componentApplication.inject(this) NextcloudTalkApplication.sharedApplication!!.componentApplication.inject(this)
@ -138,6 +140,11 @@ class MainActivity : BaseActivity(), ActionBarProvider {
super.onStop() super.onStop()
} }
@Subscribe(threadMode = ThreadMode.MAIN)
fun onMessageEvent(event: ProceedToConversationsListMessageEvent) {
openConversationList()
}
private fun openConversationList() { private fun openConversationList() {
val intent = Intent(this, ConversationsListActivity::class.java) val intent = Intent(this, ConversationsListActivity::class.java)
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP) intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
@ -255,6 +262,8 @@ class MainActivity : BaseActivity(), ActionBarProvider {
appPreferences.isDbRoomMigrated = true appPreferences.isDbRoomMigrated = true
} }
// for capture by lambda used by subscribe() below
val activityContext: Context = this
userManager.users.subscribe(object : SingleObserver<List<User>> { userManager.users.subscribe(object : SingleObserver<List<User>> {
override fun onSubscribe(d: Disposable) { override fun onSubscribe(d: Disposable) {
// unused atm // unused atm
@ -262,9 +271,16 @@ class MainActivity : BaseActivity(), ActionBarProvider {
override fun onSuccess(users: List<User>) { override fun onSuccess(users: List<User>) {
if (users.isNotEmpty()) { if (users.isNotEmpty()) {
ClosedInterfaceImpl().setUpPushTokenRegistration()
runOnUiThread { runOnUiThread {
openConversationList() if (
!ClosedInterfaceImpl().registerWithServer(
activityContext,
users.first().username,
false)
) {
// if push registration does not need to show a dialog, open the conversation list now
openConversationList()
}
} }
} else { } else {
runOnUiThread { runOnUiThread {

View File

@ -280,7 +280,7 @@ class ConversationsListActivity :
// handle notification permission on API level >= 33 // handle notification permission on API level >= 33
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU && if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU &&
!platformPermissionUtil.isPostNotificationsPermissionGranted() && !platformPermissionUtil.isPostNotificationsPermissionGranted() &&
ClosedInterfaceImpl().isGooglePlayServicesAvailable ClosedInterfaceImpl().isPushMessagingServiceAvailable(context)
) { ) {
requestPermissions( requestPermissions(
arrayOf(Manifest.permission.POST_NOTIFICATIONS), arrayOf(Manifest.permission.POST_NOTIFICATIONS),
@ -1724,7 +1724,7 @@ class ConversationsListActivity :
Log.d(TAG, "Notification permission was granted") Log.d(TAG, "Notification permission was granted")
if (!PowerManagerUtils().isIgnoringBatteryOptimizations() && if (!PowerManagerUtils().isIgnoringBatteryOptimizations() &&
ClosedInterfaceImpl().isGooglePlayServicesAvailable ClosedInterfaceImpl().isPushMessagingServiceAvailable(context)
) { ) {
val dialogText = String.format( val dialogText = String.format(
context.resources.getString(R.string.nc_ignore_battery_optimization_dialog_text), context.resources.getString(R.string.nc_ignore_battery_optimization_dialog_text),
@ -1819,7 +1819,7 @@ class ConversationsListActivity :
return settingsOfUserAreWrong && return settingsOfUserAreWrong &&
shouldShowNotificationWarningByUserChoice() && shouldShowNotificationWarningByUserChoice() &&
ClosedInterfaceImpl().isGooglePlayServicesAvailable ClosedInterfaceImpl().isPushMessagingServiceAvailable(context)
} }
private fun openConversation(textToPaste: String? = "") { private fun openConversation(textToPaste: String? = "") {

View File

@ -70,7 +70,8 @@ class DiagnoseActivity : BaseActivity() {
@Inject @Inject
lateinit var platformPermissionUtil: PlatformPermissionUtil lateinit var platformPermissionUtil: PlatformPermissionUtil
private var isGooglePlayServicesAvailable: Boolean = false private var isPushMessagingServiceAvailable: Boolean = false
private var pushMessagingProvider: String = ""
sealed class DiagnoseElement { sealed class DiagnoseElement {
data class DiagnoseHeadline(val headline: String) : DiagnoseElement() data class DiagnoseHeadline(val headline: String) : DiagnoseElement()
@ -89,7 +90,8 @@ class DiagnoseActivity : BaseActivity() {
)[DiagnoseViewModel::class.java] )[DiagnoseViewModel::class.java]
val colorScheme = viewThemeUtils.getColorScheme(this) val colorScheme = viewThemeUtils.getColorScheme(this)
isGooglePlayServicesAvailable = ClosedInterfaceImpl().isGooglePlayServicesAvailable isPushMessagingServiceAvailable = ClosedInterfaceImpl().isPushMessagingServiceAvailable(context)
pushMessagingProvider = ClosedInterfaceImpl().pushMessagingProvider()
setContent { setContent {
val backgroundColor = colorResource(id = R.color.bg_default) val backgroundColor = colorResource(id = R.color.bg_default)
@ -131,7 +133,7 @@ class DiagnoseActivity : BaseActivity() {
viewState = viewState, viewState = viewState,
onTestPushClick = { diagnoseViewModel.fetchTestPushResult() }, onTestPushClick = { diagnoseViewModel.fetchTestPushResult() },
onDismissDialog = { diagnoseViewModel.dismissDialog() }, onDismissDialog = { diagnoseViewModel.dismissDialog() },
isGooglePlayServicesAvailable = isGooglePlayServicesAvailable pushMessagingProvider = pushMessagingProvider
) )
} }
} }
@ -225,17 +227,16 @@ class DiagnoseActivity : BaseActivity() {
value = Build.VERSION.SDK_INT.toString() value = Build.VERSION.SDK_INT.toString()
) )
if (isGooglePlayServicesAvailable) { addDiagnosisEntry(
addDiagnosisEntry( key = context.resources.getString(R.string.nc_diagnose_push_notifications_available_title),
key = context.resources.getString(R.string.nc_diagnose_gplay_available_title), value = context.resources.getString(
value = context.resources.getString(R.string.nc_diagnose_gplay_available_yes) when (pushMessagingProvider) {
"gplay" -> R.string.nc_diagnose_push_notifications_gplay
"unifiedpush" -> R.string.nc_diagnose_push_notifications_unified_push
else -> R.string.nc_diagnose_push_notifications_available_no
}
) )
} else { )
addDiagnosisEntry(
key = context.resources.getString(R.string.nc_diagnose_gplay_available_title),
value = context.resources.getString(R.string.nc_diagnose_gplay_available_no)
)
}
} }
@SuppressLint("SetTextI18n") @SuppressLint("SetTextI18n")
@ -258,8 +259,8 @@ class DiagnoseActivity : BaseActivity() {
value = BuildConfig.FLAVOR value = BuildConfig.FLAVOR
) )
if (isGooglePlayServicesAvailable) { if (isPushMessagingServiceAvailable) {
setupAppValuesForGooglePlayServices() setupAppValuesForPushMessaging()
} }
addDiagnosisEntry( addDiagnosisEntry(
@ -269,7 +270,7 @@ class DiagnoseActivity : BaseActivity() {
} }
@Suppress("Detekt.LongMethod") @Suppress("Detekt.LongMethod")
private fun setupAppValuesForGooglePlayServices() { private fun setupAppValuesForPushMessaging() {
addDiagnosisEntry( addDiagnosisEntry(
key = context.resources.getString(R.string.nc_diagnose_battery_optimization_title), key = context.resources.getString(R.string.nc_diagnose_battery_optimization_title),
value = if (PowerManagerUtils().isIgnoringBatteryOptimizations()) { value = if (PowerManagerUtils().isIgnoringBatteryOptimizations()) {
@ -307,37 +308,39 @@ class DiagnoseActivity : BaseActivity() {
) )
) )
addDiagnosisEntry( if (pushMessagingProvider == "gplay") {
key = context.resources.getString(R.string.nc_diagnose_firebase_push_token_title), addDiagnosisEntry(
value = if (appPreferences.pushToken.isNullOrEmpty()) { key = context.resources.getString(R.string.nc_diagnose_firebase_push_token_title),
context.resources.getString(R.string.nc_diagnose_firebase_push_token_missing) value = if (appPreferences.pushToken.isNullOrEmpty()) {
} else { context.resources.getString(R.string.nc_diagnose_firebase_push_token_missing)
"${appPreferences.pushToken.substring(0, PUSH_TOKEN_PREFIX_END)}..." } else {
} "${appPreferences.pushToken.substring(0, PUSH_TOKEN_PREFIX_END)}..."
) }
)
addDiagnosisEntry( addDiagnosisEntry(
key = context.resources.getString(R.string.nc_diagnose_firebase_push_token_latest_generated), key = context.resources.getString(R.string.nc_diagnose_firebase_push_token_latest_generated),
value = if (appPreferences.pushTokenLatestGeneration != null && value = if (appPreferences.pushTokenLatestGeneration != null &&
appPreferences.pushTokenLatestGeneration != 0L appPreferences.pushTokenLatestGeneration != 0L
) { ) {
DisplayUtils.unixTimeToHumanReadable( DisplayUtils.unixTimeToHumanReadable(
appPreferences appPreferences
.pushTokenLatestGeneration .pushTokenLatestGeneration
) )
} else { } else {
context.resources.getString(R.string.nc_common_unknown) context.resources.getString(R.string.nc_common_unknown)
} }
) )
addDiagnosisEntry( addDiagnosisEntry(
key = context.resources.getString(R.string.nc_diagnose_firebase_push_token_latest_fetch), key = context.resources.getString(R.string.nc_diagnose_firebase_push_token_latest_fetch),
value = if (appPreferences.pushTokenLatestFetch != null && appPreferences.pushTokenLatestFetch != 0L) { value = if (appPreferences.pushTokenLatestFetch != null && appPreferences.pushTokenLatestFetch != 0L) {
DisplayUtils.unixTimeToHumanReadable(appPreferences.pushTokenLatestFetch) DisplayUtils.unixTimeToHumanReadable(appPreferences.pushTokenLatestFetch)
} else { } else {
context.resources.getString(R.string.nc_common_unknown) context.resources.getString(R.string.nc_common_unknown)
} }
) )
}
} }
private fun setupAccountValues() { private fun setupAccountValues() {
@ -371,7 +374,7 @@ class DiagnoseActivity : BaseActivity() {
translateBoolean(currentUser.capabilities?.notificationsCapability?.features?.isNotEmpty()) translateBoolean(currentUser.capabilities?.notificationsCapability?.features?.isNotEmpty())
) )
if (isGooglePlayServicesAvailable) { if ((isPushMessagingServiceAvailable) && (pushMessagingProvider == "gplay")) {
setupPushRegistrationDiagnose() setupPushRegistrationDiagnose()
} }

View File

@ -59,7 +59,7 @@ fun DiagnoseContentComposable(
viewState: NotificationUiState, viewState: NotificationUiState,
onTestPushClick: () -> Unit, onTestPushClick: () -> Unit,
onDismissDialog: () -> Unit, onDismissDialog: () -> Unit,
isGooglePlayServicesAvailable: Boolean pushMessagingProvider: String
) { ) {
val context = LocalContext.current val context = LocalContext.current
Column( Column(
@ -96,7 +96,7 @@ fun DiagnoseContentComposable(
} }
} }
} }
if (isGooglePlayServicesAvailable) { if (pushMessagingProvider == "gplay") {
ShowTestPushButton(onTestPushClick) ShowTestPushButton(onTestPushClick)
} }
ShowNotificationData(isLoading, showDialog, context, viewState, onDismissDialog) ShowNotificationData(isLoading, showDialog, context, viewState, onDismissDialog)
@ -254,6 +254,6 @@ fun DiagnoseContentPreview() {
NotificationUiState.Success("Test notification successful"), NotificationUiState.Success("Test notification successful"),
{}, {},
{}, {},
true "gplay"
) )
} }

View File

@ -7,9 +7,13 @@
*/ */
package com.nextcloud.talk.interfaces package com.nextcloud.talk.interfaces
import android.content.Context
interface ClosedInterface { interface ClosedInterface {
val isGooglePlayServicesAvailable: Boolean fun isPushMessagingServiceAvailable(context: Context): Boolean
fun pushMessagingProvider(): String
fun providerInstallerInstallIfNeededAsync() fun providerInstallerInstallIfNeededAsync()
fun setUpPushTokenRegistration() fun registerWithServer(context: Context, username: String?, forceChoose: Boolean): Boolean
fun unregisterWithServer(context: Context, username: String?)
} }

View File

@ -139,10 +139,38 @@ class NotificationWorker(context: Context, workerParams: WorkerParameters) : Wor
private lateinit var notificationManager: NotificationManagerCompat private lateinit var notificationManager: NotificationManagerCompat
override fun doWork(): Result { override fun doWork(): Result {
Log.d(TAG, "started work")
sharedApplication!!.componentApplication.inject(this) sharedApplication!!.componentApplication.inject(this)
context = applicationContext context = applicationContext
initDecryptedData(inputData) when (inputData.getInt(BundleKeys.KEY_NOTIFICATION_BACKEND_TYPE, -1)) {
Companion.BackendType.FIREBASE_CLOUD_MESSAGING.value -> {
initDecryptedData(inputData)
}
Companion.BackendType.UNIFIED_PUSH.value -> {
pushMessage = LoganSquare.parse(
inputData.getString(BundleKeys.KEY_NOTIFICATION_SUBJECT),
DecryptedPushMessage::class.java
)
userManager.users.blockingGet()?.let {
for (user in it) {
if (user.username == inputData.getString(BundleKeys.KEY_NOTIFICATION_SIGNATURE)) {
signatureVerification = SignatureVerification(true, user)
break
}
}
}
Log.d(TAG, "parsed UP message")
}
else -> {
// message not received from a valid backend
return Result.failure()
}
}
initNcApiAndCredentials() initNcApiAndCredentials()
notificationManager = NotificationManagerCompat.from(context!!) notificationManager = NotificationManagerCompat.from(context!!)
@ -1028,5 +1056,14 @@ class NotificationWorker(context: Context, workerParams: WorkerParameters) : Wor
private const val TIMER_START = 1 private const val TIMER_START = 1
private const val TIMER_COUNT = 12 private const val TIMER_COUNT = 12
private const val TIMER_DELAY: Long = 5 private const val TIMER_DELAY: Long = 5
private const val GET_ROOM_RETRY_COUNT: Long = 3
enum class BackendType(val value: Int) {
NONE(-1),
FIREBASE_CLOUD_MESSAGING(1),
UNIFIED_PUSH(2);
companion object {
fun fromInt(value: Int) = BackendType.values().first { it.value == value }
}
}
} }
} }

View File

@ -40,15 +40,18 @@ public class PushRegistrationWorker extends Worker {
@Inject @Inject
OkHttpClient okHttpClient; OkHttpClient okHttpClient;
Context workerContext;
public PushRegistrationWorker(@NonNull Context context, @NonNull WorkerParameters workerParams) { public PushRegistrationWorker(@NonNull Context context, @NonNull WorkerParameters workerParams) {
super(context, workerParams); super(context, workerParams);
workerContext = context;
} }
@NonNull @NonNull
@Override @Override
public Result doWork() { public Result doWork() {
NextcloudTalkApplication.Companion.getSharedApplication().getComponentApplication().inject(this); NextcloudTalkApplication.Companion.getSharedApplication().getComponentApplication().inject(this);
if (new ClosedInterfaceImpl().isGooglePlayServicesAvailable()) { if (new ClosedInterfaceImpl().isPushMessagingServiceAvailable(workerContext)) {
Data data = getInputData(); Data data = getInputData();
String origin = data.getString("origin"); String origin = data.getString("origin");
Log.d(TAG, "PushRegistrationWorker called via " + origin); Log.d(TAG, "PushRegistrationWorker called via " + origin);

View File

@ -161,6 +161,7 @@ class SettingsActivity :
) )
setupDiagnose() setupDiagnose()
setupResetPushPreference()
setupPrivacyUrl() setupPrivacyUrl()
setupSourceCodeUrl() setupSourceCodeUrl()
binding.settingsVersionSummary.text = String.format("v" + BuildConfig.VERSION_NAME) binding.settingsVersionSummary.text = String.format("v" + BuildConfig.VERSION_NAME)
@ -292,8 +293,8 @@ class SettingsActivity :
@SuppressLint("StringFormatInvalid") @SuppressLint("StringFormatInvalid")
@Suppress("LongMethod") @Suppress("LongMethod")
private fun setupNotificationPermissionSettings() { private fun setupNotificationPermissionSettings() {
if (ClosedInterfaceImpl().isGooglePlayServicesAvailable) { if (ClosedInterfaceImpl().isPushMessagingServiceAvailable(context)) {
binding.settingsGplayOnlyWrapper.visibility = View.VISIBLE binding.settingsPushNotificationsOnlyWrapper.visibility = View.VISIBLE
setTroubleshootingClickListenersIfNecessary() setTroubleshootingClickListenersIfNecessary()
@ -375,8 +376,20 @@ class SettingsActivity :
binding.settingsNotificationsPermissionWrapper.visibility = View.GONE binding.settingsNotificationsPermissionWrapper.visibility = View.GONE
} }
} else { } else {
binding.settingsGplayOnlyWrapper.visibility = View.GONE binding.settingsPushNotificationsOnlyWrapper.visibility = View.GONE
binding.settingsGplayNotAvailable.visibility = View.VISIBLE
val pushMessagingProvider = ClosedInterfaceImpl().pushMessagingProvider()
if (pushMessagingProvider == "gplay") {
binding.settingsPushNotificationsNotAvailableText.append("\n".plus(
resources!!.getString(R.string.nc_diagnose_push_notifications_gplay).plus("\n").plus(resources!!
.getString(R.string.nc_diagnose_push_notifications_gplay_rectify))))
} else if (pushMessagingProvider == "unifiedpush") {
binding.settingsPushNotificationsNotAvailableText.append("\n".plus(
resources!!.getString(R.string.nc_diagnose_push_notifications_unified_push).plus("\n").plus
(resources!!.getString(R.string.nc_diagnose_push_notifications_unified_push_rectify))))
}
binding.settingsPushNotificationsNotAvailable.visibility = View.VISIBLE
} }
} }
@ -535,6 +548,17 @@ class SettingsActivity :
} }
} }
private fun setupResetPushPreference() {
binding.resetPushNotificationsWrapper.setOnClickListener {
for (user in userManager.users.blockingGet()) {
ClosedInterfaceImpl().apply {
unregisterWithServer(binding.root.context, user.username)
registerWithServer(binding.root.context, user.username, true)
}
}
}
}
private fun setupLicenceSetting() { private fun setupLicenceSetting() {
if (!TextUtils.isEmpty(resources!!.getString(R.string.nc_gpl3_url))) { if (!TextUtils.isEmpty(resources!!.getString(R.string.nc_gpl3_url))) {
binding.settingsLicence.setOnClickListener { binding.settingsLicence.setOnClickListener {
@ -932,7 +956,7 @@ class SettingsActivity :
binding.settingsShowNotificationWarningSwitch.isChecked = binding.settingsShowNotificationWarningSwitch.isChecked =
appPreferences.showRegularNotificationWarning appPreferences.showRegularNotificationWarning
if (ClosedInterfaceImpl().isGooglePlayServicesAvailable) { if (ClosedInterfaceImpl().isPushMessagingServiceAvailable(context)) {
binding.settingsShowNotificationWarning.setOnClickListener { binding.settingsShowNotificationWarning.setOnClickListener {
val isChecked = binding.settingsShowNotificationWarningSwitch.isChecked val isChecked = binding.settingsShowNotificationWarningSwitch.isChecked
binding.settingsShowNotificationWarningSwitch.isChecked = !isChecked binding.settingsShowNotificationWarningSwitch.isChecked = !isChecked

View File

@ -32,6 +32,7 @@ object BundleKeys {
const val KEY_MODIFIED_BASE_URL = "KEY_MODIFIED_BASE_URL" const val KEY_MODIFIED_BASE_URL = "KEY_MODIFIED_BASE_URL"
const val KEY_NOTIFICATION_SUBJECT = "KEY_NOTIFICATION_SUBJECT" const val KEY_NOTIFICATION_SUBJECT = "KEY_NOTIFICATION_SUBJECT"
const val KEY_NOTIFICATION_SIGNATURE = "KEY_NOTIFICATION_SIGNATURE" const val KEY_NOTIFICATION_SIGNATURE = "KEY_NOTIFICATION_SIGNATURE"
const val KEY_NOTIFICATION_BACKEND_TYPE = "KEY_NOTIFICATION_BACKEND_TYPE"
const val KEY_INTERNAL_USER_ID = "KEY_INTERNAL_USER_ID" const val KEY_INTERNAL_USER_ID = "KEY_INTERNAL_USER_ID"
const val KEY_CONVERSATION_TYPE = "KEY_CONVERSATION_TYPE" const val KEY_CONVERSATION_TYPE = "KEY_CONVERSATION_TYPE"
const val KEY_INVITED_PARTICIPANTS = "KEY_INVITED_PARTICIPANTS" const val KEY_INVITED_PARTICIPANTS = "KEY_INVITED_PARTICIPANTS"

View File

@ -60,6 +60,14 @@ class PowerManagerUtils {
orientation = context!!.resources.configuration.orientation orientation = context!!.resources.configuration.orientation
} }
fun acquireTimedPartialLock(msTimeout: Long): PowerManager.WakeLock {
// wakelock will be auto-released after the given time
val pm = context!!.getSystemService(POWER_SERVICE) as PowerManager
val wakeLock = pm!!.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "nctalk:timedPartialwakelock")
wakeLock!!.acquire(msTimeout)
return wakeLock
}
fun isIgnoringBatteryOptimizations(): Boolean { fun isIgnoringBatteryOptimizations(): Boolean {
val packageName = context!!.packageName val packageName = context!!.packageName
val pm = context!!.getSystemService(POWER_SERVICE) as PowerManager val pm = context!!.getSystemService(POWER_SERVICE) as PowerManager

View File

@ -205,7 +205,7 @@
android:textStyle="bold"/> android:textStyle="bold"/>
<LinearLayout <LinearLayout
android:id="@+id/settings_gplay_only_wrapper" android:id="@+id/settings_push_notifications_only_wrapper"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="vertical"> android:orientation="vertical">
@ -277,7 +277,7 @@
</LinearLayout> </LinearLayout>
<LinearLayout <LinearLayout
android:id="@+id/settings_gplay_not_available" android:id="@+id/settings_push_notifications_not_available"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:padding="@dimen/standard_padding" android:padding="@dimen/standard_padding"
@ -286,9 +286,10 @@
tools:visibility="visible"> tools:visibility="visible">
<com.google.android.material.textview.MaterialTextView <com.google.android.material.textview.MaterialTextView
android:id="@+id/settings_push_notifications_not_available_text"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="@string/nc_diagnose_gplay_available_no"/> android:text="@string/nc_diagnose_push_notifications_available_no" />
</LinearLayout> </LinearLayout>
@ -655,14 +656,36 @@
android:id="@+id/diagnose_title" android:id="@+id/diagnose_title"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:textSize="@dimen/headline_text_size" android:text="@string/nc_settings_diagnose_title"
android:text="@string/nc_settings_diagnose_title"/> android:textSize="@dimen/headline_text_size" />
<com.google.android.material.textview.MaterialTextView <com.google.android.material.textview.MaterialTextView
android:id="@+id/diagnose_subtitle" android:id="@+id/diagnose_subtitle"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="@string/nc_settings_diagnose_subtitle"/> android:text="@string/nc_settings_diagnose_subtitle" />
</LinearLayout>
<LinearLayout
android:id="@+id/reset_push_notifications_wrapper"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="@dimen/standard_padding"
android:background="?android:attr/selectableItemBackground">
<com.google.android.material.textview.MaterialTextView
android:id="@+id/reset_push_notifications_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="@dimen/headline_text_size"
android:text="@string/prefs_reset_push_title"/>
<com.google.android.material.textview.MaterialTextView
android:id="@+id/reset_push_notifications_subtitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/prefs_reset_push_summary"/>
</LinearLayout> </LinearLayout>
<LinearLayout <LinearLayout

View File

@ -201,9 +201,14 @@ How to translate with transifex:
<string name="nc_diagnose_app_name_title">App name</string> <string name="nc_diagnose_app_name_title">App name</string>
<string name="nc_diagnose_app_version_title">App version</string> <string name="nc_diagnose_app_version_title">App version</string>
<string name="nc_diagnose_app_users_amount">Registered users</string> <string name="nc_diagnose_app_users_amount">Registered users</string>
<string name="nc_diagnose_gplay_available_title">Google Play services</string> <string name="nc_diagnose_push_notifications_available_title">Push notification services</string>
<string name="nc_diagnose_gplay_available_yes">Google Play services are available</string> <string name="nc_diagnose_push_notifications_available_no">Push notifications are not available.</string>
<string name="nc_diagnose_gplay_available_no">Google Play services are not available. Notifications are not supported</string> <string name="nc_diagnose_push_notifications_gplay">Push notifications are using the Google Play engine.</string>
<string name="nc_diagnose_push_notifications_gplay_rectify">An alternate version of Talk is available for devices
without the Google Play Apis.</string>
<string name="nc_diagnose_push_notifications_unified_push">Push notifications are using the UnifiedPush engine.</string>
<string name="nc_diagnose_push_notifications_unified_push_rectify">Notifications can be enabled via UnifiedPush.
Please see https://unifiedpush.org</string>
<string name="nc_diagnose_battery_optimization_title">Battery settings</string> <string name="nc_diagnose_battery_optimization_title">Battery settings</string>
<string name="nc_diagnose_battery_optimization_not_ignored">Battery optimization is enabled which might cause issues. You should disable battery optimization!</string> <string name="nc_diagnose_battery_optimization_not_ignored">Battery optimization is enabled which might cause issues. You should disable battery optimization!</string>
<string name="nc_diagnose_battery_optimization_ignored">Battery optimization is ignored, all fine</string> <string name="nc_diagnose_battery_optimization_ignored">Battery optimization is ignored, all fine</string>
@ -860,4 +865,8 @@ How to translate with transifex:
<string name="unarchived_conversation">Unarchived %1$s</string> <string name="unarchived_conversation">Unarchived %1$s</string>
<string name="conversation_archived">Conversation is archived</string> <string name="conversation_archived">Conversation is archived</string>
<string name="local_time">Local time: %1$s</string> <string name="local_time">Local time: %1$s</string>
<string name="prefs_reset_push_title">Reset push notifications</string>
<string name="prefs_reset_push_summary">Reset push notifications in case of push messaging issues</string>
<string name="prefs_reset_push_done">Push notifications reset</string>
</resources> </resources>

View File

@ -8,7 +8,11 @@
package com.nextcloud.talk.utils; package com.nextcloud.talk.utils;
import android.content.Context;
import com.nextcloud.talk.interfaces.ClosedInterface; import com.nextcloud.talk.interfaces.ClosedInterface;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
public class ClosedInterfaceImpl implements ClosedInterface { public class ClosedInterfaceImpl implements ClosedInterface {
@Override @Override
@ -17,12 +21,23 @@ public class ClosedInterfaceImpl implements ClosedInterface {
} }
@Override @Override
public boolean isGooglePlayServicesAvailable() { public boolean isPushMessagingServiceAvailable(Context context) {
return false; return false;
} }
@Override @Override
public void setUpPushTokenRegistration() { public String pushMessagingProvider() {
return "qa";
}
@Override
public boolean registerWithServer(Context context, @Nullable String username) {
// no push notifications for qa build flavour :(
return false;
}
@Override
public void unregisterWithServer(@NonNull Context context, @Nullable String username) {
// no push notifications for qa build flavour :( // no push notifications for qa build flavour :(
} }
} }

View File

@ -3,6 +3,10 @@
<configuration> <configuration>
<verify-metadata>true</verify-metadata> <verify-metadata>true</verify-metadata>
<verify-signatures>true</verify-signatures> <verify-signatures>true</verify-signatures>
<ignored-keys>
<ignored-key id="4DA93C0B57A98B11" reason=""/>
<ignored-key id="73976C9C39C1479B84E2641A5A68A2249128E2C6" reason=""/>
</ignored-keys>
<trusted-artifacts> <trusted-artifacts>
<trust file="tensorflow-lite-metadata-0.1.0-rc2.pom" reason="differing hash on every CI run - temp global trust"/> <trust file="tensorflow-lite-metadata-0.1.0-rc2.pom" reason="differing hash on every CI run - temp global trust"/>
<trust group="androidx.fragment"/> <trust group="androidx.fragment"/>
@ -12,6 +16,9 @@
<trust group="org.javassist" name="javassist" version="3.26.0-GA" reason="java assist"/> <trust group="org.javassist" name="javassist" version="3.26.0-GA" reason="java assist"/>
<trust file=".*-sources[.]jar" regex="true"/> <trust file=".*-sources[.]jar" regex="true"/>
</trusted-artifacts> </trusted-artifacts>
<ignored-keys>
<ignored-key id="C020E96222A31FB3" reason="Key couldn't be downloaded from any key server"/>
</ignored-keys>
<trusted-keys> <trusted-keys>
<trusted-key id="02A36B6DB7056EB5E6FFEF893DA731F041734930" group="org.parceler"/> <trusted-key id="02A36B6DB7056EB5E6FFEF893DA731F041734930" group="org.parceler"/>
<trusted-key id="03D5EBC6C81161316CF21CEE1592D9DA6586CF26" group="^com[.]afollestad($|([.].*))" regex="true"/> <trusted-key id="03D5EBC6C81161316CF21CEE1592D9DA6586CF26" group="^com[.]afollestad($|([.].*))" regex="true"/>
@ -324,6 +331,58 @@
</trusted-keys> </trusted-keys>
</configuration> </configuration>
<components> <components>
<component group="org.unifiedpush.android" name="connector" version="3.0.7">
<artifact name="connector-3.0.7.aar">
<sha256 value="84b8edca39e4393985848acc1aa7530c6703b2851aae25d007cc7f45e20659c0" origin="Generated by Gradle"/>
</artifact>
<artifact name="connector-3.0.7.module">
<sha256 value="2149687d8bc7d0a78295c0e03ba0157c6c58ce1f3804c16a1a2713f4fbbd7847" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="org.unifiedpush.android" name="connector-ui" version="1.1.0">
<artifact name="connector-ui-1.1.0.aar">
<sha256 value="154c8346344277a9ecd49ea7821a400f621a32f1099c94ca961c6eb8b611b093" origin="Generated by Gradle"/>
</artifact>
<artifact name="connector-ui-1.1.0.module">
<sha256 value="bec6e094adc821624f8f52a7b8df0612c7d1423a7a838ec691b981160f8c8a7b" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="com.google.crypto.tink" name="tink" version="1.16.0">
<artifact name="tink-1.16.0.jar">
<sha256 value="7f5e380dde874cd44613952f374723da1f8636526c6a574945bceb0e65571d0a" origin="Generated by Gradle"/>
</artifact>
<artifact name="tink-1.16.0.pom">
<sha256 value="28393661ae65329dcf782ffd7978b0a1ba0ad614577ee4b0bbb7bebdc4319673" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="com.google.protobuf" name="protobuf-parent" version="3.0.0">
<artifact name="protobuf-parent-3.0.0.pom">
<sha256 value="932e6bab9a24a7bc958bbdb7e29e04d083b473d11c4ba3fab1e9b7149579f272" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="com.google.protobuf" name="protobuf-java" version="3.19.3">
<artifact name="protobuf-java-3.19.3.pom">
<sha256 value="68f3a9cc44963e31083993c10a49ebeb1652d7ef63cf516fcaafcc32595f3a8e" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="com.google.protobuf" name="protobuf-bom" version="4.28.2">
<artifact name="protobuf-bom-4.28.2.pom">
<sha256 value="43666cd072728c646cc45a614aeeead1e0b23f818f652982df279a0e5adaf316" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="com.google.protobuf" name="protobuf-parent" version="4.28.2">
<artifact name="protobuf-parent-4.28.2.pom">
<sha256 value="f7f23fc16ed463cf52579fffafa03940e8e653b33c6b4f106082c5bf15050f75" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="com.google.protobuf" name="protobuf-java" version="4.28.2">
<artifact name="protobuf-java-4.28.2.jar">
<sha256 value="707bccf406f4fc61b841d4700daa8d3e84db8ab499ef3481a060fa6a0f06e627" origin="Generated by Gradle"/>
</artifact>
<artifact name="protobuf-java-4.28.2.pom">
<sha256 value="1eaecce23fb47b1ae54996f16c0d5485563a972ec262b78a8180e07f0aa77253" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.activity" name="activity" version="1.0.0"> <component group="androidx.activity" name="activity" version="1.0.0">
<artifact name="activity-1.0.0.aar"> <artifact name="activity-1.0.0.aar">
<sha256 value="d1bc9842455c2e534415d88c44df4d52413b478db9093a1ba36324f705f44c3d" origin="Generated by Gradle" reason="Artifact is not signed"/> <sha256 value="d1bc9842455c2e534415d88c44df4d52413b478db9093a1ba36324f705f44c3d" origin="Generated by Gradle" reason="Artifact is not signed"/>
@ -14587,7 +14646,7 @@
<sha256 value="51b731e5cff121efd6d320f4a801cd616869c5bed4e9c0e353b29060b1d2355f" origin="Generated by Gradle"/> <sha256 value="51b731e5cff121efd6d320f4a801cd616869c5bed4e9c0e353b29060b1d2355f" origin="Generated by Gradle"/>
</artifact> </artifact>
</component> </component>
<component group="org.codehaus.groovy" name="groovy-bom" version="3.0.19"> <component group="org.codehaus.groovy" name="groovy-bom" version="3.0.19">
<artifact name="groovy-bom-3.0.19.pom"> <artifact name="groovy-bom-3.0.19.pom">
<pgp value="34441E504A937F43EB0DAEF96A65176A0FB1CD0B"/> <pgp value="34441E504A937F43EB0DAEF96A65176A0FB1CD0B"/>
</artifact> </artifact>