/* * ANT Stack * * Copyright 2009 Dynastream Innovations * * 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.dsi.ant.server; import android.bluetooth.BluetoothAdapter; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.app.Service; import android.os.Binder; import android.os.IBinder; import android.os.RemoteException; import android.provider.Settings; import android.util.Log; import android.os.SystemProperties; import com.dsi.ant.core.*; import com.dsi.ant.server.AntHalDefine; import com.dsi.ant.server.IAntHal; import com.dsi.ant.server.IAntHalCallback; import com.dsi.ant.server.Version; public class AntService extends Service { private static final String TAG = "AntHalService"; private static final boolean DEBUG = false; public static final String ANT_SERVICE = "AntService"; /** * Allows the application to directly configure the ANT radio through the * proxy service. Malicious applications may prevent other ANT applications * from connecting to ANT devices */ public static final String ANT_ADMIN_PERMISSION = "com.dsi.ant.permission.ANT_ADMIN"; /** * Request that ANT be enabled */ public static final String ACTION_REQUEST_ENABLE = "com.dsi.ant.server.action.REQUEST_ENABLE"; /** * Request that ANT be disabled */ public static final String ACTION_REQUEST_DISABLE = "com.dsi.ant.server.action.REQUEST_DISABLE"; private JAntJava mJAnt = null; private boolean mInitialized = false; /** * Flag for if Bluetooth needs to be enabled for ANT to enable */ private boolean mRequiresBluetoothOn = false; /** * Flag which specifies if we are waiting for an ANT enable intent */ private boolean mEnablePending = false; private Object mChangeAntPowerState_LOCK = new Object(); private static Object sAntHalServiceDestroy_LOCK = new Object(); /** Callback object for sending events to the upper layers */ private volatile IAntHalCallback mCallback; /** Used for synchronizing changes to {@link #mCallback} * The synchronization is needed because of how {@link #doUnregisterAntHalCallback(IAntHalCallback)} works */ private final Object mCallback_LOCK = new Object(); /** * Receives Bluetooth State Changed intent and sends {@link ACTION_REQUEST_ENABLE} * and {@link ACTION_REQUEST_DISABLE} accordingly */ private final StateChangedReceiver mStateChangedReceiver = new StateChangedReceiver(); /** * Receives {@link ACTION_REQUEST_ENABLE} and {@link ACTION_REQUEST_DISABLE} * intents to enable and disable ANT. */ private final BroadcastReceiver mReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { if (mRequiresBluetoothOn) { String action = intent.getAction(); if (ACTION_REQUEST_ENABLE.equals(action)) { if (mEnablePending) { asyncSetAntPowerState(true); mEnablePending = false; } } else if (ACTION_REQUEST_DISABLE.equals(action)) { if (mEnablePending) { mEnablePending = false; } else { asyncSetAntPowerState(false); } } } } }; /** * Checks if Bluetooth needs to be turned on for ANT to enable */ private boolean requiresBluetoothOn() { return false; // Set to true if require bluetooth on for ANT functionality } public static boolean startService(Context context) { return ( null != context.startService(new Intent(IAntHal.class.getName())) ); } /** * Calls back the registered callback with the change to the new state * @param state the {@link AntHalDefine} state */ private void setState(int state) { synchronized(mChangeAntPowerState_LOCK) { if(DEBUG) Log.i(TAG, "Setting ANT State = "+ state +" / "+ AntHalDefine.getAntHalStateString(state)); // Use caching instead of synchronization so that we do not have to hold a lock during a callback. // It is safe to not hold the lock because we are not doing any write accesses. IAntHalCallback callback = mCallback; if (callback != null) { try { if(DEBUG) Log.d(TAG, "Calling status changed callback "+ callback.toString()); callback.antHalStateChanged(state); } catch (RemoteException e) { // Don't do anything as this is a problem in the application if(DEBUG) Log.e(TAG, "ANT HAL State Changed callback failure in application", e); } } else { if(DEBUG) Log.d(TAG, "Calling status changed callback is null"); } } } /** * Requests to change the state * @param state The desired state to change to * @return An {@link AntHalDefine} result */ private int doSetAntState(int state) { synchronized(mChangeAntPowerState_LOCK) { int result = AntHalDefine.ANT_HAL_RESULT_FAIL_INVALID_REQUEST; switch(state) { case AntHalDefine.ANT_HAL_STATE_ENABLED: { result = AntHalDefine.ANT_HAL_RESULT_FAIL_NOT_ENABLED; boolean waitForBluetoothToEnable = false; if (mRequiresBluetoothOn) { // Try to turn on BT if it is not enabled. BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); if (bluetoothAdapter != null) { // run with permissions of ANTHALService long callingPid = Binder.clearCallingIdentity(); if (!bluetoothAdapter.isEnabled()) { waitForBluetoothToEnable = true; mEnablePending = true; boolean isEnabling = bluetoothAdapter.enable(); // if enabling adapter has begun, return // success. if (isEnabling) { result = AntHalDefine.ANT_HAL_RESULT_SUCCESS; // StateChangedReceiver will receive // enabled status and then enable ANT } else { mEnablePending = false; } } Binder.restoreCallingIdentity(callingPid); } } if (!waitForBluetoothToEnable) { result = asyncSetAntPowerState(true); } break; } case AntHalDefine.ANT_HAL_STATE_DISABLED: { result = asyncSetAntPowerState(false); break; } case AntHalDefine.ANT_HAL_STATE_RESET: { result = doHardReset(); break; } } return result; } } /** * Queries the native code for state * @return An {@link AntHalDefine} state */ private int doGetAntState() { if(DEBUG) Log.v(TAG, "doGetAntState start"); int retState = mJAnt.getRadioEnabledStatus(); // ANT state is native state if(DEBUG) Log.i(TAG, "Get ANT State = "+ retState +" / "+ AntHalDefine.getAntHalStateString(retState)); return retState; } /** * Perform a power change if required. * @param state true for enable, false for disable * @return {@link AntHalDefine#ANT_HAL_RESULT_SUCCESS} when the request has * been posted, false otherwise */ private int asyncSetAntPowerState(final boolean state) { int result = AntHalDefine.ANT_HAL_RESULT_FAIL_UNKNOWN; synchronized (mChangeAntPowerState_LOCK) { // Check we are not already in/transitioning to the state we want int currentState = doGetAntState(); if (state) { if ((AntHalDefine.ANT_HAL_STATE_ENABLED == currentState) || (AntHalDefine.ANT_HAL_STATE_ENABLING == currentState)) { if (DEBUG) { Log.d(TAG, "Enable request ignored as already enabled/enabling"); } return AntHalDefine.ANT_HAL_RESULT_SUCCESS; } else if (AntHalDefine.ANT_HAL_STATE_DISABLING == currentState) { Log.w(TAG, "Enable request ignored as already disabling"); return AntHalDefine.ANT_HAL_RESULT_FAIL_UNKNOWN; } } else { if ((AntHalDefine.ANT_HAL_STATE_DISABLED == currentState) || (AntHalDefine.ANT_HAL_STATE_DISABLING == currentState)) { if (DEBUG) { Log.d(TAG, "Disable request ignored as already disabled/disabling"); } return AntHalDefine.ANT_HAL_RESULT_SUCCESS; } else if (AntHalDefine.ANT_HAL_STATE_ENABLING == currentState) { Log.w(TAG, "Disable request ignored as already enabling"); return AntHalDefine.ANT_HAL_RESULT_FAIL_UNKNOWN; } } if (state) { result = enableBackground(); } else { result = disableBackground(); } } return result; } /** * Calls enable on the native libantradio.so * @return {@link AntHalDefine#ANT_HAL_RESULT_SUCCESS} when successful, or * {@link AntHalDefine#ANT_HAL_RESULT_FAIL_UNKNOWN} if unsuccessful */ private int enableBlocking() { int ret = AntHalDefine.ANT_HAL_RESULT_FAIL_UNKNOWN; synchronized(sAntHalServiceDestroy_LOCK) { if (mJAnt != null) { if(JAntStatus.SUCCESS == mJAnt.enable()) { if(DEBUG) Log.v(TAG, "Enable call: Success"); ret = AntHalDefine.ANT_HAL_RESULT_SUCCESS; } else { if(DEBUG) Log.v(TAG, "Enable call: Failure"); } } } return ret; } /** * Calls disable on the native libantradio.so * @return {@link AntHalDefine#ANT_HAL_RESULT_SUCCESS} when successful, or * {@link AntHalDefine#ANT_HAL_RESULT_FAIL_UNKNOWN} if unsuccessful */ private int disableBlocking() { int ret = AntHalDefine.ANT_HAL_RESULT_FAIL_UNKNOWN; synchronized(sAntHalServiceDestroy_LOCK) { if (mJAnt != null) { if(JAntStatus.SUCCESS == mJAnt.disable()) { if(DEBUG) Log.v(TAG, "Disable callback end: Success"); ret = AntHalDefine.ANT_HAL_RESULT_SUCCESS; } else { if (DEBUG) Log.v(TAG, "Disable callback end: Failure"); } } } return ret; } /** * Post an enable runnable. */ private int enableBackground() { if(DEBUG) Log.v(TAG, "Enable start"); if (DEBUG) Log.d(TAG, "Enable: enabling the radio"); // TODO use handler to post runnable rather than creating a new thread. new Thread(new Runnable() { public void run() { enableBlocking(); } }).start(); if(DEBUG) Log.v(TAG, "Enable call end: Successfully called"); return AntHalDefine.ANT_HAL_RESULT_SUCCESS; } /** * Post a disable runnable. */ private int disableBackground() { if(DEBUG) Log.v(TAG, "Disable start"); // TODO use handler to post runnable rather than creating a new thread. new Thread(new Runnable() { public void run() { disableBlocking(); } }).start(); if(DEBUG) Log.v(TAG, "Disable call end: Success"); return AntHalDefine.ANT_HAL_RESULT_SUCCESS; } private int doANTTxMessage(byte[] message) { if(DEBUG) Log.v(TAG, "ANT Tx Message start"); if(message == null) { Log.e(TAG, "ANTTxMessage invalid message: message is null"); return AntHalDefine.ANT_HAL_RESULT_FAIL_INVALID_REQUEST; } int result = AntHalDefine.ANT_HAL_RESULT_FAIL_UNKNOWN; JAntStatus status = mJAnt.ANTTxMessage(message); if(JAntStatus.SUCCESS == status) { if (DEBUG) Log.d (TAG, "mJAnt.ANTTxMessage returned status: " + status.toString()); result = AntHalDefine.ANT_HAL_RESULT_SUCCESS; } else { if (DEBUG) Log.w( TAG, "mJAnt.ANTTxMessage returned status: " + status.toString() ); if(JAntStatus.FAILED_BT_NOT_INITIALIZED == status) { result = AntHalDefine.ANT_HAL_RESULT_FAIL_NOT_ENABLED; } else if(JAntStatus.NOT_SUPPORTED == status) { result = AntHalDefine.ANT_HAL_RESULT_FAIL_NOT_SUPPORTED; } else if(JAntStatus.INVALID_PARM == status) { result = AntHalDefine.ANT_HAL_RESULT_FAIL_INVALID_REQUEST; } } if (DEBUG) Log.v(TAG, "ANTTxMessage: Result = "+ result); if(DEBUG) Log.v(TAG, "ANT Tx Message end"); return result; } private int doRegisterAntHalCallback(IAntHalCallback callback) { if(DEBUG) Log.i(TAG, "Registering callback: "+ callback.toString()); synchronized(mCallback_LOCK) { mCallback = callback; } return AntHalDefine.ANT_HAL_RESULT_SUCCESS; } private int doUnregisterAntHalCallback(IAntHalCallback callback) { if(DEBUG) Log.i(TAG, "UNRegistering callback: "+ callback.toString()); int result = AntHalDefine.ANT_HAL_RESULT_FAIL_UNKNOWN; synchronized (mCallback_LOCK) { if(mCallback.asBinder() == callback.asBinder()) { mCallback = null; result = AntHalDefine.ANT_HAL_RESULT_SUCCESS; } } return result; } private int doGetServiceLibraryVersionCode() { return Version.ANT_HAL_LIBRARY_VERSION_CODE; } private String doGetServiceLibraryVersionName() { return Version.ANT_HAL_LIBRARY_VERSION_NAME; } private int doHardReset() { int ret = AntHalDefine.ANT_HAL_RESULT_FAIL_UNKNOWN; synchronized(sAntHalServiceDestroy_LOCK) { if (mJAnt != null) { if(JAntStatus.SUCCESS == mJAnt.hardReset()) { if(DEBUG) Log.v(TAG, "Hard Reset end: Success"); ret = AntHalDefine.ANT_HAL_RESULT_SUCCESS; } else { if (DEBUG) Log.v(TAG, "Hard Reset end: Failure"); } } } return ret; } // ----------------------------------------------------------------------------------------- IAntHal private final IAntHal.Stub mHalBinder = new IAntHal.Stub() { public int setAntState(int state) { return doSetAntState(state); } public int getAntState() { return doGetAntState(); } public int ANTTxMessage(byte[] message) { return doANTTxMessage(message); } // Call these in onServiceConnected and when unbinding public int registerAntHalCallback(IAntHalCallback callback) { return doRegisterAntHalCallback(callback); } public int unregisterAntHalCallback(IAntHalCallback callback) { return doUnregisterAntHalCallback(callback); } public int getServiceLibraryVersionCode() { return doGetServiceLibraryVersionCode(); } public String getServiceLibraryVersionName() { return doGetServiceLibraryVersionName(); } }; // new IAntHal.Stub() // -------------------------------------------------------------------------------------- Service @Override public void onCreate() { if (DEBUG) Log.d(TAG, "onCreate() entered"); super.onCreate(); if(null != mJAnt) { // This somehow happens when quickly starting/stopping an application. if (DEBUG) Log.e(TAG, "LAST JAnt HCI Interface object not destroyed"); } // create a single new JAnt HCI Interface instance mJAnt = new JAntJava(); mRequiresBluetoothOn = requiresBluetoothOn(); JAntStatus createResult = mJAnt.create(mJAntCallback); if (createResult == JAntStatus.SUCCESS) { mInitialized = true; if (DEBUG) Log.d(TAG, "JAntJava create success"); } else { mInitialized = false; if (DEBUG) Log.e(TAG, "JAntJava create failed: " + createResult); } IntentFilter filter = new IntentFilter(); filter.addAction(ACTION_REQUEST_ENABLE); filter.addAction(ACTION_REQUEST_DISABLE); registerReceiver(mReceiver, filter); if (mRequiresBluetoothOn) { IntentFilter stateChangedFilter = new IntentFilter(); stateChangedFilter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED); registerReceiver(mStateChangedReceiver, stateChangedFilter); } } @Override public void onDestroy() { if (DEBUG) Log.d(TAG, "onDestroy() entered"); try { synchronized(sAntHalServiceDestroy_LOCK) { if(null != mJAnt) { int result = disableBlocking(); if (DEBUG) Log.d(TAG, "onDestroy: disable result is: " + AntHalDefine.getAntHalResultString(result)); mJAnt.destroy(); mJAnt = null; } } synchronized (mCallback_LOCK) { mCallback = null; } } finally { super.onDestroy(); } if (mRequiresBluetoothOn) { unregisterReceiver(mStateChangedReceiver); } unregisterReceiver(mReceiver); } @Override public IBinder onBind(Intent intent) { if (DEBUG) Log.d(TAG, "onBind() entered"); IBinder binder = null; if (mInitialized) { if(intent.getAction().equals(IAntHal.class.getName())) { if (DEBUG) Log.i(TAG, "Bind: IAntHal"); binder = mHalBinder; } } // As someone has started using us, make sure we run "forever" like we // are a system service. startService(this); return binder; } @Override public boolean onUnbind(Intent intent) { if (DEBUG) Log.d(TAG, "onUnbind() entered"); synchronized (mCallback_LOCK) { mCallback = null; } return super.onUnbind(intent); } @Override public int onStartCommand(Intent intent, int flags, int startId) { if (DEBUG) Log.d(TAG, "onStartCommand() entered"); if (!mInitialized) { if (DEBUG) Log.e(TAG, "not initialized, stopping self"); stopSelf(); } return START_NOT_STICKY; } // ----------------------------------------------------------------------------------------- JAnt Callbacks private JAntJava.ICallback mJAntCallback = new JAntJava.ICallback() { public synchronized void ANTRxMessage( byte[] message) { // Use caching instead of synchronization so that we do not have to hold a lock during a callback. // It is safe to not hold the lock because we are not doing any write accesses. IAntHalCallback callback = mCallback; if(null != callback) { try { callback.antHalRxMessage(message); } catch (RemoteException e) { // Don't do anything as this is a problem in the application if(DEBUG) Log.e(TAG, "ANT HAL Rx Message callback failure in application", e); } } else { Log.w(TAG, "JAnt callback called after service has been destroyed"); } } public synchronized void ANTStateChange(int NewState) { if (DEBUG) Log.i(TAG, "ANTStateChange callback to " + NewState); setState(NewState); } }; }