mirror of
https://github.com/signalapp/Signal-Android.git
synced 2025-12-23 12:38:33 +00:00
Ensure lower api levels do not try to use Uri based IconCompat.
This commit is contained in:
@@ -274,7 +274,7 @@ sealed class NotificationBuilder(protected val context: Context) {
|
||||
val self: PersonCompat = PersonCompat.Builder()
|
||||
.setBot(false)
|
||||
.setName(if (includeShortcut) Recipient.self().getDisplayName(context) else context.getString(R.string.SingleRecipientNotificationBuilder_you))
|
||||
.setIcon(AvatarUtil.getIconWithUriForNotification(Recipient.self().id))
|
||||
.setIcon(AvatarUtil.getIconCompat(context, Recipient.self()))
|
||||
.setKey(ConversationUtil.getShortcutId(Recipient.self().id))
|
||||
.build()
|
||||
|
||||
@@ -290,7 +290,7 @@ sealed class NotificationBuilder(protected val context: Context) {
|
||||
.setBot(false)
|
||||
.setName(notificationItem.getPersonName(context))
|
||||
.setUri(notificationItem.getPersonUri())
|
||||
.setIcon(notificationItem.getPersonIcon())
|
||||
.setIcon(notificationItem.getPersonIcon(context))
|
||||
|
||||
if (includeShortcut) {
|
||||
personBuilder.setKey(ConversationUtil.getShortcutId(notificationItem.authorRecipient))
|
||||
@@ -360,7 +360,7 @@ sealed class NotificationBuilder(protected val context: Context) {
|
||||
)
|
||||
|
||||
if (intent != null) {
|
||||
val bubbleMetadata = NotificationCompat.BubbleMetadata.Builder(intent, AvatarUtil.getIconCompatForShortcut(context, conversation.recipient))
|
||||
val bubbleMetadata = NotificationCompat.BubbleMetadata.Builder(intent, AvatarUtil.getIconCompat(context, conversation.recipient))
|
||||
.setAutoExpandBubble(bubbleState === BubbleUtil.BubbleState.SHOWN)
|
||||
.setDesiredHeight(600)
|
||||
.setSuppressNotification(bubbleState === BubbleUtil.BubbleState.SHOWN)
|
||||
|
||||
@@ -123,9 +123,9 @@ sealed class NotificationItem(val threadRecipient: Recipient, protected val reco
|
||||
}
|
||||
}
|
||||
|
||||
fun getPersonIcon(): IconCompat? {
|
||||
fun getPersonIcon(context: Context): IconCompat? {
|
||||
return if (SignalStore.settings().messageNotificationsPrivacy.isDisplayContact) {
|
||||
AvatarUtil.getIconWithUriForNotification(authorRecipient.id)
|
||||
AvatarUtil.getIconCompat(context, authorRecipient)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import org.signal.core.util.ThreadUtil;
|
||||
import org.signal.core.util.concurrent.SignalExecutors;
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||
import org.thoughtcrime.securesms.jobs.ForegroundServiceUtil;
|
||||
@@ -33,8 +34,14 @@ import org.thoughtcrime.securesms.webrtc.locks.LockManager;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
|
||||
import io.reactivex.rxjava3.core.Single;
|
||||
import io.reactivex.rxjava3.disposables.Disposable;
|
||||
import io.reactivex.rxjava3.schedulers.Schedulers;
|
||||
|
||||
/**
|
||||
* Provide a foreground service for {@link SignalCallManager} to leverage to run in the background when necessary. Also
|
||||
* provides devices listeners needed for during a call (i.e., bluetooth, power button).
|
||||
@@ -62,6 +69,7 @@ public final class WebRtcCallService extends Service implements SignalAudioManag
|
||||
private static final long FOREGROUND_SERVICE_TIMEOUT = TimeUnit.SECONDS.toMillis(10);
|
||||
|
||||
private final WebSocketKeepAliveTask webSocketKeepAliveTask = new WebSocketKeepAliveTask();
|
||||
private final Executor singleThreadExecutor = SignalExecutors.newCachedSingleThreadExecutor("signal-webrtc-in-call", ThreadUtil.PRIORITY_IMPORTANT_BACKGROUND_THREAD);
|
||||
|
||||
private SignalCallManager callManager;
|
||||
|
||||
@@ -72,6 +80,8 @@ public final class WebRtcCallService extends Service implements SignalAudioManag
|
||||
private SignalAudioManager signalAudioManager;
|
||||
private int lastNotificationId;
|
||||
private Notification lastNotification;
|
||||
private long lastNotificationRequestTime;
|
||||
private Disposable lastNotificationDisposable = Disposable.disposed();
|
||||
private boolean stopping = false;
|
||||
|
||||
public static void update(@NonNull Context context, int type, @NonNull RecipientId recipientId, boolean isVideoCall) {
|
||||
@@ -219,6 +229,7 @@ public final class WebRtcCallService extends Service implements SignalAudioManag
|
||||
private void setCallNotification() {
|
||||
setCallNotification(false);
|
||||
}
|
||||
|
||||
private void setCallNotification(boolean stopping) {
|
||||
if (!stopping && lastNotificationId != INVALID_NOTIFICATION_ID) {
|
||||
startForegroundCompat(lastNotificationId, lastNotification);
|
||||
@@ -230,10 +241,28 @@ public final class WebRtcCallService extends Service implements SignalAudioManag
|
||||
}
|
||||
|
||||
public void setCallInProgressNotification(int type, @NonNull RecipientId id, boolean isVideoCall) {
|
||||
lastNotificationDisposable.dispose();
|
||||
|
||||
boolean requiresAsyncNotificationLoad = Build.VERSION.SDK_INT <= 29;
|
||||
|
||||
lastNotificationId = CallNotificationBuilder.getNotificationId(type);
|
||||
lastNotification = CallNotificationBuilder.getCallInProgressNotification(this, type, Recipient.resolved(id), isVideoCall);
|
||||
lastNotification = CallNotificationBuilder.getCallInProgressNotification(this, type, Recipient.resolved(id), isVideoCall, requiresAsyncNotificationLoad);
|
||||
|
||||
startForegroundCompat(lastNotificationId, lastNotification);
|
||||
|
||||
if (requiresAsyncNotificationLoad) {
|
||||
final long requestTime = System.currentTimeMillis();
|
||||
lastNotificationRequestTime = requestTime;
|
||||
lastNotificationDisposable = Single
|
||||
.fromCallable(() -> CallNotificationBuilder.getCallInProgressNotification(this, type, Recipient.resolved(id), isVideoCall, false))
|
||||
.subscribeOn(Schedulers.from(singleThreadExecutor))
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.filter(unused -> requestTime == lastNotificationRequestTime && !stopping)
|
||||
.subscribe(notification -> {
|
||||
lastNotification = notification;
|
||||
startForegroundCompat(lastNotificationId, lastNotification);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private synchronized void startForegroundCompat(int notificationId, Notification notification) {
|
||||
|
||||
@@ -22,7 +22,7 @@ import com.bumptech.glide.request.target.CustomTarget;
|
||||
import com.bumptech.glide.request.target.CustomViewTarget;
|
||||
import com.bumptech.glide.request.transition.Transition;
|
||||
|
||||
import org.signal.core.util.concurrent.SignalExecutors;
|
||||
import org.signal.core.util.ThreadUtil;
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.contacts.avatars.ContactPhoto;
|
||||
@@ -30,10 +30,11 @@ import org.thoughtcrime.securesms.contacts.avatars.GeneratedContactPhoto;
|
||||
import org.thoughtcrime.securesms.contacts.avatars.ProfileContactPhoto;
|
||||
import org.thoughtcrime.securesms.mms.GlideApp;
|
||||
import org.thoughtcrime.securesms.mms.GlideRequest;
|
||||
import org.thoughtcrime.securesms.mms.GlideRequests;
|
||||
import org.thoughtcrime.securesms.providers.AvatarProvider;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
@@ -110,16 +111,11 @@ public final class AvatarUtil {
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
public static IconCompat getIconWithUriForNotification(@NonNull RecipientId recipientId) {
|
||||
return IconCompat.createWithContentUri(AvatarProvider.getContentUri(recipientId));
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
public static @NonNull IconCompat getIconCompatForShortcut(@NonNull Context context, @NonNull Recipient recipient) {
|
||||
public static @NonNull IconCompat getIconCompat(@NonNull Context context, @NonNull Recipient recipient) {
|
||||
if (Build.VERSION.SDK_INT > 29) {
|
||||
return getIconWithUriForNotification(recipient.getId());
|
||||
return IconCompat.createWithContentUri(AvatarProvider.getContentUri(recipient.getId()));
|
||||
} else {
|
||||
return IconCompat.createWithBitmap(getBitmapForNotification(context, recipient));
|
||||
return IconCompat.createWithBitmap(getBitmapForNotification(context, recipient, DrawableUtil.SHORTCUT_INFO_WRAPPED_SIZE));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -129,17 +125,19 @@ public final class AvatarUtil {
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
public static Bitmap getBitmapForNotification(@NonNull Context context, @NonNull Recipient recipient, int size) {
|
||||
public static @NonNull Bitmap getBitmapForNotification(@NonNull Context context, @NonNull Recipient recipient, int size) {
|
||||
ThreadUtil.assertNotMainThread();
|
||||
|
||||
try {
|
||||
AvatarTarget avatarTarget = new AvatarTarget(size);
|
||||
GlideRequests glideRequests = GlideApp.with(context);
|
||||
|
||||
SignalExecutors.BOUNDED_IO.submit(() -> {
|
||||
requestCircle(GlideApp.with(context).asBitmap(), context, recipient, size).into(avatarTarget);
|
||||
});
|
||||
requestCircle(glideRequests.asBitmap(), context, recipient, size).into(avatarTarget);
|
||||
|
||||
return avatarTarget.await();
|
||||
Bitmap bitmap = avatarTarget.await();
|
||||
return Objects.requireNonNullElseGet(bitmap, () -> DrawableUtil.toBitmap(getFallback(context, recipient, size), size, size));
|
||||
} catch (InterruptedException e) {
|
||||
return null;
|
||||
return DrawableUtil.toBitmap(getFallback(context, recipient, size), size, size);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -156,6 +154,7 @@ public final class AvatarUtil {
|
||||
}
|
||||
|
||||
private static <T> GlideRequest<T> request(@NonNull GlideRequest<T> glideRequest, @NonNull Context context, @NonNull Recipient recipient, boolean loadSelf, int targetSize, @Nullable BitmapTransformation transformation) {
|
||||
|
||||
final ContactPhoto photo;
|
||||
if (Recipient.self().equals(recipient) && loadSelf) {
|
||||
photo = new ProfileContactPhoto(recipient);
|
||||
@@ -163,10 +162,11 @@ public final class AvatarUtil {
|
||||
photo = recipient.getContactPhoto();
|
||||
}
|
||||
|
||||
final int size = targetSize == -1 ? DrawableUtil.SHORTCUT_INFO_WRAPPED_SIZE : targetSize;
|
||||
final GlideRequest<T> request = glideRequest.load(photo)
|
||||
.error(getFallback(context, recipient, targetSize))
|
||||
.error(getFallback(context, recipient, size))
|
||||
.diskCacheStrategy(DiskCacheStrategy.ALL)
|
||||
.override(targetSize);
|
||||
.override(size);
|
||||
|
||||
if (recipient.shouldBlurAvatar()) {
|
||||
BlurTransformation blur = new BlurTransformation(context, 0.25f, BlurTransformation.MAX_RADIUS);
|
||||
@@ -203,7 +203,6 @@ public final class AvatarUtil {
|
||||
}
|
||||
|
||||
public @Nullable Bitmap await() throws InterruptedException {
|
||||
Log.d(TAG, "AvatarTarget#await:");
|
||||
if (countDownLatch.await(1, TimeUnit.SECONDS)) {
|
||||
return bitmap.get();
|
||||
} else {
|
||||
@@ -212,17 +211,28 @@ public final class AvatarUtil {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
Log.d(TAG, "AvatarTarget: onDestroy");
|
||||
super.onDestroy();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoadStarted(@Nullable Drawable placeholder) {
|
||||
Log.d(TAG, "AvatarTarget: onLoadStarted");
|
||||
super.onLoadStarted(placeholder);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResourceReady(@NonNull Bitmap resource, @Nullable Transition<? super Bitmap> transition) {
|
||||
Log.d(TAG, "AvatarTarget#onResourceReady: " + resource.getWidth() + ", " + resource.getHeight() + ", s:" + size);
|
||||
Log.d(TAG, "AvatarTarget: onResourceReady");
|
||||
bitmap.set(resource);
|
||||
countDownLatch.countDown();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoadFailed(@Nullable Drawable errorDrawable) {
|
||||
Log.d(TAG, "AvatarTarget#onLoadFailed:");
|
||||
|
||||
Log.d(TAG, "AvatarTarget: onLoadFailed");
|
||||
if (errorDrawable == null) {
|
||||
throw new AssertionError("Expected an error drawable.");
|
||||
}
|
||||
@@ -234,8 +244,7 @@ public final class AvatarUtil {
|
||||
|
||||
@Override
|
||||
public void onLoadCleared(@Nullable Drawable placeholder) {
|
||||
Log.d(TAG, "AvatarTarget#onLoadCleared:");
|
||||
|
||||
Log.d(TAG, "AvatarTarget: onLoadCleared");
|
||||
bitmap.set(null);
|
||||
countDownLatch.countDown();
|
||||
}
|
||||
|
||||
@@ -226,7 +226,7 @@ public final class ConversationUtil {
|
||||
.setIntent(ConversationIntents.createBuilderSync(context, resolved.getId(), threadId).build())
|
||||
.setShortLabel(shortName)
|
||||
.setLongLabel(longName)
|
||||
.setIcon(AvatarUtil.getIconCompatForShortcut(context, resolved))
|
||||
.setIcon(AvatarUtil.getIconCompat(context, resolved))
|
||||
.setPersons(persons)
|
||||
.setCategories(Sets.newHashSet(CATEGORY_SHARE_TARGET))
|
||||
.setActivity(activity)
|
||||
@@ -286,11 +286,12 @@ public final class ConversationUtil {
|
||||
/**
|
||||
* @return A Compat Library Person object representing the given Recipient
|
||||
*/
|
||||
@WorkerThread
|
||||
public static @NonNull Person buildPerson(@NonNull Context context, @NonNull Recipient recipient) {
|
||||
return new Person.Builder()
|
||||
.setKey(getShortcutId(recipient.getId()))
|
||||
.setName(recipient.getDisplayName(context))
|
||||
.setIcon(AvatarUtil.getIconWithUriForNotification(recipient.getId()))
|
||||
.setIcon(AvatarUtil.getIconCompat(context, recipient))
|
||||
.setUri(recipient.isSystemContact() ? recipient.getContactUri().toString() : null)
|
||||
.build();
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Build;
|
||||
|
||||
import androidx.annotation.IntDef;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.core.app.NotificationCompat;
|
||||
@@ -36,6 +37,15 @@ public class CallNotificationBuilder {
|
||||
public static final int TYPE_ESTABLISHED = 3;
|
||||
public static final int TYPE_INCOMING_CONNECTING = 4;
|
||||
|
||||
@IntDef(value = {
|
||||
TYPE_INCOMING_RINGING,
|
||||
TYPE_OUTGOING_RINGING,
|
||||
TYPE_ESTABLISHED,
|
||||
TYPE_INCOMING_CONNECTING
|
||||
})
|
||||
public @interface CallNotificationType {
|
||||
}
|
||||
|
||||
private enum LaunchCallScreenIntentState {
|
||||
CONTENT(null, 0),
|
||||
AUDIO(WebRtcCallActivity.ANSWER_ACTION, 1),
|
||||
@@ -61,7 +71,23 @@ public class CallNotificationBuilder {
|
||||
*/
|
||||
public static final int API_LEVEL_CALL_STYLE = 29;
|
||||
|
||||
public static Notification getCallInProgressNotification(Context context, int type, Recipient recipient, boolean isVideoCall) {
|
||||
/**
|
||||
* Gets the Notification for the current in-progress call.
|
||||
*
|
||||
* @param context Context, normally the service requesting this notification
|
||||
* @param type The type of notification desired
|
||||
* @param recipient The target of the call (group, call link, or 1:1 recipient)
|
||||
* @param isVideoCall Whether the call is a video call
|
||||
* @param skipPersonIcon Whether to skip loading the icon for a person, used to avoid blocking the UI thread on older apis.
|
||||
*/
|
||||
public static Notification getCallInProgressNotification(
|
||||
Context context,
|
||||
@CallNotificationType int type,
|
||||
Recipient recipient,
|
||||
boolean isVideoCall,
|
||||
boolean skipPersonIcon
|
||||
)
|
||||
{
|
||||
PendingIntent pendingIntent = getActivityPendingIntent(context, LaunchCallScreenIntentState.CONTENT);
|
||||
NotificationCompat.Builder builder = new NotificationCompat.Builder(context, getNotificationChannel(type))
|
||||
.setSmallIcon(R.drawable.ic_call_secure_white_24dp)
|
||||
@@ -80,7 +106,9 @@ public class CallNotificationBuilder {
|
||||
builder.setCategory(NotificationCompat.CATEGORY_CALL);
|
||||
builder.setFullScreenIntent(pendingIntent, true);
|
||||
|
||||
Person person = ConversationUtil.buildPerson(context, recipient);
|
||||
Person person = skipPersonIcon ? ConversationUtil.buildPersonWithoutIcon(context, recipient)
|
||||
: ConversationUtil.buildPerson(context.getApplicationContext(), recipient);
|
||||
|
||||
builder.addPerson(person);
|
||||
|
||||
if (deviceVersionSupportsIncomingCallStyle()) {
|
||||
@@ -102,7 +130,9 @@ public class CallNotificationBuilder {
|
||||
builder.setPriority(NotificationCompat.PRIORITY_DEFAULT);
|
||||
builder.setCategory(NotificationCompat.CATEGORY_CALL);
|
||||
|
||||
Person person = ConversationUtil.buildPerson(context, recipient);
|
||||
Person person = skipPersonIcon ? ConversationUtil.buildPersonWithoutIcon(context, recipient)
|
||||
: ConversationUtil.buildPerson(context.getApplicationContext(), recipient);
|
||||
|
||||
builder.addPerson(person);
|
||||
|
||||
if (deviceVersionSupportsIncomingCallStyle()) {
|
||||
|
||||
Reference in New Issue
Block a user