ラベル Android開発 の投稿を表示しています。 すべての投稿を表示
ラベル Android開発 の投稿を表示しています。 すべての投稿を表示

2020年7月8日水曜日

LineageOS 16.0 (Android 9) for Raspberry Pi 4をインストールしてみた

前回Raspberry PiにLineageOSをインストールしてから1年以上が経過してRaspberry Piも4に進化しましたが、LineageOSも今年の2月にはRaspberry Pi 4用のLineageOS 16.0(Android 9)がリリースされていました。

前回はScratch JrをRaspberry Piで動かすことが目的でしたが、残念ながらScratch Jrについてはマウスで操作ができなかったので断念した経緯があります。新しいバージョンではどうかも含めて再度トライすることにしました。

Google appsのインストールも前回同様用意されていました。多少スクリプト名が変更になっていたり、TWRPの画面がちょっと変わったりしてしていましたが、作業は前回と全く同じでした。
注意点としてはGoogle appsのインストール後、起動設定変更作業前にパーティションのマウントを行いますが、この時に「boot」にもチェックを入れないとエラーが発生します。/bootに書き込みに行っているのが原因だと思います。


今回改善されていた点

  • インストール時に言語選択で日本語が選択できるようになっていました。頑張って下の方までスクロールして下さい。インストール後すぐに日本語環境になっています。
  • Raspberry Pi 4の威力でしょうか、前回と違って設定などですぐに落ちることはなくなりました。そこそこのレスポンスで作業ができるようになっていました。

やっぱり問題が

  • Scratch Jrはやはり使えません。画面は表示されますが、マウスで操作ができませんでした。
  • Play Storeでのインストールは保留中となって先に進みませんが、そのまま再起動するとインストールされるようです。
  • インストールもそうですが、ネットワーク周りでおかしな動作が見受けられます。

前回同様にadbでもテストしてみましたが、adbでマウスイベントを発生させると動作するのですが、実際のマウスでは動作しないという現象が発生します。adbの場合は直接イベントを発生させているので、実際のマウスの場合はOS側の動作が違うのでしょうか?

Scratch JrはAndroidとiOSで動作するように、基本部分はJavaScriptで作成されていてOSに関わる部分だけをそれぞれのネイティブな言語で作成してあります。
Androidの場合はWebViewでJavaScriptを有効にして、handleTouchStartとhandleTouchEndで処理しているようです。

私が公開しているアプリは問題なくインストールできて動作も確認できました。ただこのアプリはAndroidネイティブなのでWebViewを使ったアプリではありません。
時間があれば簡単なアプリでテストをしたいと思いますが、今回はここまでとしたいと思います。

2019年3月9日土曜日

Android on Raspberry Piを安全にシャットダウン

左手の親指を怪我して2針縫ったのでしばらく不自由していました。

現在、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)
    }

}

2019年2月22日金曜日

EclipceからAndroidStudioへのお引っ越し ーその2ー

Eclipce側での修正はひと段落したので、いよいよAndroidStudioへの移行を行いたいます。
AndroidStudio側でEclipceのワークスペースをインポートする機能があるので、簡単にできるかと思いましたが、3箇所ほどつまづきましたのでメモを残しておきます。

その1:ビルドができない。


インポートそのものは本家の解説を見て問題なくできたのですが、ビルドをするとエラーが出てしまいます。
ERROR: Could not find com.android.tools.build:gradle:3.3.1.
Searched in the following locations:
  - https://jcenter.bintray.com/com/android/tools/build/gradle/3.3.1/gradle-3.3.1.pom
  - https://jcenter.bintray.com/com/android/tools/build/gradle/3.3.1/gradle-3.3.1.jar
Required by:
    project :
Add Google Maven repository and sync project
Open File
Enable embedded Maven repository and sync project
色々と検索をして見ましたが、これはというサイトが見つかりませんでした。いずれにしろGradle関連の設定では無いかと検討をつけてこちらのサイトを参考に、プロジェクト全体のbuild.gradleにgoogle()を追加すれば良いことがわかりました。
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
    repositories {
        jcenter()
        google() ・・・・・この行を追加
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:3.3.1'
    }
}

allprojects {
    repositories {
        jcenter()
        google() ・・・・・この行を追加
    }
}

新規にプロジェクトを作成する場合は、初めから追加されているようですがEclipceからインポートした場合には記述されなかったようです。

これに関連して、自動生成された各モジュールのbuild.gradleのdependenciesですが、compileで生成されますがcompileが非推奨となったとのことで、implementationとapiに変更しておいた方が良いようです。
私のアプリでは「アプリケーションモジュール→ライブラリモジュール→Android supportライブラリ」という依存関係があるので
アプリケーションモジュール
 dependencies {
     implementation project(':myLib')
 }

myLibモジュール
 dependencies {
     api project(':lvl')
     api 'com.android.support:appcompat-v7:28.0.0'
     api 'com.android.support:preference-v7:28.0.0'
     api 'com.android.support:recyclerview-v7:28.0.0'
 }

lvlモジュール
 dependenciesは無し
lvlモジュールのメソッドで一部アプリケーションモジュールで利用するものがありますが、myLibでapi宣言しているのでアプリケーションモジュールではlvlを指定する必要はありません。

その2:実行ができない!


無事にビルドも完了したので、さて実行をと思ったところで問題発生。
実行の構成を行おうとメニューから実行>実行の構成>TemplatesからAndroid Appを選択しましたが、モジュールが空っぽで選択することができません。

ソースも特にいじっていなかったので、一旦モジュールを削除して再度インポートし直したところ、実行の構成でモジュールの選択ができるようになりました。

1つのプロジェクトにアプリケーションと共通ライブラリがあったので、まずはライブラリからインポートしたので原因でしょうか?

後でわかった事として、各モジュールのbuild.gradleの先頭で各モジュールの属性を宣言しているようです。Android developerではビルドの設定>ビルド設定ファイル(build.gradle ファイル)>モジュール レベルのビルドファイルにもそれらしい説明はなく詳細はAndroid Plugin DSL Reference(英語)を見ろと突き放しています。結局、こちらに先頭行のプラグインでアプリケーションとライブラリを切り替えている事が説明されていました。ただ、ここは日本語に翻訳されていますが、バージョンが古いのでしょう依存関係の説明でcompileが使用されています。

その3:":" is not a valid resource name


やっと実行に漕ぎ着けましたが、実行してみると上記のようエラーが出てしまいます。

同じようなエラーはネット上でもいくつか見受けましたが、何れにしてもリソース関係のようです。string.xml又はlayout.xmlあたりでnameに":"を使っていないかということをヒントに、検索してみるとlayout.xml内でname="@id/android:empty"と指定しているところがありました。これはListActivityでデータが1件も無かった場合、自動的にこのidを持っているTextViewを表示してくれるという機能を使うためなんですが、どうやらこの指定方法が古かったようです。最近では@android:id/emptyと指定するようです。

ここでも浦島太郎になっていました(笑)

その他


Javaには長いことお世話になってきましたが、Oracleは(サポート≒セキュリティパッチ)有償化に踏み切ったようで、その話題で盛り上がっている界隈もあるようです。

その昔、SunがOracleに買収された時にJavaが有償化されるのではという危惧をしましたが、ついに来たかという感想です。そうなると次は「MySqlよお前もか!」にならなければいいんですが。(MySqlは現在も有償版とGPL版がありますが、今後はサポートを有償化という流れで、Oracleがあまたある利用サイトからお金を徴収しようと考えるかですね)

まあ、使う側としては、お金のある企業さんはサポート契約を結ぶんでしょうし、中小企業ではセキュリティリスクを我慢して(というか、サポート要員もいないので納品されたシステムをそのまま運用しているので、セキュリティもへったくれも無い)今のまま何もしないという流れではないか、と予想しています。(Oracle側もプライバシーマクを取得しているような企業や、セキュリティに責任を持つ企業さんは有償化わかってるよね、的な発言しているようですしね。)

Androidの開発でもJavaを使っていますが、Androidは2016年にはJavaのライブラリをOpenJDKへ切り替えていますので、直接の影響は出ないでしょう。まあ、GoogleとOracleの間ではJavaに関して訴訟が2010年ごろからあったので、Googleとしてはこの事態はすでに予想して対応していたんでしょう。

この話題と関連してかKotlinという言語が話題に上がってきているようです。すでにAndroid関連の話題でもKotlinを使ったページもだいぶ出てきているようです。

ということで、今年は今あるアプリをSwiftとKotlinで書き直してみたいと思います。

2019年2月9日土曜日

EclipceからAndroidStudioへのお引っ越し ーその1ー

Google Play ストアにアプリを公開していますが、いつの間にかターゲット SDK を API レベル 26 以上(Android 8.0 Oreo)に設定することが求めらていました。(通知は来ていたのでしょうが、無視していました)2018年秋のアップデートは問題なかったのですが2018年11月1以降は新規アプリだけでなくアップデートもできなくなっていたんですね。1月になってちょっと修正したのでアップデートしようとしたところで怒られてしまいました(泣)

如何せんAndroid1.6の頃に作ったアプリでターゲット SDKも8(Froyo)という、とんでもアプリでした。仕方がないので急遽3倍以上のジャンプアップを目指して作業を開始しました。とはいえ、最近はAndroidの本格的な開発からは遠ざかっていた(スマフォもいつの間にかiPhoneになっています)ので最新情報にも疎く、だいぶあたふたとしております。

多くのデベロッパーの方はすでに最新のバージョンにもバリバリ対応していると思いますので、全く役に立たない情報ではありますが、作業中つまづいたところをドタバタ奮戦記として残しておきたいと思います。

さて、ターゲット SDK のAPI レベルを上げると当時に、開発環境もEclipceがサポート対象外となっているのでAndroidStudioへのお引っ越しが必要となっています。両方をいっぺんに行うのは大変なので、まずはAPI レベル を現在のEclipce環境で上げられるところまでアップしてからAndroidStudioへ引っ越したいと思います。

その1:API レベル8(Froyo)からレベル16(Jelly Bean)へ


ここでもステップを踏んで作業を進めていきたいと思います。本家でも「Google Play の対象 API レベルの要件を満たす」として説明されていますが、一番初めに「動作の変更点」があったのがレベル16のようなのでまずはここを目指します。
作業はビルドターゲットを変えてリビルドするだけで済みましたが、実行してみると「You cannot combine custom titles with other title features」なるメッセージで実行できませんでした。早速Googleさんにお伺いをたてると、タイトルをカスタマイズしている場合はテーマの設定が必要なようです。そういえばテーマなんて機能がだいぶ前に追加されていましたね。
AndroidManifest.xmlを編集してテーマを以下のように設定しところ問題なく表示されるようになりました。でも、これが後で発覚する別の問題を引き起こしていました。
<application ... android:theme="@android:style/Theme.NoTitleBar">
<activity ... android:theme="@android:style/Theme">

その2:support v7の導入


AndroidにFragmentが導入されたのはAndroid 3.0でしたが、当時Preferenceがまだサポートされていなかったと思います。そこでPreferenceFragment/PreferenceManagerCompatを使って対応していました。そのまま使っていましたが、現在ではOSで対応していますし、サポートライブラリでも使えるのでsupport v7 preferenceを導入して切り替えることにしました。
PreferenceFragmentはPreferenceFragmentCompatへPreferenceManagerCompatはPreferenceManagerにクラス名を変更することでその他にソース修正は必要ありません。ただ、support v7 preferenceはリソースなども含んでいるので、Eclipceへ取り込む必要があります。ここで問題が発生です。
取り込んだリソースの内res/values/attr.xml内でエラーが発生してしまいます。フォーマットが未定義だというので、以下のようにformat指定を追加することでエラーは出なくなりました。
<declare-styleable name="Preference">
 <attr name="icon" format="reference">
 <attr name="title" format="reference">
 <attr name="layout" format="reference">
さて、エラーがなくなって実行したところで、はたと気がついたのが設定を開くためのメニューが出ない。最近の人は知らないかの知れませんが、昔はメニューの表示は画面下部のホームボタンとか表示されているとこにあったんですね。どうやら最近は画面上部のツールバー?(タイトルバー改めアクションバーが改名してツールバー?なんて呼ぶのが正式なんでしょうか?)に表示されるようです。そうです、Theme.NoTitleBarでタイトルバーを消していたんですね。
<style name="Theme.Light_actionBar" parent="@android:style/Theme.Light">
 <item name="android:icon">@android:color/transparent</item>
 <item name="android:windowNoTitle">false</item>
 <item name="android:windowActionBar">true</item>
</style>
カスタムテーマを作って無事にメニューが表示されて、やっと設定画面を開くと、今度は実行時エラーが出ます。実はsupport  v7 recyclerviewを使っていたんですね。と言うことで、こちらもインポートして無事に設定画面も動くようになりました。
ところが、ここでカスタムPreference(ボタン)が動きません。色々と探っていると、今まではPreference全体のレイアウトを作ってカスタマイズしていたんですが、どうやらwidget部分だけをカスタマイズできるようです。カスタマイズが簡単になっていたんですね。
参考サイト:Preference(4)-Preferenceのカスタマイズ 

 その3:LVL (License Verification Library)バージョンアップ


LVLとはGoogle Play で有料アプリを配布する際に組み込んでおきたい、ライセンス認証ライブラリのことです。とは言え、ハッキングツールも公開されていたりとあまり役に立たない機能ではありますが、気休めで導入してありました。ところが、APIレベルを23(Marshmallow)に上げたところ、コンパイルでエラーが発生しました。どうやらAndroid6.0からApache HTTP クライアントが削除されたのが原因のようです。最新のソースがGithubにあったのでダウンロードして差し替えました。

オプションメニューがアクションバーに移動したことに伴い、アクションバーへの表示ができない画面が1つだけありましたが、そこはコンテキストメニューに切り替えることでクリアしました。こちらはほぼメソッド名の変更だけで完了です。

その4:テーマでハマった件


最後に残ったのがAbout画面として使っていたAlertDialogです。表示はされるのですが、背景色や文字色がダーク系になってしまい、文字が見づらいのです。テーマで設定できるかと思いましたが、なかなか思うように設定できませんでした。
結論から言うと、support v7 appcompatを導入&カスタムスタイル作成で思うような設定が可能でした。ただし、結論に達するまで数日を要し今回の作業で一番ハマったとことです。キモはカスタムスタイルを作成するときにTextAppearance.AppCompatからの派生スタイルを継承することでした。
テーマをカスタマイズするにはTheme.AppCompatを継承するように、widgetのスタイルを独自に変更しつつテーマを反映させるにはAppCompatから派生したスタイルを継承しておくことが必要なようです。
<style name="Dialog_TextSmall" parent="TextAppearance.AppCompat.Caption">
 <item name="android:textColor">?android:colorPrimary</item>
</style>
スタイルを定義して、レイアウトで指定します。
<TextView android:text="@string/about_copyright"
 android:layout_width="fill_parent"
 android:layout_height="wrap_content"
 android:layout_marginLeft="15dip"
 android:layout_marginRight="15dip"
 style="@style/Dialog_TextSmall"
/>
この作業の中で、その2で修正したdeclare-styleableですが、今度は二重定義でエラーになりました。どうやらsupport v7 appcompat側のリソースで定義されているようです。support v7 appcompatは必須という位置付けなんでしょうかね。その2での修正を削除してクリアです。

最後に


これで終わりではなく、次にAndroidStudioへの移行と、API レベル 26 への対応が必要ですが、作業が無事に終了したら別に報告したいと思います。

いづれにしろ、FragmentやらThemeやらToolBarやら浦島太郎となっている状況なので、もう一度真面目に勉強し直してアプリも1から書き直す必要がありそうです。

EXCELの表をKaTeX形式に変換するマクロ

生成AI時代にプログラミング言語を学習する必要はあるのか? 最近では生成AIがプログラムを作成する時代になってきましたが、そんな時代にプログラミング言語を学習する必要はあるのでしょうか? ということで早速 Google さんに聞いてみました。 生成AI時代にプログラミング言語を学...