Merge commit 'e8ecd090c42d11ee7c5dc30b79217079bd8d6c4d'

Stephen Paul Weber created

* commit 'e8ecd090c42d11ee7c5dc30b79217079bd8d6c4d': (33 commits)
  amend changelog for 2.18.2
  disabling autofill hints programatically seems to have no effect
  Translated using Weblate (German)
  Translated using Weblate (Estonian)
  Translated using Weblate (Chinese (Simplified Han script))
  Translated using Weblate (Ukrainian)
  Translated using Weblate (Russian)
  change selector for tablet
  change link to bouncycastle
  fix FAB not shriking
  fix selection indicator in overview
  bump version code
  remove some unused code
  set fgs type on import backup worker
  reorganize third party libraries in about section
  version bump to 2.18.2
  parse uri params as multi list
  Translated using Weblate (Estonian)
  Translated using Weblate (Chinese (Simplified Han script))
  Translated using Weblate (Turkish)
  ...

Change summary

CHANGELOG.md                                                                                 |  6 
build.gradle                                                                                 | 82 
fastlane/metadata/android/de-DE/changelogs/4214004.txt                                       |  2 
fastlane/metadata/android/de-DE/changelogs/4214204.txt                                       |  2 
fastlane/metadata/android/en-US/changelogs/4214204.txt                                       |  2 
fastlane/metadata/android/et/changelogs/4214004.txt                                          |  2 
fastlane/metadata/android/et/changelogs/4214204.txt                                          |  2 
fastlane/metadata/android/gl-ES/changelogs/4214004.txt                                       |  2 
fastlane/metadata/android/pl-PL/changelogs/4214004.txt                                       |  2 
fastlane/metadata/android/ru-RU/changelogs/4214004.txt                                       |  2 
fastlane/metadata/android/sq/changelogs/4214004.txt                                          |  2 
fastlane/metadata/android/uk/changelogs/4214204.txt                                          |  2 
fastlane/metadata/android/zh-CN/changelogs/4214204.txt                                       |  2 
src/cheogram/java/com/cheogram/ExportBackupService.java                                      |  2 
src/main/java/de/gultsch/common/MiniUri.java                                                 | 14 
src/main/java/eu/siacs/conversations/AppSettings.java                                        | 10 
src/main/java/eu/siacs/conversations/services/NotificationService.java                       |  6 
src/main/java/eu/siacs/conversations/services/XmppConnectionService.java                     | 13 
src/main/java/eu/siacs/conversations/ui/ConversationsOverviewFragment.java                   | 34 
src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java                             |  6 
src/main/java/eu/siacs/conversations/ui/ExtendedFabSizeChanger.java                          | 14 
src/main/java/eu/siacs/conversations/ui/fragment/settings/NotificationsSettingsFragment.java |  2 
src/main/java/eu/siacs/conversations/utils/Compatibility.java                                | 68 
src/main/java/eu/siacs/conversations/worker/ImportBackupWorker.java                          | 16 
src/main/res/drawable/background_selected_item_conversation.xml                              | 21 
src/main/res/layout-w1024dp-h640dp/activity_conversations.xml                                |  0 
src/main/res/layout/activity_about.xml                                                       |  9 
src/main/res/values-de/strings.xml                                                           |  3 
src/main/res/values-et/strings.xml                                                           |  3 
src/main/res/values-gl/strings.xml                                                           |  3 
src/main/res/values-pl/strings.xml                                                           |  3 
src/main/res/values-ro-rRO/strings.xml                                                       |  3 
src/main/res/values-ru/strings.xml                                                           |  3 
src/main/res/values-sq-rAL/strings.xml                                                       |  3 
src/main/res/values-tr-rTR/strings.xml                                                       | 41 
src/main/res/values-uk/strings.xml                                                           |  3 
src/main/res/values-w1024dp-h640dp/defaults.xml                                              |  0 
src/main/res/values-w1024dp-h640dp/dimens.xml                                                |  0 
src/main/res/values-zh-rCN/strings.xml                                                       | 19 
src/main/res/values/about.xml                                                                | 41 
40 files changed, 255 insertions(+), 195 deletions(-)

Detailed changes

CHANGELOG.md 🔗

@@ -1,5 +1,11 @@
 # Changelog
 
+### Version 2.18.2
+
+* Support 'Service Outage Status'
+* Fix Backup Import
+* Minor security fixes for parsing multiple bodies, occupant-ids and stanza-id
+
 ### Version 2.18.1
 
 * Fix reactions on files received via P2P

build.gradle 🔗

@@ -61,65 +61,56 @@ dependencies {
     implementation "androidx.core:core:1.10.1"
     coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.1.5'
 
-    implementation project(':libs:annotation')
     annotationProcessor project(':libs:annotation-processor')
-
+    implementation project(':libs:annotation')
+    implementation 'androidx.appcompat:appcompat:1.7.0'
+    implementation 'androidx.cardview:cardview:1.0.0'
+    implementation 'androidx.emoji2:emoji2:1.5.0'
+    freeImplementation 'androidx.emoji2:emoji2-bundled:1.5.0'
+    implementation 'androidx.exifinterface:exifinterface:1.4.0'
+    implementation 'androidx.preference:preference:1.2.1'
+    implementation 'androidx.sharetarget:sharetarget:1.2.0'
+    implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'
     implementation 'androidx.viewpager:viewpager:1.1.0'
-
+    implementation 'androidx.work:work-runtime:2.10.0'
+    implementation 'com.github.open-keychain.open-keychain:openpgp-api:v5.7.1'
+    implementation 'com.google.android.material:material:1.13.0-alpha10'
+    implementation 'com.google.guava:guava:33.4.6-android'
+    implementation 'com.google.zxing:core:3.5.3'
+    implementation 'com.leinardi.android:speed-dial:3.3.0'
+    implementation 'com.makeramen:roundedimageview:2.3.0'
+    implementation 'com.squareup.okhttp3:okhttp:4.12.0'
+    implementation 'com.squareup.retrofit2:converter-gson:2.11.0'
+    implementation 'com.squareup.retrofit2:retrofit:2.11.0'
+    implementation 'com.vanniktech:android-image-cropper:4.6.0'
+    implementation 'im.conversations.webrtc:webrtc-android:129.0.0'
+    implementation 'io.deepmedia.community:transcoder-android:0.11.2'
+    implementation 'me.drakeet.support:toastcompat:1.1.0'
+    implementation 'me.leolin:ShortcutBadger:1.1.22@aar'
+    implementation 'org.bouncycastle:bcmail-jdk18on:1.80'
+    implementation 'org.conscrypt:conscrypt-android:2.5.2'
+    implementation 'org.hsluv:hsluv:0.2'
+    implementation 'org.jxmpp:jxmpp-jid:1.0.3'
+    implementation 'org.jxmpp:jxmpp-stringprep-libidn:1.0.3'
+    implementation 'org.minidns:minidns-client:1.0.4'
+    implementation 'org.minidns:minidns-dnssec:1.0.4'
+    implementation 'org.osmdroid:osmdroid-android:6.1.20'
+    implementation 'org.whispersystems:signal-protocol-java:2.6.2'
+    conversationsPlaystoreImplementation 'com.android.installreferrer:installreferrer:2.2'
     playstoreImplementation('com.google.firebase:firebase-messaging:24.1.1') {
         exclude group: 'com.google.firebase', module: 'firebase-core'
         exclude group: 'com.google.firebase', module: 'firebase-analytics'
         exclude group: 'com.google.firebase', module: 'firebase-measurement-connector'
+
     }
     cheogramPlaystoreImplementation("com.android.installreferrer:installreferrer:2.2")
     cheogramPlaystoreImplementation 'com.github.singpolyma:play-licensing:1c637ea03c'
-    conversationsPlaystoreImplementation("com.android.installreferrer:installreferrer:2.2")
-    quicksyPlaystoreImplementation 'com.google.android.gms:play-services-auth-api-phone:18.2.0'
-    implementation 'com.github.open-keychain.open-keychain:openpgp-api:v5.7.1'
-    implementation("com.vanniktech:android-image-cropper:4.6.0")
-    implementation "androidx.sharetarget:sharetarget:1.2.0"
-
-    implementation 'androidx.appcompat:appcompat:1.7.0'
-    implementation 'androidx.exifinterface:exifinterface:1.4.0'
-    implementation 'androidx.cardview:cardview:1.0.0'
-    implementation "androidx.preference:preference:1.2.1"
-    implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'
-    implementation 'com.google.android.material:material:1.13.0-alpha10'
-    implementation 'androidx.work:work-runtime:2.10.0'
-
-    implementation "androidx.emoji2:emoji2:1.5.0"
-    freeImplementation "androidx.emoji2:emoji2-bundled:1.5.0"
 
-    implementation 'org.bouncycastle:bcmail-jdk18on:1.80'
     implementation 'org.bouncycastle:bcpg-jdk18on:1.80'
-    implementation 'com.google.zxing:core:3.5.3'
     implementation 'org.minidns:minidns-hla:1.1.1'
-    implementation 'me.leolin:ShortcutBadger:1.1.22@aar'
-    implementation 'org.whispersystems:signal-protocol-java:2.6.2'
     implementation "com.wefika:flowlayout:0.4.1"
 
-    //noinspection GradleDependency
-    implementation 'io.deepmedia.community:transcoder-android:0.11.2'
-
-    implementation 'org.jxmpp:jxmpp-jid:1.0.3'
-    implementation 'org.jxmpp:jxmpp-stringprep-libidn:1.0.3'
-    implementation 'org.osmdroid:osmdroid-android:6.1.11'
-    implementation 'org.hsluv:hsluv:0.2'
-    implementation 'org.conscrypt:conscrypt-android:2.5.2'
-    implementation 'me.drakeet.support:toastcompat:1.1.0'
-    implementation "com.leinardi.android:speed-dial:3.3.0"
-
-    implementation "com.squareup.retrofit2:retrofit:2.11.0"
-    implementation "com.squareup.retrofit2:converter-gson:2.11.0"
-    implementation "com.squareup.okhttp3:okhttp:4.12.0"
-
-    implementation 'com.google.guava:guava:33.4.6-android'
     implementation 'io.michaelrocks:libphonenumber-android:8.13.52'
-    implementation 'im.conversations.webrtc:webrtc-android:129.0.0'
-    implementation 'io.github.nishkarsh:android-permissions:2.1.6'
-    implementation 'androidx.recyclerview:recyclerview:1.1.0'
-    implementation 'androidx.documentfile:documentfile:1.0.1'
-    implementation 'androidx.browser:browser:1.8.0'
     implementation 'com.github.martin-stone:hsv-alpha-color-picker-android:3.1.0'
     implementation 'com.github.ipld:java-cid:v1.3.1'
     //implementation 'com.splitwise:tokenautocomplete:3.0.2'
@@ -135,8 +126,9 @@ dependencies {
     implementation 'net.fellbaum:jemoji:1.4.1'
     implementation 'com.github.natario1:Autocomplete:v1.1.0'
     implementation 'com.mikepenz:materialdrawer:9.0.1'
+    implementation 'androidx.browser:browser:1.8.0'
+    implementation 'io.github.nishkarsh:android-permissions:2.1.6'
 
-    //Testing
     testImplementation 'junit:junit:4.13.2'
 }
 

fastlane/metadata/android/uk/changelogs/4214204.txt 🔗

@@ -0,0 +1,2 @@
+* Підтримка стану «Сервіс не працює»
+* Незначні виправлення безпеки для розбору кількох тіл (body), occupant-id, та stanza-id

src/cheogram/java/com/cheogram/ExportBackupService.java 🔗

@@ -90,7 +90,7 @@ public class ExportBackupService extends Worker {
         //do not use 'vnd.android.document/directory' since this will trigger system file manager
         final Intent openIntent = new Intent(Intent.ACTION_VIEW);
         openIntent.addCategory(Intent.CATEGORY_DEFAULT);
-        if (Compatibility.runsAndTargetsTwentyFour(context)) {
+        if (android.os.Build.VERSION.SDK_INT >= 24) {
             openIntent.setType("resource/folder");
         } else {
             openIntent.setDataAndType(Uri.parse("file://" + path), "resource/folder");

src/main/java/de/gultsch/common/MiniUri.java 🔗

@@ -5,9 +5,10 @@ import androidx.annotation.NonNull;
 import com.google.common.base.MoreObjects;
 import com.google.common.base.Splitter;
 import com.google.common.base.Strings;
-import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableMultimap;
 import java.io.UnsupportedEncodingException;
 import java.net.URLDecoder;
+import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
 import java.util.Locale;
@@ -21,7 +22,7 @@ public class MiniUri {
     private final String scheme;
     private final String authority;
     private final String path;
-    private final Map<String, String> parameter;
+    private final Map<String, Collection<String>> parameter;
 
     public MiniUri(final String uri) {
         this.raw = uri;
@@ -63,8 +64,9 @@ public class MiniUri {
         };
     }
 
-    private static Map<String, String> parseParameters(final String query, final char separator) {
-        final ImmutableMap.Builder<String, String> builder = new ImmutableMap.Builder<>();
+    private static Map<String, Collection<String>> parseParameters(
+            final String query, final char separator) {
+        final var builder = new ImmutableMultimap.Builder<String, String>();
         for (final String pair : Splitter.on(separator).split(query)) {
             final String[] parts = pair.split("=", 2);
             if (parts.length == 0) {
@@ -81,7 +83,7 @@ public class MiniUri {
                 builder.put(key, EMPTY_STRING);
             }
         }
-        return builder.build();
+        return builder.build().asMap();
     }
 
     @NonNull
@@ -123,7 +125,7 @@ public class MiniUri {
         return Uri.parse(this.raw);
     }
 
-    public Map<String, String> getParameter() {
+    public Map<String, Collection<String>> getParameter() {
         return this.parameter;
     }
 }

src/main/java/eu/siacs/conversations/AppSettings.java 🔗

@@ -12,6 +12,7 @@ import com.google.common.base.Splitter;
 import com.google.common.base.Strings;
 import eu.siacs.conversations.persistance.FileBackend;
 import eu.siacs.conversations.services.QuickConversationsService;
+import eu.siacs.conversations.utils.Compatibility;
 import java.security.SecureRandom;
 
 public class AppSettings {
@@ -145,6 +146,15 @@ public class AppSettings {
                         AppSettings.SHOW_CONNECTION_OPTIONS, R.bool.show_connection_options);
     }
 
+    public boolean isAcceptInvitesFromStrangers() {
+        return true;
+    }
+
+    public boolean isKeepForegroundService() {
+        return Compatibility.twentySix()
+                || getBooleanPreference(KEEP_FOREGROUND_SERVICE, R.bool.enable_foreground_service);
+    }
+
     private boolean getBooleanPreference(@NonNull final String name, @BoolRes int res) {
         final SharedPreferences sharedPreferences =
                 PreferenceManager.getDefaultSharedPreferences(context);

src/main/java/eu/siacs/conversations/services/NotificationService.java 🔗

@@ -1999,7 +1999,7 @@ public class NotificationService {
                 .setSmallIcon(connected >= enabled ? R.drawable.ic_link_24dp : R.drawable.ic_link_off_24dp)
                 .setLocalOnly(true);
 
-        if (Compatibility.runsTwentySix()) {
+        if (Compatibility.twentySix()) {
             mBuilder.setChannelId("foreground");
             mBuilder.addAction(
                     R.drawable.ic_logout_24dp,
@@ -2149,7 +2149,7 @@ public class NotificationService {
                         s()
                                 ? PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_UPDATE_CURRENT
                                 : PendingIntent.FLAG_UPDATE_CURRENT));
-        if (Compatibility.runsTwentySix()) {
+        if (Compatibility.twentySix()) {
             mBuilder.setChannelId("error");
         }
         notify(ERROR_NOTIFICATION_ID, mBuilder.build());
@@ -2174,7 +2174,7 @@ public class NotificationService {
             builder.setContentIntent(createContentIntent(message.getConversation()));
         }
         builder.setOngoing(true);
-        if (Compatibility.runsTwentySix()) {
+        if (Compatibility.twentySix()) {
             builder.setChannelId("compression");
         }
         return builder.build();

src/main/java/eu/siacs/conversations/services/XmppConnectionService.java 🔗

@@ -1686,11 +1686,11 @@ public class XmppConnectionService extends Service {
         emojiSearch = new EmojiSearch(this);
         setTheme(R.style.Theme_Conversations3);
         ThemeHelper.applyCustomColors(this);
-        if (Compatibility.runsTwentySix()) {
+        if (Compatibility.twentySix()) {
             mNotificationService.initializeChannels();
         }
         mChannelDiscoveryService.initializeMuclumbusService();
-        mForceDuringOnCreate.set(Compatibility.runsAndTargetsTwentySix(this));
+        mForceDuringOnCreate.set(Compatibility.twentySix());
         toggleForegroundService();
         this.destroyed = false;
         OmemoSetting.load(this);
@@ -1957,10 +1957,7 @@ public class XmppConnectionService extends Service {
                 || mForceDuringOnCreate.get()
                 || ongoingVideoTranscoding
                 || ongoing != null
-                || (Compatibility.keepForegroundService(this) && hasEnabledAccounts())) {
-            if (Compatibility.runsTwentySix()) {
-                mNotificationService.initializeChannels();
-            }
+                || (appSettings.isKeepForegroundService() && hasEnabledAccounts())) {
             final Notification notification;
             if (ongoing != null && !diallerIntegrationActive.get()) {
                 notification = this.mNotificationService.getOngoingCallNotification(ongoing);
@@ -2033,14 +2030,14 @@ public class XmppConnectionService extends Service {
     public boolean foregroundNotificationNeedsUpdatingWhenErrorStateChanges() {
         return !mOngoingVideoTranscoding.get()
                 && ongoingCall.get() == null
-                && Compatibility.keepForegroundService(this)
+                && appSettings.isKeepForegroundService()
                 && hasEnabledAccounts();
     }
 
     @Override
     public void onTaskRemoved(final Intent rootIntent) {
         super.onTaskRemoved(rootIntent);
-        if ((Compatibility.keepForegroundService(this) && hasEnabledAccounts())
+        if ((appSettings.isKeepForegroundService() && hasEnabledAccounts())
                 || mOngoingVideoTranscoding.get()
                 || ongoingCall.get() != null) {
             Log.d(Config.LOGTAG, "ignoring onTaskRemoved because foreground service is activated");

src/main/java/eu/siacs/conversations/ui/ConversationsOverviewFragment.java 🔗

@@ -464,15 +464,17 @@ public class ConversationsOverviewFragment extends XmppFragment {
         if (this.binding == null) {
             return null;
         }
-        LinearLayoutManager layoutManager =
-                (LinearLayoutManager) this.binding.list.getLayoutManager();
-        int position = layoutManager.findFirstVisibleItemPosition();
-        final View view = this.binding.list.getChildAt(0);
-        if (view != null) {
-            return new ScrollState(position, view.getTop());
-        } else {
-            return new ScrollState(position, 0);
+        if (this.binding.list.getLayoutManager()
+                instanceof LinearLayoutManager linearLayoutManager) {
+            final int position = linearLayoutManager.findFirstVisibleItemPosition();
+            final View view = this.binding.list.getChildAt(0);
+            if (view != null) {
+                return new ScrollState(position, view.getTop());
+            } else {
+                return new ScrollState(position, 0);
+            }
         }
+        return null;
     }
 
     @Override
@@ -556,7 +558,7 @@ public class ConversationsOverviewFragment extends XmppFragment {
             }
         }
         this.conversationsAdapter.notifyDataSetChanged();
-        ScrollState scrollState = pendingScrollState.pop();
+        final var scrollState = pendingScrollState.pop();
         if (scrollState != null) {
             setScrollPosition(scrollState);
         }
@@ -612,12 +614,16 @@ public class ConversationsOverviewFragment extends XmppFragment {
         }
     }
 
-    private void setScrollPosition(ScrollState scrollPosition) {
-        if (scrollPosition != null) {
-            LinearLayoutManager layoutManager =
-                    (LinearLayoutManager) binding.list.getLayoutManager();
-            layoutManager.scrollToPositionWithOffset(
+    private void setScrollPosition(@NonNull final ScrollState scrollPosition) {
+        if (binding.list.getLayoutManager() instanceof LinearLayoutManager linearLayoutManager) {
+            linearLayoutManager.scrollToPositionWithOffset(
                     scrollPosition.position, scrollPosition.offset);
+            if (scrollPosition.position > 0) {
+                binding.fab.shrink();
+            } else {
+                binding.fab.extend();
+            }
+            binding.fab.clearAnimation();
         }
     }
 }

src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java 🔗

@@ -11,8 +11,8 @@ import android.content.SharedPreferences;
 import android.graphics.Bitmap;
 import android.graphics.drawable.ColorDrawable;
 import android.net.Uri;
-import android.os.Build;
 import android.os.Bundle;
+import android.os.Build;
 import android.os.Handler;
 import android.preference.PreferenceManager;
 import android.provider.Settings;
@@ -1334,10 +1334,6 @@ public class EditAccountActivity extends OmemoActivity
         this.binding.accountPassword.setFocusableInTouchMode(editPassword);
         this.binding.accountPassword.setCursorVisible(editPassword);
         this.binding.accountPassword.setEnabled(editPassword);
-        if (!editPassword && Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
-            this.binding.accountJid.setImportantForAutofill(View.IMPORTANT_FOR_AUTOFILL_NO);
-            this.binding.accountPassword.setImportantForAutofill(View.IMPORTANT_FOR_AUTOFILL_NO);
-        }
 
         if (!mInitMode) {
             this.binding.avater.setVisibility(View.VISIBLE);

src/main/java/eu/siacs/conversations/ui/ExtendedFabSizeChanger.java 🔗

@@ -16,11 +16,15 @@ public class ExtendedFabSizeChanger extends RecyclerView.OnScrollListener {
     @Override
     public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
         super.onScrolled(recyclerView, dx, dy);
-        if (RecyclerViews.findFirstVisibleItemPosition(recyclerView) > 0) {
-            extendedFloatingActionButton.shrink();
-        } else {
-            extendedFloatingActionButton.extend();
-        }
+        final var firstVisibleItem = RecyclerViews.findFirstVisibleItemPosition(recyclerView);
+        recyclerView.post(
+                () -> {
+                    if (firstVisibleItem > 0) {
+                        extendedFloatingActionButton.shrink();
+                    } else {
+                        extendedFloatingActionButton.extend();
+                    }
+                });
     }
 
     public static RecyclerView.OnScrollListener of(final ExtendedFloatingActionButton fab) {

src/main/java/eu/siacs/conversations/ui/fragment/settings/NotificationsSettingsFragment.java 🔗

@@ -78,7 +78,7 @@ public class NotificationsSettingsFragment extends XmppPreferenceFragment {
                 || callIntegration == null) {
             throw new IllegalStateException("The preference resource file is missing preferences");
         }
-        if (Compatibility.runsTwentySix()) {
+        if (Compatibility.twentySix()) {
             notificationRingtone.setVisible(false);
             notificationHeadsUp.setVisible(false);
             notificationVibrate.setVisible(false);

src/main/java/eu/siacs/conversations/utils/Compatibility.java 🔗

@@ -6,40 +6,30 @@ import android.annotation.SuppressLint;
 import android.app.ActivityOptions;
 import android.content.Context;
 import android.content.Intent;
-import android.content.SharedPreferences;
-import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
 import android.net.ConnectivityManager;
 import android.os.Build;
 import android.os.Bundle;
-import android.preference.PreferenceManager;
 import android.util.Log;
-
-import androidx.annotation.BoolRes;
 import androidx.annotation.NonNull;
 import androidx.annotation.RequiresApi;
 import androidx.core.content.ContextCompat;
-
-import eu.siacs.conversations.AppSettings;
 import eu.siacs.conversations.Config;
-import eu.siacs.conversations.R;
 
 public class Compatibility {
 
     public static boolean hasStoragePermission(final Context context) {
-        return Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU || ContextCompat.checkSelfPermission(
-                context, android.Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED;
+        return Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU
+                || ContextCompat.checkSelfPermission(
+                                context, android.Manifest.permission.WRITE_EXTERNAL_STORAGE)
+                        == PackageManager.PERMISSION_GRANTED;
     }
 
     public static boolean s() {
         return Build.VERSION.SDK_INT >= Build.VERSION_CODES.S;
     }
 
-    private static boolean runsTwentyFour() {
-        return Build.VERSION.SDK_INT >= Build.VERSION_CODES.N;
-    }
-
-    public static boolean runsTwentySix() {
+    public static boolean twentySix() {
         return Build.VERSION.SDK_INT >= Build.VERSION_CODES.O;
     }
 
@@ -47,55 +37,9 @@ public class Compatibility {
         return Build.VERSION.SDK_INT >= Build.VERSION_CODES.P;
     }
 
-    private static boolean getBooleanPreference(Context context, String name, @BoolRes int res) {
-        return getPreferences(context).getBoolean(name, context.getResources().getBoolean(res));
-    }
-
-    private static SharedPreferences getPreferences(final Context context) {
-        return PreferenceManager.getDefaultSharedPreferences(context);
-    }
-
-    private static boolean targetsTwentySix(Context context) {
-        try {
-            final PackageManager packageManager = context.getPackageManager();
-            final ApplicationInfo applicationInfo =
-                    packageManager.getApplicationInfo(context.getPackageName(), 0);
-            return applicationInfo.targetSdkVersion >= 26;
-        } catch (PackageManager.NameNotFoundException | RuntimeException e) {
-            return true; // when in doubt…
-        }
-    }
-
-    private static boolean targetsTwentyFour(Context context) {
-        try {
-            final PackageManager packageManager = context.getPackageManager();
-            final ApplicationInfo applicationInfo =
-                    packageManager.getApplicationInfo(context.getPackageName(), 0);
-            return applicationInfo.targetSdkVersion >= 24;
-        } catch (PackageManager.NameNotFoundException | RuntimeException e) {
-            return true; // when in doubt…
-        }
-    }
-
-    public static boolean runsAndTargetsTwentySix(Context context) {
-        return runsTwentySix() && targetsTwentySix(context);
-    }
-
-    public static boolean runsAndTargetsTwentyFour(Context context) {
-        return runsTwentyFour() && targetsTwentyFour(context);
-    }
-
-    public static boolean keepForegroundService(Context context) {
-        return runsAndTargetsTwentySix(context)
-                || getBooleanPreference(
-                        context,
-                        AppSettings.KEEP_FOREGROUND_SERVICE,
-                        R.bool.enable_foreground_service);
-    }
-
     public static void startService(final Context context, final Intent intent) {
         try {
-            if (Compatibility.runsAndTargetsTwentySix(context)) {
+            if (Compatibility.twentySix()) {
                 intent.putExtra(EXTRA_NEEDS_FOREGROUND_SERVICE, true);
                 context.startForegroundService(intent);
             } else {

src/main/java/eu/siacs/conversations/worker/ImportBackupWorker.java 🔗

@@ -8,6 +8,7 @@ import android.app.PendingIntent;
 import android.content.ContentValues;
 import android.content.Context;
 import android.content.Intent;
+import android.content.pm.ServiceInfo;
 import android.database.Cursor;
 import android.database.sqlite.SQLiteDatabase;
 import android.net.Uri;
@@ -103,8 +104,7 @@ public class ImportBackupWorker extends Worker {
     @NonNull
     @Override
     public Result doWork() {
-        setForegroundAsync(
-                new ForegroundInfo(NOTIFICATION_ID, createImportBackupNotification(1, 0)));
+        setForegroundAsync(getForegroundInfo());
         final Result result;
         try {
             result = importBackup(this.uri, this.password);
@@ -127,6 +127,18 @@ public class ImportBackupWorker extends Worker {
         return result;
     }
 
+    @NonNull
+    @Override
+    public ForegroundInfo getForegroundInfo() {
+        final var notification = createImportBackupNotification(1, 0);
+        if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.Q) {
+            return new ForegroundInfo(
+                    NOTIFICATION_ID, notification, ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC);
+        } else {
+            return new ForegroundInfo(NOTIFICATION_ID, notification);
+        }
+    }
+
     private Result importBackup(final Uri uri, final String password)
             throws IOException, InvalidKeySpecException {
         final var context = getApplicationContext();

src/main/res/drawable/background_selected_item_conversation.xml 🔗

@@ -1,7 +1,16 @@
 <?xml version="1.0" encoding="utf-8"?>
-<shape xmlns:android="http://schemas.android.com/apk/res/android">
-    <solid android:color="?colorSurfaceContainerHighest" />
-    <corners
-        android:bottomRightRadius="12dp"
-        android:topRightRadius="12dp" />
-</shape>
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+    <item>
+        <shape>
+            <solid android:color="?colorSurface" />
+        </shape>
+    </item>
+    <item>
+        <shape>
+            <solid android:color="?colorSurfaceContainerHighest" />
+            <corners
+                android:bottomRightRadius="12dp"
+                android:topRightRadius="12dp" />
+        </shape>
+    </item>
+</layer-list>

src/main/res/layout/activity_about.xml 🔗

@@ -4,6 +4,7 @@
     <LinearLayout
         android:layout_width="match_parent"
         android:layout_height="match_parent"
+        android:fitsSystemWindows="true"
         android:orientation="vertical">
 
         <com.google.android.material.appbar.AppBarLayout
@@ -25,16 +26,16 @@
             <LinearLayout
                 android:layout_width="match_parent"
                 android:layout_height="wrap_content">
+
                 <TextView
                     android:id="@+id/about"
                     android:layout_width="wrap_content"
                     android:layout_height="wrap_content"
-                    android:fontFamily="monospace"
-                    android:linksClickable="true"
                     android:layout_margin="@dimen/card_padding_regular"
-                    tools:text="@string/pref_about_message"
+                    android:fontFamily="monospace"
                     android:textAppearance="?textAppearanceBodyMedium"
-                    android:typeface="monospace" />
+                    android:typeface="monospace"
+                    tools:text="@string/pref_about_message" />
             </LinearLayout>
         </ScrollView>
     </LinearLayout>

src/main/res/values-de/strings.xml 🔗

@@ -1121,4 +1121,7 @@
     <string name="uri_copied_to_clipboard">URI in die Zwischenablage kopiert</string>
     <string name="copy_URI">URI kopieren</string>
     <string name="copied_email_address">E-Mail-Adresse in die Zwischenablage kopiert</string>
+    <string name="account_status_service_outage_known">Dienst nicht verfügbar (bekanntes Problem)</string>
+    <string name="sos_scheduled_return">Geplante Wiederaufnahme des Dienstes um %s</string>
+    <string name="account_status_service_outage_scheduled">Geplante Ausfallzeit</string>
 </resources>

src/main/res/values-et/strings.xml 🔗

@@ -1141,4 +1141,7 @@
     <string name="uri_copied_to_clipboard">Kopeerisime URI lõikelauale</string>
     <string name="copy_telephone_number">Kopeeri telefoninumber</string>
     <string name="copied_phone_number">Kopeerisime telefoninumbri lõikelauale</string>
+    <string name="account_status_service_outage_scheduled">Planeeritud teenuse katkestus</string>
+    <string name="account_status_service_outage_known">Teenus ei tööta (teadaolev probleem)</string>
+    <string name="sos_scheduled_return">Teenuse plaanilise taastumise aeg on %s</string>
 </resources>

src/main/res/values-gl/strings.xml 🔗

@@ -1121,4 +1121,7 @@
     <string name="copied_email_address">Copiouse o enderezo ao portapapeis</string>
     <string name="copied_phone_number">Copiouse o número ao portapapeis</string>
     <string name="copy_telephone_number">Copiar número de teléfono</string>
+    <string name="account_status_service_outage_scheduled">Desconexión programada</string>
+    <string name="sos_scheduled_return">Está previsto reactivar o servizo ás %s</string>
+    <string name="account_status_service_outage_known">Sen servizo (incidencia coñecida)</string>
 </resources>

src/main/res/values-pl/strings.xml 🔗

@@ -1153,4 +1153,7 @@
     <string name="copy_URI">Kopiuj URI</string>
     <string name="copied_email_address">Skopiowano adres e‑mail do schowka</string>
     <string name="uri_copied_to_clipboard">Skopiowano URI do schowka</string>
+    <string name="account_status_service_outage_scheduled">Planowana niedostępność</string>
+    <string name="sos_scheduled_return">Przywrócenie usługi planowane na %s</string>
+    <string name="account_status_service_outage_known">Usługa niedostępna (znany problem)</string>
 </resources>

src/main/res/values-ro-rRO/strings.xml 🔗

@@ -1140,4 +1140,7 @@
     <string name="uri_copied_to_clipboard">Adresă copiată</string>
     <string name="copy_telephone_number">Copiere telefon</string>
     <string name="copied_email_address">E-mail copiat</string>
+    <string name="account_status_service_outage_known">Serviciu oprit (Problemă cunoscută)</string>
+    <string name="sos_scheduled_return">Serviciul este programat să revină la %s</string>
+    <string name="account_status_service_outage_scheduled">Oprire planificată</string>
 </resources>

src/main/res/values-ru/strings.xml 🔗

@@ -1168,4 +1168,7 @@
     <string name="copy_email_address">Копировать адрес почты</string>
     <string name="copy_URI">Копировать URI</string>
     <string name="copied_email_address">Адрес электронной почты скопирован в буфер обмена</string>
+    <string name="account_status_service_outage_known">Сервис не работает (известная проблема)</string>
+    <string name="account_status_service_outage_scheduled">Запланированный простой</string>
+    <string name="sos_scheduled_return">Сервис должен заработать %s</string>
 </resources>

src/main/res/values-sq-rAL/strings.xml 🔗

@@ -1131,4 +1131,7 @@
     <string name="uri_copied_to_clipboard">URI u kopjua në të papastër</string>
     <string name="copied_email_address">Adresa email u kopjua në të papastër</string>
     <string name="copy_URI">Kopjo URI-n</string>
+    <string name="sos_scheduled_return">Shërbimi është vënë në plan të rikthehet në funksionim më %s</string>
+    <string name="account_status_service_outage_known">Shërbim Jashtë Funksionimi (Problem i Ditur)</string>
+    <string name="account_status_service_outage_scheduled">Kohë Mosfunksionimi e Planifikuar</string>
 </resources>

src/main/res/values-tr-rTR/strings.xml 🔗

@@ -84,12 +84,12 @@
     <string name="delete_file_dialog">Dosyayı sil</string>
     <string name="delete_file_dialog_msg">Bu dosyayı silmek istediğinizden emin misiniz? \n\n<b>Uyarı:</b> Bu eylem, bu dosyanın diğer aygıt ve sunucularda kayıtlı kopyalarını silmeyecektir.</string>
     <string name="choose_presence">Aygıt seç</string>
-    <string name="send_unencrypted_message">Şifrelenmemiş ileti gönder</string>
+    <string name="send_unencrypted_message">Açık metin olarak ileti gönder</string>
     <string name="send_message">Mesajı gönder</string>
     <string name="send_message_to_x">%s kişisine ileti gönder</string>
     <string name="send_omemo_x509_message">v\\OMEMO ile şifrelenmiş ileti gönder</string>
     <string name="your_nick_has_been_changed">Yeni rumuz kullanımda</string>
-    <string name="send_unencrypted">Şifrelenmemiş gönder</string>
+    <string name="send_unencrypted">Açık metin olarak gönder</string>
     <string name="decryption_failed">Deşifre edilemedi. Uygun bir özel anahtarınız olmayabilir.</string>
     <string name="openkeychain_required">OpenKeychain</string>
     <string name="openkeychain_required_long">%1$s mesajları şifrelemek, deşifre etmek ve genel anahtarlarınızı yönetmek için &lt;b&gt;OpenKeychain&lt;/b&gt; kullanmaktadır.&lt;br&gt;&lt;br&gt;lt GPLv3+ altında lisanslıdır ve F-Droid ve Google Play\'de mevcuttur.&lt;br&gt;&lt;br&gt;&lt;small&gt;(Lütfen devamında %1$s\'ı yeniden başlatın.)&lt;/small&gt;</string>
@@ -322,7 +322,7 @@
     <string name="pref_keep_foreground_service">Ön planda çalışması</string>
     <string name="pref_keep_foreground_service_summary">İşletim sisteminin bağlantınızı koparmasına engel olur</string>
     <string name="pref_create_backup">Yedek oluştur</string>
-    <string name="pref_create_backup_summary">Yedekleme dosyaları %s\'da depolanacak</string>
+    <string name="pref_create_backup_summary">Yedekler %s\'da depolanacak</string>
     <string name="notification_create_backup_title">Yedekleme dosyaları oluşturuluyor</string>
     <string name="notification_backup_created_title">Yedeklemeniz oluşturuldu</string>
     <string name="notification_backup_created_subtitle">Yedekleme dosyaları %s\'da depolandı</string>
@@ -461,7 +461,7 @@
     <string name="download_failed_invalid_file">İndirme başarısız: Geçersiz dosya</string>
     <string name="account_status_tor_unavailable">Tor ağına erişilemiyor</string>
     <string name="account_status_bind_failure">Bağlantı başarısız</string>
-    <string name="account_status_host_unknown">Sunucu bu alan adı için sorumlu değil</string>
+    <string name="account_status_host_unknown">Bu alan için sorumlu değil</string>
     <string name="server_info_broken">Bozuk</string>
     <string name="pref_presence_settings">Mevcudiyet</string>
     <string name="pref_away_when_screen_off">Telefon kilitliyken uzakta</string>
@@ -822,12 +822,12 @@
     <string name="ebook">e-kitap</string>
     <string name="video_original">Orijinal (sıkıştırılmamış)</string>
     <string name="open_with">Şununla aç…</string>
-    <string name="set_profile_picture">Conversations profil resmi</string>
+    <string name="set_profile_picture">Profil resmi</string>
     <string name="choose_account">Hesap seç</string>
     <string name="restore_backup">Yedekleri yükle</string>
     <string name="restore">Geri getir</string>
     <string name="enter_password_to_restore"> Yedekleri geri getirmek için %s hesabının şifrenizi girin.</string>
-    <string name="restore_warning">Herhangi bir yüklemeyi klonlama (aynı anda çalışan) girişiminde yedekleri geri yükleme özelliğini kullanmayın. Yedeklerin geri yüklenmesi sadece hesap aktarımları veya asıl cihazı kaybetmeniz durumu için kullanılmalıdır.</string>
+    <string name="restore_warning">Herhangi bir yüklemeyi klonlama (aynı anda çalışan) girişiminde OMEMO anahtarlarını geri yüklemeyin. OMEMO anahtarlarının geri yüklenmesi sadece hesap aktarımları veya asıl cihazı kaybetmeniz durumu için kullanılmalıdır.</string>
     <string name="unable_to_restore_backup">Yedekler geri yüklenemedi.</string>
     <string name="unable_to_decrypt_backup">Yedekleme çözülemedi. Şifre doğru mu?</string>
     <string name="backup_channel_name">Yedekleme &amp; Geri yükle</string>
@@ -981,7 +981,7 @@
     <string name="pref_send_crash_reports">Çökme raporları gönder</string>
     <string name="welcome_header">Sohbete Katıl</string>
     <string name="welcome_header_quicksy">Quicksy\'e hoşgeldiniz!</string>
-    <string name="restore_warning_continued">Kendiniz oluşturmadığınız yedekleri geri yüklemeye çalışmayın!</string>
+    <string name="restore_warning_continued">Sadece kendinizin yarattığı yedekleri geri yükleyin.</string>
     <string name="log_out">Oturumu kapat</string>
     <string name="hide_notification">Bildirimi gizle</string>
     <string name="report_spam">Spam bildir</string>
@@ -1006,7 +1006,7 @@
     <string name="contact_uses_unverified_keys">Kişiniz doğrulanmamış bir cihaz kullanıyor. Aktif MITM (Ortadaki Adam) saldırılarını önlemek için kişinizin QR kodunu okutarak doğrulama yapın.</string>
     <string name="call_integration_not_available">Arama entegrasyonu mevcut değil!</string>
     <string name="action_archive_chat">Sohbeti arşivle</string>
-    <string name="archive_this_chat">Bu sohbeti arşivle</string>
+    <string name="archive_this_chat">Ardından sohbeti sil</string>
     <string name="switch_to_chat">Sohbete git</string>
     <string name="pref_up_push_account_summary">Bildirimlerin alınacağı hesap.</string>
     <string name="unified_push_distributor">UnifiedPush dağıtıcısı</string>
@@ -1023,7 +1023,7 @@
     <string name="pref_use_colorful_bubbles">Renkli konuşma balonları</string>
     <string name="pref_use_colorful_bubbles_summary">Gönderilen ve alınan mesajlar için farklı renkler kullan</string>
     <string name="no_permission_to_place_call">Arama yapma izni yok</string>
-    <string name="corresponding_chats_closed">Uygun gelen sohbetler arşivlendi</string>
+    <string name="corresponding_chats_closed">İlgili sohbetler arşivlendi.</string>
     <string name="pref_dynamic_colors">Dinamik renklendirme</string>
     <string name="pref_dynamic_colors_summary">Sistem renkleri (Material You)</string>
     <string name="rtp_state_contact_offline">Kişi müsait değil</string>
@@ -1066,4 +1066,27 @@
     <string name="reconnect_on_other_host">Diğer sunucuda yeniden bağlan</string>
     <string name="allow_private_messages">Özel mesajlara izin ver</string>
     <string name="edit_configuration">Yapılandırmayı değiştir</string>
+    <string name="account_status_connection_timeout">Bağlantı zaman aşımı</string>
+    <string name="restore_omemo_key">OMEMO anahtarlarını geri yükle</string>
+    <string name="non_quicksy_backup">Quicksy sadece quicksy.im hesaplarındaki yedekleri geri yükleyebilir</string>
+    <string name="word_document">Word dokümanı</string>
+    <string name="uri">URI</string>
+    <string name="copy_geo_uri">Coğrafi konumu kopyala</string>
+    <string name="copy_email_address">E-posta adresini kopyala</string>
+    <string name="copied_email_address">E-posta adresi geçici taşıma panosuna kopyalandı</string>
+    <string name="pref_create_backup_one_off_summary">Bir defaya mahsus yedekle</string>
+    <string name="pref_backup_recurring">Yinelenen yedekleme</string>
+    <string name="pref_fullscreen_notification">Tam ekran bildirim</string>
+    <string name="retry_with_p2p">P2P ile tekrar dene</string>
+    <string name="rtp_state_content_add">Ek iz eklensin mi?</string>
+    <string name="uri_copied_to_clipboard">URI geçici taşıma panosuna kopyalandı</string>
+    <string name="copy_URI">URI kopyala</string>
+    <string name="copy_telephone_number">Telefon numarasını kopyala</string>
+    <string name="copied_phone_number">Telefon numarası geçici taşıma panosuna kopyalandı</string>
+    <string name="could_not_disable_video">Video etkisizleştirilemedi.</string>
+    <string name="pref_backup_summary">Bir defaya mahsus yarat, Yinelemeyi zamanla</string>
+    <string name="account_status_channel_binding">Kanal bağlantısı mevcut değil</string>
+    <string name="your_avatar_tap_to_select_new_avatar">Profil resminiz. Galeriden yeni profil resmi seçmek için dokunun.</string>
+    <string name="server_info_bind2">XEP-0386: Bağlantı 2</string>
+    <string name="server_info_sasl2">XEP-0388: Genişletilebilir SASL Profili</string>
 </resources>

src/main/res/values-uk/strings.xml 🔗

@@ -1169,4 +1169,7 @@
     <string name="copied_phone_number">Номер телефону скопійовано</string>
     <string name="copy_email_address">Копіювати адресу Email</string>
     <string name="uri_copied_to_clipboard">URI скопійовано</string>
+    <string name="account_status_service_outage_scheduled">Запланований простій</string>
+    <string name="account_status_service_outage_known">Сервіс не працює (відома проблема)</string>
+    <string name="sos_scheduled_return">Відновлення роботи заплановано на %s</string>
 </resources>

src/main/res/values-zh-rCN/strings.xml 🔗

@@ -196,7 +196,7 @@
     <string name="server_info_carbon_messages">XEP-0280:消息抄送</string>
     <string name="server_info_csi">XEP-0352:客户端状态指示</string>
     <string name="server_info_blocking">XEP-0191:屏蔽命令</string>
-    <string name="server_info_roster_version">XEP-0237:花名册版本控制</string>
+    <string name="server_info_roster_version">XEP-0237:名册版本控制</string>
     <string name="server_info_stream_management">XEP-0198:流管理</string>
     <string name="server_info_external_service_discovery">XEP-0215:外部服务发现</string>
     <string name="server_info_pep">XEP-0163:个人事件协议(头像/OMEMO)</string>
@@ -482,7 +482,7 @@
     <string name="pref_show_connection_options">主机名和端口</string>
     <string name="pref_show_connection_options_summary">设置账号时显示扩展连接设置</string>
     <string name="hostname_example">xmpp.example.com</string>
-    <string name="action_add_account_with_certificate">用证书登录</string>
+    <string name="action_add_account_with_certificate">使用证书登录</string>
     <string name="unable_to_parse_certificate">无法解析证书</string>
     <string name="mam_prefs">归档首选项</string>
     <string name="server_side_mam_prefs">服务器端归档首选项</string>
@@ -599,10 +599,10 @@
     <string name="data_saver_enabled_explained">您的操作系统正在限制 %1$s 在后台时访问互联网。要接收新消息通知,应当在“流量节省程序”开启时允许 %1$s 无限制访问。\n%1$s 仍会在可能的情况下尽可能节省数据。</string>
     <string name="device_does_not_support_data_saver">设备不支持为 %1$s 禁用流量节省程序。</string>
     <string name="error_unable_to_create_temporary_file">无法创建临时文件</string>
-    <string name="this_device_has_been_verified">此设备已经过验证</string>
+    <string name="this_device_has_been_verified">此设备已通过验证</string>
     <string name="copy_fingerprint">复制指纹</string>
     <string name="all_omemo_keys_have_been_verified">您已验证您拥有的所有 OMEMO 密钥</string>
-    <string name="verified_fingerprints">已验证的指纹</string>
+    <string name="verified_fingerprints">已验证指纹</string>
     <string name="use_camera_icon_to_scan_barcode">使用相机扫描对方二维码</string>
     <string name="please_wait_for_keys_to_be_fetched">正在获取密钥,请稍候</string>
     <string name="share_as_barcode">以二维码形式分享</string>
@@ -667,7 +667,7 @@
     <string name="today">今天</string>
     <string name="yesterday">昨天</string>
     <string name="pref_validate_hostname">用 DNSSEC 验证主机名</string>
-    <string name="pref_validate_hostname_summary">将包含已验证主机名的服务器证书视为已验证</string>
+    <string name="pref_validate_hostname_summary">包含已验证主机名的服务器证书视为已通过验证</string>
     <string name="certificate_does_not_contain_jid">证书不包含 XMPP 地址</string>
     <string name="server_info_partial">部分</string>
     <string name="attach_record_video">录制视频</string>
@@ -678,7 +678,7 @@
     <string name="mtm_accept_cert">接受未知证书?</string>
     <string name="mtm_trust_anchor">此服务器证书不是由已知的证书颁发机构签发的。</string>
     <string name="mtm_accept_servername">接受不匹配的服务器名称?</string>
-    <string name="mtm_hostname_mismatch">由于“%s”,服务器无法验证。证书仅对此有效:</string>
+    <string name="mtm_hostname_mismatch">服务器无法以“%s”进行身份验证。证书仅对此有效:</string>
     <string name="mtm_connect_anyway">是否仍要连接?</string>
     <string name="mtm_cert_details">证书详情:</string>
     <string name="once">仅一次</string>
@@ -735,7 +735,7 @@
     <string name="group_chat_name">名称</string>
     <string name="providing_a_name_is_optional">提供名称是可选的</string>
     <string name="create_dialog_group_chat_name">群聊名</string>
-    <string name="conference_destroyed">此群聊已经解散了</string>
+    <string name="conference_destroyed">此群聊已解散</string>
     <string name="unable_to_save_recording">无法保存录制</string>
     <string name="foreground_service_channel_name">前台服务</string>
     <string name="foreground_service_channel_description">此通知类别用于显示永久通知,表明 %1$s 正在运行。</string>
@@ -764,7 +764,7 @@
     <string name="video_360p">360p(中)</string>
     <string name="video_720p">720p(高)</string>
     <string name="cancelled">已取消</string>
-    <string name="already_drafting_message">您已经在起草一条消息了。</string>
+    <string name="already_drafting_message">您已经在起草消息了。</string>
     <string name="feature_not_implemented">功能未实现</string>
     <string name="invalid_country_code">国家/地区代码无效</string>
     <string name="choose_a_country">选择国家/地区</string>
@@ -1116,4 +1116,7 @@
     <string name="uri">URI</string>
     <string name="copy_URI">复制 URI</string>
     <string name="copy_telephone_number">复制电话号码</string>
+    <string name="account_status_service_outage_scheduled">计划停机时间</string>
+    <string name="account_status_service_outage_known">服务中断(已知问题)</string>
+    <string name="sos_scheduled_return">‌服务预计将于 %s 恢复</string>
 </resources>

src/main/res/values/about.xml 🔗

@@ -44,22 +44,31 @@
 			along with this program. If not, see https://www.gnu.org/licenses
 			\n\nDownload the full source code at https://git.singpolyma.net/cheogram-android
 			\n\nPrivacy policy: https://cheogram.com/android-privacy.html
-			\n\n\nLibraries
-			\n\nhttps://webrtc.org\nCopyright (c) 2011, The WebRTC project authors. All rights reserved. (https://webrtc.org/support/license)
-			\n\nhttps://github.com/ypresto/android-transcoder\n(Apache License, Version 2.0)
-			\n\nhttps://www.bouncycastle.org\n(The MIT License (MIT))
-			\n\nhttps://www.gnu.org/software/libidn\n(Apache License, Version 2.0)
-			\n\nhttps://github.com/ge0rg/MemorizingTrustManager\n(The MIT License (MIT))
-			\n\nhttps://github.com/rtreffer/minidns\n(WTFPL)
-			\n\nhttps://github.com/open-keychain/openkeychain-api-lib\n(Apache License, Version 2.0)
-			\n\nhttps://developer.android.com/tools/support-library\n(Apache License, Version 2.0)
-			\n\nhttps://github.com/google/material-design-icons\n(CC BY 4.0)
-			\n\nhttps://github.com/leolin310148/ShortcutBadger\n(Apache License, Version 2.0)
-			\n\nhttps://github.com/WhisperSystems/libaxolotl-java\n(GPLv3)
-			\n\nhttps://github.com/jdamcd/android-crop\n(Apache License, Version 2.0)
-			\n\nhttps://github.com/zxing/zxing\n(Apache License, Version 2.0)
-			\n\nhttps://github.com/osmdroid/osmdroid\n(Apache License, Version 2.0)
-			\n\n\nMaps
+			\n\n\nThird-party libraries:
+			\n\nAndroid Jetpack\nhttps://android.googlesource.com/platform/frameworks/support\n(Apache License, Version 2.0)
+			\n\nOpenKeychain Intents\nhttps://github.com/open-keychain/openkeychain-api-lib\n(Apache License, Version 2.0)
+			\n\nMaterial Components for Android\nhttps://github.com/material-components/material-components-android\n(Apache License, Version 2.0)
+			\n\nGuava: Google Core Libraries for Java\nhttps://github.com/google/guava\n(Apache License, Version 2.0)
+			\n\nZXing barcode scanning library for Android\nhttps://github.com/zxing/zxing\n(Apache License, Version 2.0)
+			\n\nFloating Action Button Speed Dial\nhttps://github.com/leinardi/FloatingActionButtonSpeedDial\n(Apache License, Version 2.0)
+			\n\nRoundedImageView\nhttps://github.com/vinc3m1/RoundedImageView\n(Apache License, Version 2.0)
+			\n\nOkHttp\nhttps://github.com/square/okhttp\n(Apache License, Version 2.0)
+			\n\nRetrofit\nhttps://github.com/square/retrofit\n(Apache License, Version 2.0)
+			\n\nAndroid Image Cropper\nhttps://github.com/CanHub/Android-Image-Cropper\n(Apache License, Version 2.0)
+			\n\nWebRTC\nhttps://webrtc.googlesource.com/src\n(Copyright (c) 2011, The WebRTC project authors)
+			\n\nTranscoder\nhttps://github.com/deepmedia/Transcoder\n(Apache License, Version 2.0)
+			\n\nToastCompat\nhttps://github.com/PureWriter/ToastCompat\n(Apache License, Version 2.0)
+			\n\nShortcutBadger\nhttps://github.com/leolin310148/ShortcutBadger\n(Copyright 2014 Leo Lin, Apache License, Version 2.0)
+			\n\nThe Bouncy Castle Crypto Package For Java\nhttps://www.bouncycastle.org/\n(Copyright (c) 2000-2024 The Legion of the Bouncy Castle Inc, MIT License)
+			\n\nConscrypt - A Java Security Provider\nhttps://github.com/google/conscrypt\n(Apache License, Version 2.0)
+			\n\nHSLuv\nhttps://github.com/hsluv/hsluv-java\n(Copyright (c) 2016 Alexei Boronine, MIT License)
+			\n\nJXMPP\nhttps://github.com/igniterealtime/jxmpp\n(Apache License, Version 2.0)
+			\n\nMiniDNS\nhttps://github.com/MiniDNS/minidns\n(Apache License, Version 2.0)
+			\n\nOpenStreetMap-Tools for Android\nhttps://github.com/osmdroid/osmdroid\n(Apache License, Version 2.0)
+			\n\nlibsignal-protocol\nhttps://github.com/signalapp/libsignal-protocol-java\n(GNU General Public License v3.0)
+			\n\nAndroid port of Googles’s libphonenumber\nhttps://github.com/MichaelRocks/libphonenumber-android\n(Apache License, Version 2.0)
+
+			\n\n\nMaps:
 			\n\nMaps by Open Street Map (https://www.openstreetmap.org). Copyright restrictions may apply.
 	</string>
 </resources>