Threema 4 лет назад
Родитель
Сommit
b5dfdc569c
22 измененных файлов с 371 добавлено и 195 удалено
  1. 2 2
      app/build.gradle
  2. 10 12
      app/src/main/java/ch/threema/app/ThreemaApplication.java
  3. 1 1
      app/src/main/java/ch/threema/app/adapters/ContactListAdapter.java
  4. 3 3
      app/src/main/java/ch/threema/app/fragments/ComposeMessageFragment.java
  5. 2 1
      app/src/main/java/ch/threema/app/managers/ServiceManager.java
  6. 1 1
      app/src/main/java/ch/threema/app/preference/SettingsPrivacyFragment.java
  7. 1 0
      app/src/main/java/ch/threema/app/services/ContactServiceImpl.java
  8. 19 13
      app/src/main/java/ch/threema/app/services/NotificationServiceImpl.java
  9. 21 21
      app/src/main/java/ch/threema/app/services/ShortcutService.java
  10. 126 79
      app/src/main/java/ch/threema/app/services/ShortcutServiceImpl.java
  11. 14 10
      app/src/main/java/ch/threema/app/utils/AndroidContactUtil.java
  12. 19 10
      app/src/main/java/ch/threema/app/utils/ConversationNotificationUtil.java
  13. 0 11
      app/src/main/java/ch/threema/app/utils/NameUtil.java
  14. 48 0
      app/src/main/res/drawable-v24/ic_thumbscroller.xml
  15. 3 25
      app/src/main/res/drawable/ic_thumbscroller.xml
  16. 7 0
      app/src/main/res/drawable/shape_rounded_corner_popup_bg_forced_light.xml
  17. 1 1
      app/src/main/res/layout/popup_qrcode.xml
  18. 80 0
      app/src/main/res/values-it/strings.xml
  19. 0 5
      app/src/main/res/xml/app_shortcuts.xml
  20. 5 0
      app/src/onprem/res/xml/app_shortcuts.xml
  21. 4 0
      app/src/red/res/xml/app_shortcuts.xml
  22. 4 0
      app/src/store_google_work/res/xml/app_shortcuts.xml

+ 2 - 2
app/build.gradle

@@ -20,7 +20,7 @@ if (getGradle().getStartParameter().getTaskRequests().toString().contains("Hms")
 }
 
 // version codes
-def app_version = "4.6"
+def app_version = "4.61"
 def beta_suffix = "" // with leading dash
 
 /**
@@ -99,7 +99,7 @@ android {
         vectorDrawables.useSupportLibrary = true
         applicationId "ch.threema.app"
         testApplicationId 'ch.threema.app.test'
-        versionCode 705
+        versionCode 708
         versionName "${app_version}${beta_suffix}"
         resValue "string", "app_name", "Threema"
         // package name used for sync adapter

+ 10 - 12
app/src/main/java/ch/threema/app/ThreemaApplication.java

@@ -950,25 +950,23 @@ public class ThreemaApplication extends MultiDexApplication implements DefaultLi
 				scheduleWorkSync(preferenceStore);
 				// schedule identity states / feature masks etc.
 				scheduleIdentityStatesSync(preferenceStore);
-			}).start();
+			}, "scheduleSync").start();
 
 			initMapbox();
 
 			// publish most recent chats or pinned shortcuts as sharing targets
-			ShortcutService shortcutService;
-			try {
-				shortcutService = serviceManager.getShortcutService();
-
-				if (shortcutService != null) {
-					if (serviceManager.getPreferenceService().isDirectShare()) {
+			new Thread(() -> {
+				ShortcutService shortcutService;
+				try {
+					shortcutService = serviceManager.getShortcutService();
+					if (shortcutService != null) {
+						shortcutService.deleteDynamicShortcuts();
 						shortcutService.publishRecentChatsAsSharingTargets();
-					} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1) {
-						shortcutService.publishPinnedShortcutsAsSharingTargets();
 					}
+				} catch (ThreemaException e) {
+					logger.error("Exception, failed to publish sharing shortcut targets", e);
 				}
-			} catch (ThreemaException e) {
-				logger.error("Exception, failed to publish sharing shortcut targets", e);
-			}
+			}, "createShareTargets").start();
 
 			// setup locale override
 			ConfigUtils.setLocaleOverride(getAppContext(), serviceManager.getPreferenceService());

+ 1 - 1
app/src/main/java/ch/threema/app/adapters/ContactListAdapter.java

@@ -128,7 +128,7 @@ public class ContactListAdapter extends FilterableListAdapter implements Section
 		Date recentlyAddedDate = new Date(System.currentTimeMillis() - DateUtils.DAY_IN_MILLIS);
 
 		for (ContactModel contactModel : all) {
-			if (contactModel != null && contactModel.getDateCreated() != null && recentlyAddedDate.before(contactModel.getDateCreated()) && !ContactUtil.isChannelContact(contactModel) && !"ECHOECHO".equalsIgnoreCase(contactModel.getIdentity())) {
+			if (contactModel != null && contactModel.getDateCreated() != null && recentlyAddedDate.before(contactModel.getDateCreated()) && !"ECHOECHO".equalsIgnoreCase(contactModel.getIdentity())) {
 				recents.add(contactModel);
 			}
 		}

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

@@ -1945,7 +1945,7 @@ public class ComposeMessageFragment extends Fragment implements
 				this.openGroupRequestNoticeView.updateGroupRequests();
 			}
 			if (preferenceService.isDirectShare()) {
-				shortcutService.createShareTargetShortcut(groupModel);
+				new Thread(() -> shortcutService.createShareTargetShortcut(groupModel), "createShortcut").start();
 			}
 		} else if (intent.hasExtra(ThreemaApplication.INTENT_DATA_DISTRIBUTION_LIST) || this.distributionListId != 0) {
 			this.isDistributionListChat = true;
@@ -1969,7 +1969,7 @@ public class ComposeMessageFragment extends Fragment implements
 				intent.removeExtra(ThreemaApplication.INTENT_DATA_DISTRIBUTION_LIST);
 				this.messageReceiver = distributionListService.createReceiver(this.distributionListModel);
 				if (preferenceService.isDirectShare()) {
-					shortcutService.createShareTargetShortcut(distributionListModel);
+					new Thread(() -> shortcutService.createShareTargetShortcut(distributionListModel), "createShortcut").start();
 				}
 			} catch (Exception e) {
 				logger.error("Exception", e);
@@ -2016,7 +2016,7 @@ public class ComposeMessageFragment extends Fragment implements
 			this.typingIndicatorTextWatcher = new TypingIndicatorTextWatcher(this.userService, contactModel);
 			this.conversationUid = ConversationUtil.getIdentityConversationUid(this.identity);
 			if (preferenceService.isDirectShare()) {
-				shortcutService.createShareTargetShortcut(contactModel);
+				new Thread(() -> shortcutService.createShareTargetShortcut(contactModel), "createShortcut").start();
 			}
 		}
 

+ 2 - 1
app/src/main/java/ch/threema/app/managers/ServiceManager.java

@@ -921,7 +921,8 @@ public class ServiceManager {
 					this.getContext(),
 					this.getContactService(),
 					this.getGroupService(),
-					this.getDistributionListService()
+					this.getDistributionListService(),
+					this.getPreferenceService()
 			);
 		}
 		return this.shortcutService;

+ 1 - 1
app/src/main/java/ch/threema/app/preference/SettingsPrivacyFragment.java

@@ -232,7 +232,7 @@ public class SettingsPrivacyFragment extends ThreemaPreferenceFragment implement
 			});
 		}
 
-		if (Build.VERSION.SDK_INT < 29) {
+		if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
 			PreferenceCategory preferenceCategory = findPreference("pref_key_other");
 			if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
 				preferenceCategory.removePreference(findPreference(getResources().getString(R.string.preferences__direct_share)));

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

@@ -1343,6 +1343,7 @@ public class ContactServiceImpl implements ContactService {
 	 * @param contactModel ContactModel to get Uri for
 	 * @return Uri of Android contact as a string or null if there's no linked contact or permission to access contacts has not been granted
 	 */
+	@Nullable
 	public String getAndroidContactLookupUriString(ContactModel contactModel) {
 		String contactLookupUri = null;
 		if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {

+ 19 - 13
app/src/main/java/ch/threema/app/services/NotificationServiceImpl.java

@@ -1838,23 +1838,29 @@ public class NotificationServiceImpl implements NotificationService {
 			try {
 				String launcherClassName = context.getPackageManager().getLaunchIntentForPackage(BuildConfig.APPLICATION_ID).getComponent().getClassName();
 				if (context.getPackageManager().resolveContentProvider("com.sonymobile.home.resourceprovider", 0) != null) {
+					logger.info("Badge: Sony content provider showing " + unreadMessages + " unread");
+
 					// use content provider
-					final ContentValues contentValues = new ContentValues();
-					contentValues.put("badge_count", unreadMessages);
-					contentValues.put("package_name", BuildConfig.APPLICATION_ID);
-					contentValues.put("activity_name", launcherClassName);
-
-					if (RuntimeUtil.isOnUiThread()) {
-						if (queryHandler == null) {
-							queryHandler = new AsyncQueryHandler(
-								context.getApplicationContext().getContentResolver()) {
-							};
+					if (unreadMessages < 0) {
+						final ContentValues contentValues = new ContentValues();
+						contentValues.put("badge_count", unreadMessages);
+						contentValues.put("package_name", BuildConfig.APPLICATION_ID);
+						contentValues.put("activity_name", launcherClassName);
+
+						if (RuntimeUtil.isOnUiThread()) {
+							if (queryHandler == null) {
+								queryHandler = new AsyncQueryHandler(
+									context.getApplicationContext().getContentResolver()) {
+								};
+							}
+							queryHandler.startInsert(0, null, Uri.parse("content://com.sonymobile.home.resourceprovider/badge"), contentValues);
+						} else {
+							context.getApplicationContext().getContentResolver().insert(Uri.parse("content://com.sonymobile.home.resourceprovider/badge"), contentValues);
 						}
-						queryHandler.startInsert(0, null, Uri.parse("content://com.sonymobile.home.resourceprovider/badge"), contentValues);
-					} else {
-						context.getApplicationContext().getContentResolver().insert(Uri.parse("content://com.sonymobile.home.resourceprovider/badge"), contentValues);
 					}
 				} else {
+					logger.info("Badge: Sony broadcast showing " + unreadMessages + " unread");
+
 					// use broadcast
 					Intent intent = new Intent("com.sonyericsson.home.action.UPDATE_BADGE");
 					intent.putExtra("com.sonyericsson.home.intent.extra.badge.PACKAGE_NAME", BuildConfig.APPLICATION_ID);

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

@@ -23,42 +23,42 @@ package ch.threema.app.services;
 
 import android.content.pm.ShortcutInfo;
 
+import androidx.annotation.WorkerThread;
 import androidx.core.content.pm.ShortcutInfoCompat;
 import ch.threema.storage.models.ContactModel;
 import ch.threema.storage.models.DistributionListModel;
 import ch.threema.storage.models.GroupModel;
 
 public interface ShortcutService {
-	public static final int TYPE_NONE = 0;
-	public static final int TYPE_CHAT = 1;
-	public static final int TYPE_CALL = 2;
-	public static final int TYPE_SHARE_SHORTCUT_CONTACT = 3;
-	public static final int TYPE_SHARE_SHORTCUT_GROUP = 4;
-	public static final int TYPE_SHARE_SHORTCUT_DISTRIBUTION_LIST = 5;
+	int TYPE_NONE = 0;
+	int TYPE_CHAT = 1;
+	int TYPE_CALL = 2;
+	int TYPE_SHARE_SHORTCUT_CONTACT = 3;
+	int TYPE_SHARE_SHORTCUT_GROUP = 4;
+	int TYPE_SHARE_SHORTCUT_DISTRIBUTION_LIST = 5;
 
-	void publishRecentChatsAsSharingTargets();
-	void publishPinnedShortcutsAsSharingTargets();
+	@WorkerThread void publishRecentChatsAsSharingTargets();
 
-	void updateShortcut(ContactModel contactModel);
-	void updateShortcut(GroupModel groupModel);
-	void updateShortcut(DistributionListModel distributionListModel);
+	@WorkerThread void updateShortcut(ContactModel contactModel);
+	@WorkerThread void updateShortcut(GroupModel groupModel);
+	@WorkerThread void updateShortcut(DistributionListModel distributionListModel);
 
-	void createShortcut(ContactModel contactModel, int type);
-	void createShortcut(GroupModel groupModel);
-	void createShortcut(DistributionListModel distributionListModel);
+	@WorkerThread void createShortcut(ContactModel contactModel, int type);
+	@WorkerThread void createShortcut(GroupModel groupModel);
+	@WorkerThread void createShortcut(DistributionListModel distributionListModel);
 
 	ShortcutInfo getShortcutInfo(ContactModel contactModel, int type);
 	ShortcutInfo getShortcutInfo(GroupModel groupModel);
 	ShortcutInfo getShortcutInfo(DistributionListModel distributionListModel);
 
-	void createShareTargetShortcut(ContactModel contactModel);
-	void createShareTargetShortcut(GroupModel groupModel);
-	void createShareTargetShortcut(DistributionListModel distributionListModel);
+	@WorkerThread void createShareTargetShortcut(ContactModel contactModel);
+	@WorkerThread void createShareTargetShortcut(GroupModel groupModel);
+	@WorkerThread void createShareTargetShortcut(DistributionListModel distributionListModel);
 
-	void deleteShortcut(ContactModel contactModel);
-	void deleteShortcut(GroupModel groupModel);
-	void deleteShortcut(DistributionListModel distributionListModel);
-	void deleteDynamicShortcuts();
+	@WorkerThread void deleteShortcut(ContactModel contactModel);
+	@WorkerThread void deleteShortcut(GroupModel groupModel);
+	@WorkerThread void deleteShortcut(DistributionListModel distributionListModel);
+	@WorkerThread void deleteDynamicShortcuts();
 
 	ShortcutInfoCompat getShortcutInfoCompat(ContactModel contactModel, int type);
 	ShortcutInfoCompat getShortcutInfoCompat(GroupModel groupModel);

+ 126 - 79
app/src/main/java/ch/threema/app/services/ShortcutServiceImpl.java

@@ -22,7 +22,6 @@
 package ch.threema.app.services;
 
 import android.annotation.TargetApi;
-import android.app.Person;
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.ShortcutInfo;
@@ -39,23 +38,25 @@ import org.slf4j.LoggerFactory;
 
 import java.util.ArrayList;
 import java.util.Collections;
-import java.util.HashSet;
 import java.util.List;
-import java.util.Set;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.annotation.RequiresApi;
+import androidx.annotation.WorkerThread;
+import androidx.core.app.Person;
 import androidx.core.content.pm.ShortcutInfoCompat;
 import androidx.core.content.pm.ShortcutManagerCompat;
 import androidx.core.graphics.drawable.IconCompat;
 import androidx.vectordrawable.graphics.drawable.VectorDrawableCompat;
+import ch.threema.app.BuildConfig;
 import ch.threema.app.R;
 import ch.threema.app.ThreemaApplication;
 import ch.threema.app.activities.ComposeMessageActivity;
 import ch.threema.app.managers.ServiceManager;
 import ch.threema.app.utils.AvatarConverterUtil;
 import ch.threema.app.utils.BitmapUtil;
+import ch.threema.app.utils.ConversationNotificationUtil;
 import ch.threema.app.utils.NameUtil;
 import ch.threema.app.utils.TestUtil;
 import ch.threema.app.voip.activities.CallActivity;
@@ -75,15 +76,17 @@ public class ShortcutServiceImpl implements ShortcutService {
 	private final ContactService contactService;
 	private final GroupService groupService;
 	private final DistributionListService distributionListService;
+	private final PreferenceService preferenceService;
 
-	private static final String DYNAMIC_SHORTCUT_SHARE_TARGET_CATEGORY = "ch.threema.app.category.DYNAMIC_SHORTCUT_SHARE_TARGET";
+	private static final String DYNAMIC_SHORTCUT_SHARE_TARGET_CATEGORY = BuildConfig.APPLICATION_ID + ".category.DYNAMIC_SHORTCUT_SHARE_TARGET";
 
 	public ShortcutServiceImpl(Context context, ContactService contactService, GroupService groupService,
-	                           DistributionListService distributionListService) {
+	                           DistributionListService distributionListService, PreferenceService preferenceService) {
 		this.context = context;
 		this.contactService = contactService;
 		this.groupService = groupService;
 		this.distributionListService = distributionListService;
+		this.preferenceService = preferenceService;
 	}
 
 	private static class CommonShortcutInfo {
@@ -93,7 +96,15 @@ public class ShortcutServiceImpl implements ShortcutService {
 	}
 
 	@Override
+	@WorkerThread
 	public void publishRecentChatsAsSharingTargets() {
+		if (!preferenceService.isDirectShare()) {
+			if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1) {
+				publishPinnedShortcutsAsSharingTargets();
+			}
+			return;
+		}
+
 		final ServiceManager serviceManager = ThreemaApplication.getServiceManager();
 		if (serviceManager == null) {
 			return;
@@ -138,12 +149,17 @@ public class ShortcutServiceImpl implements ShortcutService {
 		for (int i = 0; i < numPublishableConversations; i++) {
 			final ConversationModel conversationModel = conversations.get(i);
 
+			ShortcutInfoCompat shortcutInfoCompat;
 			if (conversationModel.isContactConversation()) {
-				sharingTargetShortcuts.add(getSharingTargetShortcutInfoCompat(conversationModel.getContact()));
-			} else if (conversationModel.isGroupConversation()){
-				sharingTargetShortcuts.add(getSharingTargetShortcutInfoCompat(conversationModel.getGroup()));
+				shortcutInfoCompat = getSharingTargetShortcutInfoCompat(conversationModel.getContact());
+			} else if (conversationModel.isGroupConversation()) {
+				shortcutInfoCompat = getSharingTargetShortcutInfoCompat(conversationModel.getGroup());
 			} else {
-				sharingTargetShortcuts.add(getSharingTargetShortcutInfoCompat(conversationModel.getDistributionList()));
+				shortcutInfoCompat = getSharingTargetShortcutInfoCompat(conversationModel.getDistributionList());
+			}
+
+			if (shortcutInfoCompat != null) {
+				sharingTargetShortcuts.add(shortcutInfoCompat);
 			}
 		}
 		if (sharingTargetShortcuts.isEmpty()) {
@@ -154,9 +170,12 @@ public class ShortcutServiceImpl implements ShortcutService {
 		logger.info("Published most recent conversations as sharing target shortcuts");
 	}
 
+	/**
+	 *  Use launcher shortcuts as share targets if direct share is disabled
+	 */
 	@RequiresApi(api = Build.VERSION_CODES.N_MR1)
-	@Override
-	public void publishPinnedShortcutsAsSharingTargets() {
+	@WorkerThread
+	private void publishPinnedShortcutsAsSharingTargets() {
 		ShortcutManager shortcutManager = context.getSystemService(ShortcutManager.class);
 		final List<ShortcutInfoCompat> sharingTargetShortcuts = new ArrayList<>();
 
@@ -165,21 +184,27 @@ public class ShortcutServiceImpl implements ShortcutService {
 			Intent shortcutIntent = shortcut.getIntent();
 
 			if (!shortcutIntent.hasExtra(CallActivity.EXTRA_CALL_FROM_SHORTCUT)) {
+				ShortcutInfoCompat shortcutInfoCompat;
+
 				if (shortcutIntent.hasExtra(ThreemaApplication.INTENT_DATA_GROUP)) {
 					GroupModel groupModel = groupService.getById(
 						shortcutIntent.getIntExtra(ThreemaApplication.INTENT_DATA_GROUP, 0)
 					);
-					sharingTargetShortcuts.add(getSharingTargetShortcutInfoCompat(groupModel));
+					 shortcutInfoCompat = getSharingTargetShortcutInfoCompat(groupModel);
 				} else if (shortcutIntent.hasExtra(ThreemaApplication.INTENT_DATA_DISTRIBUTION_LIST)) {
 					DistributionListModel distributionList = distributionListService.getById(
 						shortcutIntent.getIntExtra(ThreemaApplication.INTENT_DATA_DISTRIBUTION_LIST, 0)
 					);
-					sharingTargetShortcuts.add(getSharingTargetShortcutInfoCompat(distributionList));
+					shortcutInfoCompat = getSharingTargetShortcutInfoCompat(distributionList);
 				} else {
 					ContactModel contact = contactService.getByIdentity(
 						shortcutIntent.getStringExtra(ThreemaApplication.INTENT_DATA_CONTACT)
 					);
-					sharingTargetShortcuts.add(getSharingTargetShortcutInfoCompat(contact));
+					shortcutInfoCompat = getSharingTargetShortcutInfoCompat(contact);
+				}
+
+				if (shortcutInfoCompat != null) {
+					sharingTargetShortcuts.add(shortcutInfoCompat);
 				}
 			}
 		}
@@ -193,6 +218,7 @@ public class ShortcutServiceImpl implements ShortcutService {
 	}
 
 	@Override
+	@WorkerThread
 	public void createShortcut(ContactModel contactModel, int type) {
 		ShortcutInfoCompat shortcutInfoCompat = getShortcutInfoCompat(contactModel, type);
 
@@ -202,6 +228,7 @@ public class ShortcutServiceImpl implements ShortcutService {
 	}
 
 	@Override
+	@WorkerThread
 	public void createShortcut(GroupModel groupModel) {
 		ShortcutInfoCompat shortcutInfoCompat = getShortcutInfoCompat(groupModel);
 
@@ -211,31 +238,44 @@ public class ShortcutServiceImpl implements ShortcutService {
 	}
 
 	@Override
+	@WorkerThread
 	public void createShortcut(DistributionListModel distributionListModel) {
 		ShortcutInfoCompat shortcutInfoCompat = getShortcutInfoCompat(distributionListModel);
-
 		if (shortcutInfoCompat != null) {
 			ShortcutManagerCompat.requestPinShortcut(context, shortcutInfoCompat, null);
 		}
 	}
 
 	@Override
+	@WorkerThread
 	public void createShareTargetShortcut(ContactModel contactModel) {
-		publishDynamicShortcuts(Collections.singletonList(getSharingTargetShortcutInfoCompat(contactModel)));
+		ShortcutInfoCompat shortcutInfoCompat = getSharingTargetShortcutInfoCompat(contactModel);
+		if (shortcutInfoCompat != null) {
+			publishDynamicShortcuts(Collections.singletonList(shortcutInfoCompat));
+		}
 	}
 
 	@Override
+	@WorkerThread
 	public void createShareTargetShortcut(GroupModel groupModel) {
-		publishDynamicShortcuts(Collections.singletonList(getSharingTargetShortcutInfoCompat(groupModel)));
+		ShortcutInfoCompat shortcutInfoCompat = getSharingTargetShortcutInfoCompat(groupModel);
+		if (shortcutInfoCompat != null) {
+			publishDynamicShortcuts(Collections.singletonList(shortcutInfoCompat));
+		}
 	}
 
 	@Override
+	@WorkerThread
 	public void createShareTargetShortcut(DistributionListModel distributionListModel) {
-		publishDynamicShortcuts(Collections.singletonList(getSharingTargetShortcutInfoCompat(distributionListModel)));
+		ShortcutInfoCompat shortcutInfoCompat = getSharingTargetShortcutInfoCompat(distributionListModel);
+		if (shortcutInfoCompat != null) {
+			publishDynamicShortcuts(Collections.singletonList(shortcutInfoCompat));
+		}
 	}
 
 	@TargetApi(Build.VERSION_CODES.O)
 	@Override
+	@WorkerThread
 	public void updateShortcut(ContactModel contactModel) {
 		if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
 			ShortcutManager shortcutManager = context.getSystemService(ShortcutManager.class);
@@ -261,6 +301,7 @@ public class ShortcutServiceImpl implements ShortcutService {
 
 	@TargetApi(Build.VERSION_CODES.O)
 	@Override
+	@WorkerThread
 	public void updateShortcut(GroupModel groupModel) {
 		if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
 			ShortcutManager shortcutManager = context.getSystemService(ShortcutManager.class);
@@ -284,6 +325,7 @@ public class ShortcutServiceImpl implements ShortcutService {
 
 	@TargetApi(Build.VERSION_CODES.O)
 	@Override
+	@WorkerThread
 	public void updateShortcut(DistributionListModel distributionListModel) {
 		if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
 			ShortcutManager shortcutManager = context.getSystemService(ShortcutManager.class);
@@ -306,6 +348,7 @@ public class ShortcutServiceImpl implements ShortcutService {
 	}
 
 	@Override
+	@WorkerThread
 	public void deleteShortcut(ContactModel contactModel) {
 		String uniqueId = contactModel.getIdentity();
 
@@ -313,6 +356,7 @@ public class ShortcutServiceImpl implements ShortcutService {
 	}
 
 	@Override
+	@WorkerThread
 	public void deleteShortcut(GroupModel groupModel) {
 		String uniqueId = String.valueOf(groupModel.getId());
 
@@ -320,6 +364,7 @@ public class ShortcutServiceImpl implements ShortcutService {
 	}
 
 	@Override
+	@WorkerThread
 	public void deleteShortcut(DistributionListModel distributionListModel) {
 		String uniqueId = String.valueOf(distributionListModel.getId());
 
@@ -327,10 +372,12 @@ public class ShortcutServiceImpl implements ShortcutService {
 	}
 
 	@Override
+	@WorkerThread
 	public void deleteDynamicShortcuts() {
 		ShortcutManagerCompat.removeAllDynamicShortcuts(context);
 	}
 
+	@WorkerThread
 	private void deleteShortcutByID(String id) {
 		if (!TestUtil.empty(id)) {
 			for (ShortcutInfoCompat shortcutInfo : ShortcutManagerCompat.getDynamicShortcuts(context)) {
@@ -345,6 +392,7 @@ public class ShortcutServiceImpl implements ShortcutService {
 	 * uses addDynamicShortcuts(List<ShortcutInfoCompat> shortcuts) to add a list of shortcuts to existing shortcuts
 	 * @param shortcuts to be added to already existing dynamic shortcuts
 	 */
+	@WorkerThread
 	private void tryAddingDynamicShortcuts(List<ShortcutInfoCompat> shortcuts) {
 		logger.info("tryAddingDynamicShortcuts {}", shortcuts.size());
 		// try to catch the max shortcuts exceeded error for some devices that always return 0 active shortcuts
@@ -359,6 +407,7 @@ public class ShortcutServiceImpl implements ShortcutService {
 	 * removeAllDynamicShortcuts() + addDynamicShortcuts(List<ShortcutInfoCompat> shortcuts)
 	 * @param shortcuts to be set as a fresh list of dynamic shortcuts, all previous dynamic shortcuts not included in this list will be removed.
 	 */
+	@WorkerThread
 	private void trySettingDynamicShortcuts(List<ShortcutInfoCompat> shortcuts) {
 		logger.info("trying to reset shortcut list {}", shortcuts.size());
 		// try to catch the max shortcuts exceeded error for some devices that always return 0 active shortcuts
@@ -442,80 +491,75 @@ public class ShortcutServiceImpl implements ShortcutService {
 	}
 
 	@Nullable
-	private ShortcutInfoCompat getSharingTargetShortcutInfoCompat(ContactModel contactModel) {
-		Intent intent = new Intent(Intent.ACTION_DEFAULT);
+	private ShortcutInfoCompat createSharingShortcut(@NonNull String tag, @Nullable Bitmap avatarBitmap, @Nullable String label, @Nullable String shortLabel, @Nullable Person person) {
+		if (avatarBitmap != null && !TestUtil.empty(label)) {
 
-		Set<String> shortcutCategories = new HashSet<>();
-		shortcutCategories.add(DYNAMIC_SHORTCUT_SHARE_TARGET_CATEGORY);
-
-		Bitmap avatarBitmap = getRoundBitmap(contactService.getAvatar(contactModel, false));
-
-		if (avatarBitmap != null) {
 			// workaround because intent extras are lost in dynamic shortcut
 			// receiver identity is passed as the shortcut id with a specific type
-			return new ShortcutInfoCompat.Builder(context, TYPE_SHARE_SHORTCUT_CONTACT + contactModel.getIdentity())
-				.setIcon(IconCompat.createWithBitmap(avatarBitmap))
-				.setShortLabel(NameUtil.getDisplayNameOrNickname(contactModel, true))
-				.setIntent(intent)
-				.setLongLived(true)
-				.setCategories(shortcutCategories)
-				.build();
+			try {
+				ShortcutInfoCompat.Builder shortcutInfoCompatBuilder = new ShortcutInfoCompat.Builder(context, tag)
+					.setIcon(IconCompat.createWithBitmap(avatarBitmap))
+					.setShortLabel(shortLabel != null ? shortLabel : label)
+					.setLongLabel(label)
+					.setIntent(new Intent(Intent.ACTION_DEFAULT))
+					.setLongLived(true)
+					.setCategories(Collections.singleton(DYNAMIC_SHORTCUT_SHARE_TARGET_CATEGORY));
+
+				if (person != null) {
+					shortcutInfoCompatBuilder.setPerson(person);
+				}
+
+				return shortcutInfoCompatBuilder.build();
+			} catch (IllegalArgumentException e) {
+				logger.debug("Unable to build shortcut", e);
+			}
 		}
 		return null;
 	}
 
 	@Nullable
-	private ShortcutInfoCompat getSharingTargetShortcutInfoCompat(GroupModel groupModel) {
-		Intent intent = new Intent(Intent.ACTION_DEFAULT);
-
-		Set<String> shortcutCategories = new HashSet<>();
-		shortcutCategories.add(DYNAMIC_SHORTCUT_SHARE_TARGET_CATEGORY);
-
-		Bitmap avatarBitmap = getRoundBitmap(groupService.getAvatar(groupModel, false));
-		String groupName = groupModel.getName();
-		if (groupName == null) {
-			groupName = groupService.getMembersString(groupModel);
+	private ShortcutInfoCompat getSharingTargetShortcutInfoCompat(@Nullable ContactModel contactModel) {
+		if (contactModel == null) {
+			return null;
 		}
 
-		if (avatarBitmap != null) {
-			// workaround because intent extras are lost in dynamic shortcut
-			// receiver identity is passed as the shortcut id and a specific type
-			return new ShortcutInfoCompat.Builder(context,TYPE_SHARE_SHORTCUT_GROUP + "" + groupModel.getId())
-				.setIcon(IconCompat.createWithBitmap(avatarBitmap))
-				.setShortLabel(groupName)
-				.setIntent(intent)
-				.setLongLived(true)
-				.setCategories(shortcutCategories)
-				.build();
-		}
-		return null;
+		String fullName = NameUtil.getDisplayNameOrNickname(contactModel, true);
+		Person person = ConversationNotificationUtil.getPerson(contactService, contactModel, fullName);
+
+		return createSharingShortcut(
+			TYPE_SHARE_SHORTCUT_CONTACT + contactModel.getIdentity(),
+			contactService.getAvatar(contactModel, false),
+			fullName,
+			NameUtil.getShortName(contactModel),
+			person);
 	}
 
 	@Nullable
-	private ShortcutInfoCompat getSharingTargetShortcutInfoCompat(DistributionListModel distributionListModel) {
-		Intent intent = new Intent(Intent.ACTION_DEFAULT);
+	private ShortcutInfoCompat getSharingTargetShortcutInfoCompat(@Nullable GroupModel groupModel) {
+		if (groupModel == null) {
+			return null;
+		}
 
-		Set<String> shortcutCategories = new HashSet<>();
-		shortcutCategories.add(DYNAMIC_SHORTCUT_SHARE_TARGET_CATEGORY);
+		return createSharingShortcut(
+			TYPE_SHARE_SHORTCUT_GROUP + String.valueOf(groupModel.getId()),
+			groupService.getAvatar(groupModel, false),
+			NameUtil.getDisplayName(groupModel, groupService),
+			null,
+			null);
+	}
 
-		Bitmap avatarBitmap = getRoundBitmap(distributionListService.getAvatar(distributionListModel, false));
-		String distributionListName = distributionListModel.getName();
-		if (distributionListName == null) {
-			distributionListName = distributionListService.getMembersString(distributionListModel);
+	@Nullable
+	private ShortcutInfoCompat getSharingTargetShortcutInfoCompat(@Nullable DistributionListModel distributionListModel) {
+		if (distributionListModel == null) {
+			return null;
 		}
 
-		if (avatarBitmap != null) {
-			// workaround because intent extras are lost in dynamic shortcut
-			// receiver identity is passed as the shortcut id and a specific type
-			return new ShortcutInfoCompat.Builder(context, TYPE_SHARE_SHORTCUT_DISTRIBUTION_LIST + "" + distributionListModel.getId())
-				.setIcon(IconCompat.createWithBitmap(avatarBitmap))
-				.setShortLabel(distributionListName)
-				.setIntent(intent)
-				.setLongLived(true)
-				.setCategories(shortcutCategories)
-				.build();
-		}
-		return null;
+		return createSharingShortcut(
+			TYPE_SHARE_SHORTCUT_DISTRIBUTION_LIST + String.valueOf(distributionListModel.getId()),
+			distributionListService.getAvatar(distributionListModel, false),
+			NameUtil.getDisplayName(distributionListModel, distributionListService),
+			null,
+			null);
 	}
 
 	@TargetApi(Build.VERSION_CODES.O)
@@ -530,7 +574,7 @@ public class ShortcutServiceImpl implements ShortcutService {
 				.setLongLabel(commonShortcutInfo.longLabel)
 				.setLongLived(true)
 				.setPerson(
-					new Person.Builder()
+					new android.app.Person.Builder()
 						.setName(contactModel.getIdentity())
 						.setIcon(Icon.createWithBitmap((commonShortcutInfo.bitmap)))
 						.build()
@@ -634,15 +678,18 @@ public class ShortcutServiceImpl implements ShortcutService {
 		return null;
 	}
 
-	private void publishDynamicShortcuts(List<ShortcutInfoCompat> shortcuts) {
+	@WorkerThread
+	private void publishDynamicShortcuts(@NonNull List<ShortcutInfoCompat> shortcuts) {
 		List<ShortcutInfoCompat> activeShortcuts = ShortcutManagerCompat.getDynamicShortcuts(context);
 		int shortcutsSurplusCount = activeShortcuts.size() + shortcuts.size() - 4; //  4 as there is anyway max 4 slots of share target options in the OS share sheet
 		logger.info("publish dynamic shortcuts list compat: to publish {} active {} max limit {} surplus {}",
 			shortcuts.size(), activeShortcuts.size(), 4, shortcutsSurplusCount);
 		if (shortcutsSurplusCount > 0) {
 			while (shortcutsSurplusCount > 0) {
-				activeShortcuts.remove(0);
-				shortcutsSurplusCount -= 1;
+				if (activeShortcuts.size() > 0) {
+					activeShortcuts.remove(0);
+					shortcutsSurplusCount -= 1;
+				}
 			}
 			activeShortcuts.addAll(shortcuts);
 			// clear all and publish the modified list

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

@@ -530,16 +530,20 @@ public class AndroidContactUtil {
 
 		for (Map.Entry<String, RawContactInfo> rawContact : rawContacts.entries()) {
 			if (!TestUtil.empty(rawContact.getKey()) && rawContact.getValue().rawContactId != 0L) {
-				ContentProviderOperation.Builder builder = ContentProviderOperation.newDelete(
-					ContactsContract.RawContacts.CONTENT_URI
-						.buildUpon()
-						.appendQueryParameter(ContactsContract.CALLER_IS_SYNCADAPTER, "true")
-						.appendQueryParameter(ContactsContract.RawContacts.SYNC1, rawContact.getKey())
-						.appendQueryParameter(ContactsContract.RawContacts.ACCOUNT_NAME, account.name)
-						.appendQueryParameter(ContactsContract.RawContacts.ACCOUNT_TYPE, account.type).build())
-						.withSelection(ContactsContract.RawContacts._ID+" = ?", new String[] {String.valueOf(rawContact.getValue().rawContactId)});
-
-				contentProviderOperations.add(builder.build());
+				try {
+					ContentProviderOperation.Builder builder = ContentProviderOperation.newDelete(
+						ContactsContract.RawContacts.CONTENT_URI
+							.buildUpon()
+							.appendQueryParameter(ContactsContract.CALLER_IS_SYNCADAPTER, "true")
+							.appendQueryParameter(ContactsContract.RawContacts.SYNC1, rawContact.getKey())
+							.appendQueryParameter(ContactsContract.RawContacts.ACCOUNT_NAME, account.name)
+							.appendQueryParameter(ContactsContract.RawContacts.ACCOUNT_TYPE, account.type).build())
+						.withSelection(ContactsContract.RawContacts._ID + " = ?", new String[]{String.valueOf(rawContact.getValue().rawContactId)});
+
+					contentProviderOperations.add(builder.build());
+				} catch (Exception e) {
+					logger.error("Exception", e);
+				}
 			}
 		}
 

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

@@ -89,25 +89,34 @@ public class ConversationNotificationUtil {
 		}
 	}
 
-	private static Person getSender(AbstractMessageModel messageModel) {
+	private static Person getSenderPerson(AbstractMessageModel messageModel) {
 		//load lazy
 		try {
 			final ContactService contactService = ThreemaApplication.getServiceManager().getContactService();
 			final ContactModel contactModel = contactService.getByIdentity(messageModel.getIdentity());
 
-			Person.Builder builder = new Person.Builder().setKey(contactService.getUniqueIdString(contactModel)).setName(NameUtil.getShortName(ThreemaApplication.getAppContext(), messageModel, contactService));
-			Bitmap avatar = contactService.getAvatar(contactModel, false);
-			if (avatar != null) {
-				IconCompat iconCompat = IconCompat.createWithBitmap(avatar);
-				builder.setIcon(iconCompat);
-			}
-			return builder.build();
+			return getPerson(contactService, contactModel, NameUtil.getShortName(ThreemaApplication.getAppContext(), messageModel, contactService));
 		} catch (ThreemaException e) {
 			logger.error("ThreemaException", e);
 			return null;
 		}
 	}
 
+	public static Person getPerson(ContactService contactService, ContactModel contactModel, String name) {
+		Person.Builder builder = new Person.Builder()
+			.setKey(contactService.getUniqueIdString(contactModel))
+			.setName(name);
+		Bitmap avatar = contactService.getAvatar(contactModel, false);
+		if (avatar != null) {
+			IconCompat iconCompat = IconCompat.createWithBitmap(avatar);
+			builder.setIcon(iconCompat);
+		}
+		if (contactModel != null && contactModel.getAndroidContactLookupKey() != null) {
+			builder.setUri(contactService.getAndroidContactLookupUriString(contactModel));
+		}
+		return builder.build();
+	}
+
 	private static MessageType getMessageType(AbstractMessageModel messageModel) {
 		return messageModel.getType();
 	}
@@ -164,7 +173,7 @@ public class ConversationNotificationUtil {
 					group,
 					getFetchThumbnail(messageModel),
 					getShortcut(messageModel),
-					getSender(messageModel),
+					getSenderPerson(messageModel),
 					getMessageType(messageModel)
 			);
 
@@ -216,7 +225,7 @@ public class ConversationNotificationUtil {
 					group,
 					getFetchThumbnail(messageModel),
 					getShortcut(messageModel),
-					getSender(messageModel),
+					getSenderPerson(messageModel),
 					getMessageType(messageModel)
 			);
 		}

+ 0 - 11
app/src/main/java/ch/threema/app/utils/NameUtil.java

@@ -165,17 +165,6 @@ public class NameUtil {
 		return null;
 	}
 
-	public static String getPersonName(Context context, AbstractMessageModel messageModel, ContactService contactService) {
-		if (TestUtil.required(context, messageModel)) {
-			if (messageModel.isOutbox()) {
-				return context.getString(R.string.me_myself_and_i);
-			} else {
-				return getDisplayNameOrNickname(messageModel.getIdentity(), contactService);
-			}
-		}
-		return null;
-	}
-
 	private static String getFallbackName(ContactModel model) {
 		if (!TestUtil.empty(model.getPublicNickName()) &&
 				!model.getPublicNickName().equals(model.getIdentity())) {

+ 48 - 0
app/src/main/res/drawable-v24/ic_thumbscroller.xml

@@ -0,0 +1,48 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:aapt="http://schemas.android.com/aapt"
+    android:width="40dp"
+    android:height="64dp"
+    android:viewportWidth="40"
+    android:viewportHeight="64">
+  <path
+      android:pathData="M31.995,64C34.7718,64 37.4296,63.6522 39.9985,62.9874C39.9985,62.9874 39.9985,1.009 39.9985,1.009C37.4405,0.3503 34.7587,0 31.995,0C14.3219,0 -0.005,14.3269 -0.005,32C-0.005,49.6731 14.3219,64 31.995,64Z"
+      android:strokeWidth="0"
+      android:strokeColor="#00000000">
+    <aapt:attr name="android:fillColor">
+      <gradient
+          android:type="radial"
+          android:centerX="32"
+          android:centerY="32"
+          android:gradientRadius="31">
+        <item
+            android:offset="0.0"
+            android:color="#FF000000"/>
+        <item
+            android:offset="0.6"
+            android:color="#FF888888"/>
+        <item
+            android:offset="1.0"
+            android:color="#00000000"/>
+      </gradient>
+    </aapt:attr>
+  </path>
+  <path
+      android:pathData="M33,58C35.4297,58 37.7507,57.6944 39.9985,57.1127C39.9985,57.1127 39.9985,2.8785 39.9985,2.8785C37.7603,2.3022 35.4182,2 33,2C17.536,2 5,14.536 5,30C5,45.464 17.536,58 33,58Z"
+      android:strokeWidth="0"
+      android:fillType="evenOdd"
+      android:fillColor="?attr/quickscroll_background"/>
+ <path
+      android:pathData="M34,35l-6,6l-6,-6z"
+      android:strokeLineJoin="round"
+      android:strokeWidth="0.5"
+      android:fillColor="?attr/textColorSecondary"
+      android:fillType="nonZero"
+      android:strokeColor="?attr/textColorSecondary"/>
+  <path
+      android:pathData="M34,26l-6,-6l-6,6z"
+      android:strokeLineJoin="round"
+      android:strokeWidth="0.5"
+      android:fillColor="?attr/textColorSecondary"
+      android:fillType="nonZero"
+      android:strokeColor="?attr/textColorSecondary"/>
+</vector>

+ 3 - 25
app/src/main/res/drawable/ic_thumbscroller.xml

@@ -1,34 +1,12 @@
 <vector xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:aapt="http://schemas.android.com/aapt"
     android:width="40dp"
     android:height="64dp"
     android:viewportWidth="40"
     android:viewportHeight="64">
-  <path
-      android:pathData="M31.995,64C34.7718,64 37.4296,63.6522 39.9985,62.9874C39.9985,62.9874 39.9985,1.009 39.9985,1.009C37.4405,0.3503 34.7587,0 31.995,0C14.3219,0 -0.005,14.3269 -0.005,32C-0.005,49.6731 14.3219,64 31.995,64Z"
-      android:strokeWidth="0"
-      android:strokeColor="#00000000">
-    <aapt:attr name="android:fillColor">
-      <gradient
-          android:type="radial"
-          android:centerX="32"
-          android:centerY="32"
-          android:gradientRadius="31">
-        <item
-            android:offset="0.0"
-            android:color="#FF000000"/>
-        <item
-            android:offset="0.6"
-            android:color="#FF888888"/>
-        <item
-            android:offset="1.0"
-            android:color="#00000000"/>
-      </gradient>
-    </aapt:attr>
-  </path>
-  <path
+<path
       android:pathData="M33,58C35.4297,58 37.7507,57.6944 39.9985,57.1127C39.9985,57.1127 39.9985,2.8785 39.9985,2.8785C37.7603,2.3022 35.4182,2 33,2C17.536,2 5,14.536 5,30C5,45.464 17.536,58 33,58Z"
-      android:strokeWidth="0"
+      android:strokeWidth="0.1"
+      android:strokeColor="?attr/textColorSecondary"
       android:fillType="evenOdd"
       android:fillColor="?attr/quickscroll_background"/>
  <path

+ 7 - 0
app/src/main/res/drawable/shape_rounded_corner_popup_bg_forced_light.xml

@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<shape xmlns:android="http://schemas.android.com/apk/res/android">
+	<solid android:color="@color/activity_background_secondary"/>
+	<stroke android:width="1dip" android:color="@android:color/transparent" />
+	<corners android:radius="8dip"/>
+	<padding android:left="0dip" android:top="0dip" android:right="0dip" android:bottom="0dip" />
+</shape>

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

@@ -7,7 +7,7 @@
 	android:paddingRight="@dimen/qrcode_min_margin"
 	android:paddingTop="@dimen/qrcode_min_margin"
 	android:paddingBottom="@dimen/qrcode_min_margin"
-	android:background="@drawable/shape_rounded_corner_popup_bg">
+	android:background="@drawable/shape_rounded_corner_popup_bg_forced_light">
 
 	<ImageView
 		android:id="@+id/image_view"

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

@@ -1192,12 +1192,18 @@ messaggi ogni 15 minuti.</string>
     <string name="set_backup_path">Definisci percorso backup</string>
     <string name="set_backup_path_intro">Scegli nello schermo seguente il folder dove salvare i backup.</string>
     <string name="discard_changes_title">Annulla modifiche</string>
+    <string name="group_join_request_message_info"><![CDATA[Scrivi in un breve messaggio all\'amministratore <b>%1$s</b> perché desideri aderire a <b>%2$s</b>.]]></string>
+    <string name="group_join_request">Richiesta di adesione</string>
+    <string name="group_join_request_for">Richiesta di adesione a %s</string>
+    <string name="group_link_default_name">Link sconosciuto</string>
     <string name="group_link_share">Condividi link gruppo</string>
     <string name="group_request_incoming_dialog_title">Richiesta di %s</string>
     <string name="accept">Accetta</string>
     <string name="reject">Rifiuta</string>
     <string name="group_request_already_sent"><![CDATA[Hai già inviato una richiesta di adesione a <b>%s</b> mediante questo link.]]></string>
     <string name="group_link_none">Il link non è stato creato</string>
+    <string name="group_request_confirm_send"><![CDATA[Stai inviando una richiesta di adesione a <b>%1$s</b> all\'amministratore <b>%2$s</b>. Se il presente link non è scaduto nel frattempo, sarai aggiunto al gruppo non appena il dispositivo dell\'amministratore sarà di nuovo reperibile.]]></string>
+    <string name="really_delete_outgoing_request">Vuoi davvero cancellare %d richiesta/e di adesione? Ricorda che la richiesta ancora aperta non sarà ritirata e che potrai aderire al gruppo anche più tardi.</string>
     <string name="really_delete_group_request_title">Vuoi davvero cancellare la richiesta gruppo?</string>
     <string name="sent_to">Inviato a: %s</string>
     <string name="sent_on">Inviato il: %s</string>
@@ -1205,22 +1211,70 @@ messaggi ogni 15 minuti.</string>
     <string name="group_response_accepted">La tua richiesta gruppo per %s è stata accettata.</string>
     <string name="group_response_full">La tua richiesta gruppo per %s è stata rifiutata perché il gruppo è già pieno.</string>
     <string name="group_response_rejected">La tua richiesta gruppo per %s è stata rifiutata.</string>
+    <string name="group_link_expiration_none">Illimitato</string>
+    <string name="group_request_hint">Ciao! Mi chiamo...</string>
+    <string name="group_request_send_title">Invia richiesta di adesione</string>
+    <string name="group_request_message_empty">Il messaggio all\'amministratore non deve essere vuoto!</string>
+    <string name="group_requests_all_title">Tutte le richieste di adesione</string>
+    <string name="group_requests_none_outgoing">Nessuna richiesta di adesione inviata. Le richieste possono essere inviate tramite link di gruppo.</string>
+    <string name="notification_channel_group_join_response">Notifica sulle risposte alle richieste di adesione</string>
     <string name="group_qr_code_title">Codice QR gruppo</string>
+    <string name="group_link_qr_desc"><![CDATA[Condividi questo link con i contatti che desideri invitare al gruppo <b>%s</b>.]]></string>
+    <string name="new_group_link_success">Aggiunto nuovo link</string>
+    <string name="link_administration_off_explain">Il presente link è già stato condiviso ed è pubblico. L\'amministrazione non può essere più modificata. Crea un nuovo link con l\'opzione desiderata.</string>
+    <string name="group_link_update_success">Link aggiornato con successo</string>
+    <string name="no_group_links">Nessun link di gruppo. Per creare un link di gruppo clicca su \"Crea link\" oppure attiva il link per default nella panoramica di gruppo.</string>
+    <string name="really_delete_group_link">Vuoi davvero cancellare %d link di gruppo? Una volta cancellato/i gli utenti non potranno più inviare delle richieste valide. Tuttavia le richieste ricevute possono ancora essere accettate o rifiutate.</string>
+    <string name="group_links_overview_title">Link di gruppo per %s</string>
     <string name="group_link_invalid">Scaduto</string>
+    <string name="group_link_valid">Valido</string>
+    <string name="open_group_requests_chips_title">Richieste di adesione aperte</string>
+    <string name="all_open_group_requests">Tutte le richieste di adesione</string>
+    <string name="no_incoming_group_requests">Nessuna richiesta di adesione ricevuta. Le richieste possono essere inviate tramite link di gruppo. Se siete amministratore potete amministrare i link alla voce dettagli gruppo.</string>
     <string name="received_on">Ricevuto il %s</string>
     <string name="group_request_state_full">Gruppo pieno</string>
     <string name="group_request_state_rejected">Respinta</string>
     <string name="group_request_state_expired">Scaduta</string>
     <string name="group_request_state_pending">In sospeso</string>
     <string name="group_request_state_accepted">Accettata</string>
+    <string name="open_group_requests_show">Mostra richieste di adesione</string>
+    <string name="open_group_requests_hide">Nascondi richieste di adesione</string>
     <string name="group_request_link_already_deleted">Link gruppo già cancellato</string>
     <string name="really_delete_incoming_request">Vuoi davvero cancellare %d richieste gruppo? Una volta cancellate non potrai più rispondere a queste richieste.</string>
+    <string name="incoming_group_request_no_message">Inviato mediante link aperto... Nessun messaggio</string>
+    <string name="group_request_received_through">Mediante link: %s</string>
+    <string name="reaccept">Accetta di nuovo</string>
+    <string name="group_link_edit_expiration_date">Modifica data di scadenza</string>
+    <string name="group_link_show_qr">Mostra codice QR</string>
+    <string name="group_link_rename">Rinomina link</string>
+    <string name="group_link_rename_tag">Nuovo nome</string>
     <string name="tap_here_for_more">Tocca qui per ulteriori informazioni</string>
     <string name="another_connection_instructions">Il server ha individuato due o più connessioni da dispositivi diversi con lo stesso Threema ID.\n\nNon è possibile usare lo stesso Threema ID su diversi dispositivi contemporaneamente. I nuovi messaggi sono trasmessi solo al dispositivo che ha avuto accesso per ultimo al server.\n\nSe stai passando a un altro dispositivo, disinstalla o disattiva %s sul dispositivo precedente e riavvia quello nuovo.</string>
     <string name="app_store_error_code">Codice errore app store: %d</string>
+    <string name="backup_restore_type"><![CDATA[Che tipo di backup desideri ripristinare? <br/><br/> <a href=%s>Scopri di più sui backup di Threema</a>]]></string>
+    <string name="data_backup_info">Ripristina tutto, comprese le chat</string>
+    <string name="new_to_threema">Sei nuovo/a su %s?</string>
+    <string name="back_to_threema">Sei tornato/a su %s?</string>
+    <string name="id_backup_info">Ripristina solo il tuo ID</string>
+    <string name="restore_your_id_contacts_and_groups">Ripristina ID, contatti e gruppi</string>
+    <string name="threema_safe_backup">Backup Threema Safe</string>
+    <string name="forgot_your_password"><![CDATA[<a href=%s>Hai dimenticato la password?</a>]]></string>
     <string name="download_failed">Download non riuscito. Codice errore: %d</string>
     <string name="edit_answer">Modifica risposta</string>
     <string name="share_media">Condividi con un\'altra app...</string>
+    <string name="group_link_administered">Amministrato</string>
+    <string name="group_link_open">Aperto/a</string>
+    <string name="group_link_share_message">Aderisci al mio gruppo Threema mediante il seguente link: %s</string>
+    <string name="group_link_bottom_sheet_title">Link di gruppo</string>
+    <string name="group_link_bottom_sheet_desc">Condividi questo link con i contatti che desideri invitare. Ecco le opzioni da considerare.</string>
+    <string name="group_link_bottom_sheet_link_title">Link</string>
+    <string name="group_link_properties_title">Proprietà del link</string>
+    <string name="group_link_property_administration_title">Conferma manuale</string>
+    <string name="group_link_property_administration_desc">Se è stato attivato riceverai delle richieste che potrai accettare o rifiutare nella chat di gruppo.</string>
+    <string name="group_link_property_expiration_title">Data di scadenza</string>
+    <string name="group_link_property_expiration_desc">Dopo questa data il link non sarà più valido.</string>
+    <string name="group_image">Immagine gruppo</string>
+    <string name="add_group_link">Aggiungere membri mediante link di gruppo</string>
     <string name="miui_battery_optimization">Per eseguire questo task, disabilita \"ottimizzazione batteria\" per %s. Xiaomi non consente alle app di richiamare direttamente questa schermata di impostazioni, quindi dovrai farlo manualmente nelle impostazioni del telefono. Se hai bisogno di assistenza per disabilitare l\'ottimizzazione della batteria, contatta il supporto Xiaomi.</string>
     <string name="forward_captions">Includi didascalie</string>
     <string name="importing_files_failed">Impossibile importare il file di backup. Assicurati che ci sia spazio sufficiente nella memoria interna.</string>
@@ -1236,4 +1290,30 @@ messaggi ogni 15 minuti.</string>
     <string name="prefs_title_reset_receipts">Resettare impostazioni personali</string>
     <string name="prefs_sum_reset_receipts">Ripristinare le impostazioni relative ai contatti per la conferma di lettura e l\'indicatore di scrittura ai valori predefiniti</string>
     <string name="reset_successful">Impostazioni predefinite reimpostate con successo</string>
+    <string name="group_link_add">Crea link</string>
+    <string name="group_requests_show_menu_title">Visualizza richieste di adesione aperte</string>
+    <string name="group_request_show_all">Visualizza tutte le richieste di adesione</string>
+    <string name="group_links_manage_menu">Amministra link di gruppo</string>
+    <string name="group_request_sent_menu">Richiesta di adesione inviata</string>
+    <string name="really_delete_group_link_title">Cancella link di gruppo</string>
+    <string name="qr_scan_result_dialog_title">Risultato scansione QR</string>
+    <string name="scan_failure_dialog_title">Scansione non riuscita</string>
+    <string name="no_threema_qr_info">Questo non è un codice QR Threema, il contenuto decodificato è:</string>
+    <string name="default_group_link">Link di gruppo di default</string>
+    <string name="default_link_name">Link di default</string>
+    <string name="reset_default_group_link">Resettare link di gruppo</string>
+    <string name="resend">Invia di nuovo</string>
+    <string name="group_request_already_sent_title">Richiesta di adesione già inviata</string>
+    <string name="qr_scanner_id_hint">Scansiona il codice QR di un altro contatto. Puoi visualizzare il codice QR cliccando sull\'avatar in alto a destra sulla schermata Home.</string>
+    <string name="enable_storage_access_for_media">Attiva l\'accesso alla memoria per visualizzare immagini e video sul tuo dispositivo</string>
+    <string name="take_me_there">Attiva ora...</string>
+    <string name="notification_channel_group_join_request">Notifiche per le richieste di adesione</string>
+    <string name="reset_default_group_link_title">Resettare info</string>
+    <string name="reset_default_group_link_desc">Il link appena condiviso non è più valido e le richieste inviate tramite il vecchio link saranno automaticamente rifiutate.</string>
+    <string name="show_public_key">Visualizza chiave pubblica</string>
+    <string name="public_key_for">Chiave pubblica di %s</string>
+    <string name="no_votes_yet">Finora non sono stati espressi voti.</string>
+    <string name="permission_contacts_sync_required">Sei pregato di consentire l\'accesso ai contatti per sincronizzarli.</string>
+    <string name="not_voted_user_list">Questi partecipanti non hanno votato: %s</string>
+    <string name="invalid_onprem_id">L\'ID Threema OnPrem non è valida</string>
 </resources>

+ 0 - 5
app/src/main/res/xml/app_shortcuts.xml

@@ -27,11 +27,6 @@
 		<categories android:name="android.shortcut.conversation"/>
 	</shortcut>
 	<share-target android:targetClass="ch.threema.app.activities.RecipientListActivity">
-		<data android:mimeType="text/plain" />
-		<data android:mimeType="image/*"/>
-		<data android:mimeType="video/*"/>
-		<data android:mimeType="audio/*"/>
-		<data android:mimeType="application/*"/>
 		<data android:mimeType="*/*"/>
 		<category android:name="ch.threema.app.category.DYNAMIC_SHORTCUT_SHARE_TARGET" />
 	</share-target>

+ 5 - 0
app/src/onprem/res/xml/app_shortcuts.xml

@@ -26,4 +26,9 @@
 			android:targetPackage="ch.threema.app.onprem"/>
 		<categories android:name="android.shortcut.conversation"/>
 	</shortcut>
+	<share-target
+		android:targetClass="ch.threema.app.activities.RecipientListActivity">
+		<data android:mimeType="*/*"/>
+		<category android:name="ch.threema.app.onprem.category.DYNAMIC_SHORTCUT_SHARE_TARGET" />
+	</share-target>
 </shortcuts>

+ 4 - 0
app/src/red/res/xml/app_shortcuts.xml

@@ -26,4 +26,8 @@
 			android:targetPackage="ch.threema.app.red"/>
 		<categories android:name="android.shortcut.conversation"/>
 	</shortcut>
+	<share-target android:targetClass="ch.threema.app.activities.RecipientListActivity">
+		<data android:mimeType="*/*"/>
+		<category android:name="ch.threema.app.red.category.DYNAMIC_SHORTCUT_SHARE_TARGET" />
+	</share-target>
 </shortcuts>

+ 4 - 0
app/src/store_google_work/res/xml/app_shortcuts.xml

@@ -26,4 +26,8 @@
 			android:targetPackage="ch.threema.app.work"/>
 		<categories android:name="android.shortcut.conversation"/>
 	</shortcut>
+	<share-target android:targetClass="ch.threema.app.activities.RecipientListActivity">
+		<data android:mimeType="*/*"/>
+		<category android:name="ch.threema.app.work.category.DYNAMIC_SHORTCUT_SHARE_TARGET" />
+	</share-target>
 </shortcuts>