mirror of
https://github.com/oxen-io/session-android.git
synced 2024-11-27 12:05:22 +00:00
Merge branch 'dev' into voice-messages
This commit is contained in:
commit
6b84bb63f9
@ -182,7 +182,7 @@ dependencies {
|
|||||||
testImplementation 'org.robolectric:shadows-multidex:4.2'
|
testImplementation 'org.robolectric:shadows-multidex:4.2'
|
||||||
}
|
}
|
||||||
|
|
||||||
def canonicalVersionCode = 111
|
def canonicalVersionCode = 115
|
||||||
def canonicalVersionName = "1.6.2"
|
def canonicalVersionName = "1.6.2"
|
||||||
|
|
||||||
def postFixSize = 10
|
def postFixSize = 10
|
||||||
|
@ -42,6 +42,7 @@ import org.thoughtcrime.securesms.crypto.ProfileKeyUtil;
|
|||||||
import org.thoughtcrime.securesms.crypto.storage.TextSecureSessionStore;
|
import org.thoughtcrime.securesms.crypto.storage.TextSecureSessionStore;
|
||||||
import org.thoughtcrime.securesms.database.Address;
|
import org.thoughtcrime.securesms.database.Address;
|
||||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||||
|
import org.thoughtcrime.securesms.database.GroupDatabase;
|
||||||
import org.thoughtcrime.securesms.dependencies.AxolotlStorageModule;
|
import org.thoughtcrime.securesms.dependencies.AxolotlStorageModule;
|
||||||
import org.thoughtcrime.securesms.dependencies.InjectableType;
|
import org.thoughtcrime.securesms.dependencies.InjectableType;
|
||||||
import org.thoughtcrime.securesms.dependencies.SignalCommunicationModule;
|
import org.thoughtcrime.securesms.dependencies.SignalCommunicationModule;
|
||||||
@ -305,7 +306,8 @@ public class ApplicationContext extends MultiDexApplication implements Dependenc
|
|||||||
byte[] userPrivateKey = IdentityKeyUtil.getIdentityKeyPair(this).getPrivateKey().serialize();
|
byte[] userPrivateKey = IdentityKeyUtil.getIdentityKeyPair(this).getPrivateKey().serialize();
|
||||||
LokiAPIDatabase apiDB = DatabaseFactory.getLokiAPIDatabase(this);
|
LokiAPIDatabase apiDB = DatabaseFactory.getLokiAPIDatabase(this);
|
||||||
LokiUserDatabase userDB = DatabaseFactory.getLokiUserDatabase(this);
|
LokiUserDatabase userDB = DatabaseFactory.getLokiUserDatabase(this);
|
||||||
publicChatAPI = new PublicChatAPI(userPublicKey, userPrivateKey, apiDB, userDB);
|
GroupDatabase groupDB = DatabaseFactory.getGroupDatabase(this);
|
||||||
|
publicChatAPI = new PublicChatAPI(userPublicKey, userPrivateKey, apiDB, userDB, groupDB);
|
||||||
return publicChatAPI;
|
return publicChatAPI;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -213,6 +213,7 @@ import org.thoughtcrime.securesms.util.DateUtils;
|
|||||||
import org.thoughtcrime.securesms.util.Dialogs;
|
import org.thoughtcrime.securesms.util.Dialogs;
|
||||||
import org.thoughtcrime.securesms.util.DynamicLanguage;
|
import org.thoughtcrime.securesms.util.DynamicLanguage;
|
||||||
import org.thoughtcrime.securesms.util.ExpirationUtil;
|
import org.thoughtcrime.securesms.util.ExpirationUtil;
|
||||||
|
import org.thoughtcrime.securesms.util.GroupUtil;
|
||||||
import org.thoughtcrime.securesms.util.IdentityUtil;
|
import org.thoughtcrime.securesms.util.IdentityUtil;
|
||||||
import org.thoughtcrime.securesms.util.MediaUtil;
|
import org.thoughtcrime.securesms.util.MediaUtil;
|
||||||
import org.thoughtcrime.securesms.util.ServiceUtil;
|
import org.thoughtcrime.securesms.util.ServiceUtil;
|
||||||
@ -227,6 +228,7 @@ import org.thoughtcrime.securesms.util.views.Stub;
|
|||||||
import org.whispersystems.libsignal.InvalidMessageException;
|
import org.whispersystems.libsignal.InvalidMessageException;
|
||||||
import org.whispersystems.libsignal.util.guava.Optional;
|
import org.whispersystems.libsignal.util.guava.Optional;
|
||||||
import org.whispersystems.signalservice.loki.api.opengroups.PublicChat;
|
import org.whispersystems.signalservice.loki.api.opengroups.PublicChat;
|
||||||
|
import org.whispersystems.signalservice.loki.api.opengroups.PublicChatAPI;
|
||||||
import org.whispersystems.signalservice.loki.protocol.mentions.Mention;
|
import org.whispersystems.signalservice.loki.protocol.mentions.Mention;
|
||||||
import org.whispersystems.signalservice.loki.protocol.mentions.MentionsManager;
|
import org.whispersystems.signalservice.loki.protocol.mentions.MentionsManager;
|
||||||
import org.whispersystems.signalservice.loki.protocol.meta.SessionMetaProtocol;
|
import org.whispersystems.signalservice.loki.protocol.meta.SessionMetaProtocol;
|
||||||
@ -458,7 +460,17 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
|||||||
|
|
||||||
PublicChat publicChat = DatabaseFactory.getLokiThreadDatabase(this).getPublicChat(threadId);
|
PublicChat publicChat = DatabaseFactory.getLokiThreadDatabase(this).getPublicChat(threadId);
|
||||||
if (publicChat != null) {
|
if (publicChat != null) {
|
||||||
ApplicationContext.getInstance(this).getPublicChatAPI().getChannelInfo(publicChat.getChannel(), publicChat.getServer()).success(displayName -> {
|
PublicChatAPI publicChatAPI = ApplicationContext.getInstance(this).getPublicChatAPI();
|
||||||
|
publicChatAPI.getChannelInfo(publicChat.getChannel(), publicChat.getServer()).success(info -> {
|
||||||
|
String groupId = GroupUtil.getEncodedOpenGroupId(publicChat.getId().getBytes());
|
||||||
|
|
||||||
|
publicChatAPI.updateProfileIfNeeded(
|
||||||
|
publicChat.getChannel(),
|
||||||
|
publicChat.getServer(),
|
||||||
|
groupId,
|
||||||
|
info,
|
||||||
|
false);
|
||||||
|
|
||||||
runOnUiThread(ConversationActivity.this::updateSubtitleTextView);
|
runOnUiThread(ConversationActivity.this::updateSubtitleTextView);
|
||||||
return Unit.INSTANCE;
|
return Unit.INSTANCE;
|
||||||
});
|
});
|
||||||
|
@ -6,9 +6,10 @@ import android.content.ContentValues;
|
|||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.database.Cursor;
|
import android.database.Cursor;
|
||||||
import android.graphics.Bitmap;
|
import android.graphics.Bitmap;
|
||||||
|
import android.text.TextUtils;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import android.text.TextUtils;
|
|
||||||
|
|
||||||
import com.annimon.stream.Stream;
|
import com.annimon.stream.Stream;
|
||||||
|
|
||||||
@ -21,6 +22,7 @@ import org.thoughtcrime.securesms.util.GroupUtil;
|
|||||||
import org.thoughtcrime.securesms.util.Util;
|
import org.thoughtcrime.securesms.util.Util;
|
||||||
import org.whispersystems.libsignal.util.guava.Optional;
|
import org.whispersystems.libsignal.util.guava.Optional;
|
||||||
import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentPointer;
|
import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentPointer;
|
||||||
|
import org.whispersystems.signalservice.loki.database.LokiOpenGroupDatabaseProtocol;
|
||||||
|
|
||||||
import java.io.Closeable;
|
import java.io.Closeable;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
@ -29,7 +31,7 @@ import java.util.Collections;
|
|||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public class GroupDatabase extends Database {
|
public class GroupDatabase extends Database implements LokiOpenGroupDatabaseProtocol {
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
private static final String TAG = GroupDatabase.class.getSimpleName();
|
private static final String TAG = GroupDatabase.class.getSimpleName();
|
||||||
@ -240,35 +242,37 @@ public class GroupDatabase extends Database {
|
|||||||
notifyConversationListListeners();
|
notifyConversationListListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void updateTitle(String groupId, String title) {
|
@Override
|
||||||
|
public void updateTitle(String groupID, String newValue) {
|
||||||
ContentValues contentValues = new ContentValues();
|
ContentValues contentValues = new ContentValues();
|
||||||
contentValues.put(TITLE, title);
|
contentValues.put(TITLE, newValue);
|
||||||
databaseHelper.getWritableDatabase().update(TABLE_NAME, contentValues, GROUP_ID + " = ?",
|
databaseHelper.getWritableDatabase().update(TABLE_NAME, contentValues, GROUP_ID + " = ?",
|
||||||
new String[] {groupId});
|
new String[] {groupID});
|
||||||
|
|
||||||
Recipient recipient = Recipient.from(context, Address.fromSerialized(groupId), false);
|
Recipient recipient = Recipient.from(context, Address.fromSerialized(groupID), false);
|
||||||
recipient.setName(title);
|
recipient.setName(newValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void updateAvatar(String groupId, Bitmap avatar) {
|
public void updateProfilePicture(String groupID, Bitmap newValue) {
|
||||||
updateAvatar(groupId, BitmapUtil.toByteArray(avatar));
|
updateProfilePicture(groupID, BitmapUtil.toByteArray(newValue));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void updateAvatar(String groupId, byte[] avatar) {
|
@Override
|
||||||
|
public void updateProfilePicture(String groupID, byte[] newValue) {
|
||||||
long avatarId;
|
long avatarId;
|
||||||
|
|
||||||
if (avatar != null) avatarId = Math.abs(new SecureRandom().nextLong());
|
if (newValue != null) avatarId = Math.abs(new SecureRandom().nextLong());
|
||||||
else avatarId = 0;
|
else avatarId = 0;
|
||||||
|
|
||||||
|
|
||||||
ContentValues contentValues = new ContentValues(2);
|
ContentValues contentValues = new ContentValues(2);
|
||||||
contentValues.put(AVATAR, avatar);
|
contentValues.put(AVATAR, newValue);
|
||||||
contentValues.put(AVATAR_ID, avatarId);
|
contentValues.put(AVATAR_ID, avatarId);
|
||||||
|
|
||||||
databaseHelper.getWritableDatabase().update(TABLE_NAME, contentValues, GROUP_ID + " = ?",
|
databaseHelper.getWritableDatabase().update(TABLE_NAME, contentValues, GROUP_ID + " = ?",
|
||||||
new String[] {groupId});
|
new String[] {groupID});
|
||||||
|
|
||||||
Recipient.applyCached(Address.fromSerialized(groupId), recipient -> recipient.setGroupAvatarId(avatarId == 0 ? null : avatarId));
|
Recipient.applyCached(Address.fromSerialized(groupID), recipient -> recipient.setGroupAvatarId(avatarId == 0 ? null : avatarId));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void updateMembers(String groupId, List<Address> members) {
|
public void updateMembers(String groupId, List<Address> members) {
|
||||||
|
@ -92,9 +92,9 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper {
|
|||||||
private static final int lokiV13 = 34;
|
private static final int lokiV13 = 34;
|
||||||
private static final int lokiV14_BACKUP_FILES = 35;
|
private static final int lokiV14_BACKUP_FILES = 35;
|
||||||
private static final int lokiV15 = 36;
|
private static final int lokiV15 = 36;
|
||||||
private static final int lokiV16_AUDIO_ATTACHMENT_EXTRAS = 37;
|
private static final int lokiV16 = 37;
|
||||||
|
|
||||||
private static final int DATABASE_VERSION = lokiV16_AUDIO_ATTACHMENT_EXTRAS;
|
private static final int DATABASE_VERSION = lokiV16;
|
||||||
private static final String DATABASE_NAME = "signal.db";
|
private static final String DATABASE_NAME = "signal.db";
|
||||||
|
|
||||||
private final Context context;
|
private final Context context;
|
||||||
@ -156,6 +156,7 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper {
|
|||||||
db.execSQL(LokiAPIDatabase.getCreateSessionRequestSentTimestampTableCommand());
|
db.execSQL(LokiAPIDatabase.getCreateSessionRequestSentTimestampTableCommand());
|
||||||
db.execSQL(LokiAPIDatabase.getCreateSessionRequestProcessedTimestampTableCommand());
|
db.execSQL(LokiAPIDatabase.getCreateSessionRequestProcessedTimestampTableCommand());
|
||||||
db.execSQL(LokiAPIDatabase.getCreateOpenGroupPublicKeyTableCommand());
|
db.execSQL(LokiAPIDatabase.getCreateOpenGroupPublicKeyTableCommand());
|
||||||
|
db.execSQL(LokiAPIDatabase.getCreateOpenGroupProfilePictureTableCommand());
|
||||||
db.execSQL(LokiPreKeyBundleDatabase.getCreateTableCommand());
|
db.execSQL(LokiPreKeyBundleDatabase.getCreateTableCommand());
|
||||||
db.execSQL(LokiPreKeyRecordDatabase.getCreateTableCommand());
|
db.execSQL(LokiPreKeyRecordDatabase.getCreateTableCommand());
|
||||||
db.execSQL(LokiMessageDatabase.getCreateMessageIDTableCommand());
|
db.execSQL(LokiMessageDatabase.getCreateMessageIDTableCommand());
|
||||||
@ -633,9 +634,10 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper {
|
|||||||
db.execSQL(SharedSenderKeysDatabase.getCreateOldClosedGroupRatchetTableCommand());
|
db.execSQL(SharedSenderKeysDatabase.getCreateOldClosedGroupRatchetTableCommand());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (oldVersion < lokiV16_AUDIO_ATTACHMENT_EXTRAS) {
|
if (oldVersion < lokiV16) {
|
||||||
db.execSQL("ALTER TABLE part ADD COLUMN audio_visual_samples BLOB");
|
db.execSQL("ALTER TABLE part ADD COLUMN audio_visual_samples BLOB");
|
||||||
db.execSQL("ALTER TABLE part ADD COLUMN audio_duration INTEGER");
|
db.execSQL("ALTER TABLE part ADD COLUMN audio_duration INTEGER");
|
||||||
|
db.execSQL(LokiAPIDatabase.getCreateOpenGroupProfilePictureTableCommand());
|
||||||
}
|
}
|
||||||
|
|
||||||
db.setTransactionSuccessful();
|
db.setTransactionSuccessful();
|
||||||
|
@ -159,6 +159,7 @@ public class SignalCommunicationModule {
|
|||||||
DatabaseFactory.getLokiPreKeyBundleDatabase(context),
|
DatabaseFactory.getLokiPreKeyBundleDatabase(context),
|
||||||
new SessionResetImplementation(context),
|
new SessionResetImplementation(context),
|
||||||
DatabaseFactory.getLokiUserDatabase(context),
|
DatabaseFactory.getLokiUserDatabase(context),
|
||||||
|
DatabaseFactory.getGroupDatabase(context),
|
||||||
((ApplicationContext)context.getApplicationContext()).broadcaster);
|
((ApplicationContext)context.getApplicationContext()).broadcaster);
|
||||||
} else {
|
} else {
|
||||||
this.messageSender.setMessagePipe(IncomingMessageObserver.getPipe(), IncomingMessageObserver.getUnidentifiedPipe());
|
this.messageSender.setMessagePipe(IncomingMessageObserver.getPipe(), IncomingMessageObserver.getUnidentifiedPipe());
|
||||||
|
@ -85,7 +85,7 @@ public class GroupManager {
|
|||||||
groupDatabase.create(groupId, name, new LinkedList<>(memberAddresses), null, null, new LinkedList<>(adminAddresses));
|
groupDatabase.create(groupId, name, new LinkedList<>(memberAddresses), null, null, new LinkedList<>(adminAddresses));
|
||||||
|
|
||||||
if (!mms) {
|
if (!mms) {
|
||||||
groupDatabase.updateAvatar(groupId, avatarBytes);
|
groupDatabase.updateProfilePicture(groupId, avatarBytes);
|
||||||
DatabaseFactory.getRecipientDatabase(context).setProfileSharing(groupRecipient, true);
|
DatabaseFactory.getRecipientDatabase(context).setProfileSharing(groupRecipient, true);
|
||||||
return sendGroupUpdate(context, groupId, memberAddresses, name, avatarBytes, adminAddresses);
|
return sendGroupUpdate(context, groupId, memberAddresses, name, avatarBytes, adminAddresses);
|
||||||
} else {
|
} else {
|
||||||
@ -125,7 +125,7 @@ public class GroupManager {
|
|||||||
memberAddresses.add(Address.fromSerialized(TextSecurePreferences.getLocalNumber(context)));
|
memberAddresses.add(Address.fromSerialized(TextSecurePreferences.getLocalNumber(context)));
|
||||||
groupDatabase.create(groupId, name, new LinkedList<>(memberAddresses), null, null, new LinkedList<>());
|
groupDatabase.create(groupId, name, new LinkedList<>(memberAddresses), null, null, new LinkedList<>());
|
||||||
|
|
||||||
groupDatabase.updateAvatar(groupId, avatarBytes);
|
groupDatabase.updateProfilePicture(groupId, avatarBytes);
|
||||||
|
|
||||||
long threadID = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(groupRecipient, ThreadDatabase.DistributionTypes.CONVERSATION);
|
long threadID = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(groupRecipient, ThreadDatabase.DistributionTypes.CONVERSATION);
|
||||||
return new GroupActionResult(groupRecipient, threadID);
|
return new GroupActionResult(groupRecipient, threadID);
|
||||||
@ -148,7 +148,7 @@ public class GroupManager {
|
|||||||
groupDatabase.updateMembers(groupId, new LinkedList<>(memberAddresses));
|
groupDatabase.updateMembers(groupId, new LinkedList<>(memberAddresses));
|
||||||
groupDatabase.updateAdmins(groupId, new LinkedList<>(adminAddresses));
|
groupDatabase.updateAdmins(groupId, new LinkedList<>(adminAddresses));
|
||||||
groupDatabase.updateTitle(groupId, name);
|
groupDatabase.updateTitle(groupId, name);
|
||||||
groupDatabase.updateAvatar(groupId, avatarBytes);
|
groupDatabase.updateProfilePicture(groupId, avatarBytes);
|
||||||
|
|
||||||
if (!GroupUtil.isMmsGroup(groupId)) {
|
if (!GroupUtil.isMmsGroup(groupId)) {
|
||||||
return sendGroupUpdate(context, groupId, memberAddresses, name, avatarBytes, adminAddresses);
|
return sendGroupUpdate(context, groupId, memberAddresses, name, avatarBytes, adminAddresses);
|
||||||
|
@ -95,7 +95,7 @@ public class AvatarDownloadJob extends BaseJob implements InjectableType {
|
|||||||
InputStream inputStream = receiver.retrieveAttachment(pointer, attachment, MAX_AVATAR_SIZE);
|
InputStream inputStream = receiver.retrieveAttachment(pointer, attachment, MAX_AVATAR_SIZE);
|
||||||
Bitmap avatar = BitmapUtil.createScaledBitmap(context, new AttachmentModel(attachment, key, 0, digest), 500, 500);
|
Bitmap avatar = BitmapUtil.createScaledBitmap(context, new AttachmentModel(attachment, key, 0, digest), 500, 500);
|
||||||
|
|
||||||
database.updateAvatar(groupId, avatar);
|
database.updateProfilePicture(groupId, avatar);
|
||||||
inputStream.close();
|
inputStream.close();
|
||||||
}
|
}
|
||||||
} catch (BitmapDecodingException | NonSuccessfulResponseCodeException | InvalidMessageException e) {
|
} catch (BitmapDecodingException | NonSuccessfulResponseCodeException | InvalidMessageException e) {
|
||||||
|
@ -342,20 +342,18 @@ class HomeActivity : PassphraseRequiredActionBarActivity, ConversationClickListe
|
|||||||
val threadID = thread.threadId
|
val threadID = thread.threadId
|
||||||
val recipient = thread.recipient
|
val recipient = thread.recipient
|
||||||
val threadDB = DatabaseFactory.getThreadDatabase(this)
|
val threadDB = DatabaseFactory.getThreadDatabase(this)
|
||||||
val deleteThread = object : Runnable {
|
val deleteThread = Runnable {
|
||||||
|
AsyncTask.execute {
|
||||||
override fun run() {
|
val publicChat = DatabaseFactory.getLokiThreadDatabase(this@HomeActivity).getPublicChat(threadID)
|
||||||
AsyncTask.execute {
|
if (publicChat != null) {
|
||||||
val publicChat = DatabaseFactory.getLokiThreadDatabase(this@HomeActivity).getPublicChat(threadID)
|
val apiDB = DatabaseFactory.getLokiAPIDatabase(this@HomeActivity)
|
||||||
if (publicChat != null) {
|
apiDB.removeLastMessageServerID(publicChat.channel, publicChat.server)
|
||||||
val apiDB = DatabaseFactory.getLokiAPIDatabase(this@HomeActivity)
|
apiDB.removeLastDeletionServerID(publicChat.channel, publicChat.server)
|
||||||
apiDB.removeLastMessageServerID(publicChat.channel, publicChat.server)
|
apiDB.clearOpenGroupProfilePictureURL(publicChat.channel, publicChat.server)
|
||||||
apiDB.removeLastDeletionServerID(publicChat.channel, publicChat.server)
|
ApplicationContext.getInstance(this@HomeActivity).publicChatAPI!!.leave(publicChat.channel, publicChat.server)
|
||||||
ApplicationContext.getInstance(this@HomeActivity).publicChatAPI!!.leave(publicChat.channel, publicChat.server)
|
|
||||||
}
|
|
||||||
threadDB.deleteConversation(threadID)
|
|
||||||
ApplicationContext.getInstance(this@HomeActivity).messageNotifier.updateNotification(this@HomeActivity)
|
|
||||||
}
|
}
|
||||||
|
threadDB.deleteConversation(threadID)
|
||||||
|
ApplicationContext.getInstance(this@HomeActivity).messageNotifier.updateNotification(this@HomeActivity)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val dialogMessage = if (recipient.isGroupRecipient) R.string.activity_home_leave_group_dialog_message else R.string.activity_home_delete_conversation_dialog_message
|
val dialogMessage = if (recipient.isGroupRecipient) R.string.activity_home_leave_group_dialog_message else R.string.activity_home_delete_conversation_dialog_message
|
||||||
|
@ -82,7 +82,7 @@ class PathActivity : PassphraseRequiredActionBarActivity() {
|
|||||||
|
|
||||||
private fun update(isAnimated: Boolean) {
|
private fun update(isAnimated: Boolean) {
|
||||||
pathRowsContainer.removeAllViews()
|
pathRowsContainer.removeAllViews()
|
||||||
if (OnionRequestAPI.paths.count() >= OnionRequestAPI.pathCount) {
|
if (OnionRequestAPI.paths.isNotEmpty()) {
|
||||||
val path = OnionRequestAPI.paths.firstOrNull() ?: return finish()
|
val path = OnionRequestAPI.paths.firstOrNull() ?: return finish()
|
||||||
val dotAnimationRepeatInterval = path.count().toLong() * 1000 + 1000
|
val dotAnimationRepeatInterval = path.count().toLong() * 1000 + 1000
|
||||||
val pathRows = path.mapIndexed { index, snode ->
|
val pathRows = path.mapIndexed { index, snode ->
|
||||||
|
@ -50,13 +50,16 @@ class BackgroundPollJob private constructor(parameters: Parameters) : BaseJob(pa
|
|||||||
Log.d("Loki", "Performing background poll.")
|
Log.d("Loki", "Performing background poll.")
|
||||||
val userPublicKey = TextSecurePreferences.getLocalNumber(context)
|
val userPublicKey = TextSecurePreferences.getLocalNumber(context)
|
||||||
val promises = mutableListOf<Promise<Unit, Exception>>()
|
val promises = mutableListOf<Promise<Unit, Exception>>()
|
||||||
val promise = SnodeAPI.shared.getMessages(userPublicKey).map { envelopes ->
|
if (!TextSecurePreferences.isUsingFCM(context)) {
|
||||||
envelopes.forEach {
|
Log.d("Loki", "Not using FCM; polling for contacts and closed groups.")
|
||||||
PushContentReceiveJob(context).processEnvelope(SignalServiceEnvelope(it), false)
|
val promise = SnodeAPI.shared.getMessages(userPublicKey).map { envelopes ->
|
||||||
|
envelopes.forEach {
|
||||||
|
PushContentReceiveJob(context).processEnvelope(SignalServiceEnvelope(it), false)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
promises.add(promise)
|
||||||
|
promises.addAll(ClosedGroupPoller.shared.pollOnce())
|
||||||
}
|
}
|
||||||
promises.add(promise)
|
|
||||||
promises.addAll(ClosedGroupPoller.shared.pollOnce())
|
|
||||||
val openGroups = DatabaseFactory.getLokiThreadDatabase(context).getAllPublicChats().map { it.value }
|
val openGroups = DatabaseFactory.getLokiThreadDatabase(context).getAllPublicChats().map { it.value }
|
||||||
for (openGroup in openGroups) {
|
for (openGroup in openGroups) {
|
||||||
val poller = PublicChatPoller(context, openGroup)
|
val poller = PublicChatPoller(context, openGroup)
|
||||||
|
@ -2,6 +2,7 @@ package org.thoughtcrime.securesms.loki.api
|
|||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.database.ContentObserver
|
import android.database.ContentObserver
|
||||||
|
import android.graphics.Bitmap
|
||||||
import android.text.TextUtils
|
import android.text.TextUtils
|
||||||
import nl.komponents.kovenant.Promise
|
import nl.komponents.kovenant.Promise
|
||||||
import nl.komponents.kovenant.functional.bind
|
import nl.komponents.kovenant.functional.bind
|
||||||
@ -10,8 +11,10 @@ import org.thoughtcrime.securesms.ApplicationContext
|
|||||||
import org.thoughtcrime.securesms.database.DatabaseContentProviders
|
import org.thoughtcrime.securesms.database.DatabaseContentProviders
|
||||||
import org.thoughtcrime.securesms.database.DatabaseFactory
|
import org.thoughtcrime.securesms.database.DatabaseFactory
|
||||||
import org.thoughtcrime.securesms.groups.GroupManager
|
import org.thoughtcrime.securesms.groups.GroupManager
|
||||||
|
import org.thoughtcrime.securesms.util.BitmapUtil
|
||||||
import org.thoughtcrime.securesms.util.TextSecurePreferences
|
import org.thoughtcrime.securesms.util.TextSecurePreferences
|
||||||
import org.thoughtcrime.securesms.util.Util
|
import org.thoughtcrime.securesms.util.Util
|
||||||
|
import org.whispersystems.signalservice.loki.api.opengroups.PublicChatInfo
|
||||||
import org.whispersystems.signalservice.loki.api.opengroups.PublicChat
|
import org.whispersystems.signalservice.loki.api.opengroups.PublicChat
|
||||||
|
|
||||||
class PublicChatManager(private val context: Context) {
|
class PublicChatManager(private val context: Context) {
|
||||||
@ -24,8 +27,8 @@ class PublicChatManager(private val context: Context) {
|
|||||||
var areAllCaughtUp = true
|
var areAllCaughtUp = true
|
||||||
refreshChatsAndPollers()
|
refreshChatsAndPollers()
|
||||||
for ((threadID, chat) in chats) {
|
for ((threadID, chat) in chats) {
|
||||||
val poller = pollers[threadID] ?: PublicChatPoller(context, chat)
|
val poller = pollers[threadID]
|
||||||
areAllCaughtUp = areAllCaughtUp && poller.isCaughtUp
|
areAllCaughtUp = if (poller != null) areAllCaughtUp && poller.isCaughtUp else true
|
||||||
}
|
}
|
||||||
return areAllCaughtUp
|
return areAllCaughtUp
|
||||||
}
|
}
|
||||||
@ -56,7 +59,8 @@ class PublicChatManager(private val context: Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public fun addChat(server: String, channel: Long): Promise<PublicChat, Exception> {
|
public fun addChat(server: String, channel: Long): Promise<PublicChat, Exception> {
|
||||||
val groupChatAPI = ApplicationContext.getInstance(context).publicChatAPI ?: return Promise.ofFail(IllegalStateException("LokiPublicChatAPI is not set!"))
|
val groupChatAPI = ApplicationContext.getInstance(context).publicChatAPI
|
||||||
|
?: return Promise.ofFail(IllegalStateException("LokiPublicChatAPI is not set!"))
|
||||||
return groupChatAPI.getAuthToken(server).bind {
|
return groupChatAPI.getAuthToken(server).bind {
|
||||||
groupChatAPI.getChannelInfo(channel, server)
|
groupChatAPI.getChannelInfo(channel, server)
|
||||||
}.map {
|
}.map {
|
||||||
@ -64,12 +68,18 @@ class PublicChatManager(private val context: Context) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public fun addChat(server: String, channel: Long, name: String): PublicChat {
|
public fun addChat(server: String, channel: Long, info: PublicChatInfo): PublicChat {
|
||||||
val chat = PublicChat(channel, server, name, true)
|
val chat = PublicChat(channel, server, info.displayName, true)
|
||||||
var threadID = GroupManager.getOpenGroupThreadID(chat.id, context)
|
var threadID = GroupManager.getOpenGroupThreadID(chat.id, context)
|
||||||
|
var profilePicture: Bitmap? = null
|
||||||
// Create the group if we don't have one
|
// Create the group if we don't have one
|
||||||
if (threadID < 0) {
|
if (threadID < 0) {
|
||||||
val result = GroupManager.createOpenGroup(chat.id, context, null, chat.displayName)
|
if (info.profilePictureURL.isNotEmpty()) {
|
||||||
|
val profilePictureAsByteArray = ApplicationContext.getInstance(context).publicChatAPI
|
||||||
|
?.downloadOpenGroupProfilePicture(server, info.profilePictureURL)
|
||||||
|
profilePicture = BitmapUtil.fromByteArray(profilePictureAsByteArray)
|
||||||
|
}
|
||||||
|
val result = GroupManager.createOpenGroup(chat.id, context, profilePicture, chat.displayName)
|
||||||
threadID = result.threadId
|
threadID = result.threadId
|
||||||
}
|
}
|
||||||
DatabaseFactory.getLokiThreadDatabase(context).setPublicChat(chat, threadID)
|
DatabaseFactory.getLokiThreadDatabase(context).setPublicChat(chat, threadID)
|
||||||
|
@ -46,7 +46,8 @@ class PublicChatPoller(private val context: Context, private val group: PublicCh
|
|||||||
val userPrivateKey = IdentityKeyUtil.getIdentityKeyPair(context).privateKey.serialize()
|
val userPrivateKey = IdentityKeyUtil.getIdentityKeyPair(context).privateKey.serialize()
|
||||||
val lokiAPIDatabase = DatabaseFactory.getLokiAPIDatabase(context)
|
val lokiAPIDatabase = DatabaseFactory.getLokiAPIDatabase(context)
|
||||||
val lokiUserDatabase = DatabaseFactory.getLokiUserDatabase(context)
|
val lokiUserDatabase = DatabaseFactory.getLokiUserDatabase(context)
|
||||||
PublicChatAPI(userHexEncodedPublicKey, userPrivateKey, lokiAPIDatabase, lokiUserDatabase)
|
val openGroupDatabase = DatabaseFactory.getGroupDatabase(context)
|
||||||
|
PublicChatAPI(userHexEncodedPublicKey, userPrivateKey, lokiAPIDatabase, lokiUserDatabase, openGroupDatabase)
|
||||||
}()
|
}()
|
||||||
// endregion
|
// endregion
|
||||||
|
|
||||||
|
@ -6,7 +6,6 @@ import android.util.Log
|
|||||||
import org.thoughtcrime.securesms.database.Database
|
import org.thoughtcrime.securesms.database.Database
|
||||||
import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper
|
import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper
|
||||||
import org.thoughtcrime.securesms.loki.utilities.*
|
import org.thoughtcrime.securesms.loki.utilities.*
|
||||||
import org.thoughtcrime.securesms.util.TextSecurePreferences
|
|
||||||
import org.whispersystems.signalservice.loki.api.Snode
|
import org.whispersystems.signalservice.loki.api.Snode
|
||||||
import org.whispersystems.signalservice.loki.database.LokiAPIDatabaseProtocol
|
import org.whispersystems.signalservice.loki.database.LokiAPIDatabaseProtocol
|
||||||
import org.whispersystems.signalservice.loki.protocol.shelved.multidevice.DeviceLink
|
import org.whispersystems.signalservice.loki.protocol.shelved.multidevice.DeviceLink
|
||||||
@ -71,6 +70,10 @@ class LokiAPIDatabase(context: Context, helper: SQLCipherOpenHelper) : Database(
|
|||||||
// Open group public keys
|
// Open group public keys
|
||||||
private val openGroupPublicKeyTable = "open_group_public_keys"
|
private val openGroupPublicKeyTable = "open_group_public_keys"
|
||||||
@JvmStatic val createOpenGroupPublicKeyTableCommand = "CREATE TABLE $openGroupPublicKeyTable ($server STRING PRIMARY KEY, $publicKey INTEGER DEFAULT 0);"
|
@JvmStatic val createOpenGroupPublicKeyTableCommand = "CREATE TABLE $openGroupPublicKeyTable ($server STRING PRIMARY KEY, $publicKey INTEGER DEFAULT 0);"
|
||||||
|
// Open group profile picture cache
|
||||||
|
private val openGroupProfilePictureTable = "open_group_avatar_cache"
|
||||||
|
private val openGroupProfilePicture = "open_group_avatar"
|
||||||
|
@JvmStatic val createOpenGroupProfilePictureTableCommand = "CREATE TABLE $openGroupProfilePictureTable ($publicChatID STRING PRIMARY KEY, $openGroupProfilePicture TEXT NULLABLE DEFAULT NULL);"
|
||||||
|
|
||||||
// region Deprecated
|
// region Deprecated
|
||||||
private val deviceLinkCache = "loki_pairing_authorisation_cache"
|
private val deviceLinkCache = "loki_pairing_authorisation_cache"
|
||||||
@ -114,6 +117,30 @@ class LokiAPIDatabase(context: Context, helper: SQLCipherOpenHelper) : Database(
|
|||||||
database.insertOrUpdate(snodePoolTable, row, "${Companion.dummyKey} = ?", wrap("dummy_key"))
|
database.insertOrUpdate(snodePoolTable, row, "${Companion.dummyKey} = ?", wrap("dummy_key"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun setOnionRequestPaths(newValue: List<List<Snode>>) {
|
||||||
|
// FIXME: This approach assumes either 1 or 2 paths of length 3 each. We should do better than this.
|
||||||
|
val database = databaseHelper.writableDatabase
|
||||||
|
fun set(indexPath: String, snode: Snode) {
|
||||||
|
var snodeAsString = "${snode.address}-${snode.port}"
|
||||||
|
val keySet = snode.publicKeySet
|
||||||
|
if (keySet != null) {
|
||||||
|
snodeAsString += "-${keySet.ed25519Key}-${keySet.x25519Key}"
|
||||||
|
}
|
||||||
|
val row = wrap(mapOf( Companion.indexPath to indexPath, Companion.snode to snodeAsString ))
|
||||||
|
database.insertOrUpdate(onionRequestPathTable, row, "${Companion.indexPath} = ?", wrap(indexPath))
|
||||||
|
}
|
||||||
|
Log.d("Loki", "Persisting onion request paths to database.")
|
||||||
|
clearOnionRequestPaths()
|
||||||
|
if (newValue.count() < 1) { return }
|
||||||
|
val path0 = newValue[0]
|
||||||
|
if (path0.count() != 3) { return }
|
||||||
|
set("0-0", path0[0]); set("0-1", path0[1]); set("0-2", path0[2])
|
||||||
|
if (newValue.count() < 2) { return }
|
||||||
|
val path1 = newValue[1]
|
||||||
|
if (path1.count() != 3) { return }
|
||||||
|
set("1-0", path1[0]); set("1-1", path1[1]); set("1-2", path1[2])
|
||||||
|
}
|
||||||
|
|
||||||
override fun getOnionRequestPaths(): List<List<Snode>> {
|
override fun getOnionRequestPaths(): List<List<Snode>> {
|
||||||
val database = databaseHelper.readableDatabase
|
val database = databaseHelper.readableDatabase
|
||||||
fun get(indexPath: String): Snode? {
|
fun get(indexPath: String): Snode? {
|
||||||
@ -131,10 +158,16 @@ class LokiAPIDatabase(context: Context, helper: SQLCipherOpenHelper) : Database(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val path0Snode0 = get("0-0") ?: return listOf(); val path0Snode1 = get("0-1") ?: return listOf()
|
val result = mutableListOf<List<Snode>>()
|
||||||
val path0Snode2 = get("0-2") ?: return listOf(); val path1Snode0 = get("1-0") ?: return listOf()
|
val path0Snode0 = get("0-0"); val path0Snode1 = get("0-1"); val path0Snode2 = get("0-2")
|
||||||
val path1Snode1 = get("1-1") ?: return listOf(); val path1Snode2 = get("1-2") ?: return listOf()
|
if (path0Snode0 != null && path0Snode1 != null && path0Snode2 != null) {
|
||||||
return listOf( listOf( path0Snode0, path0Snode1, path0Snode2 ), listOf( path1Snode0, path1Snode1, path1Snode2 ) )
|
result.add(listOf( path0Snode0, path0Snode1, path0Snode2 ))
|
||||||
|
}
|
||||||
|
val path1Snode0 = get("1-0"); val path1Snode1 = get("1-1"); val path1Snode2 = get("1-2")
|
||||||
|
if (path1Snode0 != null && path1Snode1 != null && path1Snode2 != null) {
|
||||||
|
result.add(listOf( path1Snode0, path1Snode1, path1Snode2 ))
|
||||||
|
}
|
||||||
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun clearOnionRequestPaths() {
|
override fun clearOnionRequestPaths() {
|
||||||
@ -147,28 +180,6 @@ class LokiAPIDatabase(context: Context, helper: SQLCipherOpenHelper) : Database(
|
|||||||
delete("1-1"); delete("1-2")
|
delete("1-1"); delete("1-2")
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun setOnionRequestPaths(newValue: List<List<Snode>>) {
|
|
||||||
// TODO: Make this work with arbitrary paths
|
|
||||||
if (newValue.count() != 2) { return }
|
|
||||||
val path0 = newValue[0]
|
|
||||||
val path1 = newValue[1]
|
|
||||||
if (path0.count() != 3 || path1.count() != 3) { return }
|
|
||||||
Log.d("Loki", "Persisting onion request paths to database.")
|
|
||||||
val database = databaseHelper.writableDatabase
|
|
||||||
fun set(indexPath: String, snode: Snode) {
|
|
||||||
var snodeAsString = "${snode.address}-${snode.port}"
|
|
||||||
val keySet = snode.publicKeySet
|
|
||||||
if (keySet != null) {
|
|
||||||
snodeAsString += "-${keySet.ed25519Key}-${keySet.x25519Key}"
|
|
||||||
}
|
|
||||||
val row = wrap(mapOf( Companion.indexPath to indexPath, Companion.snode to snodeAsString ))
|
|
||||||
database.insertOrUpdate(onionRequestPathTable, row, "${Companion.indexPath} = ?", wrap(indexPath))
|
|
||||||
}
|
|
||||||
set("0-0", path0[0]); set("0-1", path0[1])
|
|
||||||
set("0-2", path0[2]); set("1-0", path1[0])
|
|
||||||
set("1-1", path1[1]); set("1-2", path1[2])
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getSwarm(publicKey: String): Set<Snode>? {
|
override fun getSwarm(publicKey: String): Set<Snode>? {
|
||||||
val database = databaseHelper.readableDatabase
|
val database = databaseHelper.readableDatabase
|
||||||
return database.get(swarmTable, "${Companion.swarmPublicKey} = ?", wrap(publicKey)) { cursor ->
|
return database.get(swarmTable, "${Companion.swarmPublicKey} = ?", wrap(publicKey)) { cursor ->
|
||||||
@ -343,6 +354,27 @@ class LokiAPIDatabase(context: Context, helper: SQLCipherOpenHelper) : Database(
|
|||||||
database.insertOrUpdate(openGroupPublicKeyTable, row, "${LokiAPIDatabase.server} = ?", wrap(server))
|
database.insertOrUpdate(openGroupPublicKeyTable, row, "${LokiAPIDatabase.server} = ?", wrap(server))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun getOpenGroupProfilePictureURL(group: Long, server: String): String? {
|
||||||
|
val database = databaseHelper.readableDatabase
|
||||||
|
val index = "$server.$group"
|
||||||
|
return database.get(openGroupProfilePictureTable, "$publicChatID = ?", wrap(index)) { cursor ->
|
||||||
|
cursor.getString(openGroupProfilePicture)
|
||||||
|
}?.toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setOpenGroupProfilePictureURL(group: Long, server: String, newValue: String) {
|
||||||
|
val database = databaseHelper.writableDatabase
|
||||||
|
val index = "$server.$group"
|
||||||
|
val row = wrap(mapOf(publicChatID to index, openGroupProfilePicture to newValue))
|
||||||
|
database.insertOrUpdate(openGroupProfilePictureTable, row, "$publicChatID = ?", wrap(index))
|
||||||
|
}
|
||||||
|
|
||||||
|
fun clearOpenGroupProfilePictureURL(group: Long, server: String): Boolean {
|
||||||
|
val database = databaseHelper.writableDatabase
|
||||||
|
val index = "$server.$group"
|
||||||
|
return database.delete(openGroupProfilePictureTable, "$publicChatID = ?", arrayOf(index)) > 0
|
||||||
|
}
|
||||||
|
|
||||||
// region Deprecated
|
// region Deprecated
|
||||||
override fun getDeviceLinks(publicKey: String): Set<DeviceLink> {
|
override fun getDeviceLinks(publicKey: String): Set<DeviceLink> {
|
||||||
return setOf()
|
return setOf()
|
||||||
|
@ -85,7 +85,7 @@ class PathStatusView : View {
|
|||||||
private fun handlePathsBuiltEvent() { update() }
|
private fun handlePathsBuiltEvent() { update() }
|
||||||
|
|
||||||
private fun update() {
|
private fun update() {
|
||||||
if (OnionRequestAPI.paths.count() >= OnionRequestAPI.pathCount) {
|
if (OnionRequestAPI.paths.isNotEmpty()) {
|
||||||
setBackgroundResource(R.drawable.accent_dot)
|
setBackgroundResource(R.drawable.accent_dot)
|
||||||
mainColor = resources.getColorWithID(R.color.accent, context.theme)
|
mainColor = resources.getColorWithID(R.color.accent, context.theme)
|
||||||
sessionShadowColor = resources.getColorWithID(R.color.accent, context.theme)
|
sessionShadowColor = resources.getColorWithID(R.color.accent, context.theme)
|
||||||
|
@ -68,32 +68,30 @@ class ProfilePictureView : RelativeLayout {
|
|||||||
return result ?: publicKey
|
return result ?: publicKey
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (recipient.isGroupRecipient) {
|
fun isOpenGroupWithProfilePicture(recipient: Recipient): Boolean {
|
||||||
if ("Session Public Chat" == recipient.name) {
|
return recipient.isOpenGroupRecipient && recipient.groupAvatarId != null
|
||||||
publicKey = ""
|
}
|
||||||
displayName = ""
|
if (recipient.isGroupRecipient && !isOpenGroupWithProfilePicture(recipient)) {
|
||||||
additionalPublicKey = null
|
val users = MentionsManager.shared.userPublicKeyCache[threadID]?.toMutableList() ?: mutableListOf()
|
||||||
isRSSFeed = true
|
users.remove(TextSecurePreferences.getLocalNumber(context))
|
||||||
} else {
|
val masterPublicKey = TextSecurePreferences.getMasterHexEncodedPublicKey(context)
|
||||||
val users = MentionsManager.shared.userPublicKeyCache[threadID]?.toMutableList() ?: mutableListOf()
|
if (masterPublicKey != null) {
|
||||||
users.remove(TextSecurePreferences.getLocalNumber(context))
|
users.remove(masterPublicKey)
|
||||||
val masterPublicKey = TextSecurePreferences.getMasterHexEncodedPublicKey(context)
|
|
||||||
if (masterPublicKey != null) {
|
|
||||||
users.remove(masterPublicKey)
|
|
||||||
}
|
|
||||||
val randomUsers = users.sorted().toMutableList() // Sort to provide a level of stability
|
|
||||||
if (users.count() == 1) {
|
|
||||||
val userPublicKey = TextSecurePreferences.getLocalNumber(context)
|
|
||||||
randomUsers.add(0, userPublicKey) // Ensure the current user is at the back visually
|
|
||||||
}
|
|
||||||
val pk = randomUsers.getOrNull(0) ?: ""
|
|
||||||
publicKey = pk
|
|
||||||
displayName = getUserDisplayName(pk)
|
|
||||||
val apk = randomUsers.getOrNull(1) ?: ""
|
|
||||||
additionalPublicKey = apk
|
|
||||||
additionalDisplayName = getUserDisplayName(apk)
|
|
||||||
isRSSFeed = recipient.name == "Loki News" || recipient.name == "Session Updates"
|
|
||||||
}
|
}
|
||||||
|
val randomUsers = users.sorted().toMutableList() // Sort to provide a level of stability
|
||||||
|
if (users.count() == 1) {
|
||||||
|
val userPublicKey = TextSecurePreferences.getLocalNumber(context)
|
||||||
|
randomUsers.add(0, userPublicKey) // Ensure the current user is at the back visually
|
||||||
|
}
|
||||||
|
val pk = randomUsers.getOrNull(0) ?: ""
|
||||||
|
publicKey = pk
|
||||||
|
displayName = getUserDisplayName(pk)
|
||||||
|
val apk = randomUsers.getOrNull(1) ?: ""
|
||||||
|
additionalPublicKey = apk
|
||||||
|
additionalDisplayName = getUserDisplayName(apk)
|
||||||
|
isRSSFeed = recipient.name == "Loki News" ||
|
||||||
|
recipient.name == "Session Updates" ||
|
||||||
|
recipient.name == "Session Public Chat"
|
||||||
} else {
|
} else {
|
||||||
publicKey = recipient.address.toString()
|
publicKey = recipient.address.toString()
|
||||||
displayName = getUserDisplayName(publicKey)
|
displayName = getUserDisplayName(publicKey)
|
||||||
|
@ -84,13 +84,17 @@ public class SingleRecipientNotificationBuilder extends AbstractNotificationBuil
|
|||||||
ContactPhoto contactPhoto = recipient.getContactPhoto();
|
ContactPhoto contactPhoto = recipient.getContactPhoto();
|
||||||
if (contactPhoto != null) {
|
if (contactPhoto != null) {
|
||||||
try {
|
try {
|
||||||
setLargeIcon(GlideApp.with(context.getApplicationContext())
|
// AC: For some reason, if not use ".asBitmap()" method, the returned BitmapDrawable
|
||||||
.load(contactPhoto)
|
// wraps a recycled bitmap and leads to a crash.
|
||||||
.diskCacheStrategy(DiskCacheStrategy.ALL)
|
Bitmap iconBitmap = GlideApp.with(context.getApplicationContext())
|
||||||
.circleCrop()
|
.asBitmap()
|
||||||
.submit(context.getResources().getDimensionPixelSize(android.R.dimen.notification_large_icon_width),
|
.load(contactPhoto)
|
||||||
context.getResources().getDimensionPixelSize(android.R.dimen.notification_large_icon_height))
|
.diskCacheStrategy(DiskCacheStrategy.ALL)
|
||||||
.get());
|
.circleCrop()
|
||||||
|
.submit(context.getResources().getDimensionPixelSize(android.R.dimen.notification_large_icon_width),
|
||||||
|
context.getResources().getDimensionPixelSize(android.R.dimen.notification_large_icon_height))
|
||||||
|
.get();
|
||||||
|
setLargeIcon(iconBitmap);
|
||||||
} catch (InterruptedException | ExecutionException e) {
|
} catch (InterruptedException | ExecutionException e) {
|
||||||
Log.w(TAG, e);
|
Log.w(TAG, e);
|
||||||
setLargeIcon(getPlaceholderDrawable(context, recipient));
|
setLargeIcon(getPlaceholderDrawable(context, recipient));
|
||||||
|
@ -419,6 +419,10 @@ public class Recipient implements RecipientModifiedListener {
|
|||||||
return address.isGroup();
|
return address.isGroup();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isOpenGroupRecipient() {
|
||||||
|
return address.isOpenGroup();
|
||||||
|
}
|
||||||
|
|
||||||
public boolean isMmsGroupRecipient() {
|
public boolean isMmsGroupRecipient() {
|
||||||
return address.isMmsGroup();
|
return address.isMmsGroup();
|
||||||
}
|
}
|
||||||
@ -505,6 +509,11 @@ public class Recipient implements RecipientModifiedListener {
|
|||||||
if (notify) notifyListeners();
|
if (notify) notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public synchronized Long getGroupAvatarId() {
|
||||||
|
return groupAvatarId;
|
||||||
|
}
|
||||||
|
|
||||||
public synchronized @Nullable Uri getMessageRingtone() {
|
public synchronized @Nullable Uri getMessageRingtone() {
|
||||||
if (messageRingtone != null && messageRingtone.getScheme() != null && messageRingtone.getScheme().startsWith("file")) {
|
if (messageRingtone != null && messageRingtone.getScheme() != null && messageRingtone.getScheme().startsWith("file")) {
|
||||||
return null;
|
return null;
|
||||||
|
Loading…
Reference in New Issue
Block a user