Threema 3 vuotta sitten
vanhempi
sitoutus
e360a693e3
100 muutettua tiedostoa jossa 6575 lisäystä ja 5756 poistoa
  1. 3 3
      README.md
  2. BIN
      app/assets/emojis/activity-0.png
  3. BIN
      app/assets/emojis/activity-1.png
  4. BIN
      app/assets/emojis/food-0.png
  5. BIN
      app/assets/emojis/nature-0.png
  6. BIN
      app/assets/emojis/objects-0.png
  7. BIN
      app/assets/emojis/people-0.png
  8. BIN
      app/assets/emojis/people-1.png
  9. BIN
      app/assets/emojis/people-2.png
  10. BIN
      app/assets/emojis/people-3.png
  11. BIN
      app/assets/emojis/people-4.png
  12. BIN
      app/assets/emojis/people-5.png
  13. BIN
      app/assets/emojis/people-6.png
  14. BIN
      app/assets/emojis/people-7.png
  15. BIN
      app/assets/emojis/people-8.png
  16. BIN
      app/assets/emojis/people-9.png
  17. BIN
      app/assets/emojis/symbols-0.png
  18. BIN
      app/assets/emojis/travel-0.png
  19. 15 29
      app/build.gradle
  20. 58 0
      app/src/androidTest/java/ch/threema/app/utils/GeoLocationUtilTest.kt
  21. 19 0
      app/src/androidTest/java/ch/threema/app/utils/LinkifyUtilTest.kt
  22. 0 0
      app/src/libre/AndroidManifest.xml
  23. 0 0
      app/src/libre/java/ch/threema/app/activities/DownloadApkActivity.java
  24. 0 0
      app/src/libre/java/ch/threema/app/utils/DownloadUtil.java
  25. 9 1
      app/src/main/AndroidManifest.xml
  26. 6 6
      app/src/main/java/ch/threema/app/BuildFlavor.java
  27. 1 1
      app/src/main/java/ch/threema/app/ThreemaApplication.java
  28. 7 6
      app/src/main/java/ch/threema/app/activities/ComposeMessageActivity.java
  29. 141 29
      app/src/main/java/ch/threema/app/activities/DirectoryActivity.java
  30. 1 1
      app/src/main/java/ch/threema/app/activities/DisableBatteryOptimizationsActivity.java
  31. 0 1
      app/src/main/java/ch/threema/app/activities/HomeActivity.java
  32. 53 5
      app/src/main/java/ch/threema/app/activities/MapActivity.java
  33. 16 9
      app/src/main/java/ch/threema/app/activities/RecipientListBaseActivity.java
  34. 77 52
      app/src/main/java/ch/threema/app/activities/SendMediaActivity.java
  35. 3 0
      app/src/main/java/ch/threema/app/activities/wizard/WizardBackupRestoreActivity.java
  36. 7 6
      app/src/main/java/ch/threema/app/adapters/ComposeMessageAdapter.java
  37. 5 1
      app/src/main/java/ch/threema/app/adapters/SendMediaAdapter.kt
  38. 5 0
      app/src/main/java/ch/threema/app/archive/ArchiveActivity.java
  39. 2 2
      app/src/main/java/ch/threema/app/camera/CameraFragment.kt
  40. 18 4
      app/src/main/java/ch/threema/app/camera/VideoEditView.java
  41. 8 5
      app/src/main/java/ch/threema/app/dialogs/MessageDetailDialog.java
  42. 2 1
      app/src/main/java/ch/threema/app/dialogs/PasswordEntryDialog.java
  43. 16 16
      app/src/main/java/ch/threema/app/dialogs/SelectorDialog.java
  44. 4863 4873
      app/src/main/java/ch/threema/app/emojis/EmojiParser.java
  45. 347 113
      app/src/main/java/ch/threema/app/emojis/EmojiSpritemap.java
  46. 34 18
      app/src/main/java/ch/threema/app/fragments/ContactsSectionFragment.java
  47. 5 4
      app/src/main/java/ch/threema/app/fragments/mediaviews/VideoViewFragment.java
  48. 5 1
      app/src/main/java/ch/threema/app/jobs/ReConnectJobService.java
  49. 27 31
      app/src/main/java/ch/threema/app/mediaattacher/MediaSelectionBaseActivity.java
  50. 2 2
      app/src/main/java/ch/threema/app/processors/MessageProcessor.java
  51. 20 3
      app/src/main/java/ch/threema/app/services/MessageServiceImpl.java
  52. 18 17
      app/src/main/java/ch/threema/app/services/NotificationActionService.java
  53. 5 1
      app/src/main/java/ch/threema/app/services/NotificationServiceImpl.java
  54. 13 9
      app/src/main/java/ch/threema/app/ui/AvatarEditView.java
  55. 20 2
      app/src/main/java/ch/threema/app/ui/DirectoryDataSource.java
  56. 14 1
      app/src/main/java/ch/threema/app/ui/MediaItem.java
  57. 4 1
      app/src/main/java/ch/threema/app/utils/ConfigUtils.java
  58. 2 2
      app/src/main/java/ch/threema/app/utils/ConnectionIndicatorUtil.java
  59. 7 0
      app/src/main/java/ch/threema/app/utils/FileUtil.java
  60. 59 11
      app/src/main/java/ch/threema/app/utils/GeoLocationUtil.java
  61. 3 3
      app/src/main/java/ch/threema/app/utils/MediaPlayerStateWrapper.java
  62. 2 2
      app/src/main/java/ch/threema/app/utils/MimeUtil.java
  63. 1 1
      app/src/main/java/ch/threema/app/video/transcoder/VideoConfig.java
  64. 15 13
      app/src/main/java/ch/threema/app/voip/activities/CallActivity.java
  65. 4 0
      app/src/main/java/ch/threema/app/voip/services/VoipStateService.java
  66. 1 1
      app/src/main/java/ch/threema/app/webclient/services/instance/state/SessionConnectionContext.java
  67. 4 5
      app/src/main/java/ch/threema/app/webclient/services/instance/state/SessionStateConnecting.java
  68. 107 67
      app/src/main/res/layout/activity_directory.xml
  69. 15 0
      app/src/main/res/layout/activity_map.xml
  70. 13 4
      app/src/main/res/layout/dialog_password_entry.xml
  71. 22 0
      app/src/main/res/menu/activity_directory.xml
  72. 8 1
      app/src/main/res/values-cs/strings.xml
  73. 3 0
      app/src/main/res/values-de/strings.xml
  74. 5 0
      app/src/main/res/values-es/strings.xml
  75. 5 0
      app/src/main/res/values-fr/strings.xml
  76. 5 0
      app/src/main/res/values-it/strings.xml
  77. 5 0
      app/src/main/res/values-nl-rNL/strings.xml
  78. 5 0
      app/src/main/res/values-no/strings.xml
  79. 12 7
      app/src/main/res/values-pl/strings.xml
  80. 5 0
      app/src/main/res/values-pt-rBR/strings.xml
  81. 5 0
      app/src/main/res/values-ru/strings.xml
  82. 6 1
      app/src/main/res/values-sk/strings.xml
  83. 6 6
      app/src/main/res/values-tr/poi_strings.xml
  84. 316 335
      app/src/main/res/values-tr/strings.xml
  85. 3 3
      app/src/main/res/values-tr/voicemessage_strings.xml
  86. 29 29
      app/src/main/res/values-tr/voip_strings.xml
  87. 4 4
      app/src/main/res/values-tr/webclient_strings.xml
  88. 6 1
      app/src/main/res/values-uk/strings.xml
  89. 7 2
      app/src/main/res/values-zh-rCN/strings.xml
  90. 7 2
      app/src/main/res/values-zh-rTW/strings.xml
  91. 3 0
      app/src/main/res/values/strings.xml
  92. 1 1
      app/src/main/res/values/untranslatable_strings.xml
  93. 4 1
      app/src/onprem/AndroidManifest.xml
  94. 3 0
      app/src/onprem/res/values-cs/strings.xml
  95. 3 0
      app/src/onprem/res/values-de/strings.xml
  96. 3 0
      app/src/onprem/res/values-es/strings.xml
  97. 3 0
      app/src/onprem/res/values-fr/strings.xml
  98. 3 0
      app/src/onprem/res/values-it/strings.xml
  99. 3 0
      app/src/onprem/res/values-nl-rNL/strings.xml
  100. 3 0
      app/src/onprem/res/values-pl/strings.xml

+ 3 - 3
README.md

@@ -116,14 +116,14 @@ version of Threema:
 | `store_google`         | Google Play Store version (regular, paid app)  | Google Play    |
 | `hms`                  | Huawei AppGallery version (regular, paid app)  | Huawei HMS     |
 | `store_threema`¹       | Threema Shop version (with play services)      | Threema Shop   |
-| `fdroid`¹              | F-Droid version (no proprietary code)          | Threema Shop   |
+| `libre`¹               | Libre (F-Droid) version (no proprietary code)  | Threema Shop   |
 
 For local testing, we recommend building the `store_google` or `store_threema`
 build variants.
 
-¹ The main difference between `store_threema` and `fdroid` is that the former
+¹ The main difference between `store_threema` and `libre` is that the former
   contains proprietary push services and a self-updater while the latter does
-  not. Additionally, the `fdroid` version will use your system emoji, instead
+  not. Additionally, the `libre` version will use your system emoji, instead
   of bundling emoji graphics.
 
 

BIN
app/assets/emojis/activity-0.png


BIN
app/assets/emojis/activity-1.png


BIN
app/assets/emojis/food-0.png


BIN
app/assets/emojis/nature-0.png


BIN
app/assets/emojis/objects-0.png


BIN
app/assets/emojis/people-0.png


BIN
app/assets/emojis/people-1.png


BIN
app/assets/emojis/people-2.png


BIN
app/assets/emojis/people-3.png


BIN
app/assets/emojis/people-4.png


BIN
app/assets/emojis/people-5.png


BIN
app/assets/emojis/people-6.png


BIN
app/assets/emojis/people-7.png


BIN
app/assets/emojis/people-8.png


BIN
app/assets/emojis/people-9.png


BIN
app/assets/emojis/symbols-0.png


BIN
app/assets/emojis/travel-0.png


+ 15 - 29
app/build.gradle

@@ -13,7 +13,7 @@ if (getGradle().getStartParameter().getTaskRequests().toString().contains("Hms")
 }
 
 // version codes
-def app_version = "4.82"
+def app_version = "4.83"
 def beta_suffix = "" // with leading dash
 
 /**
@@ -75,7 +75,6 @@ def keystores = [
     hms_release: findKeystore("threema_hms"),
     onprem_release: findKeystore("onprem"),
     red_release: findKeystore("red"),
-    fdroid_release: findKeystore("threema_fdroid"),
 ]
 
 android {
@@ -93,7 +92,7 @@ android {
         vectorDrawables.useSupportLibrary = true
         applicationId "ch.threema.app"
         testApplicationId 'ch.threema.app.test'
-        versionCode 751
+        versionCode 755
         versionName "${app_version}${beta_suffix}"
         resValue "string", "app_name", "Threema"
         // package name used for sync adapter - needs to match mime types below
@@ -348,10 +347,10 @@ android {
                 actionUrl: "work.threema.ch",
             ]
         }
-        fdroid {
-            versionName "${app_version}f${beta_suffix}"
-            applicationId "ch.threema.app.fdroid"
-            testApplicationId 'ch.threema.app.fdroid.test'
+        libre {
+            versionName "${app_version}l${beta_suffix}"
+            applicationId "ch.threema.app.libre"
+            testApplicationId 'ch.threema.app.libre.test'
             resValue "string", "app_name", "Threema Libre"
             buildConfigField "String", "MEDIA_PATH", "\"ThreemaLibre\""
         }
@@ -415,17 +414,7 @@ android {
             logger.warn("No red keystore found. Falling back to locally generated keystore.")
         }
 
-        // F-Droid release config
-        if (keystores.fdroid_release != null) {
-            fdroid_release {
-                storeFile file(keystores.fdroid_release.storeFile)
-                storePassword keystores.fdroid_release.storePassword
-                keyAlias keystores.fdroid_release.keyAlias
-                keyPassword keystores.fdroid_release.keyPassword
-            }
-        } else {
-            logger.warn("No fdroid keystore found. Falling back to locally generated keystore.")
-        }
+        // Note: Libre release is signed with HSM, no config here
     }
 
     sourceSets {
@@ -447,7 +436,7 @@ android {
         store_threema {
             java.srcDir 'src/google_services_based/java'
         }
-        fdroid {
+        libre {
             assets.srcDirs = ['src/foss_based/assets']
             java.srcDir 'src/foss_based/java'
         }
@@ -478,7 +467,7 @@ android {
         }
 
         // FOSS, no proprietary services
-        fdroid {
+        libre {
             assets.srcDirs = ['src/foss_based/assets']
             java.srcDir 'src/foss_based/java'
         }
@@ -528,9 +517,7 @@ android {
                 productFlavors.red.signingConfig signingConfigs.red_release
             }
 
-            if (keystores['fdroid_release'] != null) {
-                productFlavors.fdroid.signingConfig signingConfigs.fdroid_release
-            }
+            // Note: Libre release is signed with HSM, no config here
         }
     }
 
@@ -669,7 +656,7 @@ dependencies {
 
     implementation 'com.davemorrissey.labs:subsampling-scale-image-view-androidx:3.10.0'
     implementation 'net.sf.opencsv:opencsv:2.3'
-    implementation 'net.lingala.zip4j:zip4j:2.9.1'
+    implementation 'net.lingala.zip4j:zip4j:2.11.1'
     implementation 'com.getkeepsafe.taptargetview:taptargetview:1.13.3'
     implementation 'org.maplibre.gl:android-sdk:9.5.2'
     // commons-io >2.6 requires android 8
@@ -694,9 +681,9 @@ dependencies {
     implementation 'androidx.activity:activity-ktx:1.4.0'
     implementation 'androidx.sqlite:sqlite:2.1.0'
     implementation "androidx.concurrent:concurrent-futures:1.1.0"
-    implementation "androidx.camera:camera-camera2:1.1.0-rc02"
-    implementation "androidx.camera:camera-lifecycle:1.1.0-rc02"
-    implementation "androidx.camera:camera-view:1.1.0-rc02"
+    implementation "androidx.camera:camera-camera2:1.1.0"
+    implementation "androidx.camera:camera-lifecycle:1.1.0"
+    implementation "androidx.camera:camera-view:1.1.0"
     implementation 'androidx.multidex:multidex:2.0.1'
     implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.4.1"
     implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.4.1"
@@ -808,7 +795,7 @@ dependencies {
         store_googleImplementation(dependency) { excludes.each { exclude it } }
         store_google_workImplementation(dependency) { excludes.each { exclude it } }
         store_threemaImplementation(dependency) { excludes.each { exclude it } }
-        fdroidImplementation(dependency) { excludes.each { exclude it } }
+        libreImplementation(dependency) { excludes.each { exclude it } }
         onpremImplementation(dependency) { excludes.each { exclude it } }
         sandboxImplementation(dependency) { excludes.each { exclude it } }
         sandbox_workImplementation(dependency) { excludes.each { exclude it } }
@@ -821,7 +808,6 @@ dependencies {
     store_google_workImplementation(name: 'libgsaverification-client', ext: 'aar')
     onpremImplementation(name: 'libgsaverification-client', ext: 'aar')
     store_threemaImplementation(name: 'libgsaverification-client', ext: 'aar')
-    fdroidImplementation(name: 'libgsaverification-client', ext: 'aar')
     sandboxImplementation(name: 'libgsaverification-client', ext: 'aar')
     sandbox_workImplementation(name: 'libgsaverification-client', ext: 'aar')
     redImplementation(name: 'libgsaverification-client', ext: 'aar')

+ 58 - 0
app/src/androidTest/java/ch/threema/app/utils/GeoLocationUtilTest.kt

@@ -0,0 +1,58 @@
+/*  _____ _
+ * |_   _| |_  _ _ ___ ___ _ __  __ _
+ *   | | | ' \| '_/ -_) -_) '  \/ _` |_
+ *   |_| |_||_|_| \___\___|_|_|_\__,_(_)
+ *
+ * Threema for Android
+ * Copyright (c) 2022 Threema GmbH
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package ch.threema.app.utils
+
+import android.net.Uri
+import ch.threema.storage.models.data.LocationDataModel
+import org.junit.Assert.*
+import org.junit.Test
+
+class GeoLocationUtilTest {
+
+    private fun expectLocationData(expected: LocationDataModel?, uriStr: String) {
+        val uri = Uri.parse(uriStr)
+        val actual = GeoLocationUtil.getLocationDataFromGeoUri(uri)
+        if (expected == null) {
+            assertNull(actual)
+            return
+        }
+        assertNotNull(actual)
+        assertEquals(expected.latitude, actual?.latitude)
+        assertEquals(expected.longitude, actual?.longitude)
+        assertEquals(expected.poi, actual?.poi)
+        assertEquals(expected.address, actual?.address)
+        assertEquals(expected.accuracy, actual?.accuracy)
+    }
+
+    @Test
+    fun testGetLocationFromUri() {
+        val latLong1234 = LocationDataModel(12.0, 34.0, 0, "", "")
+        expectLocationData(latLong1234, "geo:12,34;abcd=efg")
+        expectLocationData(latLong1234, "geo:12.0,34.00;a=b;c=d")
+        expectLocationData(latLong1234, "geo:12.0,34.0?q=12.0,34.0")
+        expectLocationData(latLong1234, "geo:1.0,2?q=12.0,34.0")
+        expectLocationData(latLong1234, "geo:0,0?q=12,34")
+        expectLocationData(latLong1234, "geo:12,34,56")
+        expectLocationData(latLong1234, "geo:12,34,56?z=12")
+    }
+
+}

+ 19 - 0
app/src/androidTest/java/ch/threema/app/utils/LinkifyUtilTest.kt

@@ -143,4 +143,23 @@ class LinkifyUtilTest {
         assertSpans("geo:1,2\nthreema.ch", setOf(0 to 7, 8 to 18))
     }
 
+    @Test
+    fun testAndroidGeoUris() {
+        assertSingleSpan("geo:37.786971,-122.399677?q=37.786971,-122.399677(This+is+the+geo-label)")
+        assertSingleSpan("geo:37.786971,-122.399677?q=37.786971,-122.399677")
+        assertSingleSpan("geo:0,0?q=37.786971,-122.399677")
+        assertSingleSpan("geo:0,0?q=37.786971,-122.399677(With+label)")
+
+        assertSingleSpan("geo:0,0?z=21")
+        assertSingleSpan("geo:0,0?z=1")
+
+        // label should not count to the geo uri (because there is a query needed for labels)
+        assertSpans("geo:37.786971,-122.399677(This+is+the+label+without+query)", setOf(0 to 25))
+        // label should not count to the geo uri (because it is incomplete)
+        assertSpans("geo:0,0?q=37.786971,-122.399677(With+non-closing-label", setOf(0 to 31))
+
+        assertSpans("geo:0,0?z=3.1", setOf(0 to 11))
+        assertSpans("geo:0,0?z=12(Label+not+allowed+here)", setOf(0 to 12))
+    }
+
 }

+ 0 - 0
app/src/fdroid/AndroidManifest.xml → app/src/libre/AndroidManifest.xml


+ 0 - 0
app/src/fdroid/java/ch/threema/app/activities/DownloadApkActivity.java → app/src/libre/java/ch/threema/app/activities/DownloadApkActivity.java


+ 0 - 0
app/src/fdroid/java/ch/threema/app/utils/DownloadUtil.java → app/src/libre/java/ch/threema/app/utils/DownloadUtil.java


+ 9 - 1
app/src/main/AndroidManifest.xml

@@ -784,7 +784,15 @@
 		<activity
 			android:name=".activities.MapActivity"
 			android:configChanges="orientation|keyboardHidden|screenSize|uiMode"
-			android:theme="@style/Theme.LocationPicker" />
+			android:theme="@style/Theme.LocationPicker"
+			android:exported="true">
+			<intent-filter>
+				<action android:name="android.intent.action.VIEW" />
+				<category android:name="android.intent.category.DEFAULT" />
+				<category android:name="android.intent.category.BROWSABLE" />
+				<data android:scheme="geo" />
+			</intent-filter>
+		</activity>
 		<activity
 			android:name=".activities.WorkExplainActivity"
 			android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize|uiMode"

+ 6 - 6
app/src/main/java/ch/threema/app/BuildFlavor.java

@@ -32,7 +32,7 @@ public class BuildFlavor {
 	private final static String FLAVOR_RED = "red";
 	private final static String FLAVOR_HMS = "hms";
 	private final static String FLAVOR_HMS_WORK = "hms_work";
-	private final static String FLAVOR_FDROID = "fdroid";
+	private final static String FLAVOR_LIBRE = "libre";
 
 	public enum LicenseType {
 		NONE, GOOGLE, SERIAL, GOOGLE_WORK, HMS, HMS_WORK, ONPREM
@@ -77,7 +77,7 @@ public class BuildFlavor {
 	@SuppressWarnings("ConstantConditions")
 	public static boolean forceThreemaPush() {
 		switch (BuildConfig.FLAVOR) {
-			case FLAVOR_FDROID:
+			case FLAVOR_LIBRE:
 				return true;
 			default:
 				return false;
@@ -91,7 +91,7 @@ public class BuildFlavor {
 	@SuppressWarnings("ConstantConditions")
 	public static boolean isLibre() {
 		switch (BuildConfig.FLAVOR) {
-			case FLAVOR_FDROID:
+			case FLAVOR_LIBRE:
 				return true;
 			default:
 				return false;
@@ -125,7 +125,7 @@ public class BuildFlavor {
 					licenseType = LicenseType.HMS_WORK;
 					break;
 				case FLAVOR_STORE_THREEMA:
-				case FLAVOR_FDROID:
+				case FLAVOR_LIBRE:
 					licenseType = LicenseType.SERIAL;
 					break;
 				default:
@@ -164,8 +164,8 @@ public class BuildFlavor {
 				case FLAVOR_HMS_WORK:
 					name = "HMS Work";
 					break;
-				case FLAVOR_FDROID:
-					name = "F-Droid";
+				case FLAVOR_LIBRE:
+					name = "Libre";
 					break;
 				default:
 					throw new IllegalStateException("Unhandled build flavor " + BuildConfig.FLAVOR);

+ 1 - 1
app/src/main/java/ch/threema/app/ThreemaApplication.java

@@ -2043,7 +2043,7 @@ public class ThreemaApplication extends MultiDexApplication implements DefaultLi
 					logger.error("Exception", e);
 				}
 			}
-		});
+		}, THREEMA_APPLICATION_LISTENER_TAG);
 
 		if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.M || ContextCompat.checkSelfPermission(serviceManager.getContext(), android.Manifest.permission.READ_CONTACTS) == PackageManager.PERMISSION_GRANTED) {
 			serviceManager.getContext().getContentResolver()

+ 7 - 6
app/src/main/java/ch/threema/app/activities/ComposeMessageActivity.java

@@ -25,7 +25,6 @@ import android.content.Intent;
 import android.content.res.Configuration;
 import android.media.AudioManager;
 import android.os.Bundle;
-import android.preference.PreferenceActivity;
 import android.view.WindowManager;
 import android.widget.FrameLayout;
 
@@ -42,7 +41,6 @@ import ch.threema.app.listeners.MessagePlayerListener;
 import ch.threema.app.managers.ListenerManager;
 import ch.threema.app.messagereceiver.MessageReceiver;
 import ch.threema.app.preference.SettingsActivity;
-import ch.threema.app.preference.SettingsSecurityFragment;
 import ch.threema.app.services.DeadlineListService;
 import ch.threema.app.utils.ConfigUtils;
 import ch.threema.app.utils.HiddenChatUtil;
@@ -102,8 +100,6 @@ public class ComposeMessageActivity extends ThreemaToolbarActivity implements Ge
 
 		logger.debug("initActivity");
 
-		checkHiddenChatLock(getIntent(), ID_HIDDEN_CHECK_ON_CREATE);
-
 		this.getFragments();
 
 		if (findViewById(R.id.messages) != null) {
@@ -117,10 +113,12 @@ public class ComposeMessageActivity extends ThreemaToolbarActivity implements Ge
 		if (composeMessageFragment == null) {
 			// fragment no longer around
 			composeMessageFragment = new ComposeMessageFragment();
-			getSupportFragmentManager().beginTransaction().add(R.id.compose, composeMessageFragment, COMPOSE_FRAGMENT_TAG).commit();
+			getSupportFragmentManager().beginTransaction().add(R.id.compose, composeMessageFragment, COMPOSE_FRAGMENT_TAG).hide(composeMessageFragment).commit();
 		}
 
-
+		if (!checkHiddenChatLock(getIntent(), ID_HIDDEN_CHECK_ON_CREATE)) {
+			getSupportFragmentManager().beginTransaction().show(composeMessageFragment).commit();
+		}
 		return true;
 	}
 
@@ -147,6 +145,7 @@ public class ComposeMessageActivity extends ThreemaToolbarActivity implements Ge
 
 		if (composeMessageFragment != null) {
 			if (!checkHiddenChatLock(intent, ID_HIDDEN_CHECK_ON_NEW_INTENT)) {
+				getSupportFragmentManager().beginTransaction().show(composeMessageFragment).commit();
 				composeMessageFragment.onNewIntent(intent);
 			}
 		}
@@ -215,6 +214,7 @@ public class ComposeMessageActivity extends ThreemaToolbarActivity implements Ge
 				if (resultCode == RESULT_OK) {
 					serviceManager.getScreenLockService().setAuthenticated(true);
 					if (composeMessageFragment != null) {
+						getSupportFragmentManager().beginTransaction().show(composeMessageFragment).commit();
 						// mark conversation as read as soon as it's unhidden
 						composeMessageFragment.markAsRead();
 					}
@@ -228,6 +228,7 @@ public class ComposeMessageActivity extends ThreemaToolbarActivity implements Ge
 				if (resultCode == RESULT_OK) {
 					serviceManager.getScreenLockService().setAuthenticated(true);
 					if (composeMessageFragment != null) {
+						getSupportFragmentManager().beginTransaction().show(composeMessageFragment).commit();
 						composeMessageFragment.onNewIntent(this.currentIntent);
 					}
 				}

+ 141 - 29
app/src/main/java/ch/threema/app/activities/DirectoryActivity.java

@@ -31,24 +31,32 @@ import android.os.Bundle;
 import android.os.Handler;
 import android.os.SystemClock;
 import android.text.TextUtils;
+import android.view.Menu;
 import android.view.MenuItem;
 import android.view.View;
+import android.widget.TextView;
 import android.widget.Toast;
 
 import com.google.android.material.chip.Chip;
 import com.google.android.material.chip.ChipGroup;
+import com.google.android.material.progressindicator.LinearProgressIndicator;
 
 import org.slf4j.Logger;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
 import java.util.List;
 
 import androidx.annotation.ColorInt;
+import androidx.annotation.IntDef;
 import androidx.annotation.NonNull;
 import androidx.annotation.UiThread;
 import androidx.appcompat.app.ActionBar;
+import androidx.appcompat.widget.SearchView;
 import androidx.appcompat.widget.Toolbar;
 import androidx.lifecycle.LiveData;
+import androidx.lifecycle.Observer;
 import androidx.paging.LivePagedListBuilder;
 import androidx.paging.PagedList;
 import androidx.recyclerview.widget.DefaultItemAnimator;
@@ -71,6 +79,8 @@ import ch.threema.domain.protocol.api.work.WorkDirectoryCategory;
 import ch.threema.domain.protocol.api.work.WorkDirectoryContact;
 import ch.threema.domain.protocol.api.work.WorkOrganization;
 
+import static ch.threema.app.ui.DirectoryDataSource.MIN_SEARCH_STRING_LENGTH;
+
 public class DirectoryActivity extends ThreemaToolbarActivity implements ThreemaSearchView.OnQueryTextListener, MultiChoiceSelectorDialog.SelectorDialogClickListener {
 	private static final Logger logger = LoggingUtil.getThreemaLogger("DirectoryActivity");
 
@@ -78,7 +88,13 @@ public class DirectoryActivity extends ThreemaToolbarActivity implements Threema
 	private static final long QUERY_TIMEOUT = 1000; // ms
 	private static final String DIALOG_TAG_CATEGORY_SELECTOR = "cs";
 	public static final String EXTRA_ANIMATE_OUT = "anim";
-	private static final String WILDCARD_SEARCH_ALL = "*";
+
+	@Retention(RetentionPolicy.SOURCE)
+	@IntDef({EMPTY_STATE_IDLE, EMPTY_STATE_SEARCHING, EMPTY_STATE_RESULTS})
+	public @interface EmptyState {}
+	private static final int EMPTY_STATE_IDLE = 0;
+	private static final int EMPTY_STATE_SEARCHING = 1;
+	private static final int EMPTY_STATE_RESULTS = 2;
 
 	private ContactService contactService;
 	private boolean sortByFirstName;
@@ -87,6 +103,10 @@ public class DirectoryActivity extends ThreemaToolbarActivity implements Threema
 	private DirectoryDataSourceFactory directoryDataSourceFactory;
 	private EmptyRecyclerView recyclerView;
 	private ChipGroup chipGroup;
+	private TextView emptyTextView;
+	private Menu menu;
+	private MenuItem searchMenuItem;
+	private LinearProgressIndicator progressIndicator;
 
 	private List<WorkDirectoryCategory> categoryList = new ArrayList<>();
 	private final List<WorkDirectoryCategory> checkedCategories = new ArrayList<>();
@@ -100,15 +120,27 @@ public class DirectoryActivity extends ThreemaToolbarActivity implements Threema
 	private final Runnable queryTask = new Runnable() {
 		@Override
 		public void run() {
-			if (checkedCategories.size() > 0 && TestUtil.empty(queryText)) {
-				queryText = WILDCARD_SEARCH_ALL;
-			}
-
+			updateEmptyViewState(EMPTY_STATE_SEARCHING);
 			directoryDataSourceFactory.postLiveData.getValue().setQueryText(queryText);
 			directoryDataSourceFactory.postLiveData.getValue().invalidate();
 		}
 	};
 
+	final SearchView.OnQueryTextListener queryTextListener = new SearchView.OnQueryTextListener() {
+		@Override
+		public boolean onQueryTextChange(String newText) {
+			queryText = newText;
+			queryHandler.removeCallbacks(queryTask);
+			queryHandler.postDelayed(queryTask, QUERY_TIMEOUT);
+			return true;
+		}
+
+		@Override
+		public boolean onQueryTextSubmit(String query) {
+			return true;
+		}
+	};
+
 	@Override
 	public boolean onQueryTextSubmit(String query) {
 		// Do something
@@ -162,9 +194,6 @@ public class DirectoryActivity extends ThreemaToolbarActivity implements Threema
 			return false;
 		}
 
-		categoryList = preferenceService.getWorkDirectoryCategories();
-
-		findViewById(R.id.category_selector_button).setVisibility(categoryList.size() > 0 ? View.VISIBLE : View.GONE);
 
 		WorkOrganization workOrganization = preferenceService.getWorkOrganization();
 		if (workOrganization != null && !TestUtil.empty(workOrganization.getName())) {
@@ -172,12 +201,12 @@ public class DirectoryActivity extends ThreemaToolbarActivity implements Threema
 			getToolbar().setTitle(workOrganization.getName());
 		}
 
-		ThreemaSearchView searchView = findViewById(R.id.search);
-		searchView.setOnQueryTextListener(this);
-
 		sortByFirstName = preferenceService.isContactListSortingFirstName();
 
 		chipGroup = findViewById(R.id.chip_group);
+		emptyTextView = findViewById(R.id.empty_text);
+		progressIndicator = findViewById(R.id.progress_bar);
+		progressIndicator.setVisibility(View.GONE);
 
 		categorySpanColor = ConfigUtils.getColorFromAttribute(this, R.attr.mention_background);
 		categorySpanTextColor = ConfigUtils.getColorFromAttribute(this, R.attr.mention_text_color);
@@ -186,11 +215,13 @@ public class DirectoryActivity extends ThreemaToolbarActivity implements Threema
 		recyclerView.setHasFixedSize(true);
 		recyclerView.setLayoutManager(new LinearLayoutManager(this));
 		recyclerView.setItemAnimator(new DefaultItemAnimator());
-		recyclerView.setEmptyView(findViewById(R.id.empty_text));
+		recyclerView.setEmptyView(emptyTextView);
 
 		DirectoryHeaderItemDecoration headerItemDecoration = new DirectoryHeaderItemDecoration(getResources().getDimensionPixelSize(R.dimen.directory_header_height), true, getSectionCallback());
 		recyclerView.addItemDecoration(headerItemDecoration);
 
+		categoryList = preferenceService.getWorkDirectoryCategories();
+
 		directoryAdapter = new DirectoryAdapter(this, preferenceService, contactService, categoryList);
 		directoryAdapter.setOnClickItemListener(new DirectoryAdapter.OnClickItemListener() {
 			@Override
@@ -214,11 +245,79 @@ public class DirectoryActivity extends ThreemaToolbarActivity implements Threema
 		directoryDataSourceFactory = new DirectoryDataSourceFactory();
 
 		LiveData<PagedList<WorkDirectoryContact>> contacts = new LivePagedListBuilder(directoryDataSourceFactory, config).build();
-		contacts.observe(this, workDirectoryContacts -> directoryAdapter.submitList(workDirectoryContacts));
+		contacts.observe(this, new Observer<PagedList<WorkDirectoryContact>>() {
+			@Override
+			public void onChanged(PagedList<WorkDirectoryContact> workDirectoryContacts) {
+				directoryAdapter.submitList(workDirectoryContacts);
+				updateEmptyViewState(
+						(queryText != null && queryText.length() >= MIN_SEARCH_STRING_LENGTH) || checkedCategories.size() > 0 ?
+						EMPTY_STATE_RESULTS:
+						EMPTY_STATE_IDLE);
+			}
+		});
 
 		recyclerView.setAdapter(directoryAdapter);
 
-		findViewById(R.id.category_selector_button).setOnClickListener(this::selectCategories);
+		findViewById(R.id.search_container).setOnClickListener(v ->	showResultsLayout());
+		return true;
+	}
+
+	private void showResultsLayout() {
+		findViewById(R.id.initial_layout).setVisibility(View.GONE);
+		findViewById(R.id.results_layout).setVisibility(View.VISIBLE);
+
+		if (menu != null) {
+			if (searchMenuItem != null) {
+				searchMenuItem.expandActionView();
+				searchMenuItem.setOnActionExpandListener(new MenuItem.OnActionExpandListener() {
+					@Override
+					public boolean onMenuItemActionExpand(MenuItem item) {
+						return false;
+					}
+
+					@Override
+					public boolean onMenuItemActionCollapse(MenuItem item) {
+						searchMenuItem.setVisible(false);
+						showIntroLayout();
+						return true;
+					}
+				});
+				updateEmptyViewState(EMPTY_STATE_IDLE);
+			}
+		}
+	}
+
+	private void showIntroLayout() {
+		findViewById(R.id.initial_layout).setVisibility(View.VISIBLE);
+		findViewById(R.id.results_layout).setVisibility(View.GONE);
+
+		if (menu != null) {
+			if (searchMenuItem != null) {
+				searchMenuItem.setOnActionExpandListener(null);
+				searchMenuItem.collapseActionView();
+			}
+		}
+	}
+
+	public boolean onCreateOptionsMenu(@NonNull Menu menu) {
+		super.onCreateOptionsMenu(menu);
+
+		this.menu = menu;
+
+		getMenuInflater().inflate(R.menu.activity_directory, menu);
+
+		searchMenuItem = menu.findItem(R.id.menu_search_directory);
+		if (searchMenuItem != null) {
+			SearchView searchView = (SearchView) searchMenuItem.getActionView();
+			if (searchView != null) {
+				searchView.setQueryHint(getString(R.string.directory_search));
+				searchView.setOnQueryTextListener(queryTextListener);
+
+			}
+		}
+
+		categoryList = preferenceService.getWorkDirectoryCategories();
+		menu.findItem(R.id.menu_category).setVisible(categoryList.size() > 0);
 
 		return true;
 	}
@@ -229,6 +328,10 @@ public class DirectoryActivity extends ThreemaToolbarActivity implements Threema
 			case android.R.id.home:
 				this.finish();
 				return true;
+			case R.id.menu_category:
+				selectCategories();
+				break;
+
 		}
 		return super.onOptionsItemSelected(item);
 	}
@@ -301,7 +404,7 @@ public class DirectoryActivity extends ThreemaToolbarActivity implements Threema
 		};
 	}
 
-	public void selectCategories(View view) {
+	public void selectCategories() {
 		String[] categoryNames = new String[categoryList.size()];
 		boolean[] categoryChecked = new boolean[categoryList.size()];
 
@@ -357,13 +460,6 @@ public class DirectoryActivity extends ThreemaToolbarActivity implements Threema
 								if (categoryId.equals(checkedCategory.getId())) {
 									checkedCategories.remove(checkedCategory);
 									chipGroup.removeView(v);
-
-									// check if last chip has been removed
-									if (chipGroup.getChildCount() == 0 && WILDCARD_SEARCH_ALL.equals(queryText)) {
-										queryText = "";
-										directoryDataSourceFactory.postLiveData.getValue().setQueryText(queryText);
-									}
-
 									updateDirectory();
 									break;
 								}
@@ -382,10 +478,32 @@ public class DirectoryActivity extends ThreemaToolbarActivity implements Threema
 	}
 
 	private void updateDirectory() {
+		updateEmptyViewState(EMPTY_STATE_SEARCHING);
 		directoryDataSourceFactory.postLiveData.getValue().setQueryCategories(checkedCategories);
 		directoryDataSourceFactory.postLiveData.getValue().invalidate();
 	}
 
+	private void updateEmptyViewState(@EmptyState int newState) {
+		if (emptyTextView == null) {
+			return;
+		}
+
+		switch (newState) {
+			case EMPTY_STATE_SEARCHING:
+				emptyTextView.setText("");
+				progressIndicator.setVisibility(View.VISIBLE);
+				break;
+			case EMPTY_STATE_IDLE:
+				emptyTextView.setText(R.string.directory_empty_view_text);
+				progressIndicator.setVisibility(View.GONE);
+				break;
+			case EMPTY_STATE_RESULTS:
+				emptyTextView.setText(R.string.no_matching_contacts);
+				progressIndicator.setVisibility(View.GONE);
+				break;
+		}
+	}
+
 	@Override
 	public void onYes(String tag, boolean[] checkedItems) {
 		checkedCategories.clear();
@@ -399,13 +517,7 @@ public class DirectoryActivity extends ThreemaToolbarActivity implements Threema
 		}
 
 		if (numCheckedCategories > 0) {
-			if (TestUtil.empty(queryText)) {
-				queryText = WILDCARD_SEARCH_ALL;
-			}
-		} else {
-			if (WILDCARD_SEARCH_ALL.equals(queryText)) {
-				queryText = "";
-			}
+			showResultsLayout();
 		}
 		directoryDataSourceFactory.postLiveData.getValue().setQueryText(queryText);
 

+ 1 - 1
app/src/main/java/ch/threema/app/activities/DisableBatteryOptimizationsActivity.java

@@ -122,7 +122,7 @@ public class DisableBatteryOptimizationsActivity extends ThreemaActivity impleme
 	}
 
 	@Override
-	public void onConfigurationChanged(Configuration newConfig) {
+	public void onConfigurationChanged(@NonNull Configuration newConfig) {
 		super.onConfigurationChanged(newConfig);
 	}
 

+ 0 - 1
app/src/main/java/ch/threema/app/activities/HomeActivity.java

@@ -1776,7 +1776,6 @@ public class HomeActivity extends ThreemaAppCompatActivity implements
 			connectionIndicator = findViewById(R.id.connection_indicator);
 			if (connectionIndicator != null) {
 				ConnectionIndicatorUtil.getInstance().updateConnectionIndicator(connectionIndicator, connectionState);
-				invalidateOptionsMenu();
 			}
 		});
 	}

+ 53 - 5
app/src/main/java/ch/threema/app/activities/MapActivity.java

@@ -24,6 +24,7 @@ package ch.threema.app.activities;
 import android.Manifest;
 import android.annotation.SuppressLint;
 import android.content.ActivityNotFoundException;
+import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.PackageManager;
@@ -41,6 +42,7 @@ import android.widget.FrameLayout;
 import android.widget.TextView;
 import android.widget.Toast;
 
+import com.google.android.material.chip.Chip;
 import com.mapbox.mapboxsdk.annotations.IconFactory;
 import com.mapbox.mapboxsdk.annotations.MarkerOptions;
 import com.mapbox.mapboxsdk.camera.CameraUpdate;
@@ -81,6 +83,7 @@ import ch.threema.app.utils.GeoLocationUtil;
 import ch.threema.app.utils.LocationUtil;
 import ch.threema.app.utils.RuntimeUtil;
 import ch.threema.base.utils.LoggingUtil;
+import ch.threema.storage.models.data.LocationDataModel;
 
 import static ch.threema.app.utils.IntentDataUtil.INTENT_DATA_LOCATION_LAT;
 import static ch.threema.app.utils.IntentDataUtil.INTENT_DATA_LOCATION_LNG;
@@ -116,6 +119,8 @@ public class MapActivity extends ThreemaActivity implements GenericAlertDialog.D
 
 	private int insetTop = 0;
 
+	private boolean isShowingExternalLocation = false;
+
 	@Override
 	public void onCreate(Bundle savedInstanceState) {
 		super.onCreate(savedInstanceState);
@@ -177,11 +182,24 @@ public class MapActivity extends ThreemaActivity implements GenericAlertDialog.D
 
 		Intent intent = getIntent();
 
-		markerPosition = new LatLng(
+		if (intent.hasExtra(INTENT_DATA_LOCATION_LAT) || intent.hasExtra(INTENT_DATA_LOCATION_LNG)) {
+			markerPosition = new LatLng(
 				intent.getDoubleExtra(INTENT_DATA_LOCATION_LAT, 0),
 				intent.getDoubleExtra(INTENT_DATA_LOCATION_LNG, 0));
-		markerName = intent.getStringExtra(INTENT_DATA_LOCATION_NAME);
-		markerProvider = intent.getStringExtra(INTENT_DATA_LOCATION_PROVIDER);
+			markerName = intent.getStringExtra(INTENT_DATA_LOCATION_NAME);
+			markerProvider = intent.getStringExtra(INTENT_DATA_LOCATION_PROVIDER);
+			isShowingExternalLocation = false;
+		} else if (intent.getData() != null) {
+			LocationDataModel locationData = GeoLocationUtil.getLocationDataFromGeoUri(intent.getData());
+			if (locationData != null) {
+				markerPosition = new LatLng(locationData.getLatitude(), locationData.getLongitude());
+				isShowingExternalLocation = true;
+			} else {
+				Toast.makeText(this, R.string.cannot_display_location, Toast.LENGTH_LONG).show();
+				finish();
+				return;
+			}
+		}
 
 		if (preferenceService.getPrivacyPolicyAcceptedVersion() < 4.0f) {
 			GenericAlertDialog alertDialog = GenericAlertDialog.newInstanceHtml(
@@ -200,7 +218,15 @@ public class MapActivity extends ThreemaActivity implements GenericAlertDialog.D
 	private void initUi() {
 		findViewById(R.id.coordinator).setVisibility(View.VISIBLE);
 		findViewById(R.id.center_map).setOnClickListener((it -> zoomToCenter()));
-		findViewById(R.id.open_chip).setOnClickListener((it -> openExternal()));
+		Chip openChip = findViewById(R.id.open_chip);
+		Chip shareChip = findViewById(R.id.share_chip);
+		if (isShowingExternalLocation) {
+			shareChip.setOnClickListener((it -> shareLocation()));
+			openChip.setVisibility(View.GONE);
+		} else {
+			openChip.setOnClickListener((it -> openExternal()));
+			shareChip.setVisibility(View.GONE);
+		}
 		TextView locationName = findViewById(R.id.location_name);
 		TextView locationCoordinates = findViewById(R.id.location_coordinates);
 
@@ -209,7 +235,15 @@ public class MapActivity extends ThreemaActivity implements GenericAlertDialog.D
 	}
 
 	private void openExternal() {
-		Intent intent = new Intent(Intent.ACTION_VIEW, GeoLocationUtil.getLocationUri(markerPosition.getLatitude(), markerPosition.getLongitude(), markerName, markerProvider)); // todo: address
+		Intent intent = Intent.createChooser(new Intent(
+			Intent.ACTION_VIEW,
+			GeoLocationUtil.getLocationUri(markerPosition.getLatitude(), markerPosition.getLongitude(), markerName, markerProvider)
+		), getString(R.string.open_in_maps_app));
+
+		if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
+			// Don't allow opening location recursively
+			intent.putExtra(Intent.EXTRA_EXCLUDE_COMPONENTS, new ComponentName[]{getComponentName()});
+		}
 
 		try {
 			startActivity(intent);
@@ -218,6 +252,18 @@ public class MapActivity extends ThreemaActivity implements GenericAlertDialog.D
 		}
 	}
 
+	/**
+	 * Share the currently displayed location within threema.
+	 */
+	private void shareLocation() {
+		Intent intent = new Intent(this, RecipientListBaseActivity.class);
+
+		intent.setAction(Intent.ACTION_SEND);
+		intent.setType("text/plain");
+		intent.putExtra(Intent.EXTRA_STREAM, GeoLocationUtil.getLocationUri(markerPosition.getLatitude(), markerPosition.getLongitude(), "", ""));
+		startActivity(intent);
+	}
+
 	private void initMap() {
 		mapView.getMapAsync(new OnMapReadyCallback() {
 			@Override
@@ -284,6 +330,7 @@ public class MapActivity extends ThreemaActivity implements GenericAlertDialog.D
 		}.execute(markerPosition);
 	}
 
+	@SuppressLint("MissingPermission")
 	private void setupLocationComponent(Style style) {
 		logger.debug("setupLocationComponent");
 
@@ -448,6 +495,7 @@ public class MapActivity extends ThreemaActivity implements GenericAlertDialog.D
 	@Override
 	public void onRequestPermissionsResult(int requestCode,
 	                                       @NonNull String permissions[], @NonNull int[] grantResults) {
+		super.onRequestPermissionsResult(requestCode, permissions, grantResults);
 		if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
 			if (requestCode == PERMISSION_REQUEST_LOCATION) {
 				requestLocationEnabled(locationManager);

+ 16 - 9
app/src/main/java/ch/threema/app/activities/RecipientListBaseActivity.java

@@ -112,6 +112,7 @@ import ch.threema.app.utils.ConfigUtils;
 import ch.threema.app.utils.ContactLookupUtil;
 import ch.threema.app.utils.DialogUtil;
 import ch.threema.app.utils.FileUtil;
+import ch.threema.app.utils.GeoLocationUtil;
 import ch.threema.app.utils.IntentDataUtil;
 import ch.threema.app.utils.MimeUtil;
 import ch.threema.app.utils.NameUtil;
@@ -131,6 +132,7 @@ import java8.util.concurrent.CompletableFuture;
 
 import static ch.threema.app.activities.SendMediaActivity.MAX_EDITABLE_IMAGES;
 import static ch.threema.app.fragments.ComposeMessageFragment.MAX_FORWARDABLE_ITEMS;
+import static ch.threema.app.ui.MediaItem.TYPE_LOCATION;
 import static ch.threema.app.ui.MediaItem.TYPE_TEXT;
 
 public class RecipientListBaseActivity extends ThreemaToolbarActivity implements
@@ -290,7 +292,7 @@ public class RecipientListBaseActivity extends ThreemaToolbarActivity implements
 			for (MediaItem mediaItem: mediaItems) {
 				String mimeType = mediaItem.getMimeType();
 				if (mimeType != null) {
-					if (!hasMedia && !mimeType.startsWith("text/")) {
+					if (!hasMedia && !mimeType.startsWith("text/") && mediaItem.getType() != TYPE_LOCATION) {
 						hasMedia = true;
 					}
 				}
@@ -461,7 +463,7 @@ public class RecipientListBaseActivity extends ThreemaToolbarActivity implements
 					}
 
 					if (type != null && (uri != null || MimeUtil.isText(type))) {
-						if (type.equals("message/rfc822")) {
+						if (type.equals(MimeUtil.MIME_TYPE_EMAIL)) {
 							// email attachments
 							//  extract file type from uri path
 							String mimeType = FileUtil.getMimeTypeFromUri(this, uri);
@@ -480,9 +482,11 @@ public class RecipientListBaseActivity extends ThreemaToolbarActivity implements
 								}
 							}
 						}
-						if (type.equals("text/plain")) {
+						if (type.equals(MimeUtil.MIME_TYPE_TEXT)) {
 							String textIntent = getTextFromIntent(intent);
-							if (uri != null) {
+							if (GeoLocationUtil.isValidGeoUri(uri)) {
+								mediaItems.add(new MediaItem(uri, MediaItem.TYPE_LOCATION, MimeUtil.MIME_TYPE_ANY, textIntent));
+							} else if (uri != null) {
 								// default to sending text as file
 								type = "x-text/plain";
 
@@ -496,19 +500,21 @@ public class RecipientListBaseActivity extends ThreemaToolbarActivity implements
 									captionText = textIntent;
 								}
 							} else if (textIntent != null) {
-								mediaItems.add(new MediaItem(uri, TYPE_TEXT, MimeUtil.MIME_TYPE_TEXT, textIntent));
+								mediaItems.add(new MediaItem(null, TYPE_TEXT, MimeUtil.MIME_TYPE_TEXT, textIntent));
 							}
 						} else {
 							if (uri != null) {
 								// guess the correct mime type as ACTION_SEND may have been called with a generic mime type such as "image/*" which should be overridden
 								String guessedType = getMimeTypeFromContentUri(uri);
 								if (guessedType != null) {
-									type = guessedType;
+									if (!guessedType.equals(MimeUtil.MIME_TYPE_DEFAULT) || type.equals("*")) {
+										type = guessedType;
+									}
 								}
 
 								String textIntent = getTextFromIntent(intent);
 								// don't add fixed caption to media item because we want it to be editable when sending a zip file (share chat)
-								if (type.equals("application/zip") && textIntent != null) {
+								if (type.equals(MimeUtil.MIME_TYPE_ZIP) && textIntent != null) {
 									captionText = textIntent;
 									mediaItems.add(new MediaItem(uri, MediaItem.TYPE_FILE, MimeUtil.MIME_TYPE_ZIP, textIntent));
 								} else { // if text was shared along with the media item, add that too
@@ -1042,10 +1048,11 @@ public class RecipientListBaseActivity extends ThreemaToolbarActivity implements
 								} else {
 									// mixed media
 									ExpandableTextEntryDialog alertDialog;
+									boolean expandable = mediaItems.size() == 1 && mediaItems.get(0).getType() != TYPE_LOCATION;
 									if (hideUi) {
-										alertDialog = ExpandableTextEntryDialog.newInstance(getString(R.string.app_name), getString(R.string.really_send, finalRecipientName), R.string.add_caption_hint, captionText, R.string.send, R.string.cancel, mediaItems.size() == 1);
+										alertDialog = ExpandableTextEntryDialog.newInstance(getString(R.string.app_name), getString(R.string.really_send, finalRecipientName), R.string.add_caption_hint, captionText, R.string.send, R.string.cancel, expandable);
 									} else {
-										alertDialog = ExpandableTextEntryDialog.newInstance(getString(R.string.really_send, finalRecipientName), R.string.add_caption_hint, captionText, R.string.send, R.string.cancel, mediaItems.size() == 1);
+										alertDialog = ExpandableTextEntryDialog.newInstance(getString(R.string.really_send, finalRecipientName), R.string.add_caption_hint, captionText, R.string.send, R.string.cancel, expandable);
 									}
 									alertDialog.setData(recipients);
 									alertDialog.show(getSupportFragmentManager(), null);

+ 77 - 52
app/src/main/java/ch/threema/app/activities/SendMediaActivity.java

@@ -353,7 +353,10 @@ public class SendMediaActivity extends ThreemaToolbarActivity implements
 			@Override
 			public void afterTextChanged(Editable s) {
 				if (s != null && bigImagePos < sendMediaAdapter.size()) {
-					sendMediaAdapter.getItem(bigImagePos).setCaption(s.toString());
+					MediaItem mediaItem = sendMediaAdapter.getItem(bigImagePos);
+					if (mediaItem != null) {
+						mediaItem.setCaption(s.toString());
+					}
 				}
 			}
 		});
@@ -620,7 +623,7 @@ public class SendMediaActivity extends ThreemaToolbarActivity implements
 	}
 
 	@Override
-	public boolean onPrepareOptionsMenu(Menu menu) {
+	public boolean onPrepareOptionsMenu(@NonNull Menu menu) {
 		updateMenu();
 
 		return super.onPrepareOptionsMenu(menu);
@@ -636,7 +639,10 @@ public class SendMediaActivity extends ThreemaToolbarActivity implements
 			new Handler().post(() -> {
 				final View v = findViewById(R.id.settings);
 				if (v != null) {
-					showSettingsDropDown(v, sendMediaAdapter.getItem(bigImagePos));
+					MediaItem mediaItem = sendMediaAdapter.getItem(bigImagePos);
+					if (mediaItem != null) {
+						showSettingsDropDown(v, mediaItem);
+					}
 				}
 			});
 			return true;
@@ -665,16 +671,18 @@ public class SendMediaActivity extends ThreemaToolbarActivity implements
 		});
 
 		menu.findItem(R.id.crop).setOnMenuItemClickListener(item -> {
-			if (bigImagePos < sendMediaAdapter.size()) {
-				cropImage();
+			MediaItem mediaItem = sendMediaAdapter.getItem(bigImagePos);
+			if (mediaItem != null) {
+				cropImage(mediaItem);
 				return true;
 			}
 			return false;
 		});
 
 		menu.findItem(R.id.edit).setOnMenuItemClickListener(item -> {
-			if (bigImagePos < sendMediaAdapter.size()) {
-				editImage();
+			MediaItem mediaItem = sendMediaAdapter.getItem(bigImagePos);
+			if (mediaItem != null) {
+				editImage(mediaItem);
 				return true;
 			}
 			return false;
@@ -692,7 +700,12 @@ public class SendMediaActivity extends ThreemaToolbarActivity implements
 			return;
 		}
 
-		int oldRotation = sendMediaAdapter.getItem(bigImagePos).getRotation();
+		MediaItem mediaItem = sendMediaAdapter.getItem(bigImagePos);
+		if (mediaItem == null) {
+			return;
+		}
+
+		int oldRotation = mediaItem.getRotation();
 		int newRotation = ((oldRotation == 0 ? 360 : oldRotation) - 90) % 360;
 
 		int height = bigImageView.getDrawable().getBounds().width();
@@ -719,10 +732,13 @@ public class SendMediaActivity extends ThreemaToolbarActivity implements
 
 				@Override
 				public void onAnimationEnd(Animator animation) {
-					sendMediaAdapter.getItem(bigImagePos).setRotation(newRotation);
-					showBigImage(bigImagePos, false);
-					sendMediaAdapter.update(bigImagePos);
-					hasChanges = true;
+					MediaItem mediaItem = sendMediaAdapter.getItem(bigImagePos);
+					if (mediaItem != null) {
+						mediaItem.setRotation(newRotation);
+						showBigImage(bigImagePos, false);
+						sendMediaAdapter.update(bigImagePos);
+						hasChanges = true;
+					}
 				}
 
 				@Override
@@ -747,10 +763,13 @@ public class SendMediaActivity extends ThreemaToolbarActivity implements
 
 				@Override
 				public void onAnimationEnd(Animator animation) {
-					flip(sendMediaAdapter.getItem(bigImagePos));
-					showBigImage(bigImagePos, false);
-					sendMediaAdapter.update(bigImagePos);
-					hasChanges = true;
+					MediaItem mediaItem = sendMediaAdapter.getItem(bigImagePos);
+					if (mediaItem != null) {
+						flip(mediaItem);
+						showBigImage(bigImagePos, false);
+						sendMediaAdapter.update(bigImagePos);
+						hasChanges = true;
+					}
 				}
 
 				@Override
@@ -769,8 +788,8 @@ public class SendMediaActivity extends ThreemaToolbarActivity implements
 		return super.onOptionsItemSelected(item);
 	}
 
-	private void flip(MediaItem item) {
-		int currentFlip = sendMediaAdapter.getItem(bigImagePos).getFlip();
+	private void flip(@NonNull MediaItem item) {
+		int currentFlip = item.getFlip();
 
 		if (item.getRotation() == 90 || item.getRotation() == 270) {
 			if ((currentFlip & FLIP_VERTICAL) == FLIP_VERTICAL) {
@@ -787,7 +806,7 @@ public class SendMediaActivity extends ThreemaToolbarActivity implements
 				currentFlip |= FLIP_HORIZONTAL;
 			}
 		}
-		sendMediaAdapter.getItem(bigImagePos).setFlip(currentFlip);
+		item.setFlip(currentFlip);
 	}
 
 	@SuppressLint("StaticFieldLeak")
@@ -936,12 +955,16 @@ public class SendMediaActivity extends ThreemaToolbarActivity implements
 				case CropImageActivity.REQUEST_CROP:
 				case ThreemaActivity.ACTIVITY_ID_PAINT:
 					backgroundLayout.post(() -> {
-						sendMediaAdapter.getItem(bigImagePos).setUri(Uri.fromFile(cropFile));
-						sendMediaAdapter.getItem(bigImagePos).setRotation(0);
-						sendMediaAdapter.getItem(bigImagePos).setExifRotation(0);
-						sendMediaAdapter.getItem(bigImagePos).setFlip(BitmapUtil.FLIP_NONE);
-						sendMediaAdapter.getItem(bigImagePos).setExifFlip(BitmapUtil.FLIP_NONE);
-						sendMediaAdapter.update(bigImagePos);
+						MediaItem mediaItem = sendMediaAdapter.getItem(bigImagePos);
+
+						if (mediaItem != null) {
+							mediaItem.setUri(Uri.fromFile(cropFile));
+							mediaItem.setRotation(0);
+							mediaItem.setExifRotation(0);
+							mediaItem.setFlip(BitmapUtil.FLIP_NONE);
+							mediaItem.setExifFlip(BitmapUtil.FLIP_NONE);
+							sendMediaAdapter.update(bigImagePos);
+						}
 					});
 					break;
 				case ThreemaActivity.ACTIVITY_ID_PICK_CAMERA_EXTERNAL:
@@ -1066,8 +1089,8 @@ public class SendMediaActivity extends ThreemaToolbarActivity implements
 		return sendMediaAdapter.size() - 1;
 	}
 
-	private void cropImage() {
-		Uri imageUri = sendMediaAdapter.getItem(bigImagePos).getUri();
+	private void cropImage(@NonNull MediaItem mediaItem) {
+		Uri imageUri = mediaItem.getUri();
 
 		try {
 			cropFile = fileService.createTempFile(".crop", ".png");
@@ -1075,8 +1098,8 @@ public class SendMediaActivity extends ThreemaToolbarActivity implements
 			Intent intent = new Intent(this, CropImageActivity.class);
 			intent.setData(imageUri);
 			intent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(cropFile));
-			intent.putExtra(ThreemaApplication.EXTRA_ORIENTATION, sendMediaAdapter.getItem(bigImagePos).getRotation());
-			intent.putExtra(ThreemaApplication.EXTRA_FLIP, sendMediaAdapter.getItem(bigImagePos).getFlip());
+			intent.putExtra(ThreemaApplication.EXTRA_ORIENTATION, mediaItem.getRotation());
+			intent.putExtra(ThreemaApplication.EXTRA_FLIP, mediaItem.getFlip());
 			intent.putExtra(CropImageActivity.FORCE_DARK_THEME, true);
 
 			startActivityForResult(intent, CropImageActivity.REQUEST_CROP);
@@ -1086,17 +1109,17 @@ public class SendMediaActivity extends ThreemaToolbarActivity implements
 		}
 	}
 
-	private void editImage() {
+	private void editImage(@NonNull MediaItem mediaItem) {
 		try {
 			cropFile = fileService.createTempFile(".edit", ".png");
 
 			Intent intent = new Intent(this, ImagePaintActivity.class);
-			intent.putExtra(Intent.EXTRA_STREAM, sendMediaAdapter.getItem(bigImagePos));
+			intent.putExtra(Intent.EXTRA_STREAM, mediaItem);
 			intent.putExtra(ThreemaApplication.EXTRA_OUTPUT_FILE, Uri.fromFile(cropFile));
-			intent.putExtra(ThreemaApplication.EXTRA_ORIENTATION, sendMediaAdapter.getItem(bigImagePos).getRotation());
-			intent.putExtra(ThreemaApplication.EXTRA_FLIP, sendMediaAdapter.getItem(bigImagePos).getFlip());
-			intent.putExtra(ThreemaApplication.EXTRA_EXIF_ORIENTATION, sendMediaAdapter.getItem(bigImagePos).getExifRotation());
-			intent.putExtra(ThreemaApplication.EXTRA_EXIF_FLIP, sendMediaAdapter.getItem(bigImagePos).getExifFlip());
+			intent.putExtra(ThreemaApplication.EXTRA_ORIENTATION, mediaItem.getRotation());
+			intent.putExtra(ThreemaApplication.EXTRA_FLIP, mediaItem.getFlip());
+			intent.putExtra(ThreemaApplication.EXTRA_EXIF_ORIENTATION, mediaItem.getExifRotation());
+			intent.putExtra(ThreemaApplication.EXTRA_EXIF_FLIP,mediaItem.getExifFlip());
 
 			startActivityForResult(intent, ThreemaActivity.ACTIVITY_ID_PAINT);
 			overridePendingTransition(0, R.anim.slow_fade_out);
@@ -1122,9 +1145,10 @@ public class SendMediaActivity extends ThreemaToolbarActivity implements
 			this.cameraButton.setVisibility(sendMediaAdapter.size() < MAX_EDITABLE_IMAGES ? View.VISIBLE : View.GONE);
 		}
 
-		if (sendMediaAdapter.size() > 0) {
-			boolean canEdit = sendMediaAdapter.getItem(bigImagePos).getType() == TYPE_IMAGE || sendMediaAdapter.getItem(bigImagePos).getType() == TYPE_IMAGE_CAM;
-			boolean canSettings = sendMediaAdapter.getItem(bigImagePos).getType() == TYPE_IMAGE;
+		MediaItem mediaItem = sendMediaAdapter.getItem(bigImagePos);
+		if (sendMediaAdapter.size() > 0 && mediaItem != null) {
+			boolean canEdit = mediaItem.getType() == TYPE_IMAGE ||mediaItem.getType() == TYPE_IMAGE_CAM;
+			boolean canSettings = mediaItem.getType() == TYPE_IMAGE;
 
 			getToolbar().getMenu().setGroupVisible(R.id.group_tools, canEdit);
 
@@ -1155,8 +1179,8 @@ public class SendMediaActivity extends ThreemaToolbarActivity implements
 			return;
 		}
 
-		MediaItem item = sendMediaAdapter.getItem(position);
-		if (item == null) {
+		MediaItem mediaItem = sendMediaAdapter.getItem(position);
+		if (mediaItem == null) {
 			logger.debug("Item is null");
 			return;
 		}
@@ -1165,17 +1189,17 @@ public class SendMediaActivity extends ThreemaToolbarActivity implements
 
 		updateMenu();
 
-		if (item.getType() == MediaItem.TYPE_VIDEO || item.getType() == MediaItem.TYPE_VIDEO_CAM) {
-			showBigVideo(item);
+		if (mediaItem.getType() == MediaItem.TYPE_VIDEO || mediaItem.getType() == MediaItem.TYPE_VIDEO_CAM) {
+			showBigVideo(mediaItem);
 		}
 		else {
 			this.videoEditView.setVisibility(View.GONE);
 
-			if (item.getType() == TYPE_GIF) {
+			if (mediaItem.getType() == TYPE_GIF) {
 				bigProgressBar.setVisibility(View.GONE);
 				bigImageView.setVisibility(View.GONE);
 				try {
-					bigGifImageView.setImageURI(item.getUri());
+					bigGifImageView.setImageURI(mediaItem.getUri());
 					bigGifImageView.setVisibility(View.VISIBLE);
 				} catch (Exception e) {
 					// may crash with a SecurityException on some exotic devices
@@ -1183,15 +1207,15 @@ public class SendMediaActivity extends ThreemaToolbarActivity implements
 				}
 			} else {
 				BitmapWorkerTaskParams bitmapParams = new BitmapWorkerTaskParams();
-				bitmapParams.imageUri = item.getUri();
+				bitmapParams.imageUri = mediaItem.getUri();
 				bitmapParams.width = parentWidth;
 				bitmapParams.height = parentHeight;
 				bitmapParams.contentResolver = getContentResolver();
 				bitmapParams.mutable = false;
-				bitmapParams.flip = item.getFlip();
-				bitmapParams.orientation = item.getRotation();
-				bitmapParams.exifFlip = item.getExifFlip();
-				bitmapParams.exifOrientation = item.getExifRotation();
+				bitmapParams.flip = mediaItem.getFlip();
+				bitmapParams.orientation = mediaItem.getRotation();
+				bitmapParams.exifFlip = mediaItem.getExifFlip();
+				bitmapParams.exifOrientation = mediaItem.getExifRotation();
 
 				logger.debug("showBigImage uri: " + bitmapParams.imageUri);
 
@@ -1219,7 +1243,7 @@ public class SendMediaActivity extends ThreemaToolbarActivity implements
 		selectImage(bigImagePos);
 		updateMenu();
 
-		String caption = item.getCaption();
+		String caption = mediaItem.getCaption();
 		captionEditText.setText(null);
 
 		if (!TestUtil.empty(caption)) {
@@ -1252,9 +1276,10 @@ public class SendMediaActivity extends ThreemaToolbarActivity implements
 	private boolean isDuplicate(List<MediaItem> list, Uri uri) {
 		// do not allow the same image twice
 		for (int j = 0; j < list.size(); j++) {
+			Uri originalUri = list.get(j).getOriginalUri();
 			if (list.get(j).getUri().equals(uri) ||
-				(list.get(j).getOriginalUri() != null &&
-					list.get(j).getOriginalUri().equals(uri))) {
+				(originalUri != null &&
+					originalUri.equals(uri))) {
 				Snackbar.make((View) recyclerView.getParent(), getString(R.string.image_already_added), BaseTransientBottomBar.LENGTH_LONG).show();
 				return true;
 			}

+ 3 - 0
app/src/main/java/ch/threema/app/activities/wizard/WizardBackupRestoreActivity.java

@@ -32,6 +32,7 @@ import android.text.Html;
 import android.text.method.LinkMovementMethod;
 import android.view.View;
 import android.widget.TextView;
+import android.widget.Toast;
 
 import org.slf4j.Logger;
 
@@ -184,6 +185,8 @@ public class WizardBackupRestoreActivity extends ThreemaAppCompatActivity implem
 					if (file != null) {
 						restoreBackupFile(file);
 						file.deleteOnExit();
+					} else {
+						Toast.makeText(this, "Unable to access/copy selected file to temporary directory", Toast.LENGTH_LONG).show();
 					}
 				});
 			}).start();

+ 7 - 6
app/src/main/java/ch/threema/app/adapters/ComposeMessageAdapter.java

@@ -35,17 +35,18 @@ import android.widget.ArrayAdapter;
 import android.widget.Filter;
 import android.widget.ListView;
 
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.util.List;
-import java.util.Map;
-
 import androidx.annotation.IntDef;
 import androidx.annotation.LayoutRes;
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.annotation.UiThread;
 import androidx.fragment.app.Fragment;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.List;
+import java.util.Map;
+
 import ch.threema.app.R;
 import ch.threema.app.adapters.decorators.AnimGifChatAdapterDecorator;
 import ch.threema.app.adapters.decorators.AudioChatAdapterDecorator;
@@ -671,7 +672,7 @@ public class ComposeMessageAdapter extends ArrayAdapter<AbstractMessageModel> {
 			resultMapIndex = 0;
 			searchUpdate();
 
-			if (constraint == null || constraint.length() == 0) {
+			if (constraint == null || constraint.length() < 2) {
 				// no filtering
 				filterString = null;
 			} else {

+ 5 - 1
app/src/main/java/ch/threema/app/adapters/SendMediaAdapter.kt

@@ -183,7 +183,11 @@ class SendMediaAdapter(
     }
 
     fun getItem(index: Int): MediaItem? {
-        return items[index]
+        if (items.size > 0 && index < items.size) {
+            return items[index]
+        }
+        logger.info("Index out of range {} of {}", index, items.size)
+        return null
     }
 
     fun size(): Int {

+ 5 - 0
app/src/main/java/ch/threema/app/archive/ArchiveActivity.java

@@ -253,6 +253,11 @@ public class ArchiveActivity extends ThreemaToolbarActivity implements GenericAl
 					return true;
 				case R.id.menu_select_all:
 					archiveAdapter.selectAll();
+					if (archiveAdapter.getCheckedItemsCount() > 0) {
+						actionMode.invalidate();
+					} else {
+						actionMode.finish();
+					}
 					return true;
 				default:
 					return false;

+ 2 - 2
app/src/main/java/ch/threema/app/camera/CameraFragment.kt

@@ -343,7 +343,7 @@ class CameraFragment : Fragment() {
                 } else {
                     Toast.makeText(context, R.string.no_camera_installed, Toast.LENGTH_SHORT).show()
                     logger.info("Back and front camera are unavailable")
-                    requireActivity().finish()
+                    activity?.finish()
                 }
             }
 
@@ -357,7 +357,7 @@ class CameraFragment : Fragment() {
                 updateFlashButton()
             } else {
                 logger.info("Unable to bind camera use cases")
-                requireActivity().finish()
+                activity?.finish()
             }
         }, ContextCompat.getMainExecutor(requireContext()))
     }

+ 18 - 4
app/src/main/java/ch/threema/app/camera/VideoEditView.java

@@ -185,6 +185,16 @@ public class VideoEditView extends FrameLayout implements DefaultLifecycleObserv
 				Player.Listener.super.onPlaybackStateChanged(playbackState);
 				updateProgressBar();
 			}
+
+			@Override
+			public void onPositionDiscontinuity(Player.PositionInfo oldPosition, Player.PositionInfo newPosition, int reason) {
+				Player.Listener.super.onPositionDiscontinuity(oldPosition, newPosition, reason);
+				// tapping on the play button after moving end time should start playback from beginning
+				if (oldPosition.positionMs == 0 && newPosition.positionMs == videoItem.getEndTimeMs()) {
+					videoPlayer.seekTo(videoItem.getStartTimeMs());
+					videoPlayer.play();
+				}
+			}
 		});
 
 		this.videoView.setPlayer(videoPlayer);
@@ -358,13 +368,15 @@ public class VideoEditView extends FrameLayout implements DefaultLifecycleObserv
 			case MotionEvent.ACTION_CANCEL:
 			case MotionEvent.ACTION_UP:
 				if (isMoving == MOVING_LEFT || isMoving == MOVING_RIGHT) {
+					boolean previewLastFrame = isMoving == MOVING_RIGHT;
+
 					videoItem.setStartTimeMs(getVideoPositionFromTimelinePosition(offsetLeft));
 					videoItem.setEndTimeMs(getVideoPositionFromTimelinePosition(this.timelineGridLayout.getWidth() - offsetRight));
 
 					isMoving = MOVING_NONE;
 
 					updateStartAndEnd();
-					preparePlayer();
+					preparePlayer(previewLastFrame);
 
 					return true;
 				}
@@ -532,13 +544,12 @@ public class VideoEditView extends FrameLayout implements DefaultLifecycleObserv
 			videoSource = new ProgressiveMediaSource.Factory(dataSourceFactory)
 				.createMediaSource(com.google.android.exoplayer2.MediaItem.fromUri(videoItem.getUri()));
 
-			preparePlayer();
+			preparePlayer(false);
 		}
 	}
 
-	private void preparePlayer() {
+	private void preparePlayer(boolean seekToEnd) {
 		if (videoPlayer != null && videoSource != null) {
-
 			long endPosition = (videoItem.getEndTimeMs() == videoItem.getDurationMs() || videoItem.getEndTimeMs() == 0 || videoItem.getEndTimeMs() == MediaItem.TIME_UNDEFINED) ?
 							TIME_END_OF_SOURCE :
 							videoItem.getEndTimeMs() * 1000;
@@ -554,6 +565,9 @@ public class VideoEditView extends FrameLayout implements DefaultLifecycleObserv
 			}
 			videoPlayer.setPlayWhenReady(false);
 			videoPlayer.prepare(clippingSource);
+			if (seekToEnd) {
+				videoPlayer.seekTo(videoItem.getEndTimeMs());
+			}
 		}
 	}
 

+ 8 - 5
app/src/main/java/ch/threema/app/dialogs/MessageDetailDialog.java

@@ -27,14 +27,15 @@ import android.text.format.Formatter;
 import android.view.View;
 import android.widget.TextView;
 
-import com.google.android.material.dialog.MaterialAlertDialogBuilder;
-
-import java.util.Date;
-
 import androidx.annotation.NonNull;
 import androidx.annotation.StringRes;
 import androidx.appcompat.app.AlertDialog;
 import androidx.appcompat.app.AppCompatDialog;
+
+import com.google.android.material.dialog.MaterialAlertDialogBuilder;
+
+import java.util.Date;
+
 import ch.threema.app.R;
 import ch.threema.app.ThreemaApplication;
 import ch.threema.app.services.MessageService;
@@ -158,7 +159,9 @@ public class MessageDetailDialog extends ThreemaDialogFragment {
 							readDate.setVisibility(View.VISIBLE);
 						}
 
-						if (modifiedAt != null && !(messageState == MessageState.READ && modifiedAt.equals(readAt))) {
+						if (modifiedAt != null &&
+							!(messageState == MessageState.READ && modifiedAt.equals(readAt)) &&
+							!(messageState == MessageState.DELIVERED && modifiedAt.equals(deliveredAt))) {
 							modifiedText.setText(TextUtil.capitalize(getString(stateResource)));
 							modifiedDate.setText(LocaleUtil.formatTimeStampStringAbsolute(getContext(), modifiedAt.getTime()));
 							modifiedText.setVisibility(View.VISIBLE);

+ 2 - 1
app/src/main/java/ch/threema/app/dialogs/PasswordEntryDialog.java

@@ -34,6 +34,7 @@ import android.text.TextWatcher;
 import android.text.method.LinkMovementMethod;
 import android.text.util.Linkify;
 import android.view.View;
+import android.view.WindowManager;
 import android.widget.CompoundButton;
 import android.widget.EditText;
 import android.widget.TextView;
@@ -297,7 +298,7 @@ public class PasswordEntryDialog extends ThreemaDialogFragment implements Generi
 				);
 
 		alertDialog = builder.create();
-
+		alertDialog.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN);
 		return alertDialog;
 	}
 

+ 16 - 16
app/src/main/java/ch/threema/app/dialogs/SelectorDialog.java

@@ -49,7 +49,7 @@ public class SelectorDialog extends ThreemaDialogFragment {
 
 	private static final String BUNDLE_TITLE_EXTRA = "title";
 	private static final String BUNDLE_ITEMS_EXTRA = "items";
-	private static final String BUNDLE_VALUES_EXTRA = "values";
+	private static final String BUNDLE_TAGS_EXTRA = "tags";
 	private static final String BUNDLE_NEGATIVE_EXTRA = "negative";
 	private static final String BUNDLE_LISTENER_EXTRA = "listener";
 
@@ -64,11 +64,11 @@ public class SelectorDialog extends ThreemaDialogFragment {
 		return dialog;
 	}
 
-	public static SelectorDialog newInstance(String title, ArrayList<SelectorDialogItem> items, ArrayList<Integer> values, String negative) {
+	public static SelectorDialog newInstance(String title, ArrayList<SelectorDialogItem> items, ArrayList<Integer> tags, String negative) {
 		SelectorDialog dialog = new SelectorDialog();
 		Bundle args = new Bundle();
 		args.putString(BUNDLE_TITLE_EXTRA, title);
-		args.putIntegerArrayList(BUNDLE_VALUES_EXTRA, values);
+		args.putIntegerArrayList(BUNDLE_TAGS_EXTRA, tags);
 		args.putSerializable(BUNDLE_ITEMS_EXTRA, items);
 		args.putString(BUNDLE_NEGATIVE_EXTRA, negative);
 
@@ -122,17 +122,17 @@ public class SelectorDialog extends ThreemaDialogFragment {
 	@Override
 	public AppCompatDialog onCreateDialog(Bundle savedInstanceState) {
 		Bundle arguments = getArguments();
-		String title = arguments.getString("title");
-		final ArrayList<SelectorDialogItem> items = (ArrayList<SelectorDialogItem>) arguments.getSerializable("items");
-		final ArrayList<Integer> values = arguments.getIntegerArrayList("values");
-		String negative = arguments.getString("negative");
-		SelectorDialogInlineClickListener listener = arguments.getParcelable("listener");
+		String title = arguments.getString(BUNDLE_TITLE_EXTRA);
+		final ArrayList<SelectorDialogItem> items = (ArrayList<SelectorDialogItem>) arguments.getSerializable(BUNDLE_ITEMS_EXTRA);
+		final ArrayList<Integer> tags = arguments.getIntegerArrayList(BUNDLE_TAGS_EXTRA);
+		String negative = arguments.getString(BUNDLE_NEGATIVE_EXTRA);
+		SelectorDialogInlineClickListener listener = arguments.getParcelable(BUNDLE_LISTENER_EXTRA);
 
 		if (listener != null) {
 			inlineCallback = listener;
 		}
 
-		final String tag = this.getTag();
+		final String fragmentTag = this.getTag();
 
 		MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(getActivity(), getTheme());
 		if (title != null) {
@@ -164,17 +164,17 @@ public class SelectorDialog extends ThreemaDialogFragment {
 		builder.setAdapter(adapter, (dialog, which) -> {
 			dialog.dismiss();
 
-			if (values != null && values.size() > 0) {
+			if (tags != null && tags.size() > 0) {
 				if (inlineCallback != null) {
-					inlineCallback.onClick(tag, values.get(which), object);
+					inlineCallback.onClick(fragmentTag, tags.get(which), object);
 				} else {
-					callback.onClick(tag, values.get(which), object);
+					callback.onClick(fragmentTag, tags.get(which), object);
 				}
 			} else {
 				if (inlineCallback != null) {
-					inlineCallback.onClick(tag, which, object);
+					inlineCallback.onClick(fragmentTag, which, object);
 				} else {
-					callback.onClick(tag, which, object);
+					callback.onClick(fragmentTag, which, object);
 				}
 			}
 		});
@@ -183,9 +183,9 @@ public class SelectorDialog extends ThreemaDialogFragment {
 			builder.setNegativeButton(negative, (dialog, which) -> {
 				dialog.dismiss();
 				if (inlineCallback != null) {
-					inlineCallback.onNo(tag);
+					inlineCallback.onNo(fragmentTag);
 				} else {
-					callback.onNo(tag);
+					callback.onNo(fragmentTag);
 				}
 			});
 		}

Tiedoston diff-näkymää rajattu, sillä se on liian suuri
+ 4863 - 4873
app/src/main/java/ch/threema/app/emojis/EmojiParser.java


Tiedoston diff-näkymää rajattu, sillä se on liian suuri
+ 347 - 113
app/src/main/java/ch/threema/app/emojis/EmojiSpritemap.java


+ 34 - 18
app/src/main/java/ch/threema/app/fragments/ContactsSectionFragment.java

@@ -49,16 +49,6 @@ import android.widget.FrameLayout;
 import android.widget.ListView;
 import android.widget.Toast;
 
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.appcompat.widget.SearchView;
-import androidx.core.util.Pair;
-import androidx.core.view.MenuItemCompat;
-import androidx.localbroadcastmanager.content.LocalBroadcastManager;
-import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
-import androidx.work.OneTimeWorkRequest;
-import androidx.work.WorkManager;
-
 import com.google.android.material.chip.Chip;
 import com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton;
 import com.google.android.material.tabs.TabLayout;
@@ -71,6 +61,15 @@ import java.util.Date;
 import java.util.HashSet;
 import java.util.List;
 
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.appcompat.widget.SearchView;
+import androidx.core.util.Pair;
+import androidx.core.view.MenuItemCompat;
+import androidx.localbroadcastmanager.content.LocalBroadcastManager;
+import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
+import androidx.work.OneTimeWorkRequest;
+import androidx.work.WorkManager;
 import ch.threema.app.R;
 import ch.threema.app.ThreemaApplication;
 import ch.threema.app.activities.AddContactActivity;
@@ -156,6 +155,12 @@ public class ContactsSectionFragment
 	private static final int TAB_ALL_CONTACTS = 0;
 	private static final int TAB_WORK_ONLY = 1;
 
+	private static final int SELECTOR_TAG_CHAT = 0;
+	private static final int SELECTOR_TAG_SHOW_CONTACT = 1;
+	private static final int SELECTOR_TAG_REPORT_SPAM = 2;
+	private static final int SELECTOR_TAG_BLOCK = 3;
+	private static final int SELECTOR_TAG_DELETE = 4;
+
 	private ResumePauseHandler resumePauseHandler;
 	private ListView listView;
 	private Chip contactsCounterChip;
@@ -1151,18 +1156,30 @@ public class ContactsSectionFragment
 		String contactName = NameUtil.getDisplayNameOrNickname(contactModel, true);
 
 		ArrayList<SelectorDialogItem> items = new ArrayList<>();
+		ArrayList<Integer> tags = new ArrayList<>();
 
 			items.add(new SelectorDialogItem(getString(R.string.chat_with, contactName), R.drawable.ic_chat_bubble));
+			tags.add(SELECTOR_TAG_CHAT);
+
 			items.add(new SelectorDialogItem(getString(R.string.show_contact), R.drawable.ic_person_outline));
-			items.add(new SelectorDialogItem(getString(R.string.spam_report), R.drawable.ic_outline_report_24));
+			tags.add(SELECTOR_TAG_SHOW_CONTACT);
+
+			if (!ConfigUtils.isOnPremBuild()) {
+				items.add(new SelectorDialogItem(getString(R.string.spam_report), R.drawable.ic_outline_report_24));
+				tags.add(SELECTOR_TAG_REPORT_SPAM);
+			}
+
 			if (serviceManager.getBlackListService().has(contactModel.getIdentity())) {
 				items.add(new SelectorDialogItem(getString(R.string.unblock_contact), R.drawable.ic_block));
 			} else {
 				items.add(new SelectorDialogItem(getString(R.string.block_contact), R.drawable.ic_block));
 			}
+			tags.add(SELECTOR_TAG_BLOCK);
+
 			items.add(new SelectorDialogItem(getString(R.string.delete_contact_action), R.drawable.ic_delete_outline));
+			tags.add(SELECTOR_TAG_DELETE);
 
-			SelectorDialog selectorDialog = SelectorDialog.newInstance(getString(R.string.last_added_contact), items, getString(R.string.cancel));
+			SelectorDialog selectorDialog = SelectorDialog.newInstance(getString(R.string.last_added_contact), items, tags, getString(R.string.cancel));
 			selectorDialog.setData(contactModel);
 			selectorDialog.setTargetFragment(this, 0);
 			selectorDialog.show(getParentFragmentManager(), DIALOG_TAG_RECENTLY_ADDED_SELECTOR);
@@ -1343,24 +1360,23 @@ public class ContactsSectionFragment
 		ContactModel contactModel = (ContactModel) data;
 
 		switch (which) {
-			case 0:
+			case SELECTOR_TAG_CHAT:
 				openConversationForIdentity(null, contactModel.getIdentity());
 				break;
-			case 1:
+			case SELECTOR_TAG_SHOW_CONTACT:
 				openContact(null, contactModel.getIdentity());
 				break;
-			case 2:
+			case SELECTOR_TAG_REPORT_SPAM:
 				TextWithCheckboxDialog sdialog = TextWithCheckboxDialog.newInstance(requireContext().getString(R.string.spam_report_dialog_title, NameUtil.getDisplayNameOrNickname(contactModel, true)), R.string.spam_report_dialog_explain,
 					R.string.spam_report_dialog_block_checkbox, R.string.spam_report_short, R.string.cancel);
 				sdialog.setData(contactModel);
 				sdialog.setTargetFragment(this, 0);
 				sdialog.show(getParentFragmentManager(), "");
-
 				break;
-			case 3:
+			case SELECTOR_TAG_BLOCK:
 				serviceManager.getBlackListService().toggle(getActivity(), contactModel);
 				break;
-			case 4:
+			case SELECTOR_TAG_DELETE:
 				GenericAlertDialog dialog = GenericAlertDialog.newInstance(R.string.delete_contact_action, R.string.really_delete_contact, R.string.delete, R.string.cancel);
 				dialog.setData(contactModel);
 				dialog.setTargetFragment(this, 0);

+ 5 - 4
app/src/main/java/ch/threema/app/fragments/mediaviews/VideoViewFragment.java

@@ -32,6 +32,9 @@ import android.widget.ImageView;
 import android.widget.ProgressBar;
 import android.widget.Toast;
 
+import androidx.annotation.UiThread;
+import androidx.core.view.ViewCompat;
+
 import com.google.android.exoplayer2.ExoPlayer;
 import com.google.android.exoplayer2.MediaItem;
 import com.google.android.exoplayer2.PlaybackException;
@@ -48,8 +51,6 @@ import org.slf4j.Logger;
 import java.io.File;
 import java.lang.ref.WeakReference;
 
-import androidx.annotation.UiThread;
-import androidx.core.view.ViewCompat;
 import ch.threema.app.R;
 import ch.threema.app.activities.MediaViewerActivity;
 import ch.threema.app.ui.ZoomableExoPlayerView;
@@ -245,8 +246,8 @@ public class VideoViewFragment extends AudioFocusSupportingMediaViewFragment imp
 	}
 
 	@Override
-	public void onPlayerStateChanged(boolean playWhenReady, int playbackState) {
-		logger.debug("onPlayerStateChanged = " + playbackState);
+	public void onPlaybackStateChanged(int playbackState) {
+		logger.debug("onPlaybackStateChanged = " + playbackState);
 
 		if (isPreparing && playbackState == Player.STATE_READY) {
 			isPreparing = false;

+ 5 - 1
app/src/main/java/ch/threema/app/jobs/ReConnectJobService.java

@@ -55,7 +55,11 @@ public class ReConnectJobService extends JobService {
 					boolean success = pollingHelper.poll(true) || (ThreemaApplication.getMasterKey() != null && ThreemaApplication.getMasterKey().isLocked());
 
 					if (!isStopped) {
-						jobFinished(jobParameters, !success);
+						try {
+							jobFinished(jobParameters, !success);
+						} catch (Exception e) {
+							logger.error("Exception while finishing ReConnectJob", e);
+						}
 					}
 				}
 			}

+ 27 - 31
app/src/main/java/ch/threema/app/mediaattacher/MediaSelectionBaseActivity.java

@@ -49,20 +49,6 @@ import android.widget.ImageView;
 import android.widget.LinearLayout;
 import android.widget.TextView;
 
-import com.google.android.material.appbar.AppBarLayout;
-import com.google.android.material.appbar.MaterialToolbar;
-import com.google.android.material.bottomsheet.BottomSheetBehavior;
-import com.google.android.material.button.MaterialButton;
-
-import org.slf4j.Logger;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-import java.util.Map;
-import java.util.Objects;
-import java.util.TreeMap;
-
 import androidx.annotation.ColorInt;
 import androidx.annotation.NonNull;
 import androidx.annotation.UiThread;
@@ -79,6 +65,21 @@ import androidx.lifecycle.ViewModelProvider;
 import androidx.recyclerview.widget.GridLayoutManager;
 import androidx.recyclerview.widget.RecyclerView;
 import androidx.viewpager2.widget.ViewPager2;
+
+import com.google.android.material.appbar.AppBarLayout;
+import com.google.android.material.appbar.MaterialToolbar;
+import com.google.android.material.bottomsheet.BottomSheetBehavior;
+import com.google.android.material.button.MaterialButton;
+
+import org.slf4j.Logger;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.TreeMap;
+
 import ch.threema.app.R;
 import ch.threema.app.ThreemaApplication;
 import ch.threema.app.activities.EnterSerialActivity;
@@ -666,27 +667,22 @@ abstract public class MediaSelectionBaseActivity extends ThreemaActivity impleme
 
 				logger.debug("*** setStatusBarColor");
 
-				toolbar.postDelayed(new Runnable() {
-					@Override
-					public void run() {
-						savedStatusBarColor = getWindow().getStatusBarColor();
-						getWindow().setStatusBarColor(getResources().getColor(R.color.gallery_background));
-						if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
-							getWindow().setNavigationBarColor(getResources().getColor(R.color.gallery_background));
-						}
-						if (ConfigUtils.getAppTheme(MediaSelectionBaseActivity.this) != ConfigUtils.THEME_DARK) {
-							if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
-								getWindow().getDecorView().setSystemUiVisibility(getWindow().getDecorView().getSystemUiVisibility() & ~View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR);
-							}
+				toolbar.postDelayed(() -> {
+					savedStatusBarColor = getWindow().getStatusBarColor();
+					getWindow().setStatusBarColor(getResources().getColor(R.color.gallery_background));
+					if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+						getWindow().setNavigationBarColor(getResources().getColor(R.color.gallery_background));
+					}
+					if (ConfigUtils.getAppTheme(MediaSelectionBaseActivity.this) != ConfigUtils.THEME_DARK) {
+						if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+							getWindow().getDecorView().setSystemUiVisibility(getWindow().getDecorView().getSystemUiVisibility() & ~View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR);
 						}
 					}
 				}, delay);
 
-				previewPager.post(new Runnable() {
-					@Override
-					public void run() {
-						previewPager.setCurrentItem(position, false);
-					}
+				previewPager.post(() -> {
+					previewPager.setCurrentItem(position, false);
+					updatePreviewInfo(position);
 				});
 				isPreviewMode = true;
 			}

+ 2 - 2
app/src/main/java/ch/threema/app/processors/MessageProcessor.java

@@ -236,8 +236,8 @@ public class MessageProcessor implements MessageProcessorInterface {
 				return ProcessIncomingResult.ignore();
 			}
 
-			/* send delivery receipt (but not for immediate messages or delivery receipts) */
-			if (!msg.isImmediate()) {
+			/* send delivery receipt (but not for non-queued messages or delivery receipts) */
+			if (!msg.flagNoServerQueuing()) {
 				/* throw away messages from hidden contacts if block unknown is enabled - except for group messages */
 				if (
 					this.preferenceService.isBlockUnknown()

+ 20 - 3
app/src/main/java/ch/threema/app/services/MessageServiceImpl.java

@@ -189,6 +189,7 @@ import static ch.threema.app.ui.MediaItem.TYPE_FILE;
 import static ch.threema.app.ui.MediaItem.TYPE_GIF;
 import static ch.threema.app.ui.MediaItem.TYPE_IMAGE;
 import static ch.threema.app.ui.MediaItem.TYPE_IMAGE_CAM;
+import static ch.threema.app.ui.MediaItem.TYPE_LOCATION;
 import static ch.threema.app.ui.MediaItem.TYPE_TEXT;
 import static ch.threema.app.ui.MediaItem.TYPE_VIDEO;
 import static ch.threema.app.ui.MediaItem.TYPE_VIDEO_CAM;
@@ -1347,7 +1348,7 @@ public class MessageServiceImpl implements MessageService {
 			contactService.setIsArchived(message.getFromIdentity(), false);
 
 			//send msgreceived
-			if (!message.isNoDeliveryReceipts()) {
+			if (!message.flagNoDeliveryReceipts()) {
 				DeliveryReceiptMessage receipt = new DeliveryReceiptMessage();
 				receipt.setReceiptType(ProtocolDefines.DELIVERYRECEIPT_MSGRECEIVED);
 				receipt.setReceiptMessageIds(new MessageId[]{message.getMessageId()});
@@ -3615,6 +3616,22 @@ public class MessageServiceImpl implements MessageService {
 					logger.info("Text is empty");
 				}
 				continue;
+			} else if (TYPE_LOCATION == mediaItem.getType()) {
+				Location location = GeoLocationUtil.getLocationFromUri(mediaItem.getUri());
+				if (location != null) {
+					for (MessageReceiver messageReceiver : resolvedReceivers) {
+						try {
+							successfulMessageModel = sendLocation(location, "", messageReceiver, null);
+						} catch (Exception e) {
+							failedCounter++;
+							logger.error("Could not send location message");
+						}
+					}
+				} else {
+					failedCounter++;
+					logger.info("Sending location failed: invalid location");
+				}
+				continue;
 			}
 
 			final Map<MessageReceiver, AbstractMessageModel> messageModels = new HashMap<>();
@@ -4256,6 +4273,7 @@ public class MessageServiceImpl implements MessageService {
 		if (targetBitrate == -1) {
 			// will not fit
 			logger.info("Video file ist too large");
+			RuntimeUtil.runOnUiThread(() -> Toast.makeText(context, context.getString(R.string.file_too_large, MAX_BLOB_SIZE_MB), Toast.LENGTH_SHORT).show());
 			// skip this MediaItem
 			markAsTerminallyFailed(resolvedReceivers, messageModels);
 			return VideoTranscoder.FAILURE;
@@ -4523,7 +4541,6 @@ public class MessageServiceImpl implements MessageService {
 	 * @return true if trimming is required, false otherwise
 	 */
 	private boolean videoNeedsTrimming(MediaItem item) {
-		return (item.getStartTimeMs() != 0 && item.getStartTimeMs() != TIME_UNDEFINED) ||
-			(item.getEndTimeMs() != TIME_UNDEFINED && item.getEndTimeMs() != item.getDurationMs());
+		return item.getDurationMs() != item.getTrimmedDurationMs();
 	}
 }

+ 18 - 17
app/src/main/java/ch/threema/app/services/NotificationActionService.java

@@ -168,27 +168,28 @@ public class NotificationActionService extends IntentService {
 
 	private boolean reply(@NonNull MessageReceiver messageReceiver, @NonNull Intent intent) {
 		Bundle results = RemoteInput.getResultsFromIntent(intent);
+		if (results != null) {
+			String message = null;
+			CharSequence messageCs = results.getCharSequence(ThreemaApplication.EXTRA_VOICE_REPLY);
+			if (messageCs != null) {
+				message = messageCs.toString();
+			}
 
-		String message = null;
-		CharSequence messageCs = results.getCharSequence(ThreemaApplication.EXTRA_VOICE_REPLY);
-		if (messageCs != null) {
-			message = messageCs.toString();
-		}
-
-		if (!TestUtil.empty(message)) {
-			lifetimeService.acquireConnection(TAG);
+			if (!TestUtil.empty(message)) {
+				lifetimeService.acquireConnection(TAG);
 
-			try {
-				messageService.sendText(message, messageReceiver);
-				messageService.markConversationAsRead(messageReceiver, notificationService);
-				notificationService.cancel(messageReceiver);
+				try {
+					messageService.sendText(message, messageReceiver);
+					messageService.markConversationAsRead(messageReceiver, notificationService);
+					notificationService.cancel(messageReceiver);
 
-				showToast(R.string.message_sent);
-				return true;
-			} catch (Exception e) {
-				logger.error("Failed to send message", e);
+					showToast(R.string.message_sent);
+					return true;
+				} catch (Exception e) {
+					logger.error("Failed to send message", e);
+				}
+				lifetimeService.releaseConnectionLinger(TAG, NOTIFICATION_ACTION_CONNECTION_LINGER);
 			}
-			lifetimeService.releaseConnectionLinger(TAG, NOTIFICATION_ACTION_CONNECTION_LINGER);
 		}
 		logger.info("Reply message is empty");
 		return false;

+ 5 - 1
app/src/main/java/ch/threema/app/services/NotificationServiceImpl.java

@@ -410,7 +410,6 @@ public class NotificationServiceImpl implements NotificationService {
 
 	@Override
 	public void setVisibleReceiver(MessageReceiver receiver) {
-
 		if(receiver != null) {
 			//cancel
 			this.cancel(receiver);
@@ -423,6 +422,11 @@ public class NotificationServiceImpl implements NotificationService {
 	public void addConversationNotification(final ConversationNotification conversationNotification, boolean updateExisting) {
 		logger.debug("addConversationNotifications");
 
+		if (ConfigUtils.hasInvalidCredentials()) {
+			logger.debug("Credentials are not (or no longer) valid. Suppressing notification.");
+			return;
+		}
+
 		synchronized (this.conversationNotifications) {
 			//check if current receiver is the receiver of the group
 			if(this.visibleConversationReceiver != null &&

+ 13 - 9
app/src/main/java/ch/threema/app/ui/AvatarEditView.java

@@ -197,19 +197,23 @@ public class AvatarEditView extends FrameLayout implements DefaultLifecycleObser
 			return;
 		}
 
-		if (contactModel != null) {
-			Bitmap bitmap = contactService.getAvatar(contactModel, true);
-			if (isMyProfilePicture) {
-				// If it is "my" profile picture, then the AvatarEditView is round
-				avatarImage.setImageDrawable(AvatarConverterUtil.convertToRound(getContext().getResources(), bitmap));
+		try {
+			if (contactModel != null) {
+				Bitmap bitmap = contactService.getAvatar(contactModel, true);
+				if (isMyProfilePicture) {
+					// If it is "my" profile picture, then the AvatarEditView is round
+					avatarImage.setImageDrawable(AvatarConverterUtil.convertToRound(getContext().getResources(), bitmap));
+				} else {
+					avatarImage.setImageBitmap(bitmap);
+					adjustColorFilter(bitmap);
+				}
 			} else {
+				Bitmap bitmap = groupService.getAvatar(groupModel, true);
 				avatarImage.setImageBitmap(bitmap);
 				adjustColorFilter(bitmap);
 			}
-		} else {
-			Bitmap bitmap = groupService.getAvatar(groupModel, true);
-			avatarImage.setImageBitmap(bitmap);
-			adjustColorFilter(bitmap);
+		} catch (RuntimeException e) {
+			logger.debug("Unable to set avatar bitmap",e );
 		}
 
 		boolean editable = isAvatarEditable();

+ 20 - 2
app/src/main/java/ch/threema/app/ui/DirectoryDataSource.java

@@ -37,6 +37,7 @@ import ch.threema.app.managers.ServiceManager;
 import ch.threema.app.services.PreferenceService;
 import ch.threema.app.stores.IdentityStore;
 import ch.threema.app.utils.RuntimeUtil;
+import ch.threema.app.utils.TestUtil;
 import ch.threema.base.utils.LoggingUtil;
 import ch.threema.domain.protocol.api.APIConnector;
 import ch.threema.domain.protocol.api.work.WorkDirectory;
@@ -46,11 +47,13 @@ import ch.threema.domain.protocol.api.work.WorkDirectoryFilter;
 
 public class DirectoryDataSource extends PageKeyedDataSource<WorkDirectory, WorkDirectoryContact> {
 	private static final Logger logger = LoggingUtil.getThreemaLogger("DirectoryDataSource");
+	public static final int MIN_SEARCH_STRING_LENGTH = 3;
+	public static final String WILDCARD_SEARCH_ALL = "*";
 
 	private PreferenceService preferenceService;
 	private APIConnector apiConnector;
 	private IdentityStore identityStore;
-	private boolean sortByFirstName;
+	private final boolean sortByFirstName;
 	private static String queryText;
 	private static List<WorkDirectoryCategory> queryCategories = new ArrayList<>();
 
@@ -80,7 +83,21 @@ public class DirectoryDataSource extends PageKeyedDataSource<WorkDirectory, Work
 
 	@Override
 	public void loadInitial(@NonNull LoadInitialParams<WorkDirectory> params, @NonNull LoadInitialCallback<WorkDirectory, WorkDirectoryContact> callback) {
-		logger.debug("*** loadInitial");
+		logger.debug("loadInitial");
+
+		if (queryCategories.size() > 0) {
+			if (TestUtil.empty(queryText)) {
+				queryText = WILDCARD_SEARCH_ALL;
+			}
+		} else {
+			if (queryText == null || queryText.length() < MIN_SEARCH_STRING_LENGTH) {
+				// return empty result
+				callback.onResult(new ArrayList<WorkDirectoryContact>(), null, null);
+				return;
+			}
+		}
+
+		logger.debug("Fetching query {} #categories {}", queryText, queryCategories.size());
 
 		fetchInitialData(callback);
 	}
@@ -177,6 +194,7 @@ public class DirectoryDataSource extends PageKeyedDataSource<WorkDirectory, Work
 			@Override
 			protected void onPostExecute(WorkDirectory workDirectory) {
 				if (workDirectory != null) {
+					logger.debug("Fetch results {}", workDirectory.workContacts);
 					callback.onResult(workDirectory.workContacts, workDirectory, workDirectory);
 				}
 			}

+ 14 - 1
app/src/main/java/ch/threema/app/ui/MediaItem.java

@@ -61,7 +61,7 @@ public class MediaItem implements Parcelable {
 	private boolean deleteAfterUse;
 
 	@Retention(RetentionPolicy.SOURCE)
-	@IntDef({TYPE_FILE, TYPE_IMAGE, TYPE_VIDEO, TYPE_IMAGE_CAM, TYPE_VIDEO_CAM, TYPE_GIF, TYPE_VOICEMESSAGE, TYPE_TEXT})
+	@IntDef({TYPE_FILE, TYPE_IMAGE, TYPE_VIDEO, TYPE_IMAGE_CAM, TYPE_VIDEO_CAM, TYPE_GIF, TYPE_VOICEMESSAGE, TYPE_TEXT, TYPE_LOCATION})
 	public @interface MediaType {}
 	public static final int TYPE_FILE = 0;
 	public static final int TYPE_IMAGE = 1;
@@ -71,6 +71,7 @@ public class MediaItem implements Parcelable {
 	public static final int TYPE_GIF = 5;
 	public static final int TYPE_VOICEMESSAGE = 6;
 	public static final int TYPE_TEXT = 7;
+	public static final int TYPE_LOCATION = 8;
 
 	public static final long TIME_UNDEFINED = Long.MIN_VALUE;
 
@@ -216,6 +217,18 @@ public class MediaItem implements Parcelable {
 		return durationMs;
 	}
 
+	/**
+	 * Return duration of media object after a possible trimming has been applied
+	 * @return duration in ms
+	 */
+	public long getTrimmedDurationMs() {
+		return
+			(startTimeMs != 0L && startTimeMs != TIME_UNDEFINED) ||
+			(endTimeMs != TIME_UNDEFINED && endTimeMs != durationMs) ?
+			endTimeMs - startTimeMs :
+			durationMs;
+	}
+
 	public void setDurationMs(long durationMs) {
 		this.durationMs = durationMs;
 	}

+ 4 - 1
app/src/main/java/ch/threema/app/utils/ConfigUtils.java

@@ -656,7 +656,6 @@ public class ConfigUtils {
 			try {
 				LicenseService licenseService = serviceManager.getLicenseService();
 				if (licenseService != null) {
-
 					return isSerialLicensed() && licenseService.hasCredentials() && licenseService.isLicensed();
 				}
 			} catch (FileSystemNotPresentException e) {
@@ -673,6 +672,10 @@ public class ConfigUtils {
 			|| BuildFlavor.getLicenseType().equals(BuildFlavor.LicenseType.ONPREM);
 	}
 
+	public static boolean hasInvalidCredentials() {
+		return (ConfigUtils.isOnPremBuild() || ConfigUtils.isWorkBuild()) && ConfigUtils.isSerialLicensed() && !ConfigUtils.isSerialLicenseValid();
+	}
+
 	/**
 	 * Returns true if privacy settings imply that screenshots and app switcher thumbnails should be disabled
 	 * @param preferenceService

+ 2 - 2
app/src/main/java/ch/threema/app/utils/ConnectionIndicatorUtil.java

@@ -24,13 +24,14 @@ package ch.threema.app.utils;
 import android.content.Context;
 import android.view.View;
 
+import androidx.annotation.ColorInt;
 import androidx.annotation.UiThread;
 import ch.threema.app.R;
 import ch.threema.domain.protocol.csp.connection.ConnectionState;
 
 public class ConnectionIndicatorUtil {
 	private static ConnectionIndicatorUtil ourInstance;
-	private final int red, green, orange, transparent;
+	private final @ColorInt	int red, orange, transparent;
 
 	public static ConnectionIndicatorUtil getInstance() {
 		return ourInstance;
@@ -43,7 +44,6 @@ public class ConnectionIndicatorUtil {
 	private ConnectionIndicatorUtil(Context context) {
 		this.red = context.getResources().getColor(R.color.material_red);
 		this.orange = context.getResources().getColor(R.color.material_orange);
-		this.green = context.getResources().getColor(R.color.material_green);
 		this.transparent = context.getResources().getColor(android.R.color.transparent);
 	}
 

+ 7 - 0
app/src/main/java/ch/threema/app/utils/FileUtil.java

@@ -761,6 +761,13 @@ public class FileUtil {
 				}
 			} catch (Exception e) {
 				logger.error("Unable to query Content Resolver", e);
+				// guess filename from last path segment of content URI. Accept only filenames that contain one "." character
+				String lastPathSegment = uri.getLastPathSegment();
+				if (lastPathSegment != null) {
+					if (lastPathSegment.indexOf(".") == lastPathSegment.lastIndexOf(".")) {
+						filename = lastPathSegment;
+					}
+				}
 			}
 		}
 		return filename;

+ 59 - 11
app/src/main/java/ch/threema/app/utils/GeoLocationUtil.java

@@ -59,9 +59,15 @@ public class GeoLocationUtil {
 	private static final Logger logger = LoggingUtil.getThreemaLogger("GeoLocationUtil");
 
 	private static final String GEO_NUM = "-?\\d+(\\.\\d+)?";
+	private static final String GEO_PARAMS = "(;[\\w\\-]+(=[\\[\\]:&+$\\w\\-.!~*'()%]+)?)*";
+	private static final String GEO_LABEL = "(\\([\\w+%\\-&]*\\))?";
+	private static final String GEO_QUERY = "\\?q=" + GEO_NUM + "," + GEO_NUM;
+	private static final String GEO_ZOOM = "\\?z=\\d+";
+	private static final String GEO_ANDROID = "(" + GEO_QUERY + GEO_LABEL + "|" + GEO_ZOOM + ")?";
 	private static final Pattern GEO_PATTERN = Pattern.compile(
-		"\\bgeo:-?" + GEO_NUM + "," + GEO_NUM + "(," + GEO_NUM + ")?"   // the geo keyword followed by 2 or 3 coordinates
-			+ "(;[\\w\\-]+(=[\\[\\]:&+$\\w\\-.!~*'()%]+)?)*\\b"         // additional parameters, e.g., ';u=12;crs=Moon-2011'
+		"\\bgeo:-?" + GEO_NUM + "," + GEO_NUM + "(," + GEO_NUM + ")?("      // the geo keyword followed by 2 or 3 coordinates
+			+ GEO_PARAMS + "|"                                              // additional parameters, e.g., ';u=12;crs=Moon-2011', or
+			+ GEO_ANDROID + ")?(?![\\?;])(\\b|(?<=\\)))"                    // support Android query geo extension (only for coordinates)
 	);
 
 	private TextView targetView;
@@ -232,8 +238,8 @@ public class GeoLocationUtil {
 	 * correct and also performs some checks on the latitude and longitude. Parameters like the
 	 * uncertainty or altitude are ignored.
 	 */
-	public static boolean isValidGeoUri(@NonNull Uri uri) {
-		return getLocationDataFromGeoUri(uri) != null;
+	public static boolean isValidGeoUri(@Nullable Uri uri) {
+		return uri != null && getLocationDataFromGeoUri(uri) != null;
 	}
 
 	/**
@@ -277,25 +283,47 @@ public class GeoLocationUtil {
 	 * @return the location data or null if the uri could not be parsed or contained invalid values
 	 */
 	@Nullable
-	private static LocationDataModel getLocationDataFromGeoUri(@NonNull Uri uri) {
+	public static LocationDataModel getLocationDataFromGeoUri(@NonNull Uri uri) {
 		String geoUri = uri.toString();
 		if (!GEO_PATTERN.matcher(geoUri).matches()) {
 			return null;
 		}
 
-		int separator = geoUri.indexOf(',');
+		int latitudeStart;
 		int longitudeEnd;
-		for (longitudeEnd = separator + 1; longitudeEnd < geoUri.length(); longitudeEnd++) {
-			if (geoUri.charAt(longitudeEnd) == ',' || geoUri.charAt(longitudeEnd) == ';') {
-				break;
-			}
+		int separator;
+
+		latitudeStart = geoUri.indexOf("?q=");
+		if (latitudeStart == -1) {
+			// There is no query attribute therefore we take the lat/long data at the beginning
+			latitudeStart = 4;
+		} else {
+			// There is a query attribute so that latitude value starts 3 characters later than '?q='
+			latitudeStart += 3;
+		}
+		// Find position of separator
+		separator = latitudeStart + 1;
+		while (geoUri.charAt(separator) != ',') {
+			separator++;
+		}
+		// Find position where longitude ends (either '(' because a label follows, ',' because an
+		// altitude follows, ';' because an RFC5870 parameter follows or '?' because an android geo
+		// uri can contain a zoom level starting with "?z=")
+		longitudeEnd = separator + 1;
+		while (longitudeEnd < geoUri.length() &&
+			geoUri.charAt(longitudeEnd) != '(' &&
+			geoUri.charAt(longitudeEnd) != ',' &&
+			geoUri.charAt(longitudeEnd) != ';' &&
+			geoUri.charAt(longitudeEnd) != '?'
+		) {
+			longitudeEnd++;
 		}
 
 		double latitude;
 		double longitude;
 
 		try {
-			latitude = Double.parseDouble(geoUri.substring(4, separator));
+			latitude = Double.parseDouble(geoUri.substring(latitudeStart, separator));
 			longitude = Double.parseDouble(geoUri.substring(separator + 1, longitudeEnd));
 		} catch (NumberFormatException nfe) {
 			// Illegal number as latitude or longitude
@@ -315,4 +343,24 @@ public class GeoLocationUtil {
 		return new LocationDataModel(latitude, longitude, 0, "", "");
 	}
 
+	/**
+	 * Get the location from a geo uri.
+	 *
+	 * @param uri the uri where the necessary geo information is extracted
+	 * @return the location data or null if the uri could not be parsed or contained invalid values
+	 */
+	@Nullable
+	public static Location getLocationFromUri(@NonNull Uri uri) {
+		LocationDataModel locationData = getLocationDataFromGeoUri(uri);
+		if (locationData == null) {
+			return null;
+		}
+
+		Location location = new Location("");
+		location.setLatitude(locationData.getLatitude());
+		location.setLongitude(locationData.getLongitude());
+		location.setAccuracy(locationData.getAccuracy());
+		return location;
+	}
+
 }

+ 3 - 3
app/src/main/java/ch/threema/app/utils/MediaPlayerStateWrapper.java

@@ -46,9 +46,9 @@ import ch.threema.base.utils.LoggingUtil;
 public class MediaPlayerStateWrapper {
 	private static final Logger logger = LoggingUtil.getThreemaLogger("MediaPlayerStateWrapper");
 
-	private MediaPlayer mediaPlayer;
+	private final MediaPlayer mediaPlayer;
 	private State currentState;
-	private MediaPlayerStateWrapper stateWrapper;
+	private final MediaPlayerStateWrapper stateWrapper;
 	private StateListener stateListener;
 
 	public MediaPlayerStateWrapper() {
@@ -250,7 +250,7 @@ public class MediaPlayerStateWrapper {
 
 	/* OTHER STUFF */
 	public int getCurrentPosition() {
-		if (currentState != State.ERROR) {
+		if (currentState != State.ERROR && currentState != State.IDLE) {
 			return mediaPlayer.getCurrentPosition();
 		} else {
 			return 0;

+ 2 - 2
app/src/main/java/ch/threema/app/utils/MimeUtil.java

@@ -37,13 +37,12 @@ import ch.threema.storage.models.AbstractMessageModel;
 import ch.threema.storage.models.data.MessageContentsType;
 import ch.threema.storage.models.data.media.FileDataModel;
 
-import static ch.threema.domain.protocol.csp.messages.file.FileData.RENDERING_MEDIA;
 import static ch.threema.app.ui.MediaItem.TYPE_FILE;
 import static ch.threema.app.ui.MediaItem.TYPE_GIF;
 import static ch.threema.app.ui.MediaItem.TYPE_IMAGE;
 import static ch.threema.app.ui.MediaItem.TYPE_VIDEO;
 import static ch.threema.app.ui.MediaItem.TYPE_VOICEMESSAGE;
-
+import static ch.threema.domain.protocol.csp.messages.file.FileData.RENDERING_MEDIA;
 import static java.lang.annotation.RetentionPolicy.SOURCE;
 
 public class MimeUtil {
@@ -73,6 +72,7 @@ public class MimeUtil {
 	public static final String MIME_TYPE_TEXT = "text/plain";
 	public static final String MIME_TYPE_HTML = "text/html";
 	public static final String MIME_TYPE_DEFAULT = "application/octet-stream";
+	public static final String MIME_TYPE_EMAIL= "message/rfc822";
 
 	public static final String MIME_VIDEO = "video/";
 	public static final String MIME_AUDIO = "audio/";

+ 1 - 1
app/src/main/java/ch/threema/app/video/transcoder/VideoConfig.java

@@ -195,7 +195,7 @@ public class VideoConfig {
 
 		int srcVideoTrack = findTrack(extractor, MIME_VIDEO);
 		if (srcVideoTrack >= 0) {
-			float durationS = (float) mediaItem.getDurationMs() / (float) DateUtils.SECOND_IN_MILLIS;
+			float durationS = (float) mediaItem.getTrimmedDurationMs() / (float) DateUtils.SECOND_IN_MILLIS;
 			int calculatedFileSize = ((int) (durationS * originalBitrate / 8)) + calculatedAudioSize + FILE_OVERHEAD;
 			if (calculatedFileSize > MAX_BLOB_SIZE) {
 				calculatedFileSize = ((int) (durationS * BITRATE_MEDIUM / 8)) + calculatedAudioSize + FILE_OVERHEAD;

+ 15 - 13
app/src/main/java/ch/threema/app/voip/activities/CallActivity.java

@@ -2168,21 +2168,23 @@ public class CallActivity extends ThreemaActivity implements
 			aspectRatio = new Rational(videoContext.getFrameHeight(), videoContext.getFrameWidth());
 		}
 
-		launchBounds = new Rect(this.commonViews.backgroundView.getLeft(),
-			this.commonViews.backgroundView.getTop(),
-			this.commonViews.backgroundView.getRight(),
-			this.commonViews.backgroundView.getBottom());
+		if (this.commonViews != null) {
+			launchBounds = new Rect(this.commonViews.backgroundView.getLeft(),
+				this.commonViews.backgroundView.getTop(),
+				this.commonViews.backgroundView.getRight(),
+				this.commonViews.backgroundView.getBottom());
 
-		PictureInPictureParams pipParams = new PictureInPictureParams.Builder()
-			.setAspectRatio(aspectRatio)
-			.setSourceRectHint(launchBounds)
-			.build();
+			PictureInPictureParams pipParams = new PictureInPictureParams.Builder()
+				.setAspectRatio(aspectRatio)
+				.setSourceRectHint(launchBounds)
+				.build();
 
-		try {
-			enterPictureInPictureMode(pipParams);
-		} catch (IllegalArgumentException e) {
-			logger.error("Unable to enter PIP mode", e);
-			unhideNavigation(false);
+			try {
+				enterPictureInPictureMode(pipParams);
+			} catch (IllegalArgumentException e) {
+				logger.error("Unable to enter PIP mode", e);
+				unhideNavigation(false);
+			}
 		}
 	}
 

+ 4 - 0
app/src/main/java/ch/threema/app/voip/services/VoipStateService.java

@@ -567,7 +567,11 @@ public class VoipStateService implements AudioManager.OnAudioFocusChangeListener
 			// Called outside working hours
 			logCallInfo(callId, "Rejecting call from {} (called outside of working hours)", contact.getIdentity());
 			rejectReason = VoipCallAnswerData.RejectReason.OFF_HOURS;
+		} else if (ConfigUtils.hasInvalidCredentials()) {
+			logCallInfo(callId, "Rejecting call from {} (credentials have been revoked)", contact.getIdentity());
+			rejectReason = VoipCallAnswerData.RejectReason.UNKNOWN;
 		}
+
 		if (rejectReason != null) {
 			try {
 				this.sendRejectCallAnswerMessage(contact, callId, rejectReason, !silentReject);

+ 1 - 1
app/src/main/java/ch/threema/app/webclient/services/instance/state/SessionConnectionContext.java

@@ -115,7 +115,7 @@ class SessionConnectionContext {
 	SessionConnectionContext(
 		@NonNull final SessionContext ctx,
 		@NonNull final SaltyRTCBuilder builder
-	) throws NoSuchAlgorithmException, InvalidKeyException {
+	) throws NoSuchAlgorithmException, InvalidKeyException, IllegalArgumentException {
 		// Create SSL socket factory
 		final SSLSocketFactory sslSocketFactory = ConfigUtils.getSSLSocketFactory(ctx.model.getSaltyRtcHost());
 

+ 4 - 5
app/src/main/java/ch/threema/app/webclient/services/instance/state/SessionStateConnecting.java

@@ -21,16 +21,15 @@
 
 package ch.threema.app.webclient.services.instance.state;
 
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.annotation.WorkerThread;
-
 import org.saltyrtc.client.SaltyRTCBuilder;
 import org.saltyrtc.client.exceptions.ConnectionException;
 import org.saltyrtc.client.exceptions.InvalidKeyException;
 
 import java.security.NoSuchAlgorithmException;
 
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.WorkerThread;
 import ch.threema.app.webclient.services.instance.DisconnectContext;
 import ch.threema.app.webclient.state.WebClientSessionState;
 
@@ -60,7 +59,7 @@ final class SessionStateConnecting extends SessionState {
 		// Create session connection context
 		try {
 			this.cctx = new SessionConnectionContext(ctx, builder);
-		} catch (NoSuchAlgorithmException | InvalidKeyException error) {
+		} catch (NoSuchAlgorithmException | InvalidKeyException | IllegalArgumentException error) {
 			logger.error("Cannot create session connection context:", error);
 			throw new InvalidStateTransition(error.getMessage());
 		}

+ 107 - 67
app/src/main/res/layout/activity_directory.xml

@@ -3,67 +3,97 @@
   ~ All rights reserved.
   -->
 
-<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
+<androidx.coordinatorlayout.widget.CoordinatorLayout
+	xmlns:android="http://schemas.android.com/apk/res/android"
 	xmlns:app="http://schemas.android.com/apk/res-auto"
 	android:id="@+id/parent_layout"
 	android:layout_width="match_parent"
-	android:layout_height="match_parent">
+	android:layout_height="match_parent"
+	android:background="?attr/background_primary">
 
-	<include layout="@layout/toolbar_view" />
-
-	<androidx.constraintlayout.widget.ConstraintLayout
+	<LinearLayout
+		android:id="@+id/initial_layout"
 		android:layout_width="match_parent"
-		android:layout_height="@dimen/directory_search_bar_height"
-		android:layout_gravity="top"
-		android:paddingTop="8dp"
-		android:paddingBottom="4dp"
-		android:paddingLeft="16dp"
-		android:paddingRight="8dp"
-		android:background="?compose_container"
+		android:layout_height="match_parent"
+		android:orientation="vertical"
 		app:layout_behavior="@string/appbar_scrolling_view_behavior">
 
-		<ch.threema.app.ui.ThreemaSearchView
-			android:id="@+id/search"
-			android:layout_width="0dp"
-			android:layout_height="wrap_content"
-			android:layout_marginRight="8dp"
-			android:layout_gravity="center_vertical"
-			android:background="?compose_edittext_bubble"
-			android:textColor="?android:textColorPrimary"
-			app:iconifiedByDefault="false"
-			app:defaultQueryHint="@string/directory_search"
-			app:layout_constraintHorizontal_chainStyle="spread_inside"
-			app:layout_constraintHorizontal_weight="1"
-			app:layout_constraintTop_toTopOf="parent"
-			app:layout_constraintLeft_toLeftOf="parent"
-			app:layout_constraintBottom_toBottomOf="parent"
-			app:layout_constraintRight_toLeftOf="@+id/category_selector_button"/>
-
-		<androidx.appcompat.widget.AppCompatImageButton
-			style="?android:attr/borderlessButtonStyle"
-			android:id="@+id/category_selector_button"
+		<androidx.constraintlayout.widget.ConstraintLayout
+			android:id="@+id/search_container"
+			android:layout_width="match_parent"
+			android:layout_height="@dimen/directory_search_bar_height"
+			android:layout_gravity="top"
+			android:layout_marginTop="32dp"
+			android:layout_marginBottom="32dp"
+			android:layout_marginLeft="16dp"
+			android:layout_marginRight="16dp"
+			android:background="?attr/compose_edittext_bubble"
+			android:clickable="true"
+			android:focusable="true"
+			android:paddingBottom="16dp"
+			android:paddingLeft="16dp"
+			android:paddingRight="16dp"
+			android:paddingTop="16dp" >
+
+			<ImageView
+				android:id="@+id/search_image"
+				android:layout_width="24dp"
+				android:layout_height="24dp"
+				android:clickable="false"
+				android:focusable="false"
+				android:src="@drawable/ic_search_outline"
+				app:layout_constraintBottom_toBottomOf="parent"
+				app:layout_constraintLeft_toLeftOf="parent"
+				app:layout_constraintTop_toTopOf="parent"
+				app:tint="?attr/textColorPrimary" />
+
+			<TextView
+				android:id="@+id/search_text"
+				android:layout_width="0dp"
+				android:layout_height="wrap_content"
+				android:layout_marginLeft="16dp"
+				android:layout_marginBottom="4dp"
+				android:clickable="false"
+				android:focusable="false"
+				android:text="@string/directory_search"
+				android:textColor="?attr/textColorSecondary"
+				android:textSize="20dp"
+				app:layout_constraintBottom_toBottomOf="parent"
+				app:layout_constraintLeft_toRightOf="@id/search_image"
+				app:layout_constraintRight_toRightOf="parent"
+				app:layout_constraintTop_toTopOf="parent" />
+
+		</androidx.constraintlayout.widget.ConstraintLayout>
+
+		<ImageView
 			android:layout_width="48dp"
 			android:layout_height="48dp"
-			android:layout_gravity="center_vertical|right"
-			android:background="?android:selectableItemBackground"
-			android:tint="?attr/textColorSecondary"
-			app:srcCompat="@drawable/ic_filter_list_black_24dp"
-			android:contentDescription="@string/work_select_categories"
-			app:layout_constraintTop_toTopOf="parent"
-			app:layout_constraintLeft_toRightOf="@id/search"
-			app:layout_constraintBottom_toBottomOf="parent"
-			app:layout_constraintRight_toRightOf="parent"
-			app:layout_goneMarginLeft="0dp"
-			app:layout_goneMarginRight="0dp"/>
-
-	</androidx.constraintlayout.widget.ConstraintLayout>
+			android:layout_marginBottom="32dp"
+			android:src="@drawable/ic_contacts_outline"
+			app:tint="?attr/textColorSecondary"
+			android:layout_gravity="center_horizontal" />
+
+		<TextView
+			android:id="@+id/explain_text"
+			android:layout_width="match_parent"
+			android:layout_height="wrap_content"
+			android:textAlignment="center"
+			android:gravity="center_horizontal"
+			android:paddingLeft="16dp"
+			android:paddingRight="16dp"
+			android:text="@string/directory_explain_text"
+			android:textAppearance="@style/Threema.TextAppearance.Emptyview" />
+
+	</LinearLayout>
 
 	<LinearLayout
+		android:id="@+id/results_layout"
 		android:layout_width="match_parent"
 		android:layout_height="match_parent"
-		android:layout_marginTop="@dimen/directory_search_bar_height"
-		app:layout_behavior="@string/appbar_scrolling_view_behavior"
-		android:orientation="vertical">
+		android:orientation="vertical"
+		android:visibility="gone"
+		android:background="?android:windowBackground"
+		app:layout_behavior="@string/appbar_scrolling_view_behavior">
 
 		<HorizontalScrollView
 			android:layout_width="match_parent"
@@ -75,14 +105,14 @@
 				android:id="@+id/chip_group"
 				android:layout_width="wrap_content"
 				android:layout_height="wrap_content"
+				android:paddingBottom="-1dp"
 				android:paddingLeft="16dp"
-				android:paddingTop="0dp"
 				android:paddingRight="16dp"
-				android:paddingBottom="-1dp"
+				android:paddingTop="4dp"
+				android:visibility="gone"
 				app:chipSpacingHorizontal="4dp"
 				app:chipSpacingVertical="0dp"
-				app:singleLine="true"
-				android:visibility="gone" />
+				app:singleLine="true" />
 
 		</HorizontalScrollView>
 
@@ -93,25 +123,35 @@
 			android:layout_marginRight="@dimen/tablet_additional_padding_left_right"
 			app:layout_behavior="@string/appbar_scrolling_view_behavior">
 
-		<ch.threema.app.ui.EmptyRecyclerView
-			android:id="@+id/recycler"
-			android:layout_width="match_parent"
-			android:layout_height="match_parent"
-			android:layout_marginTop="4dp"/>
+			<TextView
+				android:id="@+id/empty_text"
+				android:layout_width="match_parent"
+				android:layout_height="wrap_content"
+				android:layout_marginTop="32dp"
+				android:textAlignment="center"
+				android:gravity="center_horizontal"
+				android:paddingLeft="16dp"
+				android:paddingRight="16dp"
+				android:text=""
+				android:textAppearance="@style/Threema.TextAppearance.Emptyview" />
 
-		<TextView
-			android:id="@+id/empty_text"
-			android:layout_width="wrap_content"
-			android:layout_height="wrap_content"
-			android:gravity="center_horizontal|center_vertical"
-			android:layout_marginTop="16dp"
-			android:paddingLeft="16dp"
-			android:paddingRight="16dp"
-			android:textAppearance="@style/Threema.TextAppearance.Emptyview"
-			android:text="@string/directory_empty_view_text" />
+			<ch.threema.app.ui.EmptyRecyclerView
+				android:id="@+id/recycler"
+				android:layout_width="match_parent"
+				android:layout_height="match_parent"
+				android:layout_marginTop="4dp" />
 
 		</FrameLayout>
 
 	</LinearLayout>
 
+	<include layout="@layout/toolbar_view" />
+
+	<com.google.android.material.progressindicator.LinearProgressIndicator
+		android:id="@+id/progress_bar"
+		app:layout_behavior="@string/appbar_scrolling_view_behavior"
+		android:layout_width="match_parent"
+		android:layout_height="wrap_content"
+		android:indeterminate="true"/>
+
 </androidx.coordinatorlayout.widget.CoordinatorLayout>

+ 15 - 0
app/src/main/res/layout/activity_map.xml

@@ -83,6 +83,21 @@
 					app:chipCornerRadius="18dp"
 					app:chipStartPadding="8dp"/>
 
+				<com.google.android.material.chip.Chip
+					android:id="@+id/share_chip"
+					style="@style/Widget.MaterialComponents.Chip.Action"
+					android:layout_width="wrap_content"
+					android:layout_height="wrap_content"
+					android:layout_marginTop="4dp"
+					android:text="@string/forward_location"
+					android:textColor="@android:color/white"
+					app:chipBackgroundColor="?attr/colorAccent"
+					app:chipIcon="@drawable/ic_forward_outline"
+					app:chipIconTint="@android:color/white"
+					app:chipMinHeight="36dp"
+					app:chipCornerRadius="18dp"
+					app:chipStartPadding="8dp" />
+
 			</LinearLayout>
 
 		</com.google.android.material.card.MaterialCardView>

+ 13 - 4
app/src/main/res/layout/dialog_password_entry.xml

@@ -1,9 +1,16 @@
 <?xml version="1.0" encoding="utf-8"?>
-<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
-            xmlns:app="http://schemas.android.com/apk/res-auto"
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+	xmlns:app="http://schemas.android.com/apk/res-auto"
+	android:layout_width="match_parent"
+	android:layout_height="wrap_content"
+	android:paddingLeft="5dp"
+	android:paddingRight="5dp">
+
+<ScrollView
             android:id="@+id/scroll_container"
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
+			android:scrollbarDefaultDelayBeforeFade="50000"
             android:background="@android:color/transparent">
 
 	<LinearLayout
@@ -11,8 +18,8 @@
 			android:layout_width="match_parent"
 			android:layout_height="wrap_content"
 			android:paddingTop="@dimen/dialog_margin_below_title"
-			android:paddingLeft="@dimen/edittext_padding"
-			android:paddingRight="@dimen/edittext_padding">
+			android:paddingLeft="16dp"
+			android:paddingRight="16dp">
 
 		<TextView
 			android:id="@+id/message_text"
@@ -80,3 +87,5 @@
 	</LinearLayout>
 
 </ScrollView>
+
+</FrameLayout>

+ 22 - 0
app/src/main/res/menu/activity_directory.xml

@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<menu xmlns:android="http://schemas.android.com/apk/res/android"
+      xmlns:app="http://schemas.android.com/apk/res-auto">
+
+	<item
+		android:id="@+id/menu_search_directory"
+		android:icon="@drawable/ic_search_outline"
+		app:iconTint="?android:textColorSecondary"
+		android:title="@string/directory_search"
+		android:visible="false"
+		app:showAsAction="never|collapseActionView"
+		app:actionViewClass="ch.threema.app.ui.ThreemaSearchView"/>
+
+	<item
+		android:id="@+id/menu_category"
+		android:icon="@drawable/ic_filter_list_black_24dp"
+		app:iconTint="?android:textColorSecondary"
+		android:orderInCategory="99"
+		android:title="@string/filter_list"
+		app:showAsAction="always"/>
+</menu>

+ 8 - 1
app/src/main/res/values-cs/strings.xml

@@ -820,6 +820,7 @@ přátelům vás automaticky vyhledat, pokud vás mají v adresáři svého tel
     <string name="unpin">Odepnout</string>
     <string name="location_services_disabled">Služby určování polohy jsou zakázány. Přejete si je nyní povolit?</string>
     <string name="send_location">Odeslat polohu</string>
+    <string name="forward_location">Přeposlat polohu</string>
     <string name="unknown_address">Neznámá adresa</string>
     <string name="your_location">Vaše poloha</string>
     <string name="network_blocked_title">Využití dat na pozadí je zakázáno</string>
@@ -1384,6 +1385,10 @@ přátelům vás automaticky vyhledat, pokud vás mají v adresáři svého tel
     <string name="spam_report_short">Nahlásit</string>
     <string name="wizard_incompatible_contact_sync_params">Jsou nastaveny nekompatibilní MDM parametry. Threema MDM parametr th_contact_sync nemůže být nastaven na „true“, jestliže je nastaveno uživatelské omezení DISALLOW_MODIFY_ACCOUNTS. Kontaktujte prosím správce vašeho zařízení.</string>
     <string name="messages_cannot_be_recovered">Zprávy z nich už poté nebudete moci obnovit.</string>
+    <string name="contact_deleted">Kontakt odstraněn</string>
+    <string name="last_added_contact">Naposledy přidaný</string>
+    <string name="directory_explain_text">Vyhledá v adresáři společnosti jakékoliv zaměstnance, kteří dosud nejsou ve vašem seznamu kontaktů.</string>
+    <string name="cannot_display_location">Tato poloha nemůže být zobrazena.</string>
     <plurals name="contacts_counter_label">
         <item quantity="few">%d kontakty</item>
         <item quantity="many">%d kontaktů</item>
@@ -1397,13 +1402,15 @@ přátelům vás automaticky vyhledat, pokud vás mají v adresáři svého tel
         <item quantity="other">Skutečně si přejete odstranit %d konverzací?</item>
     </plurals>
     <plurals name="sending_message_failed">
-        <item quantity="one">Nezdařilo se odeslat následující počet zpráv: %1$d</item>
         <item quantity="few">Nezdařilo se odeslat následující počet zpráv: %1$d</item>
         <item quantity="many">Nezdařilo se odeslat následující počet zpráv: %1$d</item>
+        <item quantity="one">Nezdařilo se odeslat následující počet zpráv: %1$d</item>
+        <item quantity="other">Nezdařilo se odeslat následující počet zpráv: %1$d</item>
     </plurals>
     <plurals name="selection_counter_label">
         <item quantity="one">Počet vybraných obrázků: %d</item>
         <item quantity="few">Počet vybraných obrázků: %d</item>
         <item quantity="many">Počet vybraných obrázků: %d</item>
+        <item quantity="other">Počet vybraných obrázků: %d</item>
     </plurals>
 </resources>

+ 3 - 0
app/src/main/res/values-de/strings.xml

@@ -936,6 +936,7 @@ sicheren Ort gesichert oder ausgedruckt haben.</string>
 	<string name="unpin">Entpinnen</string>
 	<string name="location_services_disabled">Die Standortdienste sind ausgeschaltet. Möchten Sie sie jetzt aktivieren?</string>
 	<string name="send_location">Standort senden</string>
+	<string name="forward_location">Ort weiterleiten</string>
 	<string name="unknown_address">Unbekannte Adresse</string>
 	<string name="your_location">Ihr Standort</string>
 	<string name="network_blocked_title">Hintergrunddaten deaktiviert</string>
@@ -1491,6 +1492,8 @@ sicheren Ort gesichert oder ausgedruckt haben.</string>
 	<string name="messages_cannot_be_recovered">Die Nachrichten können nicht wiederhergestellt werden.</string>
 	<string name="contact_deleted">Kontakt wurde gelöscht</string>
 	<string name="last_added_contact">Zuletzt hinzugefügt</string>
+	<string name="directory_explain_text">Suchen Sie im Firmenverzeichnis nach beliebigen Mitarbeitern, die sich noch nicht in Ihrer Kontaktliste befinden.</string>
+	<string name="cannot_display_location">Dieser Ort kann nicht angezeigt werden.</string>
 	<plurals name="contacts_counter_label">
 		<item quantity="one">%d Kontakt</item>
 		<item quantity="other">%d Kontakte</item>

+ 5 - 0
app/src/main/res/values-es/strings.xml

@@ -827,6 +827,7 @@ almacenado.</string>
     <string name="unpin">Desmarcar</string>
     <string name="location_services_disabled">Servicios de ubicación desactivados. ¿Quiere activarlos ahora?</string>
     <string name="send_location">Enviar ubicación</string>
+    <string name="forward_location">Reenviar ubicación</string>
     <string name="unknown_address">Dirección desconocida</string>
     <string name="your_location">Su ubicación</string>
     <string name="network_blocked_title">Datos en segundo plano desactivados</string>
@@ -1392,6 +1393,10 @@ almacenado.</string>
     <string name="spam_report_short">Reportar</string>
     <string name="wizard_incompatible_contact_sync_params">Se han establecido parámetros MDM incompatibles. El parámetro MDM th_contact_sync de Threema no se puede establecer en verdadero si se ha establecido la restricción de usuario DISALLOW_MODIFY_ACCOUNTS. Contacte con el administrador de su dispositivo.</string>
     <string name="messages_cannot_be_recovered">No podreu recuperar els missatges.</string>
+    <string name="contact_deleted">Contacto eliminado</string>
+    <string name="last_added_contact">Último añadido</string>
+    <string name="directory_explain_text">Buscar en el directorio de la compañía empleados que todavía no estén en su lista de contactos.</string>
+    <string name="cannot_display_location">No se puede mostrar la ubicación.</string>
     <plurals name="contacts_counter_label">
         <item quantity="one">%d contacto</item>
         <item quantity="other">%d contactos</item>

+ 5 - 0
app/src/main/res/values-fr/strings.xml

@@ -818,6 +818,7 @@ Veuillez saisir une question pour votre enquête.</string>
     <string name="unpin">Démarquer</string>
     <string name="location_services_disabled">Les services de localisation sont désactivés. Souhaitez-vous les activer maintenant ?</string>
     <string name="send_location">Envoyer l\'emplacement</string>
+    <string name="forward_location">Transférer l\'emplacement</string>
     <string name="unknown_address">Adresse inconnue</string>
     <string name="your_location">Votre emplacement</string>
     <string name="network_blocked_title">Données en arrière-plan désactivées</string>
@@ -1382,6 +1383,10 @@ Veuillez saisir une question pour votre enquête.</string>
     <string name="spam_report_short">Signaler</string>
     <string name="wizard_incompatible_contact_sync_params">Il y a des paramètres MDM incompatibles définis. Le paramètre Threema MDM th_contact_sync ne peut pas être activé si la restriction d\'utilisateur DISALLOW_MODIFY_ACCOUNTS est définie. Veuillez contacter l\'administrateur de votre appareil.</string>
     <string name="messages_cannot_be_recovered">Vous ne pourrez pas annuler cette action.</string>
+    <string name="contact_deleted">Contact supprimé</string>
+    <string name="last_added_contact">Dernier ajout</string>
+    <string name="directory_explain_text">Recherchez dans le répertoire de l\'entreprise tout employé ne faisant pas encore partie de votre liste de contacts.</string>
+    <string name="cannot_display_location">Impossible d\'afficher cet emplacement.</string>
     <plurals name="contacts_counter_label">
         <item quantity="one">%d contact</item>
         <item quantity="many">%d contacts</item>

+ 5 - 0
app/src/main/res/values-it/strings.xml

@@ -836,6 +836,7 @@ automaticamente in caso di inattività dopo un intervallo predefinito (solo cara
     <string name="unpin">Deselez.</string>
     <string name="location_services_disabled">I servizi di localizzazione sono disabilitati. Vuoi abilitarli adesso?</string>
     <string name="send_location">Invia posizione</string>
+    <string name="forward_location">Inoltra luogo</string>
     <string name="unknown_address">Indirizzo sconosciuto</string>
     <string name="your_location">La tua posizione</string>
     <string name="network_blocked_title">Dati in background disabilitati</string>
@@ -1394,6 +1395,10 @@ automaticamente in caso di inattività dopo un intervallo predefinito (solo cara
     <string name="spam_report_short">Segnala</string>
     <string name="wizard_incompatible_contact_sync_params">Sono stati definiti dei parametri MDM incompatibili. Il parametro MDM Threema th_contact_sync non può essere attivato se la restrizione DISALLOW_MODIFY_ACCOUNTS è anche attiva. Contatta il tuo amministratore del sistema.</string>
     <string name="messages_cannot_be_recovered">I messaggi cancellati non possono più essere ripristinati.</string>
+    <string name="contact_deleted">Il contatto è stato cancellato</string>
+    <string name="last_added_contact">Ultimo contatto aggiunto</string>
+    <string name="directory_explain_text">Cerca nell\'indice dell\'impresa tutti i collaboratori che ancora non figurano nella tua rubrica.</string>
+    <string name="cannot_display_location">Questo luogo non può essere visualizzato.</string>
     <plurals name="contacts_counter_label">
         <item quantity="one">%d contatto</item>
         <item quantity="other">%d contatti</item>

+ 5 - 0
app/src/main/res/values-nl-rNL/strings.xml

@@ -820,6 +820,7 @@ Weet u zeker dat u Threema anoniem wil gebruiken?</string>
     <string name="unpin">Wissen</string>
     <string name="location_services_disabled">Locatiediensten zijn uitgeschakeld. Wil je ze inschakelen?</string>
     <string name="send_location">Locatie verzenden</string>
+    <string name="forward_location">Locatie doorsturen</string>
     <string name="unknown_address">Onbekend adres</string>
     <string name="your_location">Je locatie</string>
     <string name="network_blocked_title">Achtergronddata uitgeschakeld</string>
@@ -1384,6 +1385,10 @@ Weet u zeker dat u Threema anoniem wil gebruiken?</string>
     <string name="spam_report_short">Melden</string>
     <string name="wizard_incompatible_contact_sync_params">Er zijn incompatibele MDM-parameters ingesteld. De Threema MDM-parameter th_contact_sync kan niet zijn ingesteld op \'True\' als gebruikersbeperking DISALLOW_MODIFY_ACCOUNTS is ingesteld. Neem contact op met de beheerder van het apparaat.</string>
     <string name="messages_cannot_be_recovered">De berichten gaan dan verloren.</string>
+    <string name="contact_deleted">Contact verwijderd</string>
+    <string name="last_added_contact">Laatst toegevoegd</string>
+    <string name="directory_explain_text">Zoek in de bedrijfsgids naar werknemers die nog niet aan je contact lijst zijn toegevoegd.</string>
+    <string name="cannot_display_location">Deze locatie kan niet worden getoond.</string>
     <plurals name="contacts_counter_label">
         <item quantity="one">%d contactpersoon</item>
         <item quantity="other">%d contactpersonen</item>

+ 5 - 0
app/src/main/res/values-no/strings.xml

@@ -815,6 +815,7 @@ http://www.7-zip.org eller https://itunes.apple.com/us/app/the-unarchiver/id4254
     <string name="unpin">Ikke fest</string>
     <string name="location_services_disabled">Posisjonstjenester er deaktivert. Vil du aktivere dem nå?</string>
     <string name="send_location">Send posisjon</string>
+    <string name="forward_location">Videresend posisjon</string>
     <string name="unknown_address">Ukjent adresse</string>
     <string name="your_location">Din posisjon</string>
     <string name="network_blocked_title">Bakgrunnsdata er deaktivert</string>
@@ -1379,6 +1380,10 @@ http://www.7-zip.org eller https://itunes.apple.com/us/app/the-unarchiver/id4254
     <string name="spam_report_short">Rapporter</string>
     <string name="wizard_incompatible_contact_sync_params">Det er valgt inkompatible MDM parametre. Threema MDM parameter th_contact_sync kan ikke bli satt til \"True\" om brukerbegrensning DISALLOW_MODIFY_ACCOUNTS er valgt. Vennligst kontakt enhetens administrator.</string>
     <string name="messages_cannot_be_recovered">Du har ikke muligheten til å gjenopprette meldingene.</string>
+    <string name="contact_deleted">Kontakt slettet</string>
+    <string name="last_added_contact">Sist lagt til</string>
+    <string name="directory_explain_text">Søk i firmaets oppføringsliste etter ansatte som ikke er lagt til i din kontaktliste.</string>
+    <string name="cannot_display_location">Denne posisjonen kan ikke vises.</string>
     <plurals name="contacts_counter_label">
         <item quantity="one">%d kontakt</item>
         <item quantity="other">%d kontakter</item>

+ 12 - 7
app/src/main/res/values-pl/strings.xml

@@ -3,7 +3,7 @@
     <string name="title_section2">Kontakty</string>
     <string name="title_section1">Czaty</string>
     <string name="title_compose_message">Rozpocznij czat</string>
-    <string name="title_choose_recipient">Wybierz odbiorców</string>
+    <string name="title_choose_recipient">Wybierz odbiorcę</string>
     <string name="title_keyfingerprint">Klucz publiczny</string>
     <string name="title_mythreemaid">Mój identyfikator Threema</string>
     <string name="title_threemaid">Identyfikator Threema</string>
@@ -44,7 +44,7 @@
     <string name="prefs_title_typing_indicator">Wyślij wskaźnik pisania</string>
     <string name="prefs_media_title">Media</string>
     <string name="prefs_sum_media_title">Ustawienia mediów</string>
-    <string name="prefs_image_size">Wymiary obrazu</string>
+    <string name="prefs_image_size">Rozmiar obrazu</string>
     <string name="prefs_notification_sound">Dźwięk powiadomienia</string>
     <string name="prefs_sum_notification_sound">Domyślne dla systemu</string>
     <string name="prefs_vibrate">Wibruj</string>
@@ -90,7 +90,7 @@
     <string name="color_red">Czerwony</string>
     <string name="color_green">Zielony</string>
     <string name="color_blue">Niebieski</string>
-    <string name="color_cyan">Cyan</string>
+    <string name="color_cyan">Cyjan</string>
     <string name="color_magenta">Magenta</string>
     <string name="color_yellow">Żółty</string>
     <string name="color_white">Biały</string>
@@ -178,8 +178,8 @@
     <string name="attach_camera">Aparat</string>
     <string name="menu_restore">Przywróć z backup-u</string>
     <string name="restore_id_hint">Wprowadź lub wklej tekst eksportu identyfikatora, aby przywrócić</string>
-    <string name="location_placeholder">Location</string>
-    <string name="video_placeholder">Video</string>
+    <string name="location_placeholder">Lokalizacja</string>
+    <string name="video_placeholder">Film</string>
     <string name="audio_placeholder">Audio</string>
     <string name="restoring_backup">Przywracanie backup-u</string>
     <string name="server_message_title">Wiadomość z serwera Threema</string>
@@ -529,7 +529,7 @@ Wprowadź pytanie do swojej ankiety.</string>
     <string name="mime_text">Plik tekstowy</string>
     <string name="mime_video">Plik wideo</string>
     <string name="mime_word">Dokument tekstowy</string>
-    <string name="no_filename"><![CDATA[<No filename>]]></string>
+    <string name="no_filename"><![CDATA[<Brak nazwy pliku>]]></string>
     <string name="send_as_files">Wyślij jako plik</string>
     <string name="send_as_files_warning">Przesyłanie nieskompresowanych plików i obrazów może doprowadzić do znacznego zużycia danych, a tym samym do naliczenia dodatkowych opłat przez operatora sieci komórkowej. Zalecamy wysyłanie i pobieranie plików wyłącznie za pośrednictwem sieci Wi-Fi.</string>
     <string name="prefs_theme">Motyw</string>
@@ -834,6 +834,7 @@ anonimowo?</string>
     <string name="unpin">Odznacz</string>
     <string name="location_services_disabled">Usługi lokalizacyjne są wyłączone. Czy chcesz włączyć je teraz?</string>
     <string name="send_location">Prześlij lokalizację</string>
+    <string name="forward_location">Przekaż lokalizację</string>
     <string name="unknown_address">Nieznany adres</string>
     <string name="your_location">Twoja lokalizacja</string>
     <string name="network_blocked_title">Obsługa danych w tle wyłączona</string>
@@ -1215,7 +1216,7 @@ anonimowo?</string>
     <string name="group_request_already_sent"><![CDATA[Prośba o dołączenie została już wysłana dla <b>%s</b> poprzez to łącze. Kliknij na prośbę, aby wysłać ją ponownie.]]></string>
     <string name="group_link_none">Nie wygenerowano jeszcze łącza</string>
     <string name="group_request_confirm_send"><![CDATA[Zamierzasz wysłać prośbę o dołączenie do grupy dla <b>%1$s</b> do administratora <b>%2$s</b>. Jeśli to łącze nie zostało unieważnione, zostaniesz automatycznie dodany/dodana do grupy, jak tylko urządzenie administratora otrzyma Twoją prośbę.]]></string>
-    <string name="really_delete_outgoing_request">Czy naprawdę chcesz usunąć prośbę o dołączenie do grupy %d? Pamiętaj, że prośba nie zostanie cofnięta i nadal możliwe będzie jej zaakceptowanie lub kontakt z Tobą, jeśli jeszcze nie odpowiedziano na prośbę.</string>
+    <string name="really_delete_outgoing_request">Czy naprawdę chcesz usunąć %d próśb o dołączenie do grupy ? Pamiętaj, że prośby nie zostanę cofnięte i nadal mogą zostać zaakceptowane albo odrzucone jeśli administrator grupy jeszcze nie odpowiedział na prośbę.</string>
     <string name="really_delete_group_request_title">Naprawdę usuń prośbę</string>
     <string name="sent_to">Wysłane do: %s</string>
     <string name="sent_on">Wysłano: %s</string>
@@ -1398,6 +1399,10 @@ anonimowo?</string>
     <string name="spam_report_short">Zgłoś</string>
     <string name="wizard_incompatible_contact_sync_params">Ustawione są niezgodne parametry MDM. Parametr MDM Threema th_contact_sync nie może być ustawiony na wartość prawda, jeśli ustawiono ograniczenie użytkownika DISALLOW_MODIFY_ACCOUNTS. Skontaktuj się z administratorem urządzenia.</string>
     <string name="messages_cannot_be_recovered">Nie będzie mógł ich odzyskać.</string>
+    <string name="contact_deleted">Kontakt usunięty</string>
+    <string name="last_added_contact">Ostatnio dodany</string>
+    <string name="directory_explain_text">Przeszukaj katalog firmowy, by znaleźć pracowników, których nie ma na liście kontaktów.</string>
+    <string name="cannot_display_location">Nie można pokazać lokalizacji.</string>
     <plurals name="contacts_counter_label">
         <item quantity="few">%d kontakty</item>
         <item quantity="many">%d kontaktów</item>

+ 5 - 0
app/src/main/res/values-pt-rBR/strings.xml

@@ -822,6 +822,7 @@ ficará órfã. Os outros membros ainda poderão conversar, mas não serão mais
     <string name="unpin">Desmarcar</string>
     <string name="location_services_disabled">Os serviços de localização estão desativados. Você gostaria de ativá-los agora?</string>
     <string name="send_location">Enviar localização</string>
+    <string name="forward_location">Encaminhar localização</string>
     <string name="unknown_address">Endereço desconhecido</string>
     <string name="your_location">Sua localização</string>
     <string name="network_blocked_title">Dados de segundo plano desativados</string>
@@ -1386,6 +1387,10 @@ ficará órfã. Os outros membros ainda poderão conversar, mas não serão mais
     <string name="spam_report_short">Denunciar</string>
     <string name="wizard_incompatible_contact_sync_params">Há parâmetros MDM incompatíveis definidos. O parâmetro MDM do Threema th_contact_sync não pode ser definido como verdadeiro se a restrição de usuário DISALLOW_MODIFY_ACCOUNTS estiver definida. Entre em contato com o administrador do seu dispositivo.</string>
     <string name="messages_cannot_be_recovered">Você não poderá desfazer esta ação.</string>
+    <string name="contact_deleted">Contato excluído</string>
+    <string name="last_added_contact">Mais recentes</string>
+    <string name="directory_explain_text">Encontre o diretório da empresa para qualquer funcionário que ainda não estiver na sua lista de contatos.</string>
+    <string name="cannot_display_location">Esta localização não pode ser exibida.</string>
     <plurals name="contacts_counter_label">
         <item quantity="one">%d contato</item>
         <item quantity="other">%d contatos</item>

+ 5 - 0
app/src/main/res/values-ru/strings.xml

@@ -818,6 +818,7 @@
     <string name="unpin">Снять</string>
     <string name="location_services_disabled">Службы определения местоположения отключены. Хотите включить их сейчас?</string>
     <string name="send_location">Отправить местопол.</string>
+    <string name="forward_location">Переслать местоположение</string>
     <string name="unknown_address">Неизвестный адрес</string>
     <string name="your_location">Ваше местоположение</string>
     <string name="network_blocked_title">Фоновая передача данных отключена</string>
@@ -1382,6 +1383,10 @@
     <string name="spam_report_short">Жалоба</string>
     <string name="wizard_incompatible_contact_sync_params">Установлены несовместимые параметры MDM. Для параметра Threema MDM th_contact_sync не может быть установлено значение «истина», если задано ограничение пользователя DISALLOW_MODIFY_ACCOUNTS. Обратитесь к администратору.</string>
     <string name="messages_cannot_be_recovered">Вы не сможете восстановить сообщения.</string>
+    <string name="contact_deleted">Контакт удален</string>
+    <string name="last_added_contact">Добавлен последним</string>
+    <string name="directory_explain_text">Поиск в каталоге компании сотрудников, которых еще нет в вашем списке контактов.</string>
+    <string name="cannot_display_location">Это местоположение невозможно показать.</string>
     <plurals name="contacts_counter_label">
         <item quantity="few">%d контакта</item>
         <item quantity="many">%d контактов</item>

+ 6 - 1
app/src/main/res/values-sk/strings.xml

@@ -817,6 +817,7 @@ Vykonajte prosím zálohú vašich údajov vhodnou metódou.</string>
     <string name="unpin">Odopnúť</string>
     <string name="location_services_disabled">Služby určovania polohy (GPS) sú vypnuté. Chcete ich teraz zapnúť?</string>
     <string name="send_location">Poslať polohu</string>
+    <string name="forward_location">Preposlanie polohy</string>
     <string name="unknown_address">Neznáma adresa</string>
     <string name="your_location">Vaša poloha</string>
     <string name="network_blocked_title">Príjem dát na pozadí vypnutý</string>
@@ -1380,6 +1381,10 @@ Vykonajte prosím zálohú vašich údajov vhodnou metódou.</string>
     <string name="spam_report_short">Nahlásiť</string>
     <string name="wizard_incompatible_contact_sync_params">Sú nastavené nekompatibilné parametre MDM. Parameter Threema MDM th_contact_sync nemožno nastaviť na hodnotu true, ak je nastavené obmedzenie používateľa DISALLOW_MODIFY_ACCOUNTS. Kontaktujte správcu svojho zariadenia.</string>
     <string name="messages_cannot_be_recovered">Nebude možné obnoviť tieto správy.</string>
+    <string name="contact_deleted">Kontakt vymazaný</string>
+    <string name="last_added_contact">Najnovší kontakt</string>
+    <string name="directory_explain_text">V adresári spoločnosti vyhľadajte zamestnancov, ktorí ešte nie sú vo vašom zozname kontaktov.</string>
+    <string name="cannot_display_location">Toto miesto nie je možné zobraziť.</string>
     <plurals name="contacts_counter_label">
         <item quantity="few">kontakty</item>
         <item quantity="many">kontakty</item>
@@ -1393,9 +1398,9 @@ Vykonajte prosím zálohú vašich údajov vhodnou metódou.</string>
         <item quantity="other">Naozaj chcete odstrániť %d konverzácií?</item>
     </plurals>
     <plurals name="sending_message_failed">
-        <item quantity="one">Nepodarilo sa odoslať správy: %1$d</item>
         <item quantity="few">Nepodarilo sa odoslať správy: %1$d</item>
         <item quantity="many">Nepodarilo sa odoslať správy: %1$d</item>
+        <item quantity="one">Nepodarilo sa odoslať správy: %1$d</item>
         <item quantity="other">Nepodarilo sa odoslať správy: %1$d</item>
     </plurals>
     <plurals name="selection_counter_label">

+ 6 - 6
app/src/main/res/values-tr/poi_strings.xml

@@ -1,17 +1,17 @@
 <?xml version="1.0" encoding="utf-8"?>
 <resources xmlns:tools="http://schemas.android.com/tools" tools:ignore="MissingTranslation">
-    <string name="hamlet">Küçük Köy</string>
+    <string name="hamlet">Mezra</string>
     <string name="village">Köy</string>
     <string name="town">Kasaba</string>
-    <string name="isolated_dwelling">İzole Konut Alanı</string>
+    <string name="isolated_dwelling">İzole konut</string>
     <string name="island">Ada</string>
     <string name="islet">Adacık</string>
     <string name="suburb">Kenar mahalle</string>
-    <string name="city">Kent</string>
+    <string name="city">Şehir</string>
     <string name="city_block">Şehir Bloğu</string>
-    <string name="neighbourhood">Komşuluk</string>
-    <string name="locality">Mekan</string>
-    <string name="state">Durum</string>
+    <string name="neighbourhood">Komşu</string>
+    <string name="locality">Muhit</string>
+    <string name="state">Eyalet</string>
     <string name="farm">Çiftlik</string>
     <string name="street">Sokak</string>
     <string name="country">Ülke</string>

Tiedoston diff-näkymää rajattu, sillä se on liian suuri
+ 316 - 335
app/src/main/res/values-tr/strings.xml


+ 3 - 3
app/src/main/res/values-tr/voicemessage_strings.xml

@@ -1,10 +1,10 @@
 <?xml version="1.0" encoding="utf-8"?>
 <resources>
-    <string name="recording">Mesaj Kaydı</string>
+    <string name="recording">Mesaj kaydediliyor</string>
     <string name="recording_stopped_title">Kayıt durduruldu</string>
     <string name="recording_stopped_message">Kaydedilen sesli mesajı şimdi göndermek ister misiniz?</string>
     <string name="recording_canceled">Bilinmeyen bir hata nedeniyle kayıt iptal edildi.</string>
-    <string name="cancel_recording">Sil</string>
-    <string name="cancel_recording_message">Kaydı gerçekten iptal edip atmak istiyor musunuz?</string>
+    <string name="cancel_recording">Kaydı sil</string>
+    <string name="cancel_recording_message">Kaydı gerçekten iptal etmek ve silmek istiyor musunuz?</string>
     <string name="stop">Dur</string>
 </resources>

+ 29 - 29
app/src/main/res/values-tr/voip_strings.xml

@@ -1,72 +1,72 @@
 <?xml version="1.0"?>
 <resources>
-    <string name="voip_title">Threema Çağrısı</string>
+    <string name="voip_title">Threema Call</string>
     <string name="voip_hangup">Telefonu kapat</string>
     <string name="voip_toggle_mic">Mikrofonu aç / kapat</string>
     <string name="voip_toggle_speaker">Hoparlörü aç / kapat</string>
     <string name="voip_switch_cam">Kamerayı değiştir</string>
     <string name="voip_switch_cam_front">Ön kameraya geçildi</string>
     <string name="voip_switch_cam_rear">Arka kameraya geçildi</string>
-    <string name="voip_toggle_video">Video modunu Aç / Kapat</string>
+    <string name="voip_toggle_video">Video modunu aç / kapat</string>
     <string name="voip_call_confirm">%1$s\'i aramak istiyor musunuz ?</string>
     <string name="voip_error_call">Threema çağrısı sırasında hata</string>
     <string name="voip_error_init_call">Çağrı başlatılırken hata oluştu</string>
     <string name="voip_notification_title">Gelen Threema Çağrısı</string>
     <string name="voip_notification_text">%1$s arıyor</string>
     <!-- Shown when starting a call, before the peer device is ringing -->
-    <string name="voip_status_initializing">Başlatılıyor</string>
+    <string name="voip_status_initializing">Aranıyor</string>
     <!-- Shown when the callee has accepted the call and the connection is being established -->
     <string name="voip_status_connecting">Bağlanıyor</string>
     <!-- Shown when a call is being disconnected -->
-    <string name="voip_status_disconnecting">Bağlantı kesilmesi</string>
+    <string name="voip_status_disconnecting">Bağlantı kesiliyor</string>
     <!-- Shown when the device of the callee is ringing -->
     <string name="voip_status_ringing">Çalıyor</string>
     <string name="voip_mic_enable">Mikrofonu etkinleştir</string>
     <string name="voip_mic_disable">Mikrofonu devre dışı bırak</string>
-    <string name="voip_checking_compatibility">Bu kişinin Threema çağrıları alıp alamayacağını kontrol etme</string>
+    <string name="voip_checking_compatibility">Bu kişinin Threema çağrılarını alıp alamayacağı kontrol ediliyor</string>
     <string name="voip_incompatible">Bu kişi henüz Threema çağrılarını alamıyor.</string>
-    <string name="voip_call_status_unavailable">Çağrı alıcısı uygun değil</string>
+    <string name="voip_call_status_unavailable">Çağrı alıcısı müsait değil</string>
     <string name="voip_call_status_rejected">Çağrı reddedildi</string>
-    <string name="voip_call_status_busy">Çağrı alıcı meşgul</string>
+    <string name="voip_call_status_busy">Çağrı alıcı meşgul</string>
     <string name="voip_call_status_busy_short">Meşgul</string>
     <string name="voip_call_status_disabled">Threema çağrıları alıcı tarafından devre dışı bırakıldı</string>
-    <string name="voip_call_status_missed">Cevapsız Çağrı</string>
-    <string name="voip_call_finished_outbox">Giden Çağrı</string>
-    <string name="voip_call_finished_inbox">Gelen Çağrı</string>
-    <string name="voip_call_status_aborted">Çağrı İptal Edildi</string>
-    <string name="voip_return_call">Geri arama</string>
+    <string name="voip_call_status_missed">Cevapsız çağrı</string>
+    <string name="voip_call_finished_outbox">Giden çağrı</string>
+    <string name="voip_call_finished_inbox">Gelen çağrı</string>
+    <string name="voip_call_status_aborted">Çağrı iptal edildi</string>
+    <string name="voip_return_call">Geri ara</string>
     <string name="voip_accept">Kabul et</string>
-    <string name="voip_reject">Reddetmek</string>
+    <string name="voip_reject">Reddet</string>
     <string name="voip_speakerphone">Hoparlör</string>
     <string name="voip_wired_headset">Kulaklık</string>
     <string name="voip_earpiece">Telefon</string>
     <string name="voip_bluetooth">Bluetooth</string>
     <string name="voip_none">Müsait değil</string>
     <string name="voip_call_finished">Threema görüşmesi sona erdi</string>
-    <string name="voip_another_call">Bağlanılamıyor. Şu anda başka bir Threema Call aktif.</string>
+    <string name="voip_another_call">Bağlanamıyor. Başka bir Threema araması şu anda aktif.</string>
     <string name="voip_prefs_title_aec">Yankı giderme</string>
-    <string name="voip_prefs_aec_sw">Yazılım yankı iptali</string>
-    <string name="voip_prefs_aec_hw">Donanım yankı iptali</string>
-    <string name="voip_prefs_title_video_codec">Video Codec\'leri</string>
-    <string name="voip_prefs_video_aec_hw">Donanım hızlandırılmış</string>
-    <string name="voip_prefs_video_codec_no_vp8">VP8\'i devre dışı bırakın</string>
-    <string name="voip_prefs_video_codec_no_h264hip">H264-HiP\'yi devre dışı bırakın</string>
+    <string name="voip_prefs_aec_sw">Yazılım tabanlı yankı giderme</string>
+    <string name="voip_prefs_aec_hw">Donanım tabanlı yankı giderme</string>
+    <string name="voip_prefs_title_video_codec">Video kodekleri</string>
+    <string name="voip_prefs_video_aec_hw">Donanım tabanlı hızlandırma</string>
+    <string name="voip_prefs_video_codec_no_vp8">VP8\'i devre dışı bırak</string>
+    <string name="voip_prefs_video_codec_no_h264hip">H264-HiP\'yi devre dışı bırak</string>
     <string name="voip_prefs_video_codec_sw">Yazılım kodekleri</string>
     <string name="voip_connection_failed">Bağlantı kurulamadı</string>
     <string name="voip_connection_lost">Bağlantı koptu</string>
     <string name="prefs_voip_reject_incoming_calls_title">Mobil aramaları reddet</string>
-    <string name="prefs_voip_reject_incoming_calls_summary">Bir Threema Call etkinken gelen mobil aramaları reddetme.</string>
+    <string name="prefs_voip_reject_incoming_calls_summary">Bir Threema araması etkinken gelen cep telefonu aramalarını reddet.</string>
     <string name="voip_contact_not_found">Bu numara için Threema kişisi bulunamadı.</string>
-    <string name="voip_another_pstn_call">Çağrı başlatılamıyor. Normal bir telefon görüşmesi hala etkindir.</string>
-    <string name="voip_call_status_off_hours">Mesai saatleri dışında çağrı</string>
+    <string name="voip_another_pstn_call">Çağrı başlatılamıyor.  Normal bir telefon görüşmesi hala aktif.</string>
+    <string name="voip_call_status_off_hours">Mesai dışı arama</string>
     <string name="voip_peer_video_disabled">Video görüşmeleri karşı taraf tarafından devre dışı bırakıldı.</string>
     <!-- WebRTC debugger -->
-    <string name="voip_prefs_webrtc_debug">WebRTC Hata Teşhisi</string>
-    <string name="voip_prefs_webrtc_debug_summary">Sesli arama bağlantısı kurma ile ilgili sorunları gidermek için bu aracı başlatın</string>
-    <string name="voip_webrtc_debug">WebRTC Hata Teşhisi</string>
-    <string name="voip_webrtc_debug_intro">Testi başlatmak için \"Başlat\" düğmesine basın.</string>
+    <string name="voip_prefs_webrtc_debug">WebRTC Tanılama</string>
+    <string name="voip_prefs_webrtc_debug_summary">Sesli arama yapmak ile ilgili sorunları tanımlamak ve gidermek için bu aracı başlatın</string>
+    <string name="voip_webrtc_debug">WebRTC Tanılama</string>
+    <string name="voip_webrtc_debug_intro">Testi başlatmak için \"Başlat\" düğmesine tıklayın.</string>
     <string name="voip_webrtc_debug_start">Başlat</string>
-    <string name="voip_webrtc_debug_done">Bitti. Çağrı bağlantısı oluşturma ile ilgili sorunlar yaşıyorsanız, lütfen bu çıkışı Threema desteğine gönderin.</string>
-    <string name="voip_webrtc_debug_copied">Panoya kopyalama tamamlandı.</string>
+    <string name="voip_webrtc_debug_done">Tamamlandı. Arama yapmak ile ilgili sorunlar yaşıyorsanız, lütfen bu çıktıyı Threema desteğine gönderin.</string>
+    <string name="voip_webrtc_debug_copied">Panoya kopyalama işlemi tamamlandı.</string>
     <string name="voip_webrtc_debug_copy_clipboard">Panoya kopyala</string>
 </resources>

+ 4 - 4
app/src/main/res/values-tr/webclient_strings.xml

@@ -5,15 +5,15 @@
     <string name="webclient_sessions_really_delete">Bu masaüstü/web oturumunu gerçekten silmek istiyor musunuz?</string>
     <string name="webclient_last_usage">Son kullanım: %s</string>
     <string name="webclient_created_at">Oluşturulduğu yer: %1$s ( %2$s )</string>
-    <string name="webclient_active_since">Etkin olduğu tarihten beri: %s</string>
+    <string name="webclient_active_since">Şu zamandan beri aktif: %s</string>
     <string name="webclient_enable">Etkinleştir Masaüstü uygulamasını / web istemcisini etkinleştir</string>
     <string name="webclient_no_sessions_found">Bağlanmak için masaüstü uygulamasını indirin ( <b> https :// Threema.ch/download </b> ) veya bilgisayarınızda web istemcisini açın ( <b> %s </b> ) ve Bilgisayar ekranınızda görüntülenen QR kodunu taramak için düğmeye dokunun.</string>
     <string name="webclient_session_rename">Oturumu yeniden adlandır</string>
     <string name="webclient_session_label">Yeni isim</string>
     <string name="webclient_session_start">Oturumu başlat</string>
     <string name="webclient_session_stop">Oturumu durdur</string>
-    <string name="webclient_persistent">Kalıcı</string>
-    <string name="webclient_disposable">Tek kullanımlık</string>
+    <string name="webclient_persistent">kalıcı</string>
+    <string name="webclient_disposable">tek kullanımlık</string>
     <string name="webclient_unnamed_session">Adsız Oturum</string>
     <string name="webclient_session_remove">Oturumu kaldır</string>
     <string name="webclient_welcome_title">PC\'nizden sohbet edin!</string>
@@ -37,6 +37,6 @@
     <string name="webclient_prefs_debug_tool_summary">Masaüstü uygulaması veya web istemcisi ile bağlantı kurma ile ilgili sorunları ayıklamak için bu aracı kullanın</string>
     <string name="webclient_diagnostics">Masaüstü/Web Tanılama</string>
     <string name="webclient_diagnostics_start">Başlat</string>
-    <string name="webclient_diagnostics_intro">Testi başlatmak için «Başlat» üzerine dokunun.</string>
+    <string name="webclient_diagnostics_intro">Testi başlatmak için «Başlat» tıklayın.</string>
     <string name="webclient_diagnostics_done">Tamamlandı. Masaüstü uygulamasına veya web istemcisine bağlantı kurmayla ilgili sorunlar yaşıyorsanız, lütfen bu günlüğü Threema desteğine gönderin.</string>
 </resources>

+ 6 - 1
app/src/main/res/values-uk/strings.xml

@@ -408,7 +408,7 @@
     <string name="push_not_available_text1">Нам не вдалося знайти сервіс push-сповіщень на вашому пристрої, оскільки сервіси Google Play не встановлено або не оновлено.</string>
     <string name="push_not_available_text2">%s спробує встановити постійне фонове з\'єднання з сервером. Ця функція може працювати нестабільно, якщо на вашому пристрої ввімкнена оптимізація використання акумулятора.</string>
     <string name="backup_in_progress">Триває резервне копіювання</string>
-    <string name="backup_or_restore_success_body">Резервне копіювання даних завершено</string>
+    <string name="backup_or_restore_success_body">Резервне копіювання даних завершено.</string>
     <string name="backup_or_restore_error">Рез. копіювання Threema</string>
     <string name="backup_or_restore_error_body">Не вдалося завершити резервне копіювання</string>
     <string name="could_not_download_message">Не вдалося завантажити повідомлення</string>
@@ -862,6 +862,7 @@
     <string name="unpin">Відкріпити</string>
     <string name="location_services_disabled">Сервіси розташування вимкнено. Увімкнути їх?</string>
     <string name="send_location">Надіслати розташування</string>
+    <string name="forward_location">Переслати розташування</string>
     <string name="unknown_address">Невідома адреса</string>
     <string name="your_location">Ваше розташування</string>
     <string name="network_blocked_title">Фонові дані вимкнено</string>
@@ -1428,6 +1429,10 @@
     <string name="spam_report_short">Поскаржитися</string>
     <string name="wizard_incompatible_contact_sync_params">Встановлено несумісні параметри MDM. Параметр Threema MDM th_contact_sync не може мати значення true, якщо для користувачів діє обмеження DISALLOW_MODIFY_ACCOUNTS. Зверніться до адміністратора пристрою.</string>
     <string name="messages_cannot_be_recovered">Ви не зможете відновити повідомлення.</string>
+    <string name="contact_deleted">Контакт видалено</string>
+    <string name="last_added_contact">Останній доданий</string>
+    <string name="directory_explain_text">Шукайте в каталозі компанії співробітників, яких ще немає у вашому списку контактів.</string>
+    <string name="cannot_display_location">Це розташування неможливо показати.</string>
     <plurals name="contacts_counter_label">
         <item quantity="few">%d контакти</item>
         <item quantity="many">%d контактів</item>

+ 7 - 2
app/src/main/res/values-zh-rCN/strings.xml

@@ -856,6 +856,7 @@ Threema ID。您将不会出现在朋友的联系人列表中。您确定要
     <string name="unpin">取消置顶</string>
     <string name="location_services_disabled">定位服务已禁用。您现在要启用吗?</string>
     <string name="send_location">发送位置</string>
+    <string name="forward_location">转发位置</string>
     <string name="unknown_address">未知地址</string>
     <string name="your_location">您的位置</string>
     <string name="network_blocked_title">后台数据已禁用</string>
@@ -1151,7 +1152,7 @@ Threema ID。您将不会出现在朋友的联系人列表中。您确定要
     <string name="translators_thanks">非常感谢我们的翻译志愿者:\n%s</string>
     <string name="ballot_window_hide">隐藏公开的投票</string>
     <string name="ballot_window_show">显示公开的投票</string>
-    <string name="tooltip_voip_other_party_video_on">对方已发起视频通话,点击这里打开摄像头。</string>
+    <string name="tooltip_voip_other_party_video_on">对方已建立视频通话,点击这里打开摄像头。</string>
     <string name="tooltip_voip_other_party_video_disabled">对方使用的应用程序版本不支持或不允许进行视频通话。</string>
     <string name="biometrics_not_enrolled">系统未注册任何生物识别信息</string>
     <string name="biometrics_not_avilable">生物识别在此系统上不可用。</string>
@@ -1238,7 +1239,7 @@ Threema ID。您将不会出现在朋友的联系人列表中。您确定要
     <string name="group_request_already_sent"><![CDATA[您已经通过这个链接发送了 <b>%s</b> 公开群组加入请求,正在等待群组管理员的回复。如果您想重新发送请求,请再次点击列表中的链接。]]></string>
     <string name="group_link_none">尚未生成链接</string>
     <string name="group_request_confirm_send"><![CDATA[您即将向管理员 <b>%2$s</b> 发送 <b>%1$s</b> 群组加入请求。如果此链接没有失效,管理员一收到您的请求,您就会自动添加到群组。]]></string>
-    <string name="really_delete_outgoing_request">您确定要删除 %d 群组加入的请求吗?请注意,删除请求井不会撤销请求如果群组管理员尚未回复,您仍然可能被批准或拒绝加入群组。</string>
+    <string name="really_delete_outgoing_request">您确定要删除 %d 群组加入的请求吗?请注意,删除请求井不会撤销请求如果群组管理员尚未回复,您仍然可能被批准或拒绝加入群组。</string>
     <string name="really_delete_group_request_title">您确定要删除群组加入请求吗?</string>
     <string name="sent_to">发送到:%s</string>
     <string name="sent_on">发送日期:%s</string>
@@ -1421,6 +1422,10 @@ Threema ID。您将不会出现在朋友的联系人列表中。您确定要
     <string name="spam_report_short">举报</string>
     <string name="wizard_incompatible_contact_sync_params">您可能设置了不兼容的 MDM 参数。如果用户限制设置为 DISALLOW_MODIFY_ACCOUNTS,Threema MDM 参数 th_contact_sync 则无法设置为 true。请联系您的设备管理员以获取帮助。</string>
     <string name="messages_cannot_be_recovered">您将无法恢复消息。</string>
+    <string name="contact_deleted">联系人已删除</string>
+    <string name="last_added_contact">最后添加</string>
+    <string name="directory_explain_text">在公司目录中搜索任何尚未列在您的联系人列表中的员工。</string>
+    <string name="cannot_display_location">无法显示此位置。</string>
     <plurals name="contacts_counter_label">
         <item quantity="other">%d个联系人</item>
     </plurals>

+ 7 - 2
app/src/main/res/values-zh-rTW/strings.xml

@@ -850,6 +850,7 @@ Threema 支援的所有表情符號。</string>
     <string name="unpin">取消置頂</string>
     <string name="location_services_disabled">定位服務已關閉。您想要現在開啟此服務嗎?</string>
     <string name="send_location">傳送位置</string>
+    <string name="forward_location">轉寄位置</string>
     <string name="unknown_address">未知地址</string>
     <string name="your_location">您的位置</string>
     <string name="network_blocked_title">背景數據已停用</string>
@@ -1144,7 +1145,7 @@ Threema 支援的所有表情符號。</string>
     <string name="translators_thanks">非常感謝我們的翻譯志工:\n%s</string>
     <string name="ballot_window_hide">隱藏公開的投票</string>
     <string name="ballot_window_show">顯示公開的投票</string>
-    <string name="tooltip_voip_other_party_video_on">對方已發起視訊通話,點擊這裡開啟攝影機。</string>
+    <string name="tooltip_voip_other_party_video_on">對方已建立視訊通話,點擊這裡開啟攝影機。</string>
     <string name="tooltip_voip_other_party_video_disabled">對方使用的應用程式版本不支援或不允許進行視訊通話。</string>
     <string name="biometrics_not_enrolled">系統中没有錄入生物辨識資料。</string>
     <string name="biometrics_not_avilable">生物辨識資料在此系統上不可用。</string>
@@ -1231,7 +1232,7 @@ Threema 支援的所有表情符號。</string>
     <string name="group_request_already_sent"><![CDATA[您已經透過這個連結傳送了 <b>%s</b> 公開群組加入要求,正在等待群組管理員的回覆。如果您想重新傳送要求,請重按清單中的連結。]]></string>
     <string name="group_link_none">尚未生成連結</string>
     <string name="group_request_confirm_send"><![CDATA[您即將向管理員 <b>%2$s</b> 傳送 <b>%1$s</b> 群組加入要求。如果此連結沒有失效,管理員一收到您的要求,您就會自動新增至群組。]]></string>
-    <string name="really_delete_outgoing_request">您確定要刪除 %d 群組加入的要求嗎?請注意,刪除要求並不會撤銷要求如果群組管理員尚未回覆,您仍然可能被批准或拒絕加入群組。</string>
+    <string name="really_delete_outgoing_request">您確定要刪除 %d 群組加入的要求嗎?請注意,刪除要求並不會撤銷要求如果群組管理員尚未回覆,您仍然可能被批准或拒絕加入群組。</string>
     <string name="really_delete_group_request_title">您確定要刪除群組加入要求嗎?</string>
     <string name="sent_to">傳送至:%s</string>
     <string name="sent_on">傳送日期:%s</string>
@@ -1414,6 +1415,10 @@ Threema 支援的所有表情符號。</string>
     <string name="spam_report_short">檢舉</string>
     <string name="wizard_incompatible_contact_sync_params">您可能設定了不相容的 MDM 參數。如果使用者限制設定為 DISALLOW_MODIFY_ACCOUNTS,Threema MDM 參數 th_contact_sync 則無法設定為 true。請聯絡您的裝置管理員以取得協助。</string>
     <string name="messages_cannot_be_recovered">您將無法還原訊息。</string>
+    <string name="contact_deleted">聯絡人已刪除</string>
+    <string name="last_added_contact">最後新增</string>
+    <string name="directory_explain_text">在公司目錄中搜尋任何尚未列在您的聯絡人清單中的員工。</string>
+    <string name="cannot_display_location">無法顯示此位置。</string>
     <plurals name="contacts_counter_label">
         <item quantity="other">%d位聯絡人</item>
     </plurals>

+ 3 - 0
app/src/main/res/values/strings.xml

@@ -864,6 +864,7 @@
 	<string name="unpin">Unpin</string>
 	<string name="location_services_disabled">Location services are disabled. Would you like to enable them now?</string>
 	<string name="send_location">Send location</string>
+	<string name="forward_location">Forward location</string>
 	<string name="unknown_address">Unknown address</string>
 	<string name="your_location">Your location</string>
 	<string name="network_blocked_title">Background data disabled</string>
@@ -1434,6 +1435,8 @@
 	<string name="messages_cannot_be_recovered">You will not be able to recover the messages.</string>
 	<string name="contact_deleted">Contact deleted</string>
 	<string name="last_added_contact">Last added</string>
+	<string name="directory_explain_text">Search the company directory for any employees who are not yet in your contact list.</string>
+	<string name="cannot_display_location">This location cannot be shown.</string>
 	<plurals name="contacts_counter_label">
 		<item quantity="one">%d contact</item>
 		<item quantity="few">%d contacts</item>

+ 1 - 1
app/src/main/res/values/untranslatable_strings.xml

@@ -18,7 +18,7 @@
 	<string name="audio_focus_loss" translatable="false">Audio focus loss</string>
 	<string name="threema_work" translatable="false">Threema Work</string>
 	<string name="work_explain_url" translatable="false">https://threema.ch/work_info/?lang=%1$s</string>
-	<string name="translators_list" translatable="false">Alperen Zekeri̇ya Çulha, Hakan Sönger, Vit Semenec, Lia Rumantscha (Marina Cajacob, Daniel Telli), よりどりグリーン, Ryota Hasegawa, Xavier Bach-Esteve, Andrej Tobola, Gábor Botzheim, Max Bakalov, Daryna Stremetska, Mathias Lungård, Alieś Cimaškoŭ</string>
+	<string name="translators_list" translatable="false">Alperen Zekeri̇ya Çulha, Hakan Sönger, Önder Nuray, Vit Semenec, Lia Rumantscha (Marina Cajacob, Daniel Telli), よりどりグリーン, Ryota Hasegawa, Xavier Bach-Esteve, Andrej Tobola, Gábor Botzheim, Max Bakalov, Daryna Stremetska, Mathias Lungård, Alieś Cimaškoŭ</string>
 	<string name="threema_safe_password_faq" translatable="false">https://threema.ch/%s/faq/safepw</string>
 	<string name="threema_passwords_faq" translatable="false">https://threema.ch/%s/faq/lost_pass</string>
 	<string name="backup_faq_url" translatable="false">https://threema.ch/%s/faq/data_backup</string>

+ 4 - 1
app/src/onprem/AndroidManifest.xml

@@ -4,7 +4,10 @@
           android:installLocation="internalOnly"
           android:testOnly="false">
 
-	<application tools:ignore="GoogleAppIndexingWarning">
+	<application
+			tools:ignore="GoogleAppIndexingWarning"
+			tools:replace="android:hasFragileUserData"
+			android:hasFragileUserData="false">
 
 		<meta-data
 			android:name="com.google.android.gms.version"

+ 3 - 0
app/src/onprem/res/values-cs/strings.xml

@@ -25,4 +25,7 @@
     <string name="list_theme_light">Světlý</string>
     <string name="list_theme_dark">Tmavý (výchozí)</string>
     <string name="private_threema_download"><![CDATA[Pro osobní používání <a href="https://play.google.com/store/apps/details?id=ch.threema.app">klepněte sem</a> a stáhněte si aplikaci Threema.]]></string>
+	<string name="directory_search">Hledat v adresáři</string>
+    <string name="directory_title">Adresář společnosti</string>
+    <string name="directory_empty_view_text">Chcete-li zahájit vyhledávání v adresáři uživatelů Threema OnPrem ve vaší společnosti, zadejte alespoň 3 znaky</string>
 </resources>

+ 3 - 0
app/src/onprem/res/values-de/strings.xml

@@ -32,5 +32,8 @@
 	<string name="list_theme_dark">Dunkel (standard)</string>
 	<string name="private_threema_download"><![CDATA[Wenn Sie die App privat nutzen möchten, laden Sie Threema bitte <a href="https://play.google.com/store/apps/details?id=ch.threema.app">hier</a> herunter.]]></string>
 	<string name="safe_configure_server_explain">Speichern Sie Ihr Threema Safe-Backup auf dem Server Ihrer Organisation, oder legen Sie einen anderen Backup-Server fest.</string>
+	<string name="directory_search">Im Verzeichnis suchen</string>
+	<string name="directory_title">Firmenverzeichnis</string>
+	<string name="directory_empty_view_text">Bitte geben Sie mindestens 3 Zeichen eines Namens ein, um mit der Suche im Firmenverzeichnis zu beginnen oder wählen Sie eine Kategorie, indem Sie auf das Filter-Symbol tippen.</string>
 </resources>
 

+ 3 - 0
app/src/onprem/res/values-es/strings.xml

@@ -26,4 +26,7 @@
     <string name="list_theme_light">Claro</string>
     <string name="list_theme_dark">Oscuro (predeterminado)</string>
     <string name="private_threema_download"><![CDATA[<a href="https://play.google.com/store/apps/details?id=ch.threema.app">Para usuarios privados</a>]]></string>
+	<string name="directory_search">Buscar en el directorio</string>
+    <string name="directory_title">Directorio de la empresa</string>
+    <string name="directory_empty_view_text">Introduce al menos 3 caracteres de un nombre para comenzar la búsqueda en el directorio de usuarios de Threema OnPrem de tu empresa.</string>
 </resources>

+ 3 - 0
app/src/onprem/res/values-fr/strings.xml

@@ -21,4 +21,7 @@ La paire de clés comprend une <b>clé publie</b> distribuée à vos contacts et
     <string name="list_theme_light">Clair</string>
     <string name="list_theme_dark">Foncé (par défaut)</string>
     <string name="private_threema_download"><![CDATA[<a href="https://play.google.com/store/apps/details?id=ch.threema.app">Pour les utilisateurs privés</a>]]></string>
+    <string name="directory_search">Rechercher dans le répertoire</string>
+    <string name="directory_title">Répertoire de l\'entreprise</string>
+    <string name="directory_empty_view_text">Veuillez saisir au moins 3 caractères d\'un nom pour lancer une recherche dans le répertoire d\'utilisateurs Threema OnPrem de votre entreprise</string>
 </resources>

+ 3 - 0
app/src/onprem/res/values-it/strings.xml

@@ -22,4 +22,7 @@ in modo anonimo.</string>
     <string name="list_theme_light">Chiaro</string>
     <string name="list_theme_dark">Scuro (default)</string>
     <string name="private_threema_download"><![CDATA[<a href="https://play.google.com/store/apps/details?id=ch.threema.app">Per utenti privati</a>]]></string>
+	<string name="directory_search">Cerca nella directory</string>
+    <string name="directory_title">Directory azienda</string>
+    <string name="directory_empty_view_text">Inserisci almeno 3 caratteri di un nome per iniziare a cercare nella directory della tua azienda degli utenti di Threema OnPrem o seleziona una categoria toccando sull\'icona del filtro.</string>
 </resources>

+ 3 - 0
app/src/onprem/res/values-nl-rNL/strings.xml

@@ -20,4 +20,7 @@
     <string name="list_theme_light">Licht</string>
     <string name="list_theme_dark">Donker (standaard)</string>
     <string name="private_threema_download"><![CDATA[<a href="https://play.google.com/store/apps/details?id=ch.threema.app">Voor privégebruikers</a>]]></string>
+	<string name="directory_search">ディレクトリ内検索</string>
+    <string name="directory_title">会社のディレクトリ</string>
+    <string name="directory_empty_view_text">あなたの会社のThreema OnPremユーザーのディレクトリで検索を開始するには、少なくとも3文字を入力してください。</string>
 </resources>

+ 3 - 0
app/src/onprem/res/values-pl/strings.xml

@@ -20,4 +20,7 @@
     <string name="list_theme_light">Jasny</string>
     <string name="list_theme_dark">Ciemny (domyślny)</string>
     <string name="private_threema_download"><![CDATA[<a href="https://play.google.com/store/apps/details?id=ch.threema.app">Dla użytkowników prywatnych</a>]]></string>
+	<string name="directory_search">Szukaj w książce telefonicznej</string>
+    <string name="directory_title">Firmowa książka telefoniczna</string>
+    <string name="directory_empty_view_text">Wpisz co najmniej 3 znaki nazwy, aby rozpocząć wyszukiwanie w Twojej firmowej książce telefonicznej użytkowników Threema OnPrem.</string>
 </resources>

Kaikkia tiedostoja ei voida näyttää, sillä liian monta tiedostoa muuttui tässä diffissä