現在、Raspberry Pi 3 B+でAndroid7.1.1を動かしてScratch Jrを動かしていますが、Raspberry Piには電源ボタンが無いので、Androidをシャットダウンするには他のPCからadbコマンドでシャットダウンしてから電源を切るか、直接電源を切る必要があります。
できればシャットダウンしてから電源を切りたいので、シャットダウンアプリを導入しようとしましたが、rootを取っておく必要があるアプリばかりのようです。
そこで「android adb shutdown」でネットを検索すると、いくつかのサイトがヒットしますが、概ねrootを取ってあることが前提のようです。
幸い、現在使用しているAndroid7.1.1のadb shellコマンドはroot権限で動作するようなので(adb shellのプロンプトが#になります)、以下のコマンドでシャットダウンが可能でした。
adb shell am start -a com.android.internal.intent.action.REQUEST_SHUTDOWN
adb shell am start -n android/com.android.internal.app.ShutdownActivity
さらに、Android4.4以降はadb shell setprop sys.powerctl shutdown
でもシャットダウン可能です。その1:ターミナルアプリのインストール
とは言え、シャットダウンする為だけにPCを立ち上げ、コマンドを叩くのも面倒です。
なんとかRaspberry Piだけでシャットダウンできないかと考え、adb shellを実行しているならターミナルアプリをインストールして、上記コマンドを実行すればいいんでは無いかと考えテストしてみると。
amを利用するものはAndroidの権限で弾かれてしまいましたが、setpropを利用するケースは問題なくシャットダウンしてくれました。
これで、わざわざPCを立ち上げなくてもRaspberry Piだけでシャットダウンしてから電源を切れるようになりました。
その2:ShutdownPiの作成
とは言っても、毎回ターミナルアプリを立ち上げてコマンドを入力するのは面倒です。ワンクリックでシャットダウンしたいものです。
ということで、Kotlinの勉強も含めてシャットダウンアプリを作成することにしました。
以下はメインとなるダイアログ部分のソースです。ダイアログを表示して、シャットダウン/リーブート/キャンセルを選択できるようにしてあります。
package com.example.keiichimaru.shutdownpi.dialog
import android.app.Activity
import android.app.Dialog
import android.content.Context
import android.content.DialogInterface
import android.os.Bundle
import android.support.v4.app.DialogFragment
import android.support.v4.app.FragmentManager
import android.support.v7.app.AlertDialog
import android.util.Log
import com.example.keiichimaru.shutdownpi.R
class DlgShutdownConfirm(context: Context) {
private val mContext: Activity? = if (context is Activity) context else null
companion object {
const val TYPE_SHUTDOWN = "shutdown"
const val TYPE_REBOOT = "reboot"
}
fun show( manager: FragmentManager, tag: String?) {
val dialog = DlgShutdownConfirmDialog()
dialog.onRebootButtonClickListener = DialogInterface.OnClickListener { _, _ ->
finishActivity()
doShutdown(TYPE_REBOOT)
}
dialog.onShutdownButtonClickListener = DialogInterface.OnClickListener { _, _ ->
finishActivity()
doShutdown(TYPE_SHUTDOWN)
}
dialog.onCancelButtonClickListener = DialogInterface.OnClickListener { _, _ ->
finishActivity()
}
dialog.show(manager, tag)
}
private fun finishActivity() {
requireNotNull(mContext) {"DlgShutdownConfirm initialize error."}
mContext.finishAndRemoveTask()
}
private fun doShutdown(type: String) {
require((type == TYPE_SHUTDOWN) or (type == TYPE_REBOOT)) { "type required $TYPE_SHUTDOWN or $TYPE_REBOOT." }
val command = arrayOf(
"sh",
"-c", "setprop sys.powerctl $type"
)
val runtime = Runtime.getRuntime()
try {
Log.d(DlgShutdownConfirm::class.qualifiedName, command.reduce { s1, s2 -> "$s1 $s2" })
val process = runtime.exec(command)
val result = process.inputStream.bufferedReader().use { it.readText() }
Log.d(DlgShutdownConfirm::class.qualifiedName, result)
process.waitFor()
} catch (e: InterruptedException) {
e.printStackTrace()
}
}
class DlgShutdownConfirmDialog : DialogFragment() {
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val builder = AlertDialog.Builder(activity!!)
builder
.setMessage(R.string.dlg_shutdown_msg)
.setNeutralButton(R.string.dlg_reboot, onRebootButtonClickListener)
.setNegativeButton(R.string.dlg_shutdown, onShutdownButtonClickListener)
.setPositiveButton(R.string.dlg_cancel, onCancelButtonClickListener)
return builder.create()
}
internal var onRebootButtonClickListener: DialogInterface.OnClickListener? = null
internal var onShutdownButtonClickListener: DialogInterface.OnClickListener? = null
internal var onCancelButtonClickListener: DialogInterface.OnClickListener? = null
}
}
MainActivityは以下の通りです。
アプリが起動されると、画面は表示せずに確認ダイアログを表示してシャットダウンします。
package com.example.keiichimaru.shutdownpi.dialog
import android.support.v7.app.AppCompatActivity
import android.os.Bundle
import com.example.keiichimaru.shutdownpi.dialog.DlgShutdownConfirm
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// setContentView(R.layout.activity_main)
DlgShutdownConfirm(this).show(supportFragmentManager, MainActivity::class.qualifiedName)
}
}