2012年9月5日水曜日

Android Bluetooth でシリアル通信(SPP)する Fragment 書いた

そのうち GitHub におくかもしれない。

・Bluetooth が利用可能かチェック
・Bluetooth が有効になっていない場合は有効にするダイアログを表示
・有効になったらペアリング済みの一覧ダイアログを表示
・BLUETOOTH_ADMIN permission がある場合は、一覧ダイアログにデバイス検索開始のボタンを表示
・バックグラウンドでの通信(読み書き)

が入ってます。

・画面回転考慮してません
・デバイス検索中のクルクルをつけてません
(そのうちやる、と、思う、たぶん)

/** * This fragment process Bluetooth connection. Host Activity have to implements * BluetoothCallback. * * @author yanzm */ public class BluetoothFragment extends Fragment { public interface BluetoothCallback { /** * check whether device support Bluetooth. * * @param isAvailable * true if device support Bluetooth. */ public void onCheckAvailability(boolean isAvailable); /** * called when bluetooth state be changed. * * @param enabled */ public void onChangeBluetoothState(boolean enabled); /** * called when try to connect. * * @param connected */ public void onConnected(boolean connected); /** * called when connection established * * @param in * @param out */ public void onOpenConnection(); public void onDataArrived(int size, byte[] buffer); } public static final String EXTRA_UUID = "uuid"; public static final int SPP_MODE = 0; private static final UUID SPP_UUID = UUID.fromString("00001101-0000-1000-8000-00805F9B34FB"); private static final int REQUEST_ENABLE_BT = 11; private BluetoothCallback mCallback; private BluetoothAdapter mBluetoothAdapter; private boolean mIsAvailable; private boolean mIsEnable; private UUID mUuid; private ConnectThread mConnectThread; private TransmitThread mTransmitThread; @Override public void onAttach(Activity activity) { super.onAttach(activity); if (activity instanceof BluetoothCallback == false) { throw new ClassCastException("Activity have to implement BluetoothCallback"); } mCallback = (BluetoothCallback) activity; } @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); } @Override public void onDestroy() { super.onDestroy(); if (mConnectThread != null) { mConnectThread.cancel(); mConnectThread = null; } if (mTransmitThread != null) { mTransmitThread.cancel(); mTransmitThread = null; } } @Override public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); Bundle args = getArguments(); int sppMode = args == null ? 0 : args.getInt(EXTRA_UUID, 0); switch (sppMode) { case 0: mUuid = SPP_UUID; break; default: mUuid = SPP_UUID; break; } // 1. Get the BluetoothAdapter mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); mIsAvailable = mBluetoothAdapter != null; mCallback.onCheckAvailability(mIsAvailable); // 2. Enable Bluetooth if (mBluetoothAdapter.isEnabled()) { mIsEnable = true; onEnabled(); } else { mIsEnable = false; Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE); startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT); } mCallback.onChangeBluetoothState(mIsEnable); } BroadcastReceiver mStateReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.STATE_OFF); if (state == BluetoothAdapter.STATE_ON) { mIsEnable = true; onEnabled(); } else { mIsEnable = false; } mCallback.onChangeBluetoothState(mIsEnable); } }; @Override public void onResume() { super.onResume(); IntentFilter filter = new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED); getActivity().registerReceiver(mStateReceiver, filter); } @Override public void onPause() { super.onPause(); getActivity().unregisterReceiver(mStateReceiver); if (mBluetoothAdapter.isDiscovering()) { stopDiscovery(); } } @Override public void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); if (requestCode == REQUEST_ENABLE_BT) { if (resultCode == Activity.RESULT_OK) { mIsEnable = true; onEnabled(); } else { mIsEnable = false; } mCallback.onChangeBluetoothState(mIsEnable); } } private void onEnabled() { selectPairedDevice(); } public void selectPairedDevice() { // 3. Querying paired devices Set<BluetoothDevice> pairedDevices = mBluetoothAdapter.getBondedDevices(); if (pairedDevices.size() > 0) { ArrayList<BluetoothDevice> deviceList = new ArrayList<BluetoothDevice>(); for (BluetoothDevice device : pairedDevices) { deviceList.add(device); } final BluetoothDeviceAdapter adapter = new BluetoothDeviceAdapter(getActivity(), deviceList); AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); builder.setTitle("Paired devices"); builder.setAdapter(adapter, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int position) { BluetoothDevice selectedDevice = adapter.getItem(position); connectToDevice(selectedDevice); } }); builder.setNegativeButton(android.R.string.cancel, null); Context context = getActivity(); PackageManager manager = context.getPackageManager(); int permission = manager.checkPermission(Manifest.permission.BLUETOOTH_ADMIN, context.getPackageName()); if (permission == PackageManager.PERMISSION_GRANTED) { builder.setPositiveButton("Search for devices", new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int which) { discoveringDevices(); } }); } builder.show(); } } BluetoothDeviceAdapter mDiscoveringAdapter; BroadcastReceiver mDiscoveringReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); mDiscoveringAdapter.add(device); } }; private void discoveringDevices() { // 4. discovering devices ArrayList<BluetoothDevice> deviceList = new ArrayList<BluetoothDevice>(); mDiscoveringAdapter = new BluetoothDeviceAdapter(getActivity(), deviceList); AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); builder.setAdapter(mDiscoveringAdapter, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int position) { stopDiscovery(); BluetoothDevice selectedDevice = mDiscoveringAdapter.getItem(position); connectToDevice(selectedDevice); } }); builder.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int which) { stopDiscovery(); } }); builder.show(); startDiscovery(); } private void startDiscovery() { IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_FOUND); getActivity().registerReceiver(mDiscoveringReceiver, filter); mBluetoothAdapter.startDiscovery(); } private void stopDiscovery() { mBluetoothAdapter.cancelDiscovery(); getActivity().unregisterReceiver(mDiscoveringReceiver); } private void connectToDevice(BluetoothDevice device) { mConnectThread = new ConnectThread(device); mConnectThread.start(); } private class ConnectThread extends Thread { private final BluetoothSocket mSocket; public ConnectThread(BluetoothDevice device) { BluetoothSocket tmp = null; try { tmp = device.createRfcommSocketToServiceRecord(mUuid); } catch (IOException e) { } mSocket = tmp; } public void run() { if (mBluetoothAdapter.isDiscovering()) { mBluetoothAdapter.cancelDiscovery(); } try { mSocket.connect(); Message msg = mHandler.obtainMessage(MESSAGE_CONNECTED, true); mHandler.sendMessage(msg); } catch (IOException connectException) { try { mSocket.close(); } catch (IOException closeException) { } Message msg = mHandler.obtainMessage(MESSAGE_CONNECTED, false); mHandler.sendMessage(msg); return; } manageConnectedSocket(mSocket); } public void cancel() { try { mSocket.close(); } catch (IOException e) { } } } private void manageConnectedSocket(BluetoothSocket socket) { mTransmitThread = new TransmitThread(socket); mTransmitThread.start(); } private static final int MESSAGE_READ = 1; private static final int MESSAGE_CONNECTED = 2; private static final int MESSAGE_OPEN = 3; final Handler mHandler = new Handler() { @Override public void handleMessage(Message msg) { int what = msg.what; switch (what) { case MESSAGE_READ: int length = msg.arg1; byte[] buffer = (byte[]) msg.obj; mCallback.onDataArrived(length, buffer); break; case MESSAGE_CONNECTED: boolean connected = (Boolean) msg.obj; mCallback.onConnected(connected); break; case MESSAGE_OPEN: mCallback.onOpenConnection(); break; } } }; private class TransmitThread extends Thread { private final BluetoothSocket mSocket; private final InputStream mInStream; private final OutputStream mOutStream; public TransmitThread(BluetoothSocket socket) { mSocket = socket; InputStream tmpIn = null; OutputStream tmpOut = null; try { tmpIn = socket.getInputStream(); tmpOut = socket.getOutputStream(); Message msg = mHandler.obtainMessage(MESSAGE_OPEN); mHandler.sendMessage(msg); } catch (IOException e) { } mInStream = tmpIn; mOutStream = tmpOut; } public void run() { byte[] buffer = new byte[1024]; int length; while (true) { try { length = mInStream.read(buffer); Message msg = mHandler.obtainMessage(MESSAGE_READ, length, -1, buffer); mHandler.sendMessage(msg); } catch (IOException e) { break; } } } public boolean write(byte[] bytes) { if (mOutStream == null) { return false; } StringBuilder sb = new StringBuilder(); for (int i = 0; i < bytes.length; i++) { sb.append(bytes[i] + " "); } try { mOutStream.write(bytes); return true; } catch (IOException e) { } return false; } public void cancel() { try { mSocket.close(); } catch (IOException e) { } try { mInStream.close(); } catch (IOException e) { } try { mOutStream.close(); } catch (IOException e) { } } } public boolean write(int i) { byte[] bytes = new byte[4]; bytes[0] = (byte) (i); bytes[1] = (byte) (i >> 8); bytes[2] = (byte) (i >> 16); bytes[3] = (byte) (i >> 24); return write(bytes); } public boolean write(String s) { return write(s.getBytes()); } public boolean write(byte[] bytes) { if (mTransmitThread == null) { return false; } return mTransmitThread.write(bytes); } public static class BluetoothDeviceAdapter extends ArrayAdapter<BluetoothDevice> { public BluetoothDeviceAdapter(Context context, List<BluetoothDevice> objects) { super(context, 0, objects); } @Override public View getView(int position, View convertView, ViewGroup parent) { if (convertView == null) { LayoutInflater inflater = LayoutInflater.from(getContext()); convertView = inflater.inflate(android.R.layout.simple_list_item_2, parent, false); } BluetoothDevice device = getItem(position); TextView nameView = (TextView) convertView.findViewById(android.R.id.text1); nameView.setText(device.getName()); TextView addressView = (TextView) convertView.findViewById(android.R.id.text2); addressView.setText(device.getAddress()); return convertView; } } }

こんな感じで Activity には BluetoothCallback を implements して利用します。

public class MainActivity extends FragmentActivity implements BluetoothCallback { private static final String TAG = "MainActivity"; BluetoothFragment mBluetoothFragment; View mButton; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); Bundle args = new Bundle(); args.putInt(BluetoothFragment.EXTRA_UUID, BluetoothFragment.SPP_MODE); if (mBluetoothFragment == null) { mBluetoothFragment = new BluetoothFragment(); getSupportFragmentManager().beginTransaction().add(mBluetoothFragment, "BluetoothFragment").commit(); } mButton = findViewById(R.id.button1); mButton.setOnClickListener(new View.OnClickListener() { public void onClick(View v) { write(new byte[] { 0x50, 0x4d, 0x03 }); } }); mButton.setEnabled(false); } public void onCheckAvailability(boolean isAvailable) { String text = "onCheckAvailability : " + isAvailable; Log.d(TAG, text); showToast(text); } public void onChangeBluetoothState(boolean enabled) { String text = "onChangeBluetoothState : " + enabled; Log.d(TAG, text); showToast(text); } public void onConnected(boolean connected) { String text = "onConnected : " + connected; Log.d(TAG, text); showToast(text); } public void onOpenConnection() { mButton.setEnabled(true); Log.d(TAG, "onOpenConnection"); showToast("onOpenConnection"); } public void onDataArrived(int length, byte[] buffer) { StringBuilder sb = new StringBuilder(); for(int i = 0; i < length; i++) { sb.append(buffer[i] + " "); } String text = "onDataArrived : " + length + ", " + sb.toString(); Log.d(TAG, text); showToast(text); } private void write(byte[] bytes) { if (mBluetoothFragment != null) { mBluetoothFragment.write(bytes); } } private void showToast(String text) { Toast.makeText(this, text, Toast.LENGTH_SHORT).show(); } }

1 件のコメント:

  1. 初心者の幸太郎と申します。
    ネット上に数多くのソースが公開されていますが、結局yanzmさんのソースが一番わかりやすく綺麗なソースなのでとても参考になります。

    今回はBluetoothをやろうと思っているのですが、公開されているソースでいくつか疑問があります。
    もし可能でしたらご解答頂けますと助かります。
    まだまだ勉強不足で、チンプンカンプンな質問をしてしまうかもしれませんが、お許しください。

    1)公開ソースで動かしてみました。
    スマホ<->タブレット 間で接続を試みました。
    対象機器は表示されたのですが、接続ができませんでした。
    (onConnected:falseです)
    ソースを追っていくと、他のサイトでは、サーバー側listenUsingRfcommWithServiceRecord()、
    クライアント側createRfcommSocketToServiceRecord()
    を使ってサーバー<->クライアント間を通信しているような作りが
    多いように思えますが、
    こちらのソースでは、createRfcommSocketToServiceRecord()のみ。
    ということはもしかしてクライアント用ソースでしょうか?
    もしそうだとしますと、サーバー側のソースは公開される予定はありますでしょうか?

    2)レイアウトxmlの公開はして頂けないのでしょうか?

    3)Androidを勉強するにあたって参考になる書籍やブログ(英語はよめません)がありましたら、教えてください。

    以上になります。
    お忙しい所恐縮ですが、よろしくお願いいたします。

    返信削除