diff --git a/UnityResources/Singularity/BluetoothUIManager.cs b/UnityResources/Singularity/BluetoothUIManager.cs index 231bf80..d6e8ec6 100644 --- a/UnityResources/Singularity/BluetoothUIManager.cs +++ b/UnityResources/Singularity/BluetoothUIManager.cs @@ -27,6 +27,11 @@ private enum uistate { Disconnected, Connecting, Connected }; // Start is called before the first frame update void Start() { + } + + public void onBluetoothReady() + { + // only setup the UI after the bluetooth is ready updateDeviceOptions(); } diff --git a/UnityResources/Singularity/SingularityManager.cs b/UnityResources/Singularity/SingularityManager.cs index 6a830f8..eab7dac 100644 --- a/UnityResources/Singularity/SingularityManager.cs +++ b/UnityResources/Singularity/SingularityManager.cs @@ -8,6 +8,7 @@ namespace Sngty { public class SingularityManager : MonoBehaviour { + public UnityEvent onBluetoothReady; public UnityEvent onConnected; public UnityEvent onMessageRecieved; public UnityEvent onError; @@ -17,41 +18,67 @@ public class SingularityManager : MonoBehaviour private List connectedDevices; + private HashSet BtPermissionsNeeded = new HashSet { + "android.permission.BLUETOOTH_CONNECT", + "android.permission.BLUETOOTH", + "android.permission.BLUETOOTH_ADMIN", + "android.permission.BLUETOOTH_SCAN" + }; + + + + internal void PermissionCallbacks_PermissionDeniedAndDontAskAgain(string permissionName) + { + Debug.Log($"{permissionName} PermissionDeniedAndDontAskAgain"); + } + + internal void PermissionCallbacks_PermissionGranted(string permissionName) + { + Debug.Log($"{permissionName} PermissionCallbacks_PermissionGranted"); + + BtPermissionsNeeded.Remove(permissionName); + if (BtPermissionsNeeded.Count == 0){ + BluetoothPermissionsGranted(); + } + } + + internal void PermissionCallbacks_PermissionDenied(string permissionName) + { + Debug.Log($"{permissionName} PermissionCallbacks_PermissionDenied"); + } + // Awake is called before any object's Start(). // Set up bluetooth using Awake() so it's ready for other objects. - // Trying to use the singularity manager in other script's Awake() methods may not work properly. + // Trying to use SingularityManager in other script's Awake() methods may not work properly + // unless you set up callbacks correctly. void Awake() { - // Check for necessary bluetooth permissions and request if necessary - // You may need to restart the app on the headset after granting permissions. - // Sometimes, you may need to restart the app twice for the permissions to fully work. - // This is a quick and dirty solution for getting the permissions. - // A better way is to use callbacks to let everything else know when the permissions have been granted. - bool hasBtConnectPermission = Permission.HasUserAuthorizedPermission("android.permission.BLUETOOTH_CONNECT"); - bool hasBtPermission = Permission.HasUserAuthorizedPermission("android.permission.BLUETOOTH"); - bool hasBtAdminPermission = Permission.HasUserAuthorizedPermission("android.permission.BLUETOOTH_ADMIN"); - bool hasBtScanPermission = Permission.HasUserAuthorizedPermission("android.permission.BLUETOOTH_SCAN"); - - List permissionsNeeded = new List(); - if (!hasBtConnectPermission) { - permissionsNeeded.Add("android.permission.BLUETOOTH_CONNECT"); - } - if (!hasBtPermission) { - permissionsNeeded.Add("android.permission.BLUETOOTH"); - } - if (!hasBtAdminPermission) { - permissionsNeeded.Add("android.permission.BLUETOOTH_ADMIN"); - } - if (!hasBtScanPermission) { - permissionsNeeded.Add("android.permission.BLUETOOTH_SCAN"); + // Remove permissions that have already been granted + BtPermissionsNeeded.RemoveWhere(item => Permission.HasUserAuthorizedPermission(item)); + + // Convert what is left in the hashset to a list as preparation to ask for permissions + List permissionsNeeded = new List(BtPermissionsNeeded); + + // If there are no permissions left to ask for, then we can proceed with bluetooth setup + // else, ask for the permissions + if (permissionsNeeded.Count == 0) + { + BluetoothPermissionsGranted(); } - Debug.LogWarning("May need to restart the app. Requesting permissions: " + string.Join(", ", permissionsNeeded)); - Permission.RequestUserPermissions(permissionsNeeded.ToArray()); + else + { + // If you have set up callbacks correctly, you should not need to restart the app. + // Otherwise, you may need to restart the app (up to 2 times) to get bluetooth working. - BluetoothManager = new AndroidJavaClass("com.harrysoft.androidbluetoothserial.BluetoothManager"); - bluetoothManager = BluetoothManager.CallStatic("getInstance"); + Debug.LogWarning("May need to restart the app. Requesting permissions: " + string.Join(", ", permissionsNeeded)); - connectedDevices = new List(); + var callbacks = new PermissionCallbacks(); + callbacks.PermissionDenied += PermissionCallbacks_PermissionDenied; + callbacks.PermissionGranted += PermissionCallbacks_PermissionGranted; + callbacks.PermissionDeniedAndDontAskAgain += PermissionCallbacks_PermissionDeniedAndDontAskAgain; + + Permission.RequestUserPermissions(permissionsNeeded.ToArray(), callbacks); + } } @@ -63,7 +90,16 @@ void Start() // Update is called once per frame void Update() { + } + + private void BluetoothPermissionsGranted() + { + BluetoothManager = new AndroidJavaClass("com.harrysoft.androidbluetoothserial.BluetoothManager"); + bluetoothManager = BluetoothManager.CallStatic("getInstance"); + + connectedDevices = new List(); + onBluetoothReady.Invoke(); } public void ConnectToDevice(DeviceSignature sig) @@ -71,7 +107,7 @@ public void ConnectToDevice(DeviceSignature sig) AndroidJavaClass Schedulers = new AndroidJavaClass("io.reactivex.schedulers.Schedulers"); AndroidJavaClass AndroidSchedulers = new AndroidJavaClass("io.reactivex.android.schedulers.AndroidSchedulers"); bluetoothManager.Call("openSerialDevice", sig.mac) - .Call("subscribeOn",Schedulers.CallStatic("io")) + .Call("subscribeOn", Schedulers.CallStatic("io")) .Call("observeOn", AndroidSchedulers.CallStatic("mainThread")) .Call("subscribe", new RxSingleObserver(onError, onConnected, onMessageRecieved, connectedDevices)); @@ -135,7 +171,7 @@ class RxSingleObserver : AndroidJavaProxy private List connectedDevices; public RxSingleObserver(UnityEvent onErrorEvent, UnityEvent onConnectedEvent, UnityEvent onMessageRecievedEvent, List connectedDevices) : base("io.reactivex.SingleObserver") { - this.onErrorEvent = onErrorEvent; + this.onErrorEvent = onErrorEvent; this.onConnectedEvent = onConnectedEvent; this.onMessageRecievedEvent = onMessageRecievedEvent; this.connectedDevices = connectedDevices;