aboutsummaryrefslogtreecommitdiffstats
path: root/src/com/dsi/ant/server/AntService.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/com/dsi/ant/server/AntService.java')
-rw-r--r--src/com/dsi/ant/server/AntService.java698
1 files changed, 698 insertions, 0 deletions
diff --git a/src/com/dsi/ant/server/AntService.java b/src/com/dsi/ant/server/AntService.java
new file mode 100644
index 0000000..5afae5a
--- /dev/null
+++ b/src/com/dsi/ant/server/AntService.java
@@ -0,0 +1,698 @@
+/*
+ * 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 java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.IBluetooth;
+import android.bluetooth.IBluetoothStateChangeCallback;
+import android.content.Context;
+import android.content.Intent;
+import android.app.Service;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.server.BluetoothService;
+import android.util.Log;
+
+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";
+
+ private JAntJava mJAnt = null;
+
+ private boolean mInitialized = false;
+
+ private Object mChangeAntPowerState_LOCK = new Object();
+ private static Object sAntHalServiceDestroy_LOCK = new Object();
+
+ IAntHalCallback mCallback;
+
+ private BluetoothService mBluetoothService;
+
+ /** We requested an ANT enable in the BT service, and are waiting for it to complete */
+ private boolean mBluetoothServiceAntEnabling;
+ /** We requested an ANT disable in the BT service, and are waiting for it to complete */
+ private boolean mBluetoothServiceAntDisabling;
+
+ 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));
+
+ if (mCallback != null)
+ {
+ try
+ {
+ if(DEBUG) Log.d(TAG, "Calling status changed callback "+ mCallback.toString());
+
+ mCallback.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 = 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;
+ if (mBluetoothServiceAntEnabling)
+ {
+ if(DEBUG) Log.d(TAG, "Bluetooth Service ANT Enabling");
+
+ retState = AntHalDefine.ANT_HAL_STATE_ENABLING;
+ }
+ else if(mBluetoothServiceAntDisabling)
+ {
+ if(DEBUG) Log.d(TAG, "Bluetooth Service ANT Disabling");
+
+ retState = AntHalDefine.ANT_HAL_STATE_DISABLING;
+ }
+ else
+ {
+ retState = mJAnt.getRadioEnabledStatus(); // ANT state is native state
+ }
+
+ if(DEBUG) Log.i(TAG, "Get ANT State = "+ retState +" / "+ AntHalDefine.getAntHalStateString(retState));
+
+ return retState;
+ }
+
+ /**
+ * The callback that the BluetoothAdapterStateMachine will call back on when power is on / off.
+ * When called back, this function will call the native enable() or disable()
+ */
+ private final IBluetoothStateChangeCallback.Stub mBluetoothStateChangeCallback = new IBluetoothStateChangeCallback.Stub()
+ {
+ @Override
+ public void onBluetoothStateChange(boolean on) throws RemoteException
+ {
+ synchronized(mChangeAntPowerState_LOCK) {
+ if (DEBUG) Log.i(TAG, "bluetooth state change callback: " + on);
+
+ if (on) {
+ mBluetoothServiceAntEnabling = false;
+
+ if (enableBlocking() == AntHalDefine.ANT_HAL_RESULT_SUCCESS) {
+ if(DEBUG) Log.v(TAG, "ANT native enable: Success");
+ setState(AntHalDefine.ANT_HAL_STATE_ENABLED);
+ } else {
+ Log.e(TAG, "ANT native enable failed");
+ setState(AntHalDefine.ANT_HAL_STATE_DISABLED);
+ }
+ } else {
+ mBluetoothServiceAntDisabling = false;
+
+ if (disableBlocking() == AntHalDefine.ANT_HAL_RESULT_SUCCESS) {
+ if(DEBUG) Log.v(TAG, "ANT native disable: Success");
+ setState(AntHalDefine.ANT_HAL_STATE_DISABLED);
+ } else {
+ Log.e(TAG, "ANT native disable failed");
+ setState(doGetAntState());
+ }
+ }
+ }
+ }
+ };
+
+ /**
+ * Perform a power change if required.
+ * Tries to use changeAntWirelessState() in {@link BluetoothService}. If it does not exist then
+ * it defaults to a native enable() or disable() call
+ * @param state true for enable, false for disable
+ * @return {@link AntHalDefine#ANT_HAL_RESULT_SUCCESS} when the request is successfully sent,
+ * false otherwise
+ */
+ private int asyncSetAntPowerState(final boolean state)
+ {
+ synchronized(mChangeAntPowerState_LOCK) {
+ // Check we are not already in/transitioning to the state we want
+ int currentState = doGetAntState();
+ boolean doNativePower = false;
+
+ 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 (mBluetoothService != null) {
+ try {
+ Method method_changeAntWirelessState = BluetoothService.class.getMethod(
+ "changeAntWirelessState", boolean.class,
+ IBluetoothStateChangeCallback.class);
+
+ boolean result = (Boolean) method_changeAntWirelessState.invoke(mBluetoothService,
+ state, mBluetoothStateChangeCallback);
+ if (result) {
+ if (state) {
+ if (DEBUG) Log.d(TAG, "enable request successful");
+ mBluetoothServiceAntEnabling = true;
+ setState(AntHalDefine.ANT_HAL_STATE_ENABLING);
+ } else {
+ if (DEBUG) Log.d(TAG, "disable request successful");
+ mBluetoothServiceAntDisabling = true;
+ setState(AntHalDefine.ANT_HAL_STATE_DISABLING);
+ }
+ return AntHalDefine.ANT_HAL_RESULT_SUCCESS;
+ } else {
+ Log.e(TAG, "power " + state + " request failed");
+ return AntHalDefine.ANT_HAL_RESULT_FAIL_UNKNOWN;
+ }
+ } catch (NoSuchMethodException e) {
+ // BluetoothService does not contain the ANT frameworks power function, which means
+ // the native code will do the chip power.
+ doNativePower = true;
+ } catch (IllegalAccessException e) {
+ // This exception should never happen, but if it does it is something we need to fix.
+ // This call is made on a Binder Thread, so rather than crash it and have it silently
+ // fail, we re-throw a supported IPC exception so that the higher level will know
+ // about the error.
+ throw new SecurityException("BluetoothService.changeAntWirelessState() function should be public\n" + e.getMessage());
+ } catch (InvocationTargetException e) {
+ // This exception should never happen, but if it does it is something we need to fix.
+ // This call is made on a Binder Thread, so rather than crash it and have it silently
+ // fail, we re-throw a supported IPC exception so that the higher level will know
+ // about the error.
+ throw new IllegalArgumentException("BluetoothService.changeAntWirelessState() should not throw exceptions\n" + e.getMessage());
+ }
+ }
+ else
+ {
+ Log.w(TAG, "in disable: No BluetoothService");
+ doNativePower = true;
+ }
+
+ if(doNativePower) {
+ if (state) {
+ enableBackground();
+ } else {
+ disableBackground();
+ }
+ return AntHalDefine.ANT_HAL_RESULT_SUCCESS;
+ }
+
+ return AntHalDefine.ANT_HAL_RESULT_FAIL_UNKNOWN;
+ }
+ }
+
+ /**
+ * 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());
+
+ 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;
+
+ 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();
+
+ mBluetoothServiceAntEnabling = false;
+ mBluetoothServiceAntDisabling = false;
+
+ 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();
+ JAntStatus createResult = mJAnt.create(mJAntCallback);
+
+ if (createResult == JAntStatus.SUCCESS)
+ {
+ mBluetoothService = (BluetoothService) IBluetooth.Stub.asInterface(ServiceManager.getService(BluetoothAdapter.BLUETOOTH_SERVICE));
+
+ if (mBluetoothService == null)
+ {
+ Log.e(TAG, "mBluetoothService == null");
+ mInitialized = false;
+ }
+ else
+ {
+ mInitialized = true;
+
+ if (DEBUG) Log.d(TAG, "JAntJava create success");
+ }
+ }
+ else
+ {
+ mInitialized = false;
+
+ if (DEBUG) Log.e(TAG, "JAntJava create failed: " + createResult);
+ }
+ }
+
+ @Override
+ public void onDestroy()
+ {
+ if (DEBUG) Log.d(TAG, "onDestroy() entered");
+
+ try
+ {
+ if(null != mJAnt)
+ {
+ synchronized(sAntHalServiceDestroy_LOCK)
+ {
+ if (asyncSetAntPowerState(false) != AntHalDefine.ANT_HAL_RESULT_SUCCESS)
+ {
+ Log.w(TAG, "onDestroy disable failed");
+ }
+ int result = disableBlocking();
+ if (DEBUG) Log.d(TAG, "onDestroy: disable result is: " + AntHalDefine.getAntHalResultString(result));
+ mJAnt.destroy();
+ mJAnt = null;
+ }
+ }
+
+ mCallback = null;
+ }
+ finally
+ {
+ super.onDestroy();
+ }
+ }
+
+ @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");
+
+ 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)
+ {
+ if(null != mCallback)
+ {
+ try
+ {
+ mCallback.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);
+
+ if(mBluetoothServiceAntEnabling) {
+ Log.w(TAG, "Native state change ignored while waiting for BluetoothService ANT enable");
+ return;
+ }
+
+ if(mBluetoothServiceAntDisabling) {
+ Log.w(TAG, "Native state change ignored while waiting for BluetoothService ANT disable");
+ return;
+ }
+
+ setState(NewState);
+ }
+ };
+}
+