diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/CallSignalingHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/CallSignalingHandler.kt index 562809a826..a6de65f9f5 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/CallSignalingHandler.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/CallSignalingHandler.kt @@ -160,7 +160,7 @@ internal class CallSignalingHandler @Inject constructor(private val activeCallHa val content = event.getClearContent().toModel() ?: return val incomingCall = mxCallFactory.createIncomingCall( roomId = event.roomId, - senderId = event.senderId, + opponentUserId = event.senderId, content = content ) ?: return activeCallHandler.addCall(incomingCall) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/MxCallFactory.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/MxCallFactory.kt index f970522cc9..3c258df31a 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/MxCallFactory.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/MxCallFactory.kt @@ -35,7 +35,7 @@ internal class MxCallFactory @Inject constructor( @UserId private val userId: String ) { - fun createIncomingCall(roomId: String, senderId: String, content: CallInviteContent): MxCall? { + fun createIncomingCall(roomId: String, opponentUserId: String, content: CallInviteContent): MxCall? { if (content.callId == null) return null return MxCallImpl( callId = content.callId, @@ -43,7 +43,7 @@ internal class MxCallFactory @Inject constructor( roomId = roomId, userId = userId, ourPartyId = deviceId ?: "", - opponentUserId = senderId, + opponentUserId = opponentUserId, isVideoCall = content.isVideo(), localEchoEventFactory = localEchoEventFactory, eventSenderProcessor = eventSenderProcessor @@ -53,14 +53,14 @@ internal class MxCallFactory @Inject constructor( } } - fun createOutgoingCall(roomId: String, otherUserId: String, isVideoCall: Boolean): MxCall { + fun createOutgoingCall(roomId: String, opponentUserId: String, isVideoCall: Boolean): MxCall { return MxCallImpl( callId = UUID.randomUUID().toString(), isOutgoing = true, roomId = roomId, userId = userId, ourPartyId = deviceId ?: "", - opponentUserId = otherUserId, + opponentUserId = opponentUserId, isVideoCall = isVideoCall, localEchoEventFactory = localEchoEventFactory, eventSenderProcessor = eventSenderProcessor diff --git a/vector/build.gradle b/vector/build.gradle index 6edaec1755..69f8daf47b 100644 --- a/vector/build.gradle +++ b/vector/build.gradle @@ -239,7 +239,7 @@ android { productFlavors { gplay { dimension "store" - + isDefault = true versionName "${versionMajor}.${versionMinor}.${versionPatch}${getGplayVersionSuffix()}" resValue "bool", "isGplay", "true" diff --git a/vector/src/main/java/im/vector/app/core/services/CallService.kt b/vector/src/main/java/im/vector/app/core/services/CallService.kt index 397394e4fe..a5a59dc0ba 100644 --- a/vector/src/main/java/im/vector/app/core/services/CallService.kt +++ b/vector/src/main/java/im/vector/app/core/services/CallService.kt @@ -24,10 +24,20 @@ import android.support.v4.media.session.MediaSessionCompat import android.view.KeyEvent import androidx.core.content.ContextCompat import androidx.media.session.MediaButtonReceiver +import com.airbnb.mvrx.MvRx import im.vector.app.core.extensions.vectorComponent -import im.vector.app.features.call.webrtc.WebRtcCallManager +import im.vector.app.features.call.CallArgs +import im.vector.app.features.call.VectorCallActivity import im.vector.app.features.call.telecom.CallConnection +import im.vector.app.features.call.webrtc.WebRtcCall +import im.vector.app.features.call.webrtc.WebRtcCallManager +import im.vector.app.features.home.AvatarRenderer +import im.vector.app.features.home.room.detail.RoomDetailActivity import im.vector.app.features.notifications.NotificationUtils +import im.vector.app.features.popup.IncomingCallAlert +import im.vector.app.features.popup.PopupAlertManager +import org.matrix.android.sdk.api.util.MatrixItem +import org.matrix.android.sdk.api.util.toMatrixItem import timber.log.Timber /** @@ -39,6 +49,8 @@ class CallService : VectorService(), WiredHeadsetStateReceiver.HeadsetEventListe private lateinit var notificationUtils: NotificationUtils private lateinit var callManager: WebRtcCallManager + private lateinit var avatarRenderer: AvatarRenderer + private lateinit var alertManager: PopupAlertManager private var callRingPlayerIncoming: CallRingPlayerIncoming? = null private var callRingPlayerOutgoing: CallRingPlayerOutgoing? = null @@ -64,6 +76,8 @@ class CallService : VectorService(), WiredHeadsetStateReceiver.HeadsetEventListe super.onCreate() notificationUtils = vectorComponent().notificationUtils() callManager = vectorComponent().webRtcCallManager() + avatarRenderer = vectorComponent().avatarRenderer() + alertManager = vectorComponent().alertManager() callRingPlayerIncoming = CallRingPlayerIncoming(applicationContext) callRingPlayerOutgoing = CallRingPlayerOutgoing(applicationContext) wiredHeadsetStateReceiver = WiredHeadsetStateReceiver.createAndRegister(this, this) @@ -111,20 +125,20 @@ class CallService : VectorService(), WiredHeadsetStateReceiver.HeadsetEventListe callRingPlayerOutgoing?.start() displayOutgoingRingingCallNotification(intent) } - ACTION_ONGOING_CALL -> { + ACTION_ONGOING_CALL -> { callRingPlayerIncoming?.stop() callRingPlayerOutgoing?.stop() displayCallInProgressNotification(intent) } - ACTION_NO_ACTIVE_CALL -> hideCallNotifications() - ACTION_CALL_CONNECTING -> { + ACTION_NO_ACTIVE_CALL -> hideCallNotifications() + ACTION_CALL_CONNECTING -> { // lower notification priority displayCallInProgressNotification(intent) // stop ringing callRingPlayerIncoming?.stop() callRingPlayerOutgoing?.stop() } - ACTION_ONGOING_CALL_BG -> { + ACTION_ONGOING_CALL_BG -> { // there is an ongoing call but call activity is in background displayCallOnGoingInBackground(intent) } @@ -147,63 +161,61 @@ class CallService : VectorService(), WiredHeadsetStateReceiver.HeadsetEventListe /** * Display a permanent notification when there is an incoming call. * - * @param session the session - * @param isVideo true if this is a video call, false for voice call - * @param room the room - * @param callId the callId */ private fun displayIncomingCallNotification(intent: Intent) { Timber.v("## VOIP displayIncomingCallNotification $intent") - - // the incoming call in progress is already displayed -// if (!TextUtils.isEmpty(mIncomingCallId)) { -// Timber.v("displayIncomingCallNotification : the incoming call in progress is already displayed") -// } else if (!TextUtils.isEmpty(mCallIdInProgress)) { -// Timber.v("displayIncomingCallNotification : a 'call in progress' notification is displayed") -// } else -// // if (null == webRtcPeerConnectionManager.currentCall) -// { - val callId = intent.getStringExtra(EXTRA_CALL_ID) - + val callId = intent.getStringExtra(EXTRA_CALL_ID) ?: "" + val call = callManager.getCallById(callId) ?: return + val isVideoCall = call.mxCall.isVideoCall + val fromBg = intent.getBooleanExtra(EXTRA_IS_IN_BG, false) + val opponentMatrixItem = getOpponentMatrixItem(call) Timber.v("displayIncomingCallNotification : display the dedicated notification") + val incomingCallAlert = IncomingCallAlert(INCOMING_CALL_ALERT_UID, + shouldBeDisplayedIn = { activity -> + if (activity is RoomDetailActivity) { + call.roomId != activity.currentRoomId + } else if (activity is VectorCallActivity) { + activity.intent.getParcelableExtra(MvRx.KEY_ARG)?.callId != call.callId + } else true + } + ).apply { + viewBinder = IncomingCallAlert.ViewBinder( + matrixItem = opponentMatrixItem, + avatarRenderer = avatarRenderer, + isVideoCall = isVideoCall, + onAccept = { showCallScreen(call, VectorCallActivity.INCOMING_ACCEPT) }, + onReject = { call.endCall() } + ) + dismissedAction = Runnable { call.endCall() } + contentAction = Runnable { showCallScreen(call, VectorCallActivity.INCOMING_RINGING) } + } + alertManager.postVectorAlert(incomingCallAlert) val notification = notificationUtils.buildIncomingCallNotification( - intent.getBooleanExtra(EXTRA_IS_VIDEO, false), - intent.getStringExtra(EXTRA_ROOM_NAME) ?: "", - intent.getStringExtra(EXTRA_ROOM_ID) ?: "", - callId ?: "") + mxCall = call.mxCall, + title = opponentMatrixItem?.getBestName() ?: call.mxCall.opponentUserId, + fromBg = fromBg + ) startForeground(NOTIFICATION_ID, notification) + } -// mIncomingCallId = callId - - // turn the screen on for 3 seconds -// if (Matrix.getInstance(VectorApp.getInstance())!!.pushManager.isScreenTurnedOn) { -// try { -// val pm = getSystemService()!! -// val wl = pm.newWakeLock( -// WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON or PowerManager.ACQUIRE_CAUSES_WAKEUP, -// CallService::class.java.simpleName) -// wl.acquire(3000) -// wl.release() -// } catch (re: RuntimeException) { -// Timber.e(re, "displayIncomingCallNotification : failed to turn screen on ") -// } -// -// } -// } -// else { -// Timber.i("displayIncomingCallNotification : do not display the incoming call notification because there is a pending call") -// } + private fun showCallScreen(call: WebRtcCall, mode: String) { + val intent = VectorCallActivity.newIntent( + context = this, + mxCall = call.mxCall, + mode = mode + ) + startActivity(intent) } private fun displayOutgoingRingingCallNotification(intent: Intent) { - val callId = intent.getStringExtra(EXTRA_CALL_ID) - + val callId = intent.getStringExtra(EXTRA_CALL_ID) ?: return + val call = callManager.getCallById(callId) ?: return + val opponentMatrixItem = getOpponentMatrixItem(call) Timber.v("displayOutgoingCallNotification : display the dedicated notification") val notification = notificationUtils.buildOutgoingRingingCallNotification( - intent.getBooleanExtra(EXTRA_IS_VIDEO, false), - intent.getStringExtra(EXTRA_ROOM_NAME) ?: "", - intent.getStringExtra(EXTRA_ROOM_ID) ?: "", - callId ?: "") + mxCall = call.mxCall, + title = opponentMatrixItem?.getBestName() ?: call.mxCall.opponentUserId + ) startForeground(NOTIFICATION_ID, notification) } @@ -213,16 +225,14 @@ class CallService : VectorService(), WiredHeadsetStateReceiver.HeadsetEventListe private fun displayCallInProgressNotification(intent: Intent) { Timber.v("## VOIP displayCallInProgressNotification") val callId = intent.getStringExtra(EXTRA_CALL_ID) ?: "" - + val call = callManager.getCallById(callId) ?: return + val opponentMatrixItem = getOpponentMatrixItem(call) + alertManager.cancelAlert(INCOMING_CALL_ALERT_UID) val notification = notificationUtils.buildPendingCallNotification( - intent.getBooleanExtra(EXTRA_IS_VIDEO, false), - intent.getStringExtra(EXTRA_ROOM_NAME) ?: "", - intent.getStringExtra(EXTRA_ROOM_ID) ?: "", - intent.getStringExtra(EXTRA_MATRIX_ID) ?: "", - callId) - + mxCall = call.mxCall, + title = opponentMatrixItem?.getBestName() ?: call.mxCall.opponentUserId + ) startForeground(NOTIFICATION_ID, notification) - // mCallIdInProgress = callId } @@ -231,18 +241,15 @@ class CallService : VectorService(), WiredHeadsetStateReceiver.HeadsetEventListe */ private fun displayCallOnGoingInBackground(intent: Intent) { Timber.v("## VOIP displayCallInProgressNotification") - val callId = intent.getStringExtra(EXTRA_CALL_ID) ?: "" + val callId = intent.getStringExtra(EXTRA_CALL_ID) ?: return + val call = callManager.getCallById(callId) ?: return + val opponentMatrixItem = getOpponentMatrixItem(call) val notification = notificationUtils.buildPendingCallNotification( - isVideo = intent.getBooleanExtra(EXTRA_IS_VIDEO, false), - roomName = intent.getStringExtra(EXTRA_ROOM_NAME) ?: "", - roomId = intent.getStringExtra(EXTRA_ROOM_ID) ?: "", - matrixId = intent.getStringExtra(EXTRA_MATRIX_ID) ?: "", - callId = callId, + mxCall = call.mxCall, + title = opponentMatrixItem?.getBestName() ?: call.mxCall.opponentUserId, fromBg = true) - startForeground(NOTIFICATION_ID, notification) - // mCallIdInProgress = callId } @@ -251,7 +258,7 @@ class CallService : VectorService(), WiredHeadsetStateReceiver.HeadsetEventListe */ private fun hideCallNotifications() { val notification = notificationUtils.buildCallEndedNotification() - + alertManager.cancelAlert(INCOMING_CALL_ALERT_UID) mediaSession?.isActive = false // It's mandatory to startForeground to avoid crash startForeground(NOTIFICATION_ID, notification) @@ -263,9 +270,14 @@ class CallService : VectorService(), WiredHeadsetStateReceiver.HeadsetEventListe connections[callConnection.callId] = callConnection } + private fun getOpponentMatrixItem(call: WebRtcCall): MatrixItem? { + return vectorComponent().currentSession().getUser(call.mxCall.opponentUserId)?.toMatrixItem() + } + companion object { private const val NOTIFICATION_ID = 6480 + private const val INCOMING_CALL_ALERT_UID = "INCOMING_CALL_ALERT_UID" private const val ACTION_INCOMING_RINGING_CALL = "im.vector.app.core.services.CallService.ACTION_INCOMING_RINGING_CALL" private const val ACTION_OUTGOING_RINGING_CALL = "im.vector.app.core.services.CallService.ACTION_OUTGOING_RINGING_CALL" private const val ACTION_CALL_CONNECTING = "im.vector.app.core.services.CallService.ACTION_CALL_CONNECTING" @@ -275,44 +287,26 @@ class CallService : VectorService(), WiredHeadsetStateReceiver.HeadsetEventListe // private const val ACTION_ACTIVITY_VISIBLE = "im.vector.app.core.services.CallService.ACTION_ACTIVITY_VISIBLE" // private const val ACTION_STOP_RINGING = "im.vector.app.core.services.CallService.ACTION_STOP_RINGING" - private const val EXTRA_IS_VIDEO = "EXTRA_IS_VIDEO" - private const val EXTRA_ROOM_NAME = "EXTRA_ROOM_NAME" - private const val EXTRA_ROOM_ID = "EXTRA_ROOM_ID" - private const val EXTRA_MATRIX_ID = "EXTRA_MATRIX_ID" private const val EXTRA_CALL_ID = "EXTRA_CALL_ID" + private const val EXTRA_IS_IN_BG = "EXTRA_IS_IN_BG" fun onIncomingCallRinging(context: Context, - isVideo: Boolean, - roomName: String, - roomId: String, - matrixId: String, - callId: String) { + callId: String, + isInBackground: Boolean) { val intent = Intent(context, CallService::class.java) .apply { action = ACTION_INCOMING_RINGING_CALL - putExtra(EXTRA_IS_VIDEO, isVideo) - putExtra(EXTRA_ROOM_NAME, roomName) - putExtra(EXTRA_ROOM_ID, roomId) - putExtra(EXTRA_MATRIX_ID, matrixId) putExtra(EXTRA_CALL_ID, callId) + putExtra(EXTRA_IS_IN_BG, isInBackground) } - ContextCompat.startForegroundService(context, intent) } fun onOnGoingCallBackground(context: Context, - isVideo: Boolean, - roomName: String, - roomId: String, - matrixId: String, callId: String) { val intent = Intent(context, CallService::class.java) .apply { action = ACTION_ONGOING_CALL_BG - putExtra(EXTRA_IS_VIDEO, isVideo) - putExtra(EXTRA_ROOM_NAME, roomName) - putExtra(EXTRA_ROOM_ID, roomId) - putExtra(EXTRA_MATRIX_ID, matrixId) putExtra(EXTRA_CALL_ID, callId) } @@ -320,18 +314,10 @@ class CallService : VectorService(), WiredHeadsetStateReceiver.HeadsetEventListe } fun onOutgoingCallRinging(context: Context, - isVideo: Boolean, - roomName: String, - roomId: String, - matrixId: String, callId: String) { val intent = Intent(context, CallService::class.java) .apply { action = ACTION_OUTGOING_RINGING_CALL - putExtra(EXTRA_IS_VIDEO, isVideo) - putExtra(EXTRA_ROOM_NAME, roomName) - putExtra(EXTRA_ROOM_ID, roomId) - putExtra(EXTRA_MATRIX_ID, matrixId) putExtra(EXTRA_CALL_ID, callId) } @@ -339,18 +325,10 @@ class CallService : VectorService(), WiredHeadsetStateReceiver.HeadsetEventListe } fun onPendingCall(context: Context, - isVideo: Boolean, - roomName: String, - roomId: String, - matrixId: String, callId: String) { val intent = Intent(context, CallService::class.java) .apply { action = ACTION_ONGOING_CALL - putExtra(EXTRA_IS_VIDEO, isVideo) - putExtra(EXTRA_ROOM_NAME, roomName) - putExtra(EXTRA_ROOM_ID, roomId) - putExtra(EXTRA_MATRIX_ID, matrixId) putExtra(EXTRA_CALL_ID, callId) } @@ -362,7 +340,6 @@ class CallService : VectorService(), WiredHeadsetStateReceiver.HeadsetEventListe .apply { action = ACTION_NO_ACTIVE_CALL } - ContextCompat.startForegroundService(context, intent) } } diff --git a/vector/src/main/java/im/vector/app/features/call/CallControlsView.kt b/vector/src/main/java/im/vector/app/features/call/CallControlsView.kt index 93fc132a8f..d0b472b295 100644 --- a/vector/src/main/java/im/vector/app/features/call/CallControlsView.kt +++ b/vector/src/main/java/im/vector/app/features/call/CallControlsView.kt @@ -18,10 +18,10 @@ package im.vector.app.features.call import android.content.Context import android.util.AttributeSet +import android.view.View import android.view.ViewGroup +import android.widget.FrameLayout import android.widget.ImageView -import android.widget.LinearLayout -import androidx.constraintlayout.widget.ConstraintLayout import androidx.core.view.isVisible import butterknife.BindView import butterknife.ButterKnife @@ -33,7 +33,7 @@ import org.matrix.android.sdk.api.session.call.MxPeerConnectionState class CallControlsView @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0 -) : LinearLayout(context, attrs, defStyleAttr) { +) : FrameLayout(context, attrs, defStyleAttr) { var interactionListener: InteractionListener? = null @@ -56,8 +56,7 @@ class CallControlsView @JvmOverloads constructor( lateinit var videoToggleIcon: ImageView init { - ConstraintLayout.inflate(context, R.layout.view_call_controls, this) - // layoutParams = FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT) + View.inflate(context, R.layout.view_call_controls, this) ButterKnife.bind(this) } diff --git a/vector/src/main/java/im/vector/app/features/call/webrtc/WebRtcCall.kt b/vector/src/main/java/im/vector/app/features/call/webrtc/WebRtcCall.kt index 4e12934d12..10c7cb2e24 100644 --- a/vector/src/main/java/im/vector/app/features/call/webrtc/WebRtcCall.kt +++ b/vector/src/main/java/im/vector/app/features/call/webrtc/WebRtcCall.kt @@ -107,6 +107,7 @@ class WebRtcCall(val mxCall: MxCall, } val callId = mxCall.callId + val roomId = mxCall.roomId private var peerConnection: PeerConnection? = null private var localAudioSource: AudioSource? = null @@ -237,16 +238,9 @@ class WebRtcCall(val mxCall: MxCall, mxCall .takeIf { it.state is CallState.Connected } ?.let { mxCall -> - val session = sessionProvider.get() - val name = session?.getUser(mxCall.opponentUserId)?.getBestName() - ?: mxCall.roomId // Start background service with notification CallService.onPendingCall( context = context, - isVideo = mxCall.isVideoCall, - roomName = name, - roomId = mxCall.roomId, - matrixId = session?.myUserId ?: "", callId = mxCall.callId) } @@ -307,15 +301,8 @@ class WebRtcCall(val mxCall: MxCall, .takeIf { it.state is CallState.Connected } ?.let { mxCall -> // Start background service with notification - val session = sessionProvider.get() - val name = session?.getUser(mxCall.opponentUserId)?.getBestName() - ?: mxCall.opponentUserId CallService.onOnGoingCallBackground( context = context, - isVideo = mxCall.isVideoCall, - roomName = name, - roomId = mxCall.roomId, - matrixId = session?.myUserId ?: "", callId = mxCall.callId ) } @@ -344,15 +331,8 @@ class WebRtcCall(val mxCall: MxCall, val turnServerResponse = getTurnServer() // Update service state withContext(Dispatchers.Main) { - val session = sessionProvider.get() - val name = session?.getUser(mxCall.opponentUserId)?.getBestName() - ?: mxCall.roomId CallService.onPendingCall( context = context, - isVideo = mxCall.isVideoCall, - roomName = name, - roomId = mxCall.roomId, - matrixId = session?.myUserId ?: "", callId = mxCall.callId ) } diff --git a/vector/src/main/java/im/vector/app/features/call/webrtc/WebRtcCallManager.kt b/vector/src/main/java/im/vector/app/features/call/webrtc/WebRtcCallManager.kt index f3665f0b45..f6fcfd446e 100644 --- a/vector/src/main/java/im/vector/app/features/call/webrtc/WebRtcCallManager.kt +++ b/vector/src/main/java/im/vector/app/features/call/webrtc/WebRtcCallManager.kt @@ -189,14 +189,8 @@ class WebRtcCallManager @Inject constructor( createWebRtcCall(mxCall) callAudioManager.startForCall(mxCall) - val name = currentSession?.getUser(mxCall.opponentUserId)?.getBestName() - ?: mxCall.opponentUserId CallService.onOutgoingCallRinging( context = context.applicationContext, - isVideo = mxCall.isVideoCall, - roomName = name, - roomId = mxCall.roomId, - matrixId = currentSession?.myUserId ?: "", callId = mxCall.callId) // start the activity now @@ -264,15 +258,10 @@ class WebRtcCallManager @Inject constructor( } callAudioManager.startForCall(mxCall) // Start background service with notification - val name = currentSession?.getUser(mxCall.opponentUserId)?.getBestName() - ?: mxCall.opponentUserId CallService.onIncomingCallRinging( context = context, - isVideo = mxCall.isVideoCall, - roomName = name, - roomId = mxCall.roomId, - matrixId = currentSession?.myUserId ?: "", - callId = mxCall.callId + callId = mxCall.callId, + isInBackground = isInBackground ) // If this is received while in background, the app will not sync, // and thus won't be able to received events. For example if the call is @@ -294,14 +283,8 @@ class WebRtcCallManager @Inject constructor( } val mxCall = call.mxCall // Update service state - val name = currentSession?.getUser(mxCall.opponentUserId)?.getBestName() - ?: mxCall.opponentUserId CallService.onPendingCall( context = context, - isVideo = mxCall.isVideoCall, - roomName = name, - roomId = mxCall.roomId, - matrixId = currentSession?.myUserId ?: "", callId = mxCall.callId ) call.onCallAnswerReceived(callAnswerContent) diff --git a/vector/src/main/java/im/vector/app/features/crypto/verification/IncomingVerificationRequestHandler.kt b/vector/src/main/java/im/vector/app/features/crypto/verification/IncomingVerificationRequestHandler.kt index 7d98b7c2a5..aca283a6ab 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/verification/IncomingVerificationRequestHandler.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/verification/IncomingVerificationRequestHandler.kt @@ -18,11 +18,11 @@ package im.vector.app.features.crypto.verification import android.content.Context import im.vector.app.R import im.vector.app.core.platform.VectorBaseActivity +import im.vector.app.features.home.AvatarRenderer import im.vector.app.features.home.room.detail.RoomDetailActivity import im.vector.app.features.home.room.detail.RoomDetailArgs import im.vector.app.features.popup.PopupAlertManager import im.vector.app.features.popup.VerificationVectorAlert -import im.vector.app.features.themes.ThemeUtils import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.crypto.verification.PendingVerificationRequest import org.matrix.android.sdk.api.session.crypto.verification.VerificationService @@ -31,6 +31,7 @@ import org.matrix.android.sdk.api.session.crypto.verification.VerificationTxStat import org.matrix.android.sdk.api.util.toMatrixItem import timber.log.Timber import javax.inject.Inject +import javax.inject.Provider import javax.inject.Singleton /** @@ -39,6 +40,7 @@ import javax.inject.Singleton @Singleton class IncomingVerificationRequestHandler @Inject constructor( private val context: Context, + private var avatarRenderer: Provider, private val popupAlertManager: PopupAlertManager) : VerificationService.Listener { private var session: Session? = null @@ -60,9 +62,8 @@ class IncomingVerificationRequestHandler @Inject constructor( when (tx.state) { is VerificationTxState.OnStarted -> { // Add a notification for every incoming request - val name = session?.getUser(tx.otherUserId)?.displayName - ?: tx.otherUserId - + val user = session?.getUser(tx.otherUserId) + val name = user?.getBestName() ?: tx.otherUserId val alert = VerificationVectorAlert( uid, context.getString(R.string.sas_incoming_request_notif_title), @@ -77,10 +78,10 @@ class IncomingVerificationRequestHandler @Inject constructor( } } ?: true } else true - }, - matrixItem = session?.getUser(tx.otherUserId)?.toMatrixItem() + } ) .apply { + viewBinder = VerificationVectorAlert.ViewBinder(user?.toMatrixItem(), avatarRenderer.get()) contentAction = Runnable { (weakCurrentActivity?.get() as? VectorBaseActivity)?.let { it.navigator.performDeviceVerification(it, tx.otherUserId, tx.transactionId) @@ -120,8 +121,8 @@ class IncomingVerificationRequestHandler @Inject constructor( Timber.v("## SAS verificationRequestCreated ${pr.transactionId}") // For incoming request we should prompt (if not in activity where this request apply) if (pr.isIncoming) { - val name = session?.getUser(pr.otherUserId)?.displayName - ?: pr.otherUserId + val user = session?.getUser(pr.otherUserId) + val name = user?.getBestName() ?: pr.otherUserId val alert = VerificationVectorAlert( uniqueIdForVerificationRequest(pr), @@ -134,10 +135,10 @@ class IncomingVerificationRequestHandler @Inject constructor( it.roomId != pr.roomId } ?: true } else true - }, - matrixItem = session?.getUser(pr.otherUserId)?.toMatrixItem() + } ) .apply { + viewBinder = VerificationVectorAlert.ViewBinder(user?.toMatrixItem(), avatarRenderer.get()) contentAction = Runnable { (weakCurrentActivity?.get() as? VectorBaseActivity)?.let { val roomId = pr.roomId @@ -154,7 +155,7 @@ class IncomingVerificationRequestHandler @Inject constructor( pr.roomId ?: "" ) } - colorInt = ThemeUtils.getColor(context, R.attr.vctr_notice_secondary) + colorAttribute = R.attr.vctr_notice_secondary // 5mn expiration expirationTimestamp = System.currentTimeMillis() + (5 * 60 * 1000L) } diff --git a/vector/src/main/java/im/vector/app/features/home/HomeActivity.kt b/vector/src/main/java/im/vector/app/features/home/HomeActivity.kt index 7dde0edf32..fac36ee3eb 100644 --- a/vector/src/main/java/im/vector/app/features/home/HomeActivity.kt +++ b/vector/src/main/java/im/vector/app/features/home/HomeActivity.kt @@ -51,7 +51,6 @@ import im.vector.app.features.popup.VerificationVectorAlert import im.vector.app.features.rageshake.VectorUncaughtExceptionHandler import im.vector.app.features.settings.VectorPreferences import im.vector.app.features.settings.VectorSettingsActivity -import im.vector.app.features.themes.ThemeUtils import im.vector.app.features.workers.signout.ServerBackupStatusViewModel import im.vector.app.features.workers.signout.ServerBackupStatusViewState import im.vector.app.push.fcm.FcmHelper @@ -82,6 +81,7 @@ class HomeActivity : VectorBaseActivity(), ToolbarConfigurable, UnknownDeviceDet private val serverBackupStatusViewModel: ServerBackupStatusViewModel by viewModel() @Inject lateinit var serverBackupviewModelFactory: ServerBackupStatusViewModel.Factory + @Inject lateinit var avatarRenderer: AvatarRenderer @Inject lateinit var activeSessionHolder: ActiveSessionHolder @Inject lateinit var vectorUncaughtExceptionHandler: VectorUncaughtExceptionHandler @Inject lateinit var pushManager: PushersManager @@ -126,9 +126,9 @@ class HomeActivity : VectorBaseActivity(), ToolbarConfigurable, UnknownDeviceDet .observe() .subscribe { sharedAction -> when (sharedAction) { - is HomeActivitySharedAction.OpenDrawer -> drawerLayout.openDrawer(GravityCompat.START) + is HomeActivitySharedAction.OpenDrawer -> drawerLayout.openDrawer(GravityCompat.START) is HomeActivitySharedAction.CloseDrawer -> drawerLayout.closeDrawer(GravityCompat.START) - is HomeActivitySharedAction.OpenGroup -> { + is HomeActivitySharedAction.OpenGroup -> { drawerLayout.closeDrawer(GravityCompat.START) replaceFragment(R.id.homeDetailFragmentContainer, HomeDetailFragment::class.java, allowStateLoss = true) } @@ -145,9 +145,9 @@ class HomeActivity : VectorBaseActivity(), ToolbarConfigurable, UnknownDeviceDet homeActivityViewModel.observeViewEvents { when (it) { is HomeActivityViewEvents.AskPasswordToInitCrossSigning -> handleAskPasswordToInitCrossSigning(it) - is HomeActivityViewEvents.OnNewSession -> handleOnNewSession(it) - HomeActivityViewEvents.PromptToEnableSessionPush -> handlePromptToEnablePush() - is HomeActivityViewEvents.OnCrossSignedInvalidated -> handleCrossSigningInvalidated(it) + is HomeActivityViewEvents.OnNewSession -> handleOnNewSession(it) + HomeActivityViewEvents.PromptToEnableSessionPush -> handlePromptToEnablePush() + is HomeActivityViewEvents.OnCrossSignedInvalidated -> handleCrossSigningInvalidated(it) }.exhaustive } homeActivityViewModel.subscribe(this) { renderState(it) } @@ -180,7 +180,7 @@ class HomeActivity : VectorBaseActivity(), ToolbarConfigurable, UnknownDeviceDet private fun renderState(state: HomeActivityViewState) { when (val status = state.initialSyncProgressServiceStatus) { - is InitialSyncProgressService.Status.Idle -> { + is InitialSyncProgressService.Status.Idle -> { waiting_view.isVisible = false } is InitialSyncProgressService.Status.Progressing -> { @@ -251,7 +251,7 @@ class HomeActivity : VectorBaseActivity(), ToolbarConfigurable, UnknownDeviceDet it is HomeActivity } ).apply { - colorInt = ThemeUtils.getColor(this@HomeActivity, R.attr.vctr_notice_secondary) + colorAttribute = R.attr.vctr_notice_secondary contentAction = Runnable { (weakCurrentActivity?.get() as? VectorBaseActivity)?.let { // action(it) @@ -282,9 +282,9 @@ class HomeActivity : VectorBaseActivity(), ToolbarConfigurable, UnknownDeviceDet uid = "upgradeSecurity", title = getString(titleRes), description = getString(descRes), - iconId = R.drawable.ic_shield_warning, - matrixItem = userItem + iconId = R.drawable.ic_shield_warning ).apply { + viewBinder = VerificationVectorAlert.ViewBinder(userItem, avatarRenderer) colorInt = ContextCompat.getColor(this@HomeActivity, R.color.riotx_positive_accent) contentAction = Runnable { (weakCurrentActivity?.get() as? VectorBaseActivity)?.let { diff --git a/vector/src/main/java/im/vector/app/features/home/HomeDetailFragment.kt b/vector/src/main/java/im/vector/app/features/home/HomeDetailFragment.kt index 853eb31274..6a48119a64 100644 --- a/vector/src/main/java/im/vector/app/features/home/HomeDetailFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/HomeDetailFragment.kt @@ -151,9 +151,9 @@ class HomeDetailFragment @Inject constructor( uid = uid, title = getString(R.string.new_session), description = getString(R.string.verify_this_session, newest.displayName ?: newest.deviceId ?: ""), - iconId = R.drawable.ic_shield_warning, - matrixItem = user + iconId = R.drawable.ic_shield_warning ).apply { + viewBinder = VerificationVectorAlert.ViewBinder(user, avatarRenderer) colorInt = ContextCompat.getColor(requireActivity(), R.color.riotx_accent) contentAction = Runnable { (weakCurrentActivity?.get() as? VectorBaseActivity) @@ -179,9 +179,9 @@ class HomeDetailFragment @Inject constructor( uid = uid, title = getString(R.string.review_logins), description = getString(R.string.verify_other_sessions), - iconId = R.drawable.ic_shield_warning, - matrixItem = user + iconId = R.drawable.ic_shield_warning ).apply { + viewBinder = VerificationVectorAlert.ViewBinder(user, avatarRenderer) colorInt = ContextCompat.getColor(requireActivity(), R.color.riotx_accent) contentAction = Runnable { (weakCurrentActivity?.get() as? VectorBaseActivity)?.let { diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailActivity.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailActivity.kt index de82689303..08c1e6806c 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailActivity.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailActivity.kt @@ -72,7 +72,7 @@ class RoomDetailActivity : } // Simple filter - private var currentRoomId: String? = null + var currentRoomId: String? = null override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt index f1617b92fb..d2afbb0fa6 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt @@ -118,8 +118,8 @@ import im.vector.app.features.attachments.preview.AttachmentsPreviewArgs import im.vector.app.features.attachments.toGroupedContentAttachmentData import im.vector.app.features.call.SharedActiveCallViewModel import im.vector.app.features.call.VectorCallActivity -import im.vector.app.features.call.webrtc.WebRtcCallManager import im.vector.app.features.call.conference.JitsiCallViewModel +import im.vector.app.features.call.webrtc.WebRtcCallManager import im.vector.app.features.command.Command import im.vector.app.features.crypto.keysbackup.restore.KeysBackupRestoreActivity import im.vector.app.features.crypto.util.toImageRes @@ -311,7 +311,6 @@ class RoomDetailFragment @Inject constructor( setupActiveCallView() setupJumpToBottomView() setupConfBannerView() - roomToolbarContentView.debouncedClicks { navigator.openRoomProfile(requireActivity(), roomDetailArgs.roomId) } @@ -535,14 +534,14 @@ class RoomDetailFragment @Inject constructor( private fun handleShareData() { when (val sharedData = roomDetailArgs.sharedData) { - is SharedData.Text -> { + is SharedData.Text -> { roomDetailViewModel.handle(RoomDetailAction.EnterRegularMode(sharedData.text, fromSharing = true)) } is SharedData.Attachments -> { // open share edition onContentAttachmentsReady(sharedData.attachmentData) } - null -> Timber.v("No share data to process") + null -> Timber.v("No share data to process") }.exhaustive } @@ -667,8 +666,8 @@ class RoomDetailFragment @Inject constructor( withState(roomDetailViewModel) { state -> // Set the visual state of the call buttons (voice/video) to enabled/disabled according to user permissions val callButtonsEnabled = when (state.asyncRoomSummary.invoke()?.joinedMembersCount) { - 1 -> false - 2 -> state.isAllowedToStartWebRTCCall + 1 -> false + 2 -> state.isAllowedToStartWebRTCCall else -> state.isAllowedToManageWidgets } setOf(R.id.voice_call, R.id.video_call).forEach { @@ -698,36 +697,36 @@ class RoomDetailFragment @Inject constructor( override fun onOptionsItemSelected(item: MenuItem): Boolean { return when (item.itemId) { - R.id.invite -> { + R.id.invite -> { navigator.openInviteUsersToRoom(requireActivity(), roomDetailArgs.roomId) true } - R.id.timeline_setting -> { + R.id.timeline_setting -> { navigator.openRoomProfile(requireActivity(), roomDetailArgs.roomId) true } - R.id.resend_all -> { + R.id.resend_all -> { roomDetailViewModel.handle(RoomDetailAction.ResendAll) true } - R.id.open_matrix_apps -> { + R.id.open_matrix_apps -> { roomDetailViewModel.handle(RoomDetailAction.ManageIntegrations) true } R.id.voice_call, - R.id.video_call -> { + R.id.video_call -> { handleCallRequest(item) true } - R.id.hangup_call -> { + R.id.hangup_call -> { roomDetailViewModel.handle(RoomDetailAction.EndCall) true } - R.id.search -> { + R.id.search -> { handleSearchAction() true } - else -> super.onOptionsItemSelected(item) + else -> super.onOptionsItemSelected(item) } } @@ -743,7 +742,7 @@ class RoomDetailFragment @Inject constructor( val roomSummary = state.asyncRoomSummary.invoke() ?: return@withState val isVideoCall = item.itemId == R.id.video_call when (roomSummary.joinedMembersCount) { - 1 -> { + 1 -> { val pendingInvite = roomSummary.invitedMembersCount ?: 0 > 0 if (pendingInvite) { // wait for other to join @@ -753,7 +752,7 @@ class RoomDetailFragment @Inject constructor( showDialogWithMessage(getString(R.string.cannot_call_yourself)) } } - 2 -> { + 2 -> { val activeCall = sharedCallActionViewModel.activeCall.value if (activeCall != null) { // resume existing if same room, if not prompt to kill and then restart new call? @@ -934,9 +933,9 @@ class RoomDetailFragment @Inject constructor( when (roomDetailPendingAction) { is RoomDetailPendingAction.JumpToReadReceipt -> roomDetailViewModel.handle(RoomDetailAction.JumpToReadReceipt(roomDetailPendingAction.userId)) - is RoomDetailPendingAction.MentionUser -> + is RoomDetailPendingAction.MentionUser -> insertUserDisplayNameInTextEditor(roomDetailPendingAction.userId) - is RoomDetailPendingAction.OpenOrCreateDm -> + is RoomDetailPendingAction.OpenOrCreateDm -> roomDetailViewModel.handle(RoomDetailAction.OpenOrCreateDm(roomDetailPendingAction.userId)) }.exhaustive } @@ -1079,9 +1078,9 @@ class RoomDetailFragment @Inject constructor( withState(roomDetailViewModel) { val showJumpToUnreadBanner = when (it.unreadState) { UnreadState.Unknown, - UnreadState.HasNoUnread -> false + UnreadState.HasNoUnread -> false is UnreadState.ReadMarkerNotLoaded -> true - is UnreadState.HasUnread -> { + is UnreadState.HasUnread -> { if (it.canShowJumpToReadMarker) { val lastVisibleItem = layoutManager.findLastVisibleItemPosition() val positionOfReadMarker = timelineEventController.getPositionOfReadMarker() @@ -1290,7 +1289,7 @@ class RoomDetailFragment @Inject constructor( navigator.openRoom(vectorBaseActivity, async()) vectorBaseActivity.finish() } - is Fail -> { + is Fail -> { vectorBaseActivity.hideWaitingView() vectorBaseActivity.toast(errorFormatter.toHumanReadable(async.error)) } @@ -1299,19 +1298,19 @@ class RoomDetailFragment @Inject constructor( private fun renderSendMessageResult(sendMessageResult: RoomDetailViewEvents.SendMessageResult) { when (sendMessageResult) { - is RoomDetailViewEvents.SlashCommandHandled -> { + is RoomDetailViewEvents.SlashCommandHandled -> { sendMessageResult.messageRes?.let { showSnackWithMessage(getString(it)) } } - is RoomDetailViewEvents.SlashCommandError -> { + is RoomDetailViewEvents.SlashCommandError -> { displayCommandError(getString(R.string.command_problem_with_parameters, sendMessageResult.command.command)) } - is RoomDetailViewEvents.SlashCommandUnknown -> { + is RoomDetailViewEvents.SlashCommandUnknown -> { displayCommandError(getString(R.string.unrecognized_command, sendMessageResult.command)) } - is RoomDetailViewEvents.SlashCommandResultOk -> { + is RoomDetailViewEvents.SlashCommandResultOk -> { updateComposerText("") } - is RoomDetailViewEvents.SlashCommandResultError -> { + is RoomDetailViewEvents.SlashCommandResultError -> { displayCommandError(errorFormatter.toHumanReadable(sendMessageResult.throwable)) } is RoomDetailViewEvents.SlashCommandNotImplemented -> { @@ -1333,7 +1332,7 @@ class RoomDetailFragment @Inject constructor( private fun displayE2eError(withHeldCode: WithHeldCode?) { val msgId = when (withHeldCode) { WithHeldCode.BLACKLISTED -> R.string.crypto_error_withheld_blacklisted - WithHeldCode.UNVERIFIED -> R.string.crypto_error_withheld_unverified + WithHeldCode.UNVERIFIED -> R.string.crypto_error_withheld_unverified WithHeldCode.UNAUTHORISED, WithHeldCode.UNAVAILABLE -> R.string.crypto_error_withheld_generic else -> R.string.notice_crypto_unable_to_decrypt_friendly_desc @@ -1385,7 +1384,7 @@ class RoomDetailFragment @Inject constructor( private fun displayRoomDetailActionSuccess(result: RoomDetailViewEvents.ActionSuccess) { when (val data = result.action) { - is RoomDetailAction.ReportContent -> { + is RoomDetailAction.ReportContent -> { when { data.spam -> { AlertDialog.Builder(requireActivity()) @@ -1422,7 +1421,7 @@ class RoomDetailFragment @Inject constructor( } } } - is RoomDetailAction.RequestVerification -> { + is RoomDetailAction.RequestVerification -> { Timber.v("## SAS RequestVerification action") VerificationBottomSheet.withArgs( roomDetailArgs.roomId, @@ -1437,7 +1436,7 @@ class RoomDetailFragment @Inject constructor( data.transactionId ).show(parentFragmentManager, "REQ") } - is RoomDetailAction.ResumeVerification -> { + is RoomDetailAction.ResumeVerification -> { val otherUserId = data.otherUserId ?: return VerificationBottomSheet().apply { arguments = Bundle().apply { @@ -1581,11 +1580,11 @@ class RoomDetailFragment @Inject constructor( is MessageVerificationRequestContent -> { roomDetailViewModel.handle(RoomDetailAction.ResumeVerification(informationData.eventId, null)) } - is MessageWithAttachmentContent -> { + is MessageWithAttachmentContent -> { val action = RoomDetailAction.DownloadOrOpen(informationData.eventId, informationData.senderId, messageContent) roomDetailViewModel.handle(action) } - is EncryptedEventContent -> { + is EncryptedEventContent -> { roomDetailViewModel.handle(RoomDetailAction.TapOnFailedToDecrypt(informationData.eventId)) } } @@ -1726,75 +1725,75 @@ class RoomDetailFragment @Inject constructor( private fun handleActions(action: EventSharedAction) { when (action) { - is EventSharedAction.OpenUserProfile -> { + is EventSharedAction.OpenUserProfile -> { openRoomMemberProfile(action.userId) } - is EventSharedAction.AddReaction -> { + is EventSharedAction.AddReaction -> { emojiActivityResultLauncher.launch(EmojiReactionPickerActivity.intent(requireContext(), action.eventId)) } - is EventSharedAction.ViewReactions -> { + is EventSharedAction.ViewReactions -> { ViewReactionsBottomSheet.newInstance(roomDetailArgs.roomId, action.messageInformationData) .show(requireActivity().supportFragmentManager, "DISPLAY_REACTIONS") } - is EventSharedAction.Copy -> { + is EventSharedAction.Copy -> { // I need info about the current selected message :/ copyToClipboard(requireContext(), action.content, false) showSnackWithMessage(getString(R.string.copied_to_clipboard), Snackbar.LENGTH_SHORT) } - is EventSharedAction.Redact -> { + is EventSharedAction.Redact -> { promptConfirmationToRedactEvent(action) } - is EventSharedAction.Share -> { + is EventSharedAction.Share -> { onShareActionClicked(action) } - is EventSharedAction.Save -> { + is EventSharedAction.Save -> { onSaveActionClicked(action) } - is EventSharedAction.ViewEditHistory -> { + is EventSharedAction.ViewEditHistory -> { onEditedDecorationClicked(action.messageInformationData) } - is EventSharedAction.ViewSource -> { + is EventSharedAction.ViewSource -> { JSonViewerDialog.newInstance( action.content, -1, createJSonViewerStyleProvider(colorProvider) ).show(childFragmentManager, "JSON_VIEWER") } - is EventSharedAction.ViewDecryptedSource -> { + is EventSharedAction.ViewDecryptedSource -> { JSonViewerDialog.newInstance( action.content, -1, createJSonViewerStyleProvider(colorProvider) ).show(childFragmentManager, "JSON_VIEWER") } - is EventSharedAction.QuickReact -> { + is EventSharedAction.QuickReact -> { // eventId,ClickedOn,Add roomDetailViewModel.handle(RoomDetailAction.UpdateQuickReactAction(action.eventId, action.clickedOn, action.add)) } - is EventSharedAction.Edit -> { + is EventSharedAction.Edit -> { roomDetailViewModel.handle(RoomDetailAction.EnterEditMode(action.eventId, composerLayout.text.toString())) } - is EventSharedAction.Quote -> { + is EventSharedAction.Quote -> { roomDetailViewModel.handle(RoomDetailAction.EnterQuoteMode(action.eventId, composerLayout.text.toString())) } - is EventSharedAction.Reply -> { + is EventSharedAction.Reply -> { roomDetailViewModel.handle(RoomDetailAction.EnterReplyMode(action.eventId, composerLayout.text.toString())) } - is EventSharedAction.CopyPermalink -> { + is EventSharedAction.CopyPermalink -> { val permalink = session.permalinkService().createPermalink(roomDetailArgs.roomId, action.eventId) copyToClipboard(requireContext(), permalink, false) showSnackWithMessage(getString(R.string.copied_to_clipboard), Snackbar.LENGTH_SHORT) } - is EventSharedAction.Resend -> { + is EventSharedAction.Resend -> { roomDetailViewModel.handle(RoomDetailAction.ResendMessage(action.eventId)) } - is EventSharedAction.Remove -> { + is EventSharedAction.Remove -> { roomDetailViewModel.handle(RoomDetailAction.RemoveFailedEcho(action.eventId)) } - is EventSharedAction.Cancel -> { + is EventSharedAction.Cancel -> { roomDetailViewModel.handle(RoomDetailAction.CancelSend(action.eventId)) } - is EventSharedAction.ReportContentSpam -> { + is EventSharedAction.ReportContentSpam -> { roomDetailViewModel.handle(RoomDetailAction.ReportContent( action.eventId, action.senderId, "This message is spam", spam = true)) } @@ -1802,22 +1801,22 @@ class RoomDetailFragment @Inject constructor( roomDetailViewModel.handle(RoomDetailAction.ReportContent( action.eventId, action.senderId, "This message is inappropriate", inappropriate = true)) } - is EventSharedAction.ReportContentCustom -> { + is EventSharedAction.ReportContentCustom -> { promptReasonToReportContent(action) } - is EventSharedAction.IgnoreUser -> { + is EventSharedAction.IgnoreUser -> { action.senderId?.let { askConfirmationToIgnoreUser(it) } } - is EventSharedAction.OnUrlClicked -> { + is EventSharedAction.OnUrlClicked -> { onUrlClicked(action.url, action.title) } - is EventSharedAction.OnUrlLongClicked -> { + is EventSharedAction.OnUrlLongClicked -> { onUrlLongClicked(action.url) } - is EventSharedAction.ReRequestKey -> { + is EventSharedAction.ReRequestKey -> { roomDetailViewModel.handle(RoomDetailAction.ReRequestKeys(action.eventId)) } - is EventSharedAction.UseKeyBackup -> { + is EventSharedAction.UseKeyBackup -> { context?.let { startActivity(KeysBackupRestoreActivity.intent(it)) } @@ -1957,10 +1956,10 @@ class RoomDetailFragment @Inject constructor( private fun launchAttachmentProcess(type: AttachmentTypeSelectorView.Type) { when (type) { - AttachmentTypeSelectorView.Type.CAMERA -> attachmentsHelper.openCamera(requireContext(), attachmentPhotoActivityResultLauncher) - AttachmentTypeSelectorView.Type.FILE -> attachmentsHelper.selectFile(attachmentFileActivityResultLauncher) + AttachmentTypeSelectorView.Type.CAMERA -> attachmentsHelper.openCamera(requireContext(), attachmentPhotoActivityResultLauncher) + AttachmentTypeSelectorView.Type.FILE -> attachmentsHelper.selectFile(attachmentFileActivityResultLauncher) AttachmentTypeSelectorView.Type.GALLERY -> attachmentsHelper.selectGallery(attachmentImageActivityResultLauncher) - AttachmentTypeSelectorView.Type.AUDIO -> attachmentsHelper.selectAudio(attachmentAudioActivityResultLauncher) + AttachmentTypeSelectorView.Type.AUDIO -> attachmentsHelper.selectAudio(attachmentAudioActivityResultLauncher) AttachmentTypeSelectorView.Type.CONTACT -> attachmentsHelper.selectContact(attachmentContactActivityResultLauncher) AttachmentTypeSelectorView.Type.STICKER -> roomDetailViewModel.handle(RoomDetailAction.SelectStickerAttachment) }.exhaustive diff --git a/vector/src/main/java/im/vector/app/features/notifications/NotificationUtils.kt b/vector/src/main/java/im/vector/app/features/notifications/NotificationUtils.kt index fbf0ed9085..74a93fceda 100755 --- a/vector/src/main/java/im/vector/app/features/notifications/NotificationUtils.kt +++ b/vector/src/main/java/im/vector/app/features/notifications/NotificationUtils.kt @@ -30,6 +30,10 @@ import android.graphics.Bitmap import android.graphics.Canvas import android.net.Uri import android.os.Build +import android.text.Spannable +import android.text.SpannableString +import android.text.style.ForegroundColorSpan +import androidx.annotation.ColorRes import androidx.annotation.DrawableRes import androidx.annotation.StringRes import androidx.core.app.NotificationCompat @@ -52,6 +56,7 @@ import im.vector.app.features.home.room.detail.RoomDetailActivity import im.vector.app.features.home.room.detail.RoomDetailArgs import im.vector.app.features.settings.VectorPreferences import im.vector.app.features.settings.troubleshoot.TestNotificationReceiver +import org.matrix.android.sdk.api.session.call.MxCall import timber.log.Timber import javax.inject.Inject import javax.inject.Singleton @@ -269,19 +274,19 @@ class NotificationUtils @Inject constructor(private val context: Context, * @param roomName the room name in which the call is pending. * @param matrixId the matrix id * @param callId the call id. + * @param fromBg true if the app is in background when posting the notification * @return the call notification. */ @SuppressLint("NewApi") - fun buildIncomingCallNotification(isVideo: Boolean, - otherUserId: String, - roomId: String, - callId: String): Notification { + fun buildIncomingCallNotification(mxCall: MxCall, + title: String, + fromBg: Boolean): Notification { val accentColor = ContextCompat.getColor(context, R.color.notification_accent_color) - - val builder = NotificationCompat.Builder(context, CALL_NOTIFICATION_CHANNEL_ID) - .setContentTitle(ensureTitleNotEmpty(otherUserId)) + val notificationChannel = if (fromBg) CALL_NOTIFICATION_CHANNEL_ID else SILENT_NOTIFICATION_CHANNEL_ID + val builder = NotificationCompat.Builder(context, notificationChannel) + .setContentTitle(ensureTitleNotEmpty(title)) .apply { - if (isVideo) { + if (mxCall.isVideoCall) { setContentText(stringProvider.getString(R.string.incoming_video_call)) } else { setContentText(stringProvider.getString(R.string.incoming_voice_call)) @@ -300,15 +305,11 @@ class NotificationUtils @Inject constructor(private val context: Context, val contentIntent = VectorCallActivity.newIntent( context = context, - callId = callId, - roomId = roomId, - otherUserId = otherUserId, - isIncomingCall = true, - isVideoCall = isVideo, + mxCall = mxCall, mode = VectorCallActivity.INCOMING_RINGING ).apply { flags = Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP - data = Uri.parse("foobar://$callId") + data = Uri.parse("foobar://${mxCall.callId}") } val contentPendingIntent = PendingIntent.getActivity(context, System.currentTimeMillis().toInt(), contentIntent, 0) @@ -316,32 +317,28 @@ class NotificationUtils @Inject constructor(private val context: Context, .addNextIntentWithParentStack(HomeActivity.newIntent(context)) .addNextIntent(VectorCallActivity.newIntent( context = context, - callId = callId, - roomId = roomId, - otherUserId = otherUserId, - isIncomingCall = true, - isVideoCall = isVideo, + mxCall = mxCall, mode = VectorCallActivity.INCOMING_ACCEPT) ) .getPendingIntent(System.currentTimeMillis().toInt(), PendingIntent.FLAG_UPDATE_CURRENT) - val rejectCallPendingIntent = buildRejectCallPendingIntent(callId) + val rejectCallPendingIntent = buildRejectCallPendingIntent(mxCall.callId) builder.addAction( NotificationCompat.Action( - R.drawable.ic_call, - // IconCompat.createWithResource(applicationContext, R.drawable.ic_call) - // .setTint(ContextCompat.getColor(applicationContext, R.color.riotx_positive_accent)), - context.getString(R.string.call_notification_answer), - answerCallPendingIntent - ) + IconCompat.createWithResource(context, R.drawable.ic_call_hangup).setTint(ContextCompat.getColor(context, R.color.riotx_notice)), + getActionText(R.string.call_notification_reject, R.color.riotx_notice), + rejectCallPendingIntent) ) builder.addAction( NotificationCompat.Action( - IconCompat.createWithResource(context, R.drawable.ic_call_end).setTint(ContextCompat.getColor(context, R.color.riotx_notice)), - context.getString(R.string.call_notification_reject), - rejectCallPendingIntent) + R.drawable.ic_call_answer, + // IconCompat.createWithResource(applicationContext, R.drawable.ic_call) + // .setTint(ContextCompat.getColor(applicationContext, R.color.riotx_positive_accent)), + getActionText(R.string.call_notification_answer, R.color.riotx_positive_accent), + answerCallPendingIntent + ) ) builder.setFullScreenIntent(contentPendingIntent, true) @@ -349,14 +346,11 @@ class NotificationUtils @Inject constructor(private val context: Context, return builder.build() } - fun buildOutgoingRingingCallNotification(isVideo: Boolean, - otherUserId: String, - roomId: String, - callId: String): Notification { + fun buildOutgoingRingingCallNotification(mxCall: MxCall, + title: String): Notification { val accentColor = ContextCompat.getColor(context, R.color.notification_accent_color) - val builder = NotificationCompat.Builder(context, SILENT_NOTIFICATION_CHANNEL_ID) - .setContentTitle(ensureTitleNotEmpty(otherUserId)) + .setContentTitle(ensureTitleNotEmpty(title)) .apply { setContentText(stringProvider.getString(R.string.call_ring)) } @@ -367,23 +361,19 @@ class NotificationUtils @Inject constructor(private val context: Context, val contentIntent = VectorCallActivity.newIntent( context = context, - callId = callId, - roomId = roomId, - otherUserId = otherUserId, - isIncomingCall = true, - isVideoCall = isVideo, + mxCall = mxCall, mode = null).apply { flags = Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP - data = Uri.parse("foobar://$callId") + data = Uri.parse("foobar://$mxCall.callId") } val contentPendingIntent = PendingIntent.getActivity(context, System.currentTimeMillis().toInt(), contentIntent, 0) - val rejectCallPendingIntent = buildRejectCallPendingIntent(callId) + val rejectCallPendingIntent = buildRejectCallPendingIntent(mxCall.callId) builder.addAction( NotificationCompat.Action( - IconCompat.createWithResource(context, R.drawable.ic_call_end).setTint(ContextCompat.getColor(context, R.color.riotx_notice)), - context.getString(R.string.call_notification_hangup), + IconCompat.createWithResource(context, R.drawable.ic_call_hangup).setTint(ContextCompat.getColor(context, R.color.riotx_notice)), + getActionText(R.string.call_notification_hangup, R.color.riotx_notice), rejectCallPendingIntent) ) builder.setContentIntent(contentPendingIntent) @@ -402,16 +392,13 @@ class NotificationUtils @Inject constructor(private val context: Context, * @return the call notification. */ @SuppressLint("NewApi") - fun buildPendingCallNotification(isVideo: Boolean, - roomName: String, - roomId: String, - matrixId: String, - callId: String, + fun buildPendingCallNotification(mxCall: MxCall, + title: String, fromBg: Boolean = false): Notification { val builder = NotificationCompat.Builder(context, if (fromBg) CALL_NOTIFICATION_CHANNEL_ID else SILENT_NOTIFICATION_CHANNEL_ID) - .setContentTitle(ensureTitleNotEmpty(roomName)) + .setContentTitle(ensureTitleNotEmpty(title)) .apply { - if (isVideo) { + if (mxCall.isVideoCall) { setContentText(stringProvider.getString(R.string.video_call_in_progress)) } else { setContentText(stringProvider.getString(R.string.call_in_progress)) @@ -425,19 +412,18 @@ class NotificationUtils @Inject constructor(private val context: Context, builder.setOngoing(true) } - val rejectCallPendingIntent = buildRejectCallPendingIntent(callId) + val rejectCallPendingIntent = buildRejectCallPendingIntent(mxCall.callId) builder.addAction( NotificationCompat.Action( - IconCompat.createWithResource(context, R.drawable.ic_call_end).setTint(ContextCompat.getColor(context, R.color.riotx_notice)), - context.getString(R.string.call_notification_hangup), + IconCompat.createWithResource(context, R.drawable.ic_call_hangup).setTint(ContextCompat.getColor(context, R.color.riotx_notice)), + getActionText(R.string.call_notification_hangup, R.color.riotx_notice), rejectCallPendingIntent) ) val contentPendingIntent = TaskStackBuilder.create(context) .addNextIntentWithParentStack(HomeActivity.newIntent(context)) - // TODO other userId - .addNextIntent(VectorCallActivity.newIntent(context, callId, roomId, "otherUserId", true, isVideo, null)) + .addNextIntent(VectorCallActivity.newIntent(context, mxCall, null)) .getPendingIntent(System.currentTimeMillis().toInt(), PendingIntent.FLAG_UPDATE_CURRENT) builder.setContentIntent(contentPendingIntent) @@ -462,7 +448,7 @@ class NotificationUtils @Inject constructor(private val context: Context, * Build a temporary (because service will be stopped just after) notification for the CallService, when a call is ended */ fun buildCallEndedNotification(): Notification { - return NotificationCompat.Builder(context, CALL_NOTIFICATION_CHANNEL_ID) + return NotificationCompat.Builder(context, SILENT_NOTIFICATION_CHANNEL_ID) .setContentTitle(stringProvider.getString(R.string.call_ended)) .setSmallIcon(R.drawable.ic_material_call_end_grey) .setCategory(NotificationCompat.CATEGORY_CALL) @@ -883,6 +869,13 @@ class NotificationUtils @Inject constructor(private val context: Context, || setting == NotificationManager.INTERRUPTION_FILTER_ALARMS } + private fun getActionText(@StringRes stringRes: Int, @ColorRes colorRes: Int): Spannable { + return SpannableString(context.getText(stringRes)).apply { + val foregroundColorSpan = ForegroundColorSpan(ContextCompat.getColor(context, colorRes)) + setSpan(foregroundColorSpan, 0, length, 0) + } + } + private fun ensureTitleNotEmpty(title: String?): CharSequence { if (title.isNullOrBlank()) { return stringProvider.getString(R.string.app_name) diff --git a/vector/src/main/java/im/vector/app/features/popup/IncomingCallAlert.kt b/vector/src/main/java/im/vector/app/features/popup/IncomingCallAlert.kt new file mode 100644 index 0000000000..64e729e54d --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/popup/IncomingCallAlert.kt @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2020 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.features.popup + +import android.app.Activity +import android.view.View +import android.widget.ImageView +import android.widget.TextView +import im.vector.app.R +import im.vector.app.features.home.AvatarRenderer +import org.matrix.android.sdk.api.util.MatrixItem + +class IncomingCallAlert(uid: String, + override val shouldBeDisplayedIn: ((Activity) -> Boolean) = { true } +) : DefaultVectorAlert(uid, "", "", 0, shouldBeDisplayedIn) { + + override val priority = PopupAlertManager.INCOMING_CALL_PRIORITY + override val layoutRes = R.layout.alerter_incoming_call_layout + override var colorAttribute: Int? = R.attr.riotx_alerter_background + override val dismissOnClick: Boolean = false + override val isLight: Boolean = true + + class ViewBinder(private val matrixItem: MatrixItem?, + private val avatarRenderer: AvatarRenderer, + private val isVideoCall: Boolean, + private val onAccept: () -> Unit, + private val onReject: () -> Unit) + : VectorAlert.ViewBinder { + + override fun bind(view: View) { + val callKind = if (isVideoCall) { + R.string.action_video_call + } else { + R.string.action_voice_call + } + view.findViewById(R.id.incomingCallKindView).setText(callKind) + view.findViewById(R.id.incomingCallNameView).text = matrixItem?.getBestName() + view.findViewById(R.id.incomingCallAvatar)?.let { imageView -> + matrixItem?.let { avatarRenderer.render(it, imageView) } + } + view.findViewById(R.id.incomingCallAcceptView).setOnClickListener { + onAccept() + } + view.findViewById(R.id.incomingCallRejectView).setOnClickListener { + onReject() + } + } + } +} diff --git a/vector/src/main/java/im/vector/app/features/popup/PopupAlertManager.kt b/vector/src/main/java/im/vector/app/features/popup/PopupAlertManager.kt index b2257b250a..3209fbdca7 100644 --- a/vector/src/main/java/im/vector/app/features/popup/PopupAlertManager.kt +++ b/vector/src/main/java/im/vector/app/features/popup/PopupAlertManager.kt @@ -21,14 +21,10 @@ import android.os.Build import android.os.Handler import android.os.Looper import android.view.View -import android.widget.ImageView import com.tapadoo.alerter.Alerter -import com.tapadoo.alerter.OnHideAlertListener -import dagger.Lazy import im.vector.app.R import im.vector.app.core.platform.VectorBaseActivity import im.vector.app.core.utils.isAnimationDisabled -import im.vector.app.features.home.AvatarRenderer import im.vector.app.features.pin.PinActivity import im.vector.app.features.themes.ThemeUtils import timber.log.Timber @@ -38,19 +34,25 @@ import javax.inject.Singleton /** * Responsible of displaying important popup alerts on top of the screen. - * Alerts are stacked and will be displayed sequentially + * Alerts are stacked and will be displayed sequentially but sorted by priority. + * So if a new alert is posted with a higher priority than the current one it will show it instead and the current one + * will be back in the queue in first position. */ @Singleton -class PopupAlertManager @Inject constructor(private val avatarRenderer: Lazy) { +class PopupAlertManager @Inject constructor() { + + companion object { + const val INCOMING_CALL_PRIORITY = Int.MAX_VALUE + } private var weakCurrentActivity: WeakReference? = null private var currentAlerter: VectorAlert? = null - private val alertFiFo = mutableListOf() + private val alertQueue = mutableListOf() fun postVectorAlert(alert: VectorAlert) { - synchronized(alertFiFo) { - alertFiFo.add(alert) + synchronized(alertQueue) { + alertQueue.add(alert) } weakCurrentActivity?.get()?.runOnUiThread { displayNextIfPossible() @@ -58,8 +60,8 @@ class PopupAlertManager @Inject constructor(private val avatarRenderer: Lazy currentAlerter?.priority ?: Int.MIN_VALUE) { + alertQueue.remove(next) + currentAlerter?.also { + alertQueue.add(0, it) + } + } else { + // otherwise, we don't do anything + return + } } currentAlerter = next next?.let { @@ -186,22 +196,19 @@ class PopupAlertManager @Inject constructor(private val avatarRenderer: Lazy - if (alert is VerificationVectorAlert) { - val tvCustomView = al.getLayoutContainer() - tvCustomView?.findViewById(R.id.ivUserAvatar)?.let { imageView -> - alert.matrixItem?.let { avatarRenderer.get().render(it, imageView) } - } + al.getLayoutContainer()?.also { + alert.viewBinder?.bind(it) } } .apply { @@ -213,7 +220,7 @@ class PopupAlertManager @Inject constructor(private val avatarRenderer: Lazy - addButton(action.title, R.style.AlerterButton, View.OnClickListener { + addButton(action.title, R.style.AlerterButton) { if (action.autoClose) { currentIsDismissed() Alerter.hide() @@ -223,21 +230,23 @@ class PopupAlertManager @Inject constructor(private val avatarRenderer: Lazy + setOnClickListener { _ -> alert.contentAction?.let { - currentIsDismissed() - Alerter.hide() + if (alert.dismissOnClick) { + currentIsDismissed() + Alerter.hide() + } try { it.run() } catch (e: java.lang.Exception) { Timber.e("## failed to perform action") } } - }) + } } - .setOnHideListener(OnHideAlertListener { + .setOnHideListener { // called when dismissed on swipe try { alert.dismissedAction?.run() @@ -245,12 +254,14 @@ class PopupAlertManager @Inject constructor(private val avatarRenderer: Lazy