Ensure lower api levels do not try to use Uri based IconCompat.

This commit is contained in:
Alex Hart
2023-09-11 15:27:58 -03:00
committed by GitHub
parent b72fe0d7a2
commit 18b33a7776
6 changed files with 119 additions and 50 deletions

View File

@@ -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)

View File

@@ -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
}

View File

@@ -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) {

View File

@@ -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();
}

View File

@@ -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();
}

View File

@@ -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()) {