Ver Fonte

Version 5.0.6

Threema há 2 anos atrás
pai
commit
a3c409923b
21 ficheiros alterados com 238 adições e 98 exclusões
  1. 1 1
      app/build.gradle
  2. 11 2
      app/src/main/java/ch/threema/app/activities/ServerMessageActivity.java
  3. 11 6
      app/src/main/java/ch/threema/app/fragments/ComposeMessageFragment.java
  4. 2 2
      app/src/main/java/ch/threema/app/fragments/ContactsSectionFragment.java
  5. 10 7
      app/src/main/java/ch/threema/app/fragments/UserListFragment.java
  6. 2 1
      app/src/main/java/ch/threema/app/fragments/UserMemberListFragment.java
  7. 16 0
      app/src/main/java/ch/threema/app/managers/ServiceManager.java
  8. 78 3
      app/src/main/java/ch/threema/app/services/ContactService.java
  9. 44 38
      app/src/main/java/ch/threema/app/services/ContactServiceImpl.java
  10. 1 1
      app/src/main/java/ch/threema/app/services/SynchronizeContactsServiceImpl.java
  11. 3 5
      app/src/main/java/ch/threema/app/services/systemupdate/SystemUpdateToVersion72.java
  12. 6 0
      app/src/main/java/ch/threema/app/ui/OngoingCallNoticeView.kt
  13. 5 3
      app/src/main/java/ch/threema/app/utils/DeviceCookieManagerImpl.java
  14. 4 0
      app/src/main/java/ch/threema/app/voip/services/VoipCallService.java
  15. 26 22
      app/src/main/java/ch/threema/app/webclient/services/instance/message/receiver/TextMessageCreateHandler.java
  16. 2 2
      app/src/main/java/ch/threema/app/workers/WorkSyncWorker.kt
  17. 5 1
      app/src/main/java/ch/threema/storage/SQLDHSessionStore.java
  18. 2 2
      app/src/main/res/values-de/strings.xml
  19. 2 2
      app/src/main/res/values/strings.xml
  20. 2 0
      domain/src/main/java/ch/threema/domain/stores/DHSessionStoreInterface.java
  21. 5 0
      domain/src/main/java/ch/threema/domain/stores/InMemoryDHSessionStore.java

+ 1 - 1
app/build.gradle

@@ -17,7 +17,7 @@ if (getGradle().getStartParameter().getTaskRequests().toString().contains("Hms")
 }
 
 // version codes
-def app_version = "5.0.5.1"
+def app_version = "5.0.6"
 def beta_suffix = "" // with leading dash
 
 /**

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

@@ -22,6 +22,7 @@
 package ch.threema.app.activities;
 
 import android.os.Bundle;
+import android.text.method.LinkMovementMethod;
 import android.view.MenuItem;
 import android.widget.TextView;
 
@@ -29,6 +30,7 @@ import org.slf4j.Logger;
 
 import androidx.annotation.NonNull;
 import androidx.appcompat.app.ActionBar;
+import androidx.core.text.HtmlCompat;
 import androidx.lifecycle.ViewModelProvider;
 import ch.threema.app.R;
 import ch.threema.app.ThreemaApplication;
@@ -68,7 +70,8 @@ public class ServerMessageActivity extends ThreemaActivity {
 			return;
 		}
 
-		this.serverMessageTextView = findViewById(R.id.server_message_text);
+		serverMessageTextView = findViewById(R.id.server_message_text);
+		serverMessageTextView.setMovementMethod(LinkMovementMethod.getInstance());
 
 		notificationService = serviceManager.getNotificationService();
 
@@ -99,11 +102,17 @@ public class ServerMessageActivity extends ThreemaActivity {
 		return super.onOptionsItemSelected(item);
 	}
 
+	@Override
+	public void onBackPressed() {
+		viewModel.markServerMessageAsRead();
+	}
+
 	private void showServerMessage(@NonNull String message) {
 		if (message.startsWith("Another connection")) {
 			message = getString(R.string.another_connection_instructions, getString(R.string.app_name));
 		}
-		serverMessageTextView.setText(message);
+
+		serverMessageTextView.setText(HtmlCompat.fromHtml(message, HtmlCompat.FROM_HTML_MODE_COMPACT));
 	}
 
 	private void cancelServerMessageNotification() {

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

@@ -543,7 +543,7 @@ public class ComposeMessageFragment extends Fragment implements
 
 		@Override
 		public void onStarted(String peerIdentity, boolean outgoing) {
-			logger.debug("VoipCallEventListener onStarted");
+			logger.info("VoipCallEventListener onStarted"); // TODO(ANDR-2441): re-set to debug level
 			updateVoipCallMenuItem(false);
 			if (messagePlayerService != null) {
 				messagePlayerService.pauseAll(SOURCE_VOIP);
@@ -552,28 +552,28 @@ public class ComposeMessageFragment extends Fragment implements
 
 		@Override
 		public void onFinished(long callId, @NonNull String peerIdentity, boolean outgoing, int duration) {
-			logger.debug("VoipCallEventListener onFinished");
+			logger.info("VoipCallEventListener onFinished"); // TODO(ANDR-2441): re-set to debug level
 			updateVoipCallMenuItem(true);
 			hideOngoingVoipCallNotice();
 		}
 
 		@Override
 		public void onRejected(long callId, String peerIdentity, boolean outgoing, byte reason) {
-			logger.debug("VoipCallEventListener onRejected");
+			logger.info("VoipCallEventListener onRejected"); // TODO(ANDR-2441): re-set to debug level
 			updateVoipCallMenuItem(true);
 			hideOngoingVoipCallNotice();
 		}
 
 		@Override
 		public void onMissed(long callId, String peerIdentity, boolean accepted, @Nullable Date date) {
-			logger.debug("VoipCallEventListener onMissed");
+			logger.info("VoipCallEventListener onMissed"); // TODO(ANDR-2441): re-set to debug level
 			updateVoipCallMenuItem(true);
 			hideOngoingVoipCallNotice();
 		}
 
 		@Override
 		public void onAborted(long callId, String peerIdentity) {
-			logger.debug("VoipCallEventListener onAborted");
+			logger.info("VoipCallEventListener onAborted"); // TODO(ANDR-2441): re-set to debug level
 			updateVoipCallMenuItem(true);
 			hideOngoingVoipCallNotice();
 		}
@@ -1141,6 +1141,7 @@ public class ComposeMessageFragment extends Fragment implements
 
 	@AnyThread
 	private void showOngoingVoipCallNotice() {
+		logger.info("Show ongoing voip call notice (notice set: {})", ongoingCallNotice != null); // TODO(ANDR-2441): remove eventually
 		if (ongoingCallNotice != null) {
 			ongoingCallNotice.showVoip();
 		}
@@ -1148,6 +1149,7 @@ public class ComposeMessageFragment extends Fragment implements
 
 	@AnyThread
 	private void hideOngoingVoipCallNotice() {
+		logger.info("Hide ongoing voip call notice (notice set: {})", ongoingCallNotice != null); // TODO(ANDR-2441): remove eventually
 		if (ongoingCallNotice != null) {
 			ongoingCallNotice.hideVoip();
 		}
@@ -1155,6 +1157,7 @@ public class ComposeMessageFragment extends Fragment implements
 
 	@AnyThread
 	private void hideOngoingCallNotice() {
+		logger.info("Hide ongoing call notice (notice set: {})", ongoingCallNotice != null);  // TODO(ANDR-2441): remove eventually
 		if (ongoingCallNotice != null) {
 			ongoingCallNotice.hide();
 		}
@@ -1165,6 +1168,7 @@ public class ComposeMessageFragment extends Fragment implements
 		removeGroupCallObserver();
 		if (groupModel != null && groupCallManager != null) {
 			groupCallObserver = call -> updateOngoingCallNotice();
+			logger.info("Add group call observer for group {}", groupModel.getId());
 			groupCallManager.addGroupCallObserver(groupModel, groupCallObserver);
 		}
 	}
@@ -1277,7 +1281,7 @@ public class ComposeMessageFragment extends Fragment implements
 
 	@Override
 	public void onResume(@NonNull LifecycleOwner owner) {
-		logger.debug("onResume");
+		logger.info("onResume"); // TODO(ANDR-2441): Re-set to debug level
 
 		//set visible receiver
 		if (this.messageReceiver != null) {
@@ -1811,6 +1815,7 @@ public class ComposeMessageFragment extends Fragment implements
 
 	private void removeGroupCallObserver() {
 		if (groupModel != null && groupCallObserver != null && groupCallManager != null) {
+			logger.info("Remove group call observer for group {}", groupModel.getId());
 			groupCallManager.removeGroupCallObserver(groupModel, groupCallObserver);
 			groupCallObserver = null;
 		}

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

@@ -451,13 +451,13 @@ public class ContactsSectionFragment
 				results.workCount = contactService.countIsWork();
 				if (selectedTab == TAB_WORK_ONLY) {
 					if (results.workCount > 0 || forceWork) {
-						allContacts = contactService.getIsWork();
+						allContacts = contactService.getAllDisplayedWork(ContactService.ContactSelection.INCLUDE_INVALID);
 					}
 				}
 			}
 
 			if (allContacts == null) {
-				allContacts = contactService.getAll();
+				allContacts = contactService.getAllDisplayed(ContactService.ContactSelection.INCLUDE_INVALID);
 			}
 
 			if (!ConfigUtils.isWorkBuild()) {

+ 10 - 7
app/src/main/java/ch/threema/app/fragments/UserListFragment.java

@@ -34,6 +34,7 @@ import ch.threema.app.activities.AddContactActivity;
 import ch.threema.app.adapters.UserListAdapter;
 import ch.threema.app.collections.Functional;
 import ch.threema.app.collections.IPredicateNonNull;
+import ch.threema.app.services.ContactService;
 import ch.threema.app.utils.ConfigUtils;
 import ch.threema.storage.models.ContactModel;
 
@@ -78,14 +79,16 @@ public class UserListFragment extends RecipientListFragment {
 			@Override
 			protected List<ContactModel> doInBackground(Void... voids) {
 				if (ConfigUtils.isWorkBuild()) {
-					return Functional.filter(contactService.getAll(false, false), new IPredicateNonNull<ContactModel>() {
-						@Override
-						public boolean apply(@NonNull ContactModel type) {
-							return !type.isWork();
-						}
-					});
+					// Only show non-work contacts here, because the work contacts are shown in the
+					// work tab. Note that we exclude invalid contacts, as they cannot receive
+					// messages anyways.
+					return Functional.filter(
+						contactService.getAllDisplayed(ContactService.ContactSelection.EXCLUDE_INVALID),
+						(IPredicateNonNull<ContactModel>) value -> !value.isWork()
+					);
 				} else {
-					return contactService.getAll(false, false);
+					// Exclude invalid contacts because they cannot receive messages anyways
+					return contactService.getAllDisplayed(ContactService.ContactSelection.EXCLUDE_INVALID);
 				}
 			}
 

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

@@ -105,7 +105,8 @@ public class UserMemberListFragment extends MemberListFragment {
 				} else if (profilePics) {
 					contactModels = contactService.getCanReceiveProfilePics();
 				} else {
-					contactModels = contactService.getAll();
+					// Don't include invalid contacts because they should not be added to groups
+					contactModels = contactService.getAllDisplayed(ContactService.ContactSelection.EXCLUDE_INVALID);
 				}
 
 				if (ConfigUtils.isWorkBuild()) {

+ 16 - 0
app/src/main/java/ch/threema/app/managers/ServiceManager.java

@@ -29,6 +29,7 @@ import androidx.annotation.Nullable;
 import org.apache.commons.io.Charsets;
 import org.slf4j.Logger;
 
+import java.io.File;
 import java.util.Date;
 import java.util.Locale;
 
@@ -127,6 +128,7 @@ import ch.threema.app.threemasafe.ThreemaSafeService;
 import ch.threema.app.threemasafe.ThreemaSafeServiceImpl;
 import ch.threema.app.utils.ConfigUtils;
 import ch.threema.app.utils.DeviceIdUtil;
+import ch.threema.app.utils.FileUtil;
 import ch.threema.app.utils.ForwardSecurityStatusSender;
 import ch.threema.app.voip.groupcall.GroupCallManager;
 import ch.threema.app.voip.groupcall.GroupCallManagerImpl;
@@ -1003,6 +1005,20 @@ public class ServiceManager {
 	public DHSessionStoreInterface getDHSessionStore() throws MasterKeyLockedException {
 		if (this.dhSessionStore == null) {
 			this.dhSessionStore = new SQLDHSessionStore(this.getContext(), this.masterKey.getKey());
+			try {
+				dhSessionStore.executeNull();
+			} catch (Exception e) {
+				logger.error("Could not execute a statement on the fs database", e);
+				Context context = ThreemaApplication.getAppContext();
+				if (context != null) {
+					// The database file seems to be corrupt, therefore we delete the file
+					File databaseFile = context.getDatabasePath(SQLDHSessionStore.DATABASE_NAME);
+					if (databaseFile.exists()) {
+						FileUtil.deleteFileOrWarn(databaseFile, "sql dh session database", logger);
+					}
+					dhSessionStore = new SQLDHSessionStore(context, masterKey.getKey());
+				}
+			}
 		}
 		return this.dhSessionStore;
 	}

+ 78 - 3
app/src/main/java/ch/threema/app/services/ContactService.java

@@ -97,9 +97,59 @@ public interface ContactService extends AvatarService<ContactModel> {
 
 	ContactModel getMe();
 
+	/**
+	 * The contact selection to include or exclude invalid contacts.
+	 */
+	enum ContactSelection {
+		/**
+		 * Includes invalid contacts. Note that in the methods {@link #getAllDisplayed(ContactSelection)}
+		 * and {@link #getAllDisplayedWork(ContactSelection)}, this may be overridden by the
+		 * preferences.
+		 */
+		INCLUDE_INVALID,
+		/**
+		 * Don't include invalid contacts.
+		 */
+		EXCLUDE_INVALID,
+	}
+
+	/**
+	 * Get all contacts (including work contacts) depending on the preference to display inactive
+	 * contacts. If inactive contacts are hidden by the preference, then invalid contacts are not
+	 * included in this list neither. If inactive contacts should be shown according to the
+	 * preference, then depending on the argument, inactive and invalid contacts may be included.
+	 *
+	 * Note that the result does not include hidden contacts.
+	 *
+	 * This list also includes work contacts.
+	 *
+	 * If a list of contacts is sought without depending on the inactive contacts preference, the
+	 * method {@link #find(Filter)} can be used.
+	 *
+	 * @param contactSelection the option to include or exclude invalid contacts
+	 * @return a list of the contact models
+	 */
+	@NonNull
+	List<ContactModel> getAllDisplayed(@NonNull ContactSelection contactSelection);
+
+	/**
+	 * Get all contacts. This does not depend on any user preferences. Invalid and hidden contacts
+	 * are included as well.
+	 *
+	 * @return a list of all contact models
+	 */
+	@NonNull
 	List<ContactModel> getAll();
-	List<ContactModel> getAll(boolean includeHidden, boolean includeInvalid);
 
+	/**
+	 * Get a list of contact models based on the given filter.
+	 *
+	 * @param filter the filter that is applied to the result
+	 * @return a list of contact models
+	 * @see #getAllDisplayed
+	 * @see #getAllDisplayedWork
+	 */
+	@NonNull
 	List<ContactModel> find(Filter filter);
 
 	@Nullable
@@ -116,10 +166,35 @@ public interface ContactService extends AvatarService<ContactModel> {
 	 */
 	@NonNull
 	ContactModel getOrCreateByIdentity(String identity, boolean force) throws EntryAlreadyExistsException, InvalidEntryException, PolicyViolationException;
-	List<ContactModel> getByIdentities(String identities[]);
+	List<ContactModel> getByIdentities(String[] identities);
 	List<ContactModel> getByIdentities(List<String> identities);
 
-	List<ContactModel> getIsWork();
+	/**
+	 * Get all work contacts depending on the preference to display inactive contacts. If inactive
+	 * contacts are hidden by the preference, then invalid contacts are not included in this list
+	 * neither. If inactive contacts should be shown according to the preference, then depending on
+	 * the contact selection argument, invalid contacts may be included.
+	 *
+	 * Note that this does not include hidden contacts.
+	 *
+	 * If a list of contacts is sought without depending on the inactive contacts preference, the
+	 * method {@link #find(Filter)} can be used.
+	 *
+	 * @param contactSelection the states that should be included (if enabled in preferences)
+	 * @return a list of the contact models
+	 */
+	@NonNull
+	List<ContactModel> getAllDisplayedWork(ContactSelection contactSelection);
+
+	/**
+	 * Get all work contacts. This does not depend on any user preferences. Invalid and hidden
+	 * contacts are also included.
+	 *
+	 * @return a list of the work contact models
+	 */
+	@NonNull
+	List<ContactModel> getAllWork();
+
 	int countIsWork();
 
 	List<ContactModel> getCanReceiveProfilePics();

+ 44 - 38
app/src/main/java/ch/threema/app/services/ContactServiceImpl.java

@@ -31,14 +31,6 @@ import android.provider.ContactsContract;
 import android.text.format.DateUtils;
 import android.widget.ImageView;
 
-import androidx.annotation.AnyThread;
-import androidx.annotation.ColorInt;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.annotation.UiThread;
-import androidx.annotation.WorkerThread;
-import androidx.core.content.ContextCompat;
-
 import com.neilalexander.jnacl.NaCl;
 
 import net.sqlcipher.Cursor;
@@ -61,6 +53,13 @@ import java.util.Map;
 import java.util.Timer;
 import java.util.TimerTask;
 
+import androidx.annotation.AnyThread;
+import androidx.annotation.ColorInt;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.UiThread;
+import androidx.annotation.WorkerThread;
+import androidx.core.content.ContextCompat;
 import ch.threema.app.BuildConfig;
 import ch.threema.app.R;
 import ch.threema.app.ThreemaApplication;
@@ -242,21 +241,21 @@ public class ContactServiceImpl implements ContactService {
 	}
 
 	@Override
-	public List<ContactModel> getAll() {
-		return getAll(false, true);
-	}
-
-	@Override
-	public List<ContactModel> getAll(final boolean includeHiddenContacts, final boolean includeInvalid) {
+	@NonNull
+	public List<ContactModel> getAllDisplayed(@NonNull ContactSelection contactSelection) {
 		return this.find(new Filter() {
 			@Override
 			public ContactModel.State[] states() {
 				if (preferenceService.showInactiveContacts()) {
-					if (includeInvalid) {
-						return null;
-					} else {
-						// do not show contacts with INVALID state
-						return new ContactModel.State[]{ContactModel.State.ACTIVE, ContactModel.State.INACTIVE};
+					switch (contactSelection) {
+						case EXCLUDE_INVALID:
+							return new ContactModel.State[]{
+								ContactModel.State.ACTIVE,
+								ContactModel.State.INACTIVE,
+							};
+						case INCLUDE_INVALID:
+						default:
+							return null;
 					}
 				} else {
 					return new ContactModel.State[]{ContactModel.State.ACTIVE};
@@ -280,7 +279,7 @@ public class ContactServiceImpl implements ContactService {
 
 			@Override
 			public Boolean includeHidden() {
-				return includeHiddenContacts;
+				return false;
 			}
 
 			@Override
@@ -291,6 +290,13 @@ public class ContactServiceImpl implements ContactService {
 	}
 
 	@Override
+	@NonNull
+	public List<ContactModel> getAll() {
+		return find(null);
+	}
+
+	@Override
+	@NonNull
 	public List<ContactModel> find(Filter filter) {
 		ContactModelFactory contactModelFactory = this.databaseServiceNew.getContactModelFactory();
 		//TODO: move this to database factory!
@@ -465,13 +471,15 @@ public class ContactServiceImpl implements ContactService {
 	}
 
 	@Override
-	public List<ContactModel> getIsWork() {
-		return Functional.filter(this.find(null), new IPredicateNonNull<ContactModel>() {
-			@Override
-			public boolean apply(@NonNull ContactModel type) {
-				return type.isWork();
-			}
-		});
+	@NonNull
+	public List<ContactModel> getAllDisplayedWork(@NonNull ContactSelection selection) {
+		return Functional.filter(this.getAllDisplayed(selection), (IPredicateNonNull<ContactModel>) ContactModel::isWork);
+	}
+
+	@Override
+	@NonNull
+	public List<ContactModel> getAllWork() {
+		return Functional.filter(this.getAll(), (IPredicateNonNull<ContactModel>) ContactModel::isWork);
 	}
 
 	@Override
@@ -1024,18 +1032,16 @@ public class ContactServiceImpl implements ContactService {
 			return false;
 		}
 
-		List<ContactModel> contactModels = this.getAll(true, true);
-		if(contactModels != null) {
-			for(ContactModel contactModel: contactModels) {
-				if(!TestUtil.empty(contactModel.getAndroidContactLookupKey())) {
-					try {
-						AndroidContactUtil.getInstance().updateNameByAndroidContact(contactModel);
-					} catch (ThreemaException e) {
-						contactModel.setAndroidContactLookupKey(null);
-						logger.error("Unable to update contact name", e);
-					}
-					this.save(contactModel);
+		List<ContactModel> contactModels = this.getAll();
+		for (ContactModel contactModel: contactModels) {
+			if (!TestUtil.empty(contactModel.getAndroidContactLookupKey())) {
+				try {
+					AndroidContactUtil.getInstance().updateNameByAndroidContact(contactModel);
+				} catch (ThreemaException e) {
+					contactModel.setAndroidContactLookupKey(null);
+					logger.error("Unable to update contact name", e);
 				}
+				this.save(contactModel);
 			}
 		}
 		return true;

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

@@ -132,7 +132,7 @@ public class SynchronizeContactsServiceImpl implements SynchronizeContactsServic
 								ListenerManager.contactListeners.enabled(false);
 							}
 
-							for (ContactModel contactModel : contactService.getAll(true, true)) {
+							for (ContactModel contactModel : contactService.getAll()) {
 								if (ContactUtil.isChannelContact(contactModel)) {
 									UpdateBusinessAvatarRoutine.start(contactModel, fileService, contactService, apiService, true);
 								}

+ 3 - 5
app/src/main/java/ch/threema/app/services/systemupdate/SystemUpdateToVersion72.java

@@ -70,11 +70,9 @@ public class SystemUpdateToVersion72 implements UpdateSystemService.SystemUpdate
 		if (s != null) {
 			try {
 				ContactService contactService = s.getContactService();
-				if (contactService != null) {
-					for (ContactModel contact : contactService.getAll(true, true)) {
-						contact.initializeIdColor();
-						contactService.save(contact);
-					}
+				for (ContactModel contact : contactService.getAll()) {
+					contact.initializeIdColor();
+					contactService.save(contact);
 				}
 			} catch (MasterKeyLockedException | FileSystemNotPresentException e) {
 				logger.error("Exception", e);

+ 6 - 0
app/src/main/java/ch/threema/app/ui/OngoingCallNoticeView.kt

@@ -45,6 +45,9 @@ import ch.threema.app.voip.groupcall.GroupCallDescription
 import ch.threema.app.voip.groupcall.LocalGroupId
 import ch.threema.app.voip.services.VoipCallService
 import com.google.android.material.chip.Chip
+import ch.threema.base.utils.LoggingUtil
+
+private val logger = LoggingUtil.getThreemaLogger("OngoingCallNoticeView")
 
 enum class OngoingCallNoticeMode {
 	MODE_VOIP,
@@ -87,6 +90,7 @@ class OngoingCallNoticeView : LinearLayout, DefaultLifecycleObserver {
 	 */
 	@AnyThread
 	fun hideVoip() {
+		logger.info("Hide voip in operation mode `{}`", operationMode) // TODO(ANDR-2441): remove eventually
 		if (operationMode == OngoingCallNoticeMode.MODE_VOIP) {
 			hide()
 		}
@@ -205,6 +209,7 @@ class OngoingCallNoticeView : LinearLayout, DefaultLifecycleObserver {
 	}
 
 	private fun voipContainerAction() {
+		logger.info("Run voip container action") // TODO(ANDR-2441): remove eventually
 		if (VoipCallService.isRunning()) {
 			val openIntent = Intent(context, CallActivity::class.java)
 			openIntent.putExtra(VoipCallService.EXTRA_ACTIVITY_MODE, CallActivity.MODE_ACTIVE_CALL)
@@ -218,6 +223,7 @@ class OngoingCallNoticeView : LinearLayout, DefaultLifecycleObserver {
 	}
 
 	private fun voipButtonAction() {
+		logger.info("Run voip button action") // TODO(ANDR-2441): remove eventually
 		val hangupIntent = Intent(context, VoipCallService::class.java)
 		hangupIntent.action = VoipCallService.ACTION_HANGUP
 		context.startService(hangupIntent)

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

@@ -83,11 +83,13 @@ public class DeviceCookieManagerImpl implements DeviceCookieManager {
 		}
 
 		logger.info("Device cookie change indication received, showing warning message");
+
+		ServerMessageModel serverMessageModel = new ServerMessageModel(ThreemaApplication.getAppContext().getString(R.string.rogue_device_warning), ServerMessageModel.TYPE_ALERT);
+		DatabaseServiceNew databaseService = serviceManager.getDatabaseServiceNew();
+		databaseService.getServerMessageModelFactory().storeServerMessageModel(serverMessageModel);
+
 		NotificationService n = serviceManager.getNotificationService();
 		if (n != null) {
-			ServerMessageModel serverMessageModel = new ServerMessageModel(ThreemaApplication.getAppContext().getString(R.string.rogue_device_warning), ServerMessageModel.TYPE_ALERT);
-			DatabaseServiceNew databaseService = serviceManager.getDatabaseServiceNew();
-			databaseService.getServerMessageModelFactory().storeServerMessageModel(serverMessageModel);
 			n.showServerMessage(serverMessageModel);
 		}
 	}

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

@@ -1463,6 +1463,7 @@ public class VoipCallService extends LifecycleService implements PeerConnectionC
 			this.transportConnected, this.isError, message
 		);
 
+		logger.info("Notify about finishing if call is still connected"); // TODO(ANDR-2441): remove eventually
 		// If the call is still connected, notify listeners about the finishing
 		if (this.voipStateService != null) {
 			if (callState.isCalling() && contact != null) {
@@ -1470,12 +1471,15 @@ public class VoipCallService extends LifecycleService implements PeerConnectionC
 				final String contactIdentity = contact.getIdentity();
 				final Boolean isInitiator = this.voipStateService.isInitiator();
 				final Integer duration = this.voipStateService.getCallDuration();
+
+				logger.info("Call is still connected, notify event listeners"); // TODO(ANDR-2441): remove eventually
 				VoipListenerManager.callEventListener.handle(listener -> {
 					if (isInitiator == null) {
 						logger.error("isInitiator is null in disconnect()");
 					} else if (duration == null) {
 						logger.error("duration is null in disconnect()");
 					} else {
+						logger.info("Notify call event listener: onFinished"); // TODO(ANDR-2441): remove eventually
 						listener.onFinished(callId, contactIdentity, isInitiator, duration);
 					}
 				});

+ 26 - 22
app/src/main/java/ch/threema/app/webclient/services/instance/message/receiver/TextMessageCreateHandler.java

@@ -33,8 +33,8 @@ import androidx.annotation.AnyThread;
 import androidx.annotation.WorkerThread;
 import ch.threema.app.BuildConfig;
 import ch.threema.app.ThreemaApplication;
-import ch.threema.app.listeners.ServerMessageListener;
 import ch.threema.app.managers.ListenerManager;
+import ch.threema.app.managers.ServiceManager;
 import ch.threema.app.messagereceiver.ContactMessageReceiver;
 import ch.threema.app.messagereceiver.GroupMessageReceiver;
 import ch.threema.app.services.IdListService;
@@ -45,6 +45,7 @@ import ch.threema.app.webclient.Protocol;
 import ch.threema.app.webclient.services.instance.MessageDispatcher;
 import ch.threema.base.utils.LoggingUtil;
 import ch.threema.domain.protocol.csp.ProtocolDefines;
+import ch.threema.storage.DatabaseServiceNew;
 import ch.threema.storage.models.AbstractMessageModel;
 import ch.threema.storage.models.ServerMessageModel;
 
@@ -134,39 +135,42 @@ public class TextMessageCreateHandler extends MessageCreateHandler {
 						String[] pieces = text.split("\\s+");
 						if (pieces.length >= 2) {
 
-							String alertMessageTmp = "";
-							for(int n = 2; n < pieces.length; n++) {
-								alertMessageTmp += pieces[n] + (n > 2 ? " " : "");
+							StringBuilder alertMessageTmp = new StringBuilder();
+							for (int n = 2; n < pieces.length; n++) {
+								alertMessageTmp.append(pieces[n]).append(n == pieces.length -1 ? "" : " ");
+							}
+							ServiceManager serviceManager = ThreemaApplication.getServiceManager();
+							DatabaseServiceNew databaseService = null;
+							if (serviceManager != null) {
+								databaseService = serviceManager.getDatabaseServiceNew();
 							}
 							final String alertMessage;
+							ServerMessageModel serverMessageModel;
 							switch (pieces[1]) {
 								case "error":
-									if(alertMessageTmp.length() == 0) {
+									if (alertMessageTmp.length() == 0) {
 										alertMessage = "test error message";
+									} else {
+										alertMessage = alertMessageTmp.toString();
 									}
-									else {
-										alertMessage = alertMessageTmp;
+									// Store server message into database
+									serverMessageModel = new ServerMessageModel(alertMessage, ServerMessageModel.TYPE_ERROR);
+									if (databaseService != null) {
+										databaseService.getServerMessageModelFactory().storeServerMessageModel(serverMessageModel);
 									}
-									ListenerManager.serverMessageListeners.handle(new ListenerManager.HandleListener<ServerMessageListener>() {
-										@Override
-										public void handle(ServerMessageListener listener) {
-											listener.onError(new ServerMessageModel(alertMessage, ServerMessageModel.TYPE_ERROR));
-										}
-									});
+									ListenerManager.serverMessageListeners.handle(listener -> listener.onError(serverMessageModel));
 									break;
 								case "alert":
-									if(alertMessageTmp.length() == 0) {
+									if (alertMessageTmp.length() == 0) {
 										alertMessage = "test alert message";
+									} else {
+										alertMessage = alertMessageTmp.toString();
 									}
-									else {
-										alertMessage = alertMessageTmp;
+									serverMessageModel = new ServerMessageModel(alertMessage, ServerMessageModel.TYPE_ALERT);
+									if (databaseService != null) {
+										databaseService.getServerMessageModelFactory().storeServerMessageModel(serverMessageModel);
 									}
-									ListenerManager.serverMessageListeners.handle(new ListenerManager.HandleListener<ServerMessageListener>() {
-										@Override
-										public void handle(ServerMessageListener listener) {
-											listener.onError(new ServerMessageModel(alertMessage, ServerMessageModel.TYPE_ALERT));
-										}
-									});
+									ListenerManager.serverMessageListeners.handle(listener -> listener.onError(serverMessageModel));
 									break;
 							}
 						}

+ 2 - 2
app/src/main/java/ch/threema/app/workers/WorkSyncWorker.kt

@@ -168,7 +168,7 @@ class WorkSyncWorker(private val context: Context, workerParameters: WorkerParam
         if (!updateRestrictionsOnly) {
             val workData: WorkData?
             try {
-                val allContacts: List<ContactModel> = contactService.getAll(true, true)
+                val allContacts: List<ContactModel> = contactService.all
                 val identities = arrayOfNulls<String>(allContacts.size)
                 for (n in allContacts.indices) {
                     identities[n] = allContacts[n].identity
@@ -195,7 +195,7 @@ class WorkSyncWorker(private val context: Context, workerParameters: WorkerParam
                 return Result.failure()
             }
 
-            val existingWorkContacts: List<ContactModel> = contactService.isWork
+            val existingWorkContacts: List<ContactModel> = contactService.allWork
             for (workContact in workData.workContacts) {
                 contactService.addWorkContact(workContact, existingWorkContacts)
             }

+ 5 - 1
app/src/main/java/ch/threema/storage/SQLDHSessionStore.java

@@ -40,7 +40,7 @@ import ch.threema.domain.stores.DHSessionStoreException;
 import ch.threema.domain.stores.DHSessionStoreInterface;
 
 public class SQLDHSessionStore extends SQLiteOpenHelper implements DHSessionStoreInterface {
-	private static final String DATABASE_NAME = "threema-fs.db";
+	public static final String DATABASE_NAME = "threema-fs.db";
 	private static final int DATABASE_VERSION = 1;
 	private static final String SESSION_TABLE = "session";
 
@@ -220,6 +220,10 @@ public class SQLDHSessionStore extends SQLiteOpenHelper implements DHSessionStor
 		}
 	}
 
+	public void executeNull() {
+		getWritableDatabase().rawQuery("SELECT NULL", null).close();
+	}
+
 	private DHSession dhSessionFromCursor(Cursor cursor) throws DHSessionStoreException {
 		try (cursor) {
 			return new DHSession(

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

@@ -1338,7 +1338,7 @@ sicheren Ort gesichert oder ausgedruckt haben.</string>
 	<string name="group_link_rename">Link umbenennen</string>
 	<string name="group_link_rename_tag">Neuer Name</string>
 	<string name="tap_here_for_more">Hier tippen für mehr Informationen</string>
-	<string name="another_connection_instructions">Der Server hat zwei oder mehr Verbindungen von verschiedenen Geräten mit der gleichen Threema-ID erkannt.\n\nEs ist nicht möglich, eine Threema-ID auf mehreren Geräten gleichzeitig zu verwenden. Neue Nachrichten werden nur an das Gerät übermittelt, welches sich zuletzt beim Server angemeldet hat.\n\nFalls Sie auf ein neues Gerät wechseln, deinstallieren oder deaktivieren Sie bitte %s auf dem alten Gerät und starten Sie anschliessend das neue Gerät neu.</string>
+	<string name="another_connection_instructions"><![CDATA[Der Server hat zwei oder mehr Verbindungen von verschiedenen Geräten mit der gleichen Threema-ID erkannt.<br><br>Es ist nicht möglich, eine Threema-ID auf mehreren Geräten gleichzeitig zu verwenden. Neue Nachrichten werden nur an das Gerät übermittelt, welches sich zuletzt beim Server angemeldet hat.<br><br>Falls Sie auf ein neues Gerät wechseln, deinstallieren oder deaktivieren Sie bitte %s auf dem alten Gerät und starten Sie anschliessend das neue Gerät neu.]]></string>
 	<string name="app_store_error_code">App Store Fehlercode: %d</string>
 	<string name="backup_restore_type"><![CDATA[Welche Art von Backup möchten Sie wiederherstellen? <br/><br/> <a href=%s>Mehr über Backups in Threema erfahren</a>]]></string>
 	<string name="data_backup_info">Alles wiederherstellen inkl. Chats</string>
@@ -1506,7 +1506,7 @@ sicheren Ort gesichert oder ausgedruckt haben.</string>
 	<string name="prefs_summary_hibernation_api_32">Um zu verhindern, dass Threema bei längerer Inaktivität nicht mehr funktioniert, deaktivieren Sie «Berechtigungen löschen und Speicherplatz freigeben» in den Systemeinstellungen.</string>
 	<string name="prefs_summary_hibernation_api">Um zu verhindern, dass Threema bei längerer Inaktivität nicht mehr funktioniert, deaktivieren Sie «App-Aktivität bei Nichtnutzung stoppen» in den Systemeinstellungen.</string>
 	<string name="unable_to_fetch_configuration">Konfiguration kann nicht vom Server geladen werden. Versuchen Sie es später erneut.</string>
-	<string name="rogue_device_warning">Der Server hat eine Verbindung von einem anderen Gerät mit derselben Threema-ID erkannt.\n\nWenn Sie Ihre Threema-ID in der Zwischenzeit nicht auf einem anderen Gerät oder mit einer älteren App-Version genutzt haben, kontaktieren Sie bitte den Support und senden Sie falls möglich die Logdatei mit.</string>
+	<string name="rogue_device_warning"><![CDATA[Eine Verbindung von einem anderen Gerät mit dieser Threema-ID wurde erkannt. Haben Sie Ihre Threema-ID kürzlich auf einem anderen Gerät verwendet? <br><br> Wenn ja, können Sie diese Nachricht ignorieren. <br><br> Wenn nein, ist Ihr privater Schlüssel möglicherweise kompromittiert. Bitte <a href="https://threema.ch/de/faq/another_connection">befolgen Sie unsere Empfehlungen</a> zum Schutz Ihres Geräts und Ihrer Daten, bevor Sie eine neue ID erstellen.]]></string>
     <string name="fetch2_failure">Der Abgleich mit dem Bereitstellungsserver ist fehlgeschlagen.</string>
     <string name="no_members_support_group_calls">Keines der anderen Gruppenmitglieder kann an Gruppenanrufen teilnehmen</string>
 	<string name="group_calls">Gruppenanrufe</string>

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

@@ -1280,7 +1280,7 @@
 	<string name="group_link_rename">Rename</string>
 	<string name="group_link_rename_tag">New name</string>
 	<string name="tap_here_for_more">Tap here for more information</string>
-	<string name="another_connection_instructions">The server has detected two or more connections from different devices with the same Threema ID.\n\nIt is not possible to use the same Threema ID on several devices simultaneously. New messages are only transmitted to the device that last logged in to the server.\n\nIf you’re switching to a new device, please uninstall or disable %s on the old device and then reboot the new one.</string>
+	<string name="another_connection_instructions"><![CDATA[The server has detected two or more connections from different devices with the same Threema ID.<br><br>It is not possible to use the same Threema ID on several devices simultaneously. New messages are only transmitted to the device that last logged in to the server.<br><br>If you’re switching to a new device, please uninstall or disable %s on the old device and then reboot the new one.]]></string>
 	<string name="app_store_error_code">App store error code: %d</string>
 	<string name="backup_restore_type"><![CDATA[What kind of backup do you want to restore? <br/><br/> <a href=%s>Learn more about backups in Threema</a>]]></string>
 	<string name="data_backup_info">Restore everything incl. chats</string>
@@ -1452,7 +1452,7 @@
 	<string name="prefs_summary_hibernation_api_32">To prevent that Threema gets paused by the system after longer inactivity, please disable the system setting “Remove permissions and free up space”.</string>
 	<string name="prefs_summary_hibernation_api">To prevent that Threema gets paused by the system after longer inactivity, please disable the system setting “Pause app activity if unused”.</string>
 	<string name="unable_to_fetch_configuration">Unable to fetch data from configuration server. Try again later.</string>
-	<string name="rogue_device_warning">The server has detected a connection from a different device with the same Threema ID.\n\nIf you haven’t used your Threema ID on another device or with an older app version in the meantime, then please contact support and send the log file if possible.</string>
+	<string name="rogue_device_warning"><![CDATA[A connection from a different device with the same Threema ID has been detected. Have you recently used your Threema ID on another device? <br><br> If you have, you can ignore this message. <br><br> If you haven\'t, your private key may be compromised. Please <a href="https://threema.ch/en/faq/another_connection">follow our suggestions</a> to protect your device and data before creating a new ID.]]></string>
     <string name="fetch2_failure">Synchronization with the provisioning server failed.</string>
 	<string name="no_members_support_group_calls">There are no other members in this group that are able to answer group calls</string>
 	<string name="group_calls">Group calls</string>

+ 2 - 0
domain/src/main/java/ch/threema/domain/stores/DHSessionStoreInterface.java

@@ -86,4 +86,6 @@ public interface DHSessionStoreInterface {
 	 * @return number of deleted sessions
 	 */
 	int deleteAllSessionsExcept(String myIdentity, String peerIdentity, DHSessionId excludeSessionId, boolean fourDhOnly) throws DHSessionStoreException;
+
+	void executeNull();
 }

+ 5 - 0
domain/src/main/java/ch/threema/domain/stores/InMemoryDHSessionStore.java

@@ -121,4 +121,9 @@ public class InMemoryDHSessionStore implements DHSessionStoreInterface {
 		}
 		return numDeleted;
 	}
+
+	@Override
+	public void executeNull() {
+		// nothing to do
+	}
 }