Update target SDK to 35.

This commit is contained in:
Michelle Tang
2025-07-31 13:55:48 -04:00
committed by GitHub
parent 87a694c87c
commit d0d44ed7ce
10 changed files with 79 additions and 14 deletions

View File

@@ -736,13 +736,18 @@ fun getMapsKey(): String {
}
fun Project.languageList(): List<String> {
// In API 35, language codes for Hebrew and Indonesian now use the ISO 639-1 code ("he" and "id").
// However, the value resources still only support the outdated code ("iw" and "in") so we have
// to manually indicate that we support these languages.
val updatedLanguageCodes = listOf("he", "id")
return fileTree("src/main/res") { include("**/strings.xml") }
.map { stringFile -> stringFile.parentFile.name }
.map { valuesFolderName -> valuesFolderName.replace("values-", "") }
.filter { valuesFolderName -> valuesFolderName != "values" }
.map { languageCode -> languageCode.replace("-r", "_") }
.distinct()
.sorted() + "en"
.sorted() + updatedLanguageCodes + "en"
}
fun String.capitalize(): String {

View File

@@ -85,8 +85,8 @@ class AttachmentProgressService : SafeForegroundService() {
}
}
private fun stop(context: Context) {
stop(context, AttachmentProgressService::class.java)
private fun stop(context: Context, fromTimeout: Boolean = false) {
stop(context, AttachmentProgressService::class.java, fromTimeout)
}
private fun onControllersChanged(context: Context) {
@@ -139,6 +139,17 @@ class AttachmentProgressService : SafeForegroundService() {
listeners -= listener
}
override fun onTimeout(startId: Int, fgsType: Int) {
Log.w(TAG, "AttachmentProgressService has timed out. Removing all controllers. startId: $startId, foregroundServiceType: $fgsType")
controllerLock.withLock {
controllers.forEach { it.closeFromTimeout() }
stop(context = this, fromTimeout = true)
}
listeners -= listener
}
class Controller(private val context: Context, title: String) : AutoCloseable {
private val coroutineScope = CoroutineScope(Dispatchers.IO)
private val progressFlow = MutableSharedFlow<Float>(replay = 0, extraBufferCapacity = 1)
@@ -178,6 +189,13 @@ class AttachmentProgressService : SafeForegroundService() {
progressFlow.tryEmit(progress)
}
fun closeFromTimeout() {
controllerLock.withLock {
coroutineScope.cancel()
controllers.remove(this)
}
}
override fun close() {
controllerLock.withLock {
coroutineScope.cancel()

View File

@@ -57,8 +57,8 @@ class BackupProgressService : SafeForegroundService() {
}
}
private fun stop(context: Context) {
SafeForegroundService.stop(context, BackupProgressService::class.java)
private fun stop(context: Context, fromTimeout: Boolean = false) {
SafeForegroundService.stop(context, BackupProgressService::class.java, fromTimeout)
controllerLock.withLock {
controller = null
}
@@ -82,6 +82,11 @@ class BackupProgressService : SafeForegroundService() {
return getForegroundNotification(this)
}
override fun onTimeout(startId: Int, fgsType: Int) {
Log.w(TAG, "BackupProgressService has timed out. startId: $startId, foregroundServiceType: $fgsType")
stop(context = this, fromTimeout = true)
}
/**
* Use to update notification progress/state.
*/

View File

@@ -27,6 +27,7 @@ class GenericForegroundService : Service() {
private val allActiveMessages = LinkedHashMap<Int, Entry>()
private val lock = ReentrantLock()
var hasTimedOut = false
private var lastPosted: Entry? = null
companion object {
@@ -175,6 +176,16 @@ class GenericForegroundService : Service() {
super.onTrimMemory(level)
}
override fun onTimeout(startId: Int, foregroundServiceType: Int) {
Log.i(TAG, "[onTimeout] startId: $startId, fgsType: $foregroundServiceType")
lock.withLock {
hasTimedOut = true
allActiveMessages.clear()
}
ServiceCompat.stopForeground(this, ServiceCompat.STOP_FOREGROUND_REMOVE)
stopSelf()
}
fun replaceTitle(id: Int, title: String) {
lock.withLock {
updateEntry(id) { oldEntry ->

View File

@@ -58,7 +58,12 @@ class NotificationController internal constructor(private val context: Context,
} else {
Log.w(TAG, "[close] Service was not bound at the time of close()...")
}
stopForegroundTask(context, id)
if (service.get()?.hasTimedOut == true) {
Log.w(TAG, "[close] Service has timed out, skipping stop foreground task.")
} else {
stopForegroundTask(context, id)
}
} catch (e: IllegalStateException) {
Log.w(TAG, "[close] Failed to unbind service...", e)
} catch (e: UnableToStartException) {

View File

@@ -34,6 +34,7 @@ abstract class SafeForegroundService : Service() {
private const val ACTION_START = "start"
private const val ACTION_UPDATE = "update"
private const val ACTION_STOP = "stop"
private const val ACTION_TIMEOUT = "timeout"
private var states: MutableMap<Class<out SafeForegroundService>, State> = mutableMapOf()
private val stateLock = ReentrantLock()
@@ -86,9 +87,11 @@ abstract class SafeForegroundService : Service() {
* Safely stops the service by starting it with an action to stop itself.
* This is done to prevent scenarios where you stop the service while
* a start is pending, preventing the posting of a foreground notification.
*
* @param fromTimeout - Whether we are stopping due to system timeout (limit is 6hr in 24hr)
* @return true if service was running previously
*/
fun stop(context: Context, serviceClass: Class<out SafeForegroundService>): Boolean {
fun stop(context: Context, serviceClass: Class<out SafeForegroundService>, fromTimeout: Boolean = false): Boolean {
stateLock.withLock {
val state = currentState(serviceClass)
@@ -98,10 +101,11 @@ abstract class SafeForegroundService : Service() {
State.STARTING -> {
Log.d(TAG, "[stop] Stopping service.")
states[serviceClass] = State.STOPPING
val stopAction = if (fromTimeout) ACTION_TIMEOUT else ACTION_STOP
try {
ForegroundServiceUtil.startWhenCapable(
context = context,
intent = Intent(context, serviceClass).apply { action = ACTION_STOP }
intent = Intent(context, serviceClass).apply { action = stopAction }
)
} catch (e: UnableToStartException) {
Log.w(TAG, "Failed to start service class $serviceClass", e)
@@ -182,7 +186,9 @@ abstract class SafeForegroundService : Service() {
Log.d(tag, "[onStartCommand] action: ${intent.action}")
if (Build.VERSION.SDK_INT >= 30 && serviceType != 0) {
if (intent.action == ACTION_TIMEOUT) {
Log.i(TAG, "Time limit for foreground services has been met. Skipping starting a foreground.")
} else if (Build.VERSION.SDK_INT >= 30 && serviceType != 0) {
startForeground(notificationId, getForegroundNotification(intent), serviceType)
} else {
startForeground(notificationId, getForegroundNotification(intent))
@@ -192,6 +198,7 @@ abstract class SafeForegroundService : Service() {
ACTION_START -> {
onServiceStartCommandReceived(intent)
}
ACTION_TIMEOUT,
ACTION_STOP -> {
onServiceStopCommandReceived(intent)
ServiceCompat.stopForeground(this, ServiceCompat.STOP_FOREGROUND_REMOVE)

View File

@@ -155,7 +155,7 @@ class ActiveCallManager(
AppDependencies.unauthWebSocket.registerKeepAliveToken(WEBSOCKET_KEEP_ALIVE_TOKEN)
}
fun shutdown() {
fun shutdown(fromTimeout: Boolean = false) {
Log.v(TAG, "shutdown")
previousNotificationDisposable.dispose()
@@ -172,7 +172,7 @@ class ActiveCallManager(
AppDependencies.authWebSocket.removeKeepAliveToken(WEBSOCKET_KEEP_ALIVE_TOKEN)
AppDependencies.unauthWebSocket.removeKeepAliveToken(WEBSOCKET_KEEP_ALIVE_TOKEN)
if (!ActiveCallForegroundService.stop(application) && previousNotificationId != -1) {
if (!ActiveCallForegroundService.stop(application, fromTimeout) && previousNotificationId != -1) {
NotificationManagerCompat.from(application).cancel(previousNotificationId)
}
}
@@ -290,8 +290,8 @@ class ActiveCallManager(
}
}
fun stop(context: Context): Boolean {
return SafeForegroundService.stop(context, ActiveCallForegroundService::class.java)
fun stop(context: Context, fromTimeout: Boolean = false): Boolean {
return SafeForegroundService.stop(context, ActiveCallForegroundService::class.java, fromTimeout)
}
}
@@ -348,6 +348,15 @@ class ActiveCallManager(
notificationDisposable.dispose()
}
override fun onTimeout(startId: Int, fgsType: Int) {
Log.w(TAG, "ActiveCallForegroundService has timed out. Hanging up. startId: $startId, foregroundServiceType: $fgsType")
AppDependencies.signalCallManager.localHangup()
activeCallManagerLock.withLock {
activeCallManager?.shutdown(fromTimeout = true)
activeCallManager = null
}
}
override fun onDestroy() {
super.onDestroy()

View File

@@ -146,6 +146,7 @@
<style name="TextSecure.BaseLightTheme" parent="@style/Theme.Material3.Light">
<item name="theme_type">light</item>
<item name="android:forceDarkAllowed" tools:targetApi="29">false</item>
<item name="android:windowOptOutEdgeToEdgeEnforcement" tools:targetApi="35">true</item>
<!-- Material 3 -->
<item name="colorPrimary">@color/signal_colorPrimary</item>
@@ -239,6 +240,7 @@
<style name="TextSecure.BaseDarkTheme" parent="@style/Theme.Material3.Dark">
<item name="theme_type">dark</item>
<item name="android:forceDarkAllowed" tools:targetApi="29">false</item>
<item name="android:windowOptOutEdgeToEdgeEnforcement" tools:targetApi="35">true</item>
<!-- Material 3 -->
<item name="colorPrimary">@color/signal_colorPrimary</item>

View File

@@ -37,6 +37,9 @@ public final class LanguageResourcesTest {
public void language_options_matches_available_resources() {
Set<String> languageEntries = languageEntries();
Set<String> foundResources = buildConfigResources();
Set<String> manuallyAddedResources = Set.of("id", "he"); // With API 35, we had to manually add certain supported languages
foundResources.removeAll(manuallyAddedResources);
if (!languageEntries.equals(foundResources)) {
assertSubset(foundResources, languageEntries, "Missing language_entries for resources");
assertSubset(languageEntries, foundResources, "Missing resources for language_entries");

View File

@@ -1,6 +1,6 @@
val signalBuildToolsVersion by extra("35.0.0")
val signalCompileSdkVersion by extra("android-35")
val signalTargetSdkVersion by extra(34)
val signalTargetSdkVersion by extra(35)
val signalMinSdkVersion by extra(21)
val signalNdkVersion by extra("28.0.13004108")
val signalJavaVersion by extra(JavaVersion.VERSION_17)