AndroidのKiosk端末化 ～ダイジェスト版～

# 自己紹介

* tomoya0x00
    * GitHub/Twitter/Qiita/mstdn.jp
* 車載関係→IoT系の小規模ベンチャー
* 組み込み/Android/iOS

---

# Kiosk端末とは？

![Interactive kiosk   Wikipedia.png](https://qiita-image-store.s3.amazonaws.com/0/24788/6a6b87e8-9dd2-df8a-6544-986bd7caba98.png)
https://en.wikipedia.org/wiki/Interactive_kiosk

## つまり

* 特定用途に特化した情報端末

---


# Androidでどうやって実現するのか？

---

## こんなことをやりたい

* **通常操作では、専用アプリ（Kioskアプリ）以外の画面には遷移できない**
* 端末を再起動しても、強制的にKioskアプリが立ち上がる
* 特定の手順を踏むと、Kioskアプリ以外の画面に遷移できる

---

# Screen Pinning

---

# Screen Pinningとは

* Android 5.0から追加された機能
* 特定アプリから他画面への遷移を制限する
    * **ただし、Pinning開始時にユーザーの合意が必要**    
    * **解除方法を知っていれば誰でも解除できてしまう**

![screen-pinning.gif](https://qiita-image-store.s3.amazonaws.com/0/24788/38c3ea29-8534-e3ed-c298-6cdcae9489c9.gif)

---

# そこでDevice Owner
---

# Device Ownerとは

* Android 5.0で追加された、Android端末に様々な制約を課すことが出来る特別な管理者
    * [Device administration](https://source.android.com/devices/tech/admin/managed-profiles.html#device_administration)
* Device OwnerをAndroid端末に設定すると、ユーザーの合意無しにPinningを開始できる
    * Device Ownerに指定できるのは、DeviceAdminReceiverを継承したクラスを実装したアプリ
* 通常のユーザー操作では解除できないPinningも実現可能
* **ただし、基本は端末初期化が必要**
    * [Device must be new or factory reset](https://developers.google.com/android/work/prov-devices#device_must_be_new_or_factory_reset)

---

# Kiosk端末化の流れ

1. Device Ownerアプリの準備
2. 端末の初期化
3. Device Ownerアプリのインストール
4. Device Ownerの指定

---

# Device Ownerアプリの準備

1. DeviceAdminReceiver継承クラスの実装
2. AndroidManifestにReceiver登録
3. Pinning開始/終了を実装

---

# DeviceAdminReceiver継承クラスの実装

```kotlin:DeviceAdminReceiver.kt
class AdminReceiver: DeviceAdminReceiver() {
    override fun onEnabled(context: Context?, intent: Intent?) {
        super.onEnabled(context, intent)
        context?.run {
            val deviceAdmin = ComponentName(this, AdminReceiver::class.java)
            val dpm = this.getSystemService(Context.DEVICE_POLICY_SERVICE) as DevicePolicyManager
            dpm.setLockTaskPackages(deviceAdmin, arrayOf(this.packageName))
        }
    }
}
```

* Device Owner指定時、ユーザー合意無しにPinningを開始できるアプリとして自分を登録する

---

# AndroidManifestにReceiver登録

```xml:AndroidManifest.xml
        <receiver
            android:name=".AdminReceiver"
            android:label="device admin"
            android:permission="android.permission.BIND_DEVICE_ADMIN">
            <meta-data
                android:name="android.app.device_admin"
                android:resource="@xml/device_admin"/>

            <intent-filter>
                <action android:name="android.app.action.DEVICE_ADMIN_ENABLED"/>
            </intent-filter>
        </receiver>
```

```xml:res/xml/device_admin.xml
<?xml version="1.0" encoding="utf-8"?>
<device-admin xmlns:android="http://schemas.android.com/apk/res/android">
    <uses-policies>
    </uses-policies>
</device-admin>
```
* `android:resource="@xml/device_admin"`はReceiver登録に必須
* uses-policiesにカメラ無効など追加できるが、Screen Pinningには不要なので省略

---

# Pinning開始/終了を実装

```kotlin:MainActivity.kt
class MainActivity : AppCompatActivity() {
    private lateinit var binding: ActivityMainBinding

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = DataBindingUtil.setContentView(this, R.layout.activity_main)

        binding.kioskOnButton.setOnClickListener {
            this.startLockTask()
        }

        binding.kioskOffButton.setOnClickListener {
            this.stopLockTask()
        }
    }
}
```
---

# 端末の初期化

* ファクトリーリセットする
* WiFi設定はスキップすること
    * 端末によってはDevice Owner指定不可となる
        * MediaPad M2ではNG

---

# Device Ownerアプリのインストール

## NFC

* あらかじめアプリをサーバーにアップロードし、これのURLやWiFi情報などをNFCで転送する
    * [Android 5.0 の Device Owner を有効にする方法](http://gsbina.com/?p=1678)
    * [googlesamples/android-NfcProvisioning](https://github.com/googlesamples/android-NfcProvisioning)

## adb

* 案件で指定された端末がNFC非対応だったので、adbでインストール

---

# Device Ownerの指定

## NFC

* 案件で指定された端末がNFC非対応だったので、詳しくは調べていない
* 前述のandroid-NfcProvisioningで同時にできるっぽい

## adb shell

* 今回の案件ではこちらを採用
 
---

# adb shellでDeviceOwnerを指定

インストールしたアプリをDeviceOwnerとして指定。

```bash
adb shell dpm set-device-owner jp.gr.java_conf.miwax.kioskexample/.AdminReceiver
```

成功すると、下記が表示される。

```
Success: Device owner set to package jp.gr.java_conf.miwax.kioskexample
Active admin set to component {jp.gr.java_conf.miwax.kioskexample/jp.gr.java_conf.miwax.kioskexample.AdminReceiver}
```

---

# デモ

![deviceowner.gif](https://qiita-image-store.s3.amazonaws.com/0/24788/e9e4737d-5ef1-f04b-d1b6-23ca4456076d.gif)

---

# あとやること

* 端末を再起動しても、強制的にKioskアプリが立ち上がる
* 特定の手順を踏むと、Kioskアプリ以外の画面に遷移できる
* バッテリー残量と電波強度の表示
* Kioskアプリが落ちても、自動で再度立ち上がる

---

# 完全版はDroidKaigi or RejectKaigiで！

---

# まとめ

* Android 5.0以降なら、Device Owner + Screen Pinningで他画面への遷移を制限できる
* Device OwnerとなるアプリにはDeviceAdminReceiver継承したクラスの実装が必要
* Device Ownerを指定するには端末の初期化が必要
* 今回使用したソースは [tomoya0x00/KioskExample](https://github.com/tomoya0x00/KioskExample)