Merge pull request #2 from rohan-martin/master

Android_System_Package_2-0-0
diff --git a/.classpath b/.classpath
index 6c635c0..1eef5b5 100644
--- a/.classpath
+++ b/.classpath
@@ -1,7 +1,8 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <classpath>
-	<classpathentry kind="src" path="src"/>
-	<classpathentry kind="src" path="gen"/>
-	<classpathentry kind="con" path="com.android.ide.eclipse.adt.ANDROID_FRAMEWORK"/>
-	<classpathentry kind="output" path="bin/classes"/>
+    <classpathentry kind="src" path="src"/>
+    <classpathentry kind="src" path="gen"/>
+    <classpathentry kind="con" path="com.android.ide.eclipse.adt.ANDROID_FRAMEWORK"/>
+    <classpathentry exported="true" kind="con" path="com.android.ide.eclipse.adt.LIBRARIES"/>
+    <classpathentry kind="output" path="bin/classes"/>
 </classpath>
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 11ac0e2..11dc7f0 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -16,10 +16,15 @@
 -->
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
     package="com.dsi.ant.server"
-    android:versionName="1.3.2"
-    android:versionCode="010302"
+    android:versionName="2.1.0"
+    android:versionCode="020100"
     android:sharedUserId="android.uid.system">
-    
+
+    <uses-sdk
+        android:minSdkVersion="7"
+        android:targetSdkVersion="10"
+        />
+
     <application android:icon="@drawable/icon" 
         android:label="@string/app_name"
         android:process="system"
@@ -66,7 +71,5 @@
         android:description="@string/permdesc_antradio"
         android:label="@string/permlab_antradio"/>
 
-    <uses-sdk android:minSdkVersion="7" />
-
     <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
 </manifest> 
diff --git a/res/values/strings.xml b/res/values/strings.xml
index c07c1c1..a1ad8b8 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -17,7 +17,7 @@
  limitations under the License.
 -->
 <resources>
-    <string name="app_name">ANT HAL Service - DSI BlueZ</string>
+    <string name="app_name">ANT HAL Service</string>
     
 <!-- Permissions -->
     <!-- Permission description for com.dsi.ant.permission.ANTRADIO -->
diff --git a/src/com/dsi/ant/server/AntService.java b/src/com/dsi/ant/server/AntService.java
index 42882cc..5afae5a 100644
--- a/src/com/dsi/ant/server/AntService.java
+++ b/src/com/dsi/ant/server/AntService.java
@@ -18,11 +18,19 @@
 
 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.*;
@@ -44,50 +52,259 @@
 
     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)
     {
-        if(DEBUG) Log.i(TAG, "Setting ANT State = "+ state +" / "+ AntHalDefine.getAntHalStateString(state));
+        synchronized(mChangeAntPowerState_LOCK) {
+            if(DEBUG) Log.i(TAG, "Setting ANT State = "+ state +" / "+ AntHalDefine.getAntHalStateString(state));
 
-        if (mCallback != null)
-        {
-            try
+            if (mCallback != null)
             {
-                if(DEBUG) Log.d(TAG, "Calling status changed callback "+ mCallback.toString());
+                try
+                {
+                    if(DEBUG) Log.d(TAG, "Calling status changed callback "+ mCallback.toString());
 
-                mCallback.antHalStateChanged(state);
+                    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);
+                }
             }
-            catch (RemoteException e)
+            else
             {
-                // 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);
+                if(DEBUG) Log.d(TAG, "Calling status changed callback is null");
             }
         }
-        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 = mJAnt.getRadioEnabledStatus();
+        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;
@@ -97,18 +314,23 @@
             {
                 if(JAntStatus.SUCCESS == mJAnt.enable())
                 {
-                    if(DEBUG) Log.v(TAG, "Enable callback end: Success");
+                    if(DEBUG) Log.v(TAG, "Enable call: Success");
                     ret = AntHalDefine.ANT_HAL_RESULT_SUCCESS;
                 }
                 else
                 {
-                    if(DEBUG) Log.v(TAG, "Enable callback end: Failure");
+                    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;
@@ -130,12 +352,16 @@
         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();
@@ -146,10 +372,14 @@
         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();
@@ -266,28 +496,7 @@
     {
         public int setAntState(int state)
         {
-            int result = AntHalDefine.ANT_HAL_RESULT_FAIL_INVALID_REQUEST;
-
-            switch(state)
-            {
-                case AntHalDefine.ANT_HAL_STATE_ENABLED:
-                {
-                    result = enableBackground();
-                    break;
-                }
-                case AntHalDefine.ANT_HAL_STATE_DISABLED:
-                {
-                    result = disableBackground();
-                    break;
-                }
-                case AntHalDefine.ANT_HAL_STATE_RESET:
-                {
-                    result = doHardReset();
-                    break;
-                }
-            }
-
-            return result;
+            return doSetAntState(state);
         }
 
         public int getAntState()
@@ -331,6 +540,9 @@
 
         super.onCreate();
 
+        mBluetoothServiceAntEnabling = false;
+        mBluetoothServiceAntDisabling = false;
+        
         if(null != mJAnt)
         {
             // This somehow happens when quickly starting/stopping an application.
@@ -342,9 +554,19 @@
 
         if (createResult == JAntStatus.SUCCESS)
         {
-            mInitialized = true;
+            mBluetoothService = (BluetoothService) IBluetooth.Stub.asInterface(ServiceManager.getService(BluetoothAdapter.BLUETOOTH_SERVICE));
 
-            if (DEBUG) Log.d(TAG, "JAntJava create success");
+            if (mBluetoothService == null)
+            {
+                Log.e(TAG, "mBluetoothService == null");
+                mInitialized = false;
+            }
+            else
+            {
+                mInitialized = true;
+
+                if (DEBUG) Log.d(TAG, "JAntJava create success");
+            }
         }
         else
         {
@@ -365,6 +587,10 @@
             {
                 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();
@@ -454,6 +680,17 @@
         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);
         }
     };