Threema 4 жил өмнө
parent
commit
bfdef5ff49
71 өөрчлөгдсөн 680 нэмэгдсэн , 196 устгасан
  1. 2 2
      app/build.gradle
  2. 6 0
      app/src/main/AndroidManifest.xml
  3. 46 4
      app/src/main/java/ch/threema/app/ThreemaApplication.java
  4. 14 6
      app/src/main/java/ch/threema/app/activities/RecipientListBaseActivity.java
  5. 5 1
      app/src/main/java/ch/threema/app/activities/wizard/WizardBaseActivity.java
  6. 33 18
      app/src/main/java/ch/threema/app/backuprestore/csv/BackupService.java
  7. 11 1
      app/src/main/java/ch/threema/app/fragments/BackupDataFragment.java
  8. 5 0
      app/src/main/java/ch/threema/app/fragments/wizard/WizardFragment4.java
  9. 2 1
      app/src/main/java/ch/threema/app/locationpicker/LocationPickerActivity.java
  10. 19 14
      app/src/main/java/ch/threema/app/messagereceiver/ContactMessageReceiver.java
  11. 1 1
      app/src/main/java/ch/threema/app/messagereceiver/DistributionListMessageReceiver.java
  12. 8 5
      app/src/main/java/ch/threema/app/messagereceiver/GroupMessageReceiver.java
  13. 1 5
      app/src/main/java/ch/threema/app/messagereceiver/MessageReceiver.java
  14. 2 2
      app/src/main/java/ch/threema/app/services/LocaleServiceImpl.java
  15. 21 22
      app/src/main/java/ch/threema/app/services/MessageServiceImpl.java
  16. 3 3
      app/src/main/java/ch/threema/app/utils/AndroidContactUtil.java
  17. 2 2
      app/src/main/java/ch/threema/app/utils/AnimationUtil.java
  18. 2 2
      app/src/main/java/ch/threema/app/utils/AppRestrictionUtil.java
  19. 2 2
      app/src/main/java/ch/threema/app/utils/AvatarConverterUtil.java
  20. 2 2
      app/src/main/java/ch/threema/app/utils/BallotUtil.java
  21. 2 2
      app/src/main/java/ch/threema/app/utils/BiometricUtil.java
  22. 2 2
      app/src/main/java/ch/threema/app/utils/BitmapUtil.java
  23. 2 2
      app/src/main/java/ch/threema/app/utils/BitmapWorkerTask.java
  24. 2 2
      app/src/main/java/ch/threema/app/utils/ConfigUtils.java
  25. 2 2
      app/src/main/java/ch/threema/app/utils/ContactUtil.java
  26. 2 2
      app/src/main/java/ch/threema/app/utils/ConversationNotificationUtil.java
  27. 2 3
      app/src/main/java/ch/threema/app/utils/DNDUtil.java
  28. 3 2
      app/src/main/java/ch/threema/app/utils/DeviceIdUtil.java
  29. 2 2
      app/src/main/java/ch/threema/app/utils/DialogUtil.java
  30. 2 2
      app/src/main/java/ch/threema/app/utils/ExifInterface.java
  31. 3 2
      app/src/main/java/ch/threema/app/utils/ExponentialBackOffUtil.java
  32. 2 2
      app/src/main/java/ch/threema/app/utils/FileUtil.java
  33. 4 2
      app/src/main/java/ch/threema/app/utils/GeoLocationUtil.java
  34. 2 2
      app/src/main/java/ch/threema/app/utils/IconUtil.java
  35. 2 3
      app/src/main/java/ch/threema/app/utils/IntentDataUtil.java
  36. 353 0
      app/src/main/java/ch/threema/app/utils/LinkifyCompatUtil.java
  37. 4 5
      app/src/main/java/ch/threema/app/utils/LinkifyUtil.java
  38. 2 2
      app/src/main/java/ch/threema/app/utils/LoadingUtil.java
  39. 0 5
      app/src/main/java/ch/threema/app/utils/LocationUtil.java
  40. 2 2
      app/src/main/java/ch/threema/app/utils/LogUtil.java
  41. 3 2
      app/src/main/java/ch/threema/app/utils/LoggingUEH.java
  42. 2 2
      app/src/main/java/ch/threema/app/utils/MediaPlayerStateWrapper.java
  43. 2 2
      app/src/main/java/ch/threema/app/utils/MessageUtil.java
  44. 2 2
      app/src/main/java/ch/threema/app/utils/NavigationUtil.java
  45. 2 2
      app/src/main/java/ch/threema/app/utils/PowermanagerUtil.java
  46. 2 2
      app/src/main/java/ch/threema/app/utils/PushUtil.java
  47. 2 2
      app/src/main/java/ch/threema/app/utils/QRScannerUtil.java
  48. 2 2
      app/src/main/java/ch/threema/app/utils/QuoteUtil.java
  49. 3 2
      app/src/main/java/ch/threema/app/utils/SecureDeleteUtil.java
  50. 2 2
      app/src/main/java/ch/threema/app/utils/ShortcutUtil.java
  51. 2 2
      app/src/main/java/ch/threema/app/utils/StreamUtil.java
  52. 2 2
      app/src/main/java/ch/threema/app/utils/SynchronizeContactsUtil.java
  53. 2 2
      app/src/main/java/ch/threema/app/utils/TextUtil.java
  54. 2 2
      app/src/main/java/ch/threema/app/utils/TurnServerCache.java
  55. 3 2
      app/src/main/java/ch/threema/app/utils/VideoUtil.java
  56. 2 2
      app/src/main/java/ch/threema/app/utils/WebRTCUtil.java
  57. 2 2
      app/src/main/java/ch/threema/app/utils/WidgetUtil.java
  58. 6 0
      app/src/main/java/ch/threema/app/voip/receivers/CallRejectReceiver.java
  59. 2 2
      app/src/main/java/ch/threema/app/voip/services/CallRejectService.java
  60. 10 0
      app/src/main/res/drawable/ic_places_free_flying.xml
  61. 1 1
      app/src/main/res/layout/fragment_wizard4.xml
  62. 1 1
      app/src/main/res/values-it/webclient_strings.xml
  63. 3 0
      app/src/onprem/res/values-de/strings.xml
  64. 2 0
      app/src/onprem/res/values/strings.xml
  65. 2 2
      build.gradle
  66. 2 2
      domain/build.gradle
  67. 6 4
      domain/src/main/java/ch/threema/domain/onprem/OnPremConfig.java
  68. 8 5
      domain/src/main/java/ch/threema/domain/onprem/OnPremConfigParser.java
  69. 8 1
      domain/src/main/java/ch/threema/domain/onprem/ServerAddressProviderOnPrem.java
  70. 9 4
      domain/src/main/java/ch/threema/domain/protocol/csp/coders/MessageCoder.java
  71. 1 1
      gradle.properties

+ 2 - 2
app/build.gradle

@@ -20,7 +20,7 @@ if (getGradle().getStartParameter().getTaskRequests().toString().contains("Hms")
 }
 
 // version codes
-def app_version = "4.64"
+def app_version = "4.65"
 def beta_suffix = "" // with leading dash
 
 /**
@@ -99,7 +99,7 @@ android {
         vectorDrawables.useSupportLibrary = true
         applicationId "ch.threema.app"
         testApplicationId 'ch.threema.app.test'
-        versionCode 715
+        versionCode 718
         versionName "${app_version}${beta_suffix}"
         resValue "string", "app_name", "Threema"
         // package name used for sync adapter - needs to match mime types below

+ 6 - 0
app/src/main/AndroidManifest.xml

@@ -130,6 +130,10 @@
 		<package android:name="com.asus.mobilemanager" />
 		<package android:name="com.transsion.phonemanager" />
 
+		<!-- provider for xperia home icon badge counters -->
+		<provider android:authorities="com.sonymobile.home.resourceprovider"
+			android:exported="false" />
+
 		<intent>
 			<action android:name="miui.intent.action.POWER_HIDE_MODE_APP_LIST"/>
 			<category android:name="android.intent.category.DEFAULT"/>
@@ -561,6 +565,7 @@
 			android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation|touchscreen|keyboard|keyboardHidden|uiMode"
 			android:supportsPictureInPicture="true"
 			android:launchMode="singleTask"
+			android:taskAffinity=".voip.activities.CallActivity"
 			android:excludeFromRecents="true"
 			android:screenOrientation="sensorPortrait"
 			android:theme="@style/Theme.Threema.TransparentStatusbar"
@@ -847,6 +852,7 @@
 			android:exported="false"/>
 		<service
 			android:name=".voip.services.VoipCallService"
+			android:foregroundServiceType="phoneCall|camera|microphone"
 			android:exported="false"/>
 		<service
 			android:name=".voip.services.CallRejectService"

+ 46 - 4
app/src/main/java/ch/threema/app/ThreemaApplication.java

@@ -24,6 +24,8 @@ package ch.threema.app;
 import android.annotation.SuppressLint;
 import android.annotation.TargetApi;
 import android.app.Activity;
+import android.app.ActivityManager;
+import android.app.ApplicationExitInfo;
 import android.app.NotificationManager;
 import android.app.job.JobInfo;
 import android.app.job.JobScheduler;
@@ -53,8 +55,11 @@ import net.sqlcipher.database.SQLiteException;
 
 import org.slf4j.Logger;
 
+import java.io.BufferedReader;
 import java.io.File;
 import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
 import java.net.Inet6Address;
 import java.net.InetSocketAddress;
 import java.util.Date;
@@ -263,6 +268,7 @@ public class ThreemaApplication extends MultiDexApplication implements DefaultLi
 	public static final int SHORTCUTS_UPDATE_JOB_ID = 63340;
 
 	private static final String WORKER_IDENTITY_STATES_PERIODIC_NAME = "IdentityStates";
+	private static final String EXIT_REASON_LOGGING_TIMESTAMP = "exit_reason_timestamp";
 
 	private static Context context;
 
@@ -701,9 +707,7 @@ public class ThreemaApplication extends MultiDexApplication implements DefaultLi
 	}
 
 	@SuppressLint("ApplySharedPref")
-	private static void resetPreferences() {
-		SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getAppContext());
-
+	private static void resetPreferences(SharedPreferences prefs) {
 		// Fix master key preference state if necessary (could be wrong if user kills app
 		// while disabling master key passphrase).
 		if (masterKey.isProtected() && prefs != null && !prefs.getBoolean(getAppContext().getString(R.string.preferences__masterkey_switch), false)) {
@@ -779,7 +783,8 @@ public class ThreemaApplication extends MultiDexApplication implements DefaultLi
 	public static synchronized void reset() {
 
 		//set default preferences
-		resetPreferences();
+		SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(getAppContext());
+		resetPreferences(sharedPreferences);
 
 		// init state bitmap cache singleton
 		StateBitmapUtil.init(getAppContext());
@@ -836,6 +841,43 @@ public class ThreemaApplication extends MultiDexApplication implements DefaultLi
 				ConfigUtils.getBuildNumber(getAppContext())
 			);
 
+			if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
+				ActivityManager activityManager = (ActivityManager) getAppContext().getSystemService(Context.ACTIVITY_SERVICE);
+				List<ApplicationExitInfo> applicationExitInfos = activityManager.getHistoricalProcessExitReasons(null, 0, 0);
+
+				if (applicationExitInfos.size() > 0) {
+					for (ApplicationExitInfo exitInfo : applicationExitInfos) {
+						long timestampOfLastLog = 0L;
+						if (sharedPreferences != null) {
+							timestampOfLastLog = sharedPreferences.getLong(EXIT_REASON_LOGGING_TIMESTAMP, timestampOfLastLog);
+						}
+
+						if (exitInfo.getTimestamp() > timestampOfLastLog) {
+							logger.info(String.format(Locale.US, "*** App last exited at %s with reason: %d, description: %s", DateUtils.formatDateTime(getAppContext(), exitInfo.getTimestamp(), DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_TIME), exitInfo.getReason(), exitInfo.getDescription()));
+							if (exitInfo.getReason() == ApplicationExitInfo.REASON_ANR) {
+								try {
+									InputStream traceInputStream = exitInfo.getTraceInputStream();
+									if (traceInputStream != null) {
+										BufferedReader r = new BufferedReader(new InputStreamReader(traceInputStream));
+										StringBuilder s = new StringBuilder();
+										for (String line; (line = r.readLine()) != null; ) {
+											s.append(line).append('\n');
+										}
+										logger.info(s.toString());
+									}
+								} catch (IOException e) {
+									logger.error("Error getting ANR trace", e);
+								}
+							}
+						}
+					}
+
+					if (sharedPreferences != null) {
+						sharedPreferences.edit().putLong(EXIT_REASON_LOGGING_TIMESTAMP, System.currentTimeMillis()).apply();
+					}
+				}
+			}
+
 			// Set up logging
 			setupLogging(preferenceStore);
 

+ 14 - 6
app/src/main/java/ch/threema/app/activities/RecipientListBaseActivity.java

@@ -475,7 +475,7 @@ public class RecipientListBaseActivity extends ThreemaToolbarActivity implements
 									type = guessedType;
 								}
 
-								addMediaItem(type, uri, textIntent);
+								addMediaItemSharedFromOtherApp(type, uri, textIntent);
 								if (textIntent != null) {
 									captionText = textIntent;
 								}
@@ -496,7 +496,7 @@ public class RecipientListBaseActivity extends ThreemaToolbarActivity implements
 									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
-									addMediaItem(type, uri, textIntent);
+									addMediaItemSharedFromOtherApp(type, uri, textIntent);
 								}
 							}
 						}
@@ -509,7 +509,7 @@ public class RecipientListBaseActivity extends ThreemaToolbarActivity implements
 								CharSequence text = clipData.getItemAt(i).getText();
 
 								if (uri1 != null) {
-									addMediaItem(type, uri1, null);
+									addMediaItemSharedFromOtherApp(type, uri1, null);
 								} else if (!TestUtil.empty(text)) {
 									mediaItems.add(new MediaItem(uri, TYPE_TEXT, MimeUtil.MIME_TYPE_TEXT, text.toString()));
 								}
@@ -617,7 +617,7 @@ public class RecipientListBaseActivity extends ThreemaToolbarActivity implements
 									if (mimeType == null) {
 										mimeType = type;
 									}
-									addMediaItem(mimeType, uri, null);
+									addMediaItemSharedFromOtherApp(mimeType, uri, null);
 								}
 							} else {
 								Toast.makeText(getApplicationContext(), getString(R.string.max_selectable_media_exceeded, MAX_SELECTABLE_IMAGES), Toast.LENGTH_LONG).show();
@@ -698,7 +698,7 @@ public class RecipientListBaseActivity extends ThreemaToolbarActivity implements
 		return subject + " - " + text;
 	}
 
-	private void addMediaItem(String mimeType, @NonNull Uri uri, @Nullable String caption) {
+	private void addMediaItemSharedFromOtherApp(String mimeType, @NonNull Uri uri, @Nullable String caption) {
 		if (ContentResolver.SCHEME_FILE.equalsIgnoreCase(uri.getScheme())) {
 			String path = uri.getPath();
 			File applicationDir = new File(getApplicationInfo().dataDir);
@@ -726,7 +726,15 @@ public class RecipientListBaseActivity extends ThreemaToolbarActivity implements
 				}
 			}
 		}
-		mediaItems.add(new MediaItem(uri, mimeType, caption));
+		MediaItem mediaItem = new MediaItem(uri, mimeType, caption);
+
+		// never create a voice message out of a shared audio file - fix default
+		if (mediaItem.getType() == MediaItem.TYPE_VOICEMESSAGE) {
+			mediaItem.setType(MediaItem.TYPE_FILE);
+			mediaItem.setRenderingType(FileData.RENDERING_DEFAULT);
+		}
+
+		mediaItems.add(mediaItem);
 	}
 
 	@SuppressLint("StaticFieldLeak")

+ 5 - 1
app/src/main/java/ch/threema/app/activities/wizard/WizardBaseActivity.java

@@ -190,7 +190,11 @@ public class WizardBaseActivity extends ThreemaAppCompatActivity implements View
 						if (ConfigUtils.isWorkBuild()) {
 							needConfirm = TestUtil.empty(number) && TestUtil.empty(email) && TestUtil.empty(getPresetEmail()) && TestUtil.empty(getPresetPhone());
 						} else {
-							needConfirm = TestUtil.empty(number) && TestUtil.empty(getPresetPhone());
+							if (ConfigUtils.isOnPremBuild()) {
+								needConfirm = false;
+							} else {
+								needConfirm = TestUtil.empty(number) && TestUtil.empty(getPresetPhone());
+							}
 						}
 						if (needConfirm) {
 							WizardDialog wizardDialog = WizardDialog.newInstance(

+ 33 - 18
app/src/main/java/ch/threema/app/backuprestore/csv/BackupService.java

@@ -171,7 +171,7 @@ public class BackupService extends Service {
 				config = (BackupRestoreDataConfig) intent.getSerializableExtra(EXTRA_BACKUP_RESTORE_DATA_CONFIG);
 
 				if (config == null || userService.getIdentity() == null || userService.getIdentity().length() == 0) {
-					stopSelf();
+					safeStopSelf();
 					return START_NOT_STICKY;
 				}
 
@@ -191,15 +191,6 @@ public class BackupService extends Service {
 					}
 				}
 
-				//first of all, close connection
-				try {
-					serviceManager.stopConnection();
-				} catch (InterruptedException e) {
-					showBackupErrorNotification("BackupService interrupted");
-					stopSelf();
-					return START_NOT_STICKY;
-				}
-
 				boolean success = false;
 				Date now = new Date();
 				DocumentFile zipFile = null;
@@ -207,7 +198,7 @@ public class BackupService extends Service {
 
 				if (backupUri == null) {
 					showBackupErrorNotification("Destination directory has not been selected yet");
-					stopSelf();
+					safeStopSelf();
 					return START_NOT_STICKY;
 				}
 
@@ -232,12 +223,23 @@ public class BackupService extends Service {
 
 				if (zipFile == null || !success) {
 					showBackupErrorNotification(getString(R.string.backup_data_no_permission));
-					stopSelf();
+					safeStopSelf();
 					return START_NOT_STICKY;
 				}
 
 				backupFile = zipFile;
 
+				showPersistentNotification();
+
+				// close connection
+				try {
+					serviceManager.stopConnection();
+				} catch (InterruptedException e) {
+					showBackupErrorNotification("BackupService interrupted");
+					stopSelf();
+					return START_NOT_STICKY;
+				}
+
 				new AsyncTask<Void, Void, Boolean>() {
 					@Override
 					protected Boolean doInBackground(Void... params) {
@@ -272,7 +274,7 @@ public class BackupService extends Service {
 
 		serviceManager = ThreemaApplication.getServiceManager();
 		if (serviceManager == null) {
-			stopSelf();
+			safeStopSelf();
 			return;
 		}
 
@@ -287,7 +289,7 @@ public class BackupService extends Service {
 			preferenceService = serviceManager.getPreferenceService();
 		} catch (Exception e) {
 			logger.error("Exception", e);
-			stopSelf();
+			safeStopSelf();
 			return;
 		}
 
@@ -302,7 +304,6 @@ public class BackupService extends Service {
 		if (isCanceled) {
 			onFinished(getString(R.string.backup_data_cancelled));
 		}
-
 		super.onDestroy();
 	}
 
@@ -329,9 +330,6 @@ public class BackupService extends Service {
 
 	private boolean backup() {
 		String identity = userService.getIdentity();
-
-		showPersistentNotification();
-
 		try(final ZipOutputStream zipOutputStream = ZipUtil.initializeZipOutputStream(getContentResolver(), backupFile.getUri(), config.getPassword())) {
 			logger.debug("Creating zip file {}", backupFile.getUri());
 
@@ -1393,4 +1391,21 @@ public class BackupService extends Service {
 			});
 		}
 	}
+
+	/**
+	 * Show a fake notification before stopping service in order to prevent Context.startForegroundService() did not then call Service.startForeground() crash
+	 */
+	private void safeStopSelf() {
+		Notification notification = new NotificationBuilderWrapper(this, NOTIFICATION_CHANNEL_BACKUP_RESTORE_IN_PROGRESS, null)
+			.setContentTitle("")
+			.setContentText("").
+				build();
+
+		startForeground(BACKUP_NOTIFICATION_ID, notification);
+		stopForeground(true);
+		isRunning = false;
+		stopSelf();
+	}
 }
+
+

+ 11 - 1
app/src/main/java/ch/threema/app/fragments/BackupDataFragment.java

@@ -239,7 +239,17 @@ public class BackupDataFragment extends Fragment implements
 			if (backupUri == null) {
 				showPathSelectionIntro();
 			} else {
-				checkBatteryOptimizations();
+				DocumentFile documentFile = null;
+				try {
+					documentFile = DocumentFile.fromTreeUri(ThreemaApplication.getAppContext(), backupUri);
+				} catch (IllegalArgumentException e) {
+					logger.error("DocumentFile.fromTreeUri failed", e);
+				}
+				if (documentFile == null || !documentFile.exists()) {
+					showPathSelectionIntro();
+				} else {
+					checkBatteryOptimizations();
+				}
 			}
 		}
 	}

+ 5 - 0
app/src/main/java/ch/threema/app/fragments/wizard/WizardFragment4.java

@@ -26,6 +26,7 @@ import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
 import android.widget.CompoundButton;
+import android.widget.TextView;
 
 import androidx.appcompat.widget.SwitchCompat;
 import ch.threema.app.R;
@@ -53,6 +54,10 @@ public class WizardFragment4 extends WizardFragment {
 
 		if (ConfigUtils.isOnPremBuild() && ConfigUtils.isDemoOPServer(preferenceService)) {
 			defaultSwitchValue = false;
+			// Add another warning for dull-witted G**gle reviewers
+			TextView textView = rootView.findViewById(R.id.disabled_by_policy);
+			textView.setText(R.string.new_wizard_info_sync_contacts);
+			textView.setVisibility(View.VISIBLE);
 		}
 
 		if (SynchronizeContactsUtil.isRestrictedProfile(getActivity()) &&

+ 2 - 1
app/src/main/java/ch/threema/app/locationpicker/LocationPickerActivity.java

@@ -335,7 +335,8 @@ public class LocationPickerActivity extends ThreemaActivity implements
 					public void onStyleLoaded(@NonNull Style style) {
 						// Map is set up and the style has loaded. Now you can add data or make other mapView adjustments
 						setupLocationComponent(style);
-						zoomToCenter();
+						// hack: delay location query
+						mapView.postDelayed(() -> zoomToCenter(), 500);
 					}
 				});
 				mapboxMap.getUiSettings().setAttributionEnabled(false);

+ 19 - 14
app/src/main/java/ch/threema/app/messagereceiver/ContactMessageReceiver.java

@@ -35,6 +35,7 @@ import java.util.Date;
 import java.util.List;
 import java.util.UUID;
 
+import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import ch.threema.app.R;
 import ch.threema.app.ThreemaApplication;
@@ -47,16 +48,16 @@ import ch.threema.app.utils.NameUtil;
 import ch.threema.app.utils.TestUtil;
 import ch.threema.base.ThreemaException;
 import ch.threema.base.utils.LoggingUtil;
-import ch.threema.domain.protocol.csp.messages.AbstractMessage;
+import ch.threema.base.utils.Utils;
+import ch.threema.domain.models.MessageId;
+import ch.threema.domain.protocol.ThreemaFeature;
 import ch.threema.domain.protocol.blob.BlobUploader;
-import ch.threema.domain.protocol.csp.messages.BoxLocationMessage;
-import ch.threema.domain.protocol.csp.messages.BoxTextMessage;
+import ch.threema.domain.protocol.csp.ProtocolDefines;
 import ch.threema.domain.protocol.csp.coders.MessageBox;
-import ch.threema.domain.models.MessageId;
 import ch.threema.domain.protocol.csp.connection.MessageQueue;
-import ch.threema.domain.protocol.csp.ProtocolDefines;
-import ch.threema.domain.protocol.ThreemaFeature;
-import ch.threema.base.utils.Utils;
+import ch.threema.domain.protocol.csp.messages.AbstractMessage;
+import ch.threema.domain.protocol.csp.messages.BoxLocationMessage;
+import ch.threema.domain.protocol.csp.messages.BoxTextMessage;
 import ch.threema.domain.protocol.csp.messages.ballot.BallotCreateMessage;
 import ch.threema.domain.protocol.csp.messages.ballot.BallotData;
 import ch.threema.domain.protocol.csp.messages.ballot.BallotId;
@@ -70,6 +71,7 @@ import ch.threema.storage.models.ContactModel;
 import ch.threema.storage.models.MessageModel;
 import ch.threema.storage.models.MessageType;
 import ch.threema.storage.models.ballot.BallotModel;
+import ch.threema.storage.models.data.LocationDataModel;
 import ch.threema.storage.models.data.MessageContentsType;
 import ch.threema.storage.models.data.media.FileDataModel;
 
@@ -83,8 +85,8 @@ public class ContactMessageReceiver implements MessageReceiver<MessageModel> {
 	private final DatabaseServiceNew databaseServiceNew;
 	private final MessageQueue messageQueue;
 	private final IdentityStore identityStore;
-	private IdListService blackListIdentityService;
-	private ApiService apiService;
+	private final IdListService blackListIdentityService;
+	private final ApiService apiService;
 
 	public ContactMessageReceiver(ContactModel contactModel,
 								  ContactService contactService,
@@ -181,14 +183,17 @@ public class ContactMessageReceiver implements MessageReceiver<MessageModel> {
 	}
 
 	@Override
-	public boolean createBoxedLocationMessage(double lat, double lng, float acc, String poiName, MessageModel messageModel) throws ThreemaException {
+	public boolean createBoxedLocationMessage(@NonNull MessageModel messageModel) throws ThreemaException {
+
+		LocationDataModel locationDataModel = messageModel.getLocationData();
 
 		BoxLocationMessage msg = new BoxLocationMessage();
-		msg.setLatitude(lat);
-		msg.setLongitude(lng);
-		msg.setAccuracy(acc);
+		msg.setLatitude(locationDataModel.getLatitude());
+		msg.setLongitude(locationDataModel.getLongitude());
+		msg.setAccuracy(locationDataModel.getAccuracy());
 		msg.setToIdentity(this.contactModel.getIdentity());
-		msg.setPoiName(poiName);
+		msg.setPoiName(locationDataModel.getPoi());
+		msg.setPoiAddress(locationDataModel.getAddress());
 
 		//fix #ANDR-512
 		//save model after receiving a new message id

+ 1 - 1
app/src/main/java/ch/threema/app/messagereceiver/DistributionListMessageReceiver.java

@@ -131,7 +131,7 @@ public class DistributionListMessageReceiver implements MessageReceiver<Distribu
 	}
 
 	@Override
-	public boolean createBoxedLocationMessage(final double lat, final double lng, final float acc, String poiName, final DistributionListMessageModel messageModel) throws ThreemaException {
+	public boolean createBoxedLocationMessage(final DistributionListMessageModel messageModel) throws ThreemaException {
 		return this.handleSendImage(messageModel);
 	}
 

+ 8 - 5
app/src/main/java/ch/threema/app/messagereceiver/GroupMessageReceiver.java

@@ -77,6 +77,7 @@ import ch.threema.storage.models.MessageState;
 import ch.threema.storage.models.MessageType;
 import ch.threema.storage.models.access.GroupAccessModel;
 import ch.threema.storage.models.ballot.BallotModel;
+import ch.threema.storage.models.data.LocationDataModel;
 import ch.threema.storage.models.data.MessageContentsType;
 import ch.threema.storage.models.data.media.FileDataModel;
 
@@ -156,14 +157,16 @@ public class GroupMessageReceiver implements MessageReceiver<GroupMessageModel>
 	}
 
 	@Override
-	public boolean createBoxedLocationMessage(final double lat, final double lng, final float acc, final String poiName, GroupMessageModel messageModel) throws ThreemaException {
+	public boolean createBoxedLocationMessage(GroupMessageModel messageModel) throws ThreemaException {
 		return this.sendMessage(messageId -> {
+			final LocationDataModel locationDataModel = messageModel.getLocationData();
 			final GroupLocationMessage msg = new GroupLocationMessage();
 			msg.setMessageId(messageId);
-			msg.setLatitude(lat);
-			msg.setLongitude(lng);
-			msg.setAccuracy(acc);
-			msg.setPoiName(poiName);
+			msg.setLatitude(locationDataModel.getLatitude());
+			msg.setLongitude(locationDataModel.getLongitude());
+			msg.setAccuracy(locationDataModel.getAccuracy());
+			msg.setPoiName(locationDataModel.getPoi());
+			msg.setPoiAddress(locationDataModel.getAddress());
 
 			if (messageId != null) {
 				messageModel.setApiMessageId(messageId.toString());

+ 1 - 5
app/src/main/java/ch/threema/app/messagereceiver/MessageReceiver.java

@@ -129,15 +129,11 @@ public interface MessageReceiver<M extends AbstractMessageModel> {
 	/**
 	 * send a location message
 	 *
-	 * @param lat
-	 * @param lng
-	 * @param acc
-	 * @param poiName
 	 * @param messageModel
 	 * @return
 	 * @throws ThreemaException
 	 */
-	boolean createBoxedLocationMessage(double lat, double lng, float acc, String poiName, M messageModel) throws ThreemaException;
+	boolean createBoxedLocationMessage(M messageModel) throws ThreemaException;
 
 	/**
 	 * send a file message

+ 2 - 2
app/src/main/java/ch/threema/app/services/LocaleServiceImpl.java

@@ -45,11 +45,11 @@ public class LocaleServiceImpl implements LocaleService {
 		if (this.countryIsoCode == null) {
 			try {
 				TelephonyManager tm = (TelephonyManager) this.context.getSystemService(Context.TELEPHONY_SERVICE);
-				this.countryIsoCode = tm.getSimCountryIso().toUpperCase();
+				this.countryIsoCode = tm.getSimCountryIso().toUpperCase(Locale.US);
 			}
 			catch (Exception x) {
 				//do nothing
-				//is TELEPHONY_SERVICE disabled?s
+				//is TELEPHONY_SERVICE disabled?
 			}
 
 			if(this.countryIsoCode == null || this.countryIsoCode.length() == 0) {

+ 21 - 22
app/src/main/java/ch/threema/app/services/MessageServiceImpl.java

@@ -44,7 +44,6 @@ import com.neilalexander.jnacl.NaCl;
 
 import org.apache.commons.io.IOUtils;
 import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 
 import java.io.BufferedInputStream;
 import java.io.ByteArrayInputStream;
@@ -121,6 +120,7 @@ import ch.threema.app.video.transcoder.VideoConfig;
 import ch.threema.app.video.transcoder.VideoTranscoder;
 import ch.threema.base.ProgressListener;
 import ch.threema.base.ThreemaException;
+import ch.threema.base.utils.LoggingUtil;
 import ch.threema.base.utils.Utils;
 import ch.threema.domain.models.MessageId;
 import ch.threema.domain.protocol.blob.BlobUploader;
@@ -195,7 +195,7 @@ import static ch.threema.app.ui.MediaItem.TYPE_VOICEMESSAGE;
 import static ch.threema.domain.protocol.csp.messages.file.FileData.RENDERING_STICKER;
 
 public class MessageServiceImpl implements MessageService {
-	private static final Logger logger = LoggerFactory.getLogger(MessageServiceImpl.class);
+	private static final Logger logger = LoggingUtil.getThreemaLogger("MessageServiceImpl");
 
 	private final MessageQueue messageQueue;
 	public static final String MESSAGE_QUEUE_SAVE_FILE = "msgqueue.ser";
@@ -480,13 +480,7 @@ public class MessageServiceImpl implements MessageService {
 
 		this.fireOnCreatedMessage(messageModel);
 
-		receiver.createBoxedLocationMessage(
-				location.getLatitude(),
-				location.getLongitude(),
-				location.getAccuracy(),
-				poiName,
-				messageModel);
-
+		receiver.createBoxedLocationMessage(messageModel);
 
 		messageModel.setState(receiver.sendMediaData() ? MessageState.SENDING : MessageState.SENT);
 		receiver.saveLocalModel(messageModel);
@@ -2092,6 +2086,7 @@ public class MessageServiceImpl implements MessageService {
 				messageModel);
 	}
 
+	@WorkerThread
 	private GroupMessageModel saveGroupMessage(GroupLocationMessage message, GroupMessageModel messageModel) throws SQLException {
 		GroupModel groupModel = this.groupService.getGroup(message);
 		boolean isNewMessage = false;
@@ -2114,15 +2109,16 @@ public class MessageServiceImpl implements MessageService {
 			isNewMessage = true;
 		}
 
-		String address = null;
-		try {
-			address = GeoLocationUtil.getAddressFromLocation(context, message.getLatitude(), message.getLongitude());
-		} catch (IOException e) {
-			logger.error("Exception", e);
-			//do not show this error!
+		String address = message.getPoiAddress();
+		if (TestUtil.empty(address)) {
+			try {
+				address = GeoLocationUtil.getAddressFromLocation(context, message.getLatitude(), message.getLongitude());
+			} catch (IOException e) {
+				logger.error("Exception", e);
+				//do not show this error!
+			}
 		}
 
-
 		messageModel.setLocationData(new LocationDataModel(
 				message.getLatitude(),
 				message.getLongitude(),
@@ -2314,6 +2310,7 @@ public class MessageServiceImpl implements MessageService {
 		return success;
 	}
 
+	@WorkerThread
 	private MessageModel saveBoxMessage(BoxLocationMessage message, MessageModel messageModel) {
 		ContactModel contactModel = this.contactService.getByIdentity(message.getFromIdentity());
 		ContactMessageReceiver r = this.contactService.createReceiver(contactModel);
@@ -2325,12 +2322,14 @@ public class MessageServiceImpl implements MessageService {
 			messageModel.setOutbox(false);
 		}
 
-		String address = null;
-		try {
-			address = GeoLocationUtil.getAddressFromLocation(context, message.getLatitude(), message.getLongitude());
-		} catch (IOException e) {
-			logger.error("Exception", e);
-			//do not show this error!
+		String address = message.getPoiAddress();
+		if (TestUtil.empty(address)) {
+			try {
+				address = GeoLocationUtil.getAddressFromLocation(context, message.getLatitude(), message.getLongitude());
+			} catch (IOException e) {
+				logger.error("Exception", e);
+				//do not show this error!
+			}
 		}
 
 		messageModel.setLocationData(new LocationDataModel(

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

@@ -44,7 +44,6 @@ import com.google.common.collect.ArrayListMultimap;
 import com.google.common.collect.ListMultimap;
 
 import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 
 import java.util.ArrayList;
 import java.util.Date;
@@ -66,12 +65,13 @@ import ch.threema.app.managers.ServiceManager;
 import ch.threema.app.services.FileService;
 import ch.threema.app.services.UserService;
 import ch.threema.base.ThreemaException;
+import ch.threema.base.utils.LoggingUtil;
 import ch.threema.storage.models.ContactModel;
 
 import static ch.threema.storage.models.ContactModel.DEFAULT_ANDROID_CONTACT_AVATAR_EXPIRY;
 
 public class AndroidContactUtil {
-	private static final Logger logger = LoggerFactory.getLogger(AndroidContactUtil.class);
+	private static final Logger logger = LoggingUtil.getThreemaLogger("AndroidContactUtil");
 	private UserService userService;
 	private FileService fileService;
 
@@ -223,7 +223,7 @@ public class AndroidContactUtil {
 			}
 		}
 
-		logger.info("Unable to get avatar for {} lookupKey = {} contactUri = {}", contactModel.getIdentity(), contactModel.getAndroidContactLookupKey(), contactUri);
+		logger.debug("Unable to get avatar for {} lookupKey = {} contactUri = {}", contactModel.getIdentity(), contactModel.getAndroidContactLookupKey(), contactUri);
 		return false;
 	}
 

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

@@ -47,7 +47,6 @@ import android.view.animation.TranslateAnimation;
 import android.widget.LinearLayout;
 
 import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
@@ -58,9 +57,10 @@ import androidx.transition.Fade;
 import androidx.transition.Transition;
 import androidx.transition.TransitionManager;
 import ch.threema.app.R;
+import ch.threema.base.utils.LoggingUtil;
 
 public class AnimationUtil {
-	private static final Logger logger = LoggerFactory.getLogger(AnimationUtil.class);
+	private static final Logger logger = LoggingUtil.getThreemaLogger("AnimationUtil");
 
 	public static void expand(final View v) {
 		expand(v, null);

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

@@ -25,7 +25,6 @@ import android.content.Context;
 import android.os.Bundle;
 
 import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -38,10 +37,11 @@ import androidx.annotation.StringRes;
 import ch.threema.app.R;
 import ch.threema.app.ThreemaApplication;
 import ch.threema.app.services.AppRestrictionService;
+import ch.threema.base.utils.LoggingUtil;
 
 public class AppRestrictionUtil {
 
-	private static final Logger logger = LoggerFactory.getLogger(AppRestrictionUtil.class);
+	private static final Logger logger = LoggingUtil.getThreemaLogger("AppRestrictionUtil");
 
 	public static boolean isAddContactDisabled(Context context) {
 		return getBoolRestriction(context, R.string.restriction__disable_add_contact);

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

@@ -39,7 +39,6 @@ import android.net.Uri;
 import android.provider.ContactsContract;
 
 import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 
 import java.io.IOException;
 import java.io.InputStream;
@@ -52,12 +51,13 @@ import androidx.core.graphics.drawable.RoundedBitmapDrawable;
 import androidx.core.graphics.drawable.RoundedBitmapDrawableFactory;
 import androidx.vectordrawable.graphics.drawable.VectorDrawableCompat;
 import ch.threema.app.R;
+import ch.threema.base.utils.LoggingUtil;
 
 import static ch.threema.app.dialogs.ContactEditDialog.CONTACT_AVATAR_HEIGHT_PX;
 import static ch.threema.app.dialogs.ContactEditDialog.CONTACT_AVATAR_WIDTH_PX;
 
 public class AvatarConverterUtil {
-	private static final Logger logger = LoggerFactory.getLogger(AvatarConverterUtil.class);
+	private static final Logger logger = LoggingUtil.getThreemaLogger("AvatarConverterUtil");
 
 	private static int avatarSize = -1, iconSize = -1, iconOffset = -1;
 

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

@@ -26,7 +26,6 @@ import android.content.Intent;
 import android.widget.Toast;
 
 import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 
 import java.security.SecureRandom;
 import java.util.List;
@@ -48,6 +47,7 @@ import ch.threema.app.messagereceiver.GroupMessageReceiver;
 import ch.threema.app.messagereceiver.MessageReceiver;
 import ch.threema.app.services.UserService;
 import ch.threema.app.services.ballot.BallotService;
+import ch.threema.base.utils.LoggingUtil;
 import ch.threema.domain.protocol.csp.connection.ConnectionState;
 import ch.threema.domain.protocol.csp.connection.MessageTooLongException;
 import ch.threema.storage.models.AbstractMessageModel;
@@ -56,7 +56,7 @@ import ch.threema.storage.models.ballot.BallotModel;
 
 @SuppressWarnings("rawtypes")
 public class BallotUtil {
-	private static final Logger logger = LoggerFactory.getLogger(BallotUtil.class);
+	private static final Logger logger = LoggingUtil.getThreemaLogger("BallotUtil");
 
 	public static boolean canVote(BallotModel model, String identity) {
 		return model != null

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

@@ -30,7 +30,6 @@ import android.os.Build;
 import android.widget.Toast;
 
 import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 
 import androidx.biometric.BiometricManager;
 import androidx.core.app.ActivityCompat;
@@ -38,11 +37,12 @@ import androidx.fragment.app.Fragment;
 import ch.threema.app.R;
 import ch.threema.app.ThreemaApplication;
 import ch.threema.app.activities.BiometricLockActivity;
+import ch.threema.base.utils.LoggingUtil;
 
 import static ch.threema.app.activities.BiometricLockActivity.INTENT_DATA_AUTHENTICATION_TYPE;
 
 public class BiometricUtil {
-	private static final Logger logger = LoggerFactory.getLogger(BiometricUtil.class);
+	private static final Logger logger = LoggingUtil.getThreemaLogger("BiometricUtil");
 
 	public static boolean isBiometricsSupported(Context context) {
 		String toast = context.getString(R.string.biometrics_not_avilable);

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

@@ -39,7 +39,6 @@ import android.provider.MediaStore;
 import android.view.View;
 
 import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 
 import java.io.BufferedInputStream;
 import java.io.ByteArrayOutputStream;
@@ -55,9 +54,10 @@ import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.annotation.WorkerThread;
 import ch.threema.app.webclient.utils.ThumbnailUtils;
+import ch.threema.base.utils.LoggingUtil;
 
 public class BitmapUtil {
-	private static final Logger logger = LoggerFactory.getLogger(BitmapUtil.class);
+	private static final Logger logger = LoggingUtil.getThreemaLogger("BitmapUtil");
 
 	private static final int DEFAULT_JPG_QUALITY = 80;
 	private static final int DEFAULT_PNG_QUALITY = 100; // PNG is lossless anyway

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

@@ -28,7 +28,6 @@ import android.os.AsyncTask;
 import android.widget.ImageView;
 
 import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 
 import java.io.FileNotFoundException;
 import java.io.IOException;
@@ -38,11 +37,12 @@ import java.lang.ref.WeakReference;
 import androidx.appcompat.content.res.AppCompatResources;
 import ch.threema.app.R;
 import ch.threema.app.ThreemaApplication;
+import ch.threema.base.utils.LoggingUtil;
 
 import static ch.threema.app.utils.BitmapUtil.FLIP_NONE;
 
 public class BitmapWorkerTask extends AsyncTask<BitmapWorkerTaskParams, Void, Bitmap> {
-	private static final Logger logger = LoggerFactory.getLogger(BitmapWorkerTask.class);
+	private static final Logger logger = LoggingUtil.getThreemaLogger("BitmapWorkerTask");
 
 	private final WeakReference<ImageView> imageViewReference;
 

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

@@ -67,7 +67,6 @@ import com.google.android.material.snackbar.BaseTransientBottomBar;
 import com.google.android.material.snackbar.Snackbar;
 
 import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -112,6 +111,7 @@ import ch.threema.app.services.LockAppService;
 import ch.threema.app.services.PreferenceService;
 import ch.threema.app.services.license.LicenseService;
 import ch.threema.app.threemasafe.ThreemaSafeConfigureActivity;
+import ch.threema.base.utils.LoggingUtil;
 
 import static android.content.res.Configuration.UI_MODE_NIGHT_YES;
 import static android.view.View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR;
@@ -124,7 +124,7 @@ import static ch.threema.app.services.NotificationService.NOTIFICATION_CHANNEL_A
 import static ch.threema.app.services.NotificationServiceImpl.APP_RESTART_NOTIFICATION_ID;
 
 public class ConfigUtils {
-	private static final Logger logger = LoggerFactory.getLogger(ConfigUtils.class);
+	private static final Logger logger = LoggingUtil.getThreemaLogger("ConfigUtils");
 
 	public static final int THEME_LIGHT = 0;
 	public static final int THEME_DARK = 1;

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

@@ -29,7 +29,6 @@ import android.provider.ContactsContract;
 import android.text.TextUtils;
 
 import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 
 import java.util.Date;
 
@@ -41,10 +40,11 @@ import ch.threema.app.ThreemaApplication;
 import ch.threema.app.services.FileService;
 import ch.threema.app.services.IdListService;
 import ch.threema.app.services.PreferenceService;
+import ch.threema.base.utils.LoggingUtil;
 import ch.threema.storage.models.ContactModel;
 
 public class ContactUtil {
-	private static final Logger logger = LoggerFactory.getLogger(ContactUtil.class);
+	private static final Logger logger = LoggingUtil.getThreemaLogger("ContactUtil");
 
 	public static final int CHANNEL_NAME_MAX_LENGTH_BYTES = 256;
 

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

@@ -25,7 +25,6 @@ import android.content.Context;
 import android.graphics.Bitmap;
 
 import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 
 import java.util.Date;
 import java.util.HashMap;
@@ -43,6 +42,7 @@ import ch.threema.app.services.GroupService;
 import ch.threema.app.services.MessageService;
 import ch.threema.app.services.NotificationService;
 import ch.threema.base.ThreemaException;
+import ch.threema.base.utils.LoggingUtil;
 import ch.threema.domain.protocol.csp.messages.file.FileData;
 import ch.threema.storage.models.AbstractMessageModel;
 import ch.threema.storage.models.ContactModel;
@@ -53,7 +53,7 @@ import ch.threema.storage.models.MessageType;
 import ch.threema.storage.models.data.MessageContentsType;
 
 public class ConversationNotificationUtil {
-	private static final Logger logger = LoggerFactory.getLogger(ConversationNotificationUtil.class);
+	private static final Logger logger = LoggingUtil.getThreemaLogger("ConversationNotificationUtil");
 
 	protected static final HashMap<String, NotificationService.ConversationNotificationGroup> notificationGroupHashMap = new HashMap<>();
 

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

@@ -33,7 +33,6 @@ import android.os.Build;
 import android.provider.ContactsContract;
 
 import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 
 import java.util.Calendar;
 import java.util.Set;
@@ -51,11 +50,11 @@ import ch.threema.app.services.ContactService;
 import ch.threema.app.services.DeadlineListService;
 import ch.threema.app.services.PreferenceService;
 import ch.threema.app.stores.IdentityStore;
+import ch.threema.base.utils.LoggingUtil;
 import ch.threema.storage.models.ContactModel;
 
-
 public class DNDUtil {
-	private static final Logger logger = LoggerFactory.getLogger(DNDUtil.class);
+	private static final Logger logger = LoggingUtil.getThreemaLogger("DNDUtil");
 	private DeadlineListService mutedChatsListService;
 	private DeadlineListService mentionOnlyChatsListService;
 	private final IdentityStore identityStore;

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

@@ -25,14 +25,15 @@ import android.content.Context;
 
 import org.apache.commons.io.FileUtils;
 import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 
 import java.io.File;
 import java.io.IOException;
 import java.util.UUID;
 
+import ch.threema.base.utils.LoggingUtil;
+
 public class DeviceIdUtil {
-	private static final Logger logger = LoggerFactory.getLogger(DeviceIdUtil.class);
+	private static final Logger logger = LoggingUtil.getThreemaLogger("DeviceIdUtil");
 
 	private static final String DEVICE_ID_FILENAME = "device_id";
 

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

@@ -26,7 +26,6 @@ import android.content.res.ColorStateList;
 import android.content.res.TypedArray;
 
 import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 
 import androidx.annotation.UiThread;
 import androidx.fragment.app.DialogFragment;
@@ -34,9 +33,10 @@ import androidx.fragment.app.FragmentManager;
 import ch.threema.app.R;
 import ch.threema.app.dialogs.CancelableHorizontalProgressDialog;
 import ch.threema.app.dialogs.GenericProgressDialog;
+import ch.threema.base.utils.LoggingUtil;
 
 public abstract class DialogUtil {
-	private static final Logger logger = LoggerFactory.getLogger(DialogUtil.class);
+	private static final Logger logger = LoggingUtil.getThreemaLogger("DialogUtil");
 
 	public static void dismissDialog(FragmentManager fragmentManager, String tag, boolean allowStateLoss) {
 		logger.debug("dismissDialog: " + tag);

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

@@ -41,7 +41,6 @@ import android.os.Build;
 import android.util.Pair;
 
 import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 
 import java.io.BufferedInputStream;
 import java.io.ByteArrayInputStream;
@@ -83,6 +82,7 @@ import androidx.annotation.IntDef;
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.annotation.RestrictTo;
+import ch.threema.base.utils.LoggingUtil;
 
 import static ch.threema.base.utils.Utils.byteArrayToHexString;
 import static java.nio.charset.StandardCharsets.US_ASCII;
@@ -95,7 +95,7 @@ import static java.nio.charset.StandardCharsets.US_ASCII;
  * Attribute mutation is supported for JPEG image files.
  */
 public class ExifInterface {
-	private static final Logger logger = LoggerFactory.getLogger(ExifInterface.class);
+	private static final Logger logger = LoggingUtil.getThreemaLogger("ExifInterface");
 
 	private static final boolean DEBUG = false;
 

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

@@ -22,15 +22,16 @@
 package ch.threema.app.utils;
 
 import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 
 import java.util.Random;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
 import java.util.concurrent.Future;
 
+import ch.threema.base.utils.LoggingUtil;
+
 public class ExponentialBackOffUtil {
-	private static final Logger logger = LoggerFactory.getLogger(ExponentialBackOffUtil.class);
+	private static final Logger logger = LoggingUtil.getThreemaLogger("ExponentialBackOffUtil");
 	protected final static ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
 	private Random random;
 

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

@@ -43,7 +43,6 @@ import android.widget.Toast;
 
 import org.apache.commons.io.IOUtils;
 import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 
 import java.io.File;
 import java.io.FileInputStream;
@@ -66,6 +65,7 @@ import ch.threema.app.camera.CameraActivity;
 import ch.threema.app.filepicker.FilePickerActivity;
 import ch.threema.app.services.FileService;
 import ch.threema.app.ui.MediaItem;
+import ch.threema.base.utils.LoggingUtil;
 import ch.threema.storage.models.AbstractMessageModel;
 import ch.threema.storage.models.data.media.FileDataModel;
 
@@ -73,7 +73,7 @@ import static ch.threema.app.ThreemaApplication.MAX_BLOB_SIZE;
 import static ch.threema.app.filepicker.FilePickerActivity.INTENT_DATA_DEFAULT_PATH;
 
 public class FileUtil {
-	private static final Logger logger = LoggerFactory.getLogger(FileUtil.class);
+	private static final Logger logger = LoggingUtil.getThreemaLogger("FileUtil");
 
 	private FileUtil() {
 

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

@@ -32,7 +32,6 @@ import android.os.Message;
 import android.widget.TextView;
 
 import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 
 import java.io.IOException;
 import java.net.URLEncoder;
@@ -41,13 +40,15 @@ import java.util.List;
 import java.util.Locale;
 import java.util.Map;
 
+import androidx.annotation.WorkerThread;
 import ch.threema.app.R;
 import ch.threema.app.services.MessageService;
+import ch.threema.base.utils.LoggingUtil;
 import ch.threema.storage.models.AbstractMessageModel;
 import ch.threema.storage.models.data.LocationDataModel;
 
 public class GeoLocationUtil {
-	private static final Logger logger = LoggerFactory.getLogger(GeoLocationUtil.class);
+	private static final Logger logger = LoggingUtil.getThreemaLogger("GeoLocationUtil");
 
 	private TextView targetView;
 
@@ -57,6 +58,7 @@ public class GeoLocationUtil {
 		this.targetView = targetView;
 	}
 
+	@WorkerThread
 	public static String getAddressFromLocation(Context context, double latitude, double longitude) throws IOException {
 		String addressString = context.getString(R.string.unknown_address);
 		String key = String.valueOf(latitude) + '|' + String.valueOf(longitude);

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

@@ -37,19 +37,19 @@ import android.provider.MediaStore;
 
 import org.msgpack.core.annotations.Nullable;
 import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 
 import java.util.HashMap;
 
 import androidx.annotation.WorkerThread;
 import ch.threema.app.R;
 import ch.threema.app.ui.MediaItem;
+import ch.threema.base.utils.LoggingUtil;
 
 import static android.media.MediaMetadataRetriever.OPTION_CLOSEST_SYNC;
 import static ch.threema.app.services.MessageServiceImpl.THUMBNAIL_SIZE_PX;
 
 public class IconUtil {
-	private static final Logger logger = LoggerFactory.getLogger(IconUtil.class);
+	private static final Logger logger = LoggingUtil.getThreemaLogger("IconUtil");
 	private static final HashMap<String, Integer> mimeIcons = new HashMap<>();
 
 	private static void add(String mimeType, int resId) {

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

@@ -31,7 +31,6 @@ import android.os.SystemClock;
 import com.mapbox.mapboxsdk.geometry.LatLng;
 
 import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 
 import java.util.ArrayList;
 import java.util.Iterator;
@@ -42,7 +41,6 @@ import ch.threema.app.BuildConfig;
 import ch.threema.app.ThreemaApplication;
 import ch.threema.app.activities.ComposeMessageActivity;
 import ch.threema.app.activities.HomeActivity;
-import ch.threema.app.activities.RecipientListBaseActivity;
 import ch.threema.app.backuprestore.BackupRestoreDataService;
 import ch.threema.app.fragments.ComposeMessageFragment;
 import ch.threema.app.managers.ServiceManager;
@@ -55,6 +53,7 @@ import ch.threema.app.services.ContactService;
 import ch.threema.app.services.DistributionListService;
 import ch.threema.app.services.GroupService;
 import ch.threema.app.services.MessageService;
+import ch.threema.base.utils.LoggingUtil;
 import ch.threema.storage.models.AbstractMessageModel;
 import ch.threema.storage.models.ContactModel;
 import ch.threema.storage.models.ConversationModel;
@@ -68,7 +67,7 @@ import ch.threema.storage.models.ballot.BallotModel;
 import ch.threema.storage.models.group.GroupInviteModel;
 
 public class IntentDataUtil {
-	private static final Logger logger = LoggerFactory.getLogger(RecipientListBaseActivity.class);
+	private static final Logger logger = LoggingUtil.getThreemaLogger("RecipientListBaseActivity");
 
 	public static final String ACTION_LICENSE_NOT_ALLOWED = BuildConfig.APPLICATION_ID + "license_not_allowed";
 	public static final String ACTION_CONTACTS_CHANGED = BuildConfig.APPLICATION_ID + "contacts_changed";

+ 353 - 0
app/src/main/java/ch/threema/app/utils/LinkifyCompatUtil.java

@@ -0,0 +1,353 @@
+/*  _____ _
+ * |_   _| |_  _ _ ___ ___ _ __  __ _
+ *   | | | ' \| '_/ -_) -_) '  \/ _` |_
+ *   |_| |_||_|_| \___\___|_|_|_\__,_(_)
+ *
+ * Threema for Android
+ * Copyright (c) 2019-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/>.
+ */
+
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package ch.threema.app.utils;
+
+import android.annotation.SuppressLint;
+import android.telephony.PhoneNumberUtils;
+import android.text.Spannable;
+import android.text.SpannableString;
+import android.text.Spanned;
+import android.text.method.LinkMovementMethod;
+import android.text.method.MovementMethod;
+import android.text.style.URLSpan;
+import android.text.util.Linkify;
+import android.text.util.Linkify.MatchFilter;
+import android.text.util.Linkify.TransformFilter;
+import android.widget.TextView;
+
+import com.google.i18n.phonenumbers.PhoneNumberMatch;
+import com.google.i18n.phonenumbers.PhoneNumberUtil;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Locale;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import androidx.annotation.IntDef;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.core.util.PatternsCompat;
+import ch.threema.app.ThreemaApplication;
+import ch.threema.app.services.LocaleService;
+
+/**
+ * LinkifyCompatUtil is based on AOSPs LinkifyCompat ensuring consistent behaviour across different Android versions
+ * and device manufacturers as well as fixing the lenient phone number handling on some devices
+ */
+public final class LinkifyCompatUtil {
+
+    private static final Comparator<LinkSpec>  COMPARATOR = (a, b) -> {
+        if (a.start < b.start) {
+            return -1;
+        }
+
+        if (a.start > b.start) {
+            return 1;
+        }
+
+	    return Integer.compare(b.end, a.end);
+
+    };
+
+    @IntDef(flag = true, value = { Linkify.WEB_URLS, Linkify.EMAIL_ADDRESSES, Linkify.PHONE_NUMBERS,
+            Linkify.MAP_ADDRESSES, Linkify.ALL })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface LinkifyMask {}
+
+    /**
+     *  Scans the text of the provided Spannable and turns all occurrences
+     *  of the link types indicated in the mask into clickable links.
+     *  If the mask is nonzero, it also removes any existing URLSpans
+     *  attached to the Spannable, to avoid problems if you call it
+     *  repeatedly on the same text.
+     *
+     *  @param text Spannable whose text is to be marked-up with links
+     *  @param mask Mask to define which kinds of links will be searched.
+     *
+     *  @return True if at least one link is found and applied.
+     */
+    @SuppressLint("RestrictedApi")
+    public static boolean addLinks(@NonNull Spannable text, @LinkifyMask int mask) {
+        if (shouldAddLinksFallbackToFramework()) {
+            return Linkify.addLinks(text, mask);
+        }
+        if (mask == 0) {
+            return false;
+        }
+
+        URLSpan[] old = text.getSpans(0, text.length(), URLSpan.class);
+
+        for (int i = old.length - 1; i >= 0; i--) {
+            text.removeSpan(old[i]);
+        }
+
+	    final ArrayList<LinkSpec> links = new ArrayList<>();
+
+        if ((mask & Linkify.WEB_URLS) != 0) {
+            gatherLinks(links, text, PatternsCompat.AUTOLINK_WEB_URL,
+                    new String[] { "http://", "https://", "rtsp://" },
+                    Linkify.sUrlMatchFilter, null);
+        }
+
+        if ((mask & Linkify.EMAIL_ADDRESSES) != 0) {
+            gatherLinks(links, text, PatternsCompat.AUTOLINK_EMAIL_ADDRESS,
+                    new String[] { "mailto:" },
+                    null, null);
+        }
+
+        // Threema-added
+	    if ((mask & Linkify.PHONE_NUMBERS) != 0) {
+		    gatherTelLinks(links, text);
+	    }
+
+	    pruneOverlaps(links, text);
+
+        if (links.size() == 0) {
+            return false;
+        }
+
+        for (LinkSpec link: links) {
+            if (link.frameworkAddedSpan == null) {
+                applyLink(link.url, link.start, link.end, text);
+            }
+        }
+
+        return true;
+    }
+
+    /**
+     *  Scans the text of the provided TextView and turns all occurrences of
+     *  the link types indicated in the mask into clickable links.  If matches
+     *  are found the movement method for the TextView is set to
+     *  LinkMovementMethod.
+     *
+     *  @param text TextView whose text is to be marked-up with links
+     *  @param mask Mask to define which kinds of links will be searched.
+     *
+     *  @return True if at least one link is found and applied.
+     */
+    public static boolean addLinks(@NonNull TextView text, @LinkifyMask int mask) {
+        if (shouldAddLinksFallbackToFramework()) {
+            return Linkify.addLinks(text, mask);
+        }
+        if (mask == 0) {
+            return false;
+        }
+
+        CharSequence t = text.getText();
+
+        if (t instanceof Spannable) {
+            if (addLinks((Spannable) t, mask)) {
+                addLinkMovementMethod(text);
+                return true;
+            }
+
+            return false;
+        } else {
+            SpannableString s = SpannableString.valueOf(t);
+
+            if (addLinks(s, mask)) {
+                addLinkMovementMethod(text);
+                text.setText(s);
+
+                return true;
+            }
+
+            return false;
+        }
+    }
+
+    private static boolean shouldAddLinksFallbackToFramework() {
+    	// Threema-added: Never use the system's linkify
+        return false;
+		// return Build.VERSION.SDK_INT >= 28;
+    }
+
+    private static void addLinkMovementMethod(@NonNull TextView t) {
+        MovementMethod m = t.getMovementMethod();
+
+        if (!(m instanceof LinkMovementMethod)) {
+            if (t.getLinksClickable()) {
+                t.setMovementMethod(LinkMovementMethod.getInstance());
+            }
+        }
+    }
+
+    private static String makeUrl(@NonNull String url, @NonNull String[] prefixes,
+            Matcher matcher, @Nullable TransformFilter filter) {
+        if (filter != null) {
+            url = filter.transformUrl(matcher, url);
+        }
+
+        boolean hasPrefix = false;
+
+        for (int i = 0; i < prefixes.length; i++) {
+            if (url.regionMatches(true, 0, prefixes[i], 0, prefixes[i].length())) {
+                hasPrefix = true;
+
+                // Fix capitalization if necessary
+                if (!url.regionMatches(false, 0, prefixes[i], 0, prefixes[i].length())) {
+                    url = prefixes[i] + url.substring(prefixes[i].length());
+                }
+
+                break;
+            }
+        }
+
+        if (!hasPrefix && prefixes.length > 0) {
+            url = prefixes[0] + url;
+        }
+
+        return url;
+    }
+
+    private static void gatherLinks(ArrayList<LinkSpec> links,
+            Spannable s, Pattern pattern, String[] schemes,
+            MatchFilter matchFilter, TransformFilter transformFilter) {
+        Matcher m = pattern.matcher(s);
+
+        while (m.find()) {
+            int start = m.start();
+            int end = m.end();
+
+            if (matchFilter == null || matchFilter.acceptMatch(s, start, end)) {
+                LinkSpec spec = new LinkSpec();
+                String url = makeUrl(m.group(0), schemes, m, transformFilter);
+
+                spec.url = url;
+                spec.start = start;
+                spec.end = end;
+
+                links.add(spec);
+            }
+        }
+    }
+
+	private static boolean gatherTelLinks(@NonNull ArrayList<LinkSpec> links, @NonNull Spannable s) {
+    	// Threema-added: try to get the current locale from LocaleService
+    	String countryCode;
+    	try {
+		    LocaleService localeService = ThreemaApplication.getServiceManager().getLocaleService();
+		    countryCode = localeService.getCountryIsoCode();
+	    } catch (Exception e) {
+    		countryCode = Locale.getDefault().getCountry();
+	    }
+		PhoneNumberUtil phoneUtil = PhoneNumberUtil.getInstance();
+    	// Threema-changed: only allow for valid phone numbers
+		Iterable<PhoneNumberMatch> matches = phoneUtil.findNumbers(s.toString(), countryCode, PhoneNumberUtil.Leniency.VALID, Long.MAX_VALUE);
+		for (PhoneNumberMatch match : matches) {
+			LinkSpec spec = new LinkSpec();
+			spec.url = "tel:" + PhoneNumberUtils.normalizeNumber(match.rawString());
+			spec.start = match.start();
+			spec.end = match.end();
+			links.add(spec);
+		}
+		return links.size() > 0;
+	}
+
+	private static void applyLink(String url, int start, int end, Spannable text) {
+        URLSpan span = new URLSpan(url);
+
+        text.setSpan(span, start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+    }
+
+    private static void pruneOverlaps(ArrayList<LinkSpec> links, Spannable text) {
+        // Append spans added by framework
+        URLSpan[] urlSpans = text.getSpans(0, text.length(), URLSpan.class);
+        for (int i = 0; i < urlSpans.length; i++) {
+            LinkSpec spec = new LinkSpec();
+            spec.frameworkAddedSpan = urlSpans[i];
+            spec.start = text.getSpanStart(urlSpans[i]);
+            spec.end = text.getSpanEnd(urlSpans[i]);
+            links.add(spec);
+        }
+
+        Collections.sort(links, COMPARATOR);
+
+        int len = links.size();
+        int i = 0;
+
+        while (i < len - 1) {
+            LinkSpec a = links.get(i);
+            LinkSpec b = links.get(i + 1);
+            int remove = -1;
+
+            if ((a.start <= b.start) && (a.end > b.start)) {
+                if (b.end <= a.end) {
+                    remove = i + 1;
+                } else if ((a.end - a.start) > (b.end - b.start)) {
+                    remove = i + 1;
+                } else if ((a.end - a.start) < (b.end - b.start)) {
+                    remove = i;
+                }
+
+                if (remove != -1) {
+                    URLSpan span = links.get(remove).frameworkAddedSpan;
+                    if (span != null) {
+                        text.removeSpan(span);
+                    }
+                    links.remove(remove);
+                    len--;
+                    continue;
+                }
+
+            }
+
+            i++;
+        }
+    }
+
+    /**
+     * Do not create this static utility class.
+     */
+    private LinkifyCompatUtil() {}
+
+    private static class LinkSpec {
+        URLSpan frameworkAddedSpan;
+        String url;
+        int start;
+        int end;
+
+        LinkSpec() {
+        }
+    }
+}

+ 4 - 5
app/src/main/java/ch/threema/app/utils/LinkifyUtil.java

@@ -42,7 +42,6 @@ import android.widget.TextView;
 import android.widget.Toast;
 
 import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 
 import java.net.IDN;
 import java.util.regex.Pattern;
@@ -61,13 +60,13 @@ import ch.threema.app.dialogs.GenericAlertDialog;
 import ch.threema.app.fragments.ComposeMessageFragment;
 import ch.threema.app.services.ContactService;
 import ch.threema.app.ui.MentionClickableSpan;
+import ch.threema.base.utils.LoggingUtil;
 import ch.threema.storage.models.AbstractMessageModel;
 
 import static android.content.Context.CLIPBOARD_SERVICE;
 
-
 public class LinkifyUtil {
-	private static final Logger logger = LoggerFactory.getLogger(LinkifyUtil.class);
+	private static final Logger logger = LoggingUtil.getThreemaLogger("LinkifyUtil");
 	public static final String DIALOG_TAG_CONFIRM_LINK = "cnfl";
 	private final Pattern compose, add, license;
 	private GestureDetectorCompat gestureDetector;
@@ -141,9 +140,9 @@ public class LinkifyUtil {
 		// do not linkify phone numbers in longer texts because things can get messy on samsung devices
 		// which linkify every kind of number combination imaginable
 		if (includePhoneNumbers) {
-			LinkifyCompat.addLinks(bodyTextView, Linkify.WEB_URLS | Linkify.EMAIL_ADDRESSES | Linkify.PHONE_NUMBERS);
+			LinkifyCompatUtil.addLinks(bodyTextView, Linkify.WEB_URLS | Linkify.EMAIL_ADDRESSES | Linkify.PHONE_NUMBERS);
 		} else {
-			LinkifyCompat.addLinks(bodyTextView, Linkify.WEB_URLS | Linkify.EMAIL_ADDRESSES);
+			LinkifyCompatUtil.addLinks(bodyTextView, Linkify.WEB_URLS | Linkify.EMAIL_ADDRESSES);
 		}
 		LinkifyCompat.addLinks(bodyTextView, this.add, null);
 		LinkifyCompat.addLinks(bodyTextView, this.compose, null);

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

@@ -22,13 +22,13 @@
 package ch.threema.app.utils;
 
 import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 
 import androidx.fragment.app.FragmentManager;
 import ch.threema.app.dialogs.GenericProgressDialog;
+import ch.threema.base.utils.LoggingUtil;
 
 public class LoadingUtil {
-	private static final Logger logger = LoggerFactory.getLogger(LocaleUtil.class);
+	private static final Logger logger = LoggingUtil.getThreemaLogger("LocaleUtil");
 
 	private static String DIALOG_TAG_PROGRESS_LOADINGUTIL = "lou";
 

+ 0 - 5
app/src/main/java/ch/threema/app/utils/LocationUtil.java

@@ -29,9 +29,6 @@ import android.graphics.drawable.Drawable;
 import com.mapbox.mapboxsdk.annotations.Icon;
 import com.mapbox.mapboxsdk.annotations.IconFactory;
 
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
 import androidx.annotation.NonNull;
 import androidx.appcompat.content.res.AppCompatResources;
 import androidx.core.graphics.drawable.DrawableCompat;
@@ -41,8 +38,6 @@ import ch.threema.app.locationpicker.Poi;
 import ch.threema.app.services.PreferenceService;
 
 public class LocationUtil {
-	private static final Logger logger = LoggerFactory.getLogger(LocationUtil.class);
-
 	public static int getPlaceDrawableRes(@NonNull Context context, @NonNull Poi poi, boolean returnDefault) {
 		int id = 0;
 		String defPackage = context.getPackageName();

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

@@ -22,16 +22,16 @@
 package ch.threema.app.utils;
 
 import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 import org.slf4j.Marker;
 
 import androidx.appcompat.app.AppCompatActivity;
 import androidx.fragment.app.FragmentActivity;
 import ch.threema.app.R;
 import ch.threema.app.dialogs.SimpleStringAlertDialog;
+import ch.threema.base.utils.LoggingUtil;
 
 public class LogUtil {
-	private static final Logger logger = LoggerFactory.getLogger(LogUtil.class);
+	private static final Logger logger = LoggingUtil.getThreemaLogger("LogUtil");
 
 	private LogUtil() { }
 

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

@@ -24,10 +24,11 @@ package ch.threema.app.utils;
 import android.content.Context;
 
 import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
+
+import ch.threema.base.utils.LoggingUtil;
 
 public class LoggingUEH implements Thread.UncaughtExceptionHandler {
-	private static final Logger logger = LoggerFactory.getLogger(LoggingUEH.class);
+	private static final Logger logger = LoggingUtil.getThreemaLogger("LoggingUEH");
 
 	private final Thread.UncaughtExceptionHandler defaultUEH;
 	private Context context;

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

@@ -30,12 +30,12 @@ import android.net.Uri;
 import android.os.Build;
 
 import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 
 import java.io.IOException;
 import java.util.EnumSet;
 
 import androidx.annotation.RequiresApi;
+import ch.threema.base.utils.LoggingUtil;
 
 /**
  * A wrapper class for {@link android.media.MediaPlayer}.
@@ -45,7 +45,7 @@ import androidx.annotation.RequiresApi;
  * </p>
  */
 public class MediaPlayerStateWrapper {
-	private static final Logger logger = LoggerFactory.getLogger(MediaPlayerStateWrapper.class);
+	private static final Logger logger = LoggingUtil.getThreemaLogger("MediaPlayerStateWrapper");
 
 	private MediaPlayer mediaPlayer;
 	private State currentState;

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

@@ -24,7 +24,6 @@ package ch.threema.app.utils;
 import android.content.Context;
 
 import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 
 import java.util.ArrayList;
 import java.util.Date;
@@ -40,6 +39,7 @@ import ch.threema.app.R;
 import ch.threema.app.collections.Functional;
 import ch.threema.app.collections.IPredicateNonNull;
 import ch.threema.app.messagereceiver.MessageReceiver;
+import ch.threema.base.utils.LoggingUtil;
 import ch.threema.domain.protocol.csp.ProtocolDefines;
 import ch.threema.domain.protocol.csp.messages.file.FileData;
 import ch.threema.domain.protocol.csp.messages.voip.VoipCallAnswerData;
@@ -53,7 +53,7 @@ import ch.threema.storage.models.data.MessageContentsType;
 import ch.threema.storage.models.data.status.VoipStatusDataModel;
 
 public class MessageUtil {
-	private static final Logger logger = LoggerFactory.getLogger(MessageUtil.class);
+	private static final Logger logger = LoggingUtil.getThreemaLogger("MessageUtil");
 
 	private final static java.util.Set<MessageType> fileMessageModelTypes = EnumSet.of(
 			MessageType.IMAGE,

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

@@ -25,16 +25,16 @@ import android.app.Activity;
 import android.content.Intent;
 
 import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 
 import androidx.annotation.NonNull;
 import androidx.core.app.NavUtils;
 import androidx.core.app.TaskStackBuilder;
 import ch.threema.app.R;
 import ch.threema.app.activities.PinLockActivity;
+import ch.threema.base.utils.LoggingUtil;
 
 public class NavigationUtil {
-	private static final Logger logger = LoggerFactory.getLogger(NavigationUtil.class);
+	private static final Logger logger = LoggingUtil.getThreemaLogger("NavigationUtil");
 
 	public static void navigateUpToHome(@NonNull Activity activity) {
 		// navigate to home and get rid of the backstack (since we may have pulled the rug from under our feet)

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

@@ -28,16 +28,16 @@ import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
 
 import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 
 import java.util.List;
 
 import androidx.annotation.NonNull;
 import androidx.fragment.app.Fragment;
 import ch.threema.app.activities.DisableBatteryOptimizationsActivity;
+import ch.threema.base.utils.LoggingUtil;
 
 public class PowermanagerUtil {
-	private static final Logger logger = LoggerFactory.getLogger(PowermanagerUtil.class);
+	private static final Logger logger = LoggingUtil.getThreemaLogger("PowermanagerUtil");
 
 	// https://stackoverflow.com/questions/48166206/how-to-start-power-manager-of-all-android-manufactures-to-enable-push-notificati/48166241
 	// https://stackoverflow.com/questions/31638986/protected-apps-setting-on-huawei-phones-and-how-to-handle-it

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

@@ -33,7 +33,6 @@ import android.net.Uri;
 import android.text.format.DateUtils;
 
 import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 
 import java.util.Map;
 
@@ -63,10 +62,11 @@ import ch.threema.app.services.RingtoneService;
 import ch.threema.app.stores.PreferenceStore;
 import ch.threema.app.webclient.services.SessionWakeUpServiceImpl;
 import ch.threema.base.ThreemaException;
+import ch.threema.base.utils.LoggingUtil;
 import ch.threema.domain.protocol.csp.connection.ThreemaConnection;
 
 public class PushUtil {
-	private static final Logger logger = LoggerFactory.getLogger(PushUtil.class);
+	private static final Logger logger = LoggingUtil.getThreemaLogger("PushUtil");
 
 	public static final String EXTRA_CLEAR_TOKEN = "clear";
 	public static final String EXTRA_WITH_CALLBACK = "cb";

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

@@ -26,7 +26,6 @@ import android.content.Intent;
 import android.content.pm.ActivityInfo;
 
 import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 
 import androidx.annotation.NonNull;
 import androidx.appcompat.app.AppCompatActivity;
@@ -36,9 +35,10 @@ import ch.threema.app.dialogs.SimpleStringAlertDialog;
 import ch.threema.app.qrscanner.activity.BaseQrScannerActivity;
 import ch.threema.app.qrscanner.activity.CaptureActivity;
 import ch.threema.app.services.QRCodeService;
+import ch.threema.base.utils.LoggingUtil;
 
 public class QRScannerUtil {
-	private static final Logger logger = LoggerFactory.getLogger(QRScannerUtil.class);
+	private static final Logger logger = LoggingUtil.getThreemaLogger("QRScannerUtil");
 
 	private static boolean scanAnyCode;
 	public static final int REQUEST_CODE_QR_SCANNER = 26657;

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

@@ -26,7 +26,6 @@ import android.content.Context;
 import android.graphics.Bitmap;
 
 import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
@@ -41,6 +40,7 @@ import ch.threema.app.messagereceiver.MessageReceiver.MessageReceiverType;
 import ch.threema.app.services.FileService;
 import ch.threema.app.services.MessageService;
 import ch.threema.app.services.UserService;
+import ch.threema.base.utils.LoggingUtil;
 import ch.threema.base.utils.Utils;
 import ch.threema.storage.models.AbstractMessageModel;
 import ch.threema.storage.models.DistributionListMessageModel;
@@ -52,7 +52,7 @@ import static ch.threema.app.messagereceiver.MessageReceiver.Type_DISTRIBUTION_L
 import static ch.threema.app.messagereceiver.MessageReceiver.Type_GROUP;
 
 public class QuoteUtil {
-	private static final Logger logger = LoggerFactory.getLogger(QuoteUtil.class);
+	private static final Logger logger = LoggingUtil.getThreemaLogger("QuoteUtil");
 
 	private static final Pattern bodyMatchPattern = Pattern.compile("(?sm)(\\A> .*?)^(?!> ).+");
 	private static final Pattern quoteV1MatchPattern = Pattern.compile("(?sm)\\A> ([A-Z0-9*]{8}): (.*?)^(?!> ).+");

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

@@ -22,14 +22,15 @@
 package ch.threema.app.utils;
 
 import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 
 import java.io.File;
 import java.io.IOException;
 import java.io.RandomAccessFile;
 
+import ch.threema.base.utils.LoggingUtil;
+
 public class SecureDeleteUtil {
-	private static final Logger logger = LoggerFactory.getLogger(SecureDeleteUtil.class);
+	private static final Logger logger = LoggingUtil.getThreemaLogger("SecureDeleteUtil");
 
     public static void secureDelete(File file) throws IOException {
         if (file != null && file.exists()) {

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

@@ -33,7 +33,6 @@ import android.os.SystemClock;
 import android.widget.Toast;
 
 import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 
 import java.util.ArrayList;
 import java.util.Collection;
@@ -64,6 +63,7 @@ import ch.threema.app.services.ConversationService;
 import ch.threema.app.voip.activities.CallActivity;
 import ch.threema.app.voip.services.VoipCallService;
 import ch.threema.base.ThreemaException;
+import ch.threema.base.utils.LoggingUtil;
 import ch.threema.storage.models.AbstractMessageModel;
 import ch.threema.storage.models.ContactModel;
 import ch.threema.storage.models.ConversationModel;
@@ -71,7 +71,7 @@ import ch.threema.storage.models.ConversationModel;
 import static androidx.core.content.pm.ShortcutManagerCompat.FLAG_MATCH_PINNED;
 
 public final class ShortcutUtil {
-	private static final Logger logger = LoggerFactory.getLogger(ShortcutUtil.class);
+	private static final Logger logger = LoggingUtil.getThreemaLogger("ShortcutUtil");
 
 	private static final int MAX_SHARE_TARGETS = 100; // we recommend that you publish only four distinct shortcuts to improve their visual appearance in the launcher. https://developer.android.com/guide/topics/ui/shortcuts/best-practices
 

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

@@ -26,7 +26,6 @@ import android.content.Context;
 import android.net.Uri;
 
 import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 
 import java.io.File;
 import java.io.FileInputStream;
@@ -34,9 +33,10 @@ import java.io.FileNotFoundException;
 import java.io.InputStream;
 
 import ch.threema.app.ThreemaApplication;
+import ch.threema.base.utils.LoggingUtil;
 
 public class StreamUtil {
-	private static final Logger logger = LoggerFactory.getLogger(StreamUtil.class);
+	private static final Logger logger = LoggingUtil.getThreemaLogger("StreamUtil");
 
 	public static InputStream getFromUri(Context context, Uri uri) throws FileNotFoundException {
 		InputStream inputStream = null;

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

@@ -26,7 +26,6 @@ import android.os.Bundle;
 import android.os.UserManager;
 
 import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 
 import ch.threema.app.ThreemaApplication;
 import ch.threema.app.exceptions.FileSystemNotPresentException;
@@ -34,10 +33,11 @@ import ch.threema.app.managers.ServiceManager;
 import ch.threema.app.routines.SynchronizeContactsRoutine;
 import ch.threema.app.services.PreferenceService;
 import ch.threema.app.services.SynchronizeContactsService;
+import ch.threema.base.utils.LoggingUtil;
 import ch.threema.localcrypto.MasterKeyLockedException;
 
 public class SynchronizeContactsUtil {
-	private static final Logger logger = LoggerFactory.getLogger(SynchronizeContactsUtil.class);
+	private static final Logger logger = LoggingUtil.getThreemaLogger("SynchronizeContactsUtil");
 
 	public static void startDirectly() {
 		SynchronizeContactsRoutine routine = getSynchronizeContactsRoutine();

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

@@ -29,7 +29,6 @@ import android.text.style.BackgroundColorSpan;
 import android.text.style.ForegroundColorSpan;
 
 import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 
 import java.io.BufferedReader;
 import java.io.IOException;
@@ -42,9 +41,10 @@ import androidx.annotation.NonNull;
 import androidx.annotation.WorkerThread;
 import ch.threema.app.R;
 import ch.threema.app.emojis.EmojiParser;
+import ch.threema.base.utils.LoggingUtil;
 
 public class TextUtil {
-	private static final Logger logger = LoggerFactory.getLogger(TextUtil.class);
+	private static final Logger logger = LoggingUtil.getThreemaLogger("TextUtil");
 
 	public static String trim(String string, int maxLength, String postFix) {
 		if ((maxLength > 0) && (string.length() > maxLength)) {

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

@@ -22,18 +22,18 @@
 package ch.threema.app.utils;
 
 import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 
 import java.util.Date;
 
 import androidx.annotation.NonNull;
 import ch.threema.app.ThreemaApplication;
+import ch.threema.base.utils.LoggingUtil;
 import ch.threema.domain.protocol.api.APIConnector;
 import ch.threema.logging.ThreemaLogger;
 
 public class TurnServerCache {
 	// Logger
-	private final Logger logger = LoggerFactory.getLogger(TurnServerCache.class);
+	private final Logger logger = LoggingUtil.getThreemaLogger("TurnServerCache");
 
 	private final String type;
 	private final int minSpareValidity;

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

@@ -29,10 +29,11 @@ import android.net.Uri;
 import android.provider.MediaStore;
 
 import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
+
+import ch.threema.base.utils.LoggingUtil;
 
 public class VideoUtil {
-	private static final Logger logger = LoggerFactory.getLogger(VideoUtil.class);
+	private static final Logger logger = LoggingUtil.getThreemaLogger("VideoUtil");
 
 	/**
 	 * Get duration of a video represented by uri in Milliseconds

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

@@ -24,19 +24,19 @@ package ch.threema.app.utils;
 import android.content.Context;
 
 import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 import org.webrtc.IceCandidate;
 import org.webrtc.Logging;
 import org.webrtc.PeerConnectionFactory;
 
 import androidx.annotation.NonNull;
+import ch.threema.base.utils.LoggingUtil;
 import ch.threema.logging.WebRTCLoggable;
 
 /**
  * This util handles WebRTC initialization.
  */
 public class WebRTCUtil {
-	private static final Logger logger = LoggerFactory.getLogger(WebRTCUtil.class);
+	private static final Logger logger = LoggingUtil.getThreemaLogger("WebRTCUtil");
 
 	private static boolean initialized = false;
 

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

@@ -26,13 +26,13 @@ import android.content.ComponentName;
 import android.content.Context;
 
 import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 
 import ch.threema.app.R;
 import ch.threema.app.receivers.WidgetProvider;
+import ch.threema.base.utils.LoggingUtil;
 
 public class WidgetUtil {
-	private static final Logger logger = LoggerFactory.getLogger(WidgetUtil.class);
+	private static final Logger logger = LoggingUtil.getThreemaLogger("WidgetUtil");
 
 	public static void updateWidgets(Context context) {
 		logger.debug("Update Widgets");

+ 6 - 0
app/src/main/java/ch/threema/app/voip/receivers/CallRejectReceiver.java

@@ -25,11 +25,17 @@ import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
 
+import org.slf4j.Logger;
+
 import ch.threema.app.voip.services.CallRejectService;
+import ch.threema.base.utils.LoggingUtil;
 
 public class CallRejectReceiver extends BroadcastReceiver {
+	private static final Logger logger = LoggingUtil.getThreemaLogger("CallRejectReceiver");
+
 	@Override
 	public void onReceive(Context context, Intent intent) {
+		logger.info("onReceive");
 		CallRejectService.enqueueWork(context, intent);
 	}
 }

+ 2 - 2
app/src/main/java/ch/threema/app/voip/services/CallRejectService.java

@@ -54,13 +54,13 @@ public class CallRejectService extends FixedJobIntentService {
 	public static final int JOB_ID = 344339;
 
 	public static void enqueueWork(Context context, Intent work) {
-		logger.debug("enqueWork entered");
+		logger.info("enqueueWork");
 		enqueueWork(context, CallRejectService.class, JOB_ID, work);
 	}
 
 	@Override
 	protected void onHandleWork(@NonNull Intent intent) {
-		logger.debug("CallRejectService onHandle work");
+		logger.info("onHandleWork");
 
 		// Intent parameters
 		final String contactIdentity = intent.getStringExtra(EXTRA_CONTACT_IDENTITY);

+ 10 - 0
app/src/main/res/drawable/ic_places_free_flying.xml

@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24"
+        android:viewportHeight="24">
+    <path
+        android:fillColor="#000"
+        android:pathData="M12,17c-1.1,0 -2,-0.9 -2,-2s0.9,-2 2,-2s2,0.9 2,2S13.1,17 12,17zM8.52,17.94C8.04,17.55 7,16.76 7,14H5c0,2.7 0.93,4.41 2.3,5.5c0.5,0.4 1.1,0.7 1.7,0.9L9,24h6v-3.6c0.6,-0.2 1.2,-0.5 1.7,-0.9c1.37,-1.09 2.3,-2.8 2.3,-5.5h-2c0,2.76 -1.04,3.55 -1.52,3.94C14.68,18.54 14,19 12,19S9.32,18.54 8.52,17.94zM12,0C5.92,0 1,1.9 1,4.25v3.49C1,8.55 1.88,9 2.56,8.57C2.7,8.48 2.84,8.39 3,8.31L5,13h2l1.5,-6.28C9.6,6.58 10.78,6.5 12,6.5s2.4,0.08 3.5,0.22L17,13h2l2,-4.69c0.16,0.09 0.3,0.17 0.44,0.26C22.12,9 23,8.55 23,7.74V4.25C23,1.9 18.08,0 12,0zM5.88,11.24L4.37,7.69c0.75,-0.28 1.6,-0.52 2.53,-0.71L5.88,11.24zM18.12,11.24L17.1,6.98c0.93,0.19 1.78,0.43 2.53,0.71L18.12,11.24z"/>
+</vector>

+ 1 - 1
app/src/main/res/layout/fragment_wizard4.xml

@@ -41,7 +41,7 @@
 		android:id="@+id/disabled_by_policy"
 		android:layout_width="match_parent"
 		android:layout_height="wrap_content"
-		android:layout_marginTop="4dp"
+		android:layout_marginTop="32dp"
 		android:layout_below="@+id/wizard_switch_sync_contacts"
 		android:textSize="14sp"
 		android:text="@string/disabled_by_policy"

+ 1 - 1
app/src/main/res/values-it/webclient_strings.xml

@@ -7,7 +7,7 @@
     <string name="webclient_created_at">Creato in: %1$s (%2$s)</string>
     <string name="webclient_active_since">Attivo da: %s</string>
     <string name="webclient_enable">Abilita Threema Web</string>
-    <string name="webclient_no_sessions_found">Per connetterti apri <b>https://web.threema.ch</b> nel browser del tuo PC e tocca il pulsante sotto per lo scan del codice.</string>
+    <string name="webclient_no_sessions_found">Per connetterti apri <b>%s</b> nel browser del tuo PC e tocca il pulsante sotto per lo scan del codice.</string>
     <string name="webclient_session_rename">Rinomina sessione</string>
     <string name="webclient_session_label">Nuovo nome</string>
     <string name="webclient_session_start">Avvia sessione</string>

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

@@ -23,6 +23,9 @@
 		automatisch zu finden, wenn Sie in deren Adressbuch eingetragen sind. Die Angaben werden dazu in einwegverschlüsselter (gehashter) Form
 		auf unserem Server gespeichert. Sie können diesen Schritt auch einfach übespringen, wenn Sie Threema OnPrem anonym benutzen möchten.
 	</string>
+	<string name="new_wizard_info_sync_contacts">Wenn Sie diese Option aktivieren, werden E-Mail-Adressen und Telefonnummern aus Ihrem Adressbuch einwegverschlüsselt (gehasht), bevor sie zum
+		Kontaktabgleich an der Server geschickt werden. Adressbuchdaten werden nicht auf dem Server gespeichert.
+	</string>
 	<string name="threema_contact">Threema OnPrem-Kontakt</string>
 	<string name="menu_about">Über Threema OnPrem</string>
 	<string name="list_theme_light">Hell</string>

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

@@ -23,6 +23,8 @@
 		one-way encrypted (hashed) form on our server. You can simply skip this step, if you would like to use Threema OnPrem
 		anonymously.
 	</string>
+	<string name="new_wizard_info_sync_contacts">If you enable this option, Threema OnPrem one-way encrypts (hashes) email addresses and phone numbers
+		before sending them to the server to look for matching contacts. The data is kept in memory only and never stored on the server.</string>
 	<string name="threema_contact">Threema OnPrem Contact</string>
 	<string name="menu_about">About Threema OnPrem</string>
 	<string name="list_theme_light">Light</string>

+ 2 - 2
build.gradle

@@ -9,8 +9,8 @@ buildscript {
     }
     dependencies {
         classpath 'com.android.tools.build:gradle:4.1.3'
-        classpath 'org.owasp:dependency-check-gradle:6.1.6'
-        classpath 'com.google.protobuf:protobuf-gradle-plugin:0.8.16'
+        classpath 'org.owasp:dependency-check-gradle:6.5.3'
+        classpath 'com.google.protobuf:protobuf-gradle-plugin:0.8.18'
 
         // Huawei agconnect plugin
         classpath 'com.huawei.agconnect:agcp-1.4.2.300'

+ 2 - 2
domain/build.gradle

@@ -36,7 +36,7 @@ dependencies {
     api 'androidx.annotation:annotation:1.3.0'
     api 'net.sourceforge.streamsupport:streamsupport-flow:1.7.0'
 
-    api 'com.google.protobuf:protobuf-javalite:3.11.4'
+    api 'com.google.protobuf:protobuf-javalite:3.19.4'
 
     implementation "org.slf4j:slf4j-api:$slf4j_version"
     // commons-io >2.6 requires android 8
@@ -61,7 +61,7 @@ sourceCompatibility = '1.8'
 
 protobuf {
     protoc {
-        artifact = 'com.google.protobuf:protoc:3.16.0'
+        artifact = 'com.google.protobuf:protoc:3.19.4'
     }
     generateProtoTasks {
         all().each { task ->

+ 6 - 4
domain/src/main/java/ch/threema/domain/onprem/OnPremConfig.java

@@ -21,6 +21,8 @@
 
 package ch.threema.domain.onprem;
 
+import androidx.annotation.Nullable;
+
 public class OnPremConfig {
 
 	private final int refresh;
@@ -42,8 +44,8 @@ public class OnPremConfig {
 	                    OnPremConfigWork workConfig,
 	                    OnPremConfigAvatar avatarConfig,
 	                    OnPremConfigSafe safeConfig,
-	                    OnPremConfigWeb webConfig,
-	                    OnPremConfigMediator mediatorConfig) {
+	                    @Nullable OnPremConfigWeb webConfig,
+	                    @Nullable OnPremConfigMediator mediatorConfig) {
 		this.refresh = refresh;
 		this.chatConfig = chatConfig;
 		this.license = license;
@@ -88,11 +90,11 @@ public class OnPremConfig {
 		return safeConfig;
 	}
 
-	public OnPremConfigWeb getWebConfig() {
+	public @Nullable OnPremConfigWeb getWebConfig() {
 		return webConfig;
 	}
 
-	public OnPremConfigMediator getMediatorConfig() {
+	public @Nullable OnPremConfigMediator getMediatorConfig() {
 		return mediatorConfig;
 	}
 }

+ 8 - 5
domain/src/main/java/ch/threema/domain/onprem/OnPremConfigParser.java

@@ -44,7 +44,7 @@ public class OnPremConfigParser {
 			this.parseWorkConfig(obj.getJSONObject("work")),
 			this.parseAvatarConfig(obj.getJSONObject("avatar")),
 			this.parseSafeConfig(obj.getJSONObject("safe")),
-			this.parseWebConfig(obj.getJSONObject("web")),
+			this.parseWebConfig(obj.optJSONObject("web")),
 			this.parseMediatorConfig(obj.optJSONObject("mediator")));
 	}
 
@@ -88,14 +88,17 @@ public class OnPremConfigParser {
 		return new OnPremConfigSafe(obj.getString("url"));
 	}
 
-	private OnPremConfigWeb parseWebConfig(JSONObject obj) throws JSONException {
-		return new OnPremConfigWeb(obj.getString("url"));
-	}
-
 	private OnPremConfigWork parseWorkConfig(JSONObject obj) throws JSONException {
 		return new OnPremConfigWork(obj.getString("url"));
 	}
 
+	private OnPremConfigWeb parseWebConfig(JSONObject obj) throws JSONException {
+		if (obj == null) {
+			return null;
+		}
+		return new OnPremConfigWeb(obj.getString("url"));
+	}
+
 	private OnPremConfigMediator parseMediatorConfig(JSONObject obj) throws JSONException {
 		if (obj == null) {
 			return null;

+ 8 - 1
domain/src/main/java/ch/threema/domain/onprem/ServerAddressProviderOnPrem.java

@@ -21,6 +21,7 @@
 
 package ch.threema.domain.onprem;
 
+import androidx.annotation.Nullable;
 import ch.threema.base.ThreemaException;
 import ch.threema.domain.protocol.ServerAddressProvider;
 
@@ -103,8 +104,14 @@ public class ServerAddressProviderOnPrem implements ServerAddressProvider {
 	}
 
 	@Override
+	@Nullable
 	public String getWebServerUrl() throws ThreemaException {
-		return getOnPremConfigFetcher().fetch().getWebConfig().getUrl();
+		OnPremConfigWeb onPremConfigWeb = getOnPremConfigFetcher().fetch().getWebConfig();
+
+		if (onPremConfigWeb != null) {
+			return onPremConfigWeb.getUrl();
+		}
+		throw new ThreemaException("Unable to fetch Threema Web server url");
 	}
 
 	private OnPremConfigFetcher getOnPremConfigFetcher() throws ThreemaException {

+ 9 - 4
domain/src/main/java/ch/threema/domain/protocol/csp/coders/MessageCoder.java

@@ -260,11 +260,16 @@ public class MessageCoder {
 					locationmsg.setAccuracy(Double.parseDouble(locArr[2]));
 				}
 
-				if (lines.length >= 2) {
+				String address = null;
+				if (lines.length == 2) {
+					address = lines[1];
+				} else if (lines.length >= 3) {
 					locationmsg.setPoiName(lines[1]);
-					if (lines.length >= 3) {
-						locationmsg.setPoiAddress(lines[2].replace("\\n", "\n"));
-					}
+					address = lines[2];
+				}
+
+				if (address != null) {
+					locationmsg.setPoiAddress(address.replace("\\n", "\n"));
 				}
 
 				if (locationmsg.getLatitude() < -90.0 || locationmsg.getLatitude() > 90.0 ||

+ 1 - 1
gradle.properties

@@ -19,7 +19,7 @@
 
 #android.useDeprecatedNdk=true
 manifestmerger.enabled=true
-org.gradle.jvmargs=-Xmx4096M
+org.gradle.jvmargs=-Xmx4096M -Dfile.encoding=UTF-8
 org.gradle.caching=true
 android.useAndroidX=true
 android.enableJetifier=true