/* * Copyright (C) 2015 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of * the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations under * the License. */ package com.googlecode.eyesfree.braille.service.display; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothSocket; import android.content.res.Resources; import android.util.Log; import com.googlecode.eyesfree.braille.display.BrailleDisplayProperties; import com.googlecode.eyesfree.braille.service.R; import java.io.File; import java.io.IOException; import java.util.List; /** * The thread that connects to a device that it finds, reads from the device * and starts another thread that manages the driver. */ class ReadThread extends Thread implements DriverThread.OnInitListener { private static final String LOG_TAG = ReadThread.class.getSimpleName(); private final DisplayService mDisplayService; private final DeviceFinder mDeviceFinder; private final File mTablesDir; private final String mBluetoothAddressToConnectTo; private final Resources mResources; private volatile BluetoothSocket mSocket; private volatile boolean mDisconnecting; private volatile DriverThread mDriverThread; private volatile DeviceFinder.DeviceInfo mConnectedDeviceInfo; /** * Constructs the thread so that it can be started. * {@code displayService} will get called when the display is either * connected or disconnected. {@code tablesDir} is a directory that * contains keyboard tables and is forwarded to the driver thread. * {@code bluetoothAddressToConnectTo}, if non-null, makes the connection * process only consider a device with the give bluetooth address. */ public ReadThread(DisplayService displayService, File tablesDir, String bluetoothAddressToConnectTo) { mDisplayService = displayService; mDeviceFinder = new DeviceFinder(displayService); mTablesDir = tablesDir; mBluetoothAddressToConnectTo = bluetoothAddressToConnectTo; mResources = displayService.getResources(); } @Override public void run() { try { if (connect()) { readLoop(); } } finally { cleanup(); } } /** Returns the driver thread, or null if not connected. */ public DriverThread getDriverThread() { return mDriverThread; } /** * Asynchronously disconnects, which will eventually lead to the * thread terminating. If not connected yet, the attempts to connect * to a display will be aborted. Calls back into * {@link DisplayService#onDisplayDisconnected} before dying. */ public void disconnect() { // Close the socket to abort any blocking operations. closeSocket(); mDisconnecting = true; } private boolean connect() { List<DeviceFinder.DeviceInfo> bonded = mDeviceFinder.findDevices(); if (bonded.size() > 0) { tryToConnect(bonded); } else { mDisplayService.setConnectionProgress( mResources.getString(R.string.connprog_no_devices)); } if (mSocket != null) { BluetoothDevice bthDev = mConnectedDeviceInfo.getBluetoothDevice(); mDisplayService.setConnectionProgress( mResources.getString(R.string.connprog_initializing, bthDev.getName())); try { mDriverThread = new DriverThread(mSocket.getOutputStream(), mConnectedDeviceInfo, mResources, mTablesDir, this /*initListener*/, mDisplayService /*inputEventListener*/); Log.i(LOG_TAG, "Device connected"); return true; } catch (IOException ex) { Log.e(LOG_TAG, "Error while starting driver thread", ex); } } return false; } private void readLoop() { try { byte[] buf = new byte[128]; int readSize; do { readSize = mSocket.getInputStream().read(buf, 0, buf.length); if (readSize > 0) { // Enqueue the input which will eventually wake up the // driver to poll for this data. mDriverThread.addReadOperation(buf, readSize); } } while (readSize >= 0); Log.i(LOG_TAG, "End of input from device."); } catch (IOException ex) { Log.i(LOG_TAG, "Socket closed while reading: " + ex); } } private void tryToConnect(List<DeviceFinder.DeviceInfo> bonded) { mSocket = null; try { for (DeviceFinder.DeviceInfo dev : bonded) { if (mDisconnecting) { return; } BluetoothDevice bthDev = dev.getBluetoothDevice(); if (mBluetoothAddressToConnectTo != null && !mBluetoothAddressToConnectTo.equals( bthDev.getAddress())) { continue; } mDisplayService.setConnectionProgress( mResources.getString(R.string.connprog_trying, bthDev.getName())); Log.d(LOG_TAG, "Trying to connect to braille device: " + bthDev.getName()); try { BluetoothSocket socket; if (dev.getConnectSecurely()) { socket = bthDev .createRfcommSocketToServiceRecord( dev.getSdpUuid()); } else { socket = bthDev .createInsecureRfcommSocketToServiceRecord( dev.getSdpUuid()); } if (socket != null) { socket.connect(); mSocket = socket; mConnectedDeviceInfo = dev; } return; } catch (IOException ex) { Log.e(LOG_TAG, "Error opening a socket: " + ex.toString()); } } } finally { if (mSocket == null) { mDisplayService.setConnectionProgress(null); } } } private void closeSocket() { // More than one calls of this function is allowed, even in paralell // because close on the socket is idempotent. if (mSocket != null) { try { mSocket.close(); } catch (IOException ex) { Log.d(LOG_TAG, "Error closing socket: ", ex); } } } private void cleanup() { closeSocket(); if (mDriverThread != null) { DriverThread localDriverThread = mDriverThread; mDriverThread = null; localDriverThread.stop(); } mDisplayService.onDisplayDisconnected(); Log.i(LOG_TAG, "Display disconnected"); } @Override public void onInit(BrailleDisplayProperties properties) { // We're in the driver thread. if (properties != null) { mDeviceFinder.rememberSuccessfulConnection(mConnectedDeviceInfo); mDisplayService.setConnectionProgress(null); mDisplayService.onDisplayConnected(properties); } else { BluetoothDevice bthDev = mConnectedDeviceInfo.getBluetoothDevice(); mDisplayService.setConnectionProgress( mResources.getString( R.string.connprog_failed_to_initialize, bthDev.getName())); disconnect(); return; } } }