Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Kioskアプリと端末の作り方

 Kioskアプリと端末の作り方

Tomoya Miwa

February 07, 2018
Tweet

More Decks by Tomoya Miwa

Other Decks in Programming

Transcript

  1. Copyright © DeNA Co.,Ltd. All Rights Reserved. Kioskアプリと端末の作り方 Feb, 08,

    2018 DroidKaigi 2018 Tomoya Miwa Core System Development Dept. Automotive Business Unit. DeNA Co., Ltd.
  2. Copyright © DeNA Co.,Ltd. All Rights Reserved. 自己紹介 • 三輪

    智也(みわ ともや) @tomoya0x00 • Android & 組み込みエンジニア • 株式会社ディー・エヌ・エー(11/16入社) ◦ オートモーティブ事業本部 基幹システム開発部 • これまでの開発経験 ◦ 車載機器(カーナビなど) ◦ BLEデバイスファームウェア、BLEアプリ(iOS/Android) ◦ KioskなAndroidアプリ 2
  3. Copyright © DeNA Co.,Ltd. All Rights Reserved. アウトライン •Kiosk端末とは? •Kiosk端末の実現方法

    •活用事例紹介:タクベル •Kiosk端末開発のノウハウ •まとめ 4
  4. Copyright © DeNA Co.,Ltd. All Rights Reserved. Kiosk端末とは? 6 Interactive

    kiosk. (2017, December 8). In Wikipedia, The Free Encyclopedia. Retrieved 07:39, February 7, 2018, from https://en.wikipedia.org/w/index.php?title=Interactive_kiosk&oldid=814355848 “An interactive kiosk is a computer terminal featuring specialized hardware and software that provides access to information and applications for communication, commerce, entertainment, or education.”
  5. Copyright © DeNA Co.,Ltd. All Rights Reserved. こんなことをやりたい • 通常操作ではKioskアプリ以外の画面に遷移できない

    • 端末を再起動しても強制的にKioskアプリが起動 • 特定の手順でKioskアプリ以外の画面に遷移可能 9
  6. Copyright © DeNA Co.,Ltd. All Rights Reserved. こんなことをやりたい • 通常操作ではKioskアプリ以外の画面に遷移できない

    • 端末を再起動しても強制的にKioskアプリが起動 • 特定の手順でKioskアプリ以外の画面に遷移可能 10
  7. Copyright © DeNA Co.,Ltd. All Rights Reserved. Screen Pinningとは •

    Android 5.0から追加された機能 ◦ Activity#startLockTaskで開始 • 特定アプリから他画面への遷移を制限 • Pinning開始にユーザー合意が必要 • 「戻る」と「最近」の長押しで解除可能 12
  8. Copyright © DeNA Co.,Ltd. All Rights Reserved. Device Ownerとは 15

    “A device owner is a specialized type of device administrator that has the additional ability to create and remove secondary users and to configure global settings on the device. “ Android 5.0 APIs by the Android Open Source Project is licensed under the Creative Commons Attribution 2.5 License. Retrieved Feburary 07, 2018, from https://developer.android.com/about/versions/android- 5.0.html
  9. Copyright © DeNA Co.,Ltd. All Rights Reserved. Device Ownerとは •

    Android端末に様々な制約を課すことが出来る特別な管理者 ◦ Android 5.0で追加された • Device Ownerに指定できるのは、 DeviceAdminReceiverを継承したクラスを実装したアプリ 16
  10. Copyright © DeNA Co.,Ltd. All Rights Reserved. Device Ownerとは •

    Device Ownerは特定のアプリにLock task modeを許可できる • Lock task mode ◦ ユーザー合意無しに開始出来るScreen Pinning ◦ 通常のユーザー操作では解除できない ◦ Activity#startLockTaskで開始 ▪ Screen Pinningと同じ 17
  11. Copyright © DeNA Co.,Ltd. All Rights Reserved. Screen PinningとLock task

    modeの違い 18 Set up Single-Purpose Devices by the Android Open Source Project is licensed under the Creative Commons Attribution 2.5 License. Retrieved Feburary 07, 2018, from https://developer.android.com/work/cosu.html
  12. Copyright © DeNA Co.,Ltd. All Rights Reserved. Device Ownerの注意点 Device

    Owner指定には、基本的に端末初期化が必要 20 “You must provision the device owner mode of operation during the initial setup of a new device or after a factory reset. Device owner mode can’t be provisioned on a device at any other time.” EMM developer's guide by Google is licensed under the Creative Commons Attribution 3.0 License. Retrieved Feburary 07, 2018, from https://developers.google.com/android/work/prov- devices#device_must_be_new_or_factory_reset
  13. Copyright © DeNA Co.,Ltd. All Rights Reserved. Device Ownerの注意点 21

    • WiFiやアカウント設定後だと、DeviceOwner指定に失敗する ◦ ただし、挙動は機種によって異なる ◦ Nexus 5はWiFi設定後でもOKだけど、MediaPad M2はNG ▪ 挙動の違いに気付かなくて、かなりハマった ▪ モバイルネットワークの設定は問題無しだったり
  14. Copyright © DeNA Co.,Ltd. All Rights Reserved. こんなことをやりたい • 通常操作ではKioskアプリ以外の画面に遷移できない

    • 端末を再起動しても強制的にKioskアプリが起動 • 特定の手順でKioskアプリ以外の画面に遷移可能 22
  15. Copyright © DeNA Co.,Ltd. All Rights Reserved. 強制的にホームアプリ化するには? 特定のActivityをホームアプリとして登録可能 25

    DevicePolicyManager by the Android Open Source Project is licensed under the Creative Commons Attribution 2.5 License. Retrieved Feburary 07, 2018, from https://developer.android.com/reference/android/app/admin/DevicePolicyManager.html#addPersistentPrefe rredActivity(android.content.ComponentName%2C%20android.content.IntentFilter%2C%20android.content. ComponentName) DevicePolicyManager#addPersistentPreferredActivity “Called by a profile owner or device owner to add a default intent handler activity for intents that match a certain intent filter. This activity will remain the default intent handler even if the set of potential event handlers for the intent filter changes and if the intent preferences are reset.”
  16. Copyright © DeNA Co.,Ltd. All Rights Reserved. こんなことをやりたい • 通常操作ではKioskアプリ以外の画面に遷移できない

    • 端末を再起動しても強制的にKioskアプリが起動 • 特定の手順でKioskアプリ以外の画面に遷移可能 26
  17. Copyright © DeNA Co.,Ltd. All Rights Reserved. Lock task modeを解除するには?

    特定のパスワード入力後にLock task mode解除が実現可能 29 Activity by the Android Open Source Project is licensed under the Creative Commons Attribution 2.5 License. Retrieved Feburary 07, 2018, from https://developer.android.com/reference/android/app/Activity.html#stopLockTask() Activity#stopLockTask “Allow the user to switch away from the current task. Called to end the mode started by startLockTask(). This can only be called by activities that have successfully called startLockTask previously. This will allow the user to exit this app and move onto other activities.”
  18. Copyright © DeNA Co.,Ltd. All Rights Reserved. ホームアプリ化を解除するには? パッケージ名がわかればホームアプリ化解除可能 32

    DevicePolicyManager by the Android Open Source Project is licensed under the Creative Commons Attribution 2.5 License. Retrieved Feburary 07, 2018, from https://developer.android.com/reference/android/app/admin/DevicePolicyManager.html#clearPackagePersis tentPreferredActivities(android.content.ComponentName%2C%20java.lang.String) DevicePolicyManager#clearPackagePersistentPreferredActivities “Called by a profile owner or device owner to remove all persistent intent handler preferences associated with the given package that were set by addPersistentPreferredActivity(ComponentName, IntentFilter, ComponentName).”
  19. Copyright © DeNA Co.,Ltd. All Rights Reserved. こんなことをやりたい • 通常操作ではKioskアプリ以外の画面に遷移できない

    • 端末を再起動しても強制的にKioskアプリが起動 • 特定の手順でKioskアプリ以外の画面に遷移可能 33
  20. Copyright © DeNA Co.,Ltd. All Rights Reserved. Kiosk端末化の流れ 1. Kioskアプリの準備

    2. 端末の初期化 3. Kioskアプリのインストール 4. Device Ownerの指定 35
  21. Copyright © DeNA Co.,Ltd. All Rights Reserved. Kiosk端末化の流れ 1. Kioskアプリの準備

    2. 端末の初期化 3. Kioskアプリのインストール 4. Device Ownerの指定 36
  22. Copyright © DeNA Co.,Ltd. All Rights Reserved. Kioskアプリの準備 1. DeviceAdminReceiver継承クラスの実装

    2. KioskUtils作成 3. AndroidManifestにReceiver登録 4. AndroidManifestにIntentFilterカテゴリ追加 5. MainActivityからKiosk開始/終了 37
  23. Copyright © DeNA Co.,Ltd. All Rights Reserved. DeviceAdminReceiver継承クラスの実装 class AdminReceiver:

    DeviceAdminReceiver() { // Device Owner指定時に実行される override fun onEnabled( context: Context, intent: Intent ) { super.onEnabled(context, intent) // Lock task modeを許可する KioskUtils(context).setLockTaskPackage() } }
  24. Copyright © DeNA Co.,Ltd. All Rights Reserved. KioskUtils作成 class KioskUtils(private

    val context: Context) { private val deviceAdmin = ComponentName(context, AdminReceiver::class.java) private val dpm = context.getSystemService( Context.DEVICE_POLICY_SERVICE ) as DevicePolicyManager fun setLockTaskPackage() {...} fun setHomeActivity(activity: Activity) {...} fun resetHomeActivity() {...} fun hasDeviceOwnerPermission(): Boolean {...} fun start(activity: Activity) {...} fun stop(activity: Activity) {...} }
  25. Copyright © DeNA Co.,Ltd. All Rights Reserved. KioskUtils作成 class KioskUtils(private

    val context: Context) { private val deviceAdmin = ComponentName(context, AdminReceiver::class.java) private val dpm = context.getSystemService( Context.DEVICE_POLICY_SERVICE ) as DevicePolicyManager fun setLockTaskPackage() {...} fun setHomeActivity(activity: Activity) {...} fun resetHomeActivity() {...} fun hasDeviceOwnerPermission(): Boolean {...} fun start(activity: Activity) {...} fun stop(activity: Activity) {...} }
  26. Copyright © DeNA Co.,Ltd. All Rights Reserved. KioskUtils作成 // Lock

    task modeを許可する fun setLockTaskPackage() = dpm.setLockTaskPackages( deviceAdmin, arrayOf(context.packageName)) // DeviceOwner権限を持っているか否か fun hasDeviceOwnerPermission(): Boolean = dpm.isAdminActive(deviceAdmin) && dpm.isDeviceOwnerApp(context.packageName)
  27. Copyright © DeNA Co.,Ltd. All Rights Reserved. KioskUtils作成 class KioskUtils(private

    val context: Context) { private val deviceAdmin = ComponentName(context, AdminReceiver::class.java) private val dpm = context.getSystemService( Context.DEVICE_POLICY_SERVICE ) as DevicePolicyManager fun setLockTaskPackage() {...} fun setHomeActivity(activity: Activity) {...} fun resetHomeActivity() {...} fun hasDeviceOwnerPermission(): Boolean {...} fun start(activity: Activity) {...} fun stop(activity: Activity) {...} }
  28. Copyright © DeNA Co.,Ltd. All Rights Reserved. KioskUtils作成 // ホームアプリ化

    fun setHomeActivity(activity: Activity) { val intentFilter = IntentFilter(Intent.ACTION_MAIN).apply { addCategory(Intent.CATEGORY_DEFAULT) addCategory(Intent.CATEGORY_HOME) } val home = ComponentName(context, activity::class.java) dpm.addPersistentPreferredActivity( deviceAdmin, intentFilter, home) } // ホームアプリ化解除 fun resetHomeActivity() = dpm.clearPackagePersistentPreferredActivities( deviceAdmin, context.packageName)
  29. Copyright © DeNA Co.,Ltd. All Rights Reserved. KioskUtils作成 class KioskUtils(private

    val context: Context) { private val deviceAdmin = ComponentName(context, AdminReceiver::class.java) private val dpm = context.getSystemService( Context.DEVICE_POLICY_SERVICE ) as DevicePolicyManager fun setLockTaskPackage() {...} fun setHomeActivity(activity: Activity) {...} fun resetHomeActivity() {...} fun hasDeviceOwnerPermission(): Boolean {...} fun start(activity: Activity) {...} fun stop(activity: Activity) {...} }
  30. Copyright © DeNA Co.,Ltd. All Rights Reserved. KioskUtils作成 fun start(activity:

    Activity) { activity.startLockTask() if (hasDeviceOwnerPermission()) { setHomeActivity(activity) } } fun stop(activity: Activity) { activity.stopLockTask() if (hasDeviceOwnerPermission()) { resetHomeActivity() } }
  31. Copyright © DeNA Co.,Ltd. All Rights Reserved. AndroidManifestにReceiver登録 <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> 参考: https://developer.android.com/guide/topics/admin/device-admin.html#developing
  32. Copyright © DeNA Co.,Ltd. All Rights Reserved. AndroidManifestにReceiver登録 <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>
  33. Copyright © DeNA Co.,Ltd. All Rights Reserved. AndroidManifestにReceiver登録 <?xml version="1.0"

    encoding="utf-8"?> <device-admin xmlns:android="http://schemas.android.com/apk/res/android"> <uses-policies> <!-- カメラ無効やWipe許可などポリシー追加できる --> <!-- Lock task mode & ホームアプリ化は追加ポリシー不要 --> </uses-policies> </device-admin>
  34. Copyright © DeNA Co.,Ltd. All Rights Reserved. AndroidManifestにIntentFilterカテゴリ追加 <activity android:name=".MainActivity">

    <intent-filter> <action android:name="android.intent.action.MAIN"/> <category android:name="android.intent.category.LAUNCHER"/> <category android:name="android.intent.category.DEFAULT"/> <category android:name="android.intent.category.HOME"/> </intent-filter> </activity>
  35. Copyright © DeNA Co.,Ltd. All Rights Reserved. MainActivityからKiosk開始/終了 override fun

    onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) binding = DataBindingUtil.setContentView(this, R.layout.activity_main) kioskUtils = KioskUtils(this) binding.kioskOnButton.setOnClickListener { kioskUtils.start(this) } binding.kioskOffButton.setOnClickListener { kioskUtils.stop(this) } }
  36. Copyright © DeNA Co.,Ltd. All Rights Reserved. Kiosk端末化の流れ 1. Kioskアプリの準備

    2. 端末の初期化 3. Kioskアプリのインストール 4. Device Ownerの指定 51
  37. Copyright © DeNA Co.,Ltd. All Rights Reserved. 端末の初期化 • ファクトリーリセットする

    • 初期設定は極力スキップすること ◦ WiFiやアカウント設定すると、 Device Owner指定不可となる事がある 52
  38. Copyright © DeNA Co.,Ltd. All Rights Reserved. Kiosk端末化の流れ 1. Kioskアプリの準備

    2. 端末の初期化 3. Kioskアプリのインストール 4. Device Ownerの指定 53
  39. Copyright © DeNA Co.,Ltd. All Rights Reserved. Kioskアプリのインストール • NFC

    ◦ あらかじめKioskアプリをサーバーにアップロードし、 KioskアプリのURLやWiFi情報などをNFCで転送する • adb install ◦ NFC非対応端末の場合、adb install 54
  40. Copyright © DeNA Co.,Ltd. All Rights Reserved. Kiosk端末化の流れ 1. Kioskアプリの準備

    2. 端末の初期化 3. Kioskアプリのインストール 4. Device Ownerの指定 55
  41. Copyright © DeNA Co.,Ltd. All Rights Reserved. Device Ownerの指定 •

    NFC ◦ 詳しくは調べていない ▪ お仕事でKiosk化した端末はNFC非対応だったので・・・ ◦ 気になる方は googlesamples をご参照 ▪ https://github.com/googlesamples/android-NfcProvisioning • adb shell dpm set-device-owner ◦ 今回はこちらの方法で指定 56
  42. Copyright © DeNA Co.,Ltd. All Rights Reserved. adb shellでDeviceOwnerを指定 #

    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.miwa x.kioskexample.AdminReceiver} インストールしたアプリをDeviceOwnerとして指定
  43. Copyright © DeNA Co.,Ltd. All Rights Reserved. Kiosk端末化の流れ 1. Kioskアプリの準備

    2. 端末の初期化 3. Kioskアプリのインストール 4. Device Ownerの指定 58
  44. Copyright © DeNA Co.,Ltd. All Rights Reserved. 活用事例紹介:タクベル • タクベルとは?

    • タクベルの乗務員アプリ • 活用しているDeviceOwnerの機能紹介 (Lock task modeとホームアプリ化以外) ◦ サイレントインストール(自動でAPKをインストール) ◦ ワイプ(盗難・紛失時のデータ消去) ◦ USBデバッグOFF ◦ スクリーンショットOFF 61
  45. Copyright © DeNA Co.,Ltd. All Rights Reserved. タクベルとは? 62 “賢いタクシーアプリ「タクベル」は、交通情報を活用したよ

    り効率的な運行・配車が可能なサービスです。” ※正式リリースは2018年春頃を予定しています。 https://dena-taxi.jp/
  46. Copyright © DeNA Co.,Ltd. All Rights Reserved. サイレントインストールするには? ユーザー操作無しでAPKのインストールができる 68

    Android 6.0 APIs by the Android Open Source Project is licensed under the Creative Commons Attribution 2.5 License. Retrieved Feburary 07, 2018, from https://developer.android.com/about/versions/marshmallow/android-6.0.html PackageInstaller※Android6.0からサイレントインストール可能 “Silent install and uninstall of apps by Device Owner: A Device Owner can now silently install and uninstall applications using the PackageInstaller APIs, independent of Google Play for Work. You can now provision devices through a Device Owner that fetches and installs apps without user interaction. This feature is useful for enabling one-touch provisioning of kiosks or other such devices without activating a Google account.”
  47. Copyright © DeNA Co.,Ltd. All Rights Reserved. サイレントインストールするには? val packageInstaller

    = context.packageManager.packageInstaller val params = PackageInstaller .SessionParams(SessionParams.MODE_FULL_INSTALL).apply { setInstallLocation(PackageInfo.INSTALL_LOCATION_AUTO) } val sessionId = packageInstaller.createSession(params) packageInstaller.openSession(sessionId).use { session -> session.openWrite("hoge", 0, file.length()).use { output -> FileInputStream(file).use { input -> input.copyTo(output) session.fsync(output) } } val dummySender = PendingIntent.getBroadcast(context, sessionId, Intent("dummy"), 0).intentSender session.commit(dummySender) }
  48. Copyright © DeNA Co.,Ltd. All Rights Reserved. サイレントインストールするには? val packageInstaller

    = context.packageManager.packageInstaller val params = PackageInstaller .SessionParams(SessionParams.MODE_FULL_INSTALL).apply { setInstallLocation(PackageInfo.INSTALL_LOCATION_AUTO) } val sessionId = packageInstaller.createSession(params) packageInstaller.openSession(sessionId).use { session -> session.openWrite("hoge", 0, file.length()).use { output -> FileInputStream(file).use { input -> input.copyTo(output) session.fsync(output) } } val dummySender = PendingIntent.getBroadcast(context, sessionId, Intent("dummy"), 0).intentSender session.commit(dummySender) }
  49. Copyright © DeNA Co.,Ltd. All Rights Reserved. サイレントインストールするには? val packageInstaller

    = context.packageManager.packageInstaller val params = PackageInstaller .SessionParams(SessionParams.MODE_FULL_INSTALL).apply { setInstallLocation(PackageInfo.INSTALL_LOCATION_AUTO) } val sessionId = packageInstaller.createSession(params) packageInstaller.openSession(sessionId).use { session -> session.openWrite("hoge", 0, file.length()).use { output -> FileInputStream(file).use { input -> input.copyTo(output) session.fsync(output) } } val dummySender = PendingIntent.getBroadcast(context, sessionId, Intent("dummy"), 0).intentSender session.commit(dummySender) }
  50. Copyright © DeNA Co.,Ltd. All Rights Reserved. サイレントインストールするには? val packageInstaller

    = context.packageManager.packageInstaller val params = PackageInstaller .SessionParams(SessionParams.MODE_FULL_INSTALL).apply { setInstallLocation(PackageInfo.INSTALL_LOCATION_AUTO) } val sessionId = packageInstaller.createSession(params) packageInstaller.openSession(sessionId).use { session -> session.openWrite("hoge", 0, file.length()).use { output -> FileInputStream(file).use { input -> input.copyTo(output) session.fsync(output) } } val dummySender = PendingIntent.getBroadcast(context, sessionId, Intent("dummy"), 0).intentSender session.commit(dummySender) }
  51. Copyright © DeNA Co.,Ltd. All Rights Reserved. ワイプするには? 強制的に全データ消去できる 76

    DevicePolicyManager by the Android Open Source Project is licensed under the Creative Commons Attribution 2.5 License. Retrieved Feburary 07, 2018, from https://developer.android.com/reference/android/app/admin/DevicePolicyManager.html#wipeData(int) DevicePolicyManager#wipeData “Ask that all user data be wiped. If called as a secondary user, the user will be removed and other users will remain unaffected. Calling from the primary user will cause the device to reboot, erasing all device data - including all the secondary users and their data - while booting up.”
  52. Copyright © DeNA Co.,Ltd. All Rights Reserved. ワイプするには? val deviceAdmin

    = ComponentName(context, AdminReceiver::class.java) val dpm = context.getSystemService( Context.DEVICE_POLICY_SERVICE ) as DevicePolicyManager // ワイプ dpm.wipeData(0) <?xml version="1.0" encoding="utf-8"?> <device-admin xmlns:android="http://schemas.android.com/apk/res/android"> <uses-policies> <wipe-data/> </uses-policies> </device-admin>
  53. Copyright © DeNA Co.,Ltd. All Rights Reserved. USBデバッグOFFするには? 強制的にADBやUSBマスストレージのON/OFFができる 80

    DevicePolicyManager by the Android Open Source Project is licensed under the Creative Commons Attribution 2.5 License. Retrieved Feburary 07, 2018, from https://developer.android.com/reference/android/app/admin/DevicePolicyManager.html#setGlobalSetting(andr oid.content.ComponentName%2C%20java.lang.String%2C%20java.lang.String) DevicePolicyManager#setGlobalSetting “Called by device owners to update Settings.Global settings. Validation that the value of the setting is in the correct form for the setting type should be performed by the caller.”
  54. Copyright © DeNA Co.,Ltd. All Rights Reserved. USBデバッグOFFするには? val deviceAdmin

    = ComponentName(context, AdminReceiver::class.java) val dpm = context.getSystemService( Context.DEVICE_POLICY_SERVICE ) as DevicePolicyManager // USBデバッグOFF dpm.setGlobalSetting(deviceAdmin, Settings.Global.ADB_ENABLED, "0")
  55. Copyright © DeNA Co.,Ltd. All Rights Reserved. スクリーンショットOFFするには? 強制的にスクリーンショットON/OFFができる 84

    DevicePolicyManager by the Android Open Source Project is licensed under the Creative Commons Attribution 2.5 License. Retrieved Feburary 07, 2018, from https://developer.android.com/reference/android/app/admin/DevicePolicyManager.html#setScreenCaptureDisa bled(android.content.ComponentName%2C%20boolean) “Called by a device/profile owner to set whether the screen capture is disabled.” DevicePolicyManager#setScreenCaptureDisabled
  56. Copyright © DeNA Co.,Ltd. All Rights Reserved. スクリーンショットOFFするには? val deviceAdmin

    = ComponentName(context, AdminReceiver::class.java) val dpm = context.getSystemService( Context.DEVICE_POLICY_SERVICE ) as DevicePolicyManager // スクリーンショットOFF dpm.setScreenCaptureDisabled(deviceAdmin, true)
  57. Copyright © DeNA Co.,Ltd. All Rights Reserved. Kiosk端末開発のノウハウ • バッテリー残量と電波強度が表示されない

    • ホームアプリ化すると再起動後にロック画面が表示される • Kioskアプリが落ちると他画面に遷移できてしまう • 端末起動直後のアップデート確認処理が毎回失敗する • ホームアプリ化が解除されない • デバッグメニューが欲しい 87
  58. Copyright © DeNA Co.,Ltd. All Rights Reserved. バッテリー残量と電波強度が表示されない • Lock

    task modeはデフォルトでステータスバー非表示 ◦ バッテリー残量と電波強度がわからなくなる • どちらもプログラマブルに取得可能 ◦ Intent.ACTION_BATTERY_CHANGED ◦ TelephonyManager • 取得した情報をKioskアプリ側で表示すればOK 88
  59. Copyright © DeNA Co.,Ltd. All Rights Reserved. ホームアプリ化すると再起動後にロック画面が表示される • Lock

    task modeだけだとロック画面表示されない • しかし、ホームアプリ化して端末再起動すると ロック画面が表示されてしまう • Androidの設定でロック画面を「なし」に変更が楽 89
  60. Copyright © DeNA Co.,Ltd. All Rights Reserved. 落ちたらアプリを再起動させる private fun

    setDefaultUncaughtExceptionHandler() { val pendingIntent = ... val origin = Thread.getDefaultUncaughtExceptionHandler() Thread.setDefaultUncaughtExceptionHandler( object : Thread.UncaughtExceptionHandler { override fun uncaughtException(thread: Thread, throwable: Throwable) { val alarmManager = getSystemService(Context.ALARM_SERVICE) as AlarmManager alarmManager.set(AlarmManager.RTC_WAKEUP, System.currentTimeMillis() + 100, pendingIntent) } finally { origin.uncaughtException(thread, throwable) } } }) }
  61. Copyright © DeNA Co.,Ltd. All Rights Reserved. 落ちたらアプリを再起動させる private fun

    setDefaultUncaughtExceptionHandler() { val pendingIntent = ... val origin = Thread.getDefaultUncaughtExceptionHandler() Thread.setDefaultUncaughtExceptionHandler( object : Thread.UncaughtExceptionHandler { override fun uncaughtException(thread: Thread, throwable: Throwable) { val alarmManager = getSystemService(Context.ALARM_SERVICE) as AlarmManager alarmManager.set(AlarmManager.RTC_WAKEUP, System.currentTimeMillis() + 100, pendingIntent) } finally { origin.uncaughtException(thread, throwable) } } }) }
  62. Copyright © DeNA Co.,Ltd. All Rights Reserved. 落ちたらアプリを再起動させる val pendingIntent

    = PendingIntent.getActivity( application.baseContext, REQUEST_START, Intent(intent), PendingIntent.FLAG_CANCEL_CURRENT )
  63. Copyright © DeNA Co.,Ltd. All Rights Reserved. 端末起動直後のアップデート確認処理が毎回失敗する • 端末起動直後はインターネット接続確立していない事がある

    • 特にモバイルネットワークは接続確立に時間がかかる ◦ 端末起動≒Kioskアプリの起動から、 約1分程度かかる事もある • 通信リトライ回数と間隔を多めに取るなどで対策! 97 必ず、モバイルネットワーク&端末起動直後で 実機テストしよう!!!
  64. Copyright © DeNA Co.,Ltd. All Rights Reserved. ホームアプリ化が解除されない • DeviceOwner権限を削除しても、ホームアプリ化されたまま

    ◦ DevicePolicyManager#clearDeviceOwnerAppで削除可 • 設定画面でホームアプリ変更しても無視される ◦ しかも、設定画面からアンインストールもできない 98 DeviceOwner権限削除前にホームアプリ化解除 or セキュリティ->端末管理アプリからチェック外してadb uninstall
  65. Copyright © DeNA Co.,Ltd. All Rights Reserved. デバッグメニューが欲しい • 巷で流行っているNotificationに常駐させる案は使えない

    ◦ ステータスバーが非表示となるので • デバッグメニューを表示するボタンを、 ホームアプリ化したActivityの邪魔にならない箇所に追加する ◦ BuildConfig.DEBUGで有効/無効を切り替える 99
  66. Copyright © DeNA Co.,Ltd. All Rights Reserved. まとめ • Device

    Owner活用でKiosk端末が実現できる ◦ 色々な制約を課すことができる ◦ APKの自動アップデートも可能 ◦ Device Owner指定には条件があるので注意 • Kiosk端末開発固有の注意・工夫が必要 ◦ 端末起動後のネットワーク接続時間 ◦ デバッグメニューの実現方法 10 1