連載
» 2014年10月17日 18時00分 公開

Androidで動く携帯Javaアプリ作成入門(55):Android 5.0発表&スマホと連動する音声認識Android Wearアプリの作り方 (3/4)

[緒方聡,株式会社イーフロー]

サンプルアプリの画面構成

 アプリは以下のような画面構成になっています。

図12 初回起動時

 初回起動時は、タクシー会社の電話番号を登録する画面が現れます。これは、アプリをボイスアクションから起動した場合でも、メニューから選択して起動した場合でも同様です。

 電話番号登録は音声で行います。マイクアイコンをタッチすると、音声入力のためのノーティフィケーションを通知し、アプリはいったん終了します(※本来であればノーティフィケーションではなく、アプリ内で音声入力を行いたいのですが、現段階ではそのような方法は提供されていないようです)。

図13 ノーティフィケーション表示

 マイクアイコンで通知したノーティフィケーションを表示したところです。このノーティフィケーションは右側にスワイプすることであらかじめ登録しておいたアクション画面に遷移させられます。

図14 ノーティフィケーションのAction画面表示

 RemoteInputで音声入力を行うためのアイコンが設置されているノーティフィケーション画面です。マイクアイコンをタッチすることで、音声入力画面に遷移します。

図15 音声入力

 Android Wear上のアプリでは、頻繁に利用することになるRemoteInputによる音声入力です。ここで電話番号を日本語で入力します。

図16 入力確認

 音声入力の結果確認画面です。入力が期待通りではない場合、キャンセルをタップすることで、何度でも入力し直せます。キャンセルアイコンの周りの円が一周するまでにタップしなければなりません(大体3秒ぐらいです)。間違えてキャンセルをタップしても、本当にキャンセルするか、確定するかが選べるので、迷ったらタップするようにすればよいでしょう。

図17 タクシーを呼ぶ

 音声入力が完了すると、入力結果を受け取ってアプリが起動するように設定してあります。受け取った電話番号はSharedPreferenceに保存して、以降はすぐにタクシーを呼べるようにしています。「タクシーを呼ぶ」というアイコンをタップすることで、電話をかけるようにする予定ですが、スマートフォン側の実装も必要なので、今回のバージョンでは何も起こりません。

 電話番号を変更したい場合は、右側にスワイプします。

図18 電話番号リセット

 登録した電話番号をリセットする画面です。SharedPreferenceから電話番号を削除して、アプリを終了します。アプリは初期状態に戻るので、登録からやり直しとなります。

 それでは、それぞれの実装を見ていきましょう。

Asndroid WearのActivity「MyActivity」

 このアプリ唯一のActivityであるMyActivityは、Android Studioで作成したひな型の名前のままですが、内容は大きく変更されています。

 まず、読み込むレイアウトですが、xxxFragmentをページのように複数切り替えて管理するViewPagerを使用するようにしています。大まかなレイアウトXMLの構成は以下のようになっています。

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout>
    <android.support.v4.view.ViewPager>
    <LinearLayout>
        <ImageView />
        <ImageView />
    </LinearLayout>
</RelativeLayout>

 RelativeLayoutにViewPagerを上マージンで配置し、その下にページ切り替えのインジケータが表示されるようにしています。

図19 MyActivityのプレビュー

 Activity生成時に呼び出されるonCreate()では、judgeLaunchReason()にIntentを渡して、Activityが起動された方法を判別するようにしています。

 このアプリが起動される方法は、(1)開始メニューから直接起動、(2)ボイスアクション「タクシーを呼ぶ」から起動、(3)ノーティフィケーションの音声入力後に起動、の3パターンです。

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    judgeLaunchReason(getIntent());
}
 
private void judgeLaunchReason(Intent intent) {
    final String ACTION_TAXI = "com.google.android.gms.actions.RESERVE_TAXI_RESERVATION";
    Bundle remoteInputResults = RemoteInput.getResultsFromIntent(intent);
    if (remoteInputResults != null) {
        // (3)のパターンは、remoteInputResultsが存在する
        CharSequence replyMessage = remoteInputResults.getCharSequence(EXTRA_REPLY);
        Log.d(TAG, "replyMessage=" + replyMessage.toString());
        SharedPreferences sp = getSharedPreferences(PREF_NAME, Activity.MODE_PRIVATE);
        SharedPreferences.Editor editor = sp.edit();
        editor.putString(KEY_NUMBER, replyMessage.toString());
        editor.apply();
        NotificationManagerCompat.from(this).cancel(NOTIFICATION_ID);
    }
    if (ACTION_TAXI.equals(intent.getAction())) {
        // (2)のパターンは、アクションが「タクシーを呼ぶ」で規定されたもの
        setContentView(R.layout.activity_taxi);
        setupViews();
    } else {
        // (1)のパターンはそれ以外
        setContentView(R.layout.activity_taxi);
        setupViews();
    }
}

 コードでは、(1)と(2)で全く同じ処理を行っていますが、説明のためにif文の条件分岐を行っています。

 このコードで特に重要なのが、音声によって入力された文字列を取得する方法です。該当箇所を再掲します。

Bundle remoteInputResults = RemoteInput.getResultsFromIntent(intent);
if (remoteInputResults != null) {
    CharSequence replyMessage = remoteInputResults.getCharSequence(EXTRA_REPLY);
}

 RemoteInput.getResultFromIntent()にIntentを渡しBundleが取得できれば音声入力による文字列が存在することになります。文字列はRemoteInputを生成する際に指定したキーでgetCharSequence()で取り出します。

 Android Wear上で動作するアプリは、キーボード入力がないため、音声入力に頼らざるを得ない場面がしばしば出て来ることになり、その際上記のスニペットはイディオムとして使用できます。

 MyActivityには、setupViews()というFragmentとViewを設定するメソッドが存在します。

private void setupViews() {
    mPager = (ViewPager) findViewById(R.id.pager);
    mFirstIndicator = (ImageView) findViewById(R.id.indicator_0);
    mSecondIndicator = (ImageView) findViewById(R.id.indicator_1);
    PagerAdapter adapter = new PagerAdapter(getFragmentManager());
    if (getSharedPreferences(PREF_NAME, MODE_PRIVATE).contains(KEY_NUMBER)) {
        adapter.addFragment(new CallFragment());
        adapter.addFragment(new ResetFragment());
        setIndicator(0);
    } else {
        adapter.addFragment(new SetupFragment());
        findViewById(R.id.trail).setVisibility(View.GONE);
    }
    mPager.setOnPageChangeListener(new ViewPager.OnPageChangeListener() {
        @Override
        public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
        }
 
        @Override
        public void onPageSelected(int position) {
            setIndicator(position);
        }
 
        @Override
        public void onPageScrollStateChanged(int state) {
        }
    });
 
    mPager.setAdapter(adapter);
}

 電話番号がすでに入力済みならタクシーを呼ぶためのFragmentと電話番号リセットのためのFragmentを用意し、未入力なら電話番号登録のためのFragmentだけを用意し、ViewPagerを初期化しています。ページ切り替えのインジケーターを非表示にしているのもここです。OnPageChangeListenerもインジケーターを制御するための簡単な実装になっています。

Copyright © ITmedia, Inc. All Rights Reserved.

RSSについて

アイティメディアIDについて

メールマガジン登録

@ITのメールマガジンは、 もちろん、すべて無料です。ぜひメールマガジンをご購読ください。