1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

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

Last updated at Posted at 2020-05-16

##動作結果
jobi9-m8t0q.gif

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

###manufactreDataMacDataStringの見つけ方
このプログラムではPrimarySesameMacDataの値について
取得場所.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の位置が理解できたのですが、、、、

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

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?