com.zentri.otademo.ZentriOSBLEService.java Source code

Java tutorial

Introduction

Here is the source code for com.zentri.otademo.ZentriOSBLEService.java

Source

/*
 * Copyright (C) 2015, Zentri, Inc. All Rights Reserved.
 *
 * The Zentri BLE Android Libraries and Zentri BLE example applications are provided free of charge
 * by Zentri. The combined source code, and all derivatives, are licensed by Zentri SOLELY for use
 * with devices manufactured by Zentri, or devices approved by Zentri.
 *
 * Use of this software on any other devices or hardware platforms is strictly prohibited.
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AS IS AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING,
 * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 */
package com.zentri.otademo;

import android.app.Service;
import android.content.Intent;
import android.os.Binder;
import android.os.IBinder;
import android.support.v4.content.LocalBroadcastManager;
import android.util.Log;
import android.os.Handler;

import com.zentri.zentri_ble.BLECallbacks;
import com.zentri.zentri_ble.BLEHandlerAPI;
import com.zentri.zentri_ble.BLEHandlerSingleton;
import com.zentri.zentri_ble_ota.FirmwareVersion;
import com.zentri.zentri_ble_ota.OTACallbacks;
import com.zentri.zentri_ble_ota.OTAManager;
import com.zentri.zentri_ble_ota.OTAState;
import com.zentri.zentri_ble_ota.OTAStatus;

import java.util.regex.Pattern;

public class ZentriOSBLEService extends Service implements BLECallbacks, OTACallbacks {
    public static final String ACTION_CONNECTED = "com.zentri.otademo.ACTION_CONNECTED";
    public static final String ACTION_DISCONNECTED = "com.zentri.otademo.ACTION_DISCONNECTED";

    public static final String ACTION_SCAN_RESULT = "com.zentri.otademo.ACTION_SCAN_RESULT";
    public static final String ACTION_CURRENT_VERSION_UPDATE = "com.zentri.otademo.ACTION_CURRENT_VERSION_UPDATE";
    public static final String ACTION_UPDATE_VERSION_UPDATE = "com.zentri.otademo.ACTION_UPDATE_VERSION_UPDATE";
    public static final String ACTION_PROGRESS_MAX_UPDATE = "com.zentri.otademo.ACTION_PROGRESS_MAX_UPDATE";
    public static final String ACTION_PROGRESS_UPDATE = "com.zentri.otademo.ACTION_PROGRESS_UPDATE";
    public static final String ACTION_STATUS_UPDATE = "com.zentri.otademo.ACTION_STATUS_UPDATE";
    public static final String ACTION_UPDATE_COMPLETE = "com.zentri.otademo.ACTION_UPDATE_COMPLETE";
    public static final String ACTION_ERROR = "com.zentri.otademo.ACTION_ERROR";
    public static final String ACTION_TIMEOUT_CONNECT = "com.zentri.otademo.ACTION_TIMEOUT_CONNECT";
    public static final String ACTION_TIMEOUT_DISCONNECT = "com.zentri.otademo.ACTION_TIMEOUT_DISCONNECT";

    public static final String EXTRA_VERSION = "EXTRA_VERSION";
    public static final String EXTRA_NAME = "EXTRA_NAME";
    public static final String EXTRA_STATUS = "EXTRA_STATUS";
    public static final String EXTRA_COLOR = "EXTRA_COLOR";
    public static final String EXTRA_PROGRESS = "EXTRA_PROGRESS";
    public static final String EXTRA_ERROR = "EXTRA_ERROR";
    public static final String EXTRA_FINISH = "EXTRA_FINISH";

    public static final boolean TIMEOUT_ENABLED = true;

    //auto reconnect feature does not work on all devices
    private static final boolean AUTO_RECONNECT = true;
    private static final boolean FINISH_ON_CLOSE = true;
    private static final boolean DISABLE_TX_NOTIFY = true;
    private static final String FILENAME_LATEST = "";

    public static final int SERVICES_NONE = 0;
    public static final int SERVICES_ZENTRIOS_ONLY = 1;
    public static final int SERVICES_OTA_ONLY = 2;
    public static final int SERVICES_ALL = 3;

    private static final long DELAY_RECONNECT_MS = 500;
    private static final long TIMEOUT_CONNECT_MS = 5000;
    private static final long TIMEOUT_DISCONNECT_MS = 10000;

    private static final int ERROR_MSG_NONE = 0;

    private static final String PATTERN_MAC_ADDRESS = "(\\p{XDigit}{2}:){5}\\p{XDigit}{2}";

    private final String TAG = ZentriOSBLEService.class.getSimpleName();

    private final int mStartMode = START_NOT_STICKY;
    private final IBinder mBinder = new LocalBinder();
    boolean mAllowRebind = true;
    private OTAManager mOTAManager;
    private BLEHandlerAPI mBLEHandler;

    private String mDeviceName;
    private boolean mReconnect = false;

    private Status mStatus = Status.INITIALISING;
    private int mError;
    private boolean mFinishOnClose = false;
    private int mProgressMax = 30000;

    private Settings mSettings;

    private Handler mHandler;
    private Runnable mDisconnectTimeoutTask;
    private Runnable mConnectTimeoutTask;
    private Runnable mReconnectDelayTask;
    private Runnable mDisconnectDelayTask;

    private LocalBroadcastManager mBroadcastManager;

    public enum Status {
        INITIALISING, CHECKING_FOR_UPDATE, READY, UPDATING, UP_TO_DATE, ERROR
    }

    public class LocalBinder extends Binder {
        ZentriOSBLEService getService() {
            // Return this instance of LocalService so clients can call public methods
            return ZentriOSBLEService.this;
        }
    }

    private String mCurrentVersion;
    private String mUpdateVersion;

    @Override
    public void onCreate() {
        // The service is being created
        Log.d(TAG, "Creating service");

        mDeviceName = "";
        mHandler = new Handler();
        mBLEHandler = BLEHandlerSingleton.getInstance();
        mBLEHandler.init(this, this);
        mOTAManager = new OTAManager();
        mBroadcastManager = LocalBroadcastManager.getInstance(this);
        initTimeouts();
        mStatus = Status.INITIALISING;
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        // The service is starting, due to a call to startService()
        return mStartMode;
    }

    @Override
    public IBinder onBind(Intent intent) {
        // A client is binding to the service with bindService()
        return mBinder;
    }

    @Override
    public boolean onUnbind(Intent intent) {
        // All clients have unbound with unbindService()
        return mAllowRebind;
    }

    @Override
    public void onRebind(Intent intent) {
        // A client is binding to the service with bindService(),
        // after onUnbind() has already been called
    }

    @Override
    public void onDestroy() {
        // The service is no longer used and is being destroyed
        Log.d(TAG, "Destroying service");

        if (mBLEHandler != null) {
            mBLEHandler.stopBLEScan();
            mBLEHandler.disconnect(mDeviceName, !DISABLE_TX_NOTIFY);
            mBLEHandler.deinit();
        }

        mOTAManager.deinit();
    }

    public boolean initOTAManager() {
        sendStatus(Status.INITIALISING);
        return mOTAManager.init(mDeviceName, this);
    }

    /**
     * Used to stop all timeouts on application close
     */
    public void cancelTimeouts() {
        Log.d(TAG, "Cancelling timeouts");
        cancelTimeout(mDisconnectTimeoutTask);
        cancelTimeout(mConnectTimeoutTask);
        cancelTimeout(mReconnectDelayTask);
        cancelTimeout(mDisconnectDelayTask);
    }

    public String getCurrentVersion() {
        return mCurrentVersion;
    }

    public String getUpdateVersion() {
        return mUpdateVersion;
    }

    public int getProgressMax() {
        return mProgressMax;
    }

    public int getError() {
        return mError;
    }

    public boolean getFinishOnClose() {
        return mFinishOnClose;
    }

    public Status getStatus() {
        return mStatus;
    }

    public boolean getReconnect() {
        return mReconnect;
    }

    public void setReconnect(boolean reconnect) {
        mReconnect = reconnect;
    }

    public BLEHandlerAPI getBLEHandler() {
        return mBLEHandler;
    }

    public boolean isConnected() {
        return mBLEHandler.isConnected(mDeviceName);
    }

    public boolean isUpdateInProgress() {
        return mOTAManager.isOTAInProgress();
    }

    public void setSettings(Settings settings) {
        mSettings = settings;
    }

    public String getDeviceName() {
        return mDeviceName;
    }

    public OTAState getState() {
        return mOTAManager.getState();
    }

    public boolean updateStart() {
        return mOTAManager.updateStart();
    }

    public boolean updateAbort() {
        return mOTAManager.updateAbort();
    }

    public void checkForUpdates(String filename) {
        mStatus = Status.CHECKING_FOR_UPDATE;
        sendStatus(mStatus);
        if (!mOTAManager.checkForUpdates(this, filename)) {
            Log.e(TAG, "Failed to check for updates");
            sendError(R.string.error_get_image_failed, !FINISH_ON_CLOSE);
        }
    }

    public boolean disconnect(boolean disableTxNotification, boolean enableTimeout) {
        Log.d(TAG, "Disconnecting...");
        if (enableTimeout) {
            startTimeout(mDisconnectTimeoutTask, TIMEOUT_DISCONNECT_MS);
        }
        return mBLEHandler.disconnect(mDeviceName, disableTxNotification);
    }

    public boolean reconnect() {
        return mBLEHandler.connect(mDeviceName, !AUTO_RECONNECT);
    }

    @Override
    public void onScanResult(String deviceName) {
        Log.d(TAG, "onScanResult");

        if (deviceName != null && !Pattern.matches(PATTERN_MAC_ADDRESS, deviceName)) {
            Intent intent = new Intent(ACTION_SCAN_RESULT);
            intent.putExtra(EXTRA_NAME, deviceName);
            mBroadcastManager.sendBroadcast(intent);
        }
    }

    @Override
    public void onConnect(String deviceName, int servicesSupported) {
        mDeviceName = deviceName;

        cancelTimeout(mConnectTimeoutTask);

        if (servicesSupported == SERVICES_ALL || servicesSupported == SERVICES_OTA_ONLY) {
            //can upgrade device
            initOTAManager();
            Intent intent = new Intent(ACTION_CONNECTED);
            intent.putExtra(EXTRA_NAME, deviceName);
            mBroadcastManager.sendBroadcast(intent);
        } else {
            //error - device does not support firmware updates!
            sendError(R.string.error_ota_unsupported, FINISH_ON_CLOSE);
        }
    }

    @Override
    public void onConnectFailed(String deviceName, Result result) {
        if (result == Result.CONNECT_FAILURE) {
            sendError(R.string.error_connect_fail, FINISH_ON_CLOSE);
        } else if (result == Result.SERVICE_DISC_ERROR) {
            sendError(R.string.error_service_disc, FINISH_ON_CLOSE);
        }
    }

    @Override
    public void onDisconnect(String deviceName) {
        Log.d(TAG, "onDisconnect()");
        cancelTimeout(mDisconnectTimeoutTask);
        Intent intent = new Intent(ACTION_DISCONNECTED);
        intent.putExtra(EXTRA_NAME, deviceName);

        if (mReconnect) {
            Log.d(TAG, "Scheduling reconnect");
            mHandler.postDelayed(mReconnectDelayTask, DELAY_RECONNECT_MS);
            //dont send broadcast, we hope to be reconnected soon
        } else {
            mDeviceName = "";
            mBroadcastManager.sendBroadcast(intent);
        }

    }

    @Override
    public void onDisconnectFailed(String deviceName) {
        sendError(R.string.error_disconnect_failed, FINISH_ON_CLOSE);
    }

    @Override
    public void onStringDataRead(String deviceName, String data) {
    }

    @Override
    public void onBinaryDataRead(String deviceName, byte[] data) {
    }

    @Override
    public void onStringDataWrite(String deviceName, String data) {
    }

    @Override
    public void onBinaryDataWrite(String deviceName, byte[] data) {
    }

    @Override
    public void onModeChanged(String deviceName, int mode) {
    }

    @Override
    public void onModeRead(String deviceName, int mode) {
    }

    @Override
    public void onError(String deviceName, BLECallbacks.Error error, String s1) {
        onBLEError(error);
    }

    @Override
    public void onFirmwareVersionRead(String deviceName, String version) {
    }

    /***********************************************************************************************
    * OTA callbacks
    ***********************************************************************************************/
    public void onUpdateInitSuccess(String deviceName) {
        Log.d(TAG, "OTA init success");
        mOTAManager.readFirmwareVersion();
        sendStatus(Status.CHECKING_FOR_UPDATE);
    }

    public void onUpdateVersionRead(String deviceName, String version) {
        if (version != null) {
            Log.d(TAG, "Current version read - " + version);
            mCurrentVersion = version;
        } else {
            Log.e(TAG, "Current version read, was null!");
            mCurrentVersion = "";
        }

        sendCurrentVersion(mCurrentVersion);

        String filename;

        if (mSettings != null && !mSettings.useLatest()) {
            filename = String.format("truconnect-%s.bin", mSettings.getFirmwareFilename());
        } else {
            filename = FILENAME_LATEST;
        }

        sendStatus(Status.CHECKING_FOR_UPDATE);
        mOTAManager.checkForUpdates(this, filename);
    }

    public void onUpdateCheckComplete(String deviceName, boolean isUpToDate, FirmwareVersion version) {
        Log.d(TAG, "OTA update check complete!");

        if (isUpToDate) {
            mStatus = Status.UP_TO_DATE;
        } else {
            mStatus = Status.READY;
        }

        if (version != null) {
            mUpdateVersion = version.toString();
        } else {
            Log.e(TAG, "Update version was null!");
            mUpdateVersion = "";
        }

        sendStatus(mStatus);
        sendUpdateVersion(mUpdateVersion);
        mProgressMax = mOTAManager.getUpdateFileSize();
        sendProgressMaxUpdate(mProgressMax);
    }

    public void onUpdateAbort(String deviceName) {
        Log.d(TAG, "OTA aborted");
        mStatus = Status.READY;
        sendStatus(mStatus);
    }

    public void onUpdateStart(String deviceName) {
        Log.d(TAG, "OTA started");
        mStatus = Status.UPDATING;
        sendStatus(mStatus);
    }

    public void onUpdateComplete(String deviceName) {
        Log.d(TAG, "OTA completed successfully!");

        mReconnect = true;

        //ensure last BLE packets are sent
        startTimeout(mDisconnectDelayTask, DELAY_RECONNECT_MS);
    }

    public void onUpdateDataSent(String deviceName, int bytesSent, int bytesRemaining) {
        Log.d(TAG, "" + bytesRemaining + "bytes remaining");
        sendProgressUpdate(bytesSent);
    }

    public void onUpdateError(String deviceName, OTAStatus status) {
        mStatus = Status.ERROR;
        onOTAError(status);
        sendStatus(mStatus);
    }

    public void onTransitionUpdateRequired(String deviceName) {
        //not used
        sendError(R.string.error_transition_unsupported, !FINISH_ON_CLOSE);
    }

    public void onTransitionUpdateComplete(String deviceName) {
        //not used
    }

    /**********************************************************************************************/

    private void sendError(int msgId, boolean finishOnClose) {
        Intent intent = new Intent(ACTION_ERROR);

        intent.putExtra(EXTRA_ERROR, msgId);
        intent.putExtra(EXTRA_FINISH, finishOnClose);

        mBroadcastManager.sendBroadcast(intent);
    }

    private void sendStatus(Status status) {
        Intent intent = new Intent(ACTION_STATUS_UPDATE);
        intent.putExtra(EXTRA_STATUS, status);

        mBroadcastManager.sendBroadcast(intent);
    }

    private void sendCurrentVersion(String version) {
        sendIntentWithStringExtra(ACTION_CURRENT_VERSION_UPDATE, EXTRA_VERSION, version);
    }

    private void sendUpdateVersion(String version) {
        sendIntentWithStringExtra(ACTION_UPDATE_VERSION_UPDATE, EXTRA_VERSION, version);
    }

    private void sendProgressMaxUpdate(int max) {
        sendIntentWithIntExtra(ACTION_PROGRESS_MAX_UPDATE, EXTRA_PROGRESS, max);
    }

    private void sendProgressUpdate(int progress) {
        sendIntentWithIntExtra(ACTION_PROGRESS_UPDATE, EXTRA_PROGRESS, progress);
    }

    private void sendIntentWithStringExtra(String action, String extraID, String extra) {
        Intent intent = new Intent(action);

        if (extra != null) {
            intent.putExtra(extraID, extra);
        }

        mBroadcastManager.sendBroadcast(intent);
    }

    private void sendIntentWithIntExtra(String action, String extraID, int extra) {
        Intent intent = new Intent(action);

        intent.putExtra(extraID, extra);

        mBroadcastManager.sendBroadcast(intent);
    }

    private void onOTAError(OTAStatus status) {
        mFinishOnClose = false;

        Log.d(TAG, "OTA Error - " + status.toString());

        switch (status.getCode()) {
        case OTAStatus.FAILED_TO_GET_IMAGE:
            sendUpdateVersion(getString(R.string.update_version_on_error));
            mError = R.string.error_get_image_failed;
            mFinishOnClose = false;
            break;

        case OTAStatus.INIT_FAILED:
            mError = R.string.error_init_failed;
            mFinishOnClose = FINISH_ON_CLOSE;
            break;

        case OTAStatus.COMMUNICATION_ERROR:
            mError = R.string.error_comms;
            break;

        case OTAStatus.NO_INTERNET:
            mError = R.string.error_no_internet;
            break;

        case OTAStatus.ABORT_FAILED:
            mError = R.string.error_abort_failed;
            break;

        case OTAStatus.UNSUPPORTED_COMMAND:
            mError = R.string.error_ota_unsupported;
            break;

        case OTAStatus.ILLEGAL_STATE:
            mError = R.string.error_illegal_state;
            break;

        case OTAStatus.VERIFICATION_FAILED:
            mError = R.string.error_verify_failed;
            break;

        case OTAStatus.INVALID_IMAGE://from device:
            mError = R.string.error_invalid_image;
            break;

        case OTAStatus.INVALID_IMAGE_SIZE:
            mError = R.string.error_invalid_size;
            break;

        case OTAStatus.MORE_DATA:
            mError = R.string.error_more_data;
            break;

        case OTAStatus.INVALID_APPID:
            mError = R.string.error_invalid_app_id;
            break;

        case OTAStatus.INVALID_VERSION:
            mError = R.string.error_invalid_version;
            break;

        default:
            mError = R.string.error_unexpected;
            mFinishOnClose = false;
            break;
        }

        if (mError != ERROR_MSG_NONE) {
            sendError(mError, mFinishOnClose);
        }
    }

    private void onBLEError(Error error) {
        mFinishOnClose = true;

        Log.d(TAG, "BLE Error - " + error.toString());

        switch (error) {
        case CONNECT_WITHOUT_REQUEST:
            //reconnect, no need for error
            mFinishOnClose = false;
            break;

        case DISCONNECT_WITHOUT_REQUEST:
            //connection lost
            mError = R.string.error_connection_lost_message;
            break;

        case INVALID_MODE:
            //internal error - invalid connection mode
            mError = R.string.error_invalid_con_mode;
            break;

        case NO_TX_CHARACTERISTIC:
        case NO_RX_CHARACTERISTIC:
        case NO_MODE_CHARACTERISTIC:
            //cant communicate with device
            mError = R.string.device_error_message;
            break;

        case NO_CONNECTION_FOUND:
            //Connected to service, but device not connected
            mError = R.string.error_connection_lost_message;
            break;

        case NULL_GATT_ON_CALLBACK:
            mError = R.string.error_null_gatt_on_callback;
            break;

        case NULL_CHAR_ON_CALLBACK:
            mError = R.string.error_null_gatt_on_callback;
            break;

        case DATA_READ_FAILED:
            mError = R.string.error_data_read_failed;
            mFinishOnClose = false;
            break;

        case DATA_WRITE_FAILED:
            mError = R.string.error_data_write_failed;
            mFinishOnClose = false;
            break;

        case MODE_READ_FAILED:
            mError = R.string.error_mode_set_read_failed;
            mFinishOnClose = false;
            break;

        case MODE_WRITE_FAILED:
            mError = R.string.error_mode_set_write_failed;
            mFinishOnClose = false;
            break;

        case SET_TX_NOTIFY_FAILED:
            mError = R.string.error_set_tx_notify_failed;
            mFinishOnClose = false;
            break;

        case SET_OTA_CONTROL_NOTIFY_FAILED:
            mError = R.string.error_set_ota_control_notify_failed;
            break;

        case SERVICE_DISCOVERY_FAILED:
            mError = R.string.error_service_disc;
            break;

        case VERSION_READ_FAILED:
            mError = R.string.error_version_read_failed;
            mFinishOnClose = false;
            break;

        default:
            mError = R.string.error_unexpected;
            mFinishOnClose = false;
            break;
        }

        if (mError != ERROR_MSG_NONE) {
            sendError(mError, mFinishOnClose);
        }
    }

    private void startTimeout(Runnable task, long delay) {
        mHandler.postDelayed(task, delay);
    }

    private void cancelTimeout(Runnable task) {
        mHandler.removeCallbacks(task);
    }

    private void initTimeouts() {
        mDisconnectTimeoutTask = new Runnable() {
            @Override
            public void run() {
                //show error
                sendError(R.string.discon_timeout_message, FINISH_ON_CLOSE);
            }
        };

        mConnectTimeoutTask = new Runnable() {
            @Override
            public void run() {
                //show error
                sendError(R.string.con_timeout_message, FINISH_ON_CLOSE);
            }
        };

        mDisconnectDelayTask = new Runnable() {
            @Override
            public void run() {
                disconnect(!DISABLE_TX_NOTIFY, TIMEOUT_ENABLED);
            }
        };

        mReconnectDelayTask = new Runnable() {
            @Override
            public void run() {
                mBLEHandler.connect(mDeviceName, !AUTO_RECONNECT);
                startTimeout(mConnectTimeoutTask, TIMEOUT_CONNECT_MS);
            }
        };
    }

}