com.tenkiv.tekdaqc.android.services.CommunicationService.java Source code

Java tutorial

Introduction

Here is the source code for com.tenkiv.tekdaqc.android.services.CommunicationService.java

Source

/**
 * Copyright 2013 Tenkiv, LLC.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.tenkiv.tekdaqc.android.services;

import android.app.Service;
import android.content.Intent;
import android.os.*;
import android.os.Process;
import android.support.v4.content.LocalBroadcastManager;
import android.util.Log;

import com.tenkiv.tekdaqc.ATekdaqc;
import com.tenkiv.tekdaqc.ATekdaqc.CONNECTION_METHOD;
import com.tenkiv.tekdaqc.android.application.TekCast;
import com.tenkiv.tekdaqc.communication.ascii.ASCIICommunicationSession;
import com.tenkiv.tekdaqc.communication.ascii.command.ASCIICommand;
import com.tenkiv.tekdaqc.communication.tasks.ITask;
import com.tenkiv.tekdaqc.communication.tasks.ITaskComplete;

import java.io.IOException;
import java.net.SocketTimeoutException;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * Android service for communicating with one or more Tekdaqcs. Handles connecting, disconnect, transmission of
 * commands and receipt of messages/data.
 *
 * @author Jared Woolston (jwoolston@tenkiv.com)
 * @since v1.0.0.0
 */
public class CommunicationService extends Service {

    /**
     * Logcat tag.
     */
    protected static final String TAG = CommunicationService.class.getSimpleName();

    /**
     * Map of {@link ASCIICommunicationSession}s keyed by the associated board's serial number.
     */
    private Map<String, ASCIICommunicationSession> mCommSessions;

    /**
     * @link Looper} for the background thread.
     */
    private Looper mServiceLooper;

    /**
     * {@link Handler} which allows for serialized processing of commands to this {@link Service} in the background.
     */
    private ServiceHandler mServiceHandler;

    /**
     * Application local {@link Intent} broadcast manager.
     */
    private LocalBroadcastManager mLocalBroadcastMgr;

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

        // Setup the background thread and its controls
        HandlerThread thread = new HandlerThread("TekDAQC Communication Service",
                Process.THREAD_PRIORITY_BACKGROUND);
        thread.start();

        mServiceLooper = thread.getLooper();
        mServiceHandler = new ServiceHandler(mServiceLooper, this);
        mLocalBroadcastMgr = LocalBroadcastManager.getInstance(getApplicationContext());

        // Initialize the session map
        mCommSessions = new ConcurrentHashMap<String, ASCIICommunicationSession>();
    }

    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        // Extract the service instruction
        final String action = intent.getAction();
        Log.d(TAG, "Received start command: " + action);
        // Build the message parameters
        Bundle extras = intent.getExtras();
        if (extras == null)
            extras = new Bundle();
        extras.putString(TekCast.EXTRA_SERVICE_ACTION, action);

        // Run each task in a background thread.
        final Message msg = mServiceHandler.obtainMessage();
        msg.arg1 = startId;
        msg.setData(extras);
        Log.d(TAG, "Sending message to background thread.");
        mServiceHandler.sendMessage(msg);
        return Service.START_NOT_STICKY;
    }

    @Override
    public void onDestroy() {
        // Send shutdown action
        Bundle extras = new Bundle();
        extras.putString(TekCast.EXTRA_SERVICE_ACTION, ServiceAction.STOP.name());
        final Message msg = mServiceHandler.obtainMessage();
        msg.setData(extras);
        mServiceHandler.sendMessage(msg);
        super.onDestroy();
    }

    /**
     * Broadcast through the application that a board has connected.
     *
     * @param serial {@link String} The serial number of the board which has connected.
     */
    private void notifyOfBoardConnection(String serial) {
        final Intent intent = new Intent(TekCast.ACTION_BOARD_CONNECTED);
        intent.putExtra(TekCast.EXTRA_BOARD_SERIAL, serial);
        mLocalBroadcastMgr.sendBroadcast(intent);
    }

    /**
     * Broadcast through the application that a board has disconnected.
     *
     * @param serial {@link String} The serial number of the board which has disconnected.
     */
    private void notifyOfBoardDisconnection(String serial) {
        final Intent intent = new Intent(TekCast.ACTION_BOARD_DISCONNECTED);
        intent.putExtra(TekCast.EXTRA_BOARD_SERIAL, serial);
        mLocalBroadcastMgr.sendBroadcast(intent);
    }

    /**
     * Broadcast through the application that a board connection has timed out.
     * 
     * @param serial {@link String} The serial number of the board which has timed out.
     */
    private void notifyOfBoardTimeout(String serial) {
        final Intent intent = new Intent(TekCast.ACTION_CONNECTION_TIMEOUT);
        intent.putExtra(TekCast.EXTRA_BOARD_SERIAL, serial);
        mLocalBroadcastMgr.sendBroadcast(intent);
    }

    /**
     * Actions which can be processed by the {@link CommunicationService}.
     *
     * @author Ian Thomas (toxicbakery@gmail.com)
     * @author Jared Woolston (jwoolston@tenkiv.com)
     * @since 1.0.0.0
     */
    public static enum ServiceAction {
        /**
         * Connect to a specific Tekdaqc board based on the information returned by the {@link DiscoveryService}.
         */
        CONNECT,

        /**
         * Disconnect from a specific Tekdaqc board.
         */
        DISCONNECT,

        /**
         * Issue a command to a specific Tekdaqc board.
         */
        COMMAND,

        /**
         * Execute a provided {@link ITask}.
         */
        EXECUTE_TASK,

        /**
         * Force a shutdown of the {@link CommunicationService}.
         */
        STOP;
    }

    /**
     * Worker thread {@link Handler} for handling incoming {@link DiscoveryService} {@link ServiceAction} requests.
     *
     * @author Jared Woolston (jwoolston@tenkiv.com)
     * @since 1.0.0.0
     */
    private static final class ServiceHandler extends Handler {

        /**
         * The {@link CommunicationService} which this {@link Handler} is serving.
         */
        private CommunicationService mService;

        /**
         * Constructor
         *
         * @param looper {@link Looper} The worker thread's {@link Looper}.
         * @param service {@link CommunicationService} The {@link CommunicationService} this {@link Handler} is serving.
         */
        public ServiceHandler(Looper looper, CommunicationService service) {
            super(looper);
            mService = service;
        }

        @Override
        public void handleMessage(Message msg) {
            // Fetch the task parameters
            final Bundle data = msg.getData();
            final ServiceAction action = ServiceAction.valueOf(data.getString(TekCast.EXTRA_SERVICE_ACTION));
            final ATekdaqc tekdaqc = ATekdaqc.getTekdaqcForSerial(data.getString(TekCast.EXTRA_BOARD_SERIAL));
            switch (action) {
            case CONNECT:
                // Connect to a tekdaqc
                Log.d(TAG, "Processing CONNECT message for Tekdaqc: " + tekdaqc);
                if (tekdaqc == null) {
                    throw new IllegalArgumentException("Missing required board extra.");
                } else if (tekdaqc.isConnected()) {
                    throw new IllegalStateException(
                            "Board " + tekdaqc.getSerialNumber() + " is already connected!");
                } else {
                    // Create the communication session
                    final ASCIICommunicationSession session = new ASCIICommunicationSession(tekdaqc);
                    try {
                        // Connect
                        Log.d(TAG, "Calling session connect method.");
                        session.connect(CONNECTION_METHOD.ETHERNET);
                        // Add this session to the session map
                        Log.d(TAG, "Adding session to the service session map.");
                        mService.mCommSessions.put(tekdaqc.getSerialNumber(), session);
                        // Notify the application that the new board is connected
                        Log.d(TAG, "Notifying service of board connection.");
                        mService.notifyOfBoardConnection(tekdaqc.getSerialNumber());
                    } catch (SocketTimeoutException e) {
                        e.printStackTrace();
                        mService.notifyOfBoardTimeout(tekdaqc.getSerialNumber());
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
                Log.d(TAG, "Returning from service connect message handler.");
                break;
            case DISCONNECT:
                // Disconnect from a tekdaqc
                Log.d(TAG, "Processing DISCONNECT message for Tekdaqc: " + tekdaqc);
                if (tekdaqc == null) {
                    throw new IllegalArgumentException("Missing required board extra.");
                } else if (!tekdaqc.isConnected()) {
                    throw new IllegalStateException(
                            "Board " + tekdaqc.getSerialNumber() + " is already disconnected!");
                } else {
                    try {
                        // Retrieve the communication session
                        final ASCIICommunicationSession session = mService.mCommSessions
                                .get(tekdaqc.getSerialNumber());
                        if (session != null) {
                            // Disconnect
                            session.disconnect();
                            // Remove the session from the map
                            mService.mCommSessions.remove(tekdaqc.getSerialNumber());
                            // Notify the application of the disconnect
                            mService.notifyOfBoardDisconnection(tekdaqc.getSerialNumber());
                        }
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
                break;
            case COMMAND:
                // Issue a command to a tekdaqc
                if (tekdaqc == null) {
                    throw new IllegalArgumentException("Missing required board extra.");
                } else if (!tekdaqc.isConnected()) {
                    throw new IllegalStateException("Board " + tekdaqc.getSerialNumber() + " is not connected!");
                } else {
                    final ASCIICommunicationSession session = mService.mCommSessions.get(tekdaqc.getSerialNumber());
                    final ASCIICommand command = (ASCIICommand) data.getSerializable(TekCast.EXTRA_BOARD_COMMAND);
                    session.executeCommand(command);
                }
                break;
            case EXECUTE_TASK:
                // Execute an ITask
                if (tekdaqc == null) {
                    throw new IllegalArgumentException("Missing required board extra.");
                } else if (!tekdaqc.isConnected()) {
                    throw new IllegalStateException("Board " + tekdaqc.getSerialNumber() + " is not connected!");
                } else {
                    final ASCIICommunicationSession session = mService.mCommSessions.get(tekdaqc.getSerialNumber());
                    final ITask task = (ITask) data.getSerializable(TekCast.EXTRA_TASK);
                    task.setSession(session);
                    Log.d(TAG, "Calling execute on task: " + task);
                    task.execute((ITaskComplete) data.getSerializable(TekCast.EXTRA_TASK_COMPLETE_CALLBACK));
                }
                break;
            case STOP:
                // Stop execution of this service cleanly
                for (String key : mService.mCommSessions.keySet()) {
                    try {
                        // Disconnect from all known connected tekdaqc's.
                        final ASCIICommunicationSession session = mService.mCommSessions.get(key);
                        session.disconnect();
                        mService.notifyOfBoardDisconnection(key);
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
                mService.stopSelf();
                mService.mServiceLooper.quit();
                break;
            }
        }
    }
}