Improve notification channel consistency checks with Android Conversations.

This commit is contained in:
Cody Henthorne 2021-06-07 15:58:39 -04:00 committed by GitHub
parent 36443c59f9
commit bece58d939
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 108 additions and 15 deletions

View file

@ -430,7 +430,7 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper implements SignalDatab
}
}
String channelId = NotificationChannels.createChannelFor(context, "contact_" + address + "_" + System.currentTimeMillis(), displayName, messageSoundUri, vibrateEnabled);
String channelId = NotificationChannels.createChannelFor(context, "contact_" + address + "_" + System.currentTimeMillis(), displayName, messageSoundUri, vibrateEnabled, null);
ContentValues values = new ContentValues(1);
values.put("notification_channel", channelId);

View file

@ -25,6 +25,7 @@ import org.thoughtcrime.securesms.groups.MembershipNotSuitableForV2Exception;
import org.thoughtcrime.securesms.groups.SelectionLimits;
import org.thoughtcrime.securesms.groups.ui.GroupChangeErrorCallback;
import org.thoughtcrime.securesms.groups.ui.GroupChangeFailureReason;
import org.thoughtcrime.securesms.notifications.NotificationChannels;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientId;
import org.thoughtcrime.securesms.recipients.RecipientUtil;
@ -148,6 +149,15 @@ final class ManageGroupRepository {
});
}
@WorkerThread
boolean hasCustomNotifications(Recipient recipient) {
if (recipient.getNotificationChannel() != null || !NotificationChannels.supported()) {
return true;
}
return NotificationChannels.updateWithShortcutBasedChannel(context, recipient);
}
static final class GroupStateResult {
private final long threadId;

View file

@ -36,7 +36,6 @@ import org.thoughtcrime.securesms.groups.ui.GroupMemberEntry;
import org.thoughtcrime.securesms.groups.ui.addmembers.AddMembersActivity;
import org.thoughtcrime.securesms.groups.ui.managegroup.dialogs.GroupMentionSettingDialog;
import org.thoughtcrime.securesms.groups.v2.GroupLinkUrlAndStatus;
import org.thoughtcrime.securesms.notifications.NotificationChannels;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientId;
import org.thoughtcrime.securesms.recipients.RecipientUtil;
@ -120,8 +119,7 @@ public class ManageGroupViewModel extends ViewModel {
this.canAddMembers = liveGroup.selfCanAddMembers();
this.muteState = Transformations.map(this.groupRecipient,
recipient -> new MuteState(recipient.getMuteUntil(), recipient.isMuted()));
this.hasCustomNotifications = Transformations.map(this.groupRecipient,
recipient -> recipient.getNotificationChannel() != null || !NotificationChannels.supported());
this.hasCustomNotifications = LiveDataUtil.mapAsync(this.groupRecipient, manageGroupRepository::hasCustomNotifications);
this.canLeaveGroup = liveGroup.isActive();
this.canBlockGroup = Transformations.map(this.groupRecipient, recipient -> RecipientUtil.isBlockable(recipient) && !recipient.isBlocked());
this.canUnblockGroup = Transformations.map(this.groupRecipient, Recipient::isBlocked);

View file

@ -176,10 +176,15 @@ public final class NotificationCancellationHelper {
return true;
}
RecipientId recipientId = RecipientId.from(notification.getShortcutId());
Long threadId = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(recipientId);
RecipientId recipientId = ConversationUtil.getRecipientId(notification.getShortcutId());
if (recipientId == null) {
Log.d(TAG, "isCancellable: Unable to get recipient from shortcut id");
return true;
}
Long threadId = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(recipientId);
long focusedThreadId = ApplicationDependencies.getMessageNotifier().getVisibleThread();
if (Objects.equals(threadId, focusedThreadId)) {
Log.d(TAG, "isCancellable: user entered full screen thread.");
return true;

View file

@ -31,6 +31,8 @@ import org.thoughtcrime.securesms.database.RecipientDatabase;
import org.thoughtcrime.securesms.database.RecipientDatabase.VibrateState;
import org.thoughtcrime.securesms.keyvalue.SignalStore;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientId;
import org.thoughtcrime.securesms.util.ConversationUtil;
import org.thoughtcrime.securesms.util.ServiceUtil;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
@ -38,8 +40,12 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import static org.thoughtcrime.securesms.util.ConversationUtil.CONVERSATION_SUPPORT_VERSION;
public class NotificationChannels {
private static final String TAG = Log.tag(NotificationChannels.class);
@ -165,7 +171,7 @@ public class NotificationChannels {
Uri messageRingtone = recipient.getMessageRingtone() != null ? recipient.getMessageRingtone() : getMessageRingtone(context);
String displayName = recipient.getDisplayName(context);
return createChannelFor(context, generateChannelIdFor(recipient), displayName, messageRingtone, vibrationEnabled);
return createChannelFor(context, generateChannelIdFor(recipient), displayName, messageRingtone, vibrationEnabled, ConversationUtil.getShortcutId(recipient));
}
/**
@ -175,7 +181,8 @@ public class NotificationChannels {
@NonNull String channelId,
@NonNull String displayName,
@Nullable Uri messageSound,
boolean vibrationEnabled)
boolean vibrationEnabled,
@Nullable String shortcutId)
{
if (!supported()) {
return null;
@ -193,6 +200,10 @@ public class NotificationChannels {
.build());
}
if (Build.VERSION.SDK_INT >= CONVERSATION_SUPPORT_VERSION && shortcutId != null) {
channel.setConversationId(getMessagesChannel(context), shortcutId);
}
NotificationManager notificationManager = ServiceUtil.getNotificationManager(context);
notificationManager.createNotificationChannel(channel);
@ -465,6 +476,32 @@ public class NotificationChannels {
}
}
/**
* Attempt to update a recipient with shortcut based notification channel if the system made one for us and we don't
* have a channel set yet.
*
* @return true if a shortcut based notification channel was found and then associated with the recipient, false otherwise
*/
@WorkerThread
public static boolean updateWithShortcutBasedChannel(@NonNull Context context, @NonNull Recipient recipient) {
if (Build.VERSION.SDK_INT >= CONVERSATION_SUPPORT_VERSION && TextUtils.isEmpty(recipient.getNotificationChannel())) {
String shortcutId = ConversationUtil.getShortcutId(recipient);
Optional<NotificationChannel> channel = ServiceUtil.getNotificationManager(context)
.getNotificationChannels()
.stream()
.filter(c -> Objects.equals(shortcutId, c.getConversationId()))
.findFirst();
if (channel.isPresent()) {
Log.i(TAG, "Conversation channel created outside of app, while running. Update " + recipient.getId() + " to use '" + channel.get().getId() + "'");
DatabaseFactory.getRecipientDatabase(context).setNotificationChannel(recipient.getId(), channel.get().getId());
return true;
}
}
return false;
}
/**
* Updates the name of an existing channel to match the recipient's current name. Will have no
* effect if the recipient doesn't have an existing valid channel.
@ -516,8 +553,24 @@ public class NotificationChannels {
if (existingChannel.getId().startsWith(CONTACT_PREFIX) && !customChannelIds.contains(existingChannel.getId())) {
Log.i(TAG, "Consistency: Deleting channel '"+ existingChannel.getId() + "' because the DB has no record of it.");
notificationManager.deleteNotificationChannel(existingChannel.getId());
} else if (existingChannel.getId().startsWith(MESSAGES_PREFIX) &&
Build.VERSION.SDK_INT >= CONVERSATION_SUPPORT_VERSION &&
existingChannel.getConversationId() != null)
{
if (customChannelIds.contains(existingChannel.getId())) {
continue;
}
RecipientId id = ConversationUtil.getRecipientId(existingChannel.getConversationId());
if (id != null) {
Log.i(TAG, "Consistency: Conversation channel created outside of app, update " + id + " to use '" + existingChannel.getId() + "'");
db.setNotificationChannel(id, existingChannel.getId());
} else {
Log.i(TAG, "Consistency: Conversation channel created outside of app with no matching recipient, deleting channel '" + existingChannel.getId() + "'");
notificationManager.deleteNotificationChannel(existingChannel.getId());
}
} else if (existingChannel.getId().startsWith(MESSAGES_PREFIX) && !existingChannel.getId().equals(getMessagesChannel(context))) {
Log.i(TAG, "Consistency: Deleting channel '"+ existingChannel.getId() + "' because it's out of date.");
Log.i(TAG, "Consistency: Deleting channel '" + existingChannel.getId() + "' because it's out of date.");
notificationManager.deleteNotificationChannel(existingChannel.getId());
}
}
@ -617,6 +670,10 @@ public class NotificationChannels {
copy.setLightColor(original.getLightColor());
copy.enableLights(original.shouldShowLights());
if (Build.VERSION.SDK_INT >= CONVERSATION_SUPPORT_VERSION && original.getConversationId() != null) {
copy.setConversationId(original.getParentChannelId(), original.getConversationId());
}
return copy;
}

View file

@ -10,15 +10,12 @@ import com.annimon.stream.Stream;
import org.signal.core.util.concurrent.SignalExecutors;
import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.color.MaterialColor;
import org.thoughtcrime.securesms.color.MaterialColors;
import org.thoughtcrime.securesms.contacts.sync.DirectoryHelper;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.GroupDatabase;
import org.thoughtcrime.securesms.database.IdentityDatabase;
import org.thoughtcrime.securesms.database.ThreadDatabase;
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
import org.thoughtcrime.securesms.jobs.MultiDeviceContactUpdateJob;
import org.thoughtcrime.securesms.notifications.NotificationChannels;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientId;
@ -106,4 +103,13 @@ final class ManageRecipientRepository {
void getActiveGroupCount(@NonNull Consumer<Integer> onComplete) {
SignalExecutors.BOUNDED.execute(() -> onComplete.accept(DatabaseFactory.getGroupDatabase(context).getActiveGroupCount()));
}
@WorkerThread
boolean hasCustomNotifications(Recipient recipient) {
if (recipient.getNotificationChannel() != null || !NotificationChannels.supported()) {
return true;
}
return NotificationChannels.updateWithShortcutBasedChannel(context, recipient);
}
}

View file

@ -1,6 +1,7 @@
package org.thoughtcrime.securesms.recipients.ui.managerecipient;
import android.app.Activity;
import android.app.NotificationChannel;
import android.content.Context;
import android.database.Cursor;
@ -78,7 +79,7 @@ public final class ManageRecipientViewModel extends ViewModel {
this.groupListCollapseState = new DefaultValueLiveData<>(CollapseState.COLLAPSED);
this.disappearingMessageTimer = Transformations.map(this.recipient, r -> ExpirationUtil.getExpirationDisplayValue(context, r.getExpireMessages()));
this.muteState = Transformations.map(this.recipient, r -> new MuteState(r.getMuteUntil(), r.isMuted()));
this.hasCustomNotifications = Transformations.map(this.recipient, r -> r.getNotificationChannel() != null || !NotificationChannels.supported());
this.hasCustomNotifications = LiveDataUtil.mapAsync(this.recipient, manageRecipientRepository::hasCustomNotifications);
this.canBlock = Transformations.map(this.recipient, r -> RecipientUtil.isBlockable(r) && !r.isBlocked());
this.canUnblock = Transformations.map(this.recipient, Recipient::isBlocked);
this.internalDetails = Transformations.map(this.recipient, this::populateInternalDetails);

View file

@ -8,6 +8,7 @@ import android.content.pm.ShortcutManager;
import android.os.Build;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.annotation.WorkerThread;
@ -19,7 +20,6 @@ import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.conversation.ConversationIntents;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.GroupDatabase;
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
import org.thoughtcrime.securesms.groups.GroupId;
import org.thoughtcrime.securesms.jobs.ConversationShortcutUpdateJob;
import org.thoughtcrime.securesms.notifications.NotificationChannels;
@ -132,6 +132,22 @@ public final class ConversationUtil {
return getShortcutId(recipient.getId());
}
/**
* Extract the recipient id from the provided shortcutId.
*/
public static @Nullable RecipientId getRecipientId(@Nullable String shortcutId) {
if (shortcutId == null) {
return null;
}
try {
return RecipientId.from(shortcutId);
} catch (Throwable t) {
Log.d(TAG, "Unable to parse recipientId from shortcutId", t);
return null;
}
}
@RequiresApi(CONVERSATION_SUPPORT_VERSION)
public static int getMaxShortcuts(@NonNull Context context) {
ShortcutManager shortcutManager = ServiceUtil.getShortcutManager(context);