20190524のAndroidに関する記事は4件です。

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アプリ開発を学びに来てきてください!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【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.kt
fun beaconDistance(txPower: Int, rssi: Int): Double = 10.0.pow((txPower - rssi) / 20.0)

実際に観測してログに出すだけのコードはこちら

Sample.kt
    override fun onBeaconServiceConnect() {
         ~  ~

        beaconManager?.addRangeNotifier { beacons, region ->
            beacons
               .map {
                   "Distance:"+beaconDistance(it.txPower, it.rssi)
                   // it.distanceもあったけど誤差がやばい(私的見解)
               }
               .forEach { Log.d(TAG, it) }
        }
         ~  ~
    }

測ってみてどうだった?

端末持ってウロウロしてみたけど外の交通量やノイズ?や建物の作りでまあまあ誤差がでるみたい。
beacon.distance 使えないな〜、もうちょっと正確なのがいいな。
くらいの時に目安に使うのならいいかもね。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

RICOH THETA Z1の有機ELパネルをプラグインからコントロールする方法

RICOH THETA Z1がついに日本でも発売

リコーの@ueueです。

THETA Z1が日本でも発売になりました。

z1.png

THETA Z1もVと同じくプラグインをインストールして機能を拡張することができます。
最高かよ。

そして、THETA Z1には有機ELパネル(OLED)がついています。
神だな、おい。
oled.png

ということで、この記事ではプラグインから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);

表示は以下のようになります。
moji.JPG

IntentクラスのputExtraメソッドで文字列を設定します。text-middleが画面を3分割した真ん中の行に表示されます。text-bottomが画面が一番下の行です。

表示できるのはASCII printable charactersの大文字です。サンプルコードのように指定した文字列に小文字が使われている場合は、自動的に大文字に変換されます。

以下にその他の注意事項をまとめます。

  • text-middleを指定しない場合はプラグイン名が表示されます。
  • text-bottomを指定しない場合は空白が表示されます
  • 表示は左詰めになります。
  • 表示領域から指定した文字列がはみ出る場合には、スクロールしてすべての文字が表示されます。スクロールをプラグインから制御することはできません。

以下が、スクロールさせた場合の画像です。1行目は"KAOMOJI"という短い文字列。2行目に顔文字が続く長い文字列を設定しました。2行目のほうだけスクロールが走ります。

OLED_Scroll_w640.gif

画像表示

次に画像表示ついて説明します。
画像はOLEDの下2/3の領域に一つの画像を表示させることができます。
また、点滅表示をさせることも可能です。

画像の準備

まずは画像を準備します。

画像の形式はビットマップ、大きさは128x24にする必要があります。
形式や大きさが異なると何も表示されませんのでご注意ください。

今回はサンプル画像として、いらすとやの360度カメラを表示させてみました。
camera360.png
大きさが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);

表示は以下のようになります。
oled_theta.JPG

サンプルコード(画像の点滅表示)

画像を点滅させたい場合は “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としました。

keycode.png

表示は以下のようになります。
normal_mode.JPG

何も表示させない

プラグインで制御できる下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);

表示は以下のようになります。
oled_nothing.JPG

画面の明るさ

画面の明るさは以下の部分で設定しています。

//明るさの調整
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を紹介します。

濃淡のコントロール

以下が、新宿の街で信号を見上げるワイです。
uesan.png

このようにごちゃごちゃしたカラー画像をTHETA側のおまかせでモノクロに変換すると、なんだかよくわからない画像になります。

oled_uesan.JPG

このような場合、例えばこのサイトなどで、自分でしきい値を調整しながらモノクロ画像をつくることで見やすく改善することが可能です。
上記サイトで調整しながらモノクロに変換したのが、次の画像です。

uesan_mono.png
これをTHETAで描画すると
oled_uesan_mono.JPG

けっこうよくなったのが伝わるでしょうか。

ビットマップ画像を自分で作成

これまでのサンプルはすでにできあがった画像を表示していましたが、自分で画像を作成することも可能です。
詳しくないので、横シマを描画する簡単なサンプルだけのせておきます。

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);
        }
    }
}

こんな感じで横縞が描画されます
横縞.jpg

コードのコメント行のところを下記のように入れ替えて、縦縞を有効にすると

if (width%2==0) { //縦縞
// if (height%2==0) {  //横縞

縦縞が描画されます。
縦縞.jpg

興味を持った方はこのあたりを読めばもっといろいろできると思います。

あと、近々、THETAプラグイン開発者コミュニティのメンバーがOLEDの画像描画についてもうすこしつっこんだ記事を書くっぽいです。

まとめ

THETA Z1の有機ELパネルの表示をプラグインからコントロールする方法を説明しました。

RICOH THETAプラグインパートナープログラムについて

THETAプラグインに興味を持たれた方がいれば、以下の記事もぜひご覧ください。

RICOH THETAプラグイン開発者コミュニティでは、他にも記事を書いています。
RICOH THETAプラグインについてはこちらに情報がまとまっています。興味を持たれた方はtwitterのフォローとTHETAプラグイン開発コミュニティ(slack)への参加もぜひどうぞ。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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
img_1 img_2 img_3

軽くコードの説明をします。

認証の結果を受け取るのは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秒後に再度認証可能になるみたいです(知らなかった:sweat_smile:)。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に公開しておきますので、参考にしていただければ嬉しいです。では。

参考文献

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む