【Android】5Gを取得する


# はじめに

こんにちは、Life is Tech ! メンターのなべです。
ひょんなことからVtuberにハマってしまって最近は作業しながらラジオ感覚で視聴してます。あくしおしか勝たん。

昨日のらっしーねの記事、二人零和有限確定完全情報ゲームの話でした。
オリジナルのゲームを自分でも作ってみたくなりましたね。

Life is Tech ! Advent Calendar 2020のDay25です。
ついに最終回です。メリークリスマス！！！！
皆さんはサンタさんに何をもらいましたか？
私は自分の心の中にいるお財布が緩いサンタさんにお願いしてちょっと良いスニーカーを買いました。
年末のイベントって楽しいことしかないのでこれから楽しみですね！

さて、サクッと最新技術を吸収しちゃいましょう。

# なぜこの記事を書いたか

最近、Pixel5を購入しました。ほとんど片手で操作できるように組まれてたり画質とか処理とかもサクサクでおすすめです！
~~(スピーカーはちょっと不満ですが…)~~

現在利用している通信キャリアはauと楽天モバイルのハイブリッドです。
このご時世で外出の頻度も落ちたのもありますが、今まで20GB/月だった契約も要らなくなってしまいました。
当分は通信料金2200円/月+本体料金で行けそうです。

さて、最近日本でも普及し始めた5G回線ですが、皆さんは恩恵を受けられているでしょうか。
10月からPixel5を使い始めた私ですが、ここ2ヶ月間で一度も5G回線になったことを見たことがありません。常に本当に普及しているの？って思っています。

仕事は基本都内ですので、都内で歩いていれば出会うでしょう！という気ではいたのですが…いつまで経っても4G。
5Gエリア見てみましょう。

auの5G回線エリアは[こちら](http://www13.info-mapping.com/au/map/index.aspx?maptype=5g&l=35.6939457%2c139.7011918&z=6)から、楽天の5G回線エリアは[こちら](https://network.mobile.rakuten.co.jp/area/5g_area/)から。

現段階では楽天は世田谷しか普及してないみたいですね。結構ピンポイント…
auはちらほら見られますが、特に渋谷エリアが多いみたいです。

今後は5G回線をせっかく契約したので、できるなら繋がってみたいなと思って今回は検知するアプリの記事を書こうと思います。

# 5Gとは

簡単に言えば『高速で大容量通信ができる』『低遅延の通信ができる』『同時接続数が増加』の３点です。
まだ普及が始まったばかりなので、今後3D動画や多数とのオンラインゲーム、VR(仮想現実)体験など、5Gのメリットを生かしたサービスが利用できるのが楽しみです。
メリットも多いですが記事によってはデメリットもいろいろ多いようなので、詳しくは調べてみてください。

# 実装
こんなのを作ります。『取得開始』ボタンを押すと始まって、移動したときにネットワーク回線が変わるとログが残っていく仕様にしています。
<img src = "https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/140475/aa8c0e62-7c5e-1747-cc2f-b7de56b0cc42.gif" width = "720dp">

## パーミッションを取る
まずはパーミッションを要求しましょう。
今回のAPIを利用するには`READ_PHONE_STATE`が必要です。

``` xml:AndroidManifest.xml
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
```

## APIレベルの確認、パーミッションの許可
Android11からの機能なので基本的にはAPI Level 30(R)になります。
現段階では最新版なので、Android Q以下では機能しないのでApiレベルで処理をするかどうかを判別します。
起動時に権限が許可されているか確認し、確認されていなかった場合はダイアログで要求します。

今回やるApiレベルとパーミッションの確認はif文にしていますがネストしているので改善しようとは思っています。

```kotlin:MainActivity.kt
    private fun checkPermission() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            // API30以上かどうか
            if (ContextCompat.checkSelfPermission(
                    this,
                    Manifest.permission.READ_PHONE_STATE
                ) != PackageManager.PERMISSION_GRANTED
            ) {
                if (ActivityCompat.shouldShowRequestPermissionRationale(
                        this,
                        Manifest.permission.READ_PHONE_STATE
                    )
                ) {
                    // 1度許可を取ったが外されている場合
                    Toast.makeText(this, "パーミッションがOFFになっています。", Toast.LENGTH_SHORT).show()
                    permissionIntent()
                } else {
                    ActivityCompat.requestPermissions(
                        this,
                        arrayOf(Manifest.permission.READ_EXTERNAL_STORAGE),
                        REQUEST_CODE
                    )
                }
            } else {
                // SDKバージョンが問題なく、全てのパーミッションが取れている場合
                getNetworkType()
            }

        } else {
            Toast.makeText(this, "APIレベルがたりません。", Toast.LENGTH_SHORT).show()
        }
    }
```

### パーミッションの許可が降りていない場合
設定画面にインテントして、許可を取るように促します。

```kotlin:MainActivity.kt
    private fun permissionIntent() {
        val intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS)
        val uri: Uri = Uri.fromParts("package", packageName, null)
        intent.data = uri
        startActivity(intent)
    }
```

`onRequestPermissionsResult()`を使うことでインテントで許可したかどうかを判定します。

```kotlin:MainActivity.kt
    override fun onRequestPermissionsResult(
        requestCode: Int,
        permissions: Array<out String>,
        grantResults: IntArray
    ) {
        if (requestCode == REQUEST_CODE) {
            // requestPermissionsで設定した順番で結果が格納されている
            if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                // 許可された(されている)ので処理を続行
                getNetworkType()
            } else {
                // パーミッションのリクエストに対して許可せずアプリに戻った場合、ここが走る
                Toast.makeText(this, "パーミッションが許可されていません。", Toast.LENGTH_SHORT).show()
                permissionIntent()
            }
            return
        }
        super.onRequestPermissionsResult(requestCode, permissions, grantResults)
    }
```

見た方がわかりやすいと思うので、ここまでの流れのgifです。

<img src = "https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/140475/1082c24a-9255-0fa4-823e-d214bcda51ce.gif" width = "320dp">


## ネットワークタイプを取得する

ネットワークタイプを確認するコードです。
AndroidではTelephonyDisplayInfoを利用します。

```kotlin:MainActivity.kt
    private fun getNetworkType() {
        val telephonyManager = getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManager
        telephonyManager.listen(object : PhoneStateListener() {

            @RequiresApi(Build.VERSION_CODES.R)
            override fun onDisplayInfoChanged(telephonyDisplayInfo: TelephonyDisplayInfo) {
                if (ActivityCompat.checkSelfPermission(
                        applicationContext,
                        Manifest.permission.READ_PHONE_STATE
                    ) != PackageManager.PERMISSION_GRANTED
                ) {
                    return
                }
                super.onDisplayInfoChanged(telephonyDisplayInfo)

                when (telephonyDisplayInfo.overrideNetworkType) {
                    // LTE等(4G回線)
                    TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE -> setNetworkStateText("LTE等")
                    // LTE等(キャリアアグリゲーション)
                    TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_LTE_CA -> setNetworkStateText("LTE等(キャリアアグリゲーション)")
                    // 	LTE Advanced Pro（5Ge）
                    TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_LTE_ADVANCED_PRO -> setNetworkStateText(
                        "LTE Advanced Pro（5Ge）"
                    )
                    // 5G NR（Sub-6）ネットワーク
                    TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA -> setNetworkStateText("5G NR（Sub-6）")
                    // 5G mmWave（5G+ / 5G UW）ネットワーク
                    TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA_MMWAVE -> setNetworkStateText(
                        "5G mmWave（5G+ / 5G UW）"
                    )
                    else -> setNetworkStateText("その他")
                }
            }
        }, PhoneStateListener.LISTEN_DISPLAY_INFO_CHANGED)
    }
```

コメントアウトでは書きましたがネットワークの種類を取得しています。これをTextViewに表示させてあげればこれで完成です。
でも、ログを残したいなというところでRecyclerViewで表示させようと思います。

## RecyclerViewを使ってログを表示する
RecyclerViewについては他に良記事がいっぱいあるので探してみてください。
レイアウト部分は一旦省きます！


### Dataクラスを作成する
データクラスを作成します。
シンプルなので時間と内容だけの2つです。

```kotlin:NetworkData.kt
data class NetworkData(
    val time: String,
    val state: String
)
```

### Adapterを作る
RecyclerViewAdapterを制作します。
これも必要最低限のものだけにしています。

```kotlin:RecyclerViewAdapter.kt
class RecyclerViewAdapter(private val context: Context) :
    RecyclerView.Adapter<RecyclerViewAdapter.ViewHolder>() {

    private val items: MutableList<NetworkData> = mutableListOf()

    class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
        val time: TextView = view.findViewById(R.id.dateTextView)
        val state: TextView = view.findViewById(R.id.stateTextView)
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
        val view = LayoutInflater.from(context).inflate(R.layout.network_cell, parent, false)
        return ViewHolder(view)
    }

    override fun onBindViewHolder(holder: ViewHolder, position: Int) {
        val item = items[position]
        holder.time.text = item.time
        holder.state.text = item.state
    }

    override fun getItemCount(): Int {
        return items.size
    }

    fun add(item: NetworkData) {
        this.items.add(items.size, item)
        notifyDataSetChanged()
    }
}
```

## RecyclerViewにデータを表示する
上記で制作したAdapterで制作したaddメソッドを使って追加します。
`LocalDateTime`を使って、時間を自分の表示したいように変更します。今回は`年-月-日 時:分:秒`にしています。

```kotlin:MainActivity.kt
    private fun setNetworkStateText(state: String) {
        val date = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))
        val networkData = NetworkData(date, state)
        adapter.add(networkData)
        recyclerView.scrollToPosition(recyclerView.adapter?.itemCount?.minus(1)!!)
    }
```
RecyclerViewは新たな情報を追加されると下に追加されていきます。新しい情報をすぐに出すべきなので、一番下にスクロールすて見やすいようににしています。

```kotlin
recyclerView.scrollToPosition(recyclerView.adapter?.itemCount?.minus(1)!!)
```

これで完成となります。

<details><summary>Tips</summary><div>

## ネットワークサービスが定額制かどうか取得する

これは実際に使いどころは今のところわかりませんが、`onCapabilitiesChanged`を使うことで回線が定額制かどうかをBoolean型で返すことができます。

```kotlin:MainActivity.kt
    private fun getNetWorkService() {
        val manager = getSystemService(CONNECTIVITY_SERVICE) as ConnectivityManager
        manager.registerDefaultNetworkCallback(object : ConnectivityManager.NetworkCallback() {
            override fun onCapabilitiesChanged(
                network: Network,
                networkCapabilities: NetworkCapabilities
            ) {
                super.onCapabilitiesChanged(network, networkCapabilities)

                // 通信環境が定額制かどうか
                val isNotFlowPay =
                    networkCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED) ||
                            networkCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_TEMPORARILY_NOT_METERED)

            }
        })
    }
```
この値が true の場合は、ネットワークを定額制として扱うことができます。


</div>
</details>


# 終わりに


このアプリでは5Gを取得できるようにするだけの記事ですが、他に機能を追加するならこのようなものを足せば良いかなぁと思いました。

- RecyclerViewに追加されたセルにAnimationを実装する 
- バックグラウンドでリアルタイムで確認して5Gを取得したら通知するようにする
    - 位置情報も取得して情報を残したい
- Roomなどのデータベースを使ってデータの永続化をする
- ネットワーク回線速度チェック

5Gはこれから普及していくはず…なのでこれから期待したいと思います！

最後まで読んでくださりありがとうございました！

# 参考記事

- [アプリに 5G 機能を追加する](https://developer.android.com/about/versions/11/features/5g#meteredness)