diff --git a/res/layout/conversation_item_footer.xml b/res/layout/conversation_item_footer.xml index 170f60666..4ac5a5791 100644 --- a/res/layout/conversation_item_footer.xml +++ b/res/layout/conversation_item_footer.xml @@ -15,7 +15,7 @@ android:src="@drawable/msg_encr_out" android:visibility="gone" android:layout_gravity="center_vertical|end" - android:contentDescription="@string/chat_input_placeholder" + android:importantForAccessibility="no" tools:visibility="visible"/> diff --git a/res/layout/conversation_item_sent.xml b/res/layout/conversation_item_sent.xml index 6937a1bbf..f5019c7c8 100644 --- a/res/layout/conversation_item_sent.xml +++ b/res/layout/conversation_item_sent.xml @@ -142,6 +142,7 @@ android:paddingTop="@dimen/message_bubble_top_padding" android:textColor="?conversation_item_outgoing_text_primary_color" android:textColorLink="?conversation_item_outgoing_text_primary_color" + android:importantForAccessibility="no" app:scaleEmojis="true" tools:text="Mango pickle lorem ipsum"/> diff --git a/src/org/thoughtcrime/securesms/BaseConversationItem.java b/src/org/thoughtcrime/securesms/BaseConversationItem.java index 22de4553b..695901b2f 100644 --- a/src/org/thoughtcrime/securesms/BaseConversationItem.java +++ b/src/org/thoughtcrime/securesms/BaseConversationItem.java @@ -3,6 +3,7 @@ package org.thoughtcrime.securesms; import android.content.Context; import android.util.AttributeSet; import android.view.View; +import android.view.accessibility.AccessibilityManager; import android.widget.LinearLayout; import android.widget.TextView; @@ -77,6 +78,8 @@ public abstract class BaseConversationItem extends LinearLayout return batchSelected.isEmpty() && (messageRecord.isFailed()); } + protected void onAccessibilityClick() {} + protected class PassthroughClickListener implements View.OnLongClickListener, View.OnClickListener { @Override @@ -103,6 +106,9 @@ public abstract class BaseConversationItem extends LinearLayout public void onClick(View v) { if (!shouldInterceptClicks(messageRecord) && parent != null) { + if (batchSelected.isEmpty() && ((AccessibilityManager) context.getSystemService(Context.ACCESSIBILITY_SERVICE)).isTouchExplorationEnabled()) { + BaseConversationItem.this.onAccessibilityClick(); + } parent.onClick(v); } else if (messageRecord.isFailed()) { View view = View.inflate(context, R.layout.message_details_view, null); diff --git a/src/org/thoughtcrime/securesms/ConversationItem.java b/src/org/thoughtcrime/securesms/ConversationItem.java index f340c8f06..e4dd5c6d4 100644 --- a/src/org/thoughtcrime/securesms/ConversationItem.java +++ b/src/org/thoughtcrime/securesms/ConversationItem.java @@ -22,6 +22,7 @@ import android.content.res.TypedArray; import android.graphics.Color; import android.graphics.PorterDuff; import android.graphics.Rect; +import android.os.Build; import android.text.SpannableString; import android.text.TextUtils; import android.util.AttributeSet; @@ -193,6 +194,7 @@ public class ConversationItem extends BaseConversationItem setReactions(messageRecord); setFooter(messageRecord, locale); setQuote(messageRecord); + setContentDescription(messageRecord); } @@ -305,6 +307,36 @@ public class ConversationItem extends BaseConversationItem } } + private void setContentDescription(DcMsg messageRecord) { + String desc = ""; + if (groupSenderHolder.getVisibility() == View.VISIBLE) { + desc = groupSender.getText() + "\n"; + } + if (audioViewStub.resolved() && audioViewStub.get().getVisibility() == View.VISIBLE) { + desc += audioViewStub.get().getDescription() + "\n"; + } else if (documentViewStub.resolved() && documentViewStub.get().getVisibility() == View.VISIBLE) { + desc += documentViewStub.get().getDescription() + "\n"; + } else if (webxdcViewStub.resolved() && webxdcViewStub.get().getVisibility() == View.VISIBLE) { + desc += webxdcViewStub.get().getDescription() + "\n"; + } else if (mediaThumbnailStub.resolved() && mediaThumbnailStub.get().getVisibility() == View.VISIBLE) { + desc += mediaThumbnailStub.get().getDescription() + "\n"; + } else if (stickerStub.resolved() && stickerStub.get().getVisibility() == View.VISIBLE) { + desc += context.getString(R.string.sticker) + "\n"; + } + + if (bodyText.getVisibility() == View.VISIBLE) { + desc += bodyText.getText() + "\n"; + } + + if (footer.getVisibility() == View.VISIBLE) { + desc += footer.getDescription() + "\n"; + } else if (stickerFooter.getVisibility() == View.VISIBLE) { + desc += stickerFooter.getDescription() + "\n"; + } + + this.setContentDescription(desc); + } + private boolean hasAudio(DcMsg messageRecord) { int type = messageRecord.getType(); return type==DcMsg.DC_MSG_AUDIO || type==DcMsg.DC_MSG_VOICE; @@ -447,6 +479,9 @@ public class ConversationItem extends BaseConversationItem audioViewStub.get().setAudio(new AudioSlide(context, messageRecord), duration); audioViewStub.get().setOnLongClickListener(passthroughClickListener); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { + audioViewStub.get().setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS); + } ViewUtil.updateLayoutParams(bodyText, ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); ViewUtil.updateLayoutParams(groupSenderHolder, ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); @@ -463,6 +498,9 @@ public class ConversationItem extends BaseConversationItem documentViewStub.get().setDocument(new DocumentSlide(context, messageRecord)); documentViewStub.get().setDocumentClickListener(new ThumbnailClickListener()); documentViewStub.get().setOnLongClickListener(passthroughClickListener); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { + documentViewStub.get().setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS); + } ViewUtil.updateLayoutParams(bodyText, ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); ViewUtil.updateLayoutParams(groupSenderHolder, ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); @@ -478,6 +516,9 @@ public class ConversationItem extends BaseConversationItem webxdcViewStub.get().setWebxdc(messageRecord, context.getString(R.string.webxdc_app)); webxdcViewStub.get().setWebxdcClickListener(new ThumbnailClickListener()); webxdcViewStub.get().setOnLongClickListener(passthroughClickListener); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { + webxdcViewStub.get().setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS); + } ViewUtil.updateLayoutParams(bodyText, ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); ViewUtil.updateLayoutParams(groupSenderHolder, ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); @@ -512,6 +553,9 @@ public class ConversationItem extends BaseConversationItem mediaThumbnailStub.get().setOnLongClickListener(passthroughClickListener); mediaThumbnailStub.get().setOnClickListener(passthroughClickListener); mediaThumbnailStub.get().showShade(TextUtils.isEmpty(messageRecord.getText())); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { + mediaThumbnailStub.get().setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS); + } setThumbnailOutlineCorners(messageRecord, showSender); @@ -531,9 +575,11 @@ public class ConversationItem extends BaseConversationItem stickerStub.get().setSlide(glideRequests, new StickerSlide(context, messageRecord)); stickerStub.get().setThumbnailClickListener(new StickerClickListener()); - stickerStub.get().setOnLongClickListener(passthroughClickListener); stickerStub.get().setOnClickListener(passthroughClickListener); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { + stickerStub.get().setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS); + } ViewUtil.updateLayoutParams(bodyText, ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); ViewUtil.updateLayoutParams(groupSenderHolder, ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); @@ -792,6 +838,14 @@ public class ConversationItem extends BaseConversationItem return availableWidth; } + @Override + public void onAccessibilityClick() { + if (mediaThumbnailStub.resolved()) mediaThumbnailStub.get().performClick(); + else if (audioViewStub.resolved()) audioViewStub.get().performClick(); + else if (documentViewStub.resolved()) documentViewStub.get().performClick(); + else if (webxdcViewStub.resolved()) webxdcViewStub.get().performClick(); + } + /// Event handlers private class ThumbnailClickListener implements SlideClickListener { diff --git a/src/org/thoughtcrime/securesms/components/AudioView.java b/src/org/thoughtcrime/securesms/components/AudioView.java index 04474eca5..9383f2ef3 100644 --- a/src/org/thoughtcrime/securesms/components/AudioView.java +++ b/src/org/thoughtcrime/securesms/components/AudioView.java @@ -93,6 +93,30 @@ public class AudioView extends FrameLayout implements AudioSlidePlayer.Listener } } + @Override + public boolean performClick() { + if (this.playButton.getVisibility() == View.VISIBLE) { + playButton.performClick(); + } else { + pauseButton.performClick(); + } + return super.performClick(); + } + + public String getDescription() { + String desc; + if (this.title.getVisibility() == View.VISIBLE) { + desc = getContext().getString(R.string.audio); + } else { + desc = getContext().getString(R.string.voice_message); + } + desc += "\n" + this.timestamp.getText(); + if (title.getVisibility() == View.VISIBLE) { + desc += "\n" + this.title.getText(); + } + return desc; + } + public void setDuration(int duration) { if (getProgress()==0) this.timestamp.setText(DateUtils.getFormatedDuration(duration)); diff --git a/src/org/thoughtcrime/securesms/components/ConversationItemFooter.java b/src/org/thoughtcrime/securesms/components/ConversationItemFooter.java index 69ccc01cd..2c8761f94 100644 --- a/src/org/thoughtcrime/securesms/components/ConversationItemFooter.java +++ b/src/org/thoughtcrime/securesms/components/ConversationItemFooter.java @@ -100,4 +100,13 @@ public class ConversationItemFooter extends LinearLayout { deliveryStatusView.setTint(textColor); // Reset the color to the standard color (because the footer is re-used in a RecyclerView) } } + + public String getDescription() { + String desc = dateView.getText().toString(); + String deliveryDesc = deliveryStatusView.getDescription(); + if (!"".equals(deliveryDesc)) { + desc += "\n" + deliveryDesc; + } + return desc; + } } diff --git a/src/org/thoughtcrime/securesms/components/ConversationItemThumbnail.java b/src/org/thoughtcrime/securesms/components/ConversationItemThumbnail.java index 8837ab6f6..1f8fd8337 100644 --- a/src/org/thoughtcrime/securesms/components/ConversationItemThumbnail.java +++ b/src/org/thoughtcrime/securesms/components/ConversationItemThumbnail.java @@ -86,6 +86,10 @@ public class ConversationItemThumbnail extends FrameLayout { setTouchDelegate(thumbnail.getTouchDelegate()); } + public String getDescription() { + return thumbnail.getDescription() + "\n" + footer.getDescription(); + } + @Override protected void onMeasure(int originalWidthMeasureSpec, int originalHeightMeasureSpec) { int width = MeasureSpec.getSize(originalWidthMeasureSpec); @@ -185,6 +189,11 @@ public class ConversationItemThumbnail extends FrameLayout { thumbnail.setThumbnailClickListener(listener); } + @Override + public boolean performClick() { + return thumbnail.performClick(); + } + @UiThread public void setImageResource(@NonNull GlideRequests glideRequests, @NonNull Slide slide, int naturalWidth, int naturalHeight) diff --git a/src/org/thoughtcrime/securesms/components/DeliveryStatusView.java b/src/org/thoughtcrime/securesms/components/DeliveryStatusView.java index 040631f57..b4db7fb68 100644 --- a/src/org/thoughtcrime/securesms/components/DeliveryStatusView.java +++ b/src/org/thoughtcrime/securesms/components/DeliveryStatusView.java @@ -123,4 +123,11 @@ public class DeliveryStatusView { public void resetTint() { deliveryIndicator.setColorFilter(null); } + + public String getDescription() { + if (deliveryIndicator.getVisibility() == View.VISIBLE) { + return deliveryIndicator.getContentDescription().toString(); + } + return ""; + } } diff --git a/src/org/thoughtcrime/securesms/components/DocumentView.java b/src/org/thoughtcrime/securesms/components/DocumentView.java index 5e0ebf31d..b8769eb4a 100644 --- a/src/org/thoughtcrime/securesms/components/DocumentView.java +++ b/src/org/thoughtcrime/securesms/components/DocumentView.java @@ -59,6 +59,13 @@ public class DocumentView extends FrameLayout { this.setOnClickListener(new OpenClickedListener(documentSlide)); } + public String getDescription() { + String desc = getContext().getString(R.string.file); + desc += "\n" + fileName.getText(); + desc += "\n" + fileSize.getText(); + return desc; + } + @Override public void setFocusable(boolean focusable) { super.setFocusable(focusable); diff --git a/src/org/thoughtcrime/securesms/components/ThumbnailView.java b/src/org/thoughtcrime/securesms/components/ThumbnailView.java index db7149f00..6efd8702c 100644 --- a/src/org/thoughtcrime/securesms/components/ThumbnailView.java +++ b/src/org/thoughtcrime/securesms/components/ThumbnailView.java @@ -82,6 +82,14 @@ public class ThumbnailView extends FrameLayout { } + public String getDescription() { + if (slide != null && slide.hasPlayOverlay()) { + return getContext().getString(R.string.video); + } else { + return getContext().getString(R.string.image); + } + } + @Override protected void onMeasure(int originalWidthMeasureSpec, int originalHeightMeasureSpec) { fillTargetDimensions(measureDimens, dimens, bounds); diff --git a/src/org/thoughtcrime/securesms/components/WebxdcView.java b/src/org/thoughtcrime/securesms/components/WebxdcView.java index ea8e9b5a9..a93904673 100644 --- a/src/org/thoughtcrime/securesms/components/WebxdcView.java +++ b/src/org/thoughtcrime/securesms/components/WebxdcView.java @@ -89,6 +89,15 @@ public class WebxdcView extends FrameLayout { appSubtitle.setText(summary); } + public String getDescription() { + String desc = getContext().getString(R.string.webxdc_app); + desc += "\n" + appName.getText(); + if (appSubtitle.getText() != null && !appSubtitle.getText().toString().equals("") && !appSubtitle.getText().toString().equals(getContext().getString(R.string.webxdc_app))) { + desc += "\n" + appSubtitle.getText(); + } + return desc; + } + private class OpenClickedListener implements View.OnClickListener { private final @NonNull DocumentSlide slide;