Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SS-2168 - User profile warnings #1531

Merged
merged 14 commits into from
Jul 9, 2024
3 changes: 2 additions & 1 deletion app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -271,7 +271,8 @@ dependencies {
if (project.hasProperty('huawei')) huaweiImplementation 'com.huawei.hms:push:6.7.0.300'
implementation 'com.google.android.exoplayer:exoplayer-core:2.9.1'
implementation 'com.google.android.exoplayer:exoplayer-ui:2.9.1'
implementation 'org.conscrypt:conscrypt-android:2.0.0'
//implementation 'org.conscrypt:conscrypt-android:2.0.0'
implementation 'org.conscrypt:conscrypt-android:2.5.2'
implementation 'org.signal:aesgcmprovider:0.0.3'
implementation 'org.webrtc:google-webrtc:1.0.32006'
implementation "me.leolin:ShortcutBadger:1.1.16"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import androidx.core.view.isVisible
import network.loki.messenger.R
import network.loki.messenger.databinding.ViewVisibleMessageContentBinding
import okhttp3.HttpUrl
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
import org.session.libsession.messaging.MessagingModuleConfiguration
import org.session.libsession.messaging.sending_receiving.attachments.AttachmentTransferProgress
import org.session.libsession.messaging.sending_receiving.attachments.DatabaseAttachment
Expand Down Expand Up @@ -289,7 +290,7 @@ class VisibleMessageContentView : ConstraintLayout {

// replace URLSpans with ModalURLSpans
body.getSpans<URLSpan>(0, body.length).toList().forEach { urlSpan ->
val updatedUrl = urlSpan.url.let { HttpUrl.parse(it).toString() }
val updatedUrl = urlSpan.url.let { it.toHttpUrlOrNull().toString() }
val replacementSpan = ModalURLSpan(updatedUrl) { url ->
val activity = context as AppCompatActivity
ModalUrlBottomSheet(url).show(activity.supportFragmentManager, "Open URL Dialog")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ public class RecipientDatabase extends Database {
private static final String SYSTEM_PHONE_LABEL = "system_phone_label";
private static final String SYSTEM_CONTACT_URI = "system_contact_uri";
private static final String SIGNAL_PROFILE_NAME = "signal_profile_name";
private static final String SIGNAL_PROFILE_AVATAR = "signal_profile_avatar";
private static final String SESSION_PROFILE_AVATAR = "signal_profile_avatar";
private static final String PROFILE_SHARING = "profile_sharing_approval";
private static final String CALL_RINGTONE = "call_ringtone";
private static final String CALL_VIBRATE = "call_vibrate";
Expand All @@ -69,7 +69,7 @@ public class RecipientDatabase extends Database {
private static final String[] RECIPIENT_PROJECTION = new String[] {
BLOCK, APPROVED, APPROVED_ME, NOTIFICATION, CALL_RINGTONE, VIBRATE, CALL_VIBRATE, MUTE_UNTIL, COLOR, SEEN_INVITE_REMINDER, DEFAULT_SUBSCRIPTION_ID, EXPIRE_MESSAGES, REGISTERED,
PROFILE_KEY, SYSTEM_DISPLAY_NAME, SYSTEM_PHOTO_URI, SYSTEM_PHONE_LABEL, SYSTEM_CONTACT_URI,
SIGNAL_PROFILE_NAME, SIGNAL_PROFILE_AVATAR, PROFILE_SHARING, NOTIFICATION_CHANNEL,
SIGNAL_PROFILE_NAME, SESSION_PROFILE_AVATAR, PROFILE_SHARING, NOTIFICATION_CHANNEL,
UNIDENTIFIED_ACCESS_MODE,
FORCE_SMS_SELECTION, NOTIFY_TYPE, DISAPPEARING_STATE, WRAPPER_HASH, BLOCKS_COMMUNITY_MESSAGE_REQUESTS
};
Expand Down Expand Up @@ -97,7 +97,7 @@ public class RecipientDatabase extends Database {
SYSTEM_CONTACT_URI + " TEXT DEFAULT NULL, " +
PROFILE_KEY + " TEXT DEFAULT NULL, " +
SIGNAL_PROFILE_NAME + " TEXT DEFAULT NULL, " +
SIGNAL_PROFILE_AVATAR + " TEXT DEFAULT NULL, " +
SESSION_PROFILE_AVATAR + " TEXT DEFAULT NULL, " +
PROFILE_SHARING + " INTEGER DEFAULT 0, " +
CALL_RINGTONE + " TEXT DEFAULT NULL, " +
CALL_VIBRATE + " INTEGER DEFAULT " + Recipient.VibrateState.DEFAULT.getId() + ", " +
Expand Down Expand Up @@ -204,7 +204,7 @@ Optional<RecipientSettings> getRecipientSettings(@NonNull Cursor cursor) {
String systemPhoneLabel = cursor.getString(cursor.getColumnIndexOrThrow(SYSTEM_PHONE_LABEL));
String systemContactUri = cursor.getString(cursor.getColumnIndexOrThrow(SYSTEM_CONTACT_URI));
String signalProfileName = cursor.getString(cursor.getColumnIndexOrThrow(SIGNAL_PROFILE_NAME));
String signalProfileAvatar = cursor.getString(cursor.getColumnIndexOrThrow(SIGNAL_PROFILE_AVATAR));
String signalProfileAvatar = cursor.getString(cursor.getColumnIndexOrThrow(SESSION_PROFILE_AVATAR));
boolean profileSharing = cursor.getInt(cursor.getColumnIndexOrThrow(PROFILE_SHARING)) == 1;
String notificationChannel = cursor.getString(cursor.getColumnIndexOrThrow(NOTIFICATION_CHANNEL));
int unidentifiedAccessMode = cursor.getInt(cursor.getColumnIndexOrThrow(UNIDENTIFIED_ACCESS_MODE));
Expand Down Expand Up @@ -361,7 +361,7 @@ public void setProfileKey(@NonNull Recipient recipient, @Nullable byte[] profile

public void setProfileAvatar(@NonNull Recipient recipient, @Nullable String profileAvatar) {
ContentValues contentValues = new ContentValues(1);
contentValues.put(SIGNAL_PROFILE_AVATAR, profileAvatar);
contentValues.put(SESSION_PROFILE_AVATAR, profileAvatar);
updateOrInsert(recipient.getAddress(), contentValues);
recipient.resolve().setProfileAvatar(profileAvatar);
notifyRecipientListeners();
Expand Down
145 changes: 139 additions & 6 deletions app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,13 @@ package org.thoughtcrime.securesms.database

import android.content.Context
import android.net.Uri
import android.widget.Toast
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.TimeoutCancellationException
import kotlinx.coroutines.launch
import kotlinx.coroutines.withTimeout
import network.loki.messenger.R
import network.loki.messenger.libsession_util.ConfigBase
import network.loki.messenger.libsession_util.ConfigBase.Companion.PRIORITY_HIDDEN
import network.loki.messenger.libsession_util.ConfigBase.Companion.PRIORITY_PINNED
Expand All @@ -15,6 +22,9 @@ import network.loki.messenger.libsession_util.util.ExpiryMode
import network.loki.messenger.libsession_util.util.GroupInfo
import network.loki.messenger.libsession_util.util.UserPic
import network.loki.messenger.libsession_util.util.afterSend
import nl.komponents.kovenant.Promise
import nl.komponents.kovenant.ui.failUi
import nl.komponents.kovenant.ui.successUi
import org.session.libsession.avatars.AvatarHelper
import org.session.libsession.database.StorageProtocol
import org.session.libsession.messaging.BlindedIdMapping
Expand Down Expand Up @@ -92,6 +102,7 @@ import org.thoughtcrime.securesms.mms.PartAuthority
import org.thoughtcrime.securesms.util.ConfigurationMessageUtilities
import org.thoughtcrime.securesms.util.SessionMetaProtocol
import java.security.MessageDigest
import kotlin.time.Duration.Companion.seconds
import network.loki.messenger.libsession_util.util.Contact as LibSessionContact

private const val TAG = "Storage"
Expand Down Expand Up @@ -418,9 +429,15 @@ open class Storage(
}

override fun getConfigSyncJob(destination: Destination): Job? {
return DatabaseComponent.get(context).sessionJobDatabase().getAllJobs(ConfigurationSyncJob.KEY).values.firstOrNull {

val j = DatabaseComponent.get(context).sessionJobDatabase().getAllJobs(ConfigurationSyncJob.KEY).values.firstOrNull {
(it as? ConfigurationSyncJob)?.destination == destination
}

val foundExistingJob = j != null
Log.w("ACL", "Hit getConfigSyncJob - found existing job? $foundExistingJob")

return j
}

override fun resumeMessageSendJobIfNeeded(messageSendJobID: String) {
Expand Down Expand Up @@ -471,7 +488,8 @@ open class Storage(
val userPublicKey = getUserPublicKey() ?: return
// would love to get rid of recipient and context from this
val recipient = Recipient.from(context, fromSerialized(userPublicKey), false)
// update name

// Update profile name
val name = userProfile.getName() ?: return
val userPic = userProfile.getPic()
val profileManager = SSKEnvironment.shared.profileManager
Expand All @@ -480,13 +498,14 @@ open class Storage(
profileManager.setName(context, recipient, name)
}

// update pfp
// Update profile picture
if (userPic == UserPic.DEFAULT) {
clearUserPic()
} else if (userPic.key.isNotEmpty() && userPic.url.isNotEmpty()
&& TextSecurePreferences.getProfilePictureURL(context) != userPic.url) {
setUserProfilePicture(userPic.url, userPic.key)
}

if (userProfile.getNtsPriority() == PRIORITY_HIDDEN) {
// delete nts thread if needed
val ourThread = getThreadId(recipient) ?: return
Expand Down Expand Up @@ -514,9 +533,56 @@ open class Storage(
addLibSessionContacts(extracted, messageTimestamp)
}

override fun clearUserPic() {
val userPublicKey = getUserPublicKey() ?: return
private suspend fun performClearProfilePictureJob() {
lateinit var syncPromise: Promise<Unit, Exception>
try {
Log.e("ACL", "About to try stuff with timeout")
withTimeout(5.seconds.inWholeMilliseconds) {

Log.e("ACL", "Created sync promise")
syncPromise = ConfigurationMessageUtilities.forceSyncConfigurationNowIfNeeded(context)

syncPromise.failUi {
Log.e(TAG, "Failed to sync following clearing user profile picture", syncPromise.getError())
Toast.makeText(context, context.getString(R.string.profileDisplayPictureRemoveError), Toast.LENGTH_LONG).show()
}

syncPromise.successUi {
Log.e(TAG, "Successfully synced following clearing user profile picture")
Toast.makeText(context, "Doubtful this has already synced - we just got a Promise.ofSuccess, is all!!", Toast.LENGTH_SHORT).show()
}

}.success {
Log.e("ACL", "We think we succeeded!")
}
.fail { Log.e("ACL", "We think we failed!") }
}
catch (tce: TimeoutCancellationException) {

// I don't think we ever see this! =(

Log.e("ACL", "Caught timeout!")

syncPromise.fail {
/* We don't really have to do anything here - we just mark the promise as failed */
Log.w("ACL", "Hit syncPromise.fail!!!!!")
}

// If the timeout expires
Log.e(TAG, "Timed out attempting to sync following clearing user profile picture", tce)
Toast.makeText(context, context.getString(R.string.profileDisplayPictureRemoveError) + " foo!", Toast.LENGTH_LONG).show()

}
catch (e: Exception) {
Log.e("ACL", "Caught generic exception!")
Log.e(TAG, e)
}
}

override fun clearUserPic() {
val userPublicKey = getUserPublicKey() ?: return Log.w(TAG, "No user public key when trying to clear user pic")
val recipientDatabase = DatabaseComponent.get(context).recipientDatabase()

// would love to get rid of recipient and context from this
val recipient = Recipient.from(context, fromSerialized(userPublicKey), false)
// clear picture if userPic is null
Expand All @@ -528,7 +594,74 @@ open class Storage(

Recipient.removeCached(fromSerialized(userPublicKey))
configFactory.user?.setPic(UserPic.DEFAULT)
ConfigurationMessageUtilities.forceSyncConfigurationNowIfNeeded(context)

// Attempt to sync the cleared profile picture & inform the user should that fail
//val timeoutPromise = nl.komponents.kovenant.Promise { _, reject -> setTimeout(() => fail)

//try

val scope = CoroutineScope(Dispatchers.IO)
val job = scope.launch {
performClearProfilePictureJob()
}

if (job.isCompleted) {
Log.w("ACL", "clearProfileJob is completed!")
} else if (job.isCancelled) {
Log.w("ACL", "clearProfileJob is cancelled!")
} else if (job.isActive) {
Log.w("ACL", "clearProfileJob is active!")
} else
{
Log.w("ACL", "clearProfileJob is neither completed, cancelled, or active!?!?")
}


//ThreadUtils.queue {
// coroutineScope {
//
// val foo = doStuff().await()
//
// lateinit var syncPromise: Promise<Unit, Exception>
// try {
// Log.e("ACL", "About to try stuff with timeout")
// withTimeout(5.seconds.inWholeMilliseconds) {
// //val response = fetch("/api") // Assuming you have a fetch function
// //response.json() // Assuming you have a function to parse JSON
// Log.e("ACL", "Created sync promise")
// syncPromise = ConfigurationMessageUtilities.forceSyncConfigurationNowIfNeeded(context)
//
// syncPromise.failUi {
// Log.e(TAG, "Failed to sync following clearing user profile picture", syncPromise.getError())
// Toast.makeText(context, context.getString(R.string.profileDisplayPictureRemoveError), Toast.LENGTH_LONG).show()
// }
// }
// }
// catch (tce: TimeoutCancellationException) {
// Log.e("ACL", "Caught timeout!")
// syncPromise.fail { /* We don't really have to do anything here - we just mark the promise as failed */ }
// // If the timeout expires
// Log.e(TAG, "Timed out attempting to sync following clearing user profile picture", tce)
// Toast.makeText(context, context.getString(R.string.profileDisplayPictureRemoveError) + "WANG!", Toast.LENGTH_LONG).show()
//
// }
// catch (e: Exception) {
// Log.e("ACL", "Caught generic execption!")
// Log.e(TAG, e)
// }
// }

//withDispatchers
//val foo = doStuff().await()
//foo.start()


// val syncPromise = ConfigurationMessageUtilities.forceSyncConfigurationNowIfNeeded(context)
//
// syncPromise.failUi {
// Log.e(TAG, "Failed to sync following clearing user profile picture", syncPromise.getError())
// Toast.makeText(context, context.getString(R.string.profileDisplayPictureRemoveError), Toast.LENGTH_LONG).show()
// }
}

private fun updateConvoVolatile(convos: ConversationVolatileConfig, messageTimestamp: Long) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package org.thoughtcrime.securesms.groups
import android.content.Context
import androidx.annotation.WorkerThread
import okhttp3.HttpUrl
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
import org.session.libsession.messaging.MessagingModuleConfiguration
import org.session.libsession.messaging.open_groups.GroupMemberRole
import org.session.libsession.messaging.open_groups.OpenGroup
Expand Down Expand Up @@ -143,9 +144,9 @@ object OpenGroupManager {

@WorkerThread
fun addOpenGroup(urlAsString: String, context: Context): OpenGroupApi.RoomInfo? {
val url = HttpUrl.parse(urlAsString) ?: return null
val url = urlAsString.toHttpUrlOrNull() ?: return null
val server = OpenGroup.getServer(urlAsString)
val room = url.pathSegments().firstOrNull() ?: return null
val room = url.pathSegments.firstOrNull() ?: return null
val publicKey = url.queryParameter("public_key") ?: return null

return add(server.toString().removeSuffix("/"), room, publicKey, context).second // assume migrated from calling function
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,5 @@ public void wtf(String tag, String message, Throwable t) {
}

@Override
public void blockUntilAllWritesFinished() {
}
public void blockUntilAllWritesFinished() { }
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import kotlinx.serialization.json.decodeFromStream
import nl.komponents.kovenant.Promise
import nl.komponents.kovenant.functional.map
import okhttp3.MediaType
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.Request
import okhttp3.RequestBody
import org.session.libsession.messaging.sending_receiving.notifications.Response
Expand Down Expand Up @@ -99,7 +100,7 @@ class PushRegistryV2 @Inject constructor(private val pushReceiver: PushReceiver)
private inline fun <reified T: Response> getResponseBody(path: String, requestParameters: String): Promise<T, Exception> {
val server = Server.LATEST
val url = "${server.url}/$path"
val body = RequestBody.create(MediaType.get("application/json"), requestParameters)
val body = RequestBody.create("application/json".toMediaType(), requestParameters)
val request = Request.Builder().url(url).post(body).build()

return OnionRequestAPI.sendOnionRequest(
Expand Down
Loading