mirror of
https://github.com/oxen-io/session-android.git
synced 2025-05-06 16:16:50 +00:00
Added a new control message type to handle missed calls due to permissions
This commit is contained in:
parent
5795998b81
commit
4860adcd86
@ -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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -3,6 +3,7 @@ package org.thoughtcrime.securesms.conversation.v2
|
|||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.database.Cursor
|
import android.database.Cursor
|
||||||
|
import android.net.Uri
|
||||||
import android.util.SparseArray
|
import android.util.SparseArray
|
||||||
import android.util.SparseBooleanArray
|
import android.util.SparseBooleanArray
|
||||||
import android.view.MotionEvent
|
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.database.model.MessageRecord
|
||||||
import org.thoughtcrime.securesms.dependencies.DatabaseComponent
|
import org.thoughtcrime.securesms.dependencies.DatabaseComponent
|
||||||
import com.bumptech.glide.RequestManager
|
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.preferences.PrivacySettingsActivity
|
||||||
import org.thoughtcrime.securesms.showSessionDialog
|
import org.thoughtcrime.securesms.showSessionDialog
|
||||||
import org.thoughtcrime.securesms.ui.getSubbedCharSequence
|
import org.thoughtcrime.securesms.ui.getSubbedCharSequence
|
||||||
@ -121,7 +125,11 @@ class ConversationAdapter(
|
|||||||
val senderId = message.individualRecipient.address.serialize()
|
val senderId = message.individualRecipient.address.serialize()
|
||||||
val senderIdHash = senderId.hashCode()
|
val senderIdHash = senderId.hashCode()
|
||||||
updateQueue.trySend(senderId)
|
updateQueue.trySend(senderId)
|
||||||
if (contactCache[senderIdHash] == null && !contactLoadedCache.getOrDefault(senderIdHash, false)) {
|
if (contactCache[senderIdHash] == null && !contactLoadedCache.getOrDefault(
|
||||||
|
senderIdHash,
|
||||||
|
false
|
||||||
|
)
|
||||||
|
) {
|
||||||
getSenderInfo(senderId)?.let { contact ->
|
getSenderInfo(senderId)?.let { contact ->
|
||||||
contactCache[senderIdHash] = contact
|
contactCache[senderIdHash] = contact
|
||||||
}
|
}
|
||||||
@ -129,49 +137,77 @@ class ConversationAdapter(
|
|||||||
val contact = contactCache[senderIdHash]
|
val contact = contactCache[senderIdHash]
|
||||||
|
|
||||||
visibleMessageView.bind(
|
visibleMessageView.bind(
|
||||||
message,
|
message,
|
||||||
messageBefore,
|
messageBefore,
|
||||||
getMessageAfter(position, cursor),
|
getMessageAfter(position, cursor),
|
||||||
glide,
|
glide,
|
||||||
searchQuery,
|
searchQuery,
|
||||||
contact,
|
contact,
|
||||||
senderId,
|
senderId,
|
||||||
lastSeen.get(),
|
lastSeen.get(),
|
||||||
visibleMessageViewDelegate,
|
visibleMessageViewDelegate,
|
||||||
onAttachmentNeedsDownload,
|
onAttachmentNeedsDownload,
|
||||||
lastSentMessageId
|
lastSentMessageId
|
||||||
)
|
)
|
||||||
|
|
||||||
if (!message.isDeleted) {
|
if (!message.isDeleted) {
|
||||||
visibleMessageView.onPress = { event -> onItemPress(message, viewHolder.adapterPosition, visibleMessageView, event) }
|
visibleMessageView.onPress = { event ->
|
||||||
visibleMessageView.onSwipeToReply = { onItemSwipeToReply(message, viewHolder.adapterPosition) }
|
onItemPress(
|
||||||
visibleMessageView.onLongPress = { onItemLongPress(message, viewHolder.adapterPosition, visibleMessageView) }
|
message,
|
||||||
|
viewHolder.adapterPosition,
|
||||||
|
visibleMessageView,
|
||||||
|
event
|
||||||
|
)
|
||||||
|
}
|
||||||
|
visibleMessageView.onSwipeToReply =
|
||||||
|
{ onItemSwipeToReply(message, viewHolder.adapterPosition) }
|
||||||
|
visibleMessageView.onLongPress =
|
||||||
|
{ onItemLongPress(message, viewHolder.adapterPosition, visibleMessageView) }
|
||||||
} else {
|
} else {
|
||||||
visibleMessageView.onPress = null
|
visibleMessageView.onPress = null
|
||||||
visibleMessageView.onSwipeToReply = null
|
visibleMessageView.onSwipeToReply = null
|
||||||
visibleMessageView.onLongPress = null
|
visibleMessageView.onLongPress = null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
is ControlMessageViewHolder -> {
|
is ControlMessageViewHolder -> {
|
||||||
viewHolder.view.bind(message, messageBefore)
|
viewHolder.view.bind(message, messageBefore)
|
||||||
if (message.isCallLog && message.isFirstMissedCall) {
|
when {
|
||||||
viewHolder.view.setOnClickListener {
|
// Click behaviour for first missed call control message
|
||||||
context.showSessionDialog {
|
//todo this behaviour is different than iOS where the control message is always clickable when the call toggle is disabled in the privacy page
|
||||||
val titleTxt = context.getSubbedString(R.string.callsMissedCallFrom, NAME_KEY to message.individualRecipient.name!!)
|
message.isCallLog && message.isFirstMissedCall -> {
|
||||||
title(titleTxt)
|
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!!)
|
val bodyTxt = context.getSubbedCharSequence(
|
||||||
text(bodyTxt)
|
R.string.callsYouMissedCallPermissions,
|
||||||
|
NAME_KEY to message.individualRecipient.name!!
|
||||||
|
)
|
||||||
|
text(bodyTxt)
|
||||||
|
|
||||||
button(R.string.sessionSettings) {
|
button(R.string.sessionSettings) {
|
||||||
Intent(context, PrivacySettingsActivity::class.java)
|
Intent(context, PrivacySettingsActivity::class.java)
|
||||||
.let(context::startActivity)
|
.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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -31,6 +31,7 @@ import org.session.libsession.utilities.recipients.Recipient
|
|||||||
import org.session.libsignal.utilities.Log
|
import org.session.libsignal.utilities.Log
|
||||||
import org.session.libsignal.utilities.guava.Optional
|
import org.session.libsignal.utilities.guava.Optional
|
||||||
import org.session.libsignal.utilities.toHexString
|
import org.session.libsignal.utilities.toHexString
|
||||||
|
import org.thoughtcrime.securesms.MissingMicrophonePermissionDialog
|
||||||
import org.thoughtcrime.securesms.media.MediaOverviewActivity
|
import org.thoughtcrime.securesms.media.MediaOverviewActivity
|
||||||
import org.thoughtcrime.securesms.ShortcutLauncherActivity
|
import org.thoughtcrime.securesms.ShortcutLauncherActivity
|
||||||
import org.thoughtcrime.securesms.calls.WebRtcCallActivity
|
import org.thoughtcrime.securesms.calls.WebRtcCallActivity
|
||||||
@ -182,20 +183,7 @@ object ConversationMenuHelper {
|
|||||||
// or if the user has not granted audio/microphone permissions
|
// or if the user has not granted audio/microphone permissions
|
||||||
else if (!Permissions.hasAll(context, Manifest.permission.RECORD_AUDIO)) {
|
else if (!Permissions.hasAll(context, Manifest.permission.RECORD_AUDIO)) {
|
||||||
Log.d("Loki", "Attempted to make a call without audio permissions")
|
Log.d("Loki", "Attempted to make a call without audio permissions")
|
||||||
context.showSessionDialog {
|
MissingMicrophonePermissionDialog.show(context)
|
||||||
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()
|
|
||||||
}
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -41,6 +41,7 @@ public interface MmsSmsColumns {
|
|||||||
protected static final long MISSED_CALL_TYPE = 3;
|
protected static final long MISSED_CALL_TYPE = 3;
|
||||||
protected static final long JOINED_TYPE = 4;
|
protected static final long JOINED_TYPE = 4;
|
||||||
protected static final long FIRST_MISSED_CALL_TYPE = 5;
|
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_INBOX_TYPE = 20;
|
||||||
protected static final long BASE_OUTBOX_TYPE = 21;
|
protected static final long BASE_OUTBOX_TYPE = 21;
|
||||||
@ -234,7 +235,8 @@ public interface MmsSmsColumns {
|
|||||||
|
|
||||||
public static boolean isCallLog(long type) {
|
public static boolean isCallLog(long type) {
|
||||||
long baseType = type & BASE_TYPE_MASK;
|
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) {
|
public static boolean isExpirationTimerUpdate(long type) {
|
||||||
@ -265,6 +267,10 @@ public interface MmsSmsColumns {
|
|||||||
return (type & BASE_TYPE_MASK) == MISSED_CALL_TYPE;
|
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) {
|
public static boolean isFirstMissedCall(long type) {
|
||||||
return (type & BASE_TYPE_MASK) == FIRST_MISSED_CALL_TYPE;
|
return (type & BASE_TYPE_MASK) == FIRST_MISSED_CALL_TYPE;
|
||||||
}
|
}
|
||||||
|
@ -501,6 +501,8 @@ public class SmsDatabase extends MessagingDatabase {
|
|||||||
return Types.MISSED_CALL_TYPE;
|
return Types.MISSED_CALL_TYPE;
|
||||||
case CALL_FIRST_MISSED:
|
case CALL_FIRST_MISSED:
|
||||||
return Types.FIRST_MISSED_CALL_TYPE;
|
return Types.FIRST_MISSED_CALL_TYPE;
|
||||||
|
case CALL_MISSED_PERMISSION:
|
||||||
|
return Types.MISSED_PERMISSION_CALL_TYPE;
|
||||||
default:
|
default:
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
@ -142,6 +142,9 @@ public abstract class DisplayRecord {
|
|||||||
public boolean isFirstMissedCall() {
|
public boolean isFirstMissedCall() {
|
||||||
return SmsDatabase.Types.isFirstMissedCall(type);
|
return SmsDatabase.Types.isFirstMissedCall(type);
|
||||||
}
|
}
|
||||||
|
public boolean isMissedPermissionCall() {
|
||||||
|
return SmsDatabase.Types.isMissedPermissionCall(type);
|
||||||
|
}
|
||||||
public boolean isDeleted() { return MmsSmsColumns.Types.isDeletedMessage(type); }
|
public boolean isDeleted() { return MmsSmsColumns.Types.isDeletedMessage(type); }
|
||||||
public boolean isMessageRequestResponse() { return MmsSmsColumns.Types.isMessageRequestResponse(type); }
|
public boolean isMessageRequestResponse() { return MmsSmsColumns.Types.isMessageRequestResponse(type); }
|
||||||
|
|
||||||
|
@ -134,6 +134,8 @@ public abstract class MessageRecord extends DisplayRecord {
|
|||||||
callType = CallMessageType.CALL_OUTGOING;
|
callType = CallMessageType.CALL_OUTGOING;
|
||||||
} else if (isMissedCall()) {
|
} else if (isMissedCall()) {
|
||||||
callType = CallMessageType.CALL_MISSED;
|
callType = CallMessageType.CALL_MISSED;
|
||||||
|
} else if (isMissedPermissionCall()) {
|
||||||
|
callType = CallMessageType.CALL_MISSED_PERMISSION;
|
||||||
} else {
|
} else {
|
||||||
callType = CallMessageType.CALL_FIRST_MISSED;
|
callType = CallMessageType.CALL_FIRST_MISSED;
|
||||||
}
|
}
|
||||||
|
@ -78,8 +78,10 @@ class CallMessageProcessor(private val context: Context, private val textSecureP
|
|||||||
}
|
}
|
||||||
// or if the user has not granted audio/microphone permissions
|
// or if the user has not granted audio/microphone permissions
|
||||||
else if (!Permissions.hasAll(context, Manifest.permission.RECORD_AUDIO)) {
|
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")
|
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
|
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) {
|
private fun incomingHangup(callMessage: CallMessage) {
|
||||||
val callId = callMessage.callId ?: return
|
val callId = callMessage.callId ?: return
|
||||||
val hangupIntent = WebRtcCallService.remoteHangupIntent(context, callId)
|
val hangupIntent = WebRtcCallService.remoteHangupIntent(context, callId)
|
||||||
|
@ -5,4 +5,5 @@ enum class CallMessageType {
|
|||||||
CALL_INCOMING,
|
CALL_INCOMING,
|
||||||
CALL_OUTGOING,
|
CALL_OUTGOING,
|
||||||
CALL_FIRST_MISSED,
|
CALL_FIRST_MISSED,
|
||||||
|
CALL_MISSED_PERMISSION,
|
||||||
}
|
}
|
@ -1,7 +1,6 @@
|
|||||||
package org.session.libsession.messaging.utilities
|
package org.session.libsession.messaging.utilities
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.text.SpannableString
|
|
||||||
import com.squareup.phrase.Phrase
|
import com.squareup.phrase.Phrase
|
||||||
import org.session.libsession.R
|
import org.session.libsession.R
|
||||||
import org.session.libsession.messaging.MessagingModuleConfiguration
|
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_FIRST_MISSED
|
||||||
import org.session.libsession.messaging.calls.CallMessageType.CALL_INCOMING
|
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
|
||||||
|
import org.session.libsession.messaging.calls.CallMessageType.CALL_MISSED_PERMISSION
|
||||||
import org.session.libsession.messaging.calls.CallMessageType.CALL_OUTGOING
|
import org.session.libsession.messaging.calls.CallMessageType.CALL_OUTGOING
|
||||||
import org.session.libsession.messaging.contacts.Contact
|
import org.session.libsession.messaging.contacts.Contact
|
||||||
import org.session.libsession.messaging.sending_receiving.data_extraction.DataExtractionNotificationInfoMessage
|
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.NAME_KEY
|
||||||
import org.session.libsession.utilities.StringSubstitutionConstants.OTHER_NAME_KEY
|
import org.session.libsession.utilities.StringSubstitutionConstants.OTHER_NAME_KEY
|
||||||
import org.session.libsession.utilities.StringSubstitutionConstants.TIME_KEY
|
import org.session.libsession.utilities.StringSubstitutionConstants.TIME_KEY
|
||||||
import org.session.libsession.utilities.Util
|
|
||||||
|
|
||||||
object UpdateMessageBuilder {
|
object UpdateMessageBuilder {
|
||||||
const val TAG = "libsession"
|
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_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_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, 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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user