mirror of
https://github.com/signalapp/Signal-Android.git
synced 2025-12-24 21:15:48 +00:00
Add ContentProvider for user avatars.
This commit is contained in:
@@ -1158,6 +1158,11 @@
|
||||
|
||||
<receiver android:name=".payments.backup.phrase.ClearClipboardAlarmReceiver" />
|
||||
|
||||
<provider android:name=".providers.AvatarProvider"
|
||||
android:authorities="${applicationId}.avatar"
|
||||
android:exported="false"
|
||||
android:grantUriPermissions="true" />
|
||||
|
||||
<provider android:name=".providers.PartProvider"
|
||||
android:grantUriPermissions="true"
|
||||
android:exported="false"
|
||||
|
||||
@@ -202,7 +202,7 @@ public class AvatarHelper {
|
||||
}
|
||||
}
|
||||
|
||||
private static @NonNull File getAvatarFile(@NonNull Context context, @NonNull RecipientId recipientId) {
|
||||
public static @NonNull File getAvatarFile(@NonNull Context context, @NonNull RecipientId recipientId) {
|
||||
File profileAvatar = getAvatarFile(context, recipientId, false);
|
||||
boolean profileAvatarExists = profileAvatar.exists() && profileAvatar.length() > 0;
|
||||
File syncAvatar = getAvatarFile(context, recipientId, true);
|
||||
|
||||
@@ -0,0 +1,248 @@
|
||||
/*
|
||||
* Copyright 2023 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.securesms.providers
|
||||
|
||||
import android.content.ContentUris
|
||||
import android.content.ContentValues
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.UriMatcher
|
||||
import android.database.Cursor
|
||||
import android.graphics.Bitmap
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.os.Handler
|
||||
import android.os.MemoryFile
|
||||
import android.os.ParcelFileDescriptor
|
||||
import android.os.ProxyFileDescriptorCallback
|
||||
import androidx.annotation.RequiresApi
|
||||
import org.signal.core.util.StreamUtil
|
||||
import org.signal.core.util.ThreadUtil
|
||||
import org.signal.core.util.concurrent.SignalExecutors
|
||||
import org.signal.core.util.logging.Log
|
||||
import org.thoughtcrime.securesms.BuildConfig
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase
|
||||
import org.thoughtcrime.securesms.profiles.AvatarHelper
|
||||
import org.thoughtcrime.securesms.recipients.Recipient
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId
|
||||
import org.thoughtcrime.securesms.service.KeyCachingService
|
||||
import org.thoughtcrime.securesms.util.AvatarUtil
|
||||
import org.thoughtcrime.securesms.util.DrawableUtil
|
||||
import org.thoughtcrime.securesms.util.MediaUtil
|
||||
import org.thoughtcrime.securesms.util.MemoryFileUtil
|
||||
import org.thoughtcrime.securesms.util.ServiceUtil
|
||||
import java.io.ByteArrayInputStream
|
||||
import java.io.ByteArrayOutputStream
|
||||
import java.io.File
|
||||
import java.io.FileNotFoundException
|
||||
import java.io.IOException
|
||||
|
||||
/**
|
||||
* Provides user avatar bitmaps to the android system service for use in notifications and shortcuts.
|
||||
*
|
||||
* This file heavily borrows from [PartProvider]
|
||||
*/
|
||||
class AvatarProvider : BaseContentProvider() {
|
||||
|
||||
companion object {
|
||||
private val TAG = Log.tag(AvatarProvider::class.java)
|
||||
private const val CONTENT_AUTHORITY = "${BuildConfig.APPLICATION_ID}.avatar"
|
||||
private const val CONTENT_URI_STRING = "content://$CONTENT_AUTHORITY/avatar"
|
||||
private const val AVATAR = 1
|
||||
private val CONTENT_URI = Uri.parse(CONTENT_URI_STRING)
|
||||
private val uriMatcher = UriMatcher(UriMatcher.NO_MATCH).apply {
|
||||
addURI(CONTENT_AUTHORITY, "avatar/#", AVATAR)
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun getContentUri(context: Context, recipientId: RecipientId): Uri {
|
||||
val uri = ContentUris.withAppendedId(CONTENT_URI, recipientId.toLong())
|
||||
context.applicationContext.grantUriPermission("com.android.systemui", uri, Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||
|
||||
return uri
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreate(): Boolean {
|
||||
Log.i(TAG, "onCreate called")
|
||||
return true
|
||||
}
|
||||
|
||||
@Throws(FileNotFoundException::class)
|
||||
override fun openFile(uri: Uri, mode: String): ParcelFileDescriptor? {
|
||||
Log.i(TAG, "openFile() called!")
|
||||
|
||||
if (KeyCachingService.isLocked(context)) {
|
||||
Log.w(TAG, "masterSecret was null, abandoning.")
|
||||
return null
|
||||
}
|
||||
|
||||
if (uriMatcher.match(uri) == AVATAR) {
|
||||
Log.i(TAG, "Loading avatar.")
|
||||
try {
|
||||
val recipient = getRecipientId(uri)?.let { Recipient.resolved(it) } ?: return null
|
||||
return if (Build.VERSION.SDK_INT >= 26) {
|
||||
getParcelStreamProxyForAvatar(recipient)
|
||||
} else {
|
||||
getParcelStreamForAvatar(recipient)
|
||||
}
|
||||
} catch (ioe: IOException) {
|
||||
Log.w(TAG, ioe)
|
||||
throw FileNotFoundException("Error opening file")
|
||||
}
|
||||
}
|
||||
|
||||
Log.w(TAG, "Bad request.")
|
||||
throw FileNotFoundException("Request for bad avatar.")
|
||||
}
|
||||
|
||||
override fun query(uri: Uri, projection: Array<out String>?, selection: String?, selectionArgs: Array<out String>?, sortOrder: String?): Cursor? {
|
||||
Log.i(TAG, "query() called: $uri")
|
||||
|
||||
if (SignalDatabase.instance == null) {
|
||||
Log.w(TAG, "SignalDatabase unavailable")
|
||||
return null
|
||||
}
|
||||
|
||||
if (uriMatcher.match(uri) == AVATAR) {
|
||||
val recipientId = getRecipientId(uri) ?: return null
|
||||
|
||||
if (AvatarHelper.hasAvatar(context!!, recipientId)) {
|
||||
val file: File = AvatarHelper.getAvatarFile(context!!, recipientId)
|
||||
if (file.exists()) {
|
||||
return createCursor(projection, file.name, file.length())
|
||||
}
|
||||
}
|
||||
|
||||
return createCursor(projection, "fallback-$recipientId.jpg", 0)
|
||||
} else {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
override fun getType(uri: Uri): String? {
|
||||
Log.i(TAG, "getType() called: $uri")
|
||||
|
||||
if (SignalDatabase.instance == null) {
|
||||
Log.w(TAG, "SignalDatabase unavailable")
|
||||
return null
|
||||
}
|
||||
|
||||
if (uriMatcher.match(uri) == AVATAR) {
|
||||
getRecipientId(uri) ?: return null
|
||||
|
||||
return MediaUtil.IMAGE_PNG
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
override fun insert(uri: Uri, values: ContentValues?): Uri? {
|
||||
Log.i(TAG, "insert() called")
|
||||
return null
|
||||
}
|
||||
|
||||
override fun delete(uri: Uri, selection: String?, selectionArgs: Array<out String>?): Int {
|
||||
Log.i(TAG, "delete() called")
|
||||
context?.applicationContext?.revokeUriPermission(uri, Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||
return 0
|
||||
}
|
||||
|
||||
override fun update(uri: Uri, values: ContentValues?, selection: String?, selectionArgs: Array<out String>?): Int {
|
||||
Log.i(TAG, "update() called")
|
||||
return 0
|
||||
}
|
||||
|
||||
private fun getRecipientId(uri: Uri): RecipientId? {
|
||||
val rawRecipientId = ContentUris.parseId(uri)
|
||||
if (rawRecipientId <= 0) {
|
||||
Log.w(TAG, "Invalid recipient id.")
|
||||
return null
|
||||
}
|
||||
|
||||
val recipientId = RecipientId.from(rawRecipientId)
|
||||
if (!SignalDatabase.recipients.containsId(recipientId)) {
|
||||
Log.w(TAG, "Recipient does not exist.")
|
||||
return null
|
||||
}
|
||||
|
||||
return recipientId
|
||||
}
|
||||
|
||||
@RequiresApi(26)
|
||||
private fun getParcelStreamProxyForAvatar(recipient: Recipient): ParcelFileDescriptor {
|
||||
val storageManager = requireNotNull(ServiceUtil.getStorageManager(context!!))
|
||||
val handlerThread = SignalExecutors.getAndStartHandlerThread("avatarservice-proxy", ThreadUtil.PRIORITY_IMPORTANT_BACKGROUND_THREAD)
|
||||
val handler = Handler(handlerThread.looper)
|
||||
|
||||
val parcelFileDescriptor = storageManager.openProxyFileDescriptor(
|
||||
ParcelFileDescriptor.MODE_READ_ONLY,
|
||||
ProxyCallback(context!!.applicationContext, recipient),
|
||||
handler
|
||||
)
|
||||
|
||||
Log.i(TAG, "${recipient.id}:createdProxy")
|
||||
return parcelFileDescriptor
|
||||
}
|
||||
|
||||
private fun getParcelStreamForAvatar(recipient: Recipient): ParcelFileDescriptor {
|
||||
val outputStream = ByteArrayOutputStream()
|
||||
AvatarUtil.getBitmapForNotification(context!!, recipient, DrawableUtil.SHORTCUT_INFO_WRAPPED_SIZE).apply {
|
||||
compress(Bitmap.CompressFormat.PNG, 100, outputStream)
|
||||
}
|
||||
|
||||
val memoryFile = MemoryFile("${recipient.id}-imf", outputStream.size())
|
||||
StreamUtil.copy(ByteArrayInputStream(outputStream.toByteArray()), memoryFile.outputStream)
|
||||
StreamUtil.close(memoryFile.outputStream)
|
||||
|
||||
return MemoryFileUtil.getParcelFileDescriptor(memoryFile)
|
||||
}
|
||||
|
||||
@RequiresApi(26)
|
||||
private class ProxyCallback(
|
||||
private val context: Context,
|
||||
private val recipient: Recipient
|
||||
) : ProxyFileDescriptorCallback() {
|
||||
|
||||
private var memoryFile: MemoryFile? = null
|
||||
|
||||
override fun onGetSize(): Long {
|
||||
Log.i(TAG, "${recipient.id}:onGetSize:${Thread.currentThread().name}:${hashCode()}")
|
||||
ensureResourceLoaded()
|
||||
return memoryFile!!.length().toLong()
|
||||
}
|
||||
|
||||
override fun onRead(offset: Long, size: Int, data: ByteArray?): Int {
|
||||
Log.i(TAG, "${recipient.id}:onRead")
|
||||
ensureResourceLoaded()
|
||||
|
||||
return memoryFile!!.readBytes(data, offset.toInt(), 0, size)
|
||||
}
|
||||
|
||||
override fun onRelease() {
|
||||
Log.i(TAG, "${recipient.id}:onRelease")
|
||||
memoryFile = null
|
||||
}
|
||||
|
||||
private fun ensureResourceLoaded() {
|
||||
if (memoryFile != null) {
|
||||
return
|
||||
}
|
||||
|
||||
Log.i(TAG, "Reading ${recipient.id} icon into RAM.")
|
||||
|
||||
val outputStream = ByteArrayOutputStream()
|
||||
val avatarBitmap = AvatarUtil.getBitmapForNotification(context, recipient, DrawableUtil.SHORTCUT_INFO_WRAPPED_SIZE)
|
||||
avatarBitmap.compress(Bitmap.CompressFormat.PNG, 100, outputStream)
|
||||
|
||||
Log.i(TAG, "Writing ${recipient.id} icon to MemoryFile")
|
||||
|
||||
memoryFile = MemoryFile("${recipient.id}-imf", outputStream.size())
|
||||
StreamUtil.copy(ByteArrayInputStream(outputStream.toByteArray()), memoryFile!!.outputStream)
|
||||
StreamUtil.close(memoryFile!!.outputStream)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -12,7 +12,7 @@ import androidx.annotation.Nullable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
abstract class BaseContentProvider extends ContentProvider {
|
||||
public abstract class BaseContentProvider extends ContentProvider {
|
||||
|
||||
private static final String[] COLUMNS = {OpenableColumns.DISPLAY_NAME, OpenableColumns.SIZE};
|
||||
|
||||
|
||||
@@ -35,9 +35,6 @@ import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
|
||||
import io.reactivex.rxjava3.disposables.Disposable;
|
||||
|
||||
/**
|
||||
* 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).
|
||||
@@ -76,7 +73,6 @@ public final class WebRtcCallService extends Service implements SignalAudioManag
|
||||
private int lastNotificationId;
|
||||
private Notification lastNotification;
|
||||
private boolean isGroup = true;
|
||||
private Disposable notificationDisposable = Disposable.empty();
|
||||
private boolean stopping = false;
|
||||
|
||||
public static void update(@NonNull Context context, int type, @NonNull RecipientId recipientId, boolean isVideoCall) {
|
||||
@@ -152,8 +148,6 @@ public final class WebRtcCallService extends Service implements SignalAudioManag
|
||||
Log.v(TAG, "onDestroy");
|
||||
super.onDestroy();
|
||||
|
||||
notificationDisposable.dispose();
|
||||
|
||||
if (uncaughtExceptionHandlerManager != null) {
|
||||
uncaughtExceptionHandlerManager.unregister();
|
||||
}
|
||||
@@ -236,21 +230,10 @@ public final class WebRtcCallService extends Service implements SignalAudioManag
|
||||
}
|
||||
|
||||
public void setCallInProgressNotification(int type, @NonNull RecipientId id, boolean isVideoCall) {
|
||||
if (lastNotificationId == INVALID_NOTIFICATION_ID) {
|
||||
lastNotificationId = CallNotificationBuilder.getStartingStoppingNotificationId();
|
||||
lastNotification = CallNotificationBuilder.getStartingNotification(this);
|
||||
startForegroundCompat(lastNotificationId, lastNotification);
|
||||
}
|
||||
lastNotificationId = CallNotificationBuilder.getNotificationId(type);
|
||||
lastNotification = CallNotificationBuilder.getCallInProgressNotification(this, type, Recipient.resolved(id), isVideoCall);
|
||||
|
||||
notificationDisposable.dispose();
|
||||
notificationDisposable = CallNotificationBuilder.getCallInProgressNotification(this, type, Recipient.resolved(id), isVideoCall)
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(notification -> {
|
||||
lastNotificationId = CallNotificationBuilder.getNotificationId(type);
|
||||
lastNotification = notification;
|
||||
|
||||
startForegroundCompat(lastNotificationId, lastNotification);
|
||||
});
|
||||
startForegroundCompat(lastNotificationId, lastNotification);
|
||||
}
|
||||
|
||||
private synchronized void startForegroundCompat(int notificationId, Notification notification) {
|
||||
@@ -267,7 +250,6 @@ public final class WebRtcCallService extends Service implements SignalAudioManag
|
||||
|
||||
private synchronized void stop() {
|
||||
stopping = true;
|
||||
notificationDisposable.dispose();
|
||||
stopForeground(true);
|
||||
stopSelf();
|
||||
}
|
||||
|
||||
@@ -24,7 +24,9 @@ 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.providers.AvatarProvider;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
@@ -99,12 +101,8 @@ public final class AvatarUtil {
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
public static IconCompat getIconForNotification(@NonNull Context context, @NonNull Recipient recipient) {
|
||||
try {
|
||||
return IconCompat.createWithBitmap(requestCircle(GlideApp.with(context).asBitmap(), context, recipient, UNDEFINED_SIZE).submit().get());
|
||||
} catch (ExecutionException | InterruptedException e) {
|
||||
return null;
|
||||
}
|
||||
public static IconCompat getIconWithUriForNotification(@NonNull Context context, @NonNull RecipientId recipientId) {
|
||||
return IconCompat.createWithContentUri(AvatarProvider.getContentUri(context, recipientId));
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
@@ -128,8 +126,13 @@ public final class AvatarUtil {
|
||||
|
||||
@WorkerThread
|
||||
public static Bitmap getBitmapForNotification(@NonNull Context context, @NonNull Recipient recipient) {
|
||||
return getBitmapForNotification(context, recipient, UNDEFINED_SIZE);
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
public static Bitmap getBitmapForNotification(@NonNull Context context, @NonNull Recipient recipient, int size) {
|
||||
try {
|
||||
return requestCircle(GlideApp.with(context).asBitmap(), context, recipient, UNDEFINED_SIZE).submit().get();
|
||||
return requestCircle(GlideApp.with(context).asBitmap(), context, recipient, size).submit().get();
|
||||
} catch (ExecutionException | InterruptedException e) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -286,12 +286,11 @@ 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.getIconForNotification(context, recipient))
|
||||
.setIcon(AvatarUtil.getIconWithUriForNotification(context, recipient.getId()))
|
||||
.setUri(recipient.isSystemContact() ? recipient.getContactUri().toString() : null)
|
||||
.build();
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import android.os.Build;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.core.app.NotificationCompat;
|
||||
import androidx.core.app.Person;
|
||||
|
||||
import org.signal.core.util.PendingIntentFlags;
|
||||
import org.thoughtcrime.securesms.MainActivity;
|
||||
@@ -19,10 +20,6 @@ import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.service.webrtc.WebRtcCallService;
|
||||
import org.thoughtcrime.securesms.util.ConversationUtil;
|
||||
|
||||
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
|
||||
import io.reactivex.rxjava3.core.Single;
|
||||
import io.reactivex.rxjava3.schedulers.Schedulers;
|
||||
|
||||
/**
|
||||
* Manages the state of the WebRtc items in the Android notification bar.
|
||||
*
|
||||
@@ -64,7 +61,7 @@ public class CallNotificationBuilder {
|
||||
*/
|
||||
public static final int API_LEVEL_CALL_STYLE = 29;
|
||||
|
||||
public static Single<Notification> getCallInProgressNotification(Context context, int type, Recipient recipient, boolean isVideoCall) {
|
||||
public static Notification getCallInProgressNotification(Context context, int type, Recipient recipient, boolean isVideoCall) {
|
||||
PendingIntent pendingIntent = getActivityPendingIntent(context, LaunchCallScreenIntentState.CONTENT);
|
||||
NotificationCompat.Builder builder = new NotificationCompat.Builder(context, getNotificationChannel(type))
|
||||
.setSmallIcon(R.drawable.ic_call_secure_white_24dp)
|
||||
@@ -76,33 +73,27 @@ public class CallNotificationBuilder {
|
||||
builder.setContentText(context.getString(R.string.CallNotificationBuilder_connecting));
|
||||
builder.setPriority(NotificationCompat.PRIORITY_MIN);
|
||||
builder.setContentIntent(null);
|
||||
return Single.just(builder.build());
|
||||
return builder.build();
|
||||
} else if (type == TYPE_INCOMING_RINGING) {
|
||||
builder.setContentText(getIncomingCallContentText(context, recipient, isVideoCall));
|
||||
builder.setPriority(NotificationCompat.PRIORITY_HIGH);
|
||||
builder.setCategory(NotificationCompat.CATEGORY_CALL);
|
||||
builder.setFullScreenIntent(pendingIntent, true);
|
||||
|
||||
if (deviceVersionSupportsIncomingCallStyle())
|
||||
{
|
||||
return Single.fromCallable(() -> ConversationUtil.buildPerson(context, recipient))
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.map(person -> {
|
||||
builder.setStyle(NotificationCompat.CallStyle.forIncomingCall(
|
||||
person,
|
||||
getServicePendingIntent(context, WebRtcCallService.denyCallIntent(context)),
|
||||
getActivityPendingIntent(context, isVideoCall ? LaunchCallScreenIntentState.VIDEO : LaunchCallScreenIntentState.AUDIO)
|
||||
).setIsVideo(isVideoCall));
|
||||
return builder.build();
|
||||
});
|
||||
} else {
|
||||
return Single.just(builder.build());
|
||||
if (deviceVersionSupportsIncomingCallStyle()) {
|
||||
Person person = ConversationUtil.buildPerson(context, recipient);
|
||||
builder.setStyle(NotificationCompat.CallStyle.forIncomingCall(
|
||||
person,
|
||||
getServicePendingIntent(context, WebRtcCallService.denyCallIntent(context)),
|
||||
getActivityPendingIntent(context, isVideoCall ? LaunchCallScreenIntentState.VIDEO : LaunchCallScreenIntentState.AUDIO)
|
||||
).setIsVideo(isVideoCall));
|
||||
}
|
||||
|
||||
return builder.build();
|
||||
} else if (type == TYPE_OUTGOING_RINGING) {
|
||||
builder.setContentText(context.getString(R.string.NotificationBarManager__establishing_signal_call));
|
||||
builder.addAction(getServiceNotificationAction(context, WebRtcCallService.hangupIntent(context), R.drawable.ic_call_end_grey600_32dp, R.string.NotificationBarManager__cancel_call));
|
||||
return Single.just(builder.build());
|
||||
return builder.build();
|
||||
} else {
|
||||
builder.setContentText(getOngoingCallContentText(context, recipient, isVideoCall));
|
||||
builder.setOnlyAlertOnce(true);
|
||||
@@ -110,19 +101,14 @@ public class CallNotificationBuilder {
|
||||
builder.setCategory(NotificationCompat.CATEGORY_CALL);
|
||||
|
||||
if (deviceVersionSupportsIncomingCallStyle()) {
|
||||
return Single.fromCallable(() -> ConversationUtil.buildPerson(context, recipient))
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.map(person -> {
|
||||
builder.setStyle(NotificationCompat.CallStyle.forOngoingCall(
|
||||
person,
|
||||
getServicePendingIntent(context, WebRtcCallService.hangupIntent(context))
|
||||
).setIsVideo(isVideoCall));
|
||||
return builder.build();
|
||||
});
|
||||
} else {
|
||||
return Single.just(builder.build());
|
||||
Person person = ConversationUtil.buildPerson(context, recipient);
|
||||
builder.setStyle(NotificationCompat.CallStyle.forOngoingCall(
|
||||
person,
|
||||
getServicePendingIntent(context, WebRtcCallService.hangupIntent(context))
|
||||
).setIsVideo(isVideoCall));
|
||||
}
|
||||
|
||||
return builder.build();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user