Merge remote-tracking branch 'upstream/dev' into libsession-integration

# Conflicts:
#	app/src/main/java/org/thoughtcrime/securesms/groups/OpenGroupManager.kt
#	libsession/src/main/java/org/session/libsession/messaging/sending_receiving/pollers/OpenGroupPoller.kt
This commit is contained in:
Morgan Pretty 2023-06-13 09:35:07 +10:00
commit 5ca7f76275
15 changed files with 160 additions and 130 deletions

View File

@ -159,8 +159,8 @@ dependencies {
testImplementation 'org.robolectric:shadows-multidex:4.4' testImplementation 'org.robolectric:shadows-multidex:4.4'
} }
def canonicalVersionCode = 336 def canonicalVersionCode = 338
def canonicalVersionName = "1.16.8" def canonicalVersionName = "1.16.9"
def postFixSize = 10 def postFixSize = 10
def abiPostFix = ['armeabi-v7a' : 1, def abiPostFix = ['armeabi-v7a' : 1,

View File

@ -69,7 +69,6 @@ class DeleteOptionsBottomSheet : BottomSheetDialogFragment(), View.OnClickListen
override fun onStart() { override fun onStart() {
super.onStart() super.onStart()
val window = dialog?.window ?: return val window = dialog?.window ?: return
val isLightMode = UiModeUtilities.isDayUiMode(requireContext()) window.setDimAmount(0.6f)
window.setDimAmount(if (isLightMode) 0.1f else 0.75f)
} }
} }

View File

@ -60,8 +60,7 @@ class ModalUrlBottomSheet(private val url: String): BottomSheetDialogFragment(),
override fun onStart() { override fun onStart() {
super.onStart() super.onStart()
val window = dialog?.window ?: return val window = dialog?.window ?: return
val isLightMode = UiModeUtilities.isDayUiMode(requireContext()) window.setDimAmount(0.6f)
window.setDimAmount(if (isLightMode) 0.1f else 0.75f)
} }
override fun onClick(v: View?) { override fun onClick(v: View?) {

View File

@ -340,11 +340,9 @@ class VisibleMessageView : LinearLayout {
val container = binding.messageInnerContainer val container = binding.messageInnerContainer
val content = binding.messageContentView.root val content = binding.messageContentView.root
val expiration = binding.expirationTimerView val expiration = binding.expirationTimerView
val spacing = binding.messageContentSpacing
container.removeAllViewsInLayout() container.removeAllViewsInLayout()
container.addView(if (message.isOutgoing) expiration else content) container.addView(if (message.isOutgoing) expiration else content)
container.addView(if (message.isOutgoing) content else expiration) container.addView(if (message.isOutgoing) content else expiration)
container.addView(spacing, if (message.isOutgoing) 0 else 2)
val containerParams = container.layoutParams as ConstraintLayout.LayoutParams val containerParams = container.layoutParams as ConstraintLayout.LayoutParams
containerParams.horizontalBias = if (message.isOutgoing) 1f else 0f containerParams.horizontalBias = if (message.isOutgoing) 1f else 0f
container.layoutParams = containerParams container.layoutParams = containerParams

View File

@ -14,9 +14,7 @@ open class BaseDialog : DialogFragment() {
val builder = AlertDialog.Builder(requireContext()) val builder = AlertDialog.Builder(requireContext())
setContentView(builder) setContentView(builder)
val result = builder.create() val result = builder.create()
result.window?.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT)) result.window?.setDimAmount(0.6f)
val isLightMode = UiModeUtilities.isDayUiMode(requireContext())
result.window?.setDimAmount(if (isLightMode) 0.1f else 0.75f)
return result return result
} }

View File

@ -15,7 +15,7 @@ import java.util.concurrent.Executors
object OpenGroupManager { object OpenGroupManager {
private val executorService = Executors.newScheduledThreadPool(4) private val executorService = Executors.newScheduledThreadPool(4)
private var pollers = mutableMapOf<String, OpenGroupPoller>() // One for each server private val pollers = mutableMapOf<String, OpenGroupPoller>() // One for each server
private var isPolling = false private var isPolling = false
private val pollUpdaterLock = Any() private val pollUpdaterLock = Any()
@ -47,11 +47,11 @@ object OpenGroupManager {
} }
val servers = serverGroups.map { it.server }.toSet() val servers = serverGroups.map { it.server }.toSet()
synchronized(pollUpdaterLock) {
servers.forEach { server -> servers.forEach { server ->
pollers[server]?.stop() // Shouldn't be necessary pollers[server]?.stop() // Shouldn't be necessary
val poller = OpenGroupPoller(server, executorService) pollers[server] = OpenGroupPoller(server, executorService).apply { startIfNeeded() }
poller.startIfNeeded() }
pollers[server] = poller
} }
} }
@ -66,7 +66,7 @@ object OpenGroupManager {
@WorkerThread @WorkerThread
fun add(server: String, room: String, publicKey: String, context: Context): Pair<Long,OpenGroupApi.RoomInfo?> { fun add(server: String, room: String, publicKey: String, context: Context): Pair<Long,OpenGroupApi.RoomInfo?> {
val openGroupID = "$server.$room" val openGroupID = "$server.$room"
var threadID = GroupManager.getOpenGroupThreadID(openGroupID, context) val threadID = GroupManager.getOpenGroupThreadID(openGroupID, context)
val storage = MessagingModuleConfiguration.shared.storage val storage = MessagingModuleConfiguration.shared.storage
val threadDB = DatabaseComponent.get(context).lokiThreadDatabase() val threadDB = DatabaseComponent.get(context).lokiThreadDatabase()
// Check it it's added already // Check it it's added already
@ -82,13 +82,16 @@ object OpenGroupManager {
// Get capabilities & room info // Get capabilities & room info
val (capabilities, info) = OpenGroupApi.getCapabilitiesAndRoomInfo(room, server).get() val (capabilities, info) = OpenGroupApi.getCapabilitiesAndRoomInfo(room, server).get()
storage.setServerCapabilities(server, capabilities.capabilities) storage.setServerCapabilities(server, capabilities.capabilities)
storage.setUserCount(room, server, info.activeUsers)
// Create the group locally if not available already // Create the group locally if not available already
if (threadID < 0) { if (threadID < 0) {
threadID = GroupManager.createOpenGroup(openGroupID, context, null, info.name).threadId GroupManager.createOpenGroup(openGroupID, context, null, info.name)
} }
val openGroup = OpenGroup(server = server, room = room, publicKey = publicKey, name = info.name, imageId = info.imageId, canWrite = info.write, infoUpdates = info.infoUpdates) OpenGroupPoller.handleRoomPollInfo(
threadDB.setOpenGroupChat(openGroup, threadID) server = server,
roomToken = room,
pollInfo = info.toPollInfo(),
createGroupIfMissingWithPublicKey = publicKey
)
return threadID to info return threadID to info
} }

View File

@ -95,7 +95,6 @@ class ConversationOptionsBottomSheet(private val parentContext: Context) : Botto
override fun onStart() { override fun onStart() {
super.onStart() super.onStart()
val window = dialog?.window ?: return val window = dialog?.window ?: return
val isLightMode = UiModeUtilities.isDayUiMode(requireContext()) window.setDimAmount(0.6f)
window.setDimAmount(if (isLightMode) 0.1f else 0.75f)
} }
} }

View File

@ -123,8 +123,7 @@ class UserDetailsBottomSheet: BottomSheetDialogFragment() {
override fun onStart() { override fun onStart() {
super.onStart() super.onStart()
val window = dialog?.window ?: return val window = dialog?.window ?: return
val isLightMode = UiModeUtilities.isDayUiMode(requireContext()) window.setDimAmount(0.6f)
window.setDimAmount(if (isLightMode) 0.1f else 0.75f)
} }
fun saveNickName(recipient: Recipient) = with(binding) { fun saveNickName(recipient: Recipient) = with(binding) {

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<shape
xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="?attr/dialog_background_color" />
<corners android:radius="?dialogCornerRadius" />
</shape>

View File

@ -11,6 +11,6 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_centerInParent="true" android:layout_centerInParent="true"
app:SpinKit_Color="?android:textColorPrimary" /> app:SpinKit_Color="?colorAccent" />
</RelativeLayout> </RelativeLayout>

View File

@ -79,8 +79,9 @@
<include layout="@layout/view_visible_message_content" <include layout="@layout/view_visible_message_content"
android:id="@+id/messageContentView" android:id="@+id/messageContentView"
android:layout_width="wrap_content" android:layout_width="0dp"
android:layout_height="wrap_content" /> android:layout_height="wrap_content"
android:layout_weight="1"/>
<org.thoughtcrime.securesms.conversation.v2.components.ExpirationTimerView <org.thoughtcrime.securesms.conversation.v2.components.ExpirationTimerView
android:id="@+id/expirationTimerView" android:id="@+id/expirationTimerView"
@ -90,14 +91,9 @@
android:layout_marginHorizontal="@dimen/small_spacing" android:layout_marginHorizontal="@dimen/small_spacing"
android:contentDescription="@string/AccessibilityId_timer_icon" android:contentDescription="@string/AccessibilityId_timer_icon"
android:visibility="invisible" android:visibility="invisible"
tools:visibility="visible" /> tools:visibility="visible"
tools:src="@drawable/timer60"
<View tools:tint="@color/black"/>
android:id="@+id/messageContentSpacing"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_weight="1"
android:minWidth="@dimen/very_large_spacing" />
</LinearLayout> </LinearLayout>

View File

@ -23,6 +23,7 @@
</style> </style>
<style name="ThemeOverlay.Session.AlertDialog" parent="ThemeOverlay.AppCompat.Dialog.Alert"> <style name="ThemeOverlay.Session.AlertDialog" parent="ThemeOverlay.AppCompat.Dialog.Alert">
<item name="android:windowBackground">@drawable/default_dialog_background</item>
<item name="android:colorBackground">?attr/dialog_background_color</item> <item name="android:colorBackground">?attr/dialog_background_color</item>
<item name="dialog_background_color">?colorPrimary</item> <item name="dialog_background_color">?colorPrimary</item>
<item name="android:colorBackgroundFloating">?colorPrimary</item> <item name="android:colorBackgroundFloating">?colorPrimary</item>

View File

@ -37,13 +37,7 @@ class BackgroundGroupAddJob(val joinUrl: String): Job {
delegate?.handleJobFailed(this, dispatcherName, DuplicateGroupException()) delegate?.handleJobFailed(this, dispatcherName, DuplicateGroupException())
return return
} }
// get image storage.addOpenGroup(openGroup.joinUrl())
storage.setOpenGroupPublicKey(openGroup.server, openGroup.serverPublicKey)
val info = storage.addOpenGroup(openGroup.joinUrl())
val imageId = info?.imageId
if (imageId != null && storage.getGroupAvatarDownloadJob(openGroup.server, openGroup.room, imageId) == null) {
JobQueue.shared.add(GroupAvatarDownloadJob(openGroup.server, openGroup.room, imageId))
}
Log.d(KEY, "onOpenGroupAdded(${openGroup.server})") Log.d(KEY, "onOpenGroupAdded(${openGroup.server})")
storage.onOpenGroupAdded(openGroup.server, openGroup.room) storage.onOpenGroupAdded(openGroup.server, openGroup.room)
} catch (e: Exception) { } catch (e: Exception) {

View File

@ -109,7 +109,24 @@ object OpenGroupApi {
val defaultWrite: Boolean = false, val defaultWrite: Boolean = false,
val upload: Boolean = false, val upload: Boolean = false,
val defaultUpload: Boolean = false, val defaultUpload: Boolean = false,
) {
fun toPollInfo() = RoomPollInfo(
token = token,
activeUsers = activeUsers,
admin = admin,
globalAdmin = globalAdmin,
moderator = moderator,
globalModerator = globalModerator,
read = read,
defaultRead = defaultRead,
defaultAccessible = defaultAccessible,
write = write,
defaultWrite = defaultWrite,
upload = upload,
defaultUpload = defaultUpload,
details = this
) )
}
@JsonNaming(PropertyNamingStrategy.SnakeCaseStrategy::class) @JsonNaming(PropertyNamingStrategy.SnakeCaseStrategy::class)
data class PinnedMessage( data class PinnedMessage(

View File

@ -31,6 +31,7 @@ import org.session.libsignal.protos.SignalServiceProtos
import org.session.libsignal.utilities.Base64 import org.session.libsignal.utilities.Base64
import org.session.libsignal.utilities.Log import org.session.libsignal.utilities.Log
import org.session.libsignal.utilities.successBackground import org.session.libsignal.utilities.successBackground
import java.util.UUID
import java.util.concurrent.ScheduledExecutorService import java.util.concurrent.ScheduledExecutorService
import java.util.concurrent.ScheduledFuture import java.util.concurrent.ScheduledFuture
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
@ -40,100 +41,39 @@ class OpenGroupPoller(private val server: String, private val executorService: S
var isCaughtUp = false var isCaughtUp = false
var secondToLastJob: MessageReceiveJob? = null var secondToLastJob: MessageReceiveJob? = null
private var future: ScheduledFuture<*>? = null private var future: ScheduledFuture<*>? = null
@Volatile private var runId: UUID = UUID.randomUUID()
companion object { companion object {
private const val pollInterval: Long = 4000L private const val pollInterval: Long = 4000L
const val maxInactivityPeriod = 14 * 24 * 60 * 60 * 1000 const val maxInactivityPeriod = 14 * 24 * 60 * 60 * 1000
}
fun startIfNeeded() { public fun handleRoomPollInfo(
if (hasStarted) { return }
hasStarted = true
future = executorService?.schedule(::poll, 0, TimeUnit.MILLISECONDS)
}
fun stop() {
future?.cancel(false)
hasStarted = false
}
fun poll(isPostCapabilitiesRetry: Boolean = false): Promise<Unit, Exception> {
val storage = MessagingModuleConfiguration.shared.storage
val rooms = storage.getAllOpenGroups().values.filter { it.server == server }.map { it.room }
return OpenGroupApi.poll(rooms, server).successBackground { responses ->
responses.filterNot { it.body == null }.forEach { response ->
when (response.endpoint) {
is Endpoint.Capabilities -> {
handleCapabilities(server, response.body as OpenGroupApi.Capabilities)
}
is Endpoint.RoomPollInfo -> {
handleRoomPollInfo(server, response.endpoint.roomToken, response.body as OpenGroupApi.RoomPollInfo)
}
is Endpoint.RoomMessagesRecent -> {
handleMessages(server, response.endpoint.roomToken, response.body as List<OpenGroupApi.Message>)
}
is Endpoint.RoomMessagesSince -> {
handleMessages(server, response.endpoint.roomToken, response.body as List<OpenGroupApi.Message>)
}
is Endpoint.Inbox, is Endpoint.InboxSince -> {
handleDirectMessages(server, false, response.body as List<OpenGroupApi.DirectMessage>)
}
is Endpoint.Outbox, is Endpoint.OutboxSince -> {
handleDirectMessages(server, true, response.body as List<OpenGroupApi.DirectMessage>)
}
else -> { /* We don't care about the result of any other calls (won't be polled for) */}
}
if (secondToLastJob == null && !isCaughtUp) {
isCaughtUp = true
}
}
if (hasStarted) {
executorService?.schedule(this@OpenGroupPoller::poll, pollInterval, TimeUnit.MILLISECONDS)
}
}.fail {
updateCapabilitiesIfNeeded(isPostCapabilitiesRetry, it)
}.map { }
}
private fun updateCapabilitiesIfNeeded(isPostCapabilitiesRetry: Boolean, exception: Exception) {
if (exception is OnionRequestAPI.HTTPRequestFailedBlindingRequiredException) {
if (!isPostCapabilitiesRetry) {
OpenGroupApi.getCapabilities(server).map {
handleCapabilities(server, it)
}
executorService?.schedule({ poll(isPostCapabilitiesRetry = true) }, pollInterval, TimeUnit.MILLISECONDS)
}
} else {
executorService?.schedule(this@OpenGroupPoller::poll, pollInterval, TimeUnit.MILLISECONDS)
}
}
private fun handleCapabilities(server: String, capabilities: OpenGroupApi.Capabilities) {
val storage = MessagingModuleConfiguration.shared.storage
storage.setServerCapabilities(server, capabilities.capabilities)
}
private fun handleRoomPollInfo(
server: String, server: String,
roomToken: String, roomToken: String,
pollInfo: OpenGroupApi.RoomPollInfo pollInfo: OpenGroupApi.RoomPollInfo,
createGroupIfMissingWithPublicKey: String? = null
) { ) {
if (!hasStarted) return
val storage = MessagingModuleConfiguration.shared.storage val storage = MessagingModuleConfiguration.shared.storage
val groupId = "$server.$roomToken" val groupId = "$server.$roomToken"
val dbGroupId = GroupUtil.getEncodedOpenGroupID(groupId.toByteArray()) val dbGroupId = GroupUtil.getEncodedOpenGroupID(groupId.toByteArray())
val existingOpenGroup = storage.getOpenGroup(roomToken, server) val existingOpenGroup = storage.getOpenGroup(roomToken, server)
val publicKey = existingOpenGroup?.publicKey ?: return
// If we don't have an existing group and don't have a 'createGroupIfMissingWithPublicKey'
// value then don't process the poll info
val publicKey = existingOpenGroup?.publicKey ?: createGroupIfMissingWithPublicKey
val name = pollInfo.details?.name ?: existingOpenGroup?.name
val infoUpdates = pollInfo.details?.infoUpdates ?: existingOpenGroup?.infoUpdates
if (publicKey == null) return
val openGroup = OpenGroup( val openGroup = OpenGroup(
server = server, server = server,
room = pollInfo.token, room = pollInfo.token,
name = if (pollInfo.details != null) { pollInfo.details.name } else { existingOpenGroup.name }, name = name ?: "",
publicKey = publicKey, publicKey = publicKey,
imageId = if (pollInfo.details != null) { pollInfo.details.imageId } else { existingOpenGroup.imageId }, imageId = (pollInfo.details?.imageId ?: existingOpenGroup?.imageId),
canWrite = pollInfo.write, canWrite = pollInfo.write,
infoUpdates = if (pollInfo.details != null) { pollInfo.details.infoUpdates } else { existingOpenGroup.infoUpdates } infoUpdates = infoUpdates ?: 0
) )
// - Open Group changes // - Open Group changes
storage.updateOpenGroup(openGroup) storage.updateOpenGroup(openGroup)
@ -169,27 +109,104 @@ class OpenGroupPoller(private val server: String, private val executorService: S
( (
pollInfo.details != null && pollInfo.details != null &&
pollInfo.details.imageId != null && ( pollInfo.details.imageId != null && (
pollInfo.details.imageId != existingOpenGroup.imageId || pollInfo.details.imageId != existingOpenGroup?.imageId ||
!storage.hasDownloadedProfilePicture(dbGroupId) !storage.hasDownloadedProfilePicture(dbGroupId)
) && ) &&
storage.getGroupAvatarDownloadJob(openGroup.server, openGroup.room, pollInfo.details.imageId) == null storage.getGroupAvatarDownloadJob(openGroup.server, openGroup.room, pollInfo.details.imageId) == null
) || ( ) || (
pollInfo.details == null && pollInfo.details == null &&
existingOpenGroup.imageId != null && existingOpenGroup?.imageId != null &&
!storage.hasDownloadedProfilePicture(dbGroupId) && !storage.hasDownloadedProfilePicture(dbGroupId) &&
storage.getGroupAvatarDownloadJob(openGroup.server, openGroup.room, existingOpenGroup.imageId) == null storage.getGroupAvatarDownloadJob(openGroup.server, openGroup.room, existingOpenGroup.imageId) == null
) )
) { ) {
JobQueue.shared.add(GroupAvatarDownloadJob(server, roomToken, existingOpenGroup.imageId)) JobQueue.shared.add(GroupAvatarDownloadJob(server, roomToken, openGroup.imageId))
} }
else if ( else if (
pollInfo.details != null && pollInfo.details != null &&
pollInfo.details.imageId == null && pollInfo.details.imageId == null &&
existingOpenGroup.imageId != null existingOpenGroup?.imageId != null
) { ) {
storage.removeProfilePicture(dbGroupId) storage.removeProfilePicture(dbGroupId)
} }
} }
}
fun startIfNeeded() {
if (hasStarted) { return }
hasStarted = true
runId = UUID.randomUUID()
future = executorService?.schedule(::poll, 0, TimeUnit.MILLISECONDS)
}
fun stop() {
future?.cancel(false)
hasStarted = false
}
fun poll(isPostCapabilitiesRetry: Boolean = false): Promise<Unit, Exception> {
val currentRunId = runId
val storage = MessagingModuleConfiguration.shared.storage
val rooms = storage.getAllOpenGroups().values.filter { it.server == server }.map { it.room }
return OpenGroupApi.poll(rooms, server).successBackground { responses ->
responses.filterNot { it.body == null }.forEach { response ->
when (response.endpoint) {
is Endpoint.Capabilities -> {
handleCapabilities(server, response.body as OpenGroupApi.Capabilities)
}
is Endpoint.RoomPollInfo -> {
handleRoomPollInfo(server, response.endpoint.roomToken, response.body as OpenGroupApi.RoomPollInfo)
}
is Endpoint.RoomMessagesRecent -> {
handleMessages(server, response.endpoint.roomToken, response.body as List<OpenGroupApi.Message>)
}
is Endpoint.RoomMessagesSince -> {
handleMessages(server, response.endpoint.roomToken, response.body as List<OpenGroupApi.Message>)
}
is Endpoint.Inbox, is Endpoint.InboxSince -> {
handleDirectMessages(server, false, response.body as List<OpenGroupApi.DirectMessage>)
}
is Endpoint.Outbox, is Endpoint.OutboxSince -> {
handleDirectMessages(server, true, response.body as List<OpenGroupApi.DirectMessage>)
}
else -> { /* We don't care about the result of any other calls (won't be polled for) */}
}
if (secondToLastJob == null && !isCaughtUp) {
isCaughtUp = true
}
}
// Only poll again if it's the same poller run
if (currentRunId == runId) {
future = executorService?.schedule(this@OpenGroupPoller::poll, pollInterval, TimeUnit.MILLISECONDS)
}
}.fail {
updateCapabilitiesIfNeeded(isPostCapabilitiesRetry, currentRunId, it)
}.map { }
}
private fun updateCapabilitiesIfNeeded(isPostCapabilitiesRetry: Boolean, currentRunId: UUID, exception: Exception) {
if (exception is OnionRequestAPI.HTTPRequestFailedBlindingRequiredException) {
if (!isPostCapabilitiesRetry) {
OpenGroupApi.getCapabilities(server).map {
handleCapabilities(server, it)
}
// Only poll again if it's the same poller run
if (currentRunId == runId) {
future = executorService?.schedule({ poll(isPostCapabilitiesRetry = true) }, pollInterval, TimeUnit.MILLISECONDS)
}
}
} else if (currentRunId == runId) {
future = executorService?.schedule(this@OpenGroupPoller::poll, pollInterval, TimeUnit.MILLISECONDS)
}
}
private fun handleCapabilities(server: String, capabilities: OpenGroupApi.Capabilities) {
val storage = MessagingModuleConfiguration.shared.storage
storage.setServerCapabilities(server, capabilities.capabilities)
}
private fun handleMessages( private fun handleMessages(
server: String, server: String,