Added a new control message type to handle missed calls due to permissions

This commit is contained in:
ThomasSession 2024-09-02 11:47:07 +10:00
parent 5795998b81
commit 4860adcd86
10 changed files with 121 additions and 46 deletions

View File

@ -0,0 +1,29 @@
package org.thoughtcrime.securesms
import android.content.Context
import android.content.Intent
import android.net.Uri
import com.squareup.phrase.Phrase
import network.loki.messenger.R
import org.session.libsession.utilities.StringSubstitutionConstants.APP_NAME_KEY
class MissingMicrophonePermissionDialog {
companion object {
@JvmStatic
fun show(context: Context) = context.showSessionDialog {
title(R.string.permissionsMicrophone)
text(
Phrase.from(context, R.string.permissionsMicrophoneAccessRequired)
.put(APP_NAME_KEY, context.getString(R.string.app_name))
.format().toString())
button(R.string.sessionSettings, R.string.AccessibilityId_sessionSettings) {
val intent = Intent(android.provider.Settings.ACTION_APPLICATION_DETAILS_SETTINGS)
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
val uri = Uri.fromParts("package", context.packageName, null)
intent.setData(uri)
context.startActivity(intent)
}
cancelButton()
}
}
}

View File

@ -3,6 +3,7 @@ package org.thoughtcrime.securesms.conversation.v2
import android.content.Context
import android.content.Intent
import android.database.Cursor
import android.net.Uri
import android.util.SparseArray
import android.util.SparseBooleanArray
import android.view.MotionEvent
@ -30,6 +31,9 @@ import org.thoughtcrime.securesms.database.CursorRecyclerViewAdapter
import org.thoughtcrime.securesms.database.model.MessageRecord
import org.thoughtcrime.securesms.dependencies.DatabaseComponent
import com.bumptech.glide.RequestManager
import com.squareup.phrase.Phrase
import org.session.libsession.utilities.StringSubstitutionConstants.APP_NAME_KEY
import org.thoughtcrime.securesms.MissingMicrophonePermissionDialog
import org.thoughtcrime.securesms.preferences.PrivacySettingsActivity
import org.thoughtcrime.securesms.showSessionDialog
import org.thoughtcrime.securesms.ui.getSubbedCharSequence
@ -121,7 +125,11 @@ class ConversationAdapter(
val senderId = message.individualRecipient.address.serialize()
val senderIdHash = senderId.hashCode()
updateQueue.trySend(senderId)
if (contactCache[senderIdHash] == null && !contactLoadedCache.getOrDefault(senderIdHash, false)) {
if (contactCache[senderIdHash] == null && !contactLoadedCache.getOrDefault(
senderIdHash,
false
)
) {
getSenderInfo(senderId)?.let { contact ->
contactCache[senderIdHash] = contact
}
@ -129,49 +137,77 @@ class ConversationAdapter(
val contact = contactCache[senderIdHash]
visibleMessageView.bind(
message,
messageBefore,
getMessageAfter(position, cursor),
glide,
searchQuery,
contact,
senderId,
lastSeen.get(),
visibleMessageViewDelegate,
onAttachmentNeedsDownload,
lastSentMessageId
message,
messageBefore,
getMessageAfter(position, cursor),
glide,
searchQuery,
contact,
senderId,
lastSeen.get(),
visibleMessageViewDelegate,
onAttachmentNeedsDownload,
lastSentMessageId
)
if (!message.isDeleted) {
visibleMessageView.onPress = { event -> onItemPress(message, viewHolder.adapterPosition, visibleMessageView, event) }
visibleMessageView.onSwipeToReply = { onItemSwipeToReply(message, viewHolder.adapterPosition) }
visibleMessageView.onLongPress = { onItemLongPress(message, viewHolder.adapterPosition, visibleMessageView) }
visibleMessageView.onPress = { event ->
onItemPress(
message,
viewHolder.adapterPosition,
visibleMessageView,
event
)
}
visibleMessageView.onSwipeToReply =
{ onItemSwipeToReply(message, viewHolder.adapterPosition) }
visibleMessageView.onLongPress =
{ onItemLongPress(message, viewHolder.adapterPosition, visibleMessageView) }
} else {
visibleMessageView.onPress = null
visibleMessageView.onSwipeToReply = null
visibleMessageView.onLongPress = null
}
}
is ControlMessageViewHolder -> {
viewHolder.view.bind(message, messageBefore)
if (message.isCallLog && message.isFirstMissedCall) {
viewHolder.view.setOnClickListener {
context.showSessionDialog {
val titleTxt = context.getSubbedString(R.string.callsMissedCallFrom, NAME_KEY to message.individualRecipient.name!!)
title(titleTxt)
when {
// Click behaviour for first missed call control message
//todo this behaviour is different than iOS where the control message is always clickable when the call toggle is disabled in the privacy page
message.isCallLog && message.isFirstMissedCall -> {
viewHolder.view.setOnClickListener {
context.showSessionDialog {
val titleTxt = context.getSubbedString(
R.string.callsMissedCallFrom,
NAME_KEY to message.individualRecipient.name!!
)
title(titleTxt)
val bodyTxt = context.getSubbedCharSequence(R.string.callsYouMissedCallPermissions, NAME_KEY to message.individualRecipient.name!!)
text(bodyTxt)
val bodyTxt = context.getSubbedCharSequence(
R.string.callsYouMissedCallPermissions,
NAME_KEY to message.individualRecipient.name!!
)
text(bodyTxt)
button(R.string.sessionSettings) {
Intent(context, PrivacySettingsActivity::class.java)
.let(context::startActivity)
button(R.string.sessionSettings) {
Intent(context, PrivacySettingsActivity::class.java)
.let(context::startActivity)
}
cancelButton()
}
cancelButton()
}
}
} else {
viewHolder.view.setOnClickListener(null)
// Click behaviour for missed calls due to missing permission
message.isCallLog && message.isMissedPermissionCall -> {
viewHolder.view.setOnClickListener {
MissingMicrophonePermissionDialog.show(context)
}
}
// non clickable in other cases
else -> viewHolder.view.setOnClickListener(null)
}
}
}

View File

@ -31,6 +31,7 @@ import org.session.libsession.utilities.recipients.Recipient
import org.session.libsignal.utilities.Log
import org.session.libsignal.utilities.guava.Optional
import org.session.libsignal.utilities.toHexString
import org.thoughtcrime.securesms.MissingMicrophonePermissionDialog
import org.thoughtcrime.securesms.media.MediaOverviewActivity
import org.thoughtcrime.securesms.ShortcutLauncherActivity
import org.thoughtcrime.securesms.calls.WebRtcCallActivity
@ -182,20 +183,7 @@ object ConversationMenuHelper {
// or if the user has not granted audio/microphone permissions
else if (!Permissions.hasAll(context, Manifest.permission.RECORD_AUDIO)) {
Log.d("Loki", "Attempted to make a call without audio permissions")
context.showSessionDialog {
title(R.string.permissionsMicrophone)
text(Phrase.from(context, R.string.permissionsMicrophoneAccessRequired)
.put(APP_NAME_KEY, context.getString(R.string.app_name))
.format().toString())
button(R.string.sessionSettings, R.string.AccessibilityId_sessionSettings) {
val intent = Intent(android.provider.Settings.ACTION_APPLICATION_DETAILS_SETTINGS)
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
val uri = Uri.fromParts("package", context.packageName, null)
intent.setData(uri)
context.startActivity(intent)
}
cancelButton()
}
MissingMicrophonePermissionDialog.show(context)
return
}

View File

@ -41,6 +41,7 @@ public interface MmsSmsColumns {
protected static final long MISSED_CALL_TYPE = 3;
protected static final long JOINED_TYPE = 4;
protected static final long FIRST_MISSED_CALL_TYPE = 5;
protected static final long MISSED_PERMISSION_CALL_TYPE = 6;
protected static final long BASE_INBOX_TYPE = 20;
protected static final long BASE_OUTBOX_TYPE = 21;
@ -234,7 +235,8 @@ public interface MmsSmsColumns {
public static boolean isCallLog(long type) {
long baseType = type & BASE_TYPE_MASK;
return baseType == INCOMING_CALL_TYPE || baseType == OUTGOING_CALL_TYPE || baseType == MISSED_CALL_TYPE || baseType == FIRST_MISSED_CALL_TYPE;
return baseType == INCOMING_CALL_TYPE || baseType == OUTGOING_CALL_TYPE ||
baseType == MISSED_CALL_TYPE || baseType == FIRST_MISSED_CALL_TYPE || baseType == MISSED_PERMISSION_CALL_TYPE;
}
public static boolean isExpirationTimerUpdate(long type) {
@ -265,6 +267,10 @@ public interface MmsSmsColumns {
return (type & BASE_TYPE_MASK) == MISSED_CALL_TYPE;
}
public static boolean isMissedPermissionCall(long type) {
return (type & BASE_TYPE_MASK) == MISSED_PERMISSION_CALL_TYPE;
}
public static boolean isFirstMissedCall(long type) {
return (type & BASE_TYPE_MASK) == FIRST_MISSED_CALL_TYPE;
}

View File

@ -501,6 +501,8 @@ public class SmsDatabase extends MessagingDatabase {
return Types.MISSED_CALL_TYPE;
case CALL_FIRST_MISSED:
return Types.FIRST_MISSED_CALL_TYPE;
case CALL_MISSED_PERMISSION:
return Types.MISSED_PERMISSION_CALL_TYPE;
default:
return 0;
}

View File

@ -142,6 +142,9 @@ public abstract class DisplayRecord {
public boolean isFirstMissedCall() {
return SmsDatabase.Types.isFirstMissedCall(type);
}
public boolean isMissedPermissionCall() {
return SmsDatabase.Types.isMissedPermissionCall(type);
}
public boolean isDeleted() { return MmsSmsColumns.Types.isDeletedMessage(type); }
public boolean isMessageRequestResponse() { return MmsSmsColumns.Types.isMessageRequestResponse(type); }

View File

@ -134,6 +134,8 @@ public abstract class MessageRecord extends DisplayRecord {
callType = CallMessageType.CALL_OUTGOING;
} else if (isMissedCall()) {
callType = CallMessageType.CALL_MISSED;
} else if (isMissedPermissionCall()) {
callType = CallMessageType.CALL_MISSED_PERMISSION;
} else {
callType = CallMessageType.CALL_FIRST_MISSED;
}

View File

@ -78,8 +78,10 @@ class CallMessageProcessor(private val context: Context, private val textSecureP
}
// or if the user has not granted audio/microphone permissions
else if (!Permissions.hasAll(context, Manifest.permission.RECORD_AUDIO)) {
if (nextMessage.type != PRE_OFFER) continue
val sentTimestamp = nextMessage.sentTimestamp ?: continue
Log.d("Loki", "Attempted to receive a call without audio permissions")
//TODO display something to let the user know they missed a call due to missing permission
insertMissedPermissionCall(sender, sentTimestamp)
continue
}
@ -111,6 +113,12 @@ class CallMessageProcessor(private val context: Context, private val textSecureP
}
}
private fun insertMissedPermissionCall(sender: String, sentTimestamp: Long) {
val currentUserPublicKey = storage.getUserPublicKey()
if (sender == currentUserPublicKey) return // don't insert a "missed" due to call notifications disabled if it's our own sender
storage.insertCallMessage(sender, CallMessageType.CALL_MISSED_PERMISSION, sentTimestamp)
}
private fun incomingHangup(callMessage: CallMessage) {
val callId = callMessage.callId ?: return
val hangupIntent = WebRtcCallService.remoteHangupIntent(context, callId)

View File

@ -5,4 +5,5 @@ enum class CallMessageType {
CALL_INCOMING,
CALL_OUTGOING,
CALL_FIRST_MISSED,
CALL_MISSED_PERMISSION,
}

View File

@ -1,7 +1,6 @@
package org.session.libsession.messaging.utilities
import android.content.Context
import android.text.SpannableString
import com.squareup.phrase.Phrase
import org.session.libsession.R
import org.session.libsession.messaging.MessagingModuleConfiguration
@ -9,6 +8,7 @@ import org.session.libsession.messaging.calls.CallMessageType
import org.session.libsession.messaging.calls.CallMessageType.CALL_FIRST_MISSED
import org.session.libsession.messaging.calls.CallMessageType.CALL_INCOMING
import org.session.libsession.messaging.calls.CallMessageType.CALL_MISSED
import org.session.libsession.messaging.calls.CallMessageType.CALL_MISSED_PERMISSION
import org.session.libsession.messaging.calls.CallMessageType.CALL_OUTGOING
import org.session.libsession.messaging.contacts.Contact
import org.session.libsession.messaging.sending_receiving.data_extraction.DataExtractionNotificationInfoMessage
@ -24,7 +24,6 @@ import org.session.libsession.utilities.StringSubstitutionConstants.GROUP_NAME_K
import org.session.libsession.utilities.StringSubstitutionConstants.NAME_KEY
import org.session.libsession.utilities.StringSubstitutionConstants.OTHER_NAME_KEY
import org.session.libsession.utilities.StringSubstitutionConstants.TIME_KEY
import org.session.libsession.utilities.Util
object UpdateMessageBuilder {
const val TAG = "libsession"
@ -267,6 +266,7 @@ object UpdateMessageBuilder {
CALL_INCOMING -> Phrase.from(context, R.string.callsCalledYou).put(NAME_KEY, senderName).format().toString()
CALL_OUTGOING -> Phrase.from(context, R.string.callsYouCalled).put(NAME_KEY, senderName).format().toString()
CALL_MISSED, CALL_FIRST_MISSED -> Phrase.from(context, R.string.callsMissedCallFrom).put(NAME_KEY, senderName).format().toString()
CALL_MISSED_PERMISSION -> Phrase.from(context, R.string.callsMissedCallFrom).put(NAME_KEY, senderName).format().toString() + "\n" + context.getString(R.string.permissionsMicrophoneDescription)
}
}
}