| /* |
| * 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 |
| } |
| |
| /** |
| * 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; |
| } |
| } |
| |
| 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); |
| } |
| }; |
| } |
| |