From c07df9e05ff3214261c04834e746901e8513b9a2 Mon Sep 17 00:00:00 2001
From: Stanislav Dmitrenko <7953703+avently@users.noreply.github.com>
Date: Sat, 24 Aug 2024 14:00:56 +0000
Subject: [PATCH] android: target API level 34 (Android 14) (#4697)
---
apps/multiplatform/android/build.gradle.kts | 22 +++++-----
.../android/src/main/AndroidManifest.xml | 14 +++++-
.../main/java/chat/simplex/app/CallService.kt | 44 ++++++++++++++++---
.../java/chat/simplex/app/MainActivity.kt | 2 +-
.../main/java/chat/simplex/app/SimplexApp.kt | 5 ++-
.../java/chat/simplex/app/SimplexService.kt | 36 ++++++++++++---
.../simplex/app/model/NtfManager.android.kt | 4 +-
.../simplex/app/views/call/CallActivity.kt | 10 ++---
.../android/src/main/res/values/colors.xml | 1 -
apps/multiplatform/common/build.gradle.kts | 16 +++----
.../res/drawable/edit_text_cursor.xml | 2 +-
11 files changed, 112 insertions(+), 44 deletions(-)
diff --git a/apps/multiplatform/android/build.gradle.kts b/apps/multiplatform/android/build.gradle.kts
index 5c2c786a21..250616ea5c 100644
--- a/apps/multiplatform/android/build.gradle.kts
+++ b/apps/multiplatform/android/build.gradle.kts
@@ -15,7 +15,7 @@ android {
namespace = "chat.simplex.app"
minSdk = 26
//noinspection OldTargetApi
- targetSdk = 33
+ targetSdk = 34
// !!!
// skip version code after release to F-Droid, as it uses two version codes
versionCode = (extra["android.version_code"] as String).toInt()
@@ -126,29 +126,29 @@ android {
dependencies {
implementation(project(":common"))
- implementation("androidx.core:core-ktx:1.12.0")
+ implementation("androidx.core:core-ktx:1.13.1")
//implementation("androidx.compose.ui:ui:${rootProject.extra["compose.version"] as String}")
//implementation("androidx.compose.material:material:$compose_version")
//implementation("androidx.compose.ui:ui-tooling-preview:$compose_version")
- implementation("androidx.appcompat:appcompat:1.6.1")
- implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.7.0")
- implementation("androidx.lifecycle:lifecycle-process:2.7.0")
- implementation("androidx.activity:activity-compose:1.8.2")
- val workVersion = "2.9.0"
+ implementation("androidx.appcompat:appcompat:1.7.0")
+ implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.8.4")
+ implementation("androidx.lifecycle:lifecycle-process:2.8.4")
+ implementation("androidx.activity:activity-compose:1.9.1")
+ val workVersion = "2.9.1"
implementation("androidx.work:work-runtime-ktx:$workVersion")
implementation("androidx.work:work-multiprocess:$workVersion")
- implementation("com.jakewharton:process-phoenix:2.2.0")
+ implementation("com.jakewharton:process-phoenix:3.0.0")
//Camera Permission
- implementation("com.google.accompanist:accompanist-permissions:0.23.0")
+ implementation("com.google.accompanist:accompanist-permissions:0.34.0")
//implementation("androidx.compose.material:material-icons-extended:$compose_version")
//implementation("androidx.compose.ui:ui-util:$compose_version")
testImplementation("junit:junit:4.13.2")
- androidTestImplementation("androidx.test.ext:junit:1.1.5")
- androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")
+ androidTestImplementation("androidx.test.ext:junit:1.2.1")
+ androidTestImplementation("androidx.test.espresso:espresso-core:3.6.1")
//androidTestImplementation("androidx.compose.ui:ui-test-junit4:$compose_version")
debugImplementation("androidx.compose.ui:ui-tooling:1.6.4")
}
diff --git a/apps/multiplatform/android/src/main/AndroidManifest.xml b/apps/multiplatform/android/src/main/AndroidManifest.xml
index 073f1bf8c8..deb5d83e5f 100644
--- a/apps/multiplatform/android/src/main/AndroidManifest.xml
+++ b/apps/multiplatform/android/src/main/AndroidManifest.xml
@@ -21,6 +21,12 @@
+
+
+
+
+
+
+ android:stopWithTask="false"
+ android:foregroundServiceType="remoteMessaging"
+ />
@@ -141,7 +149,9 @@
android:name=".CallService"
android:enabled="true"
android:exported="false"
- android:stopWithTask="false"/>
+ android:stopWithTask="false"
+ android:foregroundServiceType="mediaPlayback|microphone|camera|remoteMessaging"
+ />
= 34) {
+ ServiceInfo.FOREGROUND_SERVICE_TYPE_REMOTE_MESSAGING
+ } else {
+ 0
+ }
+ } else if (Build.VERSION.SDK_INT >= 30) {
+ if (call.supportsVideo()) {
+ ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK or ServiceInfo.FOREGROUND_SERVICE_TYPE_MICROPHONE or ServiceInfo.FOREGROUND_SERVICE_TYPE_CAMERA
+ } else {
+ ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK or ServiceInfo.FOREGROUND_SERVICE_TYPE_MICROPHONE
+ }
+ } else if (Build.VERSION.SDK_INT >= 29) {
+ ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK
+ } else {
+ 0
+ }
}
private fun createNotificationChannel(): NotificationManager? {
diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/MainActivity.kt b/apps/multiplatform/android/src/main/java/chat/simplex/app/MainActivity.kt
index 5a69d282b4..c63b6cb497 100644
--- a/apps/multiplatform/android/src/main/java/chat/simplex/app/MainActivity.kt
+++ b/apps/multiplatform/android/src/main/java/chat/simplex/app/MainActivity.kt
@@ -54,7 +54,7 @@ class MainActivity: FragmentActivity() {
SimplexApp.context.schedulePeriodicWakeUp()
}
- override fun onNewIntent(intent: Intent?) {
+ override fun onNewIntent(intent: Intent) {
super.onNewIntent(intent)
processIntent(intent)
processExternalIntent(intent)
diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/SimplexApp.kt b/apps/multiplatform/android/src/main/java/chat/simplex/app/SimplexApp.kt
index 9206b5be89..a8b91e261b 100644
--- a/apps/multiplatform/android/src/main/java/chat/simplex/app/SimplexApp.kt
+++ b/apps/multiplatform/android/src/main/java/chat/simplex/app/SimplexApp.kt
@@ -120,7 +120,10 @@ class SimplexApp: Application(), LifecycleEventObserver {
* */
if (chatModel.chatRunning.value != false &&
chatModel.controller.appPrefs.onboardingStage.get() == OnboardingStage.OnboardingComplete &&
- appPrefs.notificationsMode.get() == NotificationsMode.SERVICE
+ appPrefs.notificationsMode.get() == NotificationsMode.SERVICE &&
+ // New installation passes all checks above and tries to start the service which is not needed at all
+ // because preferred notification type is not yet chosen. So, check that the user has initialized db already
+ appPrefs.newDatabaseInitialized.get()
) {
SimplexService.start()
}
diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/SimplexService.kt b/apps/multiplatform/android/src/main/java/chat/simplex/app/SimplexService.kt
index a5f5d84ec2..004d2bc7f1 100644
--- a/apps/multiplatform/android/src/main/java/chat/simplex/app/SimplexService.kt
+++ b/apps/multiplatform/android/src/main/java/chat/simplex/app/SimplexService.kt
@@ -4,6 +4,7 @@ import android.annotation.SuppressLint
import android.app.*
import android.content.*
import android.content.pm.PackageManager
+import android.content.pm.ServiceInfo
import android.net.Uri
import android.os.*
import android.os.SystemClock
@@ -15,8 +16,10 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.core.app.NotificationCompat
+import androidx.core.app.ServiceCompat
import androidx.core.content.ContextCompat
import androidx.work.*
+import chat.simplex.app.model.NtfManager
import chat.simplex.common.AppLock
import chat.simplex.common.helpers.requiresIgnoringBattery
import chat.simplex.common.model.ChatController
@@ -52,18 +55,15 @@ class SimplexService: Service() {
} else {
Log.d(TAG, "null intent. Probably restarted by the system.")
}
- startForeground(SIMPLEX_SERVICE_ID, serviceNotification)
+ ServiceCompat.startForeground(this, SIMPLEX_SERVICE_ID, createNotificationIfNeeded(), foregroundServiceType())
return START_STICKY // to restart if killed
}
override fun onCreate() {
super.onCreate()
Log.d(TAG, "Simplex service created")
- val title = generalGetString(MR.strings.simplex_service_notification_title)
- val text = generalGetString(MR.strings.simplex_service_notification_text)
- notificationManager = createNotificationChannel()
- serviceNotification = createNotification(title, text)
- startForeground(SIMPLEX_SERVICE_ID, serviceNotification)
+ createNotificationIfNeeded()
+ ServiceCompat.startForeground(this, SIMPLEX_SERVICE_ID, createNotificationIfNeeded(), foregroundServiceType())
/**
* The reason [stopAfterStart] exists is because when the service is not called [startForeground] yet, and
* we call [stopSelf] on the same service, [ForegroundServiceDidNotStartInTimeException] will be thrown.
@@ -103,6 +103,26 @@ class SimplexService: Service() {
super.onDestroy()
}
+ private fun createNotificationIfNeeded(): Notification {
+ val ntf = serviceNotification
+ if (ntf != null) return ntf
+
+ val title = generalGetString(MR.strings.simplex_service_notification_title)
+ val text = generalGetString(MR.strings.simplex_service_notification_text)
+ notificationManager = createNotificationChannel()
+ val newNtf = createNotification(title, text)
+ serviceNotification = newNtf
+ return newNtf
+ }
+
+ private fun foregroundServiceType(): Int {
+ return if (Build.VERSION.SDK_INT >= 34) {
+ ServiceInfo.FOREGROUND_SERVICE_TYPE_REMOTE_MESSAGING
+ } else {
+ 0
+ }
+ }
+
private fun startService() {
Log.d(TAG, "SimplexService startService")
if (wakeLock != null || isCheckingNewMessages) return
@@ -292,6 +312,10 @@ class SimplexService: Service() {
}
private suspend fun serviceAction(action: Action) {
+ if (!NtfManager.areNotificationsEnabledInSystem()) {
+ Log.d(TAG, "SimplexService serviceAction: ${action.name}. Notifications are not enabled in OS yet, not starting service")
+ return
+ }
Log.d(TAG, "SimplexService serviceAction: ${action.name}")
withContext(Dispatchers.IO) {
Intent(androidAppContext, SimplexService::class.java).also {
diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/model/NtfManager.android.kt b/apps/multiplatform/android/src/main/java/chat/simplex/app/model/NtfManager.android.kt
index 417a81a953..cf19589d4a 100644
--- a/apps/multiplatform/android/src/main/java/chat/simplex/app/model/NtfManager.android.kt
+++ b/apps/multiplatform/android/src/main/java/chat/simplex/app/model/NtfManager.android.kt
@@ -53,7 +53,7 @@ object NtfManager {
private val msgNtfTimeoutMs = 30000L
init {
- if (manager.areNotificationsEnabled()) createNtfChannelsMaybeShowAlert()
+ if (areNotificationsEnabledInSystem()) createNtfChannelsMaybeShowAlert()
}
private fun callNotificationChannel(channelId: String, channelName: String): NotificationChannel {
@@ -287,6 +287,8 @@ object NtfManager {
}
}
+ fun areNotificationsEnabledInSystem() = manager.areNotificationsEnabled()
+
/**
* This function creates notifications channels. On Android 13+ calling it for the first time will trigger system alert,
* The alert asks a user to allow or disallow to show notifications for the app. That's why it should be called only when the user
diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/call/CallActivity.kt b/apps/multiplatform/android/src/main/java/chat/simplex/app/views/call/CallActivity.kt
index b78f3ac518..3d29737128 100644
--- a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/call/CallActivity.kt
+++ b/apps/multiplatform/android/src/main/java/chat/simplex/app/views/call/CallActivity.kt
@@ -120,6 +120,7 @@ class CallActivity: ComponentActivity(), ServiceConnection {
return grantedAudio && grantedCamera
}
+ @Deprecated("Was deprecated in OS")
override fun onBackPressed() {
if (isOnLockScreenNow()) {
super.onBackPressed()
@@ -139,6 +140,7 @@ class CallActivity: ComponentActivity(), ServiceConnection {
}
override fun onUserLeaveHint() {
+ super.onUserLeaveHint()
// On Android 12+ PiP is enabled automatically when a user hides the app
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.R && callSupportsVideo() && platform.androidPictureInPictureAllowed()) {
enterPictureInPictureMode()
@@ -248,6 +250,9 @@ fun CallActivityView() {
)
if (permissionsState.allPermissionsGranted) {
ActiveCallView()
+ LaunchedEffect(Unit) {
+ activity.startServiceAndBind()
+ }
} else {
CallPermissionsView(remember { m.activeCallViewIsCollapsed }.value, callSupportsVideo()) {
withBGApi { chatModel.callManager.endCall(call) }
@@ -285,11 +290,6 @@ fun CallActivityView() {
AlertManager.shared.showInView()
}
}
- LaunchedEffect(call == null) {
- if (call != null) {
- activity.startServiceAndBind()
- }
- }
LaunchedEffect(invitation, call, switchingCall, showCallView) {
if (!switchingCall && invitation == null && (!showCallView || call == null)) {
Log.d(TAG, "CallActivityView: finishing activity")
diff --git a/apps/multiplatform/android/src/main/res/values/colors.xml b/apps/multiplatform/android/src/main/res/values/colors.xml
index e1a994e57f..1833a6d9a3 100644
--- a/apps/multiplatform/android/src/main/res/values/colors.xml
+++ b/apps/multiplatform/android/src/main/res/values/colors.xml
@@ -2,6 +2,5 @@
#FF000000
#FFFFFFFF
- #8b8786
#121212
\ No newline at end of file
diff --git a/apps/multiplatform/common/build.gradle.kts b/apps/multiplatform/common/build.gradle.kts
index 7e97ea3414..a1afb655e2 100644
--- a/apps/multiplatform/common/build.gradle.kts
+++ b/apps/multiplatform/common/build.gradle.kts
@@ -61,8 +61,8 @@ kotlin {
val androidMain by getting {
kotlin.srcDir("build/generated/moko/androidMain/src")
dependencies {
- implementation("androidx.activity:activity-compose:1.8.2")
- val workVersion = "2.9.0"
+ implementation("androidx.activity:activity-compose:1.9.1")
+ val workVersion = "2.9.1"
implementation("androidx.work:work-runtime-ktx:$workVersion")
implementation("com.google.accompanist:accompanist-insets:0.30.1")
@@ -78,22 +78,22 @@ kotlin {
//Camera Permission
implementation("com.google.accompanist:accompanist-permissions:0.34.0")
- implementation("androidx.webkit:webkit:1.10.0")
+ implementation("androidx.webkit:webkit:1.11.0")
// GIFs support
implementation("io.coil-kt:coil-compose:2.6.0")
implementation("io.coil-kt:coil-gif:2.6.0")
- implementation("com.jakewharton:process-phoenix:2.2.0")
+ implementation("com.jakewharton:process-phoenix:3.0.0")
- val cameraXVersion = "1.3.2"
+ val cameraXVersion = "1.3.4"
implementation("androidx.camera:camera-core:${cameraXVersion}")
implementation("androidx.camera:camera-camera2:${cameraXVersion}")
implementation("androidx.camera:camera-lifecycle:${cameraXVersion}")
implementation("androidx.camera:camera-view:${cameraXVersion}")
// Calls lifecycle listener
- implementation("androidx.lifecycle:lifecycle-process:2.4.1")
+ implementation("androidx.lifecycle:lifecycle-process:2.8.4")
}
}
val desktopMain by getting {
@@ -119,8 +119,8 @@ android {
defaultConfig {
minSdk = 26
}
- testOptions.targetSdk = 33
- lint.targetSdk = 33
+ testOptions.targetSdk = 34
+ lint.targetSdk = 34
val isAndroid = gradle.startParameter.taskNames.find {
val lower = it.lowercase()
lower.contains("release") || lower.startsWith("assemble") || lower.startsWith("install")
diff --git a/apps/multiplatform/common/src/androidMain/res/drawable/edit_text_cursor.xml b/apps/multiplatform/common/src/androidMain/res/drawable/edit_text_cursor.xml
index 683c3a4dd4..948ae4d4bf 100644
--- a/apps/multiplatform/common/src/androidMain/res/drawable/edit_text_cursor.xml
+++ b/apps/multiplatform/common/src/androidMain/res/drawable/edit_text_cursor.xml
@@ -1,5 +1,5 @@
-
+