diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Cells/ChatMessageCell.java b/TMessagesProj/src/main/java/org/telegram/ui/Cells/ChatMessageCell.java index f988b1e6fb2..55537d8b1be 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Cells/ChatMessageCell.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Cells/ChatMessageCell.java @@ -1238,6 +1238,8 @@ public float getLastTouchY() { CharSequence accessibilityText; private boolean accessibilityTextUnread, accessibilityTextContentUnread; private long accessibilityTextFileSize; + private int accessibilityTextButtonState = Integer.MIN_VALUE, accessibilityTextMiniButtonState = Integer.MIN_VALUE; + private int accessibilityTextTransferIndex = -1; private boolean wasTranscriptionOpen; private Path instantLinkArrowPath; private Paint instantLinkArrowPaint; @@ -6342,6 +6344,11 @@ public TLRPC.Chat getCurrentChat() { protected void onDetachedFromWindow() { super.onDetachedFromWindow(); + accessibilityFocused = false; + announcedTransferOnce = false; + accessibilityHovered = false; + announcedTransferOnce = false; + NotificationCenter.getGlobalInstance().removeObserver(this, NotificationCenter.startSpoilers); NotificationCenter.getGlobalInstance().removeObserver(this, NotificationCenter.stopSpoilers); NotificationCenter.getGlobalInstance().removeObserver(this, NotificationCenter.emojiLoaded); @@ -6993,6 +7000,15 @@ private void setMessageContent(MessageObject messageObject, MessageObject.Groupe captionAbove = currentMessageObject.messageOwner != null && currentMessageObject.messageOwner.invert_media || groupedMessages != null && groupedMessages.captionAbove; isSmallImage = false; lastLoadingSizeTotal = 0; + lastAnnouncedTransferStep = -1; + lastAnnouncedTransferPercent = -1; + lastAnnouncedTransferSize = -1; + announcedTransferOnce = false; + lastAnnouncedTransferTime = 0; + accessibilityFocused = false; + announcedTransferOnce = false; + accessibilityHovered = false; + announcedTransferOnce = false; if (scheduledInvalidate) { AndroidUtilities.cancelRunOnUIThread(invalidateRunnable); scheduledInvalidate = false; @@ -11224,6 +11240,7 @@ private void setMessageContent(MessageObject messageObject, MessageObject.Groupe loadingProgressLayout = null; animatingLoadingProgressProgress = 0; lastLoadingSizeTotal = 0; + lastAnnouncedTransferStep = -1; selectedBackgroundProgress = 0f; if (statusDrawableAnimator != null) { statusDrawableAnimator.removeAllListeners(); @@ -18185,11 +18202,108 @@ public void onAnimationReady(ImageReceiver imageReceiver) { } } + private int lastAnnouncedTransferStep = -1; + private int lastAnnouncedTransferPercent = -1; + private long lastAnnouncedTransferSize = -1; + private boolean announcedTransferOnce; + private long lastAnnouncedTransferTime; + private boolean accessibilityFocused; + private boolean accessibilityHovered; + + private static final int TRANSFER_ANNOUNCE_STEP_PERCENT = 10; + private static final long TRANSFER_ANNOUNCE_INTERVAL = 2500; + + private boolean hasTransferSizeInfo() { + return documentAttach != null && (documentAttachType == DOCUMENT_ATTACH_TYPE_DOCUMENT || documentAttachType == DOCUMENT_ATTACH_TYPE_GIF || documentAttachType == DOCUMENT_ATTACH_TYPE_VIDEO); + } + + private CharSequence formatTransferProgress(boolean sending, int percent, long loadedSize, long totalSize) { + final StringBuilder sb = new StringBuilder(); + sb.append(getString(sending ? R.string.AccDescrMsgSending : R.string.Downloading)); + sb.append(" ").append(percent).append("%"); + if (totalSize > 0) { + sb.append(", ").append(formatString(R.string.AccDescrPlayerDuration, AndroidUtilities.formatFileSize(loadedSize), AndroidUtilities.formatFileSize(totalSize))); + } + return sb; + } + + private int getTransferPercent() { + if (currentMessageObject == null) { + return -1; + } + if (getIconForCurrentState() != MediaActionDrawable.ICON_CANCEL && getMiniIconForCurrentState() != MediaActionDrawable.ICON_CANCEL) { + return -1; + } + final float progress; + if (hasTransferSizeInfo() && loadingProgressLayout != null && lastLoadingSizeTotal > 0) { + progress = Math.min(1f, currentMessageObject.loadedFileSize / (float) lastLoadingSizeTotal); + } else { + progress = drawVideoImageButton ? videoRadialProgress.getProgress() : radialProgress.getProgress(); + } + return Math.round(progress * 100); + } + + private CharSequence getTransferProgressText() { + final int percent = getTransferPercent(); + if (percent < 0) { + return null; + } + final boolean hasSize = hasTransferSizeInfo() && loadingProgressLayout != null && lastLoadingSizeTotal > 0; + return formatTransferProgress(currentMessageObject.isSending(), percent, currentMessageObject.loadedFileSize, hasSize ? lastLoadingSizeTotal : 0); + } + + private boolean isExploredByAccessibility() { + if (!accessibilityHovered && !accessibilityFocused && !isAccessibilityFocused()) { + return false; + } + final AccessibilityManager am = (AccessibilityManager) getContext().getSystemService(Context.ACCESSIBILITY_SERVICE); + return am != null && am.isEnabled() && am.isTouchExplorationEnabled(); + } + + private void announceTransferProgress(boolean sending, long loadedSize, long totalSize) { + if (currentMessageObject == null || totalSize <= 0) { + return; + } + final boolean hovered = accessibilityHovered; + if (!isExploredByAccessibility()) { + return; + } + final int percent = (int) Math.min(100, Math.round(loadedSize / (float) totalSize * 100)); + final int step = percent / TRANSFER_ANNOUNCE_STEP_PERCENT; + final long now = SystemClock.elapsedRealtime(); + if (hovered) { + // while the finger explores the message, follow every step of the progress + if (step == lastAnnouncedTransferStep) { + return; + } + } else if (now - lastAnnouncedTransferTime < TRANSFER_ANNOUNCE_INTERVAL) { + // the message only holds accessibility focus, so keep the updates rare + return; + } else if (percent == lastAnnouncedTransferPercent && loadedSize == lastAnnouncedTransferSize) { + // nothing moved since the last update: there is nothing to say + return; + } + lastAnnouncedTransferStep = step; + lastAnnouncedTransferTime = now; + lastAnnouncedTransferPercent = percent; + lastAnnouncedTransferSize = loadedSize; + // the first update after reaching the message tells the whole of it, as reaching it does, + // and the ones after that only tell what has changed since + if (announcedTransferOnce) { + announceForAccessibility(hasTransferSizeInfo() ? percent + "%, " + AndroidUtilities.formatFileSize(loadedSize) : percent + "%"); + } else { + announcedTransferOnce = true; + announceForAccessibility(formatTransferProgress(sending, percent, loadedSize, hasTransferSizeInfo() ? totalSize : 0)); + } + } + + @Override public void onProgressDownload(String fileName, long downloadedSize, long totalSize) { float progress = totalSize == 0 ? 0 : Math.min(1f, downloadedSize / (float) totalSize); currentMessageObject.loadedFileSize = downloadedSize; createLoadingProgressLayout(downloadedSize, totalSize); + announceTransferProgress(false, downloadedSize, totalSize); if (drawVideoImageButton) { videoRadialProgress.setProgress(progress, true); } else { @@ -18236,6 +18350,7 @@ public void onProgressUpload(String fileName, long uploadedSize, long totalSize, lastLoadingSizeTotal = totalSize; } createLoadingProgressLayout(uploadedSize, totalSize); + announceTransferProgress(true, uploadedSize, totalSize); } private void createLoadingProgressLayout(TLRPC.Document document) { @@ -26347,6 +26462,12 @@ public int getLayoutHeight() { @Override public boolean performAccessibilityAction(int action, Bundle arguments) { + if (action == AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS) { + accessibilityFocused = true; + } else if (action == AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS) { + accessibilityFocused = false; + announcedTransferOnce = false; + } if (delegate != null && delegate.onAccessibilityAction(action, arguments)) { return false; } @@ -26424,6 +26545,7 @@ public boolean onHoverEvent(MotionEvent event) { int x = (int) getEventX(event); int y = (int) getEventY(event); if (event.getAction() == MotionEvent.ACTION_HOVER_ENTER || event.getAction() == MotionEvent.ACTION_HOVER_MOVE) { + accessibilityHovered = true; for (int i = 0; i < accessibilityVirtualViewBounds.size(); i++) { Rect rect = accessibilityVirtualViewBounds.valueAt(i); if (rect.contains(x, y)) { @@ -26437,6 +26559,8 @@ public boolean onHoverEvent(MotionEvent event) { } } else if (event.getAction() == MotionEvent.ACTION_HOVER_EXIT) { currentFocusedVirtualView = 0; + accessibilityHovered = false; + announcedTransferOnce = false; } return super.onHoverEvent(event); } @@ -26851,7 +26975,7 @@ public AccessibilityNodeInfo createAccessibilityNodeInfo(int virtualViewId) { final boolean unread = currentMessageObject != null && currentMessageObject.isOut() && !currentMessageObject.scheduled && currentMessageObject.isUnread(); final boolean contentUnread = currentMessageObject != null && currentMessageObject.isContentUnread(); final long fileSize = currentMessageObject != null ? currentMessageObject.loadedFileSize : 0; - if (accessibilityText == null || accessibilityTextUnread != unread || accessibilityTextContentUnread != contentUnread || accessibilityTextFileSize != fileSize) { + if (accessibilityText == null || accessibilityTextUnread != unread || accessibilityTextContentUnread != contentUnread || accessibilityTextFileSize != fileSize || accessibilityTextButtonState != buttonState || accessibilityTextMiniButtonState != miniButtonState) { SpannableStringBuilder sb = new SpannableStringBuilder(); if (isChat && currentUser != null && !currentMessageObject.isOut()) { sb.append(UserObject.getUserName(currentUser)); @@ -26930,15 +27054,7 @@ public AccessibilityNodeInfo createAccessibilityNodeInfo(int virtualViewId) { } sb.append(messageText); } - if (documentAttach != null && (documentAttachType == DOCUMENT_ATTACH_TYPE_DOCUMENT || documentAttachType == DOCUMENT_ATTACH_TYPE_GIF || documentAttachType == DOCUMENT_ATTACH_TYPE_VIDEO)) { - if (buttonState == 1 && loadingProgressLayout != null) { - sb.append("\n"); - final boolean sending = currentMessageObject.isSending(); - final String key = sending ? "AccDescrUploadProgress" : "AccDescrDownloadProgress"; - final int resId = sending ? R.string.AccDescrUploadProgress : R.string.AccDescrDownloadProgress; - sb.append(formatString(key, resId, AndroidUtilities.formatFileSize(currentMessageObject.loadedFileSize), AndroidUtilities.formatFileSize(lastLoadingSizeTotal))); - } - } + accessibilityTextTransferIndex = sb.length(); if (currentMessageObject.isMusic()) { sb.append("\n"); sb.append(formatString("AccDescrMusicInfo", R.string.AccDescrMusicInfo, currentMessageObject.getMusicAuthor(), currentMessageObject.getMusicTitle())); @@ -27006,11 +27122,13 @@ public AccessibilityNodeInfo createAccessibilityNodeInfo(int virtualViewId) { sb.append(currentMessageObject.isUnread() ? getString("AccDescrMsgUnread", R.string.AccDescrMsgUnread) : getString("AccDescrMsgRead", R.string.AccDescrMsgRead)); } } else if (currentMessageObject.isSending()) { - sb.append("\n"); - sb.append(getString("AccDescrMsgSending", R.string.AccDescrMsgSending)); - final float sendingProgress = radialProgress.getProgress(); - if (sendingProgress > 0f) { - sb.append(Integer.toString(Math.round(sendingProgress * 100))).append("%"); + if (getTransferPercent() < 0) { + sb.append("\n"); + sb.append(getString("AccDescrMsgSending", R.string.AccDescrMsgSending)); + final float sendingProgress = radialProgress.getProgress(); + if (sendingProgress > 0f) { + sb.append(" ").append(Integer.toString(Math.round(sendingProgress * 100))).append("%"); + } } } else if (currentMessageObject.isSendError()) { sb.append("\n"); @@ -27096,12 +27214,24 @@ public void onClick(View view) { accessibilityTextUnread = unread; accessibilityTextContentUnread = contentUnread; accessibilityTextFileSize = fileSize; + accessibilityTextButtonState = buttonState; + accessibilityTextMiniButtonState = miniButtonState; + } + + // the progress moves while the message stays as it is, so it cannot live in the + // text that is kept: it goes in where it belongs every time the message is read + CharSequence spokenText = accessibilityText; + final CharSequence transferProgress = getTransferProgressText(); + if (transferProgress != null && accessibilityTextTransferIndex >= 0 && accessibilityTextTransferIndex <= accessibilityText.length()) { + final SpannableStringBuilder withProgress = new SpannableStringBuilder(accessibilityText); + withProgress.insert(accessibilityTextTransferIndex, "\n" + transferProgress); + spokenText = withProgress; } if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) { - info.setContentDescription(accessibilityText.toString()); + info.setContentDescription(spokenText.toString()); } else { - info.setText(accessibilityText); + info.setText(spokenText); } info.setEnabled(true);