From d372335d570ef49a682b01befddd66dff345dad9 Mon Sep 17 00:00:00 2001 From: Andy Scherzinger Date: Wed, 26 May 2021 18:08:44 +0200 Subject: [PATCH] hardening chat controller by null-checking any UI access due to asynchronous UI accesses Signed-off-by: Andy Scherzinger --- .../talk/controllers/ChatController.kt | 162 +++++++++--------- 1 file changed, 81 insertions(+), 81 deletions(-) diff --git a/app/src/main/java/com/nextcloud/talk/controllers/ChatController.kt b/app/src/main/java/com/nextcloud/talk/controllers/ChatController.kt index b825a15b4..19d8b1444 100644 --- a/app/src/main/java/com/nextcloud/talk/controllers/ChatController.kt +++ b/app/src/main/java/com/nextcloud/talk/controllers/ChatController.kt @@ -159,7 +159,7 @@ class ChatController(args: Bundle) : MessagesListAdapter.Formatter, MessagesListAdapter.OnMessageViewLongClickListener, MessageHolders.ContentChecker { - private val binding: ControllerChatBinding by viewBinding(ControllerChatBinding::bind) + private val binding: ControllerChatBinding? by viewBinding(ControllerChatBinding::bind) @Inject @JvmField @@ -370,7 +370,7 @@ class ChatController(args: Bundle) : var adapterWasNull = false if (adapter == null) { - binding.progressBar.visibility = View.VISIBLE + binding?.progressBar?.visibility = View.VISIBLE adapterWasNull = true @@ -431,19 +431,19 @@ class ChatController(args: Bundle) : } ) } else { - binding.messagesListView.visibility = View.VISIBLE + binding?.messagesListView?.visibility = View.VISIBLE } - binding.messagesListView.setAdapter(adapter) + binding?.messagesListView?.setAdapter(adapter) adapter?.setLoadMoreListener(this) adapter?.setDateHeadersFormatter { format(it) } adapter?.setOnMessageViewLongClickListener { view, message -> onMessageViewLongClick(view, message) } - layoutManager = binding.messagesListView.layoutManager as LinearLayoutManager? + layoutManager = binding?.messagesListView?.layoutManager as LinearLayoutManager? - binding.popupBubbleView.setRecyclerView(binding.messagesListView) + binding?.popupBubbleView?.setRecyclerView(binding?.messagesListView) - binding.popupBubbleView.setPopupBubbleListener { context -> + binding?.popupBubbleView?.setPopupBubbleListener { context -> if (newMessagesCount != 0) { val scrollPosition: Int if (newMessagesCount - 1 < 0) { @@ -451,20 +451,20 @@ class ChatController(args: Bundle) : } else { scrollPosition = newMessagesCount - 1 } - Handler().postDelayed({ binding.messagesListView.smoothScrollToPosition(scrollPosition) }, 200) + Handler().postDelayed({ binding?.messagesListView?.smoothScrollToPosition(scrollPosition) }, 200) } } if (args.containsKey("showToggleChat") && args.getBoolean("showToggleChat")) { - binding.callControlToggleChat.visibility = View.VISIBLE + binding?.callControlToggleChat?.visibility = View.VISIBLE wasDetached = true } - binding.callControlToggleChat.setOnClickListener { + binding?.callControlToggleChat?.setOnClickListener { (activity as MagicCallActivity).showCall() } - binding.messagesListView.addOnScrollListener(object : RecyclerView.OnScrollListener() { + binding?.messagesListView?.addOnScrollListener(object : RecyclerView.OnScrollListener() { override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) { super.onScrollStateChanged(recyclerView, newState) @@ -473,8 +473,8 @@ class ChatController(args: Bundle) : if (layoutManager!!.findFirstCompletelyVisibleItemPosition() < newMessagesCount) { newMessagesCount = 0 - if (binding.popupBubbleView.isShown) { - binding.popupBubbleView.hide() + if (binding?.popupBubbleView?.isShown == true) { + binding?.popupBubbleView?.hide() } } } @@ -486,26 +486,26 @@ class ChatController(args: Bundle) : val lengthFilter = CapabilitiesUtil.getMessageMaxLength(conversationUser) ?: 1000 filters[0] = InputFilter.LengthFilter(lengthFilter) - binding.messageInputView.inputEditText?.filters = filters + binding?.messageInputView?.inputEditText?.filters = filters - binding.messageInputView.inputEditText?.addTextChangedListener(object : TextWatcher { + binding?.messageInputView?.inputEditText?.addTextChangedListener(object : TextWatcher { override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) { } override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) { if (s.length >= lengthFilter) { - binding.messageInputView.inputEditText?.error = String.format( + binding?.messageInputView?.inputEditText?.error = String.format( Objects.requireNonNull(resources).getString(R.string.nc_limit_hit), Integer.toString(lengthFilter) ) } else { - binding.messageInputView.inputEditText?.error = null + binding?.messageInputView?.inputEditText?.error = null } - val editable = binding.messageInputView.inputEditText?.editableText - if (editable != null && binding.messageInputView.inputEditText != null) { + val editable = binding?.messageInputView?.inputEditText?.editableText + if (editable != null && binding?.messageInputView?.inputEditText != null) { val mentionSpans = editable.getSpans( - 0, binding.messageInputView.inputEditText!!.length(), + 0, binding?.messageInputView?.inputEditText!!.length(), Spans.MentionChipSpan::class.java ) var mentionSpan: Spans.MentionChipSpan @@ -528,14 +528,14 @@ class ChatController(args: Bundle) : } }) - binding.messageInput.setText(sharedText) - binding.messageInputView.setAttachmentsListener { + binding?.messageInputView?.inputEditText?.setText(sharedText) + binding?.messageInputView?.setAttachmentsListener { activity?.let { AttachmentDialog(it, this).show() } } - binding.messageInputView.button?.setOnClickListener { v -> submitMessage() } + binding?.messageInputView?.button?.setOnClickListener { v -> submitMessage() } - binding.messageInputView.button?.contentDescription = resources?.getString( + binding?.messageInputView?.button?.contentDescription = resources?.getString( R.string .nc_description_send_message_button ) @@ -566,7 +566,7 @@ class ChatController(args: Bundle) : conversationVoiceCallMenuItem?.icon?.alpha = 99 conversationVideoMenuItem?.icon?.alpha = 99 - binding.messageInputView.visibility = View.GONE + binding?.messageInputView?.visibility = View.GONE } else { if (conversationVoiceCallMenuItem != null) { conversationVoiceCallMenuItem?.icon?.alpha = 255 @@ -578,9 +578,9 @@ class ChatController(args: Bundle) : if (currentConversation != null && currentConversation!!.shouldShowLobby(conversationUser) ) { - binding.messageInputView.visibility = View.GONE + binding?.messageInputView?.visibility = View.GONE } else { - binding.messageInputView.visibility = View.VISIBLE + binding?.messageInputView?.visibility = View.VISIBLE } } } @@ -594,15 +594,15 @@ class ChatController(args: Bundle) : } if (currentConversation?.shouldShowLobby(conversationUser) ?: false) { - binding.lobby.lobbyView.visibility = View.VISIBLE - binding.messagesListView.visibility = View.GONE - binding.messageInputView.visibility = View.GONE - binding.progressBar?.visibility = View.GONE + binding?.lobby?.lobbyView?.visibility = View.VISIBLE + binding?.messagesListView?.visibility = View.GONE + binding?.messageInputView?.visibility = View.GONE + binding?.progressBar?.visibility = View.GONE if (currentConversation?.lobbyTimer != null && currentConversation?.lobbyTimer != 0L ) { - binding.lobby.lobbyTextView.text = String.format( + binding?.lobby?.lobbyTextView?.text = String.format( resources!!.getString(R.string.nc_lobby_waiting_with_date), DateUtils.getLocalDateStringFromTimestampForLobby( currentConversation?.lobbyTimer @@ -610,12 +610,12 @@ class ChatController(args: Bundle) : ) ) } else { - binding.lobby.lobbyTextView.setText(R.string.nc_lobby_waiting) + binding?.lobby?.lobbyTextView?.setText(R.string.nc_lobby_waiting) } } else { - binding.lobby.lobbyView.visibility = View.GONE - binding.messagesListView.visibility = View.VISIBLE - binding.messageInputView.inputEditText?.visibility = View.VISIBLE + binding?.lobby?.lobbyView?.visibility = View.GONE + binding?.messagesListView?.visibility = View.VISIBLE + binding?.messageInputView?.inputEditText?.visibility = View.VISIBLE if (isFirstMessagesProcessing && pastPreconditionFailed) { pastPreconditionFailed = false pullChatMessages(0) @@ -625,9 +625,9 @@ class ChatController(args: Bundle) : } } } else { - binding.lobby.lobbyView.visibility = View.GONE - binding.messagesListView.visibility = View.VISIBLE - binding.messageInputView.inputEditText?.visibility = View.VISIBLE + binding?.lobby?.lobbyView?.visibility = View.GONE + binding?.messagesListView?.visibility = View.VISIBLE + binding?.messageInputView?.inputEditText?.visibility = View.VISIBLE } } @@ -775,11 +775,11 @@ class ChatController(args: Bundle) : val callback = MentionAutocompleteCallback( activity, conversationUser, - binding.messageInputView.inputEditText + binding?.messageInputView?.inputEditText ) - if (mentionAutocomplete == null && binding.messageInputView.inputEditText != null) { - mentionAutocomplete = Autocomplete.on(binding.messageInputView.inputEditText) + if (mentionAutocomplete == null && binding?.messageInputView?.inputEditText != null) { + mentionAutocomplete = Autocomplete.on(binding?.messageInputView?.inputEditText) .with(elevation) .with(backgroundDrawable) .with(MagicCharPolicy('@')) @@ -808,9 +808,9 @@ class ChatController(args: Bundle) : isLinkPreviewAllowed = appPreferences?.areLinkPreviewsAllowed ?: false - val smileyButton = binding.messageInputView.findViewById(R.id.smileyButton) + val smileyButton = binding?.messageInputView?.findViewById(R.id.smileyButton) - emojiPopup = binding.messageInputView.inputEditText?.let { + emojiPopup = binding?.messageInputView?.inputEditText?.let { EmojiPopup.Builder.fromRootView(view).setOnEmojiPopupShownListener { if (resources != null) { smileyButton?.setColorFilter( @@ -825,15 +825,15 @@ class ChatController(args: Bundle) : ) }.setOnEmojiClickListener { emoji, imageView -> - binding.messageInputView.inputEditText?.editableText?.append(" ") + binding?.messageInputView?.inputEditText?.editableText?.append(" ") }.build(it) } - smileyButton.setOnClickListener { + smileyButton?.setOnClickListener { emojiPopup?.toggle() } - binding.messageInputView.findViewById(R.id.cancelReplyButton).setOnClickListener { + binding?.messageInputView?.findViewById(R.id.cancelReplyButton)?.setOnClickListener { cancelReply() } @@ -853,9 +853,9 @@ class ChatController(args: Bundle) : } private fun cancelReply() { - binding.messageInputView.findViewById(R.id.quotedChatMessageView).visibility = View.GONE - binding.messageInputView.findViewById(R.id.attachmentButton)?.visibility = View.VISIBLE - binding.messageInputView.findViewById(R.id.attachmentButtonSpace)?.visibility = View.VISIBLE + binding?.messageInputView?.findViewById(R.id.quotedChatMessageView)?.visibility = View.GONE + binding?.messageInputView?.findViewById(R.id.attachmentButton)?.visibility = View.VISIBLE + binding?.messageInputView?.findViewById(R.id.attachmentButtonSpace)?.visibility = View.VISIBLE } private fun cancelNotificationsForCurrentConversation() { @@ -1053,8 +1053,8 @@ class ChatController(args: Bundle) : } private fun submitMessage() { - if (binding.messageInputView.inputEditText != null) { - val editable = binding.messageInputView.inputEditText!!.editableText + if (binding?.messageInputView?.inputEditText != null) { + val editable = binding?.messageInputView?.inputEditText!!.editableText val mentionSpans = editable.getSpans( 0, editable.length, Spans.MentionChipSpan::class.java @@ -1069,7 +1069,7 @@ class ChatController(args: Bundle) : editable.replace(editable.getSpanStart(mentionSpan), editable.getSpanEnd(mentionSpan), "@$mentionId") } - binding.messageInputView.inputEditText?.setText("") + binding?.messageInputView?.inputEditText?.setText("") val replyMessageId: Int? = view?.findViewById(R.id.quotedChatMessageView)?.tag as Int? sendMessage( editable, @@ -1100,11 +1100,11 @@ class ChatController(args: Bundle) : override fun onNext(genericOverall: GenericOverall) { myFirstMessage = message - if (binding.popupBubbleView.isShown) { - binding.popupBubbleView.hide() + if (binding?.popupBubbleView?.isShown == true) { + binding?.popupBubbleView?.hide() } - binding.messagesListView.smoothScrollToPosition(0) + binding?.messagesListView?.smoothScrollToPosition(0) } override fun onError(e: Throwable) { @@ -1113,11 +1113,11 @@ class ChatController(args: Bundle) : if (Integer.toString(code).startsWith("2")) { myFirstMessage = message - if (binding.popupBubbleView.isShown) { - binding.popupBubbleView.hide() + if (binding?.popupBubbleView?.isShown == true) { + binding?.popupBubbleView?.hide() } - binding.messagesListView.smoothScrollToPosition(0) + binding?.messagesListView?.smoothScrollToPosition(0) } } } @@ -1282,9 +1282,9 @@ class ChatController(args: Bundle) : cancelNotificationsForCurrentConversation() isFirstMessagesProcessing = false - binding.progressBar.visibility = View.GONE + binding?.progressBar?.visibility = View.GONE - binding.messagesListView.visibility = View.VISIBLE + binding?.messagesListView?.visibility = View.VISIBLE } var countGroupedMessages = 0 @@ -1349,10 +1349,10 @@ class ChatController(args: Bundle) : adapter?.itemCount == 0 if (!shouldAddNewMessagesNotice && !shouldScroll) { - if (!binding.popupBubbleView.isShown) { + if (!binding?.popupBubbleView?.isShown!!) { newMessagesCount = 1 - binding.popupBubbleView.show() - } else if (binding.popupBubbleView.isShown) { + binding?.popupBubbleView?.show() + } else if (binding?.popupBubbleView?.isShown == true) { newMessagesCount++ } } else { @@ -1375,7 +1375,7 @@ class ChatController(args: Bundle) : if (shouldAddNewMessagesNotice && adapter != null) { layoutManager?.scrollToPositionWithOffset( adapter!!.getMessagePositionByIdInReverse("-1"), - binding.messagesListView.height / 2 + binding!!.messagesListView.height / 2 ) } } @@ -1404,7 +1404,7 @@ class ChatController(args: Bundle) : cancelNotificationsForCurrentConversation() isFirstMessagesProcessing = false - binding.progressBar.visibility = View.GONE + binding?.progressBar?.visibility = View.GONE } historyRead = true @@ -1561,21 +1561,21 @@ class ChatController(args: Bundle) : R.id.action_reply_to_message -> { val chatMessage = message as ChatMessage? chatMessage?.let { - binding.messageInputView.findViewById(R.id.attachmentButton)?.visibility = View.GONE - binding.messageInputView.findViewById(R.id.attachmentButtonSpace)?.visibility = View.GONE - binding.messageInputView.findViewById(R.id.cancelReplyButton)?.visibility = + binding?.messageInputView?.findViewById(R.id.attachmentButton)?.visibility = View.GONE + binding?.messageInputView?.findViewById(R.id.attachmentButtonSpace)?.visibility = View.GONE + binding?.messageInputView?.findViewById(R.id.cancelReplyButton)?.visibility = View.VISIBLE - binding.messageInputView.findViewById(R.id.quotedMessage)?.maxLines = 2 - binding.messageInputView.findViewById(R.id.quotedMessage)?.ellipsize = + binding?.messageInputView?.findViewById(R.id.quotedMessage)?.maxLines = 2 + binding?.messageInputView?.findViewById(R.id.quotedMessage)?.ellipsize = TextUtils.TruncateAt.END - binding.messageInputView.findViewById(R.id.quotedMessage)?.text = it.text - binding.messageInputView.findViewById(R.id.quotedMessageAuthor)?.text = + binding?.messageInputView?.findViewById(R.id.quotedMessage)?.text = it.text + binding?.messageInputView?.findViewById(R.id.quotedMessageAuthor)?.text = it.actorDisplayName ?: context!!.getText(R.string.nc_nick_guest) conversationUser?.let { currentUser -> chatMessage.imageUrl?.let { previewImageUrl -> - binding.messageInputView.findViewById(R.id.quotedMessageImage)?.visibility = + binding?.messageInputView?.findViewById(R.id.quotedMessageImage)?.visibility = View.VISIBLE val px = TypedValue.applyDimension( @@ -1583,26 +1583,26 @@ class ChatController(args: Bundle) : 96f, resources?.displayMetrics ) - binding.messageInputView.findViewById(R.id.quotedMessageImage)?.maxHeight = + binding?.messageInputView?.findViewById(R.id.quotedMessageImage)?.maxHeight = px.toInt() val layoutParams = - binding.messageInputView.findViewById(R.id.quotedMessageImage)?.layoutParams as FlexboxLayout.LayoutParams + binding?.messageInputView?.findViewById(R.id.quotedMessageImage)?.layoutParams as FlexboxLayout.LayoutParams layoutParams.flexGrow = 0f - binding.messageInputView.findViewById(R.id.quotedMessageImage)?.layoutParams = + binding?.messageInputView?.findViewById(R.id.quotedMessageImage)?.layoutParams = layoutParams - binding.messageInputView.findViewById(R.id.quotedMessageImage) + binding?.messageInputView?.findViewById(R.id.quotedMessageImage) ?.load(previewImageUrl) { addHeader("Authorization", credentials!!) } } ?: run { - binding.messageInputView.findViewById(R.id.quotedMessageImage)?.visibility = + binding?.messageInputView?.findViewById(R.id.quotedMessageImage)?.visibility = View.GONE } } val quotedChatMessageView = binding - .messageInputView - .findViewById(R.id.quotedChatMessageView) + ?.messageInputView + ?.findViewById(R.id.quotedChatMessageView) quotedChatMessageView?.tag = message?.jsonMessageId quotedChatMessageView?.visibility = View.VISIBLE }