diff --git a/app/build.gradle.kts b/app/build.gradle.kts index f78b693918..3b66a6c823 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -12,6 +12,7 @@ plugins { alias(libs.plugins.jetbrains.kotlin.android) alias(libs.plugins.ktlint) alias(libs.plugins.compose.compiler) + alias(libs.plugins.kotlinx.serialization) id("androidx.navigation.safeargs") id("kotlin-parcelize") id("com.squareup.wire") @@ -607,6 +608,7 @@ dependencies { implementation(libs.rxdogtag) implementation(libs.androidx.credentials) implementation(libs.androidx.credentials.compat) + implementation(libs.kotlinx.serialization.json) implementation(project(":billing")) diff --git a/app/src/main/java/org/thoughtcrime/securesms/audio/AudioCodec.java b/app/src/main/java/org/thoughtcrime/securesms/audio/AudioCodec.java index 5d54a8fa29..7f046ab88f 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/audio/AudioCodec.java +++ b/app/src/main/java/org/thoughtcrime/securesms/audio/AudioCodec.java @@ -1,5 +1,6 @@ package org.thoughtcrime.securesms.audio; +import android.annotation.SuppressLint; import android.media.AudioFormat; import android.media.AudioRecord; import android.media.MediaCodec; @@ -191,6 +192,7 @@ public class AudioCodec implements Recorder { return adtsHeader; } + @SuppressLint("MissingPermission") private AudioRecord createAudioRecord(int bufferSize) { return new AudioRecord(MediaRecorder.AudioSource.MIC, SAMPLE_RATE, AudioFormat.CHANNEL_IN_MONO, diff --git a/app/src/main/java/org/thoughtcrime/securesms/backup/BackupFileIOError.java b/app/src/main/java/org/thoughtcrime/securesms/backup/BackupFileIOError.java index 812d771f00..fd7f7dd553 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/backup/BackupFileIOError.java +++ b/app/src/main/java/org/thoughtcrime/securesms/backup/BackupFileIOError.java @@ -1,16 +1,20 @@ package org.thoughtcrime.securesms.backup; +import android.Manifest; import android.app.Notification; import android.app.PendingIntent; import android.content.Context; +import android.content.pm.PackageManager; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.StringRes; import androidx.core.app.NotificationCompat; import androidx.core.app.NotificationManagerCompat; +import androidx.core.content.ContextCompat; import org.signal.core.util.PendingIntentFlags; +import org.signal.core.util.logging.Log; import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.components.settings.app.AppSettingsActivity; import org.thoughtcrime.securesms.notifications.NotificationCancellationHelper; @@ -26,6 +30,7 @@ public enum BackupFileIOError { ATTACHMENT_TOO_LARGE(R.string.LocalBackupJobApi29_backup_failed, R.string.LocalBackupJobApi29_your_backup_contains_a_very_large_file), UNKNOWN(R.string.LocalBackupJobApi29_backup_failed, R.string.LocalBackupJobApi29_tap_to_manage_backups); + private static final String TAG = Log.tag(BackupFileIOError.class); private static final short BACKUP_FAILED_ID = 31321; private final @StringRes int titleId; @@ -41,6 +46,11 @@ public enum BackupFileIOError { } public void postNotification(@NonNull Context context) { + if (ContextCompat.checkSelfPermission(context, Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED) { + Log.w(TAG, "postNotification: Notification permission is not granted."); + return; + } + PendingIntent pendingIntent = PendingIntent.getActivity(context, -1, AppSettingsActivity.backups(context), PendingIntentFlags.mutable()); Notification backupFailedNotification = new NotificationCompat.Builder(context, NotificationChannels.getInstance().FAILURES) .setSmallIcon(R.drawable.ic_signal_backup) diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/errors/DonationErrorNotifications.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/errors/DonationErrorNotifications.kt index 5366f394e2..c345b2f1d5 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/errors/DonationErrorNotifications.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/errors/DonationErrorNotifications.kt @@ -1,13 +1,17 @@ package org.thoughtcrime.securesms.components.settings.app.subscription.errors +import android.Manifest import android.app.PendingIntent import android.content.Context import android.content.Intent +import android.content.pm.PackageManager import android.net.Uri import android.os.Build import androidx.core.app.NotificationCompat import androidx.core.app.NotificationManagerCompat +import androidx.core.content.ContextCompat import org.signal.core.util.PendingIntentFlags +import org.signal.core.util.logging.Log import org.thoughtcrime.securesms.R import org.thoughtcrime.securesms.components.settings.app.AppSettingsActivity import org.thoughtcrime.securesms.help.HelpFragment @@ -18,7 +22,15 @@ import org.thoughtcrime.securesms.notifications.NotificationIds * Donation-related push notifications. */ object DonationErrorNotifications { + + private val TAG = Log.tag(DonationErrorNotifications::class) + fun displayErrorNotification(context: Context, donationError: DonationError) { + if (ContextCompat.checkSelfPermission(context, Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED) { + Log.w(TAG, "Permission to post notifications is not granted.") + return + } + val parameters = DonationErrorParams.create(context, donationError, NotificationCallback) val notification = NotificationCompat.Builder(context, NotificationChannels.getInstance().FAILURES) .setSmallIcon(R.drawable.ic_notification) diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/webrtc/GroupCallSafetyNumberChangeNotificationUtil.java b/app/src/main/java/org/thoughtcrime/securesms/components/webrtc/GroupCallSafetyNumberChangeNotificationUtil.java index a06909e90b..40626ff274 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/webrtc/GroupCallSafetyNumberChangeNotificationUtil.java +++ b/app/src/main/java/org/thoughtcrime/securesms/components/webrtc/GroupCallSafetyNumberChangeNotificationUtil.java @@ -1,15 +1,19 @@ package org.thoughtcrime.securesms.components.webrtc; +import android.Manifest; import android.app.Notification; import android.app.PendingIntent; import android.content.Context; import android.content.Intent; +import android.content.pm.PackageManager; import androidx.annotation.NonNull; import androidx.core.app.NotificationCompat; import androidx.core.app.NotificationManagerCompat; +import androidx.core.content.ContextCompat; import org.signal.core.util.PendingIntentFlags; +import org.signal.core.util.logging.Log; import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.components.webrtc.v2.CallIntent; import org.thoughtcrime.securesms.notifications.NotificationChannels; @@ -20,12 +24,18 @@ import org.thoughtcrime.securesms.recipients.Recipient; */ public final class GroupCallSafetyNumberChangeNotificationUtil { + public static final String TAG = Log.tag(GroupCallSafetyNumberChangeNotificationUtil.class); public static final String GROUP_CALLING_NOTIFICATION_TAG = "group_calling"; private GroupCallSafetyNumberChangeNotificationUtil() { } public static void showNotification(@NonNull Context context, @NonNull Recipient recipient) { + if (ContextCompat.checkSelfPermission(context, Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED) { + Log.w(TAG, "showNotification: Notification permission is not granted."); + return; + } + Intent contentIntent = new Intent(context, CallIntent.getActivityClass()); contentIntent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP); diff --git a/app/src/main/java/org/thoughtcrime/securesms/gcm/FcmFetchManager.kt b/app/src/main/java/org/thoughtcrime/securesms/gcm/FcmFetchManager.kt index eabbb3cedd..1649652181 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/gcm/FcmFetchManager.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/gcm/FcmFetchManager.kt @@ -1,12 +1,15 @@ package org.thoughtcrime.securesms.gcm +import android.Manifest import android.app.Notification import android.app.PendingIntent import android.content.Context import android.content.Intent +import android.content.pm.PackageManager import android.os.Build import androidx.core.app.NotificationCompat import androidx.core.app.NotificationManagerCompat +import androidx.core.content.ContextCompat import org.signal.core.util.PendingIntentFlags.mutable import org.signal.core.util.concurrent.SignalExecutors import org.signal.core.util.logging.Log @@ -83,6 +86,11 @@ object FcmFetchManager { return } + if (ContextCompat.checkSelfPermission(context, Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED) { + Log.w(TAG, "Missing permission to post notifications.") + return + } + Log.w(TAG, "Notifying the user that they may have new messages.") val mayHaveMessagesNotification: Notification = NotificationCompat.Builder(context, NotificationChannels.getInstance().ADDITIONAL_MESSAGE_NOTIFICATIONS) diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/ArchiveAttachmentReconciliationJob.kt b/app/src/main/java/org/thoughtcrime/securesms/jobs/ArchiveAttachmentReconciliationJob.kt index 2e18bcc470..a982c3c1b3 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/ArchiveAttachmentReconciliationJob.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/ArchiveAttachmentReconciliationJob.kt @@ -5,11 +5,14 @@ package org.thoughtcrime.securesms.jobs +import android.Manifest import android.app.Notification import android.app.PendingIntent import android.content.Intent +import android.content.pm.PackageManager import androidx.core.app.NotificationCompat import androidx.core.app.NotificationManagerCompat +import androidx.core.content.ContextCompat import org.signal.core.util.EventTimer import org.signal.core.util.PendingIntentFlags import org.signal.core.util.Stopwatch @@ -364,6 +367,10 @@ class ArchiveAttachmentReconciliationJob private constructor( return } + if (ContextCompat.checkSelfPermission(context, Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED) { + return + } + val notification: Notification = NotificationCompat.Builder(context, NotificationChannels.getInstance().FAILURES) .setSmallIcon(R.drawable.ic_notification) .setContentTitle("[Internal-only] Archive reconciliation found an error!") diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/BackupMessagesJob.kt b/app/src/main/java/org/thoughtcrime/securesms/jobs/BackupMessagesJob.kt index 4e7807a65e..fcd254689d 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/BackupMessagesJob.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/BackupMessagesJob.kt @@ -5,11 +5,14 @@ package org.thoughtcrime.securesms.jobs +import android.Manifest import android.app.Notification import android.app.PendingIntent import android.content.Intent +import android.content.pm.PackageManager import androidx.core.app.NotificationCompat import androidx.core.app.NotificationManagerCompat +import androidx.core.content.ContextCompat import org.signal.core.util.PendingIntentFlags import org.signal.core.util.Stopwatch import org.signal.core.util.isNotNullOrBlank @@ -440,6 +443,10 @@ class BackupMessagesJob private constructor( return } + if (ContextCompat.checkSelfPermission(context, Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED) { + return + } + val notification: Notification = NotificationCompat.Builder(context, NotificationChannels.getInstance().FAILURES) .setSmallIcon(R.drawable.ic_notification) .setContentTitle("[Internal-only] Unexpected remote key missing!") diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/RestoreAttachmentJob.kt b/app/src/main/java/org/thoughtcrime/securesms/jobs/RestoreAttachmentJob.kt index db593578e3..738e246eed 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/RestoreAttachmentJob.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/RestoreAttachmentJob.kt @@ -4,11 +4,14 @@ */ package org.thoughtcrime.securesms.jobs +import android.Manifest import android.app.Notification import android.app.PendingIntent import android.content.Intent +import android.content.pm.PackageManager import androidx.core.app.NotificationCompat import androidx.core.app.NotificationManagerCompat +import androidx.core.content.ContextCompat import org.greenrobot.eventbus.EventBus import org.signal.core.util.Base64.decodeBase64OrThrow import org.signal.core.util.PendingIntentFlags @@ -471,6 +474,11 @@ class RestoreAttachmentJob private constructor( return } + if (ContextCompat.checkSelfPermission(context, Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED) { + Log.w(TAG, "maybePostFailedToDownloadFromArchiveNotification: Notification permission is not granted.") + return + } + val notification: Notification = NotificationCompat.Builder(context, NotificationChannels.getInstance().FAILURES) .setSmallIcon(R.drawable.ic_notification) .setContentTitle("[Internal-only] Failed to restore attachment from Archive CDN!") @@ -486,6 +494,11 @@ class RestoreAttachmentJob private constructor( return } + if (ContextCompat.checkSelfPermission(context, Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED) { + Log.w(TAG, "maybePostFailedToDownloadFromArchiveAndTransitNotification: Notification permission is not granted.") + return + } + val notification: Notification = NotificationCompat.Builder(context, NotificationChannels.getInstance().FAILURES) .setSmallIcon(R.drawable.ic_notification) .setContentTitle("[Internal-only] Completely failed to restore attachment!") diff --git a/app/src/main/java/org/thoughtcrime/securesms/mediapreview/MediaPreviewV2ViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/mediapreview/MediaPreviewV2ViewModel.kt index 3464b93675..55ff1bd840 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mediapreview/MediaPreviewV2ViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/mediapreview/MediaPreviewV2ViewModel.kt @@ -1,12 +1,15 @@ package org.thoughtcrime.securesms.mediapreview +import android.Manifest import android.app.Notification import android.app.PendingIntent import android.content.Context import android.content.Intent +import android.content.pm.PackageManager import android.net.Uri import androidx.core.app.NotificationCompat import androidx.core.app.NotificationManagerCompat +import androidx.core.content.ContextCompat import androidx.lifecycle.ViewModel import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers import io.reactivex.rxjava3.core.Completable @@ -164,6 +167,11 @@ class MediaPreviewV2ViewModel : ViewModel() { return } + if (ContextCompat.checkSelfPermission(context, Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED) { + Log.w(TAG, "maybePostInvalidMacErrorNotification: Notification permission is not granted.") + return + } + val notification: Notification = NotificationCompat.Builder(context, NotificationChannels.getInstance().FAILURES) .setSmallIcon(R.drawable.ic_notification) .setContentTitle("[Internal-only] Bad incrementalMac!") diff --git a/app/src/main/java/org/thoughtcrime/securesms/mediasend/CameraXVideoCaptureHelper.java b/app/src/main/java/org/thoughtcrime/securesms/mediasend/CameraXVideoCaptureHelper.java index fbe76b3485..620e7ed59e 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mediasend/CameraXVideoCaptureHelper.java +++ b/app/src/main/java/org/thoughtcrime/securesms/mediasend/CameraXVideoCaptureHelper.java @@ -144,7 +144,7 @@ class CameraXVideoCaptureHelper implements CameraButtonView.VideoCaptureListener .execute(); } - @SuppressLint("RestrictedApi") + @SuppressLint({"RestrictedApi", "MissingPermission"}) private void beginCameraRecording() { cameraXModePolicy.setToVideo(cameraController); this.cameraController.setZoomRatio(getDefaultVideoZoomRatio()); diff --git a/app/src/main/java/org/thoughtcrime/securesms/messages/GroupSendEndorsementInternalNotifier.kt b/app/src/main/java/org/thoughtcrime/securesms/messages/GroupSendEndorsementInternalNotifier.kt index 94b81c3592..c69ba04d95 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/messages/GroupSendEndorsementInternalNotifier.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/messages/GroupSendEndorsementInternalNotifier.kt @@ -5,12 +5,15 @@ package org.thoughtcrime.securesms.messages +import android.Manifest import android.app.Notification import android.app.PendingIntent import android.content.Context import android.content.Intent +import android.content.pm.PackageManager import androidx.core.app.NotificationCompat import androidx.core.app.NotificationManagerCompat +import androidx.core.content.ContextCompat import org.signal.core.util.PendingIntentFlags import org.signal.core.util.logging.Log import org.thoughtcrime.securesms.R @@ -40,6 +43,11 @@ object GroupSendEndorsementInternalNotifier { return } + if (ContextCompat.checkSelfPermission(context, Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED) { + Log.w(TAG, "maybePostGroupSendFallbackError: Notification permission is not granted.") + return + } + Log.internal().w(TAG, "Group send with GSE failed, GSE was likely out of date or incorrect", Throwable()) val now = System.currentTimeMillis().milliseconds @@ -67,6 +75,11 @@ object GroupSendEndorsementInternalNotifier { return } + if (ContextCompat.checkSelfPermission(context, Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED) { + Log.w(TAG, "maybePostMissingGroupSendEndorsement: Notification permission is not granted.") + return + } + Log.internal().w(TAG, "GSE missing for recipient", Throwable()) val now = System.currentTimeMillis().milliseconds diff --git a/app/src/main/java/org/thoughtcrime/securesms/messages/MessageDecryptor.kt b/app/src/main/java/org/thoughtcrime/securesms/messages/MessageDecryptor.kt index f3c0713dba..875bdd99f9 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/messages/MessageDecryptor.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/messages/MessageDecryptor.kt @@ -1,11 +1,14 @@ package org.thoughtcrime.securesms.messages +import android.Manifest import android.app.Notification import android.app.PendingIntent import android.content.Context import android.content.Intent +import android.content.pm.PackageManager import androidx.core.app.NotificationCompat import androidx.core.app.NotificationManagerCompat +import androidx.core.content.ContextCompat import com.squareup.wire.internal.toUnmodifiableList import org.signal.core.util.PendingIntentFlags import org.signal.core.util.isAbsent @@ -44,7 +47,6 @@ import org.thoughtcrime.securesms.jobs.PreKeysSyncJob import org.thoughtcrime.securesms.jobs.SendRetryReceiptJob import org.thoughtcrime.securesms.keyvalue.SignalStore import org.thoughtcrime.securesms.logsubmit.SubmitDebugLogActivity -import org.thoughtcrime.securesms.messages.MessageDecryptor.FollowUpOperation import org.thoughtcrime.securesms.messages.SignalServiceProtoUtil.hasGroupContext import org.thoughtcrime.securesms.messages.protocol.BufferedProtocolStore import org.thoughtcrime.securesms.notifications.NotificationChannels @@ -430,6 +432,11 @@ object MessageDecryptor { } private fun postDecryptionErrorNotification(context: Context) { + if (ContextCompat.checkSelfPermission(context, Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED) { + Log.w(TAG, "postDecryptionErrorNotification: Notification permission is not granted.") + return + } + val notification: Notification = NotificationCompat.Builder(context, NotificationChannels.getInstance().FAILURES) .setSmallIcon(R.drawable.ic_notification) .setContentTitle("[Internal-only] Failed to decrypt a message!") @@ -441,6 +448,11 @@ object MessageDecryptor { } private fun postInvalidMessageNotification(context: Context, message: String) { + if (ContextCompat.checkSelfPermission(context, Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED) { + Log.w(TAG, "postInvalidMessageNotification: Notification permission is not granted.") + return + } + val notification: Notification = NotificationCompat.Builder(context, NotificationChannels.getInstance().FAILURES) .setSmallIcon(R.drawable.ic_notification) .setContentTitle("[Internal-only] Received an invalid message!") diff --git a/app/src/main/java/org/thoughtcrime/securesms/migrations/UserNotificationMigrationJob.java b/app/src/main/java/org/thoughtcrime/securesms/migrations/UserNotificationMigrationJob.java index 22ca4a10de..7acf8fb635 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/migrations/UserNotificationMigrationJob.java +++ b/app/src/main/java/org/thoughtcrime/securesms/migrations/UserNotificationMigrationJob.java @@ -1,14 +1,17 @@ package org.thoughtcrime.securesms.migrations; +import android.Manifest; import android.app.Notification; import android.app.PendingIntent; import android.content.Intent; +import android.content.pm.PackageManager; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.core.app.NotificationCompat; import androidx.core.app.NotificationManagerCompat; import androidx.core.app.TaskStackBuilder; +import androidx.core.content.ContextCompat; import org.signal.core.util.SetUtil; import org.signal.core.util.logging.Log; @@ -95,6 +98,11 @@ public class UserNotificationMigrationJob extends MigrationJob { return; } + if (ContextCompat.checkSelfPermission(context, Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED) { + Log.w(TAG, "Notification permission is not granted. Skipping."); + return; + } + String message = context.getResources().getQuantityString(R.plurals.UserNotificationMigrationJob_d_contacts_are_on_signal, registeredSystemContacts.size(), registeredSystemContacts.size()); diff --git a/app/src/main/java/org/thoughtcrime/securesms/registration/ui/RegistrationViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/registration/ui/RegistrationViewModel.kt index cb84118b8e..9f321ff718 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/registration/ui/RegistrationViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/registration/ui/RegistrationViewModel.kt @@ -6,6 +6,7 @@ package org.thoughtcrime.securesms.registration.ui import android.Manifest +import android.annotation.SuppressLint import android.content.Context import androidx.lifecycle.ViewModel import androidx.lifecycle.asLiveData @@ -123,6 +124,7 @@ class RegistrationViewModel : ViewModel() { } } + @SuppressLint("MissingPermission") fun maybePrefillE164(context: Context) { Log.v(TAG, "maybePrefillE164()") if (Permissions.hasAll(context, Manifest.permission.READ_PHONE_STATE, Manifest.permission.READ_PHONE_NUMBERS)) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/registrationv3/ui/RegistrationViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/registrationv3/ui/RegistrationViewModel.kt index e7a88ccdc2..57bac9f9f3 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/registrationv3/ui/RegistrationViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/registrationv3/ui/RegistrationViewModel.kt @@ -6,6 +6,7 @@ package org.thoughtcrime.securesms.registrationv3.ui import android.Manifest +import android.annotation.SuppressLint import android.content.Context import androidx.lifecycle.ViewModel import androidx.lifecycle.asLiveData @@ -150,6 +151,7 @@ class RegistrationViewModel : ViewModel() { } } + @SuppressLint("MissingPermission") fun maybePrefillE164(context: Context) { Log.v(TAG, "maybePrefillE164()") if (Permissions.hasAll(context, Manifest.permission.READ_PHONE_STATE, Manifest.permission.READ_PHONE_NUMBERS)) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/serialization/JsonSerializableNavType.kt b/app/src/main/java/org/thoughtcrime/securesms/serialization/JsonSerializableNavType.kt new file mode 100644 index 0000000000..ded226c86d --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/serialization/JsonSerializableNavType.kt @@ -0,0 +1,38 @@ +/* + * Copyright 2025 Signal Messenger, LLC + * SPDX-License-Identifier: AGPL-3.0-only + */ + +package org.thoughtcrime.securesms.serialization + +import android.net.Uri +import android.os.Bundle +import androidx.navigation.NavType +import kotlinx.serialization.KSerializer +import kotlinx.serialization.json.Json + +/** + * NavType for JSON Serializable types using KotlinX Serialization. + * + * This only needs to be used if there are embedded serializers (using `@Serializable(with = ...)`) as the + * native NavType serialization support doesn't handle these objects gracefully. + */ +class JsonSerializableNavType( + private val serializer: KSerializer +) : NavType(false) { + override fun get(bundle: Bundle, key: String): T? { + return Json.Default.decodeFromString(serializer, bundle.getString(key)!!) + } + + override fun parseValue(value: String): T { + return Json.Default.decodeFromString(serializer, value) + } + + override fun put(bundle: Bundle, key: String, value: T) { + bundle.putString(key, Json.Default.encodeToString(serializer, value)) + } + + override fun serializeAsValue(value: T): String { + return Uri.encode(Json.Default.encodeToString(serializer, value)) + } +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/serialization/RecipientIdSerializer.kt b/app/src/main/java/org/thoughtcrime/securesms/serialization/RecipientIdSerializer.kt new file mode 100644 index 0000000000..d608130604 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/serialization/RecipientIdSerializer.kt @@ -0,0 +1,29 @@ +/* + * Copyright 2025 Signal Messenger, LLC + * SPDX-License-Identifier: AGPL-3.0-only + */ + +package org.thoughtcrime.securesms.serialization + +import kotlinx.serialization.KSerializer +import kotlinx.serialization.descriptors.PrimitiveKind +import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor +import kotlinx.serialization.descriptors.SerialDescriptor +import kotlinx.serialization.encoding.Decoder +import kotlinx.serialization.encoding.Encoder +import org.thoughtcrime.securesms.recipients.RecipientId + +/** + * Kotlinx Serialization serializer for [RecipientId] objects. + */ +class RecipientIdSerializer : KSerializer { + override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("RecipientId", PrimitiveKind.STRING) + + override fun serialize(encoder: Encoder, value: RecipientId) { + encoder.encodeString(value.serialize()) + } + + override fun deserialize(decoder: Decoder): RecipientId { + return RecipientId.from(decoder.decodeString()) + } +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/serialization/UriSerializer.kt b/app/src/main/java/org/thoughtcrime/securesms/serialization/UriSerializer.kt new file mode 100644 index 0000000000..2e819796b6 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/serialization/UriSerializer.kt @@ -0,0 +1,29 @@ +/* + * Copyright 2025 Signal Messenger, LLC + * SPDX-License-Identifier: AGPL-3.0-only + */ + +package org.thoughtcrime.securesms.serialization + +import android.net.Uri +import kotlinx.serialization.KSerializer +import kotlinx.serialization.descriptors.PrimitiveKind +import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor +import kotlinx.serialization.descriptors.SerialDescriptor +import kotlinx.serialization.encoding.Decoder +import kotlinx.serialization.encoding.Encoder + +/** + * Kotlinx Serialization serializer for Android [Uri] objects. + */ +class UriSerializer : KSerializer { + override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("Uri", PrimitiveKind.STRING) + + override fun serialize(encoder: Encoder, value: Uri) { + encoder.encodeString(value.toString()) + } + + override fun deserialize(decoder: Decoder): Uri { + return Uri.parse(decoder.decodeString()) + } +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/service/BackupProgressService.kt b/app/src/main/java/org/thoughtcrime/securesms/service/BackupProgressService.kt index 4e50a3d62b..637288e52d 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/service/BackupProgressService.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/service/BackupProgressService.kt @@ -5,13 +5,16 @@ package org.thoughtcrime.securesms.service +import android.Manifest import android.annotation.SuppressLint import android.app.Notification import android.app.PendingIntent import android.content.Context import android.content.Intent +import android.content.pm.PackageManager import androidx.core.app.NotificationCompat import androidx.core.app.NotificationManagerCompat +import androidx.core.content.ContextCompat import org.signal.core.util.PendingIntentFlags import org.signal.core.util.logging.Log import org.thoughtcrime.securesms.MainActivity @@ -111,6 +114,11 @@ class BackupProgressService : SafeForegroundService() { BackupProgressService.progress = progress BackupProgressService.indeterminate = indeterminate + if (ContextCompat.checkSelfPermission(context, Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED) { + Log.w(TAG, "Cannot update notification due to missing permission.") + return@withLock + } + if (NotificationManagerCompat.from(context).activeNotifications.any { n -> n.id == NotificationIds.BACKUP_PROGRESS }) { NotificationManagerCompat.from(context).notify(NotificationIds.BACKUP_PROGRESS, getForegroundNotification(context)) } diff --git a/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/ActiveCallManager.kt b/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/ActiveCallManager.kt index 7b414e7835..926e311863 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/ActiveCallManager.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/ActiveCallManager.kt @@ -6,6 +6,7 @@ package org.thoughtcrime.securesms.service.webrtc import android.Manifest +import android.annotation.SuppressLint import android.app.Notification import android.app.PendingIntent import android.content.BroadcastReceiver @@ -177,6 +178,7 @@ class ActiveCallManager( } } + @SuppressLint("MissingPermission") fun update(type: Int, recipientId: RecipientId, isVideoCall: Boolean) { Log.i(TAG, "update $type $recipientId $isVideoCall") previousNotificationDisposable.dispose() diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/TextSecurePreferences.java b/app/src/main/java/org/thoughtcrime/securesms/util/TextSecurePreferences.java index 74215c7f67..acadff90cd 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/util/TextSecurePreferences.java +++ b/app/src/main/java/org/thoughtcrime/securesms/util/TextSecurePreferences.java @@ -1,8 +1,10 @@ package org.thoughtcrime.securesms.util; +import android.Manifest; import android.app.PendingIntent; import android.content.Context; import android.content.SharedPreferences; +import android.content.pm.PackageManager; import android.hardware.Camera.CameraInfo; import android.net.Uri; import android.os.Build; @@ -13,6 +15,7 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.core.app.NotificationCompat; import androidx.core.app.NotificationManagerCompat; +import androidx.core.content.ContextCompat; import androidx.preference.PreferenceManager; import org.greenrobot.eventbus.EventBus; @@ -900,6 +903,11 @@ public class TextSecurePreferences { } private static void notifyUnregisteredReceived(Context context) { + if (ContextCompat.checkSelfPermission(context, Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED) { + Log.w(TAG, "notifyUnregisteredReceived: Notification permission is not granted."); + return; + } + PendingIntent reRegistrationIntent = PendingIntent.getActivity(context, 0, RegistrationActivity.newIntentForReRegistration(context), diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/dualsim/SubscriptionManagerCompat.java b/app/src/main/java/org/thoughtcrime/securesms/util/dualsim/SubscriptionManagerCompat.java index 16e06a3b49..5a7c56d82e 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/util/dualsim/SubscriptionManagerCompat.java +++ b/app/src/main/java/org/thoughtcrime/securesms/util/dualsim/SubscriptionManagerCompat.java @@ -1,6 +1,8 @@ package org.thoughtcrime.securesms.util.dualsim; +import android.Manifest; import android.content.Context; +import android.content.pm.PackageManager; import android.os.Build; import android.telephony.SubscriptionInfo; import android.telephony.SubscriptionManager; @@ -8,6 +10,7 @@ import android.telephony.TelephonyManager; import androidx.annotation.NonNull; import androidx.annotation.RequiresApi; +import androidx.core.content.ContextCompat; import org.signal.core.util.logging.Log; import org.thoughtcrime.securesms.R; @@ -99,6 +102,11 @@ public final class SubscriptionManagerCompat { return Collections.emptyList(); } + if (ContextCompat.checkSelfPermission(context, Manifest.permission.READ_PHONE_STATE) != PackageManager.PERMISSION_GRANTED) { + Log.w(TAG, "Missing READ_PHONE_STATE permission."); + return Collections.emptyList(); + } + List list = subscriptionManager.getActiveSubscriptionInfoList(); return list != null? list : Collections.emptyList(); diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index cc2c44565e..ef7997b6ff 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -12,10 +12,10 @@ androidx-navigation = "2.8.5" androidx-window = "1.3.0" glide = "4.15.1" gradle = "8.9.0" -kotlin = "2.1.0" +kotlin = "2.2.20" libsignal-client = "0.81.0" mp4parser = "1.9.39" -android-gradle-plugin = "8.7.2" +android-gradle-plugin = "8.10.1" accompanist = "0.28.0" nanohttpd = "2.3.1" navigation-safe-args-gradle-plugin = "2.8.5" @@ -30,6 +30,7 @@ jetbrains-kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" ktlint = { id = "org.jlleitschuh.gradle.ktlint", version.ref = "ktlint" } androidx-navigation-safe-args = { id = "androidx.navigation.safeargs", version.ref = "navigation-safe-args-gradle-plugin" } compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" } +kotlinx-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" } [libraries] # Android Plugins @@ -66,6 +67,7 @@ kotlinx-coroutines-core = "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.9.0" kotlinx-coroutines-core-jvm = "org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.9.0" kotlinx-coroutines-play-services = "org.jetbrains.kotlinx:kotlinx-coroutines-play-services:1.9.0" kotlinx-coroutines-rx3 = "org.jetbrains.kotlinx:kotlinx-coroutines-rx3:1.9.0" +kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version = "1.9.0" } ktlint = { module = "org.jlleitschuh.gradle:ktlint-gradle", version.ref = "ktlint" } ktlint-twitter-compose = "com.twitter.compose.rules:ktlint:0.0.26" @@ -136,7 +138,7 @@ google-ez-vcard = "com.googlecode.ez-vcard:ez-vcard:0.9.11" google-jsr305 = "com.google.code.findbugs:jsr305:3.0.2" google-guava-android = "com.google.guava:guava:33.3.1-android" google-flexbox = "com.google.android.flexbox:flexbox:3.0.0" -com-google-devtools-ksp-gradle-plugin = "com.google.devtools.ksp:com.google.devtools.ksp.gradle.plugin:2.1.0-1.0.29" +com-google-devtools-ksp-gradle-plugin = "com.google.devtools.ksp:com.google.devtools.ksp.gradle.plugin:2.2.20-2.0.2" # Firebase firebase-messaging = "com.google.firebase:firebase-messaging:24.1.0" diff --git a/gradle/verification-metadata.xml b/gradle/verification-metadata.xml index e5ac8b4c65..fe21dd28df 100644 --- a/gradle/verification-metadata.xml +++ b/gradle/verification-metadata.xml @@ -1350,6 +1350,11 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + @@ -1360,6 +1365,11 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + @@ -1370,6 +1380,14 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + + + + @@ -3203,6 +3221,11 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + @@ -3213,6 +3236,11 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + @@ -3233,6 +3261,11 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + @@ -3248,6 +3281,11 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + @@ -3268,6 +3306,11 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + @@ -3298,6 +3341,11 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + @@ -3313,6 +3361,11 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + @@ -3328,6 +3381,11 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + @@ -3343,6 +3401,11 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + @@ -3358,6 +3421,11 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + @@ -3373,6 +3441,11 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + @@ -3383,6 +3456,11 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + @@ -3398,6 +3476,11 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + @@ -3413,6 +3496,11 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + @@ -3428,6 +3516,17 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + + + + + + + @@ -3450,6 +3549,14 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + + + + @@ -3474,6 +3581,14 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + + + + @@ -3490,6 +3605,11 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + @@ -3500,6 +3620,11 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + @@ -3510,6 +3635,14 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + + + + @@ -3526,6 +3659,14 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + + + + @@ -3550,6 +3691,14 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + + + + @@ -3576,6 +3725,19 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + + + + + + + + + @@ -3592,6 +3754,14 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + + + + @@ -3608,6 +3778,14 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + + + + @@ -3624,6 +3802,14 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + + + + @@ -3669,6 +3855,11 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + @@ -3684,11 +3875,21 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + + + + + + @@ -3704,6 +3905,11 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + @@ -3719,6 +3925,11 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + @@ -3734,6 +3945,11 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + @@ -3749,6 +3965,11 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + @@ -3764,6 +3985,11 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + @@ -3779,6 +4005,11 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + @@ -3794,6 +4025,11 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + @@ -3804,6 +4040,11 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + @@ -3824,6 +4065,11 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + @@ -3834,6 +4080,14 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + + + + @@ -3842,6 +4096,11 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + @@ -3852,6 +4111,14 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + + + + @@ -3860,6 +4127,11 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + @@ -3870,6 +4142,14 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + + + + @@ -3878,6 +4158,11 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + @@ -3888,6 +4173,14 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + + + + @@ -3896,6 +4189,11 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + @@ -3906,6 +4204,14 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + + + + @@ -3914,6 +4220,11 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + @@ -3924,6 +4235,14 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + + + + @@ -3932,6 +4251,11 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + @@ -3942,6 +4266,14 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + + + + @@ -3950,11 +4282,24 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + + + + + + + + + @@ -3963,6 +4308,11 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + @@ -3973,6 +4323,14 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + + + + @@ -3981,6 +4339,11 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + @@ -4009,6 +4372,14 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + + + + @@ -4017,6 +4388,11 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + @@ -4027,6 +4403,11 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + @@ -4092,6 +4473,14 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + + + + @@ -4300,6 +4689,11 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + @@ -4355,6 +4749,11 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + @@ -4385,6 +4784,16 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + + + + + + @@ -4401,11 +4810,24 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + + + + + + + + + @@ -4414,6 +4836,14 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + + + + @@ -4422,6 +4852,14 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + + + + @@ -4442,11 +4880,21 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + + + + + + @@ -4548,6 +4996,14 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + + + + @@ -4593,6 +5049,11 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + @@ -4603,6 +5064,11 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + @@ -5516,6 +5982,11 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + @@ -5531,6 +6002,11 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + @@ -5546,6 +6022,16 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + + + + + + @@ -5556,6 +6042,11 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + @@ -5571,6 +6062,11 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + @@ -5586,6 +6082,11 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + @@ -5606,6 +6107,16 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + + + + + + @@ -5798,56 +6309,111 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -5863,6 +6429,22 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + + + + + + + + + + + + @@ -5879,6 +6461,14 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + + + + @@ -6012,6 +6602,11 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + @@ -6027,6 +6622,11 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + @@ -6047,6 +6647,11 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + @@ -6062,6 +6667,11 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + @@ -6111,6 +6721,11 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + @@ -6184,6 +6799,16 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + + + + + + @@ -6192,6 +6817,22 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + + + + + + + + + + + + @@ -6215,6 +6856,11 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + @@ -6230,6 +6876,11 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + @@ -6245,11 +6896,21 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + + + + + + @@ -6275,6 +6936,11 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + @@ -6290,11 +6956,21 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + + + + + + @@ -6310,6 +6986,11 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + @@ -6335,6 +7016,11 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + @@ -6364,6 +7050,14 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + + + + @@ -6374,6 +7068,11 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + @@ -6396,6 +7095,17 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + + + + + + + @@ -6412,6 +7122,14 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + + + + @@ -6422,6 +7140,11 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + @@ -6438,6 +7161,14 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + + + + @@ -6453,6 +7184,11 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + @@ -6463,11 +7199,26 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + + + + + + + + + + + @@ -6478,16 +7229,31 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + + + + + + + + + + + @@ -6538,6 +7304,11 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + @@ -6551,6 +7322,11 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + @@ -6566,6 +7342,11 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + @@ -6576,6 +7357,11 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + @@ -6586,6 +7372,11 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + @@ -6596,6 +7387,11 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + @@ -6606,6 +7402,37 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -6722,6 +7549,11 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + @@ -6733,6 +7565,28 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + + + + + + + + + + + + + + + + + + @@ -6818,6 +7672,16 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + + + + + + @@ -6863,6 +7727,11 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + @@ -6908,6 +7777,11 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + @@ -6918,6 +7792,11 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + @@ -6928,6 +7807,11 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + @@ -6938,11 +7822,26 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + + + + + + + + + + + @@ -7133,6 +8032,14 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + + + + @@ -7275,6 +8182,14 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + + + + @@ -7307,6 +8222,14 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + + + + @@ -7317,6 +8240,14 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + + + + @@ -7333,6 +8264,14 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + + + + @@ -7505,6 +8444,11 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + @@ -7520,6 +8464,11 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + @@ -7535,6 +8484,11 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + @@ -7550,6 +8504,11 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + @@ -7565,6 +8524,11 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + +