mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-04-24 02:39:55 +01:00
Add support for Android 11 Conversation Bubbles.
This commit is contained in:
@@ -120,6 +120,15 @@ public final class AvatarUtil {
|
||||
}
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
public static IconCompat getIconCompatForShortcut(@NonNull Context context, @NonNull Recipient recipient) {
|
||||
try {
|
||||
return IconCompat.createWithAdaptiveBitmap(getShortcutInfoBitmap(context, recipient));
|
||||
} catch (ExecutionException | InterruptedException e) {
|
||||
return IconCompat.createWithAdaptiveBitmap(getFallbackForShortcut(context, recipient));
|
||||
}
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
public static Bitmap getBitmapForNotification(@NonNull Context context, @NonNull Recipient recipient) {
|
||||
try {
|
||||
|
||||
@@ -0,0 +1,117 @@
|
||||
package org.thoughtcrime.securesms.util;
|
||||
|
||||
import android.app.Notification;
|
||||
import android.app.NotificationChannel;
|
||||
import android.app.NotificationManager;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.service.notification.StatusBarNotification;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.RequiresApi;
|
||||
import androidx.annotation.WorkerThread;
|
||||
|
||||
import com.annimon.stream.Stream;
|
||||
|
||||
import org.thoughtcrime.securesms.conversation.BubbleConversationActivity;
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||
import org.thoughtcrime.securesms.logging.Log;
|
||||
import org.thoughtcrime.securesms.notifications.DefaultMessageNotifier;
|
||||
import org.thoughtcrime.securesms.notifications.NotificationIds;
|
||||
import org.thoughtcrime.securesms.notifications.SingleRecipientNotificationBuilder;
|
||||
import org.thoughtcrime.securesms.preferences.widgets.NotificationPrivacyPreference;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||
import org.thoughtcrime.securesms.util.concurrent.SignalExecutors;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
import static org.thoughtcrime.securesms.util.ConversationUtil.CONVERSATION_SUPPORT_VERSION;
|
||||
|
||||
/**
|
||||
* Bubble-related utility methods.
|
||||
*/
|
||||
public final class BubbleUtil {
|
||||
|
||||
private static final String TAG = Log.tag(BubbleUtil.class);
|
||||
|
||||
private BubbleUtil() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether we are allowed to create a bubble for the given recipient.
|
||||
*
|
||||
* In order to Bubble, a recipient must have a thread, be unblocked, and the user must not have
|
||||
* notification privacy settings enabled. Furthermore, we check the Notifications system to verify
|
||||
* that bubbles are allowed in the first place.
|
||||
*/
|
||||
@RequiresApi(CONVERSATION_SUPPORT_VERSION)
|
||||
@WorkerThread
|
||||
public static boolean canBubble(@NonNull Context context, @NonNull RecipientId recipientId, @Nullable Long threadId) {
|
||||
if (threadId == null) {
|
||||
Log.i(TAG, "Cannot bubble recipient without thread");
|
||||
return false;
|
||||
}
|
||||
|
||||
NotificationPrivacyPreference privacyPreference = TextSecurePreferences.getNotificationPrivacy(context);
|
||||
if (!privacyPreference.isDisplayMessage()) {
|
||||
Log.i(TAG, "Bubbles are not available when notification privacy settings are enabled.");
|
||||
return false;
|
||||
}
|
||||
|
||||
Recipient recipient = Recipient.resolved(recipientId);
|
||||
if (recipient.isBlocked()) {
|
||||
Log.i(TAG, "Cannot bubble blocked recipient");
|
||||
return false;
|
||||
}
|
||||
|
||||
NotificationManager notificationManager = ServiceUtil.getNotificationManager(context);
|
||||
NotificationChannel conversationChannel = notificationManager.getNotificationChannel(ConversationUtil.getChannelId(context, recipient),
|
||||
ConversationUtil.getShortcutId(recipientId));
|
||||
|
||||
return notificationManager.areBubblesAllowed() || (conversationChannel != null && conversationChannel.canBubble());
|
||||
}
|
||||
|
||||
/**
|
||||
* Display a bubble for a given recipient's thread.
|
||||
*/
|
||||
public static void displayAsBubble(@NonNull Context context, @NonNull RecipientId recipientId, long threadId) {
|
||||
if (Build.VERSION.SDK_INT >= CONVERSATION_SUPPORT_VERSION) {
|
||||
SignalExecutors.BOUNDED.execute(() -> {
|
||||
if (canBubble(context, recipientId, threadId)) {
|
||||
NotificationManager notificationManager = ServiceUtil.getNotificationManager(context);
|
||||
StatusBarNotification[] notifications = notificationManager.getActiveNotifications();
|
||||
int threadNotificationId = NotificationIds.getNotificationIdForThread(threadId);
|
||||
Notification activeThreadNotification = Stream.of(notifications)
|
||||
.filter(n -> n.getId() == threadNotificationId)
|
||||
.findFirst()
|
||||
.map(StatusBarNotification::getNotification)
|
||||
.orElse(null);
|
||||
|
||||
if (activeThreadNotification != null && activeThreadNotification.deleteIntent != null) {
|
||||
ApplicationDependencies.getMessageNotifier().updateNotification(context, threadId, BubbleState.SHOWN);
|
||||
} else {
|
||||
Recipient recipient = Recipient.resolved(recipientId);
|
||||
SingleRecipientNotificationBuilder builder = new SingleRecipientNotificationBuilder(context, TextSecurePreferences.getNotificationPrivacy(context));
|
||||
|
||||
builder.addMessageBody(recipient, recipient, "", System.currentTimeMillis(), null);
|
||||
builder.setThread(recipient);
|
||||
builder.setDefaultBubbleState(BubbleState.SHOWN);
|
||||
builder.setGroup(DefaultMessageNotifier.NOTIFICATION_GROUP);
|
||||
|
||||
Log.d(TAG, "Posting Notification for requested bubble");
|
||||
notificationManager.notify(NotificationIds.getNotificationIdForThread(threadId), builder.build());
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public enum BubbleState {
|
||||
SHOWN,
|
||||
HIDDEN
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,15 @@
|
||||
package org.thoughtcrime.securesms.util;
|
||||
|
||||
import android.app.Notification;
|
||||
import android.app.NotificationChannel;
|
||||
import android.app.NotificationManager;
|
||||
import android.app.Person;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.pm.ShortcutInfo;
|
||||
import android.content.pm.ShortcutManager;
|
||||
import android.os.Build;
|
||||
import android.service.notification.StatusBarNotification;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.RequiresApi;
|
||||
@@ -18,7 +22,11 @@ import org.thoughtcrime.securesms.conversation.ConversationActivity;
|
||||
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.notifications.NotificationChannels;
|
||||
import org.thoughtcrime.securesms.notifications.NotificationIds;
|
||||
import org.thoughtcrime.securesms.notifications.SingleRecipientNotificationBuilder;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||
import org.thoughtcrime.securesms.util.concurrent.SignalExecutors;
|
||||
@@ -33,10 +41,21 @@ import java.util.Set;
|
||||
*/
|
||||
public final class ConversationUtil {
|
||||
|
||||
public static final int CONVERSATION_SUPPORT_VERSION = 30;
|
||||
public static final int CONVERSATION_SUPPORT_VERSION = 30;
|
||||
|
||||
private ConversationUtil() {}
|
||||
|
||||
|
||||
/**
|
||||
* @return The stringified channel id for a given Recipient
|
||||
*/
|
||||
@WorkerThread
|
||||
public static @NonNull String getChannelId(@NonNull Context context, @NonNull Recipient recipient) {
|
||||
Recipient resolved = recipient.resolve();
|
||||
|
||||
return resolved.getNotificationChannel() != null ? resolved.getNotificationChannel() : NotificationChannels.getMessagesChannel(context);
|
||||
}
|
||||
|
||||
/**
|
||||
* Pushes a new dynamic shortcut for the given recipient and updates the ranks of all current
|
||||
* shortcuts.
|
||||
@@ -130,7 +149,7 @@ public final class ConversationUtil {
|
||||
ShortcutManager shortcutManager = ServiceUtil.getShortcutManager(context);
|
||||
List<ShortcutInfo> currentShortcuts = shortcutManager.getDynamicShortcuts();
|
||||
|
||||
if (Util.isEmpty(currentShortcuts)) {
|
||||
if (Util.hasItems(currentShortcuts)) {
|
||||
for (ShortcutInfo shortcutInfo : currentShortcuts) {
|
||||
RecipientId recipientId = RecipientId.from(shortcutInfo.getId());
|
||||
Recipient resolved = Recipient.resolved(recipientId);
|
||||
|
||||
Reference in New Issue
Block a user