Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -229,7 +229,7 @@ private void onAutoConnectChanged() {

private ImmutableSet<ConnectableDevice> getBondedDeviceSet() {
return connectManagerProxy.getBondedDevices().stream()
.filter(device -> allowDevice(device.name()))
.filter(this::allowDevice)
.collect(toImmutableSet());
}

Expand Down Expand Up @@ -285,11 +285,16 @@ public ConnectableDevice apply(Pair<String, String> devicePair) {
}

private boolean acceptAutoConnect(ConnectableDevice device) {
return allowDevice(device.name())
return allowDevice(device)
&& !userDisconnectedDevices.contains(device.address())
&& !userDeniedDevices.contains(device.address());
}

/** A display whose name Backtalk knows, or one that may connect through HID braille. */
private boolean allowDevice(ConnectableDevice device) {
return allowDevice(device.name()) || connectManagerProxy.mayBeHidBrailleDisplay(device);
}

private boolean allowDevice(String name) {
if (name == null) {
return false;
Expand Down Expand Up @@ -491,7 +496,7 @@ public boolean isScanning() {
/** Gets a copy list of currently visible devices. */
public List<ConnectableDevice> getScannedDevicesCopy() {
return connectioneer.connectManagerProxy.getConnectableDevices().stream()
.filter(device -> connectioneer.allowDevice(device.name()))
.filter(connectioneer::allowDevice)
.collect(toCollection(ArrayList::new));
}

Expand Down Expand Up @@ -715,7 +720,7 @@ public void onDeviceListCleared() {

@Override
public void onDeviceSeenOrUpdated(ConnectableDevice device) {
if (allowDevice(device.name())) {
if (allowDevice(device)) {
autoConnectIfPossible(ImmutableSet.of(device), ConnectReason.AUTO_CONNECT_DEVICE_SEEN);
aspectConnection.notifyConnectableDeviceSeenOrUpdated(device);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,14 @@ && getConnectingOrConnectedDevice().isPresent()
/** Returns true if the device is a HID device. */
public abstract boolean isHidDevice(ConnectableDevice device);

/**
* Returns true if the device could be a braille display that connects through HID, whatever its
* name. Connecting checks whether it really is one.
*/
public boolean mayBeHidBrailleDisplay(ConnectableDevice device) {
return false;
}

/** Set accessibility service context provider. */
public void setAccessibilityServiceContextProvider(
AccessibilityServiceContextProvider accessibilityServiceContextProvider) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,11 @@ public boolean isHidDevice(ConnectableDevice device) {
return connectManager.isHidDevice(device);
}

@Override
public boolean mayBeHidBrailleDisplay(ConnectableDevice device) {
return connectManager.mayBeHidBrailleDisplay(device);
}

@VisibleForTesting
public void testing_setConnectManager(ConnectManager connectManager) {
this.connectManager = connectManager;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
import androidx.core.content.ContextCompat;
import androidx.core.os.BuildCompat;
import com.google.android.accessibility.braille.brailledisplay.BrailleDisplayLog;
import com.google.android.accessibility.braille.brailledisplay.FeatureFlagReader;
import com.google.android.accessibility.braille.brailledisplay.platform.ConnectStage;
import com.google.android.accessibility.braille.brailledisplay.platform.connect.ConnectManager;
import com.google.android.accessibility.braille.brailledisplay.platform.connect.Connector;
Expand Down Expand Up @@ -225,6 +226,34 @@ public boolean isHidDevice(ConnectableDevice device) {
return false;
}

/**
* A device that offers HID, where Android can connect braille displays through HID. Any braille
* display that follows the HID braille standard works this way, so it doesn't need a name that
* Backtalk knows. Only the services that Bluetooth already knows about are checked, so this
* doesn't query each device, and connecting confirms that it's a braille display.
*/
@Override
public boolean mayBeHidBrailleDisplay(ConnectableDevice device) {
if (!BuildVersionUtils.isAtLeastV()
|| !FeatureFlagReader.isBdHidSupported(context)
|| !(device instanceof ConnectableBluetoothDevice)
|| ContextCompat.checkSelfPermission(context, Manifest.permission.BLUETOOTH_CONNECT)
!= PackageManager.PERMISSION_GRANTED) {
return false;
}
ParcelUuid[] uuids = ((ConnectableBluetoothDevice) device).bluetoothDevice().getUuids();
if (uuids == null) {
return false;
}
for (ParcelUuid uuid : uuids) {
String value = uuid.getUuid().toString();
if (value.equals(HID_UUID) || value.equals(HID_OVER_GATT_UUID)) {
return true;
}
}
return false;
}

@Override
public void sendOutgoingPacket(byte[] packet) {
BrailleDisplayLog.d(TAG, "sendOutgoingPacket");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,10 @@ struct BrailleDataStruct {
} text;

struct {
// The HID report ID used by both input and output. Currently expects both
// to be the same.
uint32_t reportId;
// The HID report IDs of the Braille input (keys) and output (cells). They
// can be the same report or different ones.
uint32_t inputReportId;
uint32_t outputReportId;
// The size of the HID input report.
uint32_t inputSizeBytes;
// Map each input report bit to the HID usage it represents.
Expand Down Expand Up @@ -74,9 +75,10 @@ static int probeHidDisplay(BrailleDisplay *brl, HidItemsDescriptor *items) {
// provided by the descriptor.
uint32_t reportId = 0;

// The current bit of the INPUT report, used for writing to the
// inputReportUsages map.
uint32_t inputReportBit = 0;
// The next bit of each INPUT report, by report ID, so that the Braille
// input report's usages are placed at the right bits whichever other
// reports come before and between its items.
uint32_t inputReportBits[256] = {0};

// These variables will be "learned" while parsing the report descriptor;
// set them to "unset" defaults so that parsing can detect if an inconsistency
Expand All @@ -87,8 +89,9 @@ static int probeHidDisplay(BrailleDisplay *brl, HidItemsDescriptor *items) {
// Stores the number of text rows so that brltty knows how to prepare
// output cells.
brl->textRows = 1;
// The report ID that will be providing Braille Display input and output.
brl->data->reportInfo.reportId = -1;
// The report IDs that will be providing Braille Display input and output.
brl->data->reportInfo.inputReportId = -1;
brl->data->reportInfo.outputReportId = -1;

const unsigned char *nextByte = items->bytes;
size_t bytesLeft = items->count;
Expand Down Expand Up @@ -136,29 +139,43 @@ static int probeHidDisplay(BrailleDisplay *brl, HidItemsDescriptor *items) {
item.tag == HID_ITM_Feature) {
// Reset the usage index now that we're going to process the usages array.
usageIndex = 0;
// Set the BD report ID to the current reportId.
if (usagePage == HID_UPG_Braille) {
if (brl->data->reportInfo.reportId != -1 &&
brl->data->reportInfo.reportId != reportId) {
logMessage(LOG_ERR,
"Found multiple report IDs that include Braille usages");
parsingError = 1;
break;
}
brl->data->reportInfo.reportId = reportId;
}
// Set the BD input and output report IDs to the current reportId. Input
// and output may use different reports, but each must use only one.
if (item.tag == HID_ITM_Input && usagePage == HID_UPG_Braille) {
if (brl->data->reportInfo.inputReportId != -1 &&
brl->data->reportInfo.inputReportId != reportId) {
logMessage(LOG_ERR, "Found multiple input report IDs that include "
"Braille usages");
parsingError = 1;
break;
}
brl->data->reportInfo.inputReportId = reportId;
}
if (item.tag == HID_ITM_Output && usagePage == HID_UPG_Braille) {
if (brl->data->reportInfo.outputReportId != -1 &&
brl->data->reportInfo.outputReportId != reportId) {
logMessage(LOG_ERR, "Found multiple output report IDs that include "
"Braille usages");
parsingError = 1;
break;
}
brl->data->reportInfo.outputReportId = reportId;
}

if (item.tag == HID_ITM_Input) {
if (reportId == brl->data->reportInfo.reportId) {
uint32_t *inputReportBit = &inputReportBits[reportId & 0XFF];
if (reportId != brl->data->reportInfo.inputReportId) {
*inputReportBit += reportSize * reportCount;
} else {
// Skip past constant bits
if ((item.value.u & HID_USG_FLG_CONSTANT) == HID_USG_FLG_CONSTANT) {
inputReportBit += reportSize * reportCount;
*inputReportBit += reportSize * reportCount;
continue;
}
// Skip past usages from unexpected pages.
if (usagePage != HID_UPG_Braille && usagePage != HID_UPG_Button) {
inputReportBit += reportSize * reportCount;
*inputReportBit += reportSize * reportCount;
continue;
}
// Ignore INPUT for the "Number of Braille Cells" usage.
Expand All @@ -168,7 +185,7 @@ static int probeHidDisplay(BrailleDisplay *brl, HidItemsDescriptor *items) {
// cells indicated by the OUTPUT report.
if (usagePage == HID_UPG_Braille &&
usages[0] == HID_USG_BRL_CellCount) {
inputReportBit += reportSize * reportCount;
*inputReportBit += reportSize * reportCount;
continue;
}
if ((item.value.u & HID_USG_FLG_VARIABLE) != HID_USG_FLG_VARIABLE) {
Expand All @@ -183,31 +200,25 @@ static int probeHidDisplay(BrailleDisplay *brl, HidItemsDescriptor *items) {
parsingError = 1;
break;
}
if (inputReportBit > MAX_INPUT_SIZE) {
if (*inputReportBit + reportCount > MAX_INPUT_SIZE) {
logMessage(LOG_ERR, "Unexpected input report with more than %u bits",
MAX_INPUT_SIZE);
parsingError = 1;
break;
}
for (int i = 0; i < reportCount; i++) {
if (usageMin != -1) {
brl->data->reportInfo.inputReportUsages[inputReportBit++] =
brl->data->reportInfo.inputReportUsages[(*inputReportBit)++] =
usageMin++;
} else {
brl->data->reportInfo.inputReportUsages[inputReportBit++] =
brl->data->reportInfo.inputReportUsages[(*inputReportBit)++] =
usages[i];
}
}
}
}
if (item.tag == HID_ITM_Output) {
if (usagePage == HID_UPG_Braille) {
if (reportId != brl->data->reportInfo.reportId) {
logMessage(LOG_ERR,
"Unexpected differing output and input report IDs");
parsingError = 1;
break;
}
if (reportSize != 8) {
logMessage(LOG_ERR, "Invalid output bit size %u", reportSize);
parsingError = 1;
Expand All @@ -234,7 +245,8 @@ static int probeHidDisplay(BrailleDisplay *brl, HidItemsDescriptor *items) {
logMessage(LOG_ERR, "There were parsing errors.");
return 0;
}
if (brl->data->reportInfo.reportId == -1) {
if (brl->data->reportInfo.inputReportId == -1 &&
brl->data->reportInfo.outputReportId == -1) {
logMessage(LOG_ERR, "Could not find a Braille Display report ID");
return 0;
}
Expand Down Expand Up @@ -267,8 +279,10 @@ static int probeHidDisplay(BrailleDisplay *brl, HidItemsDescriptor *items) {
}
}

int inputSizeBytes = (inputReportBit + 7) / 8;
int hasNumberedReport = brl->data->reportInfo.reportId != 0;
uint32_t inputReportId = brl->data->reportInfo.inputReportId;
int inputSizeBytes =
inputReportId == -1 ? 0 : (inputReportBits[inputReportId & 0XFF] + 7) / 8;
int hasNumberedReport = inputReportId != 0 && inputReportId != -1;
if (hasNumberedReport != 0) {
// The first byte of input should contain the report ID, then all other
// bytes should be parsed as input.
Expand Down Expand Up @@ -325,8 +339,8 @@ static int handleKeyRelease(BrailleDisplay *brl, unsigned char key) {
static void handlePressedKeysArray(BrailleDisplay *brl, unsigned char *keys) {
// Per HIDRAW spec if input descriptor report number is not zero then the
// first byte in the input should be the report number.
int hasNumberedReport = brl->data->reportInfo.reportId != 0;
if (hasNumberedReport && keys[0] != brl->data->reportInfo.reportId) {
int hasNumberedReport = brl->data->reportInfo.inputReportId != 0;
if (hasNumberedReport && keys[0] != brl->data->reportInfo.inputReportId) {
logMessage(LOG_WARNING, "Unexpected input report %u", keys[0]);
return;
}
Expand Down Expand Up @@ -366,7 +380,7 @@ static int writeHidCells(BrailleDisplay *brl, const unsigned char *cells,
// output report.
int bufferSize = cellCount + 1;
unsigned char buffer[bufferSize];
buffer[0] = brl->data->reportInfo.reportId;
buffer[0] = brl->data->reportInfo.outputReportId;
memcpy(buffer + 1, cells, cellCount);
return brl->gioEndpoint->handleMethods->writeData(
brl->gioEndpoint->handle, buffer, bufferSize, /*timeout=*/0);
Expand Down
6 changes: 6 additions & 0 deletions differences.md
Original file line number Diff line number Diff line change
Expand Up @@ -481,6 +481,12 @@ If you hold or stand your tablet facing you instead, turn off **Tablet held up f

A phone that's opened flat, and that hasn't been held up since the screen came on, follows the screen rotation, and with auto-rotate off expects the port on the left; turning it around on the table, or locking the orientation, corrects this. A phone held up screen away and laid flat without typing, or typed on screen away without ever being held within 40 degrees of upright, is taken to have faced you, so it expects the port on the wrong side; locking the orientation corrects this. Turns on the table require the game rotation vector sensor; devices without it still follow how they're held up. A tablet says where the port is only if it's a portrait tablet with the port at the bottom. Remembering how the device is held keeps the accelerometer running while the screen is on, as auto-rotate does.

## Braille displays

### Any braille display that uses HID

On Android 15 and later, TalkBack connects to braille displays that use the HID braille standard over Bluetooth, but only lists displays whose names it knows, so newer displays and braille terminals were left out. Backtalk lists any Bluetooth device that offers HID, whatever its name, and connecting checks that it's a braille display, so other HID devices, such as keyboards, can appear in the list too. TalkBack also refused displays that send their keys and receive their cells in separate HID reports, as the standard allows; Backtalk connects to those.

## Image and screen descriptions

### Gemini API key
Expand Down