me.willowcheng.makerthings.core.OpenHABVoiceService.java Source code

Java tutorial

Introduction

Here is the source code for me.willowcheng.makerthings.core.OpenHABVoiceService.java

Source

/**
 * Copyright (c) 2010-2014, openHAB.org and others.
 *
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 */

package me.willowcheng.makerthings.core;

import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Handler;
import android.os.Looper;
import android.preference.PreferenceManager;
import android.speech.RecognizerIntent;
import android.util.Log;
import android.widget.Toast;

import com.loopj.android.http.AsyncHttpResponseHandler;

import org.apache.http.Header;
import org.apache.http.entity.StringEntity;
import me.willowcheng.makerthings.R;
import me.willowcheng.makerthings.util.Constants;
import me.willowcheng.makerthings.util.ContinuingIntentService;
import me.willowcheng.makerthings.util.MyAsyncHttpClient;

import java.io.UnsupportedEncodingException;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;

/**
 * This service handles voice commands and sends them to OpenHAB.
 * It will use the openHAB base URL if passed in the intent's extra,
 * or else use the {@link OpenHABTracker} to discover openHAB itself.
 */
public class OpenHABVoiceService extends ContinuingIntentService implements OpenHABTrackerReceiver {

    private static final String TAG = OpenHABVoiceService.class.getSimpleName();
    public static final String OPENHAB_BASE_URL_EXTRA = "openHABBaseUrl";

    private String mOpenHABBaseUrl;
    private MyAsyncHttpClient mAsyncHttpClient;

    private OpenHABTracker mOpenHABTracker;
    private Queue<Intent> mBufferedIntents;

    public OpenHABVoiceService() {
        super(TAG);
    }

    @Override
    public void onCreate() {
        super.onCreate();
        Log.d(TAG, "onCreate()");

        mBufferedIntents = new LinkedList<Intent>();
        initHttpClient();
    }

    private void initHttpClient() {
        SharedPreferences mSettings = PreferenceManager.getDefaultSharedPreferences(this);
        String username = mSettings.getString(Constants.PREFERENCE_USERNAME, null);
        String password = mSettings.getString(Constants.PREFERENCE_PASSWORD, null);
        mAsyncHttpClient = new MyAsyncHttpClient(this);
        mAsyncHttpClient.setBasicAuth(username, password);
    }

    @Override
    protected void onHandleIntent(Intent intent) {
        Log.d(TAG, "onHandleIntent()");
        bufferIntent(intent);
        if (intent.hasExtra(OPENHAB_BASE_URL_EXTRA)) {
            Log.d(TAG, "openHABBaseUrl passed as Intent");
            onOpenHABTracked(intent.getStringExtra(OPENHAB_BASE_URL_EXTRA), null);
        } else if (mOpenHABTracker == null) {
            Log.d(TAG, "No openHABBaseUrl passed, starting OpenHABTracker");
            mOpenHABTracker = new OpenHABTracker(OpenHABVoiceService.this, getString(R.string.openhab_service_type),
                    false);
            mOpenHABTracker.start();
        }
    }

    /**
     * Buffers the {@link Intent} to be processed later when openHABBaseUrl has been determined by {@link OpenHABTracker}.
     *
     * Usually, the discovery of the openHABBaseUrl is fast enough, so there will be only one intent in the buffer
     * when the buffer is processed and this service is stopped.
     * However, it is not guaranteed that this service is only called once before openHABBaseUrl can be discovered.
     * Therefore all intents are buffered and later this buffer will be processed.
     *
     * @param intent The {@link Intent} to be buffered.
     */
    private void bufferIntent(Intent intent) {
        mBufferedIntents.add(intent);
    }

    @Override
    public void onOpenHABTracked(String baseUrl, String message) {
        Log.d(TAG, "onOpenHABTracked(): " + baseUrl);
        mOpenHABBaseUrl = baseUrl;
        while (!mBufferedIntents.isEmpty()) {
            processVoiceIntent(mBufferedIntents.poll());
        }
        Log.d(TAG, "Stopping service for start ID " + getLastStartId());
        stopSelf(getLastStartId());
    }

    @Override
    public void onError(String error) {
        showToast(error);
        Log.d(TAG, "onError(): " + error);
        stopSelf();
    }

    private void processVoiceIntent(Intent data) {
        Log.d(TAG, "processVoiceIntent()");

        String voiceCommand = extractVoiceCommand(data);
        if (!voiceCommand.isEmpty()) {
            if (mOpenHABBaseUrl != null) {
                sendItemCommand("VoiceCommand", voiceCommand);
            } else {
                Log.w(TAG, "Couldn't determine OpenHAB URL");
                showToast("Couldn't determine OpenHAB URL");
            }
        }
    }

    private String extractVoiceCommand(Intent data) {
        String voiceCommand = "";
        List<String> textMatchList = data.getStringArrayListExtra(RecognizerIntent.EXTRA_RESULTS);
        if (!textMatchList.isEmpty()) {
            voiceCommand = textMatchList.get(0);
        }
        Log.i(TAG, "Recognized text: " + voiceCommand);
        showToast(getString(R.string.info_voice_recognized_text, voiceCommand));
        return voiceCommand;
    }

    private void sendItemCommand(final String itemName, final String command) {
        Log.d(TAG, "sendItemCommand(): itemName=" + itemName + ", command=" + command);
        try {
            performHttpPost(itemName, new StringEntity(command, "UTF-8"));
        } catch (UnsupportedEncodingException e) {
            Log.e(TAG, "Unable to encode command " + command, e);
        }
    }

    private void performHttpPost(final String itemName, final StringEntity command) {
        /* Call MyAsyncHttpClient on the main UI thread in order to retrieve the callbacks correctly.
         * If calling MyAsyncHttpClient directly, the following would happen:
         * (1) MyAsyncHttpClient performs the HTTP post asynchronously
         * (2) OpenHABVoiceService stops because all intents have been handled
         * (3) MyAsyncHttpClient tries to call onSuccess() or onFailure(), which is not possible
         *     anymore because OpenHABVoiceService is already stopped/destroyed.
         */
        new Handler(Looper.getMainLooper()).post(new Runnable() {
            @Override
            public void run() {
                mAsyncHttpClient.post(OpenHABVoiceService.this, mOpenHABBaseUrl + "rest/items/" + itemName, command,
                        "text/plain;charset=UTF-8", new AsyncHttpResponseHandler() {
                            @Override
                            public void onSuccess(int statusCode, Header[] headers, byte[] responseBody) {
                                Log.d(TAG, "Command was sent successfully");
                            }

                            @Override
                            public void onFailure(int statusCode, Header[] headers, byte[] responseBody,
                                    Throwable error) {
                                Log.e(TAG, "Got command error " + statusCode, error);
                            }
                        });
            }
        });
    }

    @Override
    public void onDestroy() {
        Log.d(TAG, "onDestroy()");
        if (mOpenHABTracker != null) {
            mOpenHABTracker.stop();
        }
        super.onDestroy();
    }

    /**
     * Displays the given message as a toast
     *
     * @param message The message to be displayed.
     */
    private void showToast(final String message) {
        // Display toast on main looper because OpenHABVoiceService might be destroyed
        // before to toast has finished displaying
        new Handler(Looper.getMainLooper()).post(new Runnable() {
            @Override
            public void run() {
                Toast.makeText(getApplicationContext(), message, Toast.LENGTH_LONG).show();
            }
        });
    }

    @Override
    public void onBonjourDiscoveryStarted() {
        Log.d(TAG, "onBonjourDiscoveryStarted()");
    }

    @Override
    public void onBonjourDiscoveryFinished() {
        Log.d(TAG, "onBonjourDiscoveryFinished()");
    }
}