AndroidとSesameとNFCで開けゴマ 最終版

##動作結果
![jobi9-m8t0q.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/602722/79af172f-b2a5-7967-06cd-f6670d97e5de.gif)


##変更点
・Sesameの操作関係をクラス化
　　インスタンスで２台操作できそうに（未実験
　　10秒or1分で接続が切れることを利用して、それっぽく例外処理
・スマホとペアリングができていない場合は、createBondでペアリング
・マニフェストでActivityに対して”透明”テーマを適応
・Notificationでヘッドアップ通知で動作状況を把握
・BluetoothAdapterの初期化をdevelopersサイトのKotllin記載に修正

###manufactreDataMacDataStringの見つけ方
このプログラムではPrimarySesameMacDataの値について
![取得場所.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/602722/02fb516a-c7bb-750d-9d98-ef095e1233b6.png)

BLEScannerのアドバタイズパケットの内容が見れる：RAWの部分の赤線部分。この資料の場合、[00-00-C1-02-02-02-02-02-02]になる。


##成果物
MainActivity.kt

	package jp.sakujira.opensesame

	//App
	import android.os.Bundle
	import androidx.appcompat.app.AppCompatActivity
	//NFC
	import android.nfc.NfcAdapter
	//Bluetooth
	import android.content.Context
	import android.bluetooth.BluetoothAdapter
	import android.bluetooth.BluetoothManager
	//Notification
	import android.os.Build
	import android.app.Notification
	import android.app.NotificationChannel
	import android.app.NotificationManager
	import androidx.core.app.NotificationCompat
	import androidx.core.app.NotificationManagerCompat

	//SetUp-Config
	//NFCCardData
	val CardID : String = -鍵にしたいCardID-

	//Sesame UserData
	val UserID : String  = -Sesameのメールアドレス-
	val Password : String  = -公式APPから抜き出したパスワード-

	//Primary Sesame
	val PrimarySesameAddress : String = -SesameのBLEアドレスEx:FF:00:FF:00:FF:00-
	val PrimarySesameMacData : String  = -Sesameのアドバタイズパケットの該当部分:0000C1020202020202-
	val PrimarySesameLockMinAngle : Int = 10
	val PrimarySesameLockMaxAngle : Int = 270

	//Secondary Sesame
	val SecondarySesameAddress : String = ""
	val SecondarySesameMacData : String  = ""
	val SecondarySesameLockMinAngle : Int = 10
	val SecondarySesameLockMaxAngle : Int = 270

	class MainActivity : AppCompatActivity() {
	    //SesameDevice
	    private lateinit var PrimarySesame : SesameDevice
	    private lateinit var SecondarySesame : SesameDevice
	    private val HasSecondary : Boolean = (SecondarySesameAddress.length > 0)

	    //BLE用のアダプター作成
	    private val BluetoothAdapter: BluetoothAdapter by lazy(LazyThreadSafetyMode.NONE) {
	        val bluetoothManager = getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager
	        bluetoothManager.adapter
	    }

	    //NFC-Certification
	    private var isNFCLoad : Boolean = false

	    @ExperimentalUnsignedTypes
	    override fun onCreate(savedInstanceState: Bundle?) {
	        super.onCreate(savedInstanceState)

	        //SesameDeviceの初期化
	        PrimarySesame = SesameDevice(BluetoothAdapter.getRemoteDevice(PrimarySesameAddress), PrimarySesameMacData, PrimarySesameLockMinAngle, PrimarySesameLockMaxAngle, this)
	        if(HasSecondary){
	            SecondarySesame = SesameDevice(BluetoothAdapter.getRemoteDevice(SecondarySesameAddress), SecondarySesameMacData, SecondarySesameLockMinAngle, SecondarySesameLockMaxAngle,this)
	        }

	        //このアプリを開く[起因]はNFC情報を読み取り
	        //A1.[起因]は、AndroidManifest.xmlに規定した<intent-filter>に起因する
	        if(!isNFCLoad && NfcAdapter.ACTION_NDEF_DISCOVERED == intent.action) {
	            //カードのID情報を取得
	            var tagId: String = ""
	            for (byte in intent.getByteArrayExtra(NfcAdapter.EXTRA_ID)) {
	                tagId += String.format("%02X", byte) + ":"
	            }
	            //A2.読み込んだカードIDが一致すれば、BLE操作を開始
	            println("Btest:" + tagId)
	            if (tagId == CardID + ":") {
	                Notificate("ちょっとまってね！")
	                isNFCLoad = true
	                PrimarySesame.ConnectGatt()
	                if (HasSecondary) {
	                    SecondarySesame.ConnectGatt()
	                }
	            }
	        }
	        finishAndRemoveTask()
	    }

	    //Sesameに対して操作を開始
	    @ExperimentalUnsignedTypes
	    fun StartToggle(){
	        if(isNFCLoad){
	            isNFCLoad = false
	            PrimarySesame.PrimaryStart()
	        }
	    }

	    //SesameDeviceからの結果を受信
	    @ExperimentalUnsignedTypes
	    fun SetCompleteState(mCommandState : Int, mLockState : Int){
	        var _LockState = mLockState
	        if(HasSecondary && mCommandState == 4){
	            SecondarySesame.SecondayStart(mLockState)
	            _LockState = 0
	        }

	        when(_LockState){
	            1->Notificate("いってらっしゃい！")
	            2->Notificate("おかえりなさい！")
	        }
	    }

	    //通知処理
	    fun Notificate(mContent:String){
	        // Channelの取得と生成
	        val channelId = "KeyToggle"

	        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
	            //Android8 Oreo以上の場合
	            //通知チャンネルを作り
	            val channel = NotificationChannel(channelId, "Beacon", NotificationManager.IMPORTANCE_HIGH).apply {
	                this.description = "Sesame施錠・解錠通知"
	                this.lockscreenVisibility = Notification.VISIBILITY_PUBLIC
	                this.enableVibration(true)
	            }

	            // システムにチャンネルを登録する
	            val manager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
	            manager.createNotificationChannel(channel)
	        }

	        val notification = NotificationCompat.Builder(this,channelId).apply {
	            setContentTitle("Sesame施錠・解錠通知")
	            setContentText(mContent)
	            priority = NotificationCompat.PRIORITY_DEFAULT
	        }

	        with(NotificationManagerCompat.from(this)){
	            notify(1, notification.build())
	        }
	    }
	}

SesameDevice.kt

	package jp.sakujira.opensesame

	import android.bluetooth.*
	import java.security.MessageDigest
	import java.util.*
	import javax.crypto.Mac
	import javax.crypto.spec.SecretKeySpec
	import kotlinx.coroutines.GlobalScope
	import kotlinx.coroutines.launch

	class SesameDevice(val mDevice : BluetoothDevice, val mMadData :String, val mMinLockAngle : Int, val mMaxLockAngle : Int, val SesameService: MainActivity){
	    //Gattへの操作
	    private var mGatt: BluetoothGatt? = null

	    //SesameがもつBLEのサービス検索詞
	    private val ServiceOperationUuid : UUID          = UUID.fromString("00001523-1212-efde-1523-785feabcd123")
	    private val CharacteristicCommandUuid : UUID     = UUID.fromString("00001524-1212-efde-1523-785feabcd123")
	    private val CharacteristicStatusUuid : UUID      = UUID.fromString("00001526-1212-efde-1523-785feabcd123")
	    private val CharacteristicAngleStatusUuid : UUID = UUID.fromString("00001525-1212-efde-1523-785feabcd123")

	    //サービス検索結果：各サービスへの接続詞
	    var CharStatus: BluetoothGattCharacteristic? = null
	    var CharCmd:BluetoothGattCharacteristic? = null
	    var CharAngle:BluetoothGattCharacteristic? = null

	    //状態管理用の変数
	    var CommandState : Int = 0          //次のコマンドを何を投げるかを管理
	    var SesameState : Int  = 0         //Sesame側のカウンターを管理
	    var LockState : Int    = 0         //開けるか・閉めるか・状態を聞くかを管理
	    var ConnectState : Int = 0 //Gatt接続状況を管理

	    //送信データ管理
	    var mSendData: ArrayList<ByteArray> = ArrayList()
	    var mSendPointer : Int = 0

	    //Gattの接続を試みる
	    @ExperimentalUnsignedTypes
	    fun ConnectGatt(){
	        when(ConnectState){
	            0->{//未接続状態
	                ConnectState = 1//接続テスト中へ変更
	                mGatt = mDevice.connectGatt(SesameService, false, mGattcallback)
	            }
	            2->{//接続状態
	                ConnectedGatt()
	            }
	        }
	    }

	    //Gattの接続の成功
	    @ExperimentalUnsignedTypes
	    fun ConnectedGatt(){
	        SesameService.StartToggle()
	    }

	    //Gattの接続終了処理
	    fun DisConnectGatt(){
	        CharStatus = null
	        CharCmd    = null
	        CharAngle  = null
	        ConnectState = 0
	        SesameState  = 0
	        LockState    = 0
	        ConnectState = 0
	    }

	    //ここから処理順番管理
	    @ExperimentalUnsignedTypes
	    //For PrimaryDevice
	    fun PrimaryStart(){
	        StateCommand()
	    }

	    //For SecondayDevice
	    @ExperimentalUnsignedTypes
	    fun SecondayStart(mLockState : Int){
	        LockState = mLockState
	        ConnectState = 10
	        StateCommand()
	    }

	    //次処理実行
	    @ExperimentalUnsignedTypes
	    fun NextState(){
	        CommandState += 1

	        //変更点：別スレッドで次のコマンドを実行
	        GlobalScope.launch{
	            StateCommand()
	        }
	    }

	    //処理順番管理
	    @ExperimentalUnsignedTypes
	    fun StateCommand(){
	        println("Btest:Start-" + CommandState)
	        when (CommandState){
	            //For PrimaryDevice
	            0->GetSesameStatus() //B0.Sesameの状態取得：Sesameカウントを取得
	            1->SendStartData()   //B1.LockState:0を送信しAngleを検知させる
	            2->GetSesameAngle()  //B2.角度を取得し、施錠・解錠を取得
	            3->SendStartData()   //B3.施錠・解錠コマンドを送信
	            4->ControlComplete() //B4.ループを終えてアプリを閉じる

	            //For SecondaryDevice
	            10->GetSesameStatus() //B10.Sesameの状態取得：Sesameカウントを取得
	            11->SendStartData()   //B11.施錠・解錠コマンドを送信
	            12->ControlComplete() //B12.ループを終えてアプリを閉じる
	        }
	        println("Btest:End-" + CommandState)
	    }
	//ここまで処理順番管理

	    //ここから通信開始関数
	    //Sesameからステータスを取得-開始
	    fun GetSesameStatus(){
	        println("Btest:GetStatus")
	        mGatt!!.readCharacteristic(CharStatus)
	    }

	    //Sesameに対してデータを送信-開始
	    @ExperimentalUnsignedTypes
	    fun SendStartData(){
	        println("Btest:SendData:")
	        //各パラメータから送信データを作成
	        val PayLoad = CreateSign(LockState,"", Password, mMadData, UserID, SesameState)
	        //データをmtu:20byteごとに分割
	        mSendData = SplitByteArray(PayLoad)
	        mSendPointer = 0
	        //送信データをセット
	        println("Btest:SendData:Pointer"+ mSendPointer)
	        println("Btest:SendData:" + ByteArrayToString(mSendData[mSendPointer]))
	        CharCmd!!.setValue(mSendData[mSendPointer])
	        mGatt!!.writeCharacteristic(CharCmd)
	    }

	    //Sesameから角度情報取得-開始
	    fun GetSesameAngle(){
	        println("Btest:GetRange")
	        mGatt!!.readCharacteristic(CharAngle)
	    }

	    //Sesameへの操作処理終了処理
	    @ExperimentalUnsignedTypes
	    fun ControlComplete(){
	        println("Btest:EndConnect!")
	        //BLEのペアリングを保存
	        if(mDevice.bondState == BluetoothDevice.BOND_NONE) {
	            println("Btest:BOND-NONE")
	            if(mDevice.createBond()){
	                println("Btest:BOND-Create")
	            }
	        }
	        //Serviceに状態を報告
	        SesameService.SetCompleteState(CommandState,LockState)
	        mGatt?.disconnect()
	    }
	//ここまで通信開始関数

	    //ここから通信応答処理
	    @ExperimentalUnsignedTypes
	    private val mGattcallback: BluetoothGattCallback = object : BluetoothGattCallback() {
	        //B0.Sesameへの接続確認
	        override fun onConnectionStateChange(gatt: BluetoothGatt?, status: Int, newState: Int) {
	            super.onConnectionStateChange(gatt, status, newState)
	            //B0.接続確立を確認して
	            if (newState == BluetoothProfile.STATE_CONNECTED) {
	                //B0.サービスの検索を開始
	                println("Btest:GattSa-Search")
	                gatt?.discoverServices()
	            }else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
	                DisConnectGatt()
	            }
	        }

	        //B0.サービスの検索完了：結果を分析
	        override fun onServicesDiscovered(gatt: BluetoothGatt?, status: Int) {
	            super.onServicesDiscovered(gatt, status)

	            //B0.サービスの検索が成功を確認して
	            if(status == BluetoothGatt.GATT_SUCCESS) {
	                println("Btest:GattSa-OK!")
	                //B0.サービスの一覧表を取得
	                val GattSList: List<BluetoothGattService> = gatt?.services as List<BluetoothGattService>

	                //B0.サービスの一覧表を走査
	                for (GaService: BluetoothGattService in GattSList) {
	                    println("Btest:>" + GaService.uuid.toString())

	                    //B0.事前にserviceOperationUuidと一致したものがあったら、
	                    if (GaService.uuid.equals(ServiceOperationUuid)) {
	                        //B0.サービスが持っている機能・情報の一覧を取得
	                        val GattCList: List<BluetoothGattCharacteristic> = GaService.characteristics
	                        //B0.機能・情報の一覧の走査
	                        for (GaCharacteristic: BluetoothGattCharacteristic in GattCList) {
	                            println("Btest:>>" + GaCharacteristic.uuid.toString())
	                            //B0.Sesameの状態情報取得の接続詞を取得
	                            if (GaCharacteristic.uuid.equals(CharacteristicStatusUuid)){
	                                CharStatus = GaCharacteristic
	                            }
	                            //B0.Sesameのコマンド情報取得の接続詞を取得
	                            if (GaCharacteristic.uuid.equals(CharacteristicCommandUuid)){
	                                CharCmd = GaCharacteristic
	                            }
	                            //B0.Sesameの角度情報取得の接続詞を取得
	                            if (GaCharacteristic.uuid.equals(CharacteristicAngleStatusUuid)){
	                                CharAngle = GaCharacteristic
	                            }
	                        }
	                    }
	                }
	                //B0.走査した結果が全てあるかどうかをチェックし、次の状態に移行
	                if(!(CharStatus == null || CharCmd == null || CharAngle == null)){
	                    ConnectState = 2
	                    ConnectedGatt()
	                }
	            }
	        }

	        //接続詞を使っての読み込み依頼した結果を分析
	        override fun onCharacteristicRead(gatt: BluetoothGatt?, characteristic: BluetoothGattCharacteristic?, status: Int) {
	            super.onCharacteristicChanged(gatt, characteristic)

	            println("Btest:" + characteristic!!.uuid.toString())
	            when (characteristic.uuid) {
	                //B2.依頼内容が「AngleStatus」であれば
	                CharacteristicAngleStatusUuid -> {
	                    val data: ByteArray = characteristic.value      //データを取得
	                    println("Btest:" + ByteArrayToString(data))     //データをFF:FF形式で表示
	                    val angleRaw = ByteArrayToInt(data.slice(2..3).toByteArray())   //データを切り出して、Byte→Intへ
	                    val angle = Math.floor(angleRaw * 360 / 1024.0)      //角度を計算

	                    //B2.LockStateは、操作コマンドと兼ねているため 1:解錠(操作：施錠) 2:施錠(操作：解錠)と逆になる
	                    LockState = 1;
	                    if (angle < mMinLockAngle || angle > mMaxLockAngle) {
	                        LockState = 2;
	                    }
	                    println("Btest:Byte:" + ByteArrayToString(data) + ", Angle:" + angle + ", LockStatus:" + (LockState==2))
	                    NextState()//次の状態に移行
	                }
	                //B0.依頼内容が「Status」であれば
	                CharacteristicStatusUuid -> {
	                    val data: ByteArray = characteristic.value          //データの取得
	                    val Sn: Int = ByteArrayToInt(data.slice(6..9).toByteArray()) + 1    //Sesameカウンターを取得
	                    val Err: Int = ByteArrayToInt(data.slice(14..14).toByteArray()) + 1 //エラーコードを取得
	                    //エラーコードリスト
	                    val errMsg = arrayOf(
	                        "Timeout",
	                        "Unsupported",
	                        "Success",
	                        "Operating",
	                        "ErrorDeviceMac",
	                        "ErrorUserId",
	                        "ErrorNumber",
	                        "ErrorSignature",
	                        "ErrorLevel",
	                        "ErrorPermission",
	                        "ErrorLength",
	                        "ErrorUnknownCmd",
	                        "ErrorBusy",
	                        "ErrorEncryption",
	                        "ErrorFormat",
	                        "ErrorBattery",
	                        "ErrorNotSend"
	                    )
	                    println("Btest:Byte:" + ByteArrayToString(data) + ", Sn:" + Sn + ", Err:" + errMsg[Err])
	                    SesameState = Sn    //B0.Sesameカウンタを記録
	                    NextState()//次の状態に移行
	                }
	            }
	        }

	        //B1.B3.送信データの受領確認後、次パケットを送信
	        override fun onCharacteristicWrite( gatt: BluetoothGatt?, characteristic: BluetoothGattCharacteristic?, status: Int) {
	            super.onCharacteristicWrite(gatt, characteristic, status)

	            if(status == BluetoothGatt.GATT_SUCCESS){
	                //送信完了したので、ポインタを一つ進める
	                mSendPointer += 1
	                //全部送信し終えたら
	                if(mSendData.size <= mSendPointer){
	                    println("Btest:SendData:Pointer-End")
	                    //送信データを綺麗にしてから
	                    mSendData = ArrayList()
	                    mSendPointer = 0
	                    //次の処理へ
	                    NextState()
	                }else {
	                    println("Btest:SendData:Pointer"+ mSendPointer)
	                    println("Btest:SendData:" + ByteArrayToString(mSendData[mSendPointer]))
	                    //ここも別スレッドで残りを送信！
	                    GlobalScope.launch {
	                        CharCmd?.setValue(mSendData[mSendPointer])
	                        gatt?.writeCharacteristic(CharCmd)
	                    }
	                }
	            }
	        }
	    }
	//ここまで通信応答処理

	//ここからデータ処理関数
	    //Sesameに対してデータを送信(BLEの20バイト制約のため、分割して送信)
	    @ExperimentalUnsignedTypes
	    fun SplitByteArray(pPayload : ByteArray): ArrayList<ByteArray>{
	        val Data : ArrayList<ByteArray> = ArrayList()

	        //送信は20バイトごとに送信
	        //分割する際には、[先頭：01][中間：02][最後:04]と付ける必要がある
	        //なので、一回の送信は19バイトごと
	        for(i in 0..pPayload.size step 19){
	            val wSz = Math.min(pPayload.size-i,19)      //送るデータが最後かどうか？
	            var wCc : Int = 2           //初期値は[中間:02]とする
	            var wBuf : ByteArray  = ByteArray(wSz+1)    //送信用データ場所を作成

	            //先頭・最後を判定
	            if(wSz < 19){
	                wCc = 4
	            }else if(i == 0){
	                wCc = 1
	            }

	            //バイト列に分割詞をつける
	            wBuf[0] = wCc.toByte()
	            //送信データからバイト列を切り出し
	            wBuf = ByteArrayCopy(wBuf, 1, pPayload, i,wSz)
	            println("Btest:CutData:" + ByteArrayToString(wBuf))
	            Data.add(wBuf)
	        }
	        return Data
	    }

	    //認証用バイトデータを作成(普段Byteを使わないから、符号あり・なしに振り回された、、、）
	    fun CreateSign(pCode:Int, pPayload: String, pPassword : String, pMacData : String, pUserid : String, pNonce: Int) : ByteArray{
	        //バイト配列の場所を作成
	        var wBufnonPw : ByteArray = ByteArray(59 - 32 + pPayload.toByteArray().size)

	        //manufactreDataのデータをコピー
	        val ByteMacData : ByteArray = HexStringToByteArray(pMacData)
	        println("Btest:Mac:" + ByteArrayToString(ByteMacData.sliceArray(3..ByteMacData.size-1)))
	        wBufnonPw = ByteArrayCopy(wBufnonPw, 0, ByteMacData.sliceArray(3..ByteMacData.size-1),0,6)

	        //md5のデータをコピー
	        val md5 = MessageDigest.getInstance("MD5").digest(pUserid.toByteArray())
	        println("Btest:md5:" + ByteArrayToString(md5))
	        wBufnonPw = ByteArrayCopy(wBufnonPw,6,md5,0,16)

	        //Status(Nonce)をコピー
	        println("Btest:Nonce:" + ByteArrayToString(InttoByteArrayUnsign(pNonce)))
	        wBufnonPw = ByteArrayCopy(wBufnonPw,22,InttoByteArrayUnsign(pNonce),0,4)

	        //Codeをコピー
	        println("Btest:Code:" + ByteArrayToString(InttoByteArrayUnsign(pCode)))
	        wBufnonPw = ByteArrayCopy(wBufnonPw,26,InttoByteArrayUnsign(pCode),0,1)

	        //Payloadをコピー
	        wBufnonPw = ByteArrayCopy(wBufnonPw, 27, pPayload.toByteArray(),0, pPayload.toByteArray().size)

	        //パラメータの結果を確認
	        println("Btest:PrameterOK!:" + ByteArrayToString(wBufnonPw))

	        //「生成したパラメータ」を「パスワード」を使って暗号化
	        //「パスワード」を使用する暗号機を作成
	        val key = SecretKeySpec(HexStringToByteArray(pPassword), "HmacSHA256")
	        val mac = Mac.getInstance("HmacSHA256")
	        mac.init(key)
	        val wBufKey = mac.doFinal(wBufnonPw)
	        println("Btest:wBufkey:" + ByteArrayToString(wBufKey))

	        //全部を連結
	        var wBuf : ByteArray = ByteArray(pPayload.toByteArray().size + 59)
	        wBuf = ByteArrayCopy(wBuf,0, wBufKey,0, 32)
	        wBuf = ByteArrayCopy(wBuf,32, wBufnonPw,0, wBufnonPw.size)
	        println("Btest:ALL:" + ByteArrayToString(wBuf))

	        return  wBuf
	    }

	    //Intを符号なしのバイト列に変換
	    fun InttoByteArrayUnsign(num : Int): ByteArray{
	        val wHexString : String = num.toString(16).padStart(12,'0')//文字埋めを12桁にしているのはByteArrayCopyで参照値外がないようにするため
	        val wResult = HexStringToByteArray(wHexString)
	        return  wResult.reversedArray() //1101→03F3となるが、送信データ上ではF303と逆にする必要がある
	    }

	    //HEX文字列をバイト配列にキャスト
	    fun HexStringToByteArray(pHexString: String): ByteArray {
	        val wBArray = ByteArray(pHexString.length / 2)
	        for (index in 0 until wBArray.count()) {
	            val pointer = index * 2
	            wBArray[index] = pHexString.substring(pointer, pointer + 2).toInt(16).toByte()
	        }
	        return wBArray
	    }

	    //Byte配列を指定位置にコピー
	    fun ByteArrayCopy(pTarget: ByteArray, pPosition: Int, pCopy: ByteArray, pStart: Int, pLength : Int):ByteArray{
	        for(i in 0 until pLength){
	            pTarget[pPosition + i] = pCopy[pStart + i]
	        }
	        return pTarget
	    }

	    //Byte配列を文字列化
	    fun ByteArrayToString(pBytes: ByteArray): String{
	        var wRsult : String = ""
	        for (b in pBytes) {
	            wRsult += String.format("%02X", b) + ":"
	        }
	        return wRsult
	    }

	    //Byte配列を数値化
	    @ExperimentalUnsignedTypes
	    fun ByteArrayToInt(pBytes: ByteArray): Int {
	        var wResult: Int = 0
	        for (b in pBytes.reversed()){
	            wResult = wResult shl 8
	            wResult += b.toUByte().toInt()
	        }
	        return wResult
	    }
	//ここまでデータ処理関数
	}

AndroidManifest.xml

	<?xml version="1.0" encoding="utf-8"?>
	<manifest xmlns:android="http://schemas.android.com/apk/res/android"
	    package="jp.sakujira.opensesame">

	    <uses-permission android:name="android.permission.NFC" />
	    <uses-permission android:name="android.permission.BLUETOOTH" />
	    <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
	    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
	    <uses-feature android:name="android.hardware.bluetooth_le" android:required="true"/>

	    <application
	        android:allowBackup="true"
	        android:icon="@mipmap/ic_launcher"
	        android:label="@string/app_name"
	        android:roundIcon="@mipmap/ic_launcher_round"
	        android:supportsRtl="true"
	        android:theme="@style/AppTheme">
	        <activity
	            android:name=".MainActivity"
	            android:theme="@android:style/Theme.Translucent"><!--実行時にActivityが見えないように-->
	            <intent-filter>
	                <action android:name="android.intent.action.MAIN" />
	                <category android:name="android.intent.category.LAUNCHER" />
	                <category android:name="android.intent.category.DEFAULT" />
	            </intent-filter>
	            <intent-filter>
	                <action android:name="android.nfc.action.NDEF_DISCOVERED" />
	                <category android:name="android.intent.category.DEFAULT" />
	                <data android:mimeType="text/plain" />
	            </intent-filter>
	        </activity>
	    </application>
	</manifest>

kotlinx.coroutinesを使うので、buid.gradle(app)に以下を追加

	dependencies {
	・・・・
	implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:X.X.X'
	}

##参考資料
####アドバタイズパケットの分析とBeacon検討
aBeacon ~iBeacon を Android で受信する~
https://gaprot.jp/2014/03/27/abeacon/

Android Beacon Library
https://altbeacon.github.io/android-beacon-library/index.html

[Android][iBeacon] Android Beacon Library パラっと解説 その4 [距離観測]
https://dev.classmethod.jp/articles/android-beacon-library-introduction-4/

####Kotlinのサービス・レシーバーの書き方について
BLEMonitor
https://github.com/SergeiSafrigin/BLEMonitor/blob/master/src/kcg/ble/BleMonitorService.java

AndroidでServiceと通信する(Kotlinサンプル)
https://qiita.com/satken2/items/49dd76d848ceb208e937

コジオニルク-プロセスが同一の場合の例
https://kojion.com/posts/649

####Notificationの書き方
OreoでNotificationを表示させる方法
https://qiita.com/naoi/items/367fc23e55292c50d459

Androidで(ヘッドアップ)通知を表示するサンプル
https://qiita.com/gpsnmeajp/items/33392aac8d00b55bce75

####Activityを見せなくする方法
Theme.Translucentを継承せずにActivityの背景を透過にする
https://qiita.com/kgmyshin/items/a259f31b06ebab637044

####考察:このアプリのセキュリティについて
単純にこのアプリは
　Sesame公式APP：ロック解除→アプリ起動→Sesame認識→ロック解除
を
　このアプリ：ロック解除→NFCかざす→ロック解除
としているので、公式アプリと同じ位の危険性と考えています。
自宅や職場のwifiでロック解除するアプリを使っていたとしても、同じくらい、多少待ち時間が少ない分危険性は高くなるかなと考えています。

仮に、NFCカード側を持っていかれても、Sesameに関する情報はなにもないので問題なし。
また、NFCカード、スマホ、スマホのロック解除が揃っているなら、公式アプリで解除するでしょうし、そもそも鍵を開けるためにはBLEの通信範囲にいないといけないので、「こんな事をしている」ことを知っている人に絞られるかなっと思ったり。

まぁ、Sesameを着けているいる時点である程度は家の鍵にバックドアの可能性が増えていると思っているので、まぁいいかなっと自分自身は考えています。
（まぁ、アプリの情報抜かれたらと考えるのであれば、そもそもこのアプリ自体が抜いているので、、、、）

####編集後記
この話についてはココで区切り。
あとやるとしたら、施錠解錠の動画の撮影くらいかな～
最終版の書き方にして、かざす→施錠・解錠の時間が短くなったので、バックグランドサービスでSesameをScanLEで定期的に探そうかと思っていましたが、しなくていいかなっと感じています。
（その調査の結果、アドバタイズパケットからMacDataの位置が理解できたのですが、、、、

楽しかったけど、疲れた、、、。