ソースを参照

Version 4.5-beta2

Threema 5 年 前
コミット
2c4679f9b6
28 ファイル変更280 行追加371 行削除
  1. 4 4
      app/build.gradle
  2. 22 3
      app/jni/Android.mk
  3. 0 0
      app/jni/nacl/curve25519-jni.c
  4. 0 0
      app/jni/nacl/poly1305-jni.c
  5. 0 0
      app/jni/nacl/salsa20-jni.c
  6. 0 114
      app/jni/scrypt_jni.c
  7. 2 2
      app/src/main/java/ch/threema/app/activities/SendMediaActivity.java
  8. 14 4
      app/src/main/java/ch/threema/app/activities/ThreemaToolbarActivity.java
  9. 2 2
      app/src/main/java/ch/threema/app/fragments/ComposeMessageFragment.java
  10. 1 1
      app/src/main/java/ch/threema/app/mediaattacher/MediaPreviewActivity.java
  11. 10 0
      app/src/main/java/ch/threema/app/mediaattacher/MediaSelectionActivity.java
  12. 46 29
      app/src/main/java/ch/threema/app/mediaattacher/MediaSelectionBaseActivity.java
  13. 26 29
      app/src/main/java/ch/threema/app/preference/SettingsSecurityFragment.java
  14. 18 2
      app/src/main/java/ch/threema/app/preference/SettingsTroubleshootingFragment.java
  15. 65 101
      app/src/main/java/ch/threema/app/services/MessageServiceImpl.java
  16. 46 56
      app/src/main/res/layout/activity_media_attach.xml
  17. 2 2
      app/src/main/res/values-cs/strings.xml
  18. 2 2
      app/src/main/res/values-de/strings.xml
  19. 2 2
      app/src/main/res/values-es/strings.xml
  20. 2 2
      app/src/main/res/values-fr/strings.xml
  21. 2 2
      app/src/main/res/values-it/strings.xml
  22. 2 2
      app/src/main/res/values-nl-rNL/strings.xml
  23. 2 2
      app/src/main/res/values-pl/strings.xml
  24. 2 2
      app/src/main/res/values-pt-rBR/strings.xml
  25. 2 2
      app/src/main/res/values-rm/strings.xml
  26. 2 2
      app/src/main/res/values-ru/strings.xml
  27. 2 2
      app/src/main/res/values-tr/strings.xml
  28. 2 2
      app/src/main/res/values/strings.xml

+ 4 - 4
app/build.gradle

@@ -75,8 +75,8 @@ android {
         vectorDrawables.useSupportLibrary = true
         applicationId "ch.threema.app"
         testApplicationId 'ch.threema.app.test'
-        versionCode 655
-        versionName "4.5-beta1"
+        versionCode 656
+        versionName "4.5-beta2"
         resValue "string", "version_name_suffix", ""
         resValue "string", "app_name", "Threema"
         resValue "string", "uri_scheme", "threema"
@@ -141,7 +141,7 @@ android {
         }
         store_threema { }
         store_google_work {
-            versionName "4.5k-beta1"
+            versionName "4.5k-beta2"
             applicationId "ch.threema.app.work"
             testApplicationId 'ch.threema.app.work.test'
             resValue "string", "package_name", applicationId
@@ -178,7 +178,7 @@ android {
             buildConfigField "byte[]", "SERVER_PUBKEY_ALT", "new byte[] {(byte) 0x5a, (byte) 0x98, (byte) 0xf2, (byte) 0x3d, (byte) 0xe6, (byte) 0x56, (byte) 0x05, (byte) 0xd0, (byte) 0x50, (byte) 0xdc, (byte) 0x00, (byte) 0x64, (byte) 0xbe, (byte) 0x07, (byte) 0xdd, (byte) 0xdd, (byte) 0x81, (byte) 0x1d, (byte) 0xa1, (byte) 0x16, (byte) 0xa5, (byte) 0x43, (byte) 0xce, (byte) 0x43, (byte) 0xaa, (byte) 0x26, (byte) 0x87, (byte) 0xd1, (byte) 0x9f, (byte) 0x20, (byte) 0xaf, (byte) 0x3c }"
         }
         sandbox_work {
-            versionName "4.5k-beta1"
+            versionName "4.5k-beta2"
             applicationId "ch.threema.app.sandbox.work"
             testApplicationId 'ch.threema.app.sandbox.work.test'
 

+ 22 - 3
app/jni/Android.mk

@@ -1,21 +1,40 @@
+# Makefile for native JNI libraries. To be built with ndk-build.
+#
+# To view the commands that will be run when building the application, run
+#
+#     ndk-build -B --dry-run
+#
+# NOTE: Do not use `$(wildcard ...)` in this script! It makes the linking order
+#       non-deterministic.
+
 LOCAL_PATH       := $(call my-dir)
 
 TARGET_PLATFORM  := android-26
 
+# libscrypt
+
 include $(CLEAR_VARS)
+
 LOCAL_MODULE     := scrypt
 
-LOCAL_SRC_FILES  := $(wildcard $(LOCAL_PATH)/scrypt/c/*.c)
+LOCAL_SRC_FILES  := $(LOCAL_PATH)/scrypt/c/scrypt_jni.c
+LOCAL_SRC_FILES  += $(LOCAL_PATH)/scrypt/c/crypto_scrypt-nosse.c
+LOCAL_SRC_FILES  += $(LOCAL_PATH)/scrypt/c/sha256.c
 LOCAL_C_INCLUDES := $(LOCAL_PATH)/scrypt/include
 
 LOCAL_CFLAGS     += -DANDROID -DHAVE_CONFIG_H -DANDROID_TARGET_ARCH="$(TARGET_ARCH)"
-LOCAL_CFLAGS     += -D_FORTIFY_SOURCE=2
 LOCAL_LDFLAGS    += -lc -llog
 
 include $(BUILD_SHARED_LIBRARY)
 
+# libnacl
+
 include $(CLEAR_VARS)
+
 LOCAL_MODULE     := nacl-jni
-LOCAL_SRC_FILES  := salsa20-jni.c poly1305-jni.c curve25519-jni.c
+
+LOCAL_SRC_FILES  := $(LOCAL_PATH)/nacl/salsa20-jni.c
+LOCAL_SRC_FILES  += $(LOCAL_PATH)/nacl/poly1305-jni.c
+LOCAL_SRC_FILES  += $(LOCAL_PATH)/nacl/curve25519-jni.c
 
 include $(BUILD_SHARED_LIBRARY)

+ 0 - 0
app/jni/curve25519-jni.c → app/jni/nacl/curve25519-jni.c


+ 0 - 0
app/jni/poly1305-jni.c → app/jni/nacl/poly1305-jni.c


+ 0 - 0
app/jni/salsa20-jni.c → app/jni/nacl/salsa20-jni.c


+ 0 - 114
app/jni/scrypt_jni.c

@@ -1,114 +0,0 @@
-// Copyright (C) 2011 - Will Glozer.  All rights reserved.
-
-#include <errno.h>
-#include <stdlib.h>
-#include <inttypes.h>
-
-#include <jni.h>
-#include "crypto_scrypt.h"
-
-#ifdef ANDROID
-
-#include <android/log.h>
-#include <stdint.h>
-
-#define ANDROID_LOG_TAG "ScryptLog"
-#define ALOG(msg, ...) __android_log_print(ANDROID_LOG_VERBOSE, ANDROID_LOG_TAG, msg, ##__VA_ARGS__)
-
-#define STR1(x)  #x
-#define STR(x)  STR1(x)
-
-void log_basic_info();
-
-#endif
-
-jbyteArray JNICALL scryptN(JNIEnv *env, jclass cls, jbyteArray passwd, jbyteArray salt,
-    jint N, jint r, jint p, jint dkLen)
-{
-
-#ifdef ANDROID
-  log_basic_info();
-#endif
-
-    jint Plen = (*env)->GetArrayLength(env, passwd);
-    jint Slen = (*env)->GetArrayLength(env, salt);
-    jbyte *P = (*env)->GetByteArrayElements(env, passwd, NULL);
-    jbyte *S = (*env)->GetByteArrayElements(env, salt,   NULL);
-    uint8_t *buf = malloc(sizeof(uint8_t) * dkLen);
-    jbyteArray DK = NULL;
-
-    if (P == NULL || S == NULL || buf == NULL) goto cleanup;
-
-    if (crypto_scrypt((uint8_t *) P, Plen, (uint8_t *) S, Slen, N, r, p, buf, dkLen)) {
-        jclass e = (*env)->FindClass(env, "java/lang/IllegalArgumentException");
-        char *msg;
-        switch (errno) {
-            case EINVAL:
-                msg = "N must be a power of 2 greater than 1";
-                break;
-            case EFBIG:
-            case ENOMEM:
-                msg = "Insufficient memory available";
-                break;
-            default:
-                msg = "Memory allocation failed";
-        }
-        (*env)->ThrowNew(env, e, msg);
-        goto cleanup;
-    }
-
-    DK = (*env)->NewByteArray(env, dkLen);
-    if (DK == NULL) goto cleanup;
-
-    (*env)->SetByteArrayRegion(env, DK, 0, dkLen, (jbyte *) buf);
-
-  cleanup:
-
-    if (P) (*env)->ReleaseByteArrayElements(env, passwd, P, JNI_ABORT);
-    if (S) (*env)->ReleaseByteArrayElements(env, salt,   S, JNI_ABORT);
-    if (buf) free(buf);
-
-    return DK;
-}
-
-#ifdef ANDROID
-
-char *get_byte_array_summary(JNIEnv *env, jbyteArray jarray) {
-  int len = (*env)->GetArrayLength(env, jarray);
-  jbyte *bytes = (*env)->GetByteArrayElements(env, jarray, NULL);
-
-  static char buff[10240];
-  int i;
-  for (i = 0; i < len; ++i) {
-    buff[i] = bytes[i] % 32 + 'a';
-  }
-  buff[i] = '\0';
-
-  if (bytes) (*env)->ReleaseByteArrayElements(env, jarray, bytes, JNI_ABORT);
-
-  return buff;
-}
-
-void log_basic_info() {
-  ALOG("Basic info for native scrypt run:");
-  ALOG("Native library targeting arch: %s", STR(ANDROID_TARGET_ARCH));
-}
-
-#endif
-
-static const JNINativeMethod methods[] = {
-    { "scryptN", "([B[BIIII)[B", (void *) scryptN }
-};
-
-jint JNI_OnLoad(JavaVM *vm, void *reserved) {
-    JNIEnv *env;
-
-    if ((*vm)->GetEnv(vm, (void **) &env, JNI_VERSION_1_6) != JNI_OK) {
-        return -1;
-    }
-
-    jclass cls = (*env)->FindClass(env, "com/lambdaworks/crypto/SCrypt");
-    int r = (*env)->RegisterNatives(env, cls, methods, 1);
-
-    return (r == JNI_OK) ? JNI_VERSION_1_6 : -1;
-}

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

@@ -207,8 +207,8 @@ public class SendMediaActivity extends ThreemaToolbarActivity implements
 					@Override
 					public WindowInsetsCompat onApplyWindowInsets(View v, WindowInsetsCompat insets) {
 
-						logger.debug("%%% system window top " + insets.getSystemWindowInsetTop() + " bottom " + insets.getSystemWindowInsetBottom());
-						logger.debug("%%% stable insets top " + insets.getStableInsetTop() + " bottom " + insets.getStableInsetBottom());
+						logger.info("%%% system window top " + insets.getSystemWindowInsetTop() + " bottom " + insets.getSystemWindowInsetBottom());
+						logger.info("%%% stable insets top " + insets.getStableInsetTop() + " bottom " + insets.getStableInsetBottom());
 
 						if (insets.getSystemWindowInsetBottom() == insets.getStableInsetBottom()) {
 							onSoftKeyboardClosed();

+ 14 - 4
app/src/main/java/ch/threema/app/activities/ThreemaToolbarActivity.java

@@ -248,6 +248,7 @@ public abstract class ThreemaToolbarActivity extends ThreemaActivity implements
 	private static final String LANDSCAPE_HEIGHT = "kbd_landscape_height";
 	private final Set<OnSoftKeyboardChangedListener> softKeyboardChangedListeners = new HashSet<>();
 	private boolean softKeyboardOpen = false;
+	private int minKeyboardSize;
 
 	public interface OnSoftKeyboardChangedListener {
 		void onKeyboardHidden();
@@ -281,13 +282,19 @@ public abstract class ThreemaToolbarActivity extends ThreemaActivity implements
 	}
 
 	public void onSoftKeyboardOpened(int softKeyboardHeight) {
-		this.softKeyboardOpen = true;
-		saveSoftKeyboardHeight(softKeyboardHeight);
+		logger.info("Soft keyboard opened. Height = " + softKeyboardHeight + " Min = " + minKeyboardSize);
 
-		notifySoftKeyboardShown();
+		if (softKeyboardHeight >= minKeyboardSize) {
+			this.softKeyboardOpen = true;
+			saveSoftKeyboardHeight(softKeyboardHeight);
+
+			notifySoftKeyboardShown();
+		}
 	}
 
 	public void onSoftKeyboardClosed() {
+		logger.info("Soft keyboard closed");
+
 		this.softKeyboardOpen = false;
 
 		notifySoftKeyboardHidden();
@@ -344,9 +351,11 @@ public abstract class ThreemaToolbarActivity extends ThreemaActivity implements
 
 	public void saveSoftKeyboardHeight(int softKeyboardHeight) {
 		if (ConfigUtils.isLandscape(this)) {
+			logger.info("Keyboard height (landscape): " + softKeyboardHeight);
 			PreferenceManager.getDefaultSharedPreferences(this)
 				.edit().putInt(LANDSCAPE_HEIGHT, softKeyboardHeight).apply();
 		} else {
+			logger.info("Keyboard height (portrait): " + softKeyboardHeight);
 			PreferenceManager.getDefaultSharedPreferences(this)
 				.edit().putInt(PORTRAIT_HEIGHT, softKeyboardHeight).apply();
 		}
@@ -359,7 +368,7 @@ public abstract class ThreemaToolbarActivity extends ThreemaActivity implements
 			PreferenceManager.getDefaultSharedPreferences(this).getInt(LANDSCAPE_HEIGHT, getResources().getDimensionPixelSize(R.dimen.default_emoji_picker_height_landscape)) :
 			PreferenceManager.getDefaultSharedPreferences(this).getInt(PORTRAIT_HEIGHT, getResources().getDimensionPixelSize(R.dimen.default_emoji_picker_height));
 
-		if (defaultSoftKeyboardHeight < getResources().getDimensionPixelSize(R.dimen.min_keyboard_size)) {
+		if (defaultSoftKeyboardHeight < minKeyboardSize) {
 			defaultSoftKeyboardHeight = getResources().getDimensionPixelSize(isLandscape ?
 				R.dimen.default_emoji_picker_height_landscape :
 				R.dimen.default_emoji_picker_height);
@@ -376,6 +385,7 @@ public abstract class ThreemaToolbarActivity extends ThreemaActivity implements
 	}
 
 	public void resetKeyboard() {
+		minKeyboardSize = getResources().getDimensionPixelSize(R.dimen.min_keyboard_size);
 		removeAllListeners();
 		softKeyboardOpen = false;
 	}

+ 2 - 2
app/src/main/java/ch/threema/app/fragments/ComposeMessageFragment.java

@@ -1121,8 +1121,8 @@ public class ComposeMessageFragment extends Fragment implements
 					@Override
 					public WindowInsetsCompat onApplyWindowInsets(View v, WindowInsetsCompat insets) {
 
-						logger.debug("%%% system window top " + insets.getSystemWindowInsetTop() + " bottom " + insets.getSystemWindowInsetBottom());
-						logger.debug("%%% stable insets top " + insets.getStableInsetTop() + " bottom " + insets.getStableInsetBottom());
+						logger.info("%%% system window top " + insets.getSystemWindowInsetTop() + " bottom " + insets.getSystemWindowInsetBottom());
+						logger.info("%%% stable insets top " + insets.getStableInsetTop() + " bottom " + insets.getStableInsetBottom());
 
 						if (insets.getSystemWindowInsetBottom() == insets.getStableInsetBottom()) {
 							activity.onSoftKeyboardClosed();

+ 1 - 1
app/src/main/java/ch/threema/app/mediaattacher/MediaPreviewActivity.java

@@ -70,6 +70,6 @@ public class MediaPreviewActivity extends FragmentActivity {
 	@Override
 	public void finish() {
 		super.finish();
-		overridePendingTransition(R.anim.medium_fade_in, R.anim.medium_fade_out);
+		overridePendingTransition(R.anim.fast_fade_in, R.anim.fast_fade_out);
 	}
 }

+ 10 - 0
app/src/main/java/ch/threema/app/mediaattacher/MediaSelectionActivity.java

@@ -38,6 +38,7 @@ import java.util.ArrayList;
 import androidx.annotation.NonNull;
 import androidx.constraintlayout.widget.ConstraintLayout;
 import androidx.core.app.ActivityCompat;
+import androidx.core.content.ContextCompat;
 import ch.threema.app.R;
 import ch.threema.app.activities.SendMediaActivity;
 import ch.threema.app.activities.ThreemaActivity;
@@ -129,6 +130,15 @@ public class MediaSelectionActivity extends MediaSelectionBaseActivity {
 		});
 	}
 
+	/**
+	 * Check if the media attacher's selectable media grid can be shown
+	 * @return true if option has been enabled by user and permissions are available
+	 */
+	@Override
+	protected boolean shouldShowMediaGrid() {
+		return ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED;
+	}
+
 	@Override
 	public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
 		if (grantResults.length == 0 || grantResults[0] != PackageManager.PERMISSION_GRANTED) {

+ 46 - 29
app/src/main/java/ch/threema/app/mediaattacher/MediaSelectionBaseActivity.java

@@ -22,6 +22,8 @@
 package ch.threema.app.mediaattacher;
 
 import android.Manifest;
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
 import android.annotation.SuppressLint;
 import android.content.Context;
 import android.content.Intent;
@@ -35,7 +37,6 @@ import android.os.Build;
 import android.os.Bundle;
 import android.provider.BaseColumns;
 import android.text.TextUtils;
-import android.transition.TransitionManager;
 import android.util.DisplayMetrics;
 import android.util.TypedValue;
 import android.view.Gravity;
@@ -55,7 +56,6 @@ import android.widget.TextView;
 import com.getkeepsafe.taptargetview.TapTarget;
 import com.getkeepsafe.taptargetview.TapTargetView;
 import com.google.android.material.appbar.AppBarLayout;
-import com.google.android.material.appbar.CollapsingToolbarLayout;
 import com.google.android.material.appbar.MaterialToolbar;
 import com.google.android.material.bottomsheet.BottomSheetBehavior;
 
@@ -74,7 +74,6 @@ import androidx.appcompat.widget.PopupMenu;
 import androidx.appcompat.widget.PopupMenuWrapper;
 import androidx.appcompat.widget.SearchView;
 import androidx.constraintlayout.widget.ConstraintLayout;
-import androidx.constraintlayout.widget.ConstraintSet;
 import androidx.coordinatorlayout.widget.CoordinatorLayout;
 import androidx.core.content.ContextCompat;
 import androidx.cursoradapter.widget.CursorAdapter;
@@ -96,6 +95,7 @@ import ch.threema.app.ui.MediaGridItemDecoration;
 import ch.threema.app.ui.MediaItem;
 import ch.threema.app.ui.OnKeyboardBackRespondingSearchView;
 import ch.threema.app.ui.SingleToast;
+import ch.threema.app.utils.AnimationUtil;
 import ch.threema.app.utils.ConfigUtils;
 import ch.threema.app.utils.LocaleUtil;
 import ch.threema.app.utils.RuntimeUtil;
@@ -291,6 +291,22 @@ abstract public class MediaSelectionBaseActivity extends ThreemaActivity impleme
 				}
 			});
 		}
+
+		this.rootView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
+			@Override
+			public void onGlobalLayout() {
+				rootView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
+
+				// adjust height of bottom sheet container to snap smack below toolbar
+				CoordinatorLayout bottomSheetContainer = findViewById(R.id.bottom_sheet_container);
+				int topMargin = toolbar.getHeight() - getResources().getDimensionPixelSize(R.dimen.drag_handle_height) -
+					(getResources().getDimensionPixelSize(R.dimen.drag_handle_topbottom_margin) * 2);
+
+				CoordinatorLayout.LayoutParams bottomSheetContainerLayoutParams = (CoordinatorLayout.LayoutParams) bottomSheetContainer.getLayoutParams();
+				bottomSheetContainerLayoutParams.setMargins(0, topMargin, 0, 0);
+				bottomSheetContainer.setLayoutParams(bottomSheetContainerLayoutParams);
+			}
+		});
 	}
 
 	protected void setMenu() {
@@ -423,8 +439,7 @@ abstract public class MediaSelectionBaseActivity extends ThreemaActivity impleme
 	}
 
 	protected void setListeners() {
-		CollapsingToolbarLayout collapsingToolbarLayout = findViewById(R.id.collapsing_toolbar);
-		collapsingToolbarLayout.setOnClickListener(this);
+		this.appBarLayout.setOnClickListener(this);
 
 		this.searchItem.setOnActionExpandListener(new MenuItem.OnActionExpandListener() {
 			@Override
@@ -477,6 +492,7 @@ abstract public class MediaSelectionBaseActivity extends ThreemaActivity impleme
 		}
 
 		BottomSheetBehavior<ConstraintLayout> bottomSheetBehavior = BottomSheetBehavior.from(bottomSheetLayout);
+		bottomSheetBehavior.setExpandedOffset(50);
 		bottomSheetBehavior.addBottomSheetCallback(new BottomSheetBehavior.BottomSheetCallback() {
 			@Override
 			public void onStateChanged(@NonNull View bottomSheet, int newState) {
@@ -508,8 +524,7 @@ abstract public class MediaSelectionBaseActivity extends ThreemaActivity impleme
 	public void onItemLongClick(View view, int position, MediaAttachItem mediaAttachItem) {
 		Intent intent = new Intent(this, MediaPreviewActivity.class);
 		intent.putExtra(MediaPreviewActivity.EXTRA_PARCELABLE_MEDIA_ITEM, mediaAttachItem);
-		startActivity(intent);
-		overridePendingTransition(R.anim.medium_fade_in, R.anim.medium_fade_out);
+		AnimationUtil.startActivity(this, view, intent);
 	}
 
 	@UiThread
@@ -649,17 +664,19 @@ abstract public class MediaSelectionBaseActivity extends ThreemaActivity impleme
 				menuTitleFrame.setClickable(true);
 				searchView.findViewById(R.id.search_button).setClickable(true);
 
+				toolbar.setAlpha(0f);
 				toolbar.setVisibility(View.VISIBLE);
+				toolbar.animate()
+					.alpha(1f)
+					.setDuration(100)
+					.setListener(null);
 				if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
-						getWindow().setStatusBarColor(ConfigUtils.getColorFromAttribute(this, R.attr.attach_status_bar_color_expanded));
+					toolbar.postDelayed(
+						() -> getWindow().setStatusBarColor(ConfigUtils.getColorFromAttribute(this, R.attr.attach_status_bar_color_expanded)),
+						50
+					);
 				}
 
-				ConstraintSet constraintSet = new ConstraintSet();
-				constraintSet.clone(bottomSheetLayout);
-				constraintSet.connect(R.id.media_grid_recycler, ConstraintSet.TOP, R.id.bottom_sheet, ConstraintSet.TOP, 0);
-				TransitionManager.beginDelayedTransition(rootView);
-				constraintSet.applyTo(bottomSheetLayout);
-
 				if (mediaAttachViewModel.getSelectedMediaItemsHashMap().isEmpty()) {
 					controlPanel.animate().translationY(controlPanel.getHeight());
 				} else {
@@ -674,21 +691,21 @@ abstract public class MediaSelectionBaseActivity extends ThreemaActivity impleme
 				dateView.setVisibility(View.GONE);
 				dragHandle.setVisibility(View.VISIBLE);
 
-				ConstraintSet constraintSet2 = new ConstraintSet();
-				constraintSet2.clone(bottomSheetLayout);
-				constraintSet2.connect(R.id.drag_handle, ConstraintSet.TOP, R.id.bottom_sheet, ConstraintSet.TOP, pixelmargin);
-				constraintSet2.connect(R.id.drag_handle, ConstraintSet.BOTTOM, R.id.media_grid_recycler, ConstraintSet.TOP, pixelmargin);
-				constraintSet2.connect(R.id.media_grid_recycler, ConstraintSet.TOP, R.id.drag_handle, ConstraintSet.BOTTOM, 0);
-				TransitionManager.beginDelayedTransition(rootView);
-				constraintSet2.applyTo(bottomSheetLayout);
-
-				toolbar.setVisibility(View.GONE);
-				if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
-					toolbar.postDelayed(
-						() -> getWindow().setStatusBarColor(ConfigUtils.getColorFromAttribute(this, R.attr.attach_status_bar_color_collapsed)),
-						50
-					);
-				}
+				toolbar.setAlpha(1f);
+				toolbar.animate()
+					.alpha(0f)
+					.setDuration(100)
+					.setListener(new AnimatorListenerAdapter() {
+						@Override
+						public void onAnimationEnd(Animator animation) {
+							toolbar.setVisibility(View.GONE);
+						}
+					});
+				toolbar.postDelayed(() -> {
+					if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+						getWindow().setStatusBarColor(ConfigUtils.getColorFromAttribute(this, R.attr.attach_status_bar_color_collapsed));
+					}
+				}, 50);
 				break;
 			case STATE_COLLAPSED:
 				dateView.setVisibility(View.GONE);

+ 26 - 29
app/src/main/java/ch/threema/app/preference/SettingsSecurityFragment.java

@@ -100,38 +100,17 @@ public class SettingsSecurityFragment extends ThreemaPreferenceFragment implemen
 		hiddenChatsListService = ThreemaApplication.getServiceManager().getHiddenChatsListService();
 
 		addPreferencesFromResource(R.xml.preference_security);
-		preferenceScreen = (PreferenceScreen) findPreference("pref_key_security");
-
-		if (preferenceScreen == null) {
-			return;
-		}
-		// disable prefs for now - we will re-enable them when prefs are unlocked
-		preferenceScreen.setEnabled(false);
-
-		// ask for pin before entering
-		if (preferenceService.getLockMechanism().equals(LockingMech_NONE)) {
-			onCreateUnlocked();
-		} else {
-			if ((preferenceService.getLockMechanism().equals(PreferenceService.LockingMech_PIN)) && !preferenceService.isPinSet()) {
-				// fix misconfiguration
-				preferenceService.setLockMechanism(LockingMech_NONE);
-				onCreateUnlocked();
-			} else {
-				if (savedInstanceState == null) {
-					HiddenChatUtil.launchLockCheckDialog(this, preferenceService);
-				}
-			}
-		}
+		preferenceScreen = findPreference("pref_key_security");
 	}
 
 	private void onCreateUnlocked() {
 		logger.debug("### onCreateUnlocked");
-		preferenceScreen.setEnabled(true);
+		fragmentView.setVisibility(View.VISIBLE);
 
-		uiLockSwitchPreference = (TwoStatePreference) findPreference(getResources().getString(R.string.preferences__lock_ui_switch));
-		lockMechanismPreference = (DropDownPreference) findPreference(getResources().getString(R.string.preferences__lock_mechanism));
+		uiLockSwitchPreference = findPreference(getResources().getString(R.string.preferences__lock_ui_switch));
+		lockMechanismPreference = findPreference(getResources().getString(R.string.preferences__lock_mechanism));
 		pinPreference = findPreference(getResources().getString(R.string.preferences__pin_lock_code));
-		gracePreference = (DropDownPreference) findPreference(getResources().getString(R.string.preferences__pin_lock_grace_time));
+		gracePreference = findPreference(getResources().getString(R.string.preferences__pin_lock_grace_time));
 
 		//get pin switch pref from service!
 		uiLockSwitchPreference.setChecked(preferenceService.isAppLockEnabled());
@@ -349,15 +328,33 @@ public class SettingsSecurityFragment extends ThreemaPreferenceFragment implemen
 				preferenceService.setPin(null);
 				break;
 		}
-
 	}
 
 	@Override
-	public void onViewCreated(View view, Bundle savedInstanceState) {
+	public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {
+		logger.debug("### onViewCreated");
+
+		// we make the complete fragment invisible until it's been unlocked
 		fragmentView = view;
+		fragmentView.setVisibility(View.INVISIBLE);
 
 		preferenceFragmentCallbackInterface.setToolbarTitle(R.string.prefs_security);
 		super.onViewCreated(view, savedInstanceState);
+
+		// ask for pin before entering
+		if (preferenceService.getLockMechanism().equals(LockingMech_NONE)) {
+			onCreateUnlocked();
+		} else {
+			if ((preferenceService.getLockMechanism().equals(PreferenceService.LockingMech_PIN)) && !preferenceService.isPinSet()) {
+				// fix misconfiguration
+				preferenceService.setLockMechanism(LockingMech_NONE);
+				onCreateUnlocked();
+			} else {
+				if (savedInstanceState == null) {
+					HiddenChatUtil.launchLockCheckDialog(this, preferenceService);
+				}
+			}
+		}
 	}
 
 	@Override
@@ -478,7 +475,7 @@ public class SettingsSecurityFragment extends ThreemaPreferenceFragment implemen
 				@Override
 				public void onAuthenticationSucceeded(@NonNull BiometricPrompt.AuthenticationResult result) {
 					super.onAuthenticationSucceeded(result);
-					Snackbar.make(fragmentView, R.string.biometric_authnetication_successful, Snackbar.LENGTH_LONG).show();
+					Snackbar.make(fragmentView, R.string.biometric_authentication_successful, Snackbar.LENGTH_LONG).show();
 
 					lockMechanismPreference.setValue(PreferenceService.LockingMech_BIOMETRIC);
 					if (uiLockSwitchPreference.isChecked()) {

+ 18 - 2
app/src/main/java/ch/threema/app/preference/SettingsTroubleshootingFragment.java

@@ -62,6 +62,7 @@ import ch.threema.app.FcmRegistrationIntentService;
 import ch.threema.app.R;
 import ch.threema.app.ThreemaApplication;
 import ch.threema.app.activities.DisableBatteryOptimizationsActivity;
+import ch.threema.app.activities.RecipientListBaseActivity;
 import ch.threema.app.dialogs.CancelableHorizontalProgressDialog;
 import ch.threema.app.dialogs.GenericAlertDialog;
 import ch.threema.app.dialogs.GenericProgressDialog;
@@ -85,6 +86,7 @@ import ch.threema.app.ui.MediaItem;
 import ch.threema.app.utils.AppRestrictionUtil;
 import ch.threema.app.utils.ConfigUtils;
 import ch.threema.app.utils.DialogUtil;
+import ch.threema.app.utils.MimeUtil;
 import ch.threema.app.utils.PowermanagerUtil;
 import ch.threema.app.utils.PushUtil;
 import ch.threema.app.utils.RuntimeUtil;
@@ -589,8 +591,22 @@ public class SettingsTroubleshootingFragment extends ThreemaPreferenceFragment i
 							ConfigUtils.getFullAppVersion(getActivity()) + "\n" +
 							userService.getIdentity(), receiver);
 
-					messageService.sendMedia(Collections.singletonList(new MediaItem(Uri.fromFile(zipFile), MediaItem.TYPE_NONE)),
-						Collections.singletonList(receiver));
+					MediaItem mediaItem = new MediaItem(Uri.fromFile(zipFile), MediaItem.TYPE_NONE);
+					mediaItem.setFilename(zipFile.getName());
+					mediaItem.setMimeType(MimeUtil.MIME_TYPE_ZIP);
+
+					messageService.sendMedia(Collections.singletonList(mediaItem),
+						Collections.singletonList(receiver), new RecipientListBaseActivity.SendCompletionHandler() {
+							@Override
+							public void onError(String errorMessage) {
+								RuntimeUtil.runOnUiThread(() -> Toast.makeText(getContext(), R.string.an_error_occurred_during_send, Toast.LENGTH_LONG).show());
+							}
+
+							@Override
+							public void onCompleted() {
+								RuntimeUtil.runOnUiThread(() -> Toast.makeText(getContext(), R.string.message_sent, Toast.LENGTH_LONG).show());
+							}
+						});
 				} catch (Exception e) {
 					return e;
 				}

+ 65 - 101
app/src/main/java/ch/threema/app/services/MessageServiceImpl.java

@@ -34,7 +34,6 @@ import android.net.ConnectivityManager;
 import android.net.NetworkInfo;
 import android.net.Uri;
 import android.provider.DocumentsContract;
-import android.provider.OpenableColumns;
 import android.text.format.DateUtils;
 import android.util.SparseIntArray;
 import android.widget.Toast;
@@ -3655,17 +3654,14 @@ public class MessageServiceImpl implements MessageService {
 		for (MediaItem mediaItem : mediaItems) {
 			final Map<MessageReceiver, AbstractMessageModel> messageModels = new HashMap<>();
 
-			final FileDataModel fileDataModel = createFileDataModel(
-				context,
-				mediaItem.getUri(),
-				context.getContentResolver(),
-				null,
-				mediaItem.getMimeType(),
-				mediaItem.getRenderingType()
-			);
+			final FileDataModel fileDataModel = createFileDataModel(context, mediaItem);
+			if (fileDataModel == null) {
+				logger.info("Unable to create FileDataModel");
+				continue;
+			}
 
 			if (!createMessagesAndSetPending(mediaItem, resolvedReceivers, messageModels, fileDataModel)) {
-				logger.debug("Unable to create messages ");
+				logger.info("Unable to create messages ");
 				continue;
 			}
 
@@ -3681,7 +3677,7 @@ public class MessageServiceImpl implements MessageService {
 
 			final byte[] contentData = generateContentData(mediaItem, resolvedReceivers, messageModels, fileDataModel);
 			if (contentData != null) {
-				if (encryptAndSend(mediaItem, resolvedReceivers, messageModels, fileDataModel, thumbnailData, contentData)) {
+				if (encryptAndSend(resolvedReceivers, messageModels, fileDataModel, thumbnailData, contentData)) {
 					successfulMessageModel = messageModels.get(resolvedReceivers[0]);
 				}
 			} else {
@@ -3728,38 +3724,7 @@ public class MessageServiceImpl implements MessageService {
 	 * Update the FileDataModel with data from the MediaItem such as file name and rendering type
 	 */
 	private void updateFileDataModel(@NonNull MediaItem mediaItem, @NonNull FileDataModel fileDataModel) {
-		fileDataModel.setCaption(mediaItem.getCaption());
-		fileDataModel.isDownloaded(true);
-		if (TestUtil.empty(mediaItem.getFilename())) {
-			fileDataModel.setFileName(FileUtil.getDefaultFilename(mediaItem.getMimeType()));
-		} else {
-			fileDataModel.setFileName(mediaItem.getFilename());
-		}
 
-		switch (mediaItem.getType()) {
-			case TYPE_VOICEMESSAGE:
-				fileDataModel.setFileName(FileUtil.getDefaultFilename(mediaItem.getMimeType())); // the internal temporary file name is of no use to the recipient
-				fileDataModel.setRenderingType(FileData.RENDERING_MEDIA);
-				break;
-			case TYPE_GIF:
-				fileDataModel.setRenderingType(FileData.RENDERING_MEDIA);
-				break;
-			case TYPE_NONE:
-				// "regular" file messages
-				fileDataModel.setRenderingType(FileData.RENDERING_DEFAULT);
-				break;
-			default:
-				if (mediaItem.getImageScale() == PreferenceService.ImageScale_SEND_AS_FILE) {
-					// images with scale type "send as file" get the default rendering type and a file name
-					fileDataModel.setRenderingType(FileData.RENDERING_DEFAULT);
-					mediaItem.setType(TYPE_NONE);
-				} else {
-					// unlike with "real" files we override the filename for regular images with a generic one to prevent privacy leaks
-					// this mimics the behavior of traditional image messages that did not have a filename at all
-					fileDataModel.setFileName(FileUtil.getDefaultFilename(mediaItem.getMimeType()));
-				}
-				break;
-		}
 	}
 
 	/**
@@ -3953,7 +3918,6 @@ public class MessageServiceImpl implements MessageService {
 
 	/**
 	 * Encrypt content and thumbnail data, upload blobs and queue messages for the specified MediaItem
-	 * @param mediaItem MediaItem to send
 	 * @param resolvedReceivers MessageReceivers to send the MediaItem to
 	 * @param messageModels MessageModels for above MessageReceivers
 	 * @param fileDataModel fileDataModel for this message
@@ -3962,7 +3926,7 @@ public class MessageServiceImpl implements MessageService {
 	 * @return true if the message was queued successfully, false otherwise. Note that errors that occur during sending are not handled here.
 	 */
 	@WorkerThread
-	private boolean encryptAndSend(@NonNull MediaItem mediaItem,
+	private boolean encryptAndSend(
 	                       @NonNull MessageReceiver[] resolvedReceivers,
 	                       @NonNull Map<MessageReceiver, AbstractMessageModel> messageModels,
 	                       @NonNull FileDataModel fileDataModel,
@@ -4168,89 +4132,89 @@ public class MessageServiceImpl implements MessageService {
 		return true;
 	}
 
-	public FileDataModel createFileDataModel(
-		Context context,
-		Uri uri,
-		ContentResolver contentResolver,
-		Map<String, Object> metaData,
-		String mimeTypeOverride,
-		@FileData.RenderingType Integer renderingTypeOverride
-	) {
-		String filename = null, mimeType = mimeTypeOverride;
-		int size = 0;
-		boolean isError = false;
+	public @Nullable FileDataModel createFileDataModel(Context context, MediaItem mediaItem) {
+		ContentResolver contentResolver = context.getContentResolver();
+		String mimeType = mediaItem.getMimeType();
+		String filename = mediaItem.getFilename();
 
-		if ("file".equalsIgnoreCase(uri.getScheme())) {
-			File file = new File(uri.getPath());
+		if (mediaItem.getUri() == null) {
+			return null;
+		}
 
-			filename = file.getName();
-			size = (int) file.length();
+		if ("file".equalsIgnoreCase(mediaItem.getUri().getScheme())) {
+			if (TestUtil.empty(filename)) {
+				File file = new File(mediaItem.getUri().getPath());
+
+				filename = file.getName();
+			}
 		} else {
-			// assuming content uri
-			{
+			if (TestUtil.empty(filename) || TestUtil.empty(mimeType)) {
 				String[] proj = {
 					DocumentsContract.Document.COLUMN_DISPLAY_NAME,
-					DocumentsContract.Document.COLUMN_SIZE,
 					DocumentsContract.Document.COLUMN_MIME_TYPE
 				};
 
-				try (Cursor cursor = contentResolver.query(uri, proj, null, null, null)) {
+				try (Cursor cursor = contentResolver.query(mediaItem.getUri(), proj, null, null, null)) {
 					if (cursor != null && cursor.moveToFirst()) {
-						filename = cursor.getString(
-							cursor.getColumnIndex(DocumentsContract.Document.COLUMN_DISPLAY_NAME));
-						size = cursor.getInt(
-							cursor.getColumnIndex(DocumentsContract.Document.COLUMN_SIZE));
-						if (mimeTypeOverride == null || MimeUtil.MIME_TYPE_DEFAULT.equals(mimeTypeOverride)) {
+						if (TestUtil.empty(filename)) {
+							filename = cursor.getString(
+								cursor.getColumnIndex(DocumentsContract.Document.COLUMN_DISPLAY_NAME));
+						}
+						if (TestUtil.empty(mimeType) || MimeUtil.MIME_TYPE_DEFAULT.equals(mimeType)) {
 							mimeType = cursor.getString(
 								cursor.getColumnIndex(DocumentsContract.Document.COLUMN_MIME_TYPE));
 						}
 					}
 				} catch (Exception e) {
-					isError = true;
-				}
-			}
-
-			if (isError) {
-				String[] proj = {
-					OpenableColumns.DISPLAY_NAME,
-					OpenableColumns.SIZE
-				};
-
-				try (Cursor cursor = contentResolver.query(uri, proj, null, null, null)) {
-					if (cursor != null && cursor.moveToFirst()) {
-						filename = cursor.getString(
-							cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME));
-						size = cursor.getInt(
-							cursor.getColumnIndex(OpenableColumns.SIZE));
-					}
-				} catch (Exception e) {
-					//
+					logger.error("Unable to query content provider", e);
 				}
 			}
 		}
 
 		if (TestUtil.empty(mimeType) || MimeUtil.MIME_TYPE_DEFAULT.equals(mimeType)) {
-			mimeType = FileUtil.getMimeTypeFromUri(context, uri);
+			mimeType = FileUtil.getMimeTypeFromUri(context, mediaItem.getUri());
 		}
 
-		if (filename == null) {
-			filename = uri.getLastPathSegment();
-		}
+		@FileData.RenderingType int renderingType = mediaItem.getRenderingType();
 
-		// hack: inputcontent uris don't contain a filename. generate one.
-		if ("inputcontent".equalsIgnoreCase(filename)) {
-			if (MimeUtil.isGifFile(mimeType)) {
-				filename = FileUtil.getMediaFilenamePrefix() + ".gif";
-			}
+		// rendering type overrides
+		switch (mediaItem.getType()) {
+			case TYPE_VOICEMESSAGE:
+				filename = FileUtil.getDefaultFilename(mediaItem.getMimeType()); // the internal temporary file name is of no use to the recipient
+				renderingType = FileData.RENDERING_MEDIA;
+				break;
+			case TYPE_GIF:
+				renderingType = FileData.RENDERING_MEDIA;
+				break;
+			case TYPE_NONE:
+				// "regular" file messages
+				renderingType = FileData.RENDERING_DEFAULT;
+				break;
+			default:
+				if (mediaItem.getImageScale() == PreferenceService.ImageScale_SEND_AS_FILE) {
+					// images with scale type "send as file" get the default rendering type and a file name
+					renderingType = FileData.RENDERING_DEFAULT;
+					mediaItem.setType(TYPE_NONE);
+				} else {
+					// unlike with "real" files we override the filename for regular images with a generic one to prevent privacy leaks
+					// this mimics the behavior of traditional image messages that did not have a filename at all
+					filename = FileUtil.getDefaultFilename(mediaItem.getMimeType()); // the internal temporary file name is of no use to the recipient
+				}
+				break;
 		}
 
-		@FileData.RenderingType int renderingType = FileData.RENDERING_DEFAULT;
-
-		if (renderingTypeOverride != null) {
-			renderingType = renderingTypeOverride;
+		if (TestUtil.empty(filename)) {
+			filename = FileUtil.getDefaultFilename(mimeType);
 		}
 
-		return new FileDataModel(mimeType, null, 0, filename, renderingType, null, false, metaData);
+		return new FileDataModel(mimeType,
+			null,
+			0,
+			filename,
+			renderingType,
+			mediaItem.getCaption(),
+			true,
+			null);
 	}
 
 	/**

+ 46 - 56
app/src/main/res/layout/activity_media_attach.xml

@@ -8,66 +8,11 @@
 	android:layout_height="match_parent"
 	android:fitsSystemWindows="true">
 
-	<com.google.android.material.appbar.AppBarLayout
-		android:id="@id/appbar_layout"
-		android:layout_width="match_parent"
-		android:layout_height="100dp"
-		android:background="@android:color/transparent">
-
-		<com.google.android.material.appbar.CollapsingToolbarLayout
-			android:id="@id/collapsing_toolbar"
-			android:layout_width="match_parent"
-			android:layout_height="match_parent"
-			android:background="@android:color/transparent"
-			app:layout_scrollFlags="scroll|exitUntilCollapsed">
-
-			<com.google.android.material.appbar.MaterialToolbar
-				android:id="@id/toolbar"
-				android:layout_width="match_parent"
-				android:layout_height="?attr/actionBarSize"
-				android:visibility="invisible"
-				app:menu="@menu/activity_media_attach"
-				app:navigationIcon="@null"
-				app:layout_collapseMode="pin">
-
-				<LinearLayout
-					android:id="@+id/toolbar_title"
-					android:layout_width="wrap_content"
-					android:layout_height="wrap_content"
-					android:orientation="horizontal"
-					android:clickable="false"
-					android:focusable="true"
-					android:background="?attr/selectableItemBackgroundBorderless">
-
-					<TextView
-						android:id="@+id/toolbar_title_text"
-						android:layout_width="wrap_content"
-						android:layout_height="match_parent"
-						android:textColor="?android:textColorPrimary"
-						android:textSize="18sp"
-						android:ellipsize="middle"/>
-
-					<ImageView
-						android:id="@+id/dropdown_icon"
-						android:layout_width="wrap_content"
-						android:layout_height="match_parent"
-						app:srcCompat="@drawable/ic_arrow_down_filled"
-						app:tint="?attr/image_tint_default"
-						android:contentDescription="@string/filter_by_album"/>
-
-				</LinearLayout>
-
-			</com.google.android.material.appbar.MaterialToolbar>
-
-		</com.google.android.material.appbar.CollapsingToolbarLayout>
-
-	</com.google.android.material.appbar.AppBarLayout>
-
 	<androidx.coordinatorlayout.widget.CoordinatorLayout
 		android:id="@+id/bottom_sheet_container"
 		android:layout_width="match_parent"
 		android:layout_height="match_parent"
-		android:layout_marginTop="?attr/actionBarSize">
+		android:layout_marginTop="20dp">
 
 		<androidx.constraintlayout.widget.ConstraintLayout
 			android:id="@id/bottom_sheet"
@@ -107,6 +52,51 @@
 
 	</androidx.coordinatorlayout.widget.CoordinatorLayout>
 
+	<com.google.android.material.appbar.AppBarLayout
+		android:id="@id/appbar_layout"
+		android:layout_width="match_parent"
+		android:layout_height="wrap_content"
+		app:elevation="0dp">
+
+		<com.google.android.material.appbar.MaterialToolbar
+			android:id="@id/toolbar"
+			android:layout_width="match_parent"
+			android:layout_height="?attr/actionBarSize"
+			android:visibility="invisible"
+			app:menu="@menu/activity_media_attach"
+			app:navigationIcon="@null">
+
+			<LinearLayout
+				android:id="@+id/toolbar_title"
+				android:layout_width="wrap_content"
+				android:layout_height="wrap_content"
+				android:orientation="horizontal"
+				android:clickable="false"
+				android:focusable="true"
+				android:background="?attr/selectableItemBackgroundBorderless">
+
+				<TextView
+					android:id="@+id/toolbar_title_text"
+					android:layout_width="wrap_content"
+					android:layout_height="match_parent"
+					android:textColor="?android:textColorPrimary"
+					android:textSize="18sp"
+					android:ellipsize="middle"/>
+
+				<ImageView
+					android:id="@+id/dropdown_icon"
+					android:layout_width="wrap_content"
+					android:layout_height="match_parent"
+					app:srcCompat="@drawable/ic_arrow_down_filled"
+					app:tint="?attr/image_tint_default"
+					android:contentDescription="@string/filter_by_album"/>
+
+			</LinearLayout>
+
+		</com.google.android.material.appbar.MaterialToolbar>
+
+	</com.google.android.material.appbar.AppBarLayout>
+
 	<ViewStub android:id="@+id/stub"
 		android:inflatedId="@+id/control_panel"
 		android:layout_width="match_parent"

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

@@ -990,8 +990,8 @@ Threema anonymně?</string>
     <string name="username_hint">Uživ. jméno</string>
     <string name="lock_option_biometric">Biometrický</string>
     <string name="biometric_enter_authentication">Pro odemknutí se autorizujte</string>
-    <string name="biometric_authnetication_failed">Chyba autorizace</string>
-    <string name="biometric_authnetication_successful">Úspěšně autorizováno</string>
+    <string name="biometric_authentication_failed">Chyba autorizace</string>
+    <string name="biometric_authentication_successful">Úspěšně autorizováno</string>
     <string name="work_safe_forced_explain">Váš správce povolil službu Threema Safe pro vaše zařízení.</string>
     <string name="pin_locked_cannot_send">Nelze odeslat. Aplikace je uzamčena.</string>
     <string name="prefs_summary_hide_screenshots_notice">Upozornění: Při zapnuté ochraně soukromí se nebudou vytvářet náhledy obrázků ani nebude možné ukládání snímků obrazovky</string>

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

@@ -1093,8 +1093,8 @@ sicheren Ort gesichert oder ausgedruckt haben.</string>
 	<string name="username_hint">Username</string>
 	<string name="lock_option_biometric">Biometrisch</string>
 	<string name="biometric_enter_authentication">Zum Entsperren bitte authentisieren</string>
-	<string name="biometric_authnetication_failed">Die Authentisierung ist fehlgeschlagen</string>
-	<string name="biometric_authnetication_successful">Sie wurden erfolgreich authentisiert</string>
+	<string name="biometric_authentication_failed">Die Authentisierung ist fehlgeschlagen</string>
+	<string name="biometric_authentication_successful">Sie wurden erfolgreich authentisiert</string>
 	<string name="work_safe_forced_explain">Ihr Administrator hat Threema Safe für Ihr Gerät aktiviert.</string>
 	<string name="pin_locked_cannot_send">App gesperrt. Senden nicht möglich.</string>
 	<string name="prefs_summary_hide_screenshots_notice">Aus Privatsphäre-Gründen werden Miniaturansichten und Screenshots bei aktivierter App-Sperre immer verhindert.</string>

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

@@ -978,8 +978,8 @@ almacenado.</string>
     <string name="username_hint">Nom. usuario</string>
     <string name="lock_option_biometric">Biométrico</string>
     <string name="biometric_enter_authentication">Para desbloquear debe autenticarse</string>
-    <string name="biometric_authnetication_failed">Error de autenticación</string>
-    <string name="biometric_authnetication_successful">Autenticación realizada</string>
+    <string name="biometric_authentication_failed">Error de autenticación</string>
+    <string name="biometric_authentication_successful">Autenticación realizada</string>
     <string name="work_safe_forced_explain">El administrador ha habilitado Threema Safe en su dispositivo.</string>
     <string name="pin_locked_cannot_send">Envío no permitido: aplicación bloqueada.</string>
     <string name="prefs_summary_hide_screenshots_notice">Aviso: Por razones de privacidad, se bloquean las imágenes en miniatura y las capturas de pantalla cuando la protección de la aplicación está habilitada.</string>

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

@@ -968,8 +968,8 @@ Veuillez saisir une question pour votre enquête.</string>
     <string name="username_hint">Nom d\'utilisateur</string>
     <string name="lock_option_biometric">Biométrique</string>
     <string name="biometric_enter_authentication">Pour déverrouiller, veuillez vous authentifier</string>
-    <string name="biometric_authnetication_failed">Échec d\'authentification</string>
-    <string name="biometric_authnetication_successful">Bien authentifié</string>
+    <string name="biometric_authentication_failed">Échec d\'authentification</string>
+    <string name="biometric_authentication_successful">Bien authentifié</string>
     <string name="work_safe_forced_explain">Vous administrateur a activé Threema Safe pour votre appareil.</string>
     <string name="pin_locked_cannot_send">L\'application est verrouillée. Envoi impossible.</string>
     <string name="prefs_summary_hide_screenshots_notice">Remarque : pour des raisons de confidentialité, les vignettes et captures d\'écran sont toujours bloqués lorsque la protection de l\'application est activée.</string>

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

@@ -988,8 +988,8 @@ messaggi ogni 15 minuti.</string>
     <string name="username_hint">Nome utente</string>
     <string name="lock_option_biometric">Biometrica</string>
     <string name="biometric_enter_authentication">Autenticarsi per sbloccare</string>
-    <string name="biometric_authnetication_failed">Impossibile autenticare</string>
-    <string name="biometric_authnetication_successful">Autenticazione riuscita</string>
+    <string name="biometric_authentication_failed">Impossibile autenticare</string>
+    <string name="biometric_authentication_successful">Autenticazione riuscita</string>
     <string name="work_safe_forced_explain">Il tuo amministratore ha attivato Threema Safe sul tuo dispositivo.</string>
     <string name="pin_locked_cannot_send">L\'app è bloccata. Invio non possibile.</string>
     <string name="prefs_summary_hide_screenshots_notice">Nota: per motivi di privacy, anteprime e screenshot non sono mai abilitate quando la protezione app è attivata</string>

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

@@ -969,8 +969,8 @@ Weet u zeker dat u Threema anoniem wil gebruiken?</string>
     <string name="username_hint">Gebruikersnaam</string>
     <string name="lock_option_biometric">Biometrisch</string>
     <string name="biometric_enter_authentication">Verifieer om te ontgrendelen</string>
-    <string name="biometric_authnetication_failed">Verificatie is mislukt</string>
-    <string name="biometric_authnetication_successful">Verifiëren is gelukt</string>
+    <string name="biometric_authentication_failed">Verificatie is mislukt</string>
+    <string name="biometric_authentication_successful">Verifiëren is gelukt</string>
     <string name="work_safe_forced_explain">Uw administrator heeft uw apparaat toestemming gegeven Threema Safe te gebruiken.</string>
     <string name="pin_locked_cannot_send">De app is vergrendeld. Kan niet verzenden.</string>
     <string name="prefs_summary_hide_screenshots_notice">Let op: vanwege privacyredenen worden miniaturen en screenshots altijd geblokkeerd wanneer de appbescherming is ingeschakeld.</string>

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

@@ -985,8 +985,8 @@ anonimowo?</string>
     <string name="username_hint">Nazwa użytkownika</string>
     <string name="lock_option_biometric">Biometryczna</string>
     <string name="biometric_enter_authentication">Aby odblokować, dokonaj uwierzytelnienia</string>
-    <string name="biometric_authnetication_failed">Uwierzytelnianie nieudane</string>
-    <string name="biometric_authnetication_successful">Uwierzytelnianie udane</string>
+    <string name="biometric_authentication_failed">Uwierzytelnianie nieudane</string>
+    <string name="biometric_authentication_successful">Uwierzytelnianie udane</string>
     <string name="work_safe_forced_explain">Twój administrator włączył Threema Safe dla Twojego urządzenia.</string>
     <string name="pin_locked_cannot_send">Aplikacja jest zablokowana. Nie można wysłać.</string>
     <string name="prefs_summary_hide_screenshots_notice">Uwaga: W trosce o prywatność miniaturki i zrzuty ekranu są dezaktywowane, gdy włączona jest ochrona aplikacji.</string>

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

@@ -973,8 +973,8 @@ ficará órfã. Os outros membros ainda poderão conversar, mas não serão mais
     <string name="username_hint">Usuário</string>
     <string name="lock_option_biometric">Biométrico</string>
     <string name="biometric_enter_authentication">Para desbloquear, faça a autenticação</string>
-    <string name="biometric_authnetication_failed">Falha na autenticação</string>
-    <string name="biometric_authnetication_successful">Autenticação bem-sucedida</string>
+    <string name="biometric_authentication_failed">Falha na autenticação</string>
+    <string name="biometric_authentication_successful">Autenticação bem-sucedida</string>
     <string name="work_safe_forced_explain">Seu administrador permitiu o Threema Safe no seu dispositivo.</string>
     <string name="pin_locked_cannot_send">Aplicativo bloqueado. Não é possível enviar.</string>
     <string name="prefs_summary_hide_screenshots_notice">Observação: por motivos de privacidade, miniaturas e capturas de tela são sempre impedidas quando a proteção do aplicativo estiver habilitada</string>

+ 2 - 2
app/src/main/res/values-rm/strings.xml

@@ -966,8 +966,8 @@ Co plaschan ils novs emojis a tai?</string>
     <string name="username_hint">Num d\'utilisader</string>
     <string name="lock_option_biometric">biometric</string>
     <string name="biometric_enter_authentication">P.pl. autentifitgar per debloccar</string>
-    <string name="biometric_authnetication_failed">Igl ha dà in sbagl cun s\'autentifitgar</string>
-    <string name="biometric_authnetication_successful">Reussì da s\'autentifitgar</string>
+    <string name="biometric_authentication_failed">Igl ha dà in sbagl cun s\'autentifitgar</string>
+    <string name="biometric_authentication_successful">Reussì da s\'autentifitgar</string>
     <string name="work_safe_forced_explain">Tes administratur ha activà Threema Safe per tes apparat.</string>
     <string name="pin_locked_cannot_send">L\'app è bloccada. Betg pussaivel da trametter.</string>
     <string name="prefs_summary_hide_screenshots_notice">Avis: Per motivs da la sfera privata vegnan adina impedidas miniaturas e screenshots, cura ch\'è activà la protecziun da l\'app.</string>

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

@@ -967,8 +967,8 @@
     <string name="username_hint">Имя польз-ля</string>
     <string name="lock_option_biometric">Биометрия</string>
     <string name="biometric_enter_authentication">Для разблокирования выполните аутентификацию</string>
-    <string name="biometric_authnetication_failed">Сбой аутентификации</string>
-    <string name="biometric_authnetication_successful">Аутентификация успешно пройдена</string>
+    <string name="biometric_authentication_failed">Сбой аутентификации</string>
+    <string name="biometric_authentication_successful">Аутентификация успешно пройдена</string>
     <string name="work_safe_forced_explain">Ваш администратор активировал Threema Safe для вашего устройства.</string>
     <string name="pin_locked_cannot_send">Приложение заблокировано. Отправка невозможна.</string>
     <string name="prefs_summary_hide_screenshots_notice">Примечание. В целях защиты конфиденциальности при активированной защите приложения блокируются миниатюры и снимки экрана.</string>

+ 2 - 2
app/src/main/res/values-tr/strings.xml

@@ -1000,8 +1000,8 @@ yöneticisiz kalır . Diğer üyeler yine de sohbet edebilecek, ancak artık de
     <string name="username_hint">Kullanıcı Adı</string>
     <string name="lock_option_biometric">Biyometrik</string>
     <string name="biometric_enter_authentication">Kilidi açmak için lütfen kimlik doğrulaması yapın</string>
-    <string name="biometric_authnetication_failed">Kimlik doğrulama başarısız oldu</string>
-    <string name="biometric_authnetication_successful">Başarıyla doğrulandı</string>
+    <string name="biometric_authentication_failed">Kimlik doğrulama başarısız oldu</string>
+    <string name="biometric_authentication_successful">Başarıyla doğrulandı</string>
     <string name="work_safe_forced_explain">Yöneticiniz cihazınız için Threema Safe\'i etkinleştirdi.</string>
     <string name="pin_locked_cannot_send">Uygulama kilitli. Gönderilemiyor.</string>
     <string name="prefs_summary_hide_screenshots_notice">Gizlilik nedeniyle, güvenlik ayarlarında erişim koruması etkinleştirildiğinde küçük resimler ve ekran görüntüleri her zaman önlenir.</string>

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

@@ -1018,8 +1018,8 @@
 	<string name="username_hint">Username</string>
 	<string name="lock_option_biometric">Biometric</string>
 	<string name="biometric_enter_authentication">To unlock, please authenticate</string>
-	<string name="biometric_authnetication_failed">Authnetication failed</string>
-	<string name="biometric_authnetication_successful">Successfully authenticated</string>
+	<string name="biometric_authentication_failed">Authentication failed</string>
+	<string name="biometric_authentication_successful">Successfully authenticated</string>
 	<string name="work_safe_forced_explain">Your administrator enabled Threema Safe for your device.</string>
 	<string name="pin_locked_cannot_send">The app is locked. Cannot send.</string>
 	<string name="prefs_summary_hide_screenshots_notice">For privacy reasons, thumbnails and screenshots are always prevented when "App lock" is enabled in security settings</string>