From 79c6f8b2df9023bf46d6b2829ecf69bc4672566a Mon Sep 17 00:00:00 2001 From: Casey Burkhardt Date: Sun, 8 Aug 2010 20:08:39 -0700 Subject: Improves accessibility by causing AccessibilityEvents to be fired after console changes occur. --- src/org/connectbot/TerminalView.java | 108 +++++++++++++++++++++++++ src/org/connectbot/service/Relay.java | 4 +- src/org/connectbot/service/TerminalBridge.java | 10 ++- 3 files changed, 118 insertions(+), 4 deletions(-) mode change 100644 => 100755 src/org/connectbot/TerminalView.java (limited to 'src') diff --git a/src/org/connectbot/TerminalView.java b/src/org/connectbot/TerminalView.java old mode 100644 new mode 100755 index 35a3c56..057399f --- a/src/org/connectbot/TerminalView.java +++ b/src/org/connectbot/TerminalView.java @@ -17,21 +17,29 @@ package org.connectbot; +import java.util.List; + import org.connectbot.bean.SelectionArea; import org.connectbot.service.FontSizeChangedListener; import org.connectbot.service.TerminalBridge; import org.connectbot.service.TerminalKeyListener; import android.app.Activity; +import android.content.ContentResolver; import android.content.Context; +import android.content.Intent; +import android.content.pm.ResolveInfo; +import android.database.Cursor; import android.graphics.Canvas; import android.graphics.Matrix; import android.graphics.Paint; import android.graphics.Path; import android.graphics.PixelXorXfermode; import android.graphics.RectF; +import android.net.Uri; import android.view.View; import android.view.ViewGroup.LayoutParams; +import android.view.accessibility.AccessibilityEvent; import android.view.inputmethod.BaseInputConnection; import android.view.inputmethod.EditorInfo; import android.view.inputmethod.InputConnection; @@ -63,6 +71,14 @@ public class TerminalView extends View implements FontSizeChangedListener { private String lastNotification = null; private volatile boolean notifications = true; + // Related to Accessibility Features + private boolean accessibilityActive = false; + private StringBuffer accessibilityBuffer = null; + private AccessibilityEventSender eventSender = null; + private int ACCESSIBILITY_EVENT_THRESHOLD = 1000; + private static final String SCREENREADER_INTENT_ACTION = "android.accessibilityservice.AccessibilityService"; + private static final String SCREENREADER_INTENT_CATEGORY = "android.accessibilityservice.category.FEEDBACK_SPOKEN"; + public TerminalView(Context context, TerminalBridge bridge) { super(context); @@ -112,6 +128,9 @@ public class TerminalView extends View implements FontSizeChangedListener { // connect our view up to the bridge setOnKeyListener(bridge.getKeyHandler()); + + // Enable accessibility features if a screen reader is active. + accessibilityActive = isScreenReaderActive(); } public void destroy() { @@ -268,4 +287,93 @@ public class TerminalView extends View implements FontSizeChangedListener { outAttrs.inputType = EditorInfo.TYPE_NULL; return new BaseInputConnection(this, false); } + + private boolean isScreenReaderActive() { + // Restrict the set of intents to only accessibility services that have + // the category FEEDBACK_SPOKEN (aka, screen readers). + Intent screenReaderIntent = new Intent(SCREENREADER_INTENT_ACTION); + screenReaderIntent.addCategory(SCREENREADER_INTENT_CATEGORY); + List screenReaders = context.getPackageManager().queryIntentServices( + screenReaderIntent, 0); + ContentResolver cr = context.getContentResolver(); + Cursor cursor = null; + int status = 0; + for (ResolveInfo screenReader : screenReaders) { + // All screen readers are expected to implement a content provider that responds to + // content://.providers.StatusProvider + cursor = cr.query(Uri.parse("content://" + screenReader.serviceInfo.packageName + + ".providers.StatusProvider"), null, null, null, null); + if (cursor != null) { + cursor.moveToFirst(); + // These content providers use a special cursor that only has one element, + // an integer that is 1 if the screen reader is running. + status = cursor.getInt(0); + cursor.close(); + if (status == 1) { + return true; + } + } + } + return false; + } + + + public StringBuffer getAccessibilityBuffer() { + return accessibilityBuffer; + } + + public void propagateConsoleText(char[] rawText, int length) { + if (accessibilityActive) { + if (accessibilityBuffer == null) { + accessibilityBuffer = new StringBuffer(); + } + + for (int i = 0; i < length; ++i) { + accessibilityBuffer.append(rawText[i]); + } + + if (eventSender != null) { + removeCallbacks(eventSender); + } else { + eventSender = new AccessibilityEventSender(); + } + postDelayed(eventSender, ACCESSIBILITY_EVENT_THRESHOLD); + } + } + + private class AccessibilityEventSender implements Runnable { + public void run() { + synchronized (accessibilityBuffer) { + // Strip Console Codes + String regex = "" + ((char) 27) + (char) 92 + ((char) 91) + "[^m]+[m|:]"; + accessibilityBuffer = + new StringBuffer(accessibilityBuffer.toString().replaceAll(regex, " ")); + + // Apply Backspaces + String backspaceCode = "" + ((char) 8) + ((char) 27) + ((char) 91) + ((char) 75); + int i = accessibilityBuffer.indexOf(backspaceCode); + while (i != -1) { + if (i == 0) { + accessibilityBuffer = + accessibilityBuffer.replace(i, i + backspaceCode.length(), ""); + } else { + accessibilityBuffer = + accessibilityBuffer.replace(i - 1, i + backspaceCode.length(), ""); + } + i = accessibilityBuffer.indexOf(backspaceCode); + } + + if (accessibilityBuffer.length() > 0) { + AccessibilityEvent event = + AccessibilityEvent.obtain(AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED); + event.setFromIndex(0); + event.setAddedCount(accessibilityBuffer.length()); + event.getText().add(accessibilityBuffer); + + sendAccessibilityEventUnchecked(event); + accessibilityBuffer.setLength(0); + } + } + } + } } diff --git a/src/org/connectbot/service/Relay.java b/src/org/connectbot/service/Relay.java index 18bf896..f14ff53 100644 --- a/src/org/connectbot/service/Relay.java +++ b/src/org/connectbot/service/Relay.java @@ -17,8 +17,6 @@ package org.connectbot.service; -import org.apache.harmony.niochar.charset.additional.IBM437; - import java.io.IOException; import java.nio.ByteBuffer; import java.nio.CharBuffer; @@ -27,6 +25,7 @@ import java.nio.charset.CharsetDecoder; import java.nio.charset.CoderResult; import java.nio.charset.CodingErrorAction; +import org.apache.harmony.niochar.charset.additional.IBM437; import org.connectbot.transport.AbsTransport; import org.connectbot.util.EastAsianWidth; @@ -154,6 +153,7 @@ public class Relay implements Runnable { wideAttribute, isLegacyEastAsian); } buffer.putString(charArray, wideAttribute, 0, charBuffer.position()); + bridge.propagateConsoleText(charArray, charBuffer.position()); charBuffer.clear(); bridge.redraw(); } diff --git a/src/org/connectbot/service/TerminalBridge.java b/src/org/connectbot/service/TerminalBridge.java index e9e69ca..9b4b96c 100644 --- a/src/org/connectbot/service/TerminalBridge.java +++ b/src/org/connectbot/service/TerminalBridge.java @@ -35,12 +35,12 @@ import org.connectbot.util.HostDatabase; import android.content.Context; import android.graphics.Bitmap; +import android.graphics.Bitmap.Config; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; -import android.graphics.Typeface; -import android.graphics.Bitmap.Config; import android.graphics.Paint.FontMetrics; +import android.graphics.Typeface; import android.text.ClipboardManager; import android.util.Log; import de.mud.terminal.VDUBuffer; @@ -644,6 +644,12 @@ public class TerminalBridge implements VDUDisplay { return buffer; } + public void propagateConsoleText(char[] rawText, int length) { + if (parent != null) { + parent.propagateConsoleText(rawText, length); + } + } + public void onDraw() { int fg, bg; synchronized (buffer) { -- cgit v1.2.3 From 662b3208168726e9b4050fc9666181bee7585a5b Mon Sep 17 00:00:00 2001 From: Casey Burkhardt Date: Mon, 9 Aug 2010 20:04:34 -0700 Subject: Fixing formatting issues. --- src/org/connectbot/TerminalView.java | 167 ++++++++++++++++++----------------- 1 file changed, 84 insertions(+), 83 deletions(-) (limited to 'src') diff --git a/src/org/connectbot/TerminalView.java b/src/org/connectbot/TerminalView.java index 057399f..c99680b 100755 --- a/src/org/connectbot/TerminalView.java +++ b/src/org/connectbot/TerminalView.java @@ -73,11 +73,11 @@ public class TerminalView extends View implements FontSizeChangedListener { // Related to Accessibility Features private boolean accessibilityActive = false; - private StringBuffer accessibilityBuffer = null; + private StringBuffer accessibilityBuffer = null; private AccessibilityEventSender eventSender = null; private int ACCESSIBILITY_EVENT_THRESHOLD = 1000; private static final String SCREENREADER_INTENT_ACTION = "android.accessibilityservice.AccessibilityService"; - private static final String SCREENREADER_INTENT_CATEGORY = "android.accessibilityservice.category.FEEDBACK_SPOKEN"; + private static final String SCREENREADER_INTENT_CATEGORY = "android.accessibilityservice.category.FEEDBACK_SPOKEN"; public TerminalView(Context context, TerminalBridge bridge) { super(context); @@ -288,92 +288,93 @@ public class TerminalView extends View implements FontSizeChangedListener { return new BaseInputConnection(this, false); } - private boolean isScreenReaderActive() { - // Restrict the set of intents to only accessibility services that have - // the category FEEDBACK_SPOKEN (aka, screen readers). - Intent screenReaderIntent = new Intent(SCREENREADER_INTENT_ACTION); - screenReaderIntent.addCategory(SCREENREADER_INTENT_CATEGORY); - List screenReaders = context.getPackageManager().queryIntentServices( - screenReaderIntent, 0); - ContentResolver cr = context.getContentResolver(); - Cursor cursor = null; - int status = 0; - for (ResolveInfo screenReader : screenReaders) { - // All screen readers are expected to implement a content provider that responds to - // content://.providers.StatusProvider - cursor = cr.query(Uri.parse("content://" + screenReader.serviceInfo.packageName - + ".providers.StatusProvider"), null, null, null, null); - if (cursor != null) { - cursor.moveToFirst(); - // These content providers use a special cursor that only has one element, - // an integer that is 1 if the screen reader is running. - status = cursor.getInt(0); - cursor.close(); - if (status == 1) { - return true; - } - } - } - return false; - } - + private boolean isScreenReaderActive() { + // Restrict the set of intents to only accessibility services that have + // the category FEEDBACK_SPOKEN (aka, screen readers). + Intent screenReaderIntent = new Intent(SCREENREADER_INTENT_ACTION); + screenReaderIntent.addCategory(SCREENREADER_INTENT_CATEGORY); + List screenReaders = context.getPackageManager().queryIntentServices( + screenReaderIntent, 0); + ContentResolver cr = context.getContentResolver(); + Cursor cursor = null; + int status = 0; + for (ResolveInfo screenReader : screenReaders) { + // All screen readers are expected to implement a content provider + // that responds to: + // content://.providers.StatusProvider + cursor = cr.query(Uri.parse("content://" + screenReader.serviceInfo.packageName + + ".providers.StatusProvider"), null, null, null, null); + if (cursor != null) { + cursor.moveToFirst(); + // These content providers use a special cursor that only has + // one element, an integer that is 1 if the screen reader is + // running. + status = cursor.getInt(0); + cursor.close(); + if (status == 1) { + return true; + } + } + } + return false; + } public StringBuffer getAccessibilityBuffer() { - return accessibilityBuffer; + return accessibilityBuffer; } - public void propagateConsoleText(char[] rawText, int length) { - if (accessibilityActive) { - if (accessibilityBuffer == null) { - accessibilityBuffer = new StringBuffer(); - } - - for (int i = 0; i < length; ++i) { - accessibilityBuffer.append(rawText[i]); - } - - if (eventSender != null) { - removeCallbacks(eventSender); - } else { - eventSender = new AccessibilityEventSender(); - } - postDelayed(eventSender, ACCESSIBILITY_EVENT_THRESHOLD); - } - } + public void propagateConsoleText(char[] rawText, int length) { + if (accessibilityActive) { + if (accessibilityBuffer == null) { + accessibilityBuffer = new StringBuffer(); + } + + for (int i = 0; i < length; ++i) { + accessibilityBuffer.append(rawText[i]); + } + + if (eventSender != null) { + removeCallbacks(eventSender); + } else { + eventSender = new AccessibilityEventSender(); + } + postDelayed(eventSender, ACCESSIBILITY_EVENT_THRESHOLD); + } + } private class AccessibilityEventSender implements Runnable { - public void run() { - synchronized (accessibilityBuffer) { - // Strip Console Codes - String regex = "" + ((char) 27) + (char) 92 + ((char) 91) + "[^m]+[m|:]"; - accessibilityBuffer = - new StringBuffer(accessibilityBuffer.toString().replaceAll(regex, " ")); - - // Apply Backspaces - String backspaceCode = "" + ((char) 8) + ((char) 27) + ((char) 91) + ((char) 75); - int i = accessibilityBuffer.indexOf(backspaceCode); - while (i != -1) { - if (i == 0) { - accessibilityBuffer = - accessibilityBuffer.replace(i, i + backspaceCode.length(), ""); - } else { - accessibilityBuffer = - accessibilityBuffer.replace(i - 1, i + backspaceCode.length(), ""); - } - i = accessibilityBuffer.indexOf(backspaceCode); - } - - if (accessibilityBuffer.length() > 0) { - AccessibilityEvent event = - AccessibilityEvent.obtain(AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED); - event.setFromIndex(0); - event.setAddedCount(accessibilityBuffer.length()); - event.getText().add(accessibilityBuffer); - - sendAccessibilityEventUnchecked(event); - accessibilityBuffer.setLength(0); - } - } - } + public void run() { + synchronized (accessibilityBuffer) { + // Strip console codes with regex matching control codes + String regex = "" + ((char) 27) + (char) 92 + ((char) 91) + "[^m]+[m|:]"; + accessibilityBuffer = new StringBuffer( + accessibilityBuffer.toString().replaceAll(regex, " ")); + + // Apply Backspaces using backspace character sequence + String backspaceCode = "" + ((char) 8) + ((char) 27) + ((char) 91) + ((char) 75); + int i = accessibilityBuffer.indexOf(backspaceCode); + while (i != -1) { + if (i == 0) { + accessibilityBuffer = accessibilityBuffer.replace( + i, i + backspaceCode.length(), ""); + } else { + accessibilityBuffer = accessibilityBuffer.replace( + i - 1, i + backspaceCode.length(), ""); + } + i = accessibilityBuffer.indexOf(backspaceCode); + } + + if (accessibilityBuffer.length() > 0) { + AccessibilityEvent event = AccessibilityEvent.obtain( + AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED); + event.setFromIndex(0); + event.setAddedCount(accessibilityBuffer.length()); + event.getText().add(accessibilityBuffer); + + sendAccessibilityEventUnchecked(event); + accessibilityBuffer.setLength(0); + } + } + } } } -- cgit v1.2.3 From ede09527eb446790ecfa71392fe6a30640b2742e Mon Sep 17 00:00:00 2001 From: Casey Burkhardt Date: Thu, 12 Aug 2010 14:48:23 -0700 Subject: Adds fixes for checking accessibility state on background thread and compiling the regex pattern only once. --- src/org/connectbot/TerminalView.java | 85 +++++++++++++++++++++--------------- 1 file changed, 50 insertions(+), 35 deletions(-) (limited to 'src') diff --git a/src/org/connectbot/TerminalView.java b/src/org/connectbot/TerminalView.java index c99680b..45f2977 100755 --- a/src/org/connectbot/TerminalView.java +++ b/src/org/connectbot/TerminalView.java @@ -18,6 +18,8 @@ package org.connectbot; import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import org.connectbot.bean.SelectionArea; import org.connectbot.service.FontSizeChangedListener; @@ -74,7 +76,10 @@ public class TerminalView extends View implements FontSizeChangedListener { // Related to Accessibility Features private boolean accessibilityActive = false; private StringBuffer accessibilityBuffer = null; + private Pattern controlCodes = null; + private Matcher codeMatcher = null; private AccessibilityEventSender eventSender = null; + private AccessibilityStateTester stateTester = null; private int ACCESSIBILITY_EVENT_THRESHOLD = 1000; private static final String SCREENREADER_INTENT_ACTION = "android.accessibilityservice.AccessibilityService"; private static final String SCREENREADER_INTENT_CATEGORY = "android.accessibilityservice.category.FEEDBACK_SPOKEN"; @@ -130,7 +135,8 @@ public class TerminalView extends View implements FontSizeChangedListener { setOnKeyListener(bridge.getKeyHandler()); // Enable accessibility features if a screen reader is active. - accessibilityActive = isScreenReaderActive(); + stateTester = new AccessibilityStateTester(); + new Thread(stateTester).start(); } public void destroy() { @@ -288,37 +294,6 @@ public class TerminalView extends View implements FontSizeChangedListener { return new BaseInputConnection(this, false); } - private boolean isScreenReaderActive() { - // Restrict the set of intents to only accessibility services that have - // the category FEEDBACK_SPOKEN (aka, screen readers). - Intent screenReaderIntent = new Intent(SCREENREADER_INTENT_ACTION); - screenReaderIntent.addCategory(SCREENREADER_INTENT_CATEGORY); - List screenReaders = context.getPackageManager().queryIntentServices( - screenReaderIntent, 0); - ContentResolver cr = context.getContentResolver(); - Cursor cursor = null; - int status = 0; - for (ResolveInfo screenReader : screenReaders) { - // All screen readers are expected to implement a content provider - // that responds to: - // content://.providers.StatusProvider - cursor = cr.query(Uri.parse("content://" + screenReader.serviceInfo.packageName - + ".providers.StatusProvider"), null, null, null, null); - if (cursor != null) { - cursor.moveToFirst(); - // These content providers use a special cursor that only has - // one element, an integer that is 1 if the screen reader is - // running. - status = cursor.getInt(0); - cursor.close(); - if (status == 1) { - return true; - } - } - } - return false; - } - public StringBuffer getAccessibilityBuffer() { return accessibilityBuffer; } @@ -346,9 +321,16 @@ public class TerminalView extends View implements FontSizeChangedListener { public void run() { synchronized (accessibilityBuffer) { // Strip console codes with regex matching control codes - String regex = "" + ((char) 27) + (char) 92 + ((char) 91) + "[^m]+[m|:]"; - accessibilityBuffer = new StringBuffer( - accessibilityBuffer.toString().replaceAll(regex, " ")); + if (controlCodes == null) { + controlCodes = + Pattern.compile("" + ((char) 27) + (char) 92 + ((char) 91) + "[^m]+[m|:]"); + } + if (codeMatcher == null) { + codeMatcher = controlCodes.matcher(accessibilityBuffer); + } else { + codeMatcher.reset(accessibilityBuffer); + } + accessibilityBuffer = new StringBuffer(codeMatcher.replaceAll(" ")); // Apply Backspaces using backspace character sequence String backspaceCode = "" + ((char) 8) + ((char) 27) + ((char) 91) + ((char) 75); @@ -377,4 +359,37 @@ public class TerminalView extends View implements FontSizeChangedListener { } } } + + private class AccessibilityStateTester implements Runnable { + public void run() { + // Restrict the set of intents to only accessibility services that + // have the category FEEDBACK_SPOKEN (aka, screen readers). + Intent screenReaderIntent = new Intent(SCREENREADER_INTENT_ACTION); + screenReaderIntent.addCategory(SCREENREADER_INTENT_CATEGORY); + List screenReaders = context.getPackageManager().queryIntentServices( + screenReaderIntent, 0); + ContentResolver cr = context.getContentResolver(); + Cursor cursor = null; + int status = 0; + for (ResolveInfo screenReader : screenReaders) { + // All screen readers are expected to implement a content + // provider that responds to: + // content://.providers.StatusProvider + cursor = cr.query(Uri.parse("content://" + screenReader.serviceInfo.packageName + + ".providers.StatusProvider"), null, null, null, null); + if (cursor != null) { + cursor.moveToFirst(); + // These content providers use a special cursor that only has + // one element, an integer that is 1 if the screen reader is running. + status = cursor.getInt(0); + cursor.close(); + if (status == 1) { + accessibilityActive = true; + return; + } + } + } + accessibilityActive = false; + } + } } -- cgit v1.2.3