20201119のAndroidに関する記事は5件です。

Flutter実装時にお世話になったページ 2020/11/19

なにか

毎回、同じ問題に、同じ検索して、同じページで解決してるので。。。

Image.networkNetworkImage で画像が表示されない場合がある

特に画像が大量にある画面でたまに表示できてない画像があった。で、ログが流れる。

════════ Exception caught by image resource service ════════════
Connection closed before full header was received, uri = https://xxxxxx

Stack Overflow "Network Image - Connection closed before full header was received..."

これで解決する前に、同じページの別のコメントや

I globally overrode the HttpClient

別ISSUEのコメントに出てきた flutter_imageNetworkImageWithRetryを使ってみた。

Stack Overflow "Flutter how to handle Image.network error (like 404 or wrong url)"

これらは、解決に至らなかったけど、HttpClientのOverrideは今後役に立ちそうだし、flutter_imageの考え方は結構好き。

で、最終的にURLをHTTPにするという残念な対応になってしまっているので、根本解決されたときに戻しやすいようにしておいた。

Androidの通知用アイコンを設定したい

個人的には Android Asset Studioがありがたかった。

Qiita "Android Push通知のアイコンを作成 + 設定"

video_playerで連続再生している動画を、画面遷移時に止めたい。

もともと、inview_notifier_listでスクロールINしたときに再生開始にしていたんだけど、このコメント見て visibility_detectorに変えた。

Stack Overflow "How to pause flutter video(video_player plugin) when navigating from page to another"

最終的に、RouteObserverRouteAwareは使用せずに、didChangeDependencies内で、現在画面に表示中かどうかを取得してハンドリングするようにした。

そのWidgetが画面に描画されているかどうかを取得したい

Stack Overflow "How to check if a widget/page is rendered?"

さいごに

本当に、先人たちありがとう。

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

AOSP system/btでEclipse IDEを使う(中途半端)

--
README.mdより。

Eclipse IDE Support

  1. Follows the Chromium project
    Eclipse Setup Instructions
    until "Optional: Building inside Eclipse" section (don't do that section, we
    will set it up differently)

  2. Generate Eclipse settings:

  cd system/bt
  gn gen --ide=eclipse out/Default
  1. In Eclipse, do File->Import->C/C++->C/C++ Project Settings, choose the XML
    location under system/bt/out/Default

  2. Right click on the project. Go to Preferences->C/C++ Build->Builder Settings.
    Uncheck "Use default build command", but instead using "ninja -C out/Default"

5. Goto Behaviour tab, change clean command to "-t clean"

以下に従って作業開始。
https://chromium.googlesource.com/chromium/src.git/+/master/docs/linux/eclipse_dev.md

eclipse-inst-linux64.tar.gzをダウンロード
$ tar xvzf eclipse-inst-linux64.tar.gz
$ cd clipse-installer
$ gedit ./eclipse-inst.ini
ファイル名が違うけど多分これ。
-Xms256M -> -Xms1024M
-Xmx1024M -> -Xmx3072M

$ ./eclipse-inst
-> JREかJDKが必要とエラーになった。

$ java

コマンド 'java' が見つかりません。次の方法でインストールできます:

sudo apt install default-jre # version 2:1.11-72, or
sudo apt install openjdk-11-jre-headless # version 11.0.8+10-0ubuntu1~20.04
sudo apt install openjdk-13-jre-headless # version 13.0.3+3-1ubuntu2
sudo apt install openjdk-14-jre-headless # version 14.0.1+7-1ubuntu1
sudo apt install openjdk-8-jre-headless # version 8u265-b01-0ubuntu2~20.04

$ sudo apt install openjdk-14-jre-headless

$ ./eclipse-inst
-> 動いた
-> 適当にインストールしてしまった

$ cd ~/eclipse/cpp-2020-06/eclipse
$ ./eclipse
-> workspace設定もそのまま

Help -> Install New Software...を選択
"Work with:"でCDT - "http://download.eclipse.org/tools/cdt/releases/9.11"を選択
"CDT Main Features"と"CDT Optional Features"にチェックを入れてインストール -> Eclipse再起動
Window -> Perspective -> Open Perspective -> Other...で"C/C++"を選択(元からなってる?)
Java PerspectiveをClose(元からなってる?)

設定変更
"Build Automatically"のチェックを外す(恐ろしい設定だな)
  Windows -> Preferencesでworkspaceを検索(buildで検索した方が速い?)チェック外す。
"Refresh using native hooks or polling"のチェックを外す(元から外れている?)
  Windows -> Preferencesでworkspaceを検索

プロジェクト作成
File -> New -> Project... -> C/C++ -> Makefile Project with Existing CodeでNext
Project Name: aosp_bt
Existing Codec Location
aospのsystem/btの場所
ToolchainでLinux GCC選択
-> Finish

aosp_bt上で右クリック -> Properties... -> C/C++ General -> Preprocess Include Paths, Macros etc. -> Providersをクリック
"CDT GCC Built-in Compiler Settings"を選択
"Command to get compiler specs"に"-std=c++14"を追記

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

Pepper SDK入門(18) 仲良しこよしもChatから

今回の目標

複数のChatbotを使って適切な返答ができる才能をPepperに実装する回です。具体的なクラスの説明などは後日別の記事で行いますので、まずはChatの概要を掴みましょう。
本記事の読了特典として会話ごっこが出来るようになるため、もうPepperと二人きりでも気まずい沈黙は訪れません。

//ArrayList型でtopicsを宣言し、ChatTopic(ここでのファイル名はtopic_01)を入れる
ArrayList topics = ArrayList(TopicBuilder.with(qiContext).withResource(R.raw.topic_01).build());
// トピックリストからQiChatbotを作成
QiChatbot qiChatbot = QiChatbotBuilder.with(qiContext)
                                      .withTopics(topics)
                                      .build();

// Chatbotをカスタム
Chatbot myChatbot = new MyChatbot(qiContext);

//アクションをビルド
Chat chat = ChatBuilder.with(qiContext)
                       .withChatbot(qiChatbot, myChatbot)
                       .build();

// アクションを非同期に実行
chat.async().run();

これが今回の学習内容になります。
MyChatbotは独自に作ったChatbotクラスです。QiChatbotとMyChatbotを組み合わせることで、Pepperと難解だったり複雑だったりもしかすると怪奇だったりするダイアローグを成したい人は試してみてください。機械の奇怪って何だろう。

Chatbotの優先順位と選択肢

QiChatは人声の検知とPepperの返答を処理するもので、一つもしくは複数のChatbotを組み合わせて利用することができます。
複数のChatbotを使った場合、先頭から順番に応答を確認して最初に取得できたNormal Priorityの返答を採用する、という仕組みです。すべてのChatbotがNormal Priorityを返さなかった時には、Fallback Priorityの返答を採用します。コンプライアントにプライムなリプライをしてくれるのです。

Chatが複数の会話サービスをサポートしているのは何故?

入力された全ての事柄を理解し、最適な応答ができるChatbotはありません。
しかし、多くの人はPepperが何らかの応答をしてくれることを期待します。
そのためにどうするかというと、Pepperと既存の色々な会話サービスを繋げればいいのです。
Dialogflow、Botfuel、 MicrosoftBotFrameworkなど多くの会話サービスによって、QichatはPepperとの会話を上手に進めてくれます。Wikipediaとか天気の会話とか、それらのサービスで簡単に作れます。
音声認識、テキストの読み上げ、その他の応答などを独自で実装すると煩雑になってしまうので、QiChatを使用しましょう。

Chatの状態

Chatの状態を見てみましょう。

Listening

Pepperに話しかけられるのは、Chatがリスニング状態の時です。
Chatのリスニング状態は、getListeningメソッドから24時間365日いつでもアクセスできます。

Boolean isListening = chat.getListening();

リスナー側は以下のようになります。

chat.addOnListeningChangedListener(listening -> {
    // リスニングの状態が変更されたらコール
});

Hearing

PepperはgetHearingメソッドで人声を検知します。

Boolean isHearing = chat.getHearing();

リスナー側は以下のようになります。

chat.addOnHearingChangedListener(hearing -> {
    // ヒアリングの状態が変更されたらコール
});

Saying

Pepperの発話はgetSayingメソッドで確認できます。AITalkボイス

Phrase saying = chat.getSaying();

リスナー側は以下のようになります。

chat.addOnSayingChangedListener(sayingPhrase -> {
    // Pepperが話している時コール
});

入力を見てみる

リスナーを通じてPepperが何を聞いたか、確認することができます。

chat.addOnHeardListener(heardPhrase -> {
    // 人声が聞こえたらコール
});

Pepperは人声を検知できても、その内容を理解できない時があります。人間同士でも稀に発生する。

chat.addOnNoPhraseRecognizedListener {
    // 内容が理解できなかったらコール
}

答えは一つじゃないらしい

人声の検知に成功したPepperの返答には、三つの種類があります。

一つ目、Normalの場合

Fallback以外の通常の返答です。
リスナーを通じて確認できます。

chat.addOnNormalReplyFoundForListener(input -> {
    // Normalの返答を行う際にコール
});

二つ目、Fallbackの場合

Pepperが上手く答えを用意できないフレーズを聞き取った場合の返答です。
例えばQiChatbotがe:Dialog/NotUnderstoodに当てはまる時です。
この場合Pepperがフレーズの内容を聞き取っていても、inputパラメーターは空になります。

chat.addOnFallbackReplyFoundForListener(input -> {
    // Fallbackの返答を行う際にコール
});

三つ目、返答しない場合

入力がFallbackでも拾えないルール外のものである場合は、返答が行われません。
ここでも同じく、Pepperがフレーズの内容を聞き取っていてもinputパラメーターは空になります。

chat.addOnNoReplyFoundForListener(input -> {
    // 発話に対する返答が見つからない際にコール
});

じっとしておいて欲しい

Pepperは聞き取り中や発話中に、上半身が微妙に動いています。生命体ムーブ。
そのBodyLanguageを止めるには、以下のような実装を行ってください。

//ArrayList型でtopicsを宣言し、ChatTopic(ここでのファイル名はtopic_01)を入れる
ArrayList topics = ArrayList(TopicBuilder.with(qiContext).withResource(R.raw.topic_01).build());
// トピックリストからQiChatbotを作成
QiChatbot qiChatbot = QiChatbotBuilder.with(qiContext)
                                      .withTopics(topics)
                                      .build();

qiChatbot.setSpeakingBodyLanguage(BodyLanguageOption.DISABLED);     

//アクションをビルド
Chat chat = ChatBuilder.with(qiContext)
                       .withChatbot(qiChatbot, myChatbot)
                       .build();

chat.setListeningBodyLanguage(BodyLanguageOption.DISABLED);

// アクションを非同期に実行
chat.async().run();

k0038_0.png
以上で、Pepperとの会話のキャッチボールを叶えるグローブとボールの準備が整いました。
穏やかな言葉の投球と捕球を楽しみましょう。 

P.S.
Chatはあくまで関連性の高い会話をPepperと行うためのものです。
何か特定の発話や正確な音声認識をして欲しい場合には、SayやListenを使ってください。Chatの存在意義

また、DiscussやSay、Listenを実行している時にChatは機能しないので、注意してください。

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

THETA Photo Dog [THETA Plug-in OLED活用]

はじめに

リコーのYuuki_Sです。
弊社ではRICOH THETAという全周囲360度撮れるカメラを出しています。
RICOH THETA VやTHETA Z1は、OSにAndroidを採用しており、Androidアプリを作る感覚でTHETAをカスタマイズすることもでき、そのカスタマイズ機能を「プラグイン」と呼んでいます。(詳細は本記事の末尾を参照)。


プラグイン開発ができるTHETAには、VとZ1の二種類があります。
このうち、Z1はフラッグシップ機。
1.0型裏面照射型CMOSイメージセンサーを搭載しRAW撮影も出来るスグレモノです。

これまでずっとVでプラグイン開発をしてきた自分ですが、ついにZ1を利用する機会がやってきました!
Z1を手にして、さて、どんなプラグインを作ろうかとまじまじと見比べてみます。
3世代
左からZ1、V、Sです。
見て即座に分かるのは、レンズの大きさとディスプレイの有無。
Vでは詳細な状態を確認するためにスマホと繋げる必要がありましたが、Z1では本体のディスプレイで状態を確認できるのです。
ここはVとZ1の大きな違い!
なので、今回はこの有機ELディスプレイ(OLED)を使ったプラグインを作ることに決めました。

そして、出来上がったのがこちら↓

コンポ 1.gif
コンポ 1_3.gif
コンポ 1_8.gif
完全に一瞬の閃きから突っ走りました。
しかし、セルフタイマーのカウントダウンやQRコード表示等、ディスプレイがあることを最大限活用出来ているのではないでしょうか?

それでは、具体的な仕組みに関して触れていきましょう。

仕組み紹介

図1.png
仕組みは至ってシンプルです。
プラグイン側には、”待機状態” "セルフタイマー” "QRコード表示”の3つの状態を用意しています。
前者2つでは所定のアニメーション画像を再生するようにし、最後のQRコード表示では生成したQRコードと事前準備したアニメーションを合成して表示しています。

3つの状態は、撮影ボタンもしくは犬の鼻部分を押すことで推移します。
この鼻部分、実は中にBluetoothリモートシャッターが組み込まれています。
なので押されるとBluetooth経由でシャッターが切られ、セルフタイマー表示に切り替わる仕組みです。

プラグインからのOLED利用

THETAプラグイン開発者コミュニティでは過去にもOLEDの利用法を紹介しています。
・RICOH THETA Z1の有機ELパネルをプラグインからコントロールする方法
・THETA Z1 OLED描画ライブラリ的なものつくりました

これらの記事はとても詳しく、今回と重複する内容もありますが、本記事では自分が試したOLED利用方法を記載します。

※開発にはRICOH THETA Plug-in SDKを利用します。初めての方はこちらの記事を参照してください
 →THETAでRICOH THETA Plug-in SDKを動かす方法

さて、本題のOLED利用ですが画像を表示するだけなら以下を表示したいタイミングで呼ぶだけです。

//imageはBitmap
notificationOledImageShow(image);

大変簡単ですが1点だけ注意点があり、画像の解像度は横128pixel、縦は36pixelもしくは24pixelである必要があります。
36pixelにすれば今回の様に全画面表示が可能です。
(全画面表示に対応するためには2019年10月24日に公開されたファームウェア Ver 1.20.1を導入する必要があります)

また、notificationOledImageShowはカラー画像を入れても動作しますが、Z1に搭載されているOLEDは白黒の2値表示しか出来ません。
なので、階調表現が削られ望み通りの見栄えにならない可能性もあります。

今回はそれを見越して、最初からアニメーション画像を2値ビットマップの状態でAdobe After Effectsにて作成しました。
以下の連番Bitmapをassetフォルダに配置し、描画用のスレッドを用意して順次切り替えています。
image.png
白黒2値の低解像度ディスプレイというと、たまごっちやPocketStation、ウォークマンのコントローラを思い出す世代ですが、今回初めて自分で2値画像を作ってみると奥が深いな、と感じました。
少ない解像度で伝えたいことを表現するって大変ですね。
(たまごっち、執筆のために調べたら鬼滅の刃バージョンが販売されてました。2値ドットでもキャラクターが表現されていて凄い...)

あと、今回は利用していませんが以下のようにすれば文字も表示可能です。

Map<TextArea, String> output = new HashMap<>() ;
output.put(TextArea.MIDDLE, "Hello World !");
output.put(TextArea.BOTTOM, "This is output by the sample plugin.");
notificationOledTextShow(output);

なお、この他の複雑な利用に関しては、先程紹介した以下を利用すると便利になります。
・THETA Z1 OLED描画ライブラリ的なものつくりました

QRコードの表示

待機中とセルフタイマー時のOLED表示は、画像をパラパラ漫画で表示するだけでしたが、最後のQRコードはプラグイン内で生成する必要があります。
実はこれも前述の記事”THETA Z1 OLED描画ライブラリ的なものつくりました"で紹介されています。
基本的な導入に関してはこちらを参照ください。

ただし、以前の記事では縦24pixelでQRコードを表示していました。
今回は、OLEDの全画面表示に対応しているファームウェア(1.20.1以降)を利用しているので、表示できるQRコードのサイズが大きくなり、表現できる情報量も増えています。

 private void makeQrCode(String qrText) {
        int size = 36;
        try {
            BarcodeEncoder barcodeEncoder = new BarcodeEncoder();
            HashMap hints = new HashMap();
            hints.put(EncodeHintType.CHARACTER_SET, "shiftjis");
            hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.L);
            hints.put(EncodeHintType.QR_VERSION, 4);

            qrBitmap = barcodeEncoder.encodeBitmap(qrText, BarcodeFormat.QR_CODE, size, size,hints);
        } catch (WriterException e) {           
            e.printStackTrace();
        }
    }

QRコード生成部分は上記の通りで、誤り訂正レベルLにして画像サイズ36pixelにしています。
この状態でQRコードが持てる情報量はQRコードバージョン4の(英数記号含み)78文字になります。(参考:QRコードの情報量とバージョン)
この長さならURLも埋め込むこと出来ます。

(また、試した所QRコードバージョン5 "37pixel x 37pixel" で画像生成し、リサイズしてOLEDに表示しても読み込むことが出来ました。1pixel分の情報が削られているはずなのですが、誤り訂正がうまく働いたのでしょう。)

そして、このQRコード生成処理に撮影画像アドレスを渡す部分は以下の通りで、撮影後のコールバック関数に含めています。

    private TakePictureTask.Callback mTakePictureTaskCallback = new Callback() {
        @Override
        public void onTakePicture(String fileUrl)
        {           
            String fileUrl_replace = fileUrl.replace("127.0.0.1:8080","192.168.1.1");
            makeQrCodeVer(fileUrl_replace);
            FrameNum = 0;
            QRshowing = true;
        }
    };

ここで、fileUrl.replaceをおこなっています。
これはコールバックで得られるfileUrlを、APモードでアクセスできるURLに変更する処理です。
なお、今回はTHETAとスマートフォンが直接接続するAPモードのみを対象としています。
無線LANルータを挟んだCLモードへの対応は、いつかやりたい所です。

Bluetoothの接続

Bluetoothリモートシャッターの接続は、以下のページが分かりやすいです。
リモコンの使用方法(Z1/V/SC2)

プラグイン起動前に接続しておけば、起動後も利用できます。
リモートシャッターの場合、シャッターボタンが押されるとKEYCODE_VOLUME_UPが発生するので、プラグイン側では以下のように検出することができます。

  setKeyCallback(new KeyCallback() {
            @Override
            public void onKeyDown(int keyCode, KeyEvent event) {
                if (keyCode == KeyEvent.KEYCODE_VOLUME_UP) {
         ~

筐体の制作

ここまででプラグインの実装はほぼ完了です。
あとは筐体、犬部分の制作になります。
実は今回こちらの方が時間がかかりました。

まずノギス等使ってZ1の寸法を測り、Blenderで3Dモデルを作成します。
image.png
そして、それを軸に犬部分を作っていきます。
image.png
この際、3Dプリントをすることを念頭にパーツ分けをおこなうのが重要です。
image.png
この分割したパーツそれぞれをSTL形式で出力し、印刷していきます。(全部で1日かかりました)
待ちながら、カラーリングを検討します。
image.png
印刷終えたら、組み上げて塗装です。
絵の具
今回はアクリル絵の具を利用しました。PLA素材にはアクリル絵の具がのりやすいのでオススメです。

完成&まとめ

さて、これが完成した姿です。
コンポ 2_1_1.gif
結構可愛く仕上がってるのではないでしょうか?

今回の記事では、THETA Z1のOLEDが全画面利用できることを紹介しました。
自由な画像を表示したり、QRコードを表示することでTHETA単独で出来ることの幅が広がります。

ぜひ、利用してみてください!

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

THETAプラグインをご存じない方はこちらをご覧ください。
パートナープログラムへの登録方法はこちらにもまとめてあります。
QiitaのRICOH THETAプラグイン開発者コミュニティ TOPページ「About」に便利な記事リンク集もあります。
興味を持たれた方はTwitterのフォローとTHETAプラグイン開発コミュニティ(Slack)への参加もよろしくおねがいします。

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

Dagger Hilt の Context まわりでハマったこと

Dagger Hilt では、Context クラスが必要な場合のために、@ApplicationContextアノテーションと@ActivityContextアノテーションが用意されています。
Hilt を使用した依存関係の注入  |  Android デベロッパー  |  Android Developers
Context を使う上でハマったポイントがあったので、備忘録として残しておきます。

事例

例えば SharedPreferences をラップした、SharedPreferencesWrapperクラスをMainActivityで使う場合を考えてみます。

MainActivity.kt
@AndroidEntryPoint
class MainActivity : AppCompatActivity() {

    @Inject
    lateinit var sharedPreferencesWrapper: SharedPreferencesWrapper
    ...
}
SharedPreferencesWrapper.kt
/**
 * SharedPreferencesのラッパークラス
 */
class SharedPreferencesWrapper @Inject constructor(@ActivityContext context: Context) {

    private val sharedPreferences = context.getSharedPreferences("test", Context.MODE_PRIVATE)
    ...
}

そして、SharedPreferencesWrapper のインスタンスを提供するため、ActivityProvidesModuleクラスを以下の通り作成します。

ActivityProvidesModule.kt
/**
 * DI用ProvidesModule
 * @Inject が付いたプロパティや引数に提供する値の実体を定義
 */
@Module
@InstallIn(ActivityComponent::class)
object ActivityProvidesModule {

    /**
     * SharedPreferencesWrapperの提供
     */
    @Provides
    fun provideSharedPreferencesWrapper(context: Context): SharedPreferencesWrapper {
        return SharedPreferencesWrapper(context)
    }
}

ここでアプリをビルドすると、以下のエラーが出力されます。

/Users/Hitoshi/src/MyApplication/app/build/generated/source/kapt/debug/com/kmdhtsh/myapplication/MainApplication_HiltComponents.java:144: エラー: [Dagger/MissingBinding] android.content.Context cannot be provided without an @Provides-annotated method.
  public abstract static class ApplicationC implements MainApplication_GeneratedInjector,
                         ^
      android.content.Context is injected at
          com.kmdhtsh.myapplication.ActivityProvidesModule.provideSharedPreferencesWrapper(context)
      com.kmdhtsh.myapplication.SharedPreferencesWrapper is injected at
          com.kmdhtsh.myapplication.MainActivity.sharedPreferencesWrapper
      com.kmdhtsh.myapplication.MainActivity is injected at
          com.kmdhtsh.myapplication.MainActivity_GeneratedInjector.injectMainActivity(com.kmdhtsh.myapplication.MainActivity) [com.kmdhtsh.myapplication.MainApplication_HiltComponents.ApplicationC → com.kmdhtsh.myapplication.MainApplication_HiltComponents.ActivityRetainedC → com.kmdhtsh.myapplication.MainApplication_HiltComponents.ActivityC]

エラーを直訳すると、「Context は@Providesアノテーションメソッドがないと提供できません」と出ています。
解決策として、ActivityProvidesModuleクラスのprovideSharedPreferencesWrapperメソッドにも、同様のアノテーションを付与します。

ActivityProvidesModule.kt
/**
 * SharedPreferencesWrapperの提供
 */
@Provides
fun provideSharedPreferencesWrapper(@ActivityContext context: Context): SharedPreferencesWrapper {
    return SharedPreferencesWrapper(context)
}

これでビルドエラーが解消されるはずです。

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