その後のその後

iOSエンジニア 堤 修一のブログ github.com/shu223

ANCSでiOSの電話着信やメール受信の通知を外部デバイスから取得する

ANCS は「Apple Notification Center Service」の略で、電話着信やメール受信等、iOSで発生するさまざまな種類の通知に、BLEで繋がっている外部デバイスからアクセスするためのサービスです。


iOSアプリに携わるエンジニアとしてはリモート通知(プッシュ通知)の APNS (Apple Push Notification Service) と混同しそうになる略称ですが、もちろん別モノです。


IoTとかウェアラブル的な文脈では大抵のケースでiOSデバイスがセントラルになり、外部デバイスがペリフェラルとなりますが、ANCSはiOSデバイスがサービス提供側となるため、その立場が逆転し、iOSデバイスがペリフェラル、外部デバイスがセントラルとなります。





※本記事はBLEアドベントカレンダー5日目の記事です。

用語について

Appleが提供しているドキュメント「Apple Notification Center Service (ANCS) Specification」では、サービス提供側(つまりiOSデバイス)を NP (Notification Provider)、サービスを受ける側(外部デバイス)を NC (Notification Consumer) と呼んでいるので、本記事でも以降そのように表現します。


また、同ドキュメントでは、iOSにおけるPush NotificationやLocal Notificationといった通知と、GATTにおけるNotificationを呼び分けるため、"iOS Notification", "GATT Notification" という表現が使用されています。本記事内でも、これらの区別のため「iOS通知」「GATT通知」という表現を使用します。

ANCS の GATT

ANCSサービスのUUIDは次のように定義されています。

7905F431-B5CE-4E99-A40F-4B1E122D00D0


そして、ANCSサービスは、以下のキャラクタリスティックを持っています。

Characteristic UUID Properties
Notification Source 9FBF120D-6301-42D9-8C58-25E699A21DBD notifiable
Control Point 69D1D8F3-45E1-49A8-9821-9BBDFDAAD9D9 writeable with response
Data Source 22EAC6E9-24D6-4BB5-BE44-B36ACE7C7BFB notifiable


Notification Source は、

  • NPでのiOS通知の到着
  • NPでのiOS通知の変更
  • NPでのiOS通知の削除

をNCに知らせるためのキャラクタリスティックです。

また Control Point は NC が NP に iOS通知のより詳細な情報を要求するための Write キャラクタリスティックで、Control Point への書き込みが成功すると、NP は Data Source キャラクタリスティックでのGATT通知によりリクエストに応答します。


これらのサービス/キャラクタリスティックでは、別のiOSデバイスからセントラルとして接続しても見つからないようになっています。ANCSが提供されるようになったiOS7より前のiOSデバイス、あるいはそれ以外のMac等のデバイスからは見つけることができます。

実装サンプル

以下、Notification Source キャラクタリスティックを介して、iOS通知の到着や変更をNC側で受け取る実装方法について説明します。

NPの実装

NC側からペリフェラル名で発見できるように、`CBAdvertisementDataLocalNameKey` に何らかのデバイス名を指定してアドバタイズ開始しておきます。(一般的なペリフェラルマネージャの実装なのでコードは省略)

NCの実装

上述した通りiOSデバイスはNCになれないため、ここでは Mac OS X のアプリとして実装します。

なお、スキャン、接続、サービス/キャラクタリスティックの探索まではセントラルとしては通例通りの処理なので説明を省略します。

Notification Source をサブスクライブする

Notification Source キャラクタリスティックの値の更新通知を有効にします。

- (void)                      peripheral:(CBPeripheral *)peripheral
    didDiscoverCharacteristicsForService:(CBService *)service
                                   error:(NSError *)error
{
    CBUUID *notificationSourceUuid = [CBUUID UUIDWithString:@"9FBF120D-6301-42D9-8C58-25E699A21DBD"];
    
    for (CBCharacteristic *aCharacteristic in service.characteristics) {
        
        if ([aCharacteristic.UUID isEqualTo:notificationSourceUuid]) {

            self.notificationSourceCharacteristic = aCharacteristic;
            
            // Notification Source の subscribeを開始する
            [peripheral setNotifyValue:YES
                     forCharacteristic:aCharacteristic];
        }
    }
}
Notification Source の値を読む

Notification Source キャラクタリスティックの値は、次のような8バイトで構成されています。



本サンプルでは、どのようなiOS通知があったのかを最低限判別できるよう、「EventID」「CategoryID」の2つを読み取ることにします。


EventIDはiOS通知が「新規」「変更」「削除」のどれなのかを示す値で、次のように定義されています。

EventIDNotificationAdded 0
EventIDNotificationModified 1
EventIDNotificationRemoved 2
Reserved EventID values 3-255


CategoryIDはiOS通知の種類を示す値で、次のように定義されています。

CategoryIDOther 0
CategoryIDIncomingCall 1
CategoryIDMissedCall 2
CategoryIDVoicemail 3
CategoryIDSocial 4
CategoryIDSchedule 5
CategoryIDEmail 6
CategoryIDNews 7
CategoryIDHealthAndFitness 8
CategoryIDBusinessAndFinance 9
CategoryIDLocation 10
CategoryIDEntertainment 11
Reserved CategoryID values 12–255


Notification Source キャラクタリスティックの値に変更があると、すなわちNP側で電話着信などのiOS通知が新規で発生した/変更された、等のイベントが発生すると、サブスクライブしているNCに対してGATT通知が送られます。


このタイミングでキャラクタリスティックからEventIDとCategoryIDを読み取り、ログ出力するコードは次のようになります。

- (void)                 peripheral:(CBPeripheral *)peripheral
    didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic
                              error:(NSError *)error
{
    NSLog(@"%@", characteristic.value);

    // 8バイト取り出す
    unsigned char bytes[8];
    [characteristic.value getBytes:bytes length:8];    

    // Event ID
    unsigned char eventId = bytes[0];
    switch (eventId) {
        case 0:
            NSLog(@"Notification Added");
            break;
        case 1:
            NSLog(@"Notification Modified");
            break;
        case 2:
            NSLog(@"Notification Removed");
            break;
        default:
            // reserved
            break;
    }

    unsigned char categoryId = bytes[2];
    switch (categoryId) {
        case 0:
            // Other
            break;
        case 1:
            NSLog(@"Incoming Call");
            break;
        case 2:
            NSLog(@"Missed Call");
            break;
        case 3:
            NSLog(@"Voice Mail");
            break;
        case 4:
            NSLog(@"Social");
            break;
        case 5:
            NSLog(@"Schedule");
            break;
        case 6:
            NSLog(@"Email");
            break;
        case 7:
            NSLog(@"News");
            break;
        case 8:
            NSLog(@"Health and Fitness");
            break;
        case 9:
            NSLog(@"Business and Finance");
            break;
        case 10:
            NSLog(@"Location");
            break;
        case 11:
            NSLog(@"Entertainment");
            break;
        default:
            // Reserved
            break;
    }
}
実行結果

次のようにログ出力されました。(見やすいよう各通知のログを1行にまとめています)

<001d0201 00000000>, Notification Added, Missed Call
<001d0202 01000000>, Notification Added, Missed Call
<001d0203 02000000>, Notification Added, Missed Call
<00150601 03000000>, Notification Added, Email
<00150602 04000000>, Notification Added, Email
<00100603 05000000>, Notification Added, Email
<00100604 06000000>, Notification Added, Email
<00150605 07000000>, Notification Added, Email
<00150401 08000000>, Notification Added, Social
<00150b01 09000000>, Notification Added, Entertainment
<00150b02 0a000000>, Notification Added, Entertainment
<00150b03 0b000000>, Notification Added, Entertainment
<00150a01 0c000000>, Notification Added, Location
<00150801 0d000000>, Notification Added, Health and Fitness
<00150802 0e000000>, Notification Added, Health and Fitness
<00150901 0f000000>, Notification Added, Business and Finance
<00150001 10000000>, Notification Added
<00150002 11000000>, Notification Added
<00150003 12000000>, Notification Added
<00150402 13000000>, Notification Added, Social

電話着信やメール受信、SNSのiOS通知の発生や削除がNC側(ここではMacアプリ)で検知できていることがわかります。

iOSでの通知設定について

PebbleのSupport Centerのページによると、iOS側の通知をどこまで許可することでANCSによりNCに通知されるかがアプリによって違うようです。

The following apps need nothing more than "Allow Notifications" for their notifications to appear on Pebble.

  • iOS 7 email
  • SMS and iMessages
  • Calendar
  • CalenMob
  • BBM

At least one of the alert styles (banners, alerts, badge app icon, or alert sound) must be enabled for the following apps, in addition to having "Allow Notifications" on.

  • Gmail
  • Hangouts
  • WhatsApp
  • Facebook
  • Facebook Messenger
  • Snapchat

The following apps require that the banners alert style specifically be chosen in order to deliver notifications to Pebble:

  • Twitter


# 同ページでは "Here is what we have discovered so far:", "If you are aware of any other apps which require a special set up, please contact our Support Team" 等と表現されていることから、どういうルールでこれらが決まるのかははっきりしていないようです。