- 投稿日:2019-05-24T17:59:45+09:00
Android Developers Blog 適当意訳 #9 New! Learn How to Build Android Apps with Android Jetpack and Kotlin
Android Jetpack と Kotlin を使ってのアプリ開発方法を学びましょう!
2019年5月19日
原文
※英語の勉強と自分用のメモ程度のかなり適当なものです。Developing Android Apps with Kotlin(GoogleとUdacityによる共同制作) は我々の新しい無料の自分のペースで学べるオンラインコースです。
あなたはKotlinでの業界標準のツールとライブラリを使ったAndroidアプリの開発方法を学べます。
Androidアプリ開発の基礎は活発でメンテナンス可能なアプリケーションの土台を提供するアーキテクチャという文脈の中で教えられています。
このコースは、 Room や Work Manager や Navigation component などを使って何故そしてどうやってそれらを使うのかを教えます。
あなたは画像ロードライブライのGlideや通信ライブラリのRetorofitJsonパーサのMoshiなど一般的なタスクをシンプルにする有名なライブラリを使います。
このコースは、コードの記述をより早く簡潔にするためのcoroutinesのような主要なKotlinの機能を教えます。
このコースを通して、火星の写真やトリビアクイズ、睡眠追跡などの楽しくて興味深いアプリを作るでしょう。
このコースは、プログラミング経験があり、Kotolinの基礎がわかっている方を対象にしていますので、Kotlinになじみのない開発者の方は、まずUdacityの Kotlin Bootcamp course を受講する事をおすすめします。
このコースは、 Udacity で無料で受けられます。あなたの好きな時間で好きなペースで受講してください。
https://www.udacity.com/course/ud9012 に我々と一緒にKotlinでのAndroidアプリ開発を学びに来てきてください!
- 投稿日:2019-05-24T12:43:19+09:00
【Android】altbeaconでBeaconからの距離(Proximity)を測りたかった話
前回 Macから無事Beaconを発射できるようになった。
Beaconの仕様を知らないまま、BeaconからAndroid端末の距離って測れるよね?と軽い気持ちで試してみようとした記録とメモを兼ねて。。。。
結論から言うと
色々調べてみると、どうやら電波強度や建物の干渉などで正確な距離は測れないみたい。
一応
BeaconManager#addRangeNotifier
で受け取れるbeaconに生えてるdistance(メートル単位)があるけど
本当にメートル???ってなるくらい誤差がひどかった。どうやったか
ググラーエンジニアなのでこのQiitaを発見。
RSSI と TxPower からビーコンとの距離および近接度(Proximity)を推定する
この記事によるとRSSI と TxPower から推定距離を計算する
自由空間では受信信号強度は距離の二乗に反比例して減衰していく(フリスの伝達公式)ので、RSSI と TxPower と距離(d)の関係は次のようになります。
RSSI = TxPower - 20 * lg(d)(lg は底を 10 とする常用対数)
というわけで、距離 d の計算式は以下のようになります。
d = 10 ^ ((TxPower - RSSI) / 20)なんかすごい物理法則的なアレを加味して計算しないといけないっぽかった。(語彙力)
先人の知恵を頂戴して、Kotlinで便利そうな関数を作る。Sample.ktfun beaconDistance(txPower: Int, rssi: Int): Double = 10.0.pow((txPower - rssi) / 20.0)実際に観測してログに出すだけのコードはこちら
Sample.ktoverride fun onBeaconServiceConnect() { ~ 略 ~ beaconManager?.addRangeNotifier { beacons, region -> beacons .map { "Distance:"+beaconDistance(it.txPower, it.rssi) // it.distanceもあったけど誤差がやばい(私的見解) } .forEach { Log.d(TAG, it) } } ~ 略 ~ }測ってみてどうだった?
端末持ってウロウロしてみたけど外の交通量やノイズ?や建物の作りでまあまあ誤差がでるみたい。
beacon.distance
使えないな〜、もうちょっと正確なのがいいな。
くらいの時に目安に使うのならいいかもね。
- 投稿日:2019-05-24T10:02:59+09:00
RICOH THETA Z1の有機ELパネルをプラグインからコントロールする方法
RICOH THETA Z1がついに日本でも発売
リコーの@ueueです。
THETA Z1が日本でも発売になりました。
THETA Z1もVと同じくプラグインをインストールして機能を拡張することができます。
最高かよ。そして、THETA Z1には有機ELパネル(OLED)がついています。
神だな、おい。
ということで、この記事ではプラグインからOLEDの表示をコントロールする方法について説明します。
ちなみにTHETAプラグイン開発者向けドキュメントの「Control OLED」の章に同じことが記載されているので、英語つよつよの人はそちらを参照いただければと思います。THETA プラグイン is 何?という方はこちら
本題
先にも記載しましたが、THETA Z1は撮影情報を表示するモノクロのOLEDを搭載しています。大きさは128×36ピクセルです。
このOLEDの下2/3の領域(128×24)に対してプラグインから文字列または画像を表示させることが可能です。逆にプラグインモードのときでも、OLEDの表示を通常モードと同じようにすることも可能です。また、「何も表示させない」といった制御も可能です。
画面の明るさも制御可能で、1-100の間で指定できます。この記事では「各ボタンで表示を切り替えるプラグイン」のサンプルコードを元にして
OLEDの表示をコントロールする方法について解説します。サンプルプログラムのボタンと表示内容の対応は以下のとおりです。
- シャッターボタンで文字列を表示
- シャッターボタン長押しで何も表示させない
- Wi-Fiボタンで画像を表示
- Wi-Fiボタン長押しで画像を点滅表示
- ファンクションボタン(Fn)で通常モードの画面表示
また、画面の明るさは100としています。
サンプルコードは以下のとおりです。
public class MainActivity extends PluginActivity { private Bitmap theta_bmp = null; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); setAutoClose(true); // 画像の準備 try (InputStream inputStream = getAssets().open("theta.bmp")) { theta_bmp = BitmapFactory.decodeStream(inputStream); } catch (IOException e) { notificationError("Failed to load theta.bmp"); } //明るさの調整 Intent oledBrightnessIntent = new Intent("com.theta360.plugin.ACTION_LED_BRIGHTNESS_SET"); oledBrightnessIntent.putExtra("target", "OLED"); oledBrightnessIntent.putExtra("brightness", 100); sendBroadcast(oledBrightnessIntent); setKeyCallback(new KeyCallback() { @Override public void onKeyDown(int keyCode, KeyEvent event) { if (keyCode == KeyReceiver.KEYCODE_CAMERA) { Intent textIntent = new Intent("com.theta360.plugin.ACTION_OLED_TEXT_SHOW"); textIntent.putExtra("text-middle", "hogehoge"); textIntent.putExtra("text-bottom", "fugafufa"); sendBroadcast(textIntent); } else if (keyCode == KeyReceiver.KEYCODE_WLAN_ON_OFF) { Intent imageIntent = new Intent("com.theta360.plugin.ACTION_OLED_IMAGE_SHOW"); imageIntent.putExtra("bitmap", theta_bmp); sendBroadcast(imageIntent); } else if (keyCode == 119) { // 119はFnボタンのKeyCode Intent oledSetIntent = new Intent("com.theta360.plugin.ACTION_OLED_DISPLAY_SET"); oledSetIntent.putExtra("display", "basic"); sendBroadcast(oledSetIntent); } } @Override public void onKeyLongPress(int keyCode, KeyEvent event) { if (keyCode == KeyReceiver.KEYCODE_CAMERA) { Intent textIntent = new Intent("com.theta360.plugin.ACTION_OLED_HIDE"); sendBroadcast(textIntent); } else if (keyCode == KeyReceiver.KEYCODE_WLAN_ON_OFF) { Intent blinkImageIntent = new Intent("com.theta360.plugin.ACTION_OLED_IMAGE_BLINK"); blinkImageIntent.putExtra("bitmap", theta_bmp); blinkImageIntent.putExtra("period", 300); sendBroadcast(blinkImageIntent); } } @Override public void onKeyUp(int keyCode, KeyEvent event) { } }); } }※ 現時点(2019/05/22)ではプラグインSDKにOLED表示のためのメソッドは存在しないので、インテントを直接利用するコードになっています。
文字列表示
OLEDの下2/3の領域に2行の文字列を個別に表示させることが可能です。
サンプルコードの以下の部分が文字列表示に対応します。if (keyCode == KeyReceiver.KEYCODE_CAMERA) { Intent textIntent = new Intent("com.theta360.plugin.ACTION_OLED_TEXT_SHOW"); textIntent.putExtra("text-middle", "hogehoge"); // 真ん中の領域に表示される文字列 textIntent.putExtra("text-bottom", "fugafufa"); //一番下の領域に表示される文字列 sendBroadcast(textIntent);IntentクラスのputExtraメソッドで文字列を設定します。text-middleが画面を3分割した真ん中の行に表示されます。text-bottomが画面が一番下の行です。
表示できるのはASCII printable charactersの大文字です。サンプルコードのように指定した文字列に小文字が使われている場合は、自動的に大文字に変換されます。
以下にその他の注意事項をまとめます。
- text-middleを指定しない場合はプラグイン名が表示されます。
- text-bottomを指定しない場合は空白が表示されます
- 表示は左詰めになります。
- 表示領域から指定した文字列がはみ出る場合には、スクロールしてすべての文字が表示されます。スクロールをプラグインから制御することはできません。
以下が、スクロールさせた場合の画像です。1行目は"KAOMOJI"という短い文字列。2行目に顔文字が続く長い文字列を設定しました。2行目のほうだけスクロールが走ります。
画像表示
次に画像表示ついて説明します。
画像はOLEDの下2/3の領域に一つの画像を表示させることができます。
また、点滅表示をさせることも可能です。画像の準備
まずは画像を準備します。
画像の形式はビットマップ、大きさは128x24にする必要があります。
形式や大きさが異なると何も表示されませんのでご注意ください。今回はサンプル画像として、いらすとやの360度カメラを表示させてみました。
大きさが128x40のなるように画像を修正してビットマップとして保存します。この画像ファイルをアセットフォルダーに配置します。
アセットフォルダーは、Android Studioのメニューから
[File] -> [New] -> [Folder] -> [Assets Folder]
で作成できます。なお、カラー画像であってもTHETA側でモノクロに変換して表示するので、濃淡を気にしないならばカラーのビットマップファイルをアセットフォルダーに配置するだけでオッケーです。
画像そのものの作り方については最後の「おまけ」でもう少し細かく説明します。
サンプルコード(Assetsから画像を取り込む)
まずアセットフォルダ内の画像を取り込む部分が以下になります。
// 画像の準備 try (InputStream inputStream = getAssets().open("theta.bmp")) { theta_bmp = BitmapFactory.decodeStream(inputStream); } catch (IOException e) { notificationError("Failed to load theta.bmp"); }サンプルコード(画像の表示)
Wi-Fiボタンを押して画面に表示させる部分が以下になります。
“com.theta360.plugin.ACTION_OLED_IMAGE_SHOW” インテントを利用します。} else if (keyCode == KeyReceiver.KEYCODE_WLAN_ON_OFF) { Intent imageIntent = new Intent("com.theta360.plugin.ACTION_OLED_IMAGE_SHOW"); imageIntent.putExtra("bitmap", theta_image); sendBroadcast(imageIntent);サンプルコード(画像の点滅表示)
画像を点滅させたい場合は “com.theta360.plugin.ACTION_OLED_IMAGE_BLINK”インテントを利用します。
putExtraメソッドのkeyとして"period"指定して、点滅間隔を制御できます。
制御間隔1-2000[msec]の間で指定できます。デフォルトは2000 [msec]です。対応するサンプルコードは以下の箇所です。ここでは間隔を300msecに設定しています。
else if (keyCode == KeyReceiver.KEYCODE_WLAN_ON_OFF) { Intent blinkImageIntent = new Intent("com.theta360.plugin.ACTION_OLED_IMAGE_BLINK"); blinkImageIntent.putExtra("bitmap", theta_image); blinkImageIntent.putExtra("period", 300); sendBroadcast(blinkImageIntent); }通常モードと同じ表示にする
OLEDの表示を通常のモードとする場合には
“com.theta 360.plugin.ACTION_OLED_DISPLAY_SET”インテントを利用して
putExtraメソッドのkeyを"display"、値を"basic"とします。サンプルコードの以下の部分が対応します。
else if (keyCode == 119) { //ファンクションボタン Intent oledIntentSet = new Intent("com.theta360.plugin.ACTION_OLED_DISPLAY_SET"); oledIntentSet.putExtra("display", "basic"); sendBroadcast(oledIntentSet);なお、Z1で追加されたファンクションボタンについてはKey Codeの定数が定義されていないので、こちらのドキュメントを参考に値を119としました。
何も表示させない
プラグインで制御できる下2/3領域にに何も表示させたくない場合は
“com.theta360.plugin.ACTION_OLED_HIDE”インテントを利用します。if (keyCode == KeyReceiver.KEYCODE_CAMERA) { Intent textIntent = new Intent("com.theta360.plugin.ACTION_OLED_HIDE"); sendBroadcast(textIntent);画面の明るさ
画面の明るさは以下の部分で設定しています。
//明るさの調整 Intent oledBrightnessIntent = new Intent("com.theta360.plugin.ACTION_LED_BRIGHTNESS_SET"); oledBrightnessIntent.putExtra("target", "OLED"); oledBrightnessIntent.putExtra("brightness", 100); sendBroadcast(oledBrightnessIntent);インテントの値が
com.theta360.plugin.ACTION_"O"LED_BRIGHTNESS_SET
ではなく
com.theta360.plugin.ACTION_LED_BRIGHTNESS_SET
であることにご注意ください。このインテントはOLEDだけでなく、他のLEDの明るさ調整にもつかわれるため"OLED"ではなく"LED"になっています。したがって、putExtraメソッドのtargetの値を “LED1”、 “LED2”とすることでLEDの明るさコントロールにも利用できます。
おまけ(画像の作成について)
最後に表示させる画像についていくつかTipsを紹介します。
濃淡のコントロール
このようにごちゃごちゃしたカラー画像をTHETA側のおまかせでモノクロに変換すると、なんだかよくわからない画像になります。
このような場合、例えばこのサイトなどで、自分でしきい値を調整しながらモノクロ画像をつくることで見やすく改善することが可能です。
上記サイトで調整しながらモノクロに変換したのが、次の画像です。けっこうよくなったのが伝わるでしょうか。
ビットマップ画像を自分で作成
これまでのサンプルはすでにできあがった画像を表示していましたが、自分で画像を作成することも可能です。
詳しくないので、横シマを描画する簡単なサンプルだけのせておきます。int black = 0xFF000000; int white = 0xFFFFFFFF; Bitmap bitmap2 = Bitmap.createBitmap(128, 24, Bitmap.Config.ARGB_8888 ); for (int width=0; width<128; width++) { for (int height=0; height<24; height++) { //if (width%2==0) { //縦縞 if (height%2==0) { //横縞 bitmap2.setPixel(width, height, white); } else { bitmap2.setPixel(width, height, black); } } }コードのコメント行のところを下記のように入れ替えて、縦縞を有効にすると
if (width%2==0) { //縦縞 // if (height%2==0) { //横縞興味を持った方はこのあたりを読めばもっといろいろできると思います。
あと、近々、THETAプラグイン開発者コミュニティのメンバーがOLEDの画像描画についてもうすこしつっこんだ記事を書くっぽいです。
まとめ
THETA Z1の有機ELパネルの表示をプラグインからコントロールする方法を説明しました。
RICOH THETAプラグインパートナープログラムについて
THETAプラグインに興味を持たれた方がいれば、以下の記事もぜひご覧ください。
RICOH THETAプラグイン開発者コミュニティでは、他にも記事を書いています。
RICOH THETAプラグインについてはこちらに情報がまとまっています。興味を持たれた方はtwitterのフォローとTHETAプラグイン開発コミュニティ(slack)への参加もぜひどうぞ。
- 投稿日:2019-05-24T03:51:55+09:00
BiometricPromptをAndroid Mから使う
はじめに
Android Pから
BiometricPrompt
というクラスが追加されました。これはバイオメトリック(生体認証)に関連する処理を引き受けてくれるもので、これを使うと今までアプリ側で実装していたダイアログの表示をシステム側で提供してくれるようになります。また、指紋認証だけでなく、虹彩認証や顔認証にも対応しており、ユーザが選択した方式を判別し、適切なダイアログを表示してくれるという優れものです。それに伴い、従来のFingerprintManager
はAndroid API 28より非推奨になりました。こんな便利な物を使わない手がないと、早速移行してみようと思ったのですが、BiometricPrompt
の最低サポートがAndroid API 28からなので、Min SDKバージョンが28より低い場合は完全に移行することが出来ません?今回はこの問題の解決方法と実装の仕方を簡単にまとめました。
解決方法
いきなり結論からです。
android.hardware.biometrics.BiometricPrompt
ではなくandroidx.biometrics.BiometricsPrompt
を使います。ただし、AndroidXを使うので従来のAndroid Support Libraryとは共存出来ない点に注意してください。実装方法
まず、依存関係をGradleに追加します。
dependencies { ... implementation 'androidx.biometric:biometric:1.0.0-alpha04' }生体認証を使う為、
Android Manifest
にパーミッションを追加します。<uses-permission android:name="android.permission.USE_BIOMETRIC" />最後に認証ダイアログを呼び出す処理を実装します。
val executor = Executors.newSingleThreadExecutor() val biometricPrompt = BiometricPrompt(this, executor, object : BiometricPrompt.AuthenticationCallback() { override fun onAuthenticationError(errorCode: Int, errString: CharSequence) { super.onAuthenticationError(errorCode, errString) Log.d("Biometric", "Error") } override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) { super.onAuthenticationSucceeded(result) Log.d("Biometric", "Succeeded") } override fun onAuthenticationFailed() { super.onAuthenticationFailed() Log.d("Biometric", "Failed") } }) val promptInfo = BiometricPrompt.PromptInfo.Builder() .setTitle("Title") .setSubtitle("Sub title") .setDescription("Description") .setNegativeButtonText("Cancel") .build() biometricPrompt.authenticate(promptInfo)これだけで、認証ダイアログを表示できます。
i ii iii 軽くコードの説明をします。
認証の結果を受け取るのは
BiometricPrompt
インスタンス生成時に渡しているBiometricPrompt.AuthenticationCallback
になります。
関数名 内容 onAuthenticationError エラーが発生して操作が完了された時に呼び出される onAuthenticationSucceeded 生体認証が成功した時に呼び出される onAuthenticationFailed 生体認証が有効だが認識されない時に呼び出される キャンセルボタンのイベントは
onAuthenticationError
メソッドでerrorCode == BiometricPrompt.ERROR_NEGATIVE_BUTTON
と比較することでキャッチすることができます。BIOMETRIC_ERROR
定数に関しては以下のものがあります。
定数名 内容 ERROR_CANCELED 生体認証が利用できない為、操作がキャンセルされた ERROR_HW_NOT_PRESENT 端末に生体センサが搭載されていない ERROR_HW_UNAVAILABLE 端末が利用できない ERROR_LOCKOUT 生体認証に5回失敗した ERROR_LOCKOUT_PERMANENT ERROR_LOCKOUT
の発生回数が多すぎる為、操作がキャンセルされたERROR_NEGATIVE_BUTTON ユーザがネガティブボタン(ダイアログのキャンセルボタン)を押した ERROR_NO_BIOMETRICS ユーザの生体認証情報が登録されていない ERROR_NO_SPACE 操作を完了するのに十分なストレージが用意されていない ERROR_TIMEOUT 認証を実行するまでの時間が長すぎる為、タイムアウトが発生した ERROR_UNABLE_TO_PROCESS 生体センサが画像を処理できない ERROR_USER_CANCELED ユーザが操作をキャンセルした ERROR_VENDOR いずれのエラーにも該当しない
ERROR_CANCELED
は具体的に端末で使用するユーザを切り替えた場合(おそらく生体認証情報がリセットされる)や端末がロックされている場合、他のアプリが生体認証を利用中の場合に発生するみたいです。ERROR_HW_UNAVAILABLE
はどういったタイミングで発生するのかがわかりませんでした。ERROR_LOCKOUT
が発生した場合、30秒後に再度認証可能になるみたいです(知らなかった)。ERROR_UNABLE_TO_PROCESS
はおそらく虹彩認証や顔認証などのカメラを利用する場合に発生するのかと思います。認証ダイアログのカスタマイズは
BiometricPrompt.PromptInfo.Builder
で行います。指定できる項目は以下の通りです。
関数名 内容 setTitle タイトルを設定(必須) setSubtitle サブタイトルを設定(任意) setDescription 説明を設定(任意) setNegativeButtonText ネガティブボタンに表示されるテキストを設定(必須) ダイアログの画像やメッセージといった細かい部分は流石に設定できないみたいですね。
また、認証ダイアログを表示する前に
private fun isBiometricEnabled(context: Context): Boolean = Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && context.getSystemService(KeyguardManager::class.java).isKeyguardSecure && context.packageManager.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT)のように端末が生体認証を利用出来るか判定した方が良さそうです。
まとめ
足早にですが
BiometricPrompt
をAndroid Mから使う方法を解説しました。本来はBuild.VERSION.SDK_INT
でAndroidのバージョンを取得してFingerprintManager
との処理を切り分けるのが行儀がいいのですが、ぶっちゃけめんどくさい効率的に出来るのであれば致し方ないのかなと。また、虹彩認証と顔認証については、次期バージョンのAndroid Q(API 29)から利用出来るみたいです。今回のデモアプリをGitHubに公開しておきますので、参考にしていただければ嬉しいです。では。参考文献
- Android Developers Docs: https://developer.android.com/reference/androidx/biometric/BiometricPrompt
- Android 9 features and APIs: https://developer.android.com/about/versions/pie/android-9.0
- Fingerprint Authentication using BiometricPrompt Compat: https://medium.com/mindorks/fingerprint-authentication-using-biometricprompt-compat-1466365b4795