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() val self: PersonCompat = PersonCompat.Builder()
.setBot(false) .setBot(false)
.setName(if (includeShortcut) Recipient.self().getDisplayName(context) else context.getString(R.string.SingleRecipientNotificationBuilder_you)) .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)) .setKey(ConversationUtil.getShortcutId(Recipient.self().id))
.build() .build()
@@ -290,7 +290,7 @@ sealed class NotificationBuilder(protected val context: Context) {
.setBot(false) .setBot(false)
.setName(notificationItem.getPersonName(context)) .setName(notificationItem.getPersonName(context))
.setUri(notificationItem.getPersonUri()) .setUri(notificationItem.getPersonUri())
.setIcon(notificationItem.getPersonIcon()) .setIcon(notificationItem.getPersonIcon(context))
if (includeShortcut) { if (includeShortcut) {
personBuilder.setKey(ConversationUtil.getShortcutId(notificationItem.authorRecipient)) personBuilder.setKey(ConversationUtil.getShortcutId(notificationItem.authorRecipient))
@@ -360,7 +360,7 @@ sealed class NotificationBuilder(protected val context: Context) {
) )
if (intent != null) { 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) .setAutoExpandBubble(bubbleState === BubbleUtil.BubbleState.SHOWN)
.setDesiredHeight(600) .setDesiredHeight(600)
.setSuppressNotification(bubbleState === BubbleUtil.BubbleState.SHOWN) .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) { return if (SignalStore.settings().messageNotificationsPrivacy.isDisplayContact) {
AvatarUtil.getIconWithUriForNotification(authorRecipient.id) AvatarUtil.getIconCompat(context, authorRecipient)
} else { } else {
null null
} }

View File

@@ -19,6 +19,7 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import org.signal.core.util.ThreadUtil; import org.signal.core.util.ThreadUtil;
import org.signal.core.util.concurrent.SignalExecutors;
import org.signal.core.util.logging.Log; import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies; import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
import org.thoughtcrime.securesms.jobs.ForegroundServiceUtil; import org.thoughtcrime.securesms.jobs.ForegroundServiceUtil;
@@ -33,8 +34,14 @@ import org.thoughtcrime.securesms.webrtc.locks.LockManager;
import java.util.Objects; import java.util.Objects;
import java.util.Set; import java.util.Set;
import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit; 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 * 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). * 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 static final long FOREGROUND_SERVICE_TIMEOUT = TimeUnit.SECONDS.toMillis(10);
private final WebSocketKeepAliveTask webSocketKeepAliveTask = new WebSocketKeepAliveTask(); private final WebSocketKeepAliveTask webSocketKeepAliveTask = new WebSocketKeepAliveTask();
private final Executor singleThreadExecutor = SignalExecutors.newCachedSingleThreadExecutor("signal-webrtc-in-call", ThreadUtil.PRIORITY_IMPORTANT_BACKGROUND_THREAD);
private SignalCallManager callManager; private SignalCallManager callManager;
@@ -72,7 +80,9 @@ public final class WebRtcCallService extends Service implements SignalAudioManag
private SignalAudioManager signalAudioManager; private SignalAudioManager signalAudioManager;
private int lastNotificationId; private int lastNotificationId;
private Notification lastNotification; private Notification lastNotification;
private boolean stopping = false; 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) { public static void update(@NonNull Context context, int type, @NonNull RecipientId recipientId, boolean isVideoCall) {
Intent intent = new Intent(context, WebRtcCallService.class); Intent intent = new Intent(context, WebRtcCallService.class);
@@ -219,6 +229,7 @@ public final class WebRtcCallService extends Service implements SignalAudioManag
private void setCallNotification() { private void setCallNotification() {
setCallNotification(false); setCallNotification(false);
} }
private void setCallNotification(boolean stopping) { private void setCallNotification(boolean stopping) {
if (!stopping && lastNotificationId != INVALID_NOTIFICATION_ID) { if (!stopping && lastNotificationId != INVALID_NOTIFICATION_ID) {
startForegroundCompat(lastNotificationId, lastNotification); 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) { public void setCallInProgressNotification(int type, @NonNull RecipientId id, boolean isVideoCall) {
lastNotificationDisposable.dispose();
boolean requiresAsyncNotificationLoad = Build.VERSION.SDK_INT <= 29;
lastNotificationId = CallNotificationBuilder.getNotificationId(type); 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); 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) { 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.target.CustomViewTarget;
import com.bumptech.glide.request.transition.Transition; 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.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.contacts.avatars.ContactPhoto; 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.contacts.avatars.ProfileContactPhoto;
import org.thoughtcrime.securesms.mms.GlideApp; import org.thoughtcrime.securesms.mms.GlideApp;
import org.thoughtcrime.securesms.mms.GlideRequest; import org.thoughtcrime.securesms.mms.GlideRequest;
import org.thoughtcrime.securesms.mms.GlideRequests;
import org.thoughtcrime.securesms.providers.AvatarProvider; import org.thoughtcrime.securesms.providers.AvatarProvider;
import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientId;
import java.util.Objects;
import java.util.Optional; import java.util.Optional;
import java.util.concurrent.CountDownLatch; import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutionException;
@@ -91,7 +92,7 @@ public final class AvatarUtil {
} }
public static void loadIconIntoImageView(@NonNull Recipient recipient, @NonNull ImageView target, int requestedSize) { public static void loadIconIntoImageView(@NonNull Recipient recipient, @NonNull ImageView target, int requestedSize) {
Context context = target.getContext(); Context context = target.getContext();
requestCircle(GlideApp.with(context).asDrawable(), context, recipient, requestedSize).into(target); requestCircle(GlideApp.with(context).asDrawable(), context, recipient, requestedSize).into(target);
} }
@@ -103,23 +104,18 @@ public final class AvatarUtil {
throws ExecutionException, InterruptedException throws ExecutionException, InterruptedException
{ {
return requestSquare(GlideApp.with(context).asBitmap(), context, recipient) return requestSquare(GlideApp.with(context).asBitmap(), context, recipient)
.skipMemoryCache(true) .skipMemoryCache(true)
.diskCacheStrategy(DiskCacheStrategy.NONE) .diskCacheStrategy(DiskCacheStrategy.NONE)
.submit(width, height) .submit(width, height)
.get(); .get();
} }
@WorkerThread @WorkerThread
public static IconCompat getIconWithUriForNotification(@NonNull RecipientId recipientId) { public static @NonNull IconCompat getIconCompat(@NonNull Context context, @NonNull Recipient recipient) {
return IconCompat.createWithContentUri(AvatarProvider.getContentUri(recipientId));
}
@WorkerThread
public static @NonNull IconCompat getIconCompatForShortcut(@NonNull Context context, @NonNull Recipient recipient) {
if (Build.VERSION.SDK_INT > 29) { if (Build.VERSION.SDK_INT > 29) {
return getIconWithUriForNotification(recipient.getId()); return IconCompat.createWithContentUri(AvatarProvider.getContentUri(recipient.getId()));
} else { } 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 @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 { try {
AvatarTarget avatarTarget = new AvatarTarget(size); AvatarTarget avatarTarget = new AvatarTarget(size);
GlideRequests glideRequests = GlideApp.with(context);
SignalExecutors.BOUNDED_IO.submit(() -> { requestCircle(glideRequests.asBitmap(), context, recipient, size).into(avatarTarget);
requestCircle(GlideApp.with(context).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) { } 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) { 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; final ContactPhoto photo;
if (Recipient.self().equals(recipient) && loadSelf) { if (Recipient.self().equals(recipient) && loadSelf) {
photo = new ProfileContactPhoto(recipient); photo = new ProfileContactPhoto(recipient);
@@ -163,10 +162,11 @@ public final class AvatarUtil {
photo = recipient.getContactPhoto(); photo = recipient.getContactPhoto();
} }
final int size = targetSize == -1 ? DrawableUtil.SHORTCUT_INFO_WRAPPED_SIZE : targetSize;
final GlideRequest<T> request = glideRequest.load(photo) final GlideRequest<T> request = glideRequest.load(photo)
.error(getFallback(context, recipient, targetSize)) .error(getFallback(context, recipient, size))
.diskCacheStrategy(DiskCacheStrategy.ALL) .diskCacheStrategy(DiskCacheStrategy.ALL)
.override(targetSize); .override(size);
if (recipient.shouldBlurAvatar()) { if (recipient.shouldBlurAvatar()) {
BlurTransformation blur = new BlurTransformation(context, 0.25f, BlurTransformation.MAX_RADIUS); BlurTransformation blur = new BlurTransformation(context, 0.25f, BlurTransformation.MAX_RADIUS);
@@ -203,7 +203,6 @@ public final class AvatarUtil {
} }
public @Nullable Bitmap await() throws InterruptedException { public @Nullable Bitmap await() throws InterruptedException {
Log.d(TAG, "AvatarTarget#await:");
if (countDownLatch.await(1, TimeUnit.SECONDS)) { if (countDownLatch.await(1, TimeUnit.SECONDS)) {
return bitmap.get(); return bitmap.get();
} else { } 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 @Override
public void onResourceReady(@NonNull Bitmap resource, @Nullable Transition<? super Bitmap> transition) { 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); bitmap.set(resource);
countDownLatch.countDown(); countDownLatch.countDown();
} }
@Override @Override
public void onLoadFailed(@Nullable Drawable errorDrawable) { public void onLoadFailed(@Nullable Drawable errorDrawable) {
Log.d(TAG, "AvatarTarget#onLoadFailed:"); Log.d(TAG, "AvatarTarget: onLoadFailed");
if (errorDrawable == null) { if (errorDrawable == null) {
throw new AssertionError("Expected an error drawable."); throw new AssertionError("Expected an error drawable.");
} }
@@ -234,8 +244,7 @@ public final class AvatarUtil {
@Override @Override
public void onLoadCleared(@Nullable Drawable placeholder) { public void onLoadCleared(@Nullable Drawable placeholder) {
Log.d(TAG, "AvatarTarget#onLoadCleared:"); Log.d(TAG, "AvatarTarget: onLoadCleared");
bitmap.set(null); bitmap.set(null);
countDownLatch.countDown(); countDownLatch.countDown();
} }

View File

@@ -226,7 +226,7 @@ public final class ConversationUtil {
.setIntent(ConversationIntents.createBuilderSync(context, resolved.getId(), threadId).build()) .setIntent(ConversationIntents.createBuilderSync(context, resolved.getId(), threadId).build())
.setShortLabel(shortName) .setShortLabel(shortName)
.setLongLabel(longName) .setLongLabel(longName)
.setIcon(AvatarUtil.getIconCompatForShortcut(context, resolved)) .setIcon(AvatarUtil.getIconCompat(context, resolved))
.setPersons(persons) .setPersons(persons)
.setCategories(Sets.newHashSet(CATEGORY_SHARE_TARGET)) .setCategories(Sets.newHashSet(CATEGORY_SHARE_TARGET))
.setActivity(activity) .setActivity(activity)
@@ -286,11 +286,12 @@ public final class ConversationUtil {
/** /**
* @return A Compat Library Person object representing the given Recipient * @return A Compat Library Person object representing the given Recipient
*/ */
@WorkerThread
public static @NonNull Person buildPerson(@NonNull Context context, @NonNull Recipient recipient) { public static @NonNull Person buildPerson(@NonNull Context context, @NonNull Recipient recipient) {
return new Person.Builder() return new Person.Builder()
.setKey(getShortcutId(recipient.getId())) .setKey(getShortcutId(recipient.getId()))
.setName(recipient.getDisplayName(context)) .setName(recipient.getDisplayName(context))
.setIcon(AvatarUtil.getIconWithUriForNotification(recipient.getId())) .setIcon(AvatarUtil.getIconCompat(context, recipient))
.setUri(recipient.isSystemContact() ? recipient.getContactUri().toString() : null) .setUri(recipient.isSystemContact() ? recipient.getContactUri().toString() : null)
.build(); .build();
} }

View File

@@ -6,6 +6,7 @@ import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.os.Build; import android.os.Build;
import androidx.annotation.IntDef;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.core.app.NotificationCompat; import androidx.core.app.NotificationCompat;
@@ -36,13 +37,22 @@ public class CallNotificationBuilder {
public static final int TYPE_ESTABLISHED = 3; public static final int TYPE_ESTABLISHED = 3;
public static final int TYPE_INCOMING_CONNECTING = 4; 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 { private enum LaunchCallScreenIntentState {
CONTENT(null, 0), CONTENT(null, 0),
AUDIO(WebRtcCallActivity.ANSWER_ACTION, 1), AUDIO(WebRtcCallActivity.ANSWER_ACTION, 1),
VIDEO(WebRtcCallActivity.ANSWER_VIDEO_ACTION, 2); VIDEO(WebRtcCallActivity.ANSWER_VIDEO_ACTION, 2);
final @Nullable String action; final @Nullable String action;
final int requestCode; final int requestCode;
LaunchCallScreenIntentState(@Nullable String action, int requestCode) { LaunchCallScreenIntentState(@Nullable String action, int requestCode) {
this.action = action; this.action = action;
@@ -61,9 +71,25 @@ public class CallNotificationBuilder {
*/ */
public static final int API_LEVEL_CALL_STYLE = 29; public static final int API_LEVEL_CALL_STYLE = 29;
public static Notification getCallInProgressNotification(Context context, int type, Recipient recipient, boolean isVideoCall) { /**
PendingIntent pendingIntent = getActivityPendingIntent(context, LaunchCallScreenIntentState.CONTENT); * Gets the Notification for the current in-progress call.
NotificationCompat.Builder builder = new NotificationCompat.Builder(context, getNotificationChannel(type)) *
* @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) .setSmallIcon(R.drawable.ic_call_secure_white_24dp)
.setContentIntent(pendingIntent) .setContentIntent(pendingIntent)
.setOngoing(true) .setOngoing(true)
@@ -80,7 +106,9 @@ public class CallNotificationBuilder {
builder.setCategory(NotificationCompat.CATEGORY_CALL); builder.setCategory(NotificationCompat.CATEGORY_CALL);
builder.setFullScreenIntent(pendingIntent, true); 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); builder.addPerson(person);
if (deviceVersionSupportsIncomingCallStyle()) { if (deviceVersionSupportsIncomingCallStyle()) {
@@ -102,7 +130,9 @@ public class CallNotificationBuilder {
builder.setPriority(NotificationCompat.PRIORITY_DEFAULT); builder.setPriority(NotificationCompat.PRIORITY_DEFAULT);
builder.setCategory(NotificationCompat.CATEGORY_CALL); 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); builder.addPerson(person);
if (deviceVersionSupportsIncomingCallStyle()) { if (deviceVersionSupportsIncomingCallStyle()) {
@@ -126,11 +156,11 @@ public class CallNotificationBuilder {
public static @NonNull Notification getStartingNotification(@NonNull Context context) { public static @NonNull Notification getStartingNotification(@NonNull Context context) {
return new NotificationCompat.Builder(context, NotificationChannels.getInstance().CALL_STATUS) return new NotificationCompat.Builder(context, NotificationChannels.getInstance().CALL_STATUS)
.setSmallIcon(R.drawable.ic_call_secure_white_24dp) .setSmallIcon(R.drawable.ic_call_secure_white_24dp)
.setOngoing(true) .setOngoing(true)
.setContentTitle(context.getString(R.string.NotificationBarManager__starting_signal_call_service)) .setContentTitle(context.getString(R.string.NotificationBarManager__starting_signal_call_service))
.setPriority(NotificationCompat.PRIORITY_MIN) .setPriority(NotificationCompat.PRIORITY_MIN)
.build(); .build();
} }
public static @NonNull Notification getStoppingNotification(@NonNull Context context) { public static @NonNull Notification getStoppingNotification(@NonNull Context context) {