Merge branch 'features/UserSwitching'
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index ebd1541..72dbfa6 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -16,8 +16,8 @@
 -->
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
     package="com.dsi.ant.server"
-    android:versionName="4.0.0"
-    android:versionCode="030200"
+    android:versionName="4.1.0"
+    android:versionCode="040100"
     android:sharedUserId="android.uid.system">
 
     <uses-sdk
@@ -62,4 +62,7 @@
     <uses-permission android:name="android.permission.BLUETOOTH" />
     <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
     <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
+    <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS" />
+    <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" />
+    <uses-permission android:name="android.permission.MANAGE_USERS"/>
 </manifest> 
diff --git a/src/com/dsi/ant/server/AntService.java b/src/com/dsi/ant/server/AntService.java
index 4e6f891..67878c4 100644
--- a/src/com/dsi/ant/server/AntService.java
+++ b/src/com/dsi/ant/server/AntService.java
@@ -23,14 +23,16 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.annotation.SuppressLint;
+import android.annotation.TargetApi;
+import android.app.ActivityManagerNative;
 import android.app.Service;
 import android.os.Binder;
+import android.os.Build;
 import android.os.IBinder;
 import android.os.RemoteException;
-import android.provider.Settings;
+import android.os.UserHandle;
 import android.util.Log;
-import android.os.SystemProperties;
-
 import com.dsi.ant.core.*;
 
 import com.dsi.ant.server.AntHalDefine;
@@ -38,12 +40,24 @@
 import com.dsi.ant.server.IAntHalCallback;
 import com.dsi.ant.server.Version;
 
+import java.util.HashMap;
+
 public class AntService extends Service
 {
     private static final String TAG = "AntHalService";
 
     private static final boolean DEBUG = false;
 
+    private static final boolean HAS_MULTI_USER_API =
+            Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1;
+
+    /**
+     * This flag determines if background users are allowed to use the ANT radio or not. Note that
+     * even if this flag is set, the active foreground user always has priority for using the
+     * ANT radio.
+     */
+    private static final boolean ALLOW_BACKGROUND_USAGE = true;
+
     public static final String ANT_SERVICE = "AntService";
 
     /**
@@ -82,9 +96,23 @@
 
     /** 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();
+    /**
+     * Used for synchronizing changes to {@link #mCallback}, {@link #mCallbackMap}, and
+     * {@link #mCurrentUser}. Does not need to be used where a one-time read of the
+     * {@link #mCallback} value is being done, however ALL WRITE ACCESSES must use this lock.
+     */
+    private final Object mUserCallback_LOCK = new Object();
+
+    /**
+     * The user handle associated with the current active user of the ANT HAL service.
+     */
+    private volatile UserHandle mCurrentUser;
+
+    /**
+     * Map containing the callback set for each current user.
+     */
+    private final HashMap<UserHandle, IAntHalCallback> mCallbackMap =
+            new HashMap<UserHandle, IAntHalCallback>();
 
     /**
      * Receives Bluetooth State Changed intent and sends {@link ACTION_REQUEST_ENABLE}
@@ -95,12 +123,14 @@
     /**
      * Receives {@link ACTION_REQUEST_ENABLE} and {@link ACTION_REQUEST_DISABLE}
      * intents to enable and disable ANT.
+     * Also receives {@link Intent#ACTION_USER_SWITCHED} when we are not allowing background users
+     * in order to clear the current user at the appropriate time.
      */
     private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
         @Override
         public void onReceive(Context context, Intent intent) {
+            String action = intent.getAction();
             if (mRequiresBluetoothOn) {
-                String action = intent.getAction();
                 if (ACTION_REQUEST_ENABLE.equals(action)) {
                     if (mEnablePending) {
                         asyncSetAntPowerState(true);
@@ -114,6 +144,14 @@
                     }
                 }
             }
+            if(!ALLOW_BACKGROUND_USAGE)
+            {
+                if(HAS_MULTI_USER_API &&
+                        Intent.ACTION_USER_SWITCHED.equals(action))
+                {
+                    clearCurrentUser();
+                }
+            }
         }
     };
 
@@ -164,10 +202,112 @@
     }
 
     /**
+     * Clear the current user, telling the associated ARS instance that the chip is disabled.
+     */
+    private void clearCurrentUser()
+    {
+        if (DEBUG) Log.i(TAG, "Clearing active user");
+        synchronized (mUserCallback_LOCK)
+        {
+            setState(AntHalDefine.ANT_HAL_STATE_DISABLED);
+            mCurrentUser = null;
+            mCallback = null;
+            doSetAntState(AntHalDefine.ANT_HAL_STATE_DISABLED);
+        }
+    }
+
+    /**
+     * Attempt to change the current user to the calling user.
+     * @return True if the calling user is now the current user (even if they were before the call
+     *         was made), False otherwise.
+     */
+    @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
+    private boolean trySwitchToCallingUser()
+    {
+        // Lock held here to avoid ordering issues if it is needed within the function.
+        synchronized (mChangeAntPowerState_LOCK)
+        {
+            synchronized (mUserCallback_LOCK)
+            {
+                UserHandle callingUser = Binder.getCallingUserHandle();
+                if(DEBUG) Log.d(TAG, "Trying to make user: " + callingUser + " the current user.");
+                boolean isActiveUser = false;
+                boolean shouldSwitch = false;
+                long id = 0;
+
+                // Always allow if they already are the current user.
+                if(callingUser.equals(mCurrentUser))
+                {
+                    shouldSwitch = true;
+                }
+
+                try
+                {
+                    // Check foreground user using ANT HAL Service permissions.
+                    id = Binder.clearCallingIdentity();
+                    UserHandle activeUser =
+                            ActivityManagerNative.getDefault().getCurrentUser().getUserHandle();
+                    isActiveUser = activeUser.equals(callingUser);
+                } catch (RemoteException e)
+                {
+                    if(DEBUG) Log.w(TAG, "Could not determine the foreground user.");
+                    // don't know who the current user is, assume they are not the active user and
+                    // continue.
+                } finally
+                {
+                    // always restore our identity.
+                    Binder.restoreCallingIdentity(id);
+                }
+
+                if(isActiveUser)
+                {
+                    // Always allow the active user to become the current user.
+                    shouldSwitch = true;
+                }
+
+                if(ALLOW_BACKGROUND_USAGE)
+                {
+                    // Allow anyone to become the current user if there is no current user.
+                    if(mCurrentUser == null)
+                    {
+                        shouldSwitch = true;
+                    }
+                }
+
+                if(shouldSwitch)
+                {
+                    // Only actually do the switch if the users are different.
+                    if(!callingUser.equals(mCurrentUser))
+                    {
+                        if (DEBUG) Log.i(TAG, "Making " + callingUser + " the current user.");
+                        // Need to send state updates as the current user switches.
+                        // The mChangeAntPowerState_LOCK needs to be held across these calls to
+                        // prevent state updates during the user switch. It is held for this entire
+                        // function to prevent lock ordering issues.
+                        setState(AntHalDefine.ANT_HAL_STATE_DISABLED);
+                        mCurrentUser = callingUser;
+                        mCallback = mCallbackMap.get(callingUser);
+                        setState(doGetAntState(true));
+                    } else
+                    {
+                        if (DEBUG) Log.d(TAG, callingUser + " is already the current user.");
+                    }
+                } else
+                {
+                    if (DEBUG) Log.d(TAG, callingUser + " is not allowed to become the current user.");
+                }
+
+                return shouldSwitch;
+            }
+        }
+    }
+
+    /**
      * Requests to change the state
      * @param state The desired state to change to
      * @return An {@link AntHalDefine} result
      */
+    @SuppressLint("NewApi")
     private int doSetAntState(int state)
     {
         synchronized(mChangeAntPowerState_LOCK) {
@@ -179,6 +319,17 @@
                 {
                     result = AntHalDefine.ANT_HAL_RESULT_FAIL_NOT_ENABLED;
 
+                    // On platforms with multiple users the enable call is where we try to switch
+                    // the current user.
+                    if(HAS_MULTI_USER_API)
+                    {
+                        if(!trySwitchToCallingUser())
+                        {
+                            // If we cannot become the current user, fail the enable call.
+                            break;
+                        }
+                    }
+
                     boolean waitForBluetoothToEnable = false;
 
                     if (mRequiresBluetoothOn) {
@@ -220,7 +371,28 @@
                 }
                 case AntHalDefine.ANT_HAL_STATE_DISABLED:
                 {
-                    result = asyncSetAntPowerState(false);
+                    if(HAS_MULTI_USER_API)
+                    {
+                        UserHandle user = Binder.getCallingUserHandle();
+                        if(!user.equals(mCurrentUser))
+                        {
+                            // All disables succeed for non current users.
+                            result = AntHalDefine.ANT_HAL_RESULT_SUCCESS;
+                            break;
+                        }
+
+                        result = asyncSetAntPowerState(false);
+
+                        if(result == AntHalDefine.ANT_HAL_RESULT_SUCCESS &&
+                                user.equals(mCurrentUser))
+                        {
+                            // To match setting the current user in enable.
+                            clearCurrentUser();
+                        }
+                    } else
+                    {
+                        result = asyncSetAntPowerState(false);
+                    }
                     break;
                 }
                 case AntHalDefine.ANT_HAL_STATE_RESET:
@@ -238,11 +410,18 @@
      * Queries the native code for state
      * @return An {@link AntHalDefine} state
      */
-    private int doGetAntState()
+    @SuppressLint("NewApi")
+    private int doGetAntState(boolean internalCall)
     {
         if(DEBUG) Log.v(TAG, "doGetAntState start");
 
-        int retState = mJAnt.getRadioEnabledStatus(); // ANT state is native state
+        int retState = AntHalDefine.ANT_HAL_STATE_DISABLED;
+        if(!HAS_MULTI_USER_API || // If there is no multi-user api we don't have to fake a disabled state.
+                internalCall ||
+                Binder.getCallingUserHandle().equals(mCurrentUser))
+        {
+            retState = mJAnt.getRadioEnabledStatus(); // ANT state is native state
+        }
 
         if(DEBUG) Log.i(TAG, "Get ANT State = "+ retState +" / "+ AntHalDefine.getAntHalStateString(retState));
 
@@ -261,7 +440,7 @@
 
         synchronized (mChangeAntPowerState_LOCK) {
             // Check we are not already in/transitioning to the state we want
-            int currentState = doGetAntState();
+            int currentState = doGetAntState(true);
 
             if (state) {
                 if ((AntHalDefine.ANT_HAL_STATE_ENABLED == currentState)
@@ -436,33 +615,69 @@
         return result;
     }
 
+    @SuppressLint("NewApi")
     private int doRegisterAntHalCallback(IAntHalCallback callback)
     {
-        if(DEBUG) Log.i(TAG, "Registering callback: "+ callback.toString());
-
-        synchronized(mCallback_LOCK)
+        synchronized (mUserCallback_LOCK)
         {
-            mCallback = callback;
+            if(HAS_MULTI_USER_API)
+            {
+                UserHandle user = Binder.getCallingUserHandle();
+                if(DEBUG) Log.i(TAG, "Registering callback: "+ callback + " for user: " + user);
+                mCallbackMap.put(user, callback);
+                if(user.equals(mCurrentUser))
+                {
+                    mCallback = callback;
+                }
+            } else
+            {
+                if(DEBUG) Log.i(TAG, "Registering callback: "+ callback);
+                mCallback = callback;
+            }
         }
 
         return AntHalDefine.ANT_HAL_RESULT_SUCCESS;
     }
 
+    @SuppressLint("NewApi")
     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(HAS_MULTI_USER_API)
         {
-            if(mCallback.asBinder() == callback.asBinder())
+            UserHandle user = Binder.getCallingUserHandle();
+            if(DEBUG) Log.i(TAG, "Unregistering callback: "+ callback.toString() + " for user: " +
+                    user);
+            synchronized(mUserCallback_LOCK)
             {
-                mCallback = null;
-                result = AntHalDefine.ANT_HAL_RESULT_SUCCESS;
+                IAntHalCallback currentCallback = mCallbackMap.get(user);
+                if(callback != null && currentCallback != null &&
+                        callback.asBinder().equals(currentCallback.asBinder()))
+                {
+                    mCallbackMap.remove(user);
+                    result = AntHalDefine.ANT_HAL_RESULT_SUCCESS;
+                }
+                // Regardless of state, if the current user is leaving we need to allow others to
+                // take over.
+                if(user.equals(mCurrentUser))
+                {
+                    clearCurrentUser();
+                }
+            }
+        } else
+        {
+            if(DEBUG) Log.i(TAG, "Unregistering callback: "+ callback.toString());
+            synchronized(mUserCallback_LOCK)
+            {
+                if(callback != null && mCallback != null &&
+                        callback.asBinder().equals(mCallback.asBinder()))
+                {
+                    mCallback = null;
+                    result = AntHalDefine.ANT_HAL_RESULT_SUCCESS;
+                }
             }
         }
-
         return result;
     }
 
@@ -508,7 +723,7 @@
 
         public int getAntState()
         {
-            return doGetAntState();
+            return doGetAntState(false);
         }
 
         public int ANTTxMessage(byte[] message)
@@ -573,6 +788,15 @@
         IntentFilter filter = new IntentFilter();
         filter.addAction(ACTION_REQUEST_ENABLE);
         filter.addAction(ACTION_REQUEST_DISABLE);
+        if(HAS_MULTI_USER_API)
+        {
+            if(!ALLOW_BACKGROUND_USAGE)
+            {
+                // If we don't allow background users, we need to monitor user switches to clear the
+                // active user.
+                filter.addAction(Intent.ACTION_USER_SWITCHED);
+            }
+        }
         registerReceiver(mReceiver, filter);
 
         if (mRequiresBluetoothOn) {
@@ -601,8 +825,9 @@
                 }
             }
 
-            synchronized (mCallback_LOCK)
+            synchronized(mUserCallback_LOCK)
             {
+                mCallbackMap.clear();
                 mCallback = null;
             }
         }
@@ -647,9 +872,10 @@
     {
         if (DEBUG) Log.d(TAG, "onUnbind() entered");
 
-        synchronized (mCallback_LOCK)
+        synchronized(mUserCallback_LOCK)
         {
             mCallback = null;
+            mCallbackMap.clear();
         }
 
         return super.onUnbind(intent);