最初に書いておきますが、iPhoneでの接触確認アプリの実機環境は整えることができません。もともと暴露通知(Exposure Notification)が、ひとつの国でひとつのアプリでしか使えないという制限のためという理由もあるのですが、Exposure Notification を有効にしたまま実機(iPhone)にインストールすることができません。
目的としては、
- 暴露通知(Exposure Notification)の動作を把握する
- COCOA の不具合っぽいものを確認しておく(直すことはできないので、確認のみ)
とするので、内部的なところには踏み込まないようにします。
ソースコードをダウンロードする
cocoa の元ネタのソースコードを github からダウンロードします。
現在の cocoa は ver.1.1.2 なのですが、ここにあるソースコードは 1.1.1 のままになっています。このため、実際の cocoa とは違うので「動作チェック」という訳にはいかないのですが、Exposure Notification の仕組みを知るには良い材料でしょう。
プログラムが Xamarin.Forms を使っているので、Visual Studio 2019 と Visual Studio for Mac を使います。
Visual Studio 2019 のほうは、Windows 上で Android アプリのビルドができます。iPhone アプリの開発は Windows から mac を通しても可能なのですが、mac のほうに Visual Studio for Mac を入れたほうが便利です。
ソースコードを開く
Android と iOS のアプリは、Covid19Radar.sln を Visual Studio で開きます。
- Covid19Radar.sln
- Covid19Radar.Functions.sln
Covid19Radar.Functions.sln のほうは、サーバー側のプログラムです。きちんと設定してやれば、ローカルの Azure エミュレータと Cosmos DB を使ってサーバーサイドも構築することも可能なのですが、これはまだ試していません。
- Covid19Radar
- Covid19Radar.Android
- Covid19Radar.iOS
という3つのプロジェクトがあります。Xamarin.Forms で Android/iOS のアプリを作るとき、共通コードとなる Covid19Radar プロジェクトがあって、それぞれ機種特有のコードを Covid19Radar.Android や Covid19Radar.iOS に書くことになっています。
肝心の Exposure Notification のコードは、内部的に Google(Android) と Apple(iOS) では別々のコードになるところなのですが、XamarinComponents/XPlat/ExposureNotification at master · xamarin/XamarinComponents という形で、Android と iOS の動作をひとつにまとめて扱えるようになっています。
NuGet の場合は、以下からダウンロードができます。
- NuGet Gallery | Xamarin.iOS.ExposureNotification 1.0.0
- NuGet Gallery | Xamarin.GooglePlayServices.Nearby.ExposureNotification 18.0.2-eap.6
setting.json を書き替える
Exposure Notification の仕組みで優れているところは、
- 自分が陽性と解ったときだけ、サーバーに TEK(Temporary Exposure Key)を送る
- 大勢の人は、定期的に陽性者の TEK をダウンロードして、自分のスマホ内で照合する
ところです。個人データと思われる TEK の情報を、「陽性情報の登録」のときのみサーバーにアップロードします。HER-SYS 番号との連係はさておき、自発的に「陽性情報の登録」をしたときだけしか、サーバーにアクセスしにいかないので、個人データの漏洩リスクが減ります。
もうひとつ、定期的にサーバーから TEK をダウンロードしますが、この中にある RPI(Rolling Proximity Identifier)だけでは、個人を特定しにくいです。実は、全くできないわけではなくて、別途 Beacon を使って接触通知アプリを使って広域的に探っていけばできないこともないのですが、現在のところ難しい、というところです。
Covid19Radar プロジェクトの中に、setting.json というファイルがあります。
これを開くといくつかの設定があります。実際の cocoa では、APP_VERSION などが設定されているはずです。
{
"appVersion": "APP_VERSION",
"apiSecret": "API_SECRET",
"apiUrlBase": "https://API_URL_BASE/api",
"supportedRegions": "440",
"blobStorageContainerName": "c19r",
"androidSafetyNetApiKey": "ANDROID_SAFETYNETKEY",
"cdnUrlBase": "https://CDN_URL_BASE/",
"licenseUrl": "https://covid19radarjpnprod.z11.web.core.windows.net/license.html",
"appStoreUrl": "https://itunes.apple.com/jp/app/id1516764458?mt=8",
"googlePlayUrl": "https://play.google.com/store/apps/details?id=jp.go.mhlw.covid19radar",
"supportEmail": "SUPPORT_EMAIL"
}
apiUrlBase は、「陽性情報の登録」をしたときの呼び出し先で、Azure Functions が使われています。このアドレスと秘密キーは本番の cocoa だけのもなので、このままにしておきます。逆に言えば、、間違って「陽性情報の登録」を押しても、勝手にサーバーに接続できないということですね。
陽性者の TEK をダウンロードするための URL は、cdnUrlBase となるので、ここは書き替えます。
"cdnUrlBase": "https://covid19radar-jpn-prod.azureedge.net/",
デバッグページ(DebugPage)を表示させる
もうひとつ、動作確認用のデバッグページを表示できるようにします。
ViewModels/MenuPageViewModel.cs を開いて、以下のコードを追加しておきます。
#if DEBUG
MenuItems.Add(new MainMenuModel()
{
Icon = "\uf0c0",
PageName = nameof(DebugPage),
Title = nameof(DebugPage)
});
#endif
Debug_Mock と Debug の違い
プロジェクトをビルドする前に、Debug_Mock と Debug を確認しておきます。
- Debug は、Exposure Notification API を使ったデバッグモード
- Debug_Mock は、Exposure Notification API を使わずにエミュレート
の違いがあります。
Nearby.EXPOSURE_NOTIFICATION_API エラーが出る
EN api を使ったまま、実機 Android にインストール(Visual Studio からのデバッグ実行)はできるのですが、何かのタイミングで、次のようなエラーが出ます。
**Android.Gms.Common.Apis.ApiException:** '17: API: Nearby.EXPOSURE_NOTIFICATION_API is not available on this device. Connection failed with: ConnectionResult{statusCode=UNKNOWN_ERROR_CODE(39507), resolution=null, message=null}'
前後のログ出力はこんな感じ。
08-22 09:26:56.703 I/MonoDroid(23458): UNHANDLED EXCEPTION:
08-22 09:26:56.713 I/MonoDroid(23458): Android.Gms.Common.Apis.ApiException: 17: API: Nearby.EXPOSURE_NOTIFICATION_API is not available on this device. Connection failed with: ConnectionResult{statusCode=UNKNOWN_ERROR_CODE(39507), resolution=null, message=null}
08-22 09:26:56.713 I/MonoDroid(23458): at Android.Gms.Nearby.ExposureNotification.IExposureNotificationClient.IsEnabledAsync () [0x00067] in <a915b6bc332b428d88e3e93dc94335a6>:0
08-22 09:26:56.713 I/MonoDroid(23458): at Xamarin.ExposureNotifications.ExposureNotification.PlatformGetStatusAsync () [0x00074] in <e42d902759884cb2b9bcb6b7e1a5859c>:0
08-22 09:26:56.713 I/MonoDroid(23458): at Covid19Radar.Services.ExposureNotificationService.UpdateStatusMessageAsync () [0x00025] in D:\git\cocoa\Covid19Radar\Covid19Radar\Covid19Radar\Services\ExposureNotificationService.cs:88
08-22 09:26:56.713 I/MonoDroid(23458): at Covid19Radar.Services.ExposureNotificationService.OnUserDataChanged (System.Object sender, Covid19Radar.Model.UserDataModel userData) [0x00057] in D:\git\cocoa\Covid19Radar\Covid19Radar\Covid19Radar\Services\ExposureNotificationService.cs:73
08-22 09:26:56.713 I/MonoDroid(23458): at System.Runtime.CompilerServices.AsyncMethodBuilderCore+<>c.<ThrowAsync>b__7_0 (System.Object state) [0x00000] in /Users/builder/jenkins/workspace/archive-mono/2020-02/android/release/mcs/class/referencesource/mscorlib/system/runtime/compilerservices/AsyncMethodBuilder.cs:1021
08-22 09:26:56.714 I/MonoDroid(23458): at Android.App.SyncContext+<>c__DisplayClass2_0.<Post>b__0 () [0x00000] in <eaa205f580954a64824b74a79fa87c62>:0
08-22 09:26:56.714 I/MonoDroid(23458): at Java.Lang.Thread+RunnableImplementor.Run () [0x00008] in <eaa205f580954a64824b74a79fa87c62>:0
08-22 09:26:56.714 I/MonoDroid(23458): at Java.Lang.IRunnableInvoker.n_Run (System.IntPtr jnienv, System.IntPtr native__this) [0x00008] in <eaa205f580954a64824b74a79fa87c62>:0
08-22 09:26:56.714 I/MonoDroid(23458): at (wrapper dynamic-method) Android.Runtime.DynamicMethodNameCounter.1(intptr,intptr)
08-22 09:26:56.714 I/MonoDroid(23458): --- End of managed Android.Gms.Common.Apis.ApiException stack trace ---
08-22 09:26:56.714 I/MonoDroid(23458): com.google.android.gms.common.api.ApiException: 17: API: Nearby.EXPOSURE_NOTIFICATION_API is not available on this device. Connection failed with: ConnectionResult{statusCode=UNKNOWN_ERROR_CODE(39507), resolution=null, message=null}
08-22 09:26:56.714 I/MonoDroid(23458): at com.google.android.gms.common.internal.ApiExceptionUtil.fromStatus(com.google.android.gms:play-services-base@@17.1.0:4)
08-22 09:26:56.714 I/MonoDroid(23458): at com.google.android.gms.common.api.internal.ApiExceptionMapper.getException(com.google.android.gms:play-services-base@@17.1.0:2)
08-22 09:26:56.714 I/MonoDroid(23458): at com.google.android.gms.common.api.internal.zaf.zaa(com.google.android.gms:play-services-base@@17.1.0:15)
08-22 09:26:56.714 I/MonoDroid(23458): at com.google.android.gms.common.api.internal.GoogleApiManager$zaa.zac(com.google.android.gms:play-services-base@@17.1.0:175)
08-22 09:26:56.714 I/MonoDroid(23458): at com.google.android.gms.common.api.internal.GoogleApiManager$zaa.onConnectionFailed(com.google.android.gms:play-services-base@@17.1.0:95)
08-22 09:26:56.714 I/MonoDroid(23458): at com.google.android.gms.common.internal.zag.onConnectionFailed(com.google.android.gms:play-services-base@@17.1.0:2)
08-22 09:26:56.714 I/MonoDroid(23458): at com.google.android.gms.common.internal.BaseGmsClient$zzf.zza(com.google.android.gms:play-services-basement@@17.2.1:6)
08-22 09:26:56.714 I/MonoDroid(23458): at com.google.android.gms.common.internal.BaseGmsClient$zza.zza(com.google.android.gms:play-services-basement@@17.2.1:25)
08-22 09:26:56.714 I/MonoDroid(23458): at com.google.android.gms.common.internal.BaseGmsClient$zzc.zzo(com.google.android.gms:play-services-basement@@17.2.1:11)
08-22 09:26:56.714 I/MonoDroid(23458): at com.google.android.gms.common.internal.BaseGmsClient$zzb.handleMessage(com.google.android.gms:play-services-basement@@17.2.1:49)
08-22 09:26:56.714 I/MonoDroid(23458): at android.os.Handler.dispatchMessage(Handler.java:107)
要は、EN api を利用するときには、アプリが正式に Google Play に登録しておく必要があり、アプリが EN api を使うときに Google Play Service が呼び出しのチェックをしているそうです。
このあたりのデバッグのしにくさは、ドイツ版の接触確認アプリ(corona-warn-app)や Google の exposure-notifications-android でも話題になっています。
- 17: API: Nearby.EXPOSURE_NOTIFICATION_API is not available on this device · Issue #8 · google/exposure-notifications-android
- Product flavor for using mock exposure notification API · Issue #321 · corona-warn-app/cwa-app-android
ちなみに、corona-warn-app のほうでは、Frida を使った Google Play Service の偽装の方法が提案されているので、これだと上手くいくかもしれません(ちょっと試してない)。
- kbobrowski/en-i13n: Exposure Notifications framework instrumentation for Android
- Android | Frida • A world-class dynamic instrumentation framework
Google Play 開発者サービスでガードが掛かる
つまり、Android 実機の場合は、
- EN api を有効したままインストールはできるが、実行時に EN api を呼び出したときに Google Play Service でガードがかかる。
という訳で、UI 周りのチェックをしたいときは Debug_Mock で動かし、EN api の挙動をを調べる場合は今のところ手がないというパターンです。
iOS の場合は、インストール時にガードが掛かる
iPhone 版の場合は、Entitlements.plist に EN api の記述があります。
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.developer.exposure-notification</key>
<true/>
<key>aps-environment</key>
<string>development</string>
</dict>
</plist>
この「com.apple.developer.exposure-notification」の値が true になることで、iPhone で EN api を利用できるようになります。
ただし、ビルドして実機に iPhone にインストールするときにプロビジョニングでこの値をチェックするのですが、はねられてしまいます。この値を有効にするには、あらかじめ申請が必要で
Exposure Notification Entitlement Request – Contact Us – Apple Developer で送ります。これはい各国の保健省(日本では厚生労働省)の認可が必要なので、日本の場合は cocoa の開発サイドのみになります。