2012年9月5日水曜日

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

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

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

が入ってます。

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

  1. /** 
  2.  * This fragment process Bluetooth connection. Host Activity have to implements 
  3.  * BluetoothCallback. 
  4.  *  
  5.  * @author yanzm 
  6.  */  
  7. public class BluetoothFragment extends Fragment {  
  8.   
  9.     public interface BluetoothCallback {  
  10.         /** 
  11.          * check whether device support Bluetooth. 
  12.          *  
  13.          * @param isAvailable 
  14.          *            true if device support Bluetooth. 
  15.          */  
  16.         public void onCheckAvailability(boolean isAvailable);  
  17.   
  18.         /** 
  19.          * called when bluetooth state be changed. 
  20.          *  
  21.          * @param enabled 
  22.          */  
  23.         public void onChangeBluetoothState(boolean enabled);  
  24.   
  25.         /** 
  26.          * called when try to connect. 
  27.          *  
  28.          * @param connected 
  29.          */  
  30.         public void onConnected(boolean connected);  
  31.   
  32.         /** 
  33.          * called when connection established 
  34.          *  
  35.          * @param in 
  36.          * @param out 
  37.          */  
  38.         public void onOpenConnection();  
  39.   
  40.         public void onDataArrived(int size, byte[] buffer);  
  41.     }  
  42.   
  43.     public static final String EXTRA_UUID = "uuid";  
  44.     public static final int SPP_MODE = 0;  
  45.     private static final UUID SPP_UUID = UUID.fromString("00001101-0000-1000-8000-00805F9B34FB");  
  46.   
  47.     private static final int REQUEST_ENABLE_BT = 11;  
  48.   
  49.     private BluetoothCallback mCallback;  
  50.     private BluetoothAdapter mBluetoothAdapter;  
  51.     private boolean mIsAvailable;  
  52.     private boolean mIsEnable;  
  53.     private UUID mUuid;  
  54.   
  55.     private ConnectThread mConnectThread;  
  56.     private TransmitThread mTransmitThread;  
  57.   
  58.     @Override  
  59.     public void onAttach(Activity activity) {  
  60.         super.onAttach(activity);  
  61.   
  62.         if (activity instanceof BluetoothCallback == false) {  
  63.             throw new ClassCastException("Activity have to implement BluetoothCallback");  
  64.         }  
  65.   
  66.         mCallback = (BluetoothCallback) activity;  
  67.     }  
  68.   
  69.     @Override  
  70.     public void onCreate(Bundle savedInstanceState) {  
  71.         super.onCreate(savedInstanceState);  
  72.     }  
  73.   
  74.     @Override  
  75.     public void onDestroy() {  
  76.         super.onDestroy();  
  77.         if (mConnectThread != null) {  
  78.             mConnectThread.cancel();  
  79.             mConnectThread = null;  
  80.         }  
  81.   
  82.         if (mTransmitThread != null) {  
  83.             mTransmitThread.cancel();  
  84.             mTransmitThread = null;  
  85.         }  
  86.     }  
  87.   
  88.     @Override  
  89.     public void onActivityCreated(Bundle savedInstanceState) {  
  90.         super.onActivityCreated(savedInstanceState);  
  91.   
  92.         Bundle args = getArguments();  
  93.         int sppMode = args == null ? 0 : args.getInt(EXTRA_UUID, 0);  
  94.         switch (sppMode) {  
  95.             case 0:  
  96.                 mUuid = SPP_UUID;  
  97.                 break;  
  98.             default:  
  99.                 mUuid = SPP_UUID;  
  100.                 break;  
  101.         }  
  102.   
  103.         // 1. Get the BluetoothAdapter  
  104.         mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();  
  105.         mIsAvailable = mBluetoothAdapter != null;  
  106.   
  107.         mCallback.onCheckAvailability(mIsAvailable);  
  108.   
  109.         // 2. Enable Bluetooth  
  110.         if (mBluetoothAdapter.isEnabled()) {  
  111.             mIsEnable = true;  
  112.             onEnabled();  
  113.         } else {  
  114.             mIsEnable = false;  
  115.             Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);  
  116.             startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT);  
  117.         }  
  118.         mCallback.onChangeBluetoothState(mIsEnable);  
  119.     }  
  120.   
  121.     BroadcastReceiver mStateReceiver = new BroadcastReceiver() {  
  122.   
  123.         @Override  
  124.         public void onReceive(Context context, Intent intent) {  
  125.             int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.STATE_OFF);  
  126.             if (state == BluetoothAdapter.STATE_ON) {  
  127.                 mIsEnable = true;  
  128.                 onEnabled();  
  129.             } else {  
  130.                 mIsEnable = false;  
  131.             }  
  132.             mCallback.onChangeBluetoothState(mIsEnable);  
  133.         }  
  134.     };  
  135.   
  136.     @Override  
  137.     public void onResume() {  
  138.         super.onResume();  
  139.         IntentFilter filter = new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED);  
  140.         getActivity().registerReceiver(mStateReceiver, filter);  
  141.     }  
  142.   
  143.     @Override  
  144.     public void onPause() {  
  145.         super.onPause();  
  146.         getActivity().unregisterReceiver(mStateReceiver);  
  147.         if (mBluetoothAdapter.isDiscovering()) {  
  148.             stopDiscovery();  
  149.         }  
  150.     }  
  151.   
  152.     @Override  
  153.     public void onActivityResult(int requestCode, int resultCode, Intent data) {  
  154.         super.onActivityResult(requestCode, resultCode, data);  
  155.   
  156.         if (requestCode == REQUEST_ENABLE_BT) {  
  157.             if (resultCode == Activity.RESULT_OK) {  
  158.                 mIsEnable = true;  
  159.                 onEnabled();  
  160.             } else {  
  161.                 mIsEnable = false;  
  162.             }  
  163.             mCallback.onChangeBluetoothState(mIsEnable);  
  164.         }  
  165.     }  
  166.   
  167.     private void onEnabled() {  
  168.         selectPairedDevice();  
  169.     }  
  170.   
  171.     public void selectPairedDevice() {  
  172.         // 3. Querying paired devices  
  173.         Set<BluetoothDevice> pairedDevices = mBluetoothAdapter.getBondedDevices();  
  174.   
  175.         if (pairedDevices.size() > 0) {  
  176.             ArrayList<BluetoothDevice> deviceList = new ArrayList<BluetoothDevice>();  
  177.             for (BluetoothDevice device : pairedDevices) {  
  178.                 deviceList.add(device);  
  179.             }  
  180.             final BluetoothDeviceAdapter adapter = new BluetoothDeviceAdapter(getActivity(), deviceList);  
  181.   
  182.             AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());  
  183.             builder.setTitle("Paired devices");  
  184.             builder.setAdapter(adapter, new DialogInterface.OnClickListener() {  
  185.   
  186.                 public void onClick(DialogInterface dialog, int position) {  
  187.                     BluetoothDevice selectedDevice = adapter.getItem(position);  
  188.                     connectToDevice(selectedDevice);  
  189.                 }  
  190.             });  
  191.             builder.setNegativeButton(android.R.string.cancel, null);  
  192.   
  193.             Context context = getActivity();  
  194.   
  195.             PackageManager manager = context.getPackageManager();  
  196.             int permission = manager.checkPermission(Manifest.permission.BLUETOOTH_ADMIN, context.getPackageName());  
  197.   
  198.             if (permission == PackageManager.PERMISSION_GRANTED) {  
  199.                 builder.setPositiveButton("Search for devices"new DialogInterface.OnClickListener() {  
  200.   
  201.                     public void onClick(DialogInterface dialog, int which) {  
  202.                         discoveringDevices();  
  203.                     }  
  204.                 });  
  205.             }  
  206.             builder.show();  
  207.         }  
  208.     }  
  209.   
  210.     BluetoothDeviceAdapter mDiscoveringAdapter;  
  211.   
  212.     BroadcastReceiver mDiscoveringReceiver = new BroadcastReceiver() {  
  213.   
  214.         @Override  
  215.         public void onReceive(Context context, Intent intent) {  
  216.             BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);  
  217.             mDiscoveringAdapter.add(device);  
  218.         }  
  219.     };  
  220.   
  221.     private void discoveringDevices() {  
  222.         // 4. discovering devices  
  223.         ArrayList<BluetoothDevice> deviceList = new ArrayList<BluetoothDevice>();  
  224.         mDiscoveringAdapter = new BluetoothDeviceAdapter(getActivity(), deviceList);  
  225.   
  226.         AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());  
  227.         builder.setAdapter(mDiscoveringAdapter, new DialogInterface.OnClickListener() {  
  228.   
  229.             public void onClick(DialogInterface dialog, int position) {  
  230.                 stopDiscovery();  
  231.   
  232.                 BluetoothDevice selectedDevice = mDiscoveringAdapter.getItem(position);  
  233.                 connectToDevice(selectedDevice);  
  234.             }  
  235.         });  
  236.         builder.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {  
  237.   
  238.             public void onClick(DialogInterface dialog, int which) {  
  239.                 stopDiscovery();  
  240.             }  
  241.         });  
  242.         builder.show();  
  243.   
  244.         startDiscovery();  
  245.     }  
  246.   
  247.     private void startDiscovery() {  
  248.         IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_FOUND);  
  249.         getActivity().registerReceiver(mDiscoveringReceiver, filter);  
  250.         mBluetoothAdapter.startDiscovery();  
  251.     }  
  252.   
  253.     private void stopDiscovery() {  
  254.         mBluetoothAdapter.cancelDiscovery();  
  255.         getActivity().unregisterReceiver(mDiscoveringReceiver);  
  256.     }  
  257.   
  258.     private void connectToDevice(BluetoothDevice device) {  
  259.         mConnectThread = new ConnectThread(device);  
  260.         mConnectThread.start();  
  261.     }  
  262.   
  263.     private class ConnectThread extends Thread {  
  264.         private final BluetoothSocket mSocket;  
  265.   
  266.         public ConnectThread(BluetoothDevice device) {  
  267.             BluetoothSocket tmp = null;  
  268.             try {  
  269.                 tmp = device.createRfcommSocketToServiceRecord(mUuid);  
  270.             } catch (IOException e) {  
  271.             }  
  272.             mSocket = tmp;  
  273.         }  
  274.   
  275.         public void run() {  
  276.             if (mBluetoothAdapter.isDiscovering()) {  
  277.                 mBluetoothAdapter.cancelDiscovery();  
  278.             }  
  279.   
  280.             try {  
  281.                 mSocket.connect();  
  282.   
  283.                 Message msg = mHandler.obtainMessage(MESSAGE_CONNECTED, true);  
  284.                 mHandler.sendMessage(msg);  
  285.   
  286.             } catch (IOException connectException) {  
  287.                 try {  
  288.                     mSocket.close();  
  289.                 } catch (IOException closeException) {  
  290.                 }  
  291.   
  292.                 Message msg = mHandler.obtainMessage(MESSAGE_CONNECTED, false);  
  293.                 mHandler.sendMessage(msg);  
  294.                 return;  
  295.             }  
  296.   
  297.             manageConnectedSocket(mSocket);  
  298.         }  
  299.   
  300.         public void cancel() {  
  301.             try {  
  302.                 mSocket.close();  
  303.             } catch (IOException e) {  
  304.             }  
  305.         }  
  306.     }  
  307.   
  308.     private void manageConnectedSocket(BluetoothSocket socket) {  
  309.         mTransmitThread = new TransmitThread(socket);  
  310.         mTransmitThread.start();  
  311.     }  
  312.   
  313.     private static final int MESSAGE_READ = 1;  
  314.     private static final int MESSAGE_CONNECTED = 2;  
  315.     private static final int MESSAGE_OPEN = 3;  
  316.   
  317.     final Handler mHandler = new Handler() {  
  318.   
  319.         @Override  
  320.         public void handleMessage(Message msg) {  
  321.             int what = msg.what;  
  322.             switch (what) {  
  323.                 case MESSAGE_READ:  
  324.                     int length = msg.arg1;  
  325.                     byte[] buffer = (byte[]) msg.obj;  
  326.                     mCallback.onDataArrived(length, buffer);  
  327.                     break;  
  328.   
  329.                 case MESSAGE_CONNECTED:  
  330.                     boolean connected = (Boolean) msg.obj;  
  331.                     mCallback.onConnected(connected);  
  332.                     break;  
  333.   
  334.                 case MESSAGE_OPEN:  
  335.                     mCallback.onOpenConnection();  
  336.                     break;  
  337.             }  
  338.         }  
  339.     };  
  340.   
  341.     private class TransmitThread extends Thread {  
  342.         private final BluetoothSocket mSocket;  
  343.         private final InputStream mInStream;  
  344.         private final OutputStream mOutStream;  
  345.   
  346.         public TransmitThread(BluetoothSocket socket) {  
  347.             mSocket = socket;  
  348.   
  349.             InputStream tmpIn = null;  
  350.             OutputStream tmpOut = null;  
  351.             try {  
  352.                 tmpIn = socket.getInputStream();  
  353.                 tmpOut = socket.getOutputStream();  
  354.   
  355.                 Message msg = mHandler.obtainMessage(MESSAGE_OPEN);  
  356.                 mHandler.sendMessage(msg);  
  357.   
  358.             } catch (IOException e) {  
  359.             }  
  360.   
  361.             mInStream = tmpIn;  
  362.             mOutStream = tmpOut;  
  363.         }  
  364.   
  365.         public void run() {  
  366.             byte[] buffer = new byte[1024];  
  367.             int length;  
  368.   
  369.             while (true) {  
  370.                 try {  
  371.                     length = mInStream.read(buffer);  
  372.   
  373.                     Message msg = mHandler.obtainMessage(MESSAGE_READ, length, -1, buffer);  
  374.                     mHandler.sendMessage(msg);  
  375.   
  376.                 } catch (IOException e) {  
  377.                     break;  
  378.                 }  
  379.             }  
  380.         }  
  381.   
  382.         public boolean write(byte[] bytes) {  
  383.             if (mOutStream == null) {  
  384.                 return false;  
  385.             }  
  386.   
  387.             StringBuilder sb = new StringBuilder();  
  388.             for (int i = 0; i < bytes.length; i++) {  
  389.                 sb.append(bytes[i] + " ");  
  390.             }  
  391.   
  392.             try {  
  393.                 mOutStream.write(bytes);  
  394.                 return true;  
  395.             } catch (IOException e) {  
  396.             }  
  397.   
  398.             return false;  
  399.         }  
  400.   
  401.         public void cancel() {  
  402.             try {  
  403.                 mSocket.close();  
  404.             } catch (IOException e) {  
  405.             }  
  406.             try {  
  407.                 mInStream.close();  
  408.             } catch (IOException e) {  
  409.             }  
  410.             try {  
  411.                 mOutStream.close();  
  412.             } catch (IOException e) {  
  413.             }  
  414.         }  
  415.     }  
  416.   
  417.     public boolean write(int i) {  
  418.         byte[] bytes = new byte[4];  
  419.         bytes[0] = (byte) (i);  
  420.         bytes[1] = (byte) (i >> 8);  
  421.         bytes[2] = (byte) (i >> 16);  
  422.         bytes[3] = (byte) (i >> 24);  
  423.   
  424.         return write(bytes);  
  425.     }  
  426.   
  427.     public boolean write(String s) {  
  428.         return write(s.getBytes());  
  429.     }  
  430.   
  431.     public boolean write(byte[] bytes) {  
  432.         if (mTransmitThread == null) {  
  433.             return false;  
  434.         }  
  435.   
  436.         return mTransmitThread.write(bytes);  
  437.     }  
  438.   
  439.     public static class BluetoothDeviceAdapter extends ArrayAdapter<BluetoothDevice> {  
  440.   
  441.         public BluetoothDeviceAdapter(Context context, List<BluetoothDevice> objects) {  
  442.             super(context, 0, objects);  
  443.         }  
  444.   
  445.         @Override  
  446.         public View getView(int position, View convertView, ViewGroup parent) {  
  447.             if (convertView == null) {  
  448.                 LayoutInflater inflater = LayoutInflater.from(getContext());  
  449.                 convertView = inflater.inflate(android.R.layout.simple_list_item_2, parent, false);  
  450.             }  
  451.   
  452.             BluetoothDevice device = getItem(position);  
  453.   
  454.             TextView nameView = (TextView) convertView.findViewById(android.R.id.text1);  
  455.             nameView.setText(device.getName());  
  456.               
  457.             TextView addressView = (TextView) convertView.findViewById(android.R.id.text2);  
  458.             addressView.setText(device.getAddress());  
  459.   
  460.             return convertView;  
  461.         }  
  462.     }  
  463. }  


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

  1. public class MainActivity extends FragmentActivity implements BluetoothCallback {  
  2.   
  3.     private static final String TAG = "MainActivity";  
  4.       
  5.     BluetoothFragment mBluetoothFragment;  
  6.   
  7.     View mButton;  
  8.   
  9.     @Override  
  10.     public void onCreate(Bundle savedInstanceState) {  
  11.         super.onCreate(savedInstanceState);  
  12.         setContentView(R.layout.main);  
  13.   
  14.         Bundle args = new Bundle();  
  15.         args.putInt(BluetoothFragment.EXTRA_UUID, BluetoothFragment.SPP_MODE);  
  16.   
  17.         if (mBluetoothFragment == null) {  
  18.             mBluetoothFragment = new BluetoothFragment();  
  19.             getSupportFragmentManager().beginTransaction().add(mBluetoothFragment, "BluetoothFragment").commit();  
  20.         }  
  21.   
  22.         mButton = findViewById(R.id.button1);  
  23.         mButton.setOnClickListener(new View.OnClickListener() {  
  24.   
  25.             public void onClick(View v) {  
  26.                 write(new byte[] { 0x500x4d0x03 });  
  27.             }  
  28.         });  
  29.         mButton.setEnabled(false);  
  30.     }  
  31.   
  32.     public void onCheckAvailability(boolean isAvailable) {  
  33.         String text = "onCheckAvailability : " + isAvailable;  
  34.         Log.d(TAG, text);  
  35.         showToast(text);  
  36.     }  
  37.   
  38.     public void onChangeBluetoothState(boolean enabled) {  
  39.         String text = "onChangeBluetoothState : " + enabled;  
  40.         Log.d(TAG, text);  
  41.         showToast(text);  
  42.     }  
  43.   
  44.     public void onConnected(boolean connected) {  
  45.         String text = "onConnected : " + connected;  
  46.         Log.d(TAG, text);  
  47.         showToast(text);  
  48.     }  
  49.   
  50.     public void onOpenConnection() {  
  51.         mButton.setEnabled(true);  
  52.         Log.d(TAG, "onOpenConnection");  
  53.         showToast("onOpenConnection");  
  54.     }  
  55.   
  56.     public void onDataArrived(int length, byte[] buffer) {  
  57.         StringBuilder sb = new StringBuilder();  
  58.         for(int i = 0; i < length; i++) {  
  59.             sb.append(buffer[i] + " ");  
  60.         }  
  61.         String text = "onDataArrived : " + length + ", " + sb.toString();  
  62.         Log.d(TAG, text);  
  63.         showToast(text);  
  64.     }  
  65.   
  66.     private void write(byte[] bytes) {  
  67.         if (mBluetoothFragment != null) {  
  68.             mBluetoothFragment.write(bytes);  
  69.         }  
  70.     }  
  71.   
  72.     private void showToast(String text) {  
  73.         Toast.makeText(this, text, Toast.LENGTH_SHORT).show();  
  74.     }  
  75. }  

1 件のコメント:

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

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

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

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

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

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

    返信削除