次の方法で共有


ユニバーサル アプリ

アプリに OBEX を装備する

Uday Gupta

コード サンプルのダウンロード

この 10 年間で、携帯電話、PC、ヘッドセットなどのデバイス間の近距離ワイヤレス通信技術として、 Bluetooth が広く使われるようになっています。Buluetooth Special Interest Group (BTSIG) は、Bluetooth プロファイル仕様のワイヤレス サービスに関する標準を定める業界団体です。

こうしたプロファイルの 1 つが Object Push Profile (OPP) で、デバイス間のファイルのやり取りに使われます。この OPP の基礎の一部となるのがオブジェクト交換プロトコル (OBEX: Object Exchange Protocol) です。OBEX は OPP 以外のプロファイルでも使われ、デバイス間でオブジェクトを転送する汎用プロトコルです。

今回は、アプリで OBEX を使おうと考える開発者向けに、Windows ランタイム (WinRT) の Bluetooth API を基盤に、アプリ内部から OBEX を提供する一連の API を開発します。この一連の API はユニバーサル アプリ用のライブラリ パッケージになるため、Windows Store アプリや Windows Phone Silverlight アプリにも同じ API を活用できます。

OBEX と OPP

最初に、OBEX と OPP の概要としくみを理解しておくことが大切です。OPP は、ある Bluetooth デバイスから別の OPP 対応の Bluetooth デバイスに、ファイルまたはオブジェクトを送信できるようにします。OBEX 本来の用途は赤外チャネル経由のファイル共有でしたが、BTSIG はこのプロトコルを Bluethooth 経由のファイル共有に再利用しました。基盤となるトランスポート メディアを除き、赤外チャネル経由の OBEX と Bluetooth 経由の OBEX はほぼ同じです。

OBEX はクライアント/サーバー モデルが基盤です。つまり、受信側の Bluetooth デバイスが OBEX サーバーを実行し、クライアント接続をリッスンし、受け入れます。クライアント側の Bluetooth デバイスは、サーバー側の Blutooth デバイスへ、Bluetooth 経由のストリーム チャネルとして接続します。接続とオブジェクト転送を許可する認証要件は、OBEX を使用するサーバーやアプリケーションによって異なります。たとえば、すばやく名刺交換を行うプロセスを効率化する場合、OPP では認証を行わない接続を可能にします。OBEX を使うサービスによっては、認証を受けた接続以外は許可されないものもあります。

ファイルは 3 種類のパケットを使って共有します。このパケットをそれぞれ先頭 PUT、中間 PUT、末尾 PUT と呼びます。先頭 PUT パケットはファイル転送の初期化を表し、末尾 PUT パケットはパケットの完了を表します。中間 PUT パケットは複数存在し、データのブロックを含みます。サーバーは各 PUT パケットを受信すると、受信確認パケットをクライアントへ返します。

通常のシナリオでは、OBEX パケットを以下の手順で送信します。

  1. OBEX クライアントから接続パケットを送信し、受信側のデバイスに接続します。このパケットではクライアントが受信できる最大パケット サイズを指定します。
  2. 接続の承諾を示す応答をサーバーから受信したら、OBEX クライアントは先頭 PUT パケットを送信します。このパケットには、ファイル名やサイズなど、オブジェクトを表すメタデータが含まれています (OBEX プロトコルでは先頭 PUT パケットにオブジェクト データも含めることも可能ですが、今回開発したライブラリのOBEX 実装では、先頭 PUT パケットでオブジェクト データをまったく送信していません)。
  3. 先頭 PUT パケットを受け取った確認をサーバーから受信したら、OBEX クライアントはオブジェクト データを含む複数の PUT パケットを送信します。送信するパケットの長さはサーバーが受信できる最大パケット サイズに制限します。この制限は、手順 1. で送信した接続パケットに対してサーバーが応答することで設定します。
  4. 末尾 PUT パケットには末尾 PUT を表す定数と、オブジェクト データの最終ブロックを含めます。
  5. ファイル共有を完了したら、OBEX クライアントは切断パケットを送信して Bluetooth 接続を閉じます。OBEX プロトコルでは、手順 2. と 3. を繰り返し、同じ接続上で複数のオブジェクトを送信できます。

OBEX クライアントは任意の時点で ABORT パケットを送信して共有プロセスを中止することができます。その場合、共有は直ちにキャンセルされます。今回のライブラリでは、OBEX プロトコルの実装の詳細は隠されているので、大まかな API しか確認できません。

OBEX ライブラリ

Windows Store アプリ用の Bluetooth OBEX クライアント ライブラリは、Windows Store アプリおよび Windows Phone Silverlight 8.1 アプリをターゲットとする移植可能なライブラリとして設計しています。このライブラリには、このライブラリを OBEX クライアントのランタイムにする 3 つの DLL が含まれています。Bluetooth.Core.Service、Bluetooth.Core.Sockets、および Bluetooth.Services.Obex. という 3 つの DLL はそれぞれ特定のタスクを処理するように設計しました。

Bluetooth Core Service: Bluetooth.Core.Service.dll ファイルは Bluetooth.Core.Service 名前空間を含みます。このライブラリは、クライアント デバイスとペアになる近くの Bluetooth デバイスを検索してカウントするように設計しています (図 1 参照)。現在はペアになるデバイスを 1 回だけカウントするように制限します。今後のバージョンには、さらに Bluetooth デバイスの検索を続けるウォッチャーを含める予定です。

図 1 BluetoothService のメソッドと関連イベント

メソッド (パラメーター) 関連イベント
[static] GetDefault なし
SerchForPairedDevicesAsync

成功時 - SearchForDevicesSucceeded

失敗時 - SearchForPairedDevicesFailed

Bluetooth のコア サービスは静的クラス BluetoothService で表します (図 2 参照)。このクラスには、デバイス数を非同期にカウントする API があります。

図 2 ペアとなるデバイスをカウントする BluetoothService

BluetoothService btService = BluetoothService.GetDefault();
btService.SearchForPairedDevicesFailed 
  += btService_SearchForPairedDevicesFailed;
btService.SearchForPairedDevicesSucceeded 
  += btService_SearchForPairedDevicesSucceeded;
await btService.SearchForPairedDevicesAsync();
void btService_SearchForPairedDevicesSucceeded(object sender,
  SearchForPairedDevicesSucceededEventArgs e)
{
  // Get list of paired devices from e.PairedDevices collection
}
void btService_SearchForPairedDevicesFailed(object sender,
  SearchForPairedDevicesFailedEventArgs e)
{
  // Get the failure reason from e.FailureReason enumeration
}

Bluetooth Core Sockets: Bluetooth.Core.Sockets.dll ファイルは Bluetooth.Core.Sockets 名前空間を含み、Bluetooth 接続経由でのストリームベースのソケット操作をサポートするように設計しています。ソケット機能は BluetoothSockets クラスで公開します (図 3 参照)。ソケットは TCP ソケットでも UDP ソケットでもありません。受信側のデバイスとのすべての通信は BluetoothSockets クラス経由で行います。

図 3 BluetoothSockets のメソッドと関連イベント

メソッド (パラメーター) 関連イベント

コンストラクター (Bluetooth.Core.Services.BluetoothDevice)

コンストラクター (Bluetooth.Core.Services.BluetoothDevice, System.UInt32)

コンストラクター (Bluetooth.Core.Services.BluetoothDevice, System.String)

コンストラクター (Bluetooth.Core.Services.BluetoothDevice, System.UInt32, System.String)

なし
PrepareSocketAsync

成功時 - SocketConnected

失敗時 - ErrorOccured

SendDataAsync(System.Byte[])

SendDataAsync(System.String)

なし
CloseSocket SocketClosed
関連メソッドなし DataReceived

Bluetooth Services Obex: Bluetooth.Services.Obex.dll ファイルは Bluetooth.Services.Obex 名前空間を含みます。この DLL が OBEX のコア実装で、ObexService というクラスで公開します。このクラスは、Bluetooth OPP 仕様を抽象化するビューを提供し、受信側の Bluetooth デバイスからの接続、送信、および切断をサポートするメソッドを公開します。図 4 にはこのクラスが公開する API と関連イベントを一覧します。図 5 はこのクラスの使用法の例を示しています。

図 4 ObexService のメソッドと関連イベント

メソッド (パラメーター) 関連イベント
[static] GetDefaultForBluetoothDevice(Bluetooth.Core.Services.BluetoothDevice) なし
ConnectAsync

成功時 - DeviceConnected

失敗時 - ConnectionFailed

SendFileAsync(Windows.Storage.IStorageFile)

成功時:

ServiceConnected

DataTransferProgressed

DataTransferSucceeded

Disconnecting

Disconnected

失敗時:

ConnectionFailed

DataTransferFailed

AbortAsync Aborted

図 5 Obex サービスの使用法

protected async override void OnNavigatedTo(Windows.UI.Xaml.Navigation.NavigationEventArgs e)
{
  base.OnNavigatedTo(e);
  ObexService obexService = ObexService.GetDefaultForBluetoothDevice(null);
  obexService.DeviceConnected += obexService_DeviceConnected;
  obexService.ServiceConnected += obexService_ServiceConnected;
  obexService.ConnectionFailed += obexService_ConnectionFailed;
  obexService.DataTransferProgressed += obexService_DataTransferProgressed;
  obexService.DataTransferSucceeded += obexService_DataTransferSucceeded;
  obexService.DataTransferFailed += obexService_DataTransferFailed;
  obexService.Disconnecting += obexService_Disconnecting;
  obexService.Disconnected += obexService_Disconnected;
  obexService.Aborted += obexService_Aborted;
  await obexService.ConnectAsync();
}
async void obexService_DeviceConnected(object sender, EventArgs e)
{
  // Device is connected, now send file
  await (sender as ObexService).SendFileAsync(fileToSend);
}
void obexService_ServiceConnected(object sender, EventArgs e)
{
  // Device connected to Obex Service on target device
}
void obexService_ConnectionFailed(object sender, 
  ConnectionFailedEventArgs e)
{
  // Connection to service failed
}
void obexService_DataTransferProgressed(object sender, 
  DataTransferProgressedEventArgs e)
{
  // Get data transfer progress from DataTransferProgressedEventArgs
}
void obexService_DataTransferSucceeded(object sender, EventArgs e)
{
  // Data transfer succeeded
}
void obexService_DataTransferFailed(object sender, DataTransferFailedEventArgs e)
{
  // Data transfer failed, get the reason from DataTransferFailedEventArgs
}
void obexService_Disconnecting(object sender, EventArgs e)
{
  // Device is disconnecting from service
  }
void obexService_Disconnected(object sender, EventArgs e)
{
  // Device is now disconnected from targeted device and service
}
void obexService_Aborted(object sender, EventArgs e)
{
  // Data transfer operation is aborted
}

これらのライブラリを使用する代表的なシナリオには以下のようなものがあります。

  • Bluetooth.Core.Service の API を使用して、ペアになる OPP 対応 Bluetooth デバイスをすべてカウントする (OPP 対応かどうかのチェックは既に実装されています)。
  • ファイルの共有に使用する BluetoothDevice のインスタンスを取得する。
  • BluetoothDevice のインスタンスをファクトリ メソッドに渡して、受信側の Bluetooth デバイス用に ObexService のインスタンスを取得する (ObexService クラスは内部で BluetoothSocket のインスタンスを作成し、このインスタンスでファイルを共有します)。
  • ファイル共有が完了したら、ObexService を自動的に切断する。

作業を始める

今回のライブラリは Windows Store アプリと Windows Phone 8.1 アプリをターゲットにするため、ユニバーサル アプリを使って作業を始めます。ユニバーサル アプリは、あらゆる Windows デバイス向のアプリを開発する優れた方法です。ユニバーサル アプリの詳細については、bit.ly/1h3AQeu (英語) を参照してください。ユニバーサル アプリを使って作業を始めるには、Visual Studio 2013 で [ストア アプリ] ノードの [ユニバーサル アプリ] から空のアプリケーション プロジェクトを新しく作成します。(図 6 参照)。今回は Visual C# を使いますが、Visual Basic や Visual C++ を使うこともできます。

空のユニバーサル アプリの新しいプロジェクトを作成
図 6 空のユニバーサル アプリの新しいプロジェクトを作成

Bluetooth OBEX クライアント アプリのプログラミングを始める前に、両方のプロジェクト (Windows 8.1 用と Windows Phone 8.1 用) の package.appxmanifest ファイルを次のように更新します。

<Capabilities>
  <m2:DeviceCapability Name="bluetooth.rfcomm">
    <m2:Device Id="any">
      <m2:Function Type="name:obexObjectPush"/>
    </m2:Device>
  </m2:DeviceCapability>
</Capabilities>

この更新を行うには、ソリューション エクスプローラーのコンテキスト メニューで [コードの表示] を選択し、package.appxmanifest をコード ファイルとして開きます。今回は <Application> タグの終りにこのスニペットを追加します。このコード スニペットは、今回のアプリで Bluetooth RFCOMM (Radio Frequency Communication) サービスを使うためのデバイスレベル機能を提供するために必要です。さらに、今回のデバイスと互換性のある全種類のサービスとデバイスも指定しています。

このシナリオでは、あらゆる OPP 対応デバイスと互換性のあるデバイスからの obexObjectPush サポートが必要です。Windows 8.1 と Windows Phone 8.1 でサポートされるプロファイルの詳細については、bit.ly/1pG6rYO (英語) を参照してください。その機能が指定されていなければ、デバイスの列挙が CapabilityNotDefined 列挙型定数で失敗します。

コーディングを始める前に、先ほどの 3 つのライブラリ ファイルへの参照を追加して、ライブラリの OBEX 実装を使えるようにします。両方のプロジェクトにそれぞれライブラリへの参照を追加する必要があります。参照を追加しないと、プロジェクトでその機能を使用することができません。

以下のコーディング パターンとプラクティスに従います。

  • Windows プロジェクトには Windows Store 8.1 アプリ用の UX デザインを実装する。
  • Windows Phone プロジェクトには Windows Phone 8.1 アプリ用の UX デザインを実装する。
  • 共有プロジェクトに共通機能を実装する。
  • 共有プロジェクト内でプラットフォーム固有のコンパイラ定数を使って、プラットフォーム固有の実装を行う (Windows 8.1 には WINDOWS_APP、Windows Phone 8.1 には WINDOWS_PHONE_APP を使用します。このコンパイラ定数はプロジェクトの一部として既に定義されています)。

サンプル コードをダウンロードして、ライブラリに関する実践例を確認し、そのコーディング方法に沿ってユニバーサル アプリを開発することをお勧めします。図 7 はソリューション エクスプローラー ウィンドウに表示されたファイル構造とパターンです。

BluetoothDemo アプリのソリューション エクスプローラー
図 7 BluetoothDemo アプリのソリューション エクスプローラー

ペアになるデバイスの列挙

ペアになるデバイスとファイルを共有する前に、ペアになるデバイスの一覧を確認し、ターゲットにするデバイスを選択します。デバイスから提供される Bluetooth コア サービスを表す、Bluetooth.Core.Service.BluetoothService のインスタンスへのハンドルを取得します。Bluetooth サービスはデバイスごとに 1 つしか利用できないため、このインスタンスを GetDefault 静的ファクトリ メソッドを使って取得します。

デバイスを列挙するには、SearchForPairedDevicesAsync メソッドを呼び出します。このメソッドは、ペアになるデバイスの列挙を開始します。Windows Store 8.1 アプリの場合、デバイスを列挙するにはペアになるデバイスの使用を許可する必要があります。デバイスの使用をブロックすると、ペアになるデバイスは列挙されません。

API が正常に完了すると、SearchForPairedDevicesSucceeded イベントが発生するので、イベントの引数からペアになるデバイスのコレクションを取得します。正常に終了しなかった場合、SearchForPairedDevicesFailed イベントが発生し、イベントの引数には使用可能なエラー列挙型定数が含まれます。図 8 に、デバイスを列挙するコードを示します。

図 8 ペアになるデバイスの列挙

protected async override void OnNavigatedTo(NavigationEventArgs e)
{
  base.OnNavigatedTo(e);
  await EnumerateDevicesAsync();
}
public async Task EnumerateDevicesAsync()
{
  BluetoothService btService = BluetoothService.GetDefault();
  btService.SearchForPairedDevicesFailed +=
    btService_SearchForPairedDevicesFailed;
  btService.SearchForPairedDevicesSucceeded +=
    btService_SearchForPairedDevicesSucceeded;
  await btService.SearchForPairedDevicesAsync();
}
void btService_SearchForPairedDevicesSucceeded(object sender,
  SearchForPairedDevicesSucceededEventArgs e)
{
  (sender as BluetoothService).SearchForPairedDevicesFailed -=
    btService_SearchForPairedDevicesFailed;
  (sender as BluetoothService).SearchForPairedDevicesSucceeded -=
    btService_SearchForPairedDevicesSucceeded;
  this.cvBtDevices.Source = e.PairedDevices;
}
void btService_SearchForPairedDevicesFailed(object sender,
  SearchForPairedDevicesFailedEventArgs e)
{
  (sender as BluetoothService).SearchForPairedDevicesFailed -=
    btService_SearchForPairedDevicesFailed;
  (sender as BluetoothService).SearchForPairedDevicesSucceeded -=
    btService_SearchForPairedDevicesSucceeded;
  txtblkErrorBtDevices.Text = e.FailureReason.ToString();
}

今回は、Windows 8.1 では BottomAppBar に、Windows Phone 8.1 では ApplicationBar にスキャン ボタンを用意しました。アプリのユーザーはこのボタンを使用して、新しくペアになるデバイスが現れたとき、再スキャンを行えるようになります。

Windows Phone 8.1 でデバイスが列挙されるとき、デバイスが実在するかどうか、Bluetooth 無線がオンかオフかに関わらず、すべての OBEX 対応デバイスが列挙されます。ただし、Windows 8.1 でデバイスが列挙されるときは、近くに実在し、Bluetooth 無線がオンになっているデバイスのみが一覧されます。

ペアになるデバイスを列挙したら、ファイルを共有するデバイスを選択します。各デバイスは Bluetooth.Core.Services.BluetoothDevice クラスのオブジェクトとして表されます。オブジェクトにはペアになるデバイスとの接続状態や、デバイスの表示名が含まれます。Bluetooth.Services.Obex.ObexService はその接続状態の情報を内部で使用して、Bluetooth.Core.Sockets.BluetoothSocket のインスタンスを作成し、ペアになるデバイスに接続します。

ObexSercive の使用

ファイル共有のターゲットになるデバイスを表す Bluetooth.Core.Services.BluetoothDevice オブジェクトのインスタンスを取得したら、OPP を用いたファイル共有に Bluetooth.Services.Obex.ObexService を使うことができます。また、共有の際にキューに登録するためファイルのリストも必要です。コード サンプルではごくわずかなファイルしか用意していません。別の方法として、複数のファイルを選択するために Windows.Storage.Pickers.FileOpenPicker (「FileOpenPicker Class」) を使用するか、Windows.Storage.ApplicationData.Current.LocalFolder (「ApplicationData.LocalFolder localFolder property」) のカスタム ロジックを使用します。

現時点では、共有できるファイルは接続ごとに 1 つだけです。共有を完了するときに、ターゲットのデバイスとの接続を閉じます。複数のファイルを送信する場合は、Bluetooth.Services.Obex.ObexService インスタンスへのハンドルを複数回取得する必要があります。このインスタンスは、静的ファクトリ メソッドの GetDefaultForBluetoothDevice (Bluetooth.Core.Services.BluetoothDevice) を使って取得できます。このメソッドは、デバイス上の Obex サービスを表す Bluetooth.Services.Obex.Obex.Service のインスタンスを 1 つ返します。

共有するファイルを表すには FileItemToShare クラスを使用します。このクラスは、ファイルの名前、パス、サイズ、およびディスク上のファイル インスタンスを表す Windows.Storage.IStorageFile (「IStorageFile Interface」) のインスタンスを含みます。今回は、共有する必要のあるすべてのファイルをデータ構造オブジェクト単位にキューに登録します。複数のファイルを共有するときは、現状、リストの先頭にあるファイルを共有します。共有を完了すると、そのファイルはリストから除外されます。図 9 はファイルの共有用に ObexService とイベントをフックする方法を示しています。

図 9 ObexService とイベントのフック

ObexService obexService = null;
BluetoothDevice BtDevice = null;
ObservableCollection<FileItemToShare> filesToShare = null;
async override void OnNavigatedTo(NavigationEventArgs e)
{
  base.OnNavigatedTo(e);
  if (e.Parameter == null || !(e.Parameter is BluetoothDevice))
  {
    MessageDialog messageBox = new MessageDialog(
      "Invalid navigation detected. Moving to Main Page", "Bluetooth Hub");
    messageBox.Commands.Add(new UICommand("OK", (uiCommand) =>
    {
      this.Frame.Navigate(typeof(MainPage));
    }));
    await messageBox.ShowAsync();
    return;
  }
  BtDevice = e.Parameter as BluetoothDevice;
  filesToShare = GetFilesFromLocalStorage();
  this.cvFileItems.Source = filesToShare;
  obexService = ObexService.GetDefaultForBluetoothDevice(BtDevice);
  obexService.Aborted += obexService_Aborted;
  obexService.ConnectionFailed += obexService_ConnectionFailed;
  obexService.DataTransferFailed += obexService_DataTransferFailed;
  obexService.DataTransferProgressed += obexService_DataTransferProgressed;
  obexService.DataTransferSucceeded += obexService_DataTransferSucceeded;
  obexService.DeviceConnected += obexService_DeviceConnected;
  obexService.Disconnected += obexService_Disconnected;
  obexService.Disconnecting += obexService_Disconnecting;
  obexService.ServiceConnected += obexService_ServiceConnected;
  await obexService.ConnectAsync();
}

ConnectAsync メソッドを呼び出すと、ObexService オブジェクトが、ファクトリ メソッドで渡された BluetoothDevice オブジェクトの接続プロパティを取得します。これにより、Bluetooth チャンネル経由でターゲットの BluetoothDevice との接続の作成が試みられます。正常に接続されると、DeviceConnected イベントが発生します。図 10 は、ObexService の DeviceConnected イベント ハンドラーを示しています。

図 10 DeviceConnected イベント ハンドラー メソッド

async void obexService_DeviceConnected(object sender, EventArgs e)
{
  await this.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, async () =>
  {
    System.Diagnostics.Debug.WriteLine("device connected");
    if (filesToShare.Count > 0)
    {
      filesToShare.ShareStatus = FileShareStatus.Connecting;
      await obexService.SendFileAsync(filesToShare[0].FileToShare);
    }
    ...
  });
}

デバイスがターゲットのデバイスと接続すると、直ちにファイルのリストからインデックス 0 のファイルの共有を開始します。ファイルは、FileToShare 型のデータ構造を持つオブジェクトの IStorageFile によって表されるファイル オブジェクトを渡して SendFileAsync (Windows.Storage.IStorageFile) を呼び出すことで共有します。このメソッドを呼び出すと、ObexService がターゲット デバイスで実行されている OBEX サーバーへの接続を試みます。接続が正常に行われると、ServiceConnected イベントが発生します。接続に失敗すると ConnectionFailed イベントが発生します。以下のコードは ServiceConnected イベント ハンドラーを示します。

async void obexService_ServiceConnected(object sender, EventArgs e)
{
  await this.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
  {
    System.Diagnostics.Debug.WriteLine("service connected");
    filesToShare[0].ShareStatus = FileShareStatus.Sharing;
  });
}

図 11 は、ObexService の ConnectionFailed イベント ハンドラーを示しています。

図 11 ConnectionFailed イベント ハンドラー メソッド

async void obexService_ConnectionFailed(object sender, 
  ConnectionFailedEventArgs e)
{
  await this.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, async () =>
  {
    System.Diagnostics.Debug.WriteLine("connection failed");
    filesToShare[0].ShareStatus = FileShareStatus.Error;
    filesToShare[0].Progress = 0;
    FileItemToShare currentItem = filesToShare[0];
    filesToShare.RemoveAt(0);
    filesToShare.Add(currentItem);
  });
}

ターゲットのデバイスの OBEX サーバーに接続すると、ファイル共有プロセスが開始します。ファイル共有の進行状況は DataTransferProgressed イベントで判断できます。以下のコードは DataTransferProgressed メソッドを示します。

async void obexService_DataTransferProgressed(object sender,
  DataTransferProgressedEventArgs e)
{
  await this.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
  {
    System.Diagnostics.Debug.WriteLine("Bytes {0}, Percentage {1}",
      e.TransferInBytes, e.TransferInPercentage);
    filesToShare[0].Progress = e.TransferInBytes;
  });
}

ファイル共有が完了すると、DataTransferSucceeded イベントが発生します。ファイル共有が失敗すると、DataTransferFailedEvent が発生します。以下のコードは、DataTransferSucceeded イベント ハンドラーを示しています。

async void obexService_DataTransferSucceeded(object sender, EventArgs e)
{
  await this.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
  {
    System.Diagnostics.Debug.WriteLine("data transfer succeeded");
    filesToShare.RemoveAt(0);
  });
}

ファイル共有エラーが発生すると、DataTransferFailed イベントが発生します。以下のコードは、このイベント ハンドラーを示しています。

async void obexService_DataTransferFailed(object sender,
  DataTransferFailedEventArgs e)
{
  await this.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, async () =>
  {
    System.Diagnostics.Debug.WriteLine("Data transfer failed {0}",
      e.ExceptionObject.ToString());
    filesToShare[0].ShareStatus = FileShareStatus.Error;
    filesToShare[0].Progress = 0;
    FileItemToShare fileToShare = this.filesToShare[0];
    filesToShare.RemoveAt(0);
    this.filesToShare.Add(fileToShare);
  });
}

ファイル共有によるデータ転送が完了すると、共有されたファイルがリストから除外され、ObexService が切断されます。ObexService の切断を開始すると、Disconnecting イベントが発生します。接続が正しく切断されると、Disconnected イベントが発生します。以下は、Disconnecting イベント ハンドラーを示しています。

void obexService_Disconnecting(object sender, EventArgs e)
{
  System.Diagnostics.Debug.WriteLine("disconnecting");
}

切断が正しく完了すると、図 12 に示したコードで Disconnected イベントの発生を処理します。

図 12 Disconnected イベント ハンドラー メソッド

async void obexService_Disconnected(object sender, EventArgs e)
{
  await this.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, async () =>
  {
    System.Diagnostics.Debug.WriteLine("disconnected");
    obexService.Aborted -= obexService_Aborted;
    obexService.ConnectionFailed -= obexService_ConnectionFailed;
    obexService.DataTransferFailed -= obexService_DataTransferFailed;
    obexService.DataTransferProgressed -= 
      obexService_DataTransferProgressed;
    obexService.DataTransferSucceeded -= 
      obexService_DataTransferSucceeded;
    obexService.DeviceConnected -= obexService_DeviceConnected;
    obexService.Disconnected -= obexService_Disconnected;
    obexService.Disconnecting -= obexService_Disconnecting;
    obexService.ServiceConnected -= obexService_ServiceConnected;
    obexService = null;
    if (filesToShare.Count.Equals(0))
    {
      ...
      MessageDialog messageBox =
        new MessageDialog("All files are shared successfully",
        "Bluetooth DemoApp");
      messageBox.Commands.Add(new UICommand("OK", (uiCommand) =>
      {
        this.Frame.Navigate(typeof(MainPage));
      }));
      await messageBox.ShowAsync();
    }
    else
    {
      obexService = ObexService.GetDefaultForBluetoothDevice(BtDevice);
      obexService.Aborted += obexService_Aborted;
      obexService.ConnectionFailed += obexService_ConnectionFailed;
      obexService.DataTransferFailed += obexService_DataTransferFailed;
      obexService.DataTransferProgressed += 
        obexService_DataTransferProgressed;
      obexService.DataTransferSucceeded += 
        obexService_DataTransferSucceeded;
      obexService.DeviceConnected += obexService_DeviceConnected;
      obexService.Disconnected += obexService_Disconnected;
      obexService.Disconnecting += obexService_Disconnecting;
      obexService.ServiceConnected += obexService_ServiceConnected;
      await obexService.ConnectAsync();
    }
  });
}

Disconnected イベントが発生したら、すべてのハンドラーを削除し、ObexService インスタンスをクリアします。データの転送中、実行中の転送を中止しなければならなくなる場合があります。実行中の転送を中止する場合は AbortAsync を呼び出します。Aborted イベントは以下のコードによって発生し、ターゲットのデバイスとの接続を閉じます。

async void obexService_Aborted(object sender, EventArgs e)
{
  await this.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
  {
    System.Diagnostics.Debug.WriteLine("Aborted");
    if (!filesToShare.Count.Equals(0))
    {
      filesToShare.RemoveAt(0);
    }
  });
}

まとめ

これでデモ アプリは完成です。ユニバーサル アプリという考え方は、複数の Windows プラットフォームやフォーム ファクターに対して 1 つのコードを作成するもので、これにより開発全体の負担が非常に軽くなります。

ここで使用したライブラリは、数多くの Windows Store アプリや Windows Phone アプリで使うことができます。Code Create (無料の Windows Phone アプリ) や OBEX (ユニバーサル アプリ) を検索して、API がアプリと連携してどのような働きをするか確かめてみてください。今回のライブラリは NuGet リポジトリからダウンロードして入手できます。NuGet オンライン ダイアログで "Bluetooth OBEX for Store Apps" を検索して、Visual Studio ソリューションを取得し、プロジェクトへの参照としてこのライブラリをインポートします。


Uday Gupta は、Symphony Teleca Corp. (インド) Pvt Ltd の製品開発部門でシニア エンジニアを務めており、数多くの .NET テクノロジ (特に、Windows Presentation Foundation (WPF)、Silverlight、Windows Phone、および Windows 8) に携わった経験を持っています。

この記事のレビューに協力してくれたマイクロソフト技術スタッフの Jeff Kelley および Guruprasad Subbarayan に心より感謝いたします。