1313import android .bluetooth .BluetoothGatt ;
1414import android .bluetooth .BluetoothGattCharacteristic ;
1515import android .bluetooth .BluetoothGattDescriptor ;
16+ import android .bluetooth .BluetoothManager ;
17+ import android .content .Context ;
1618import android .content .pm .PackageManager ;
1719import android .os .Build ;
1820import appcelerator .ble .peripheral .TiBLEMutableCharacteristicProxy ;
1921import appcelerator .ble .peripheral .TiBLEPeripheralManagerProxy ;
22+ import java .util .ArrayList ;
2023import org .appcelerator .kroll .KrollDict ;
24+ import org .appcelerator .kroll .KrollFunction ;
2125import org .appcelerator .kroll .KrollModule ;
26+ import org .appcelerator .kroll .KrollObject ;
27+ import org .appcelerator .kroll .KrollPromise ;
28+ import org .appcelerator .kroll .KrollRuntime ;
2229import org .appcelerator .kroll .annotations .Kroll ;
2330import org .appcelerator .titanium .TiApplication ;
31+ import org .appcelerator .titanium .TiBaseActivity ;
32+ import org .appcelerator .titanium .TiC ;
2433import ti .modules .titanium .BufferProxy ;
2534
2635@ SuppressLint ("MissingPermission" )
2736@ Kroll .module (name = "AppceleratorBleModule" , id = "appcelerator.ble" )
2837public class AppceleratorBleModule extends KrollModule
2938{
30-
31- private final BluetoothAdapter btAdapter ;
39+ private BluetoothAdapter btAdapter ;
3240 private TiBLECentralManagerProxy centralManagerProxy ;
3341 private TiBLEPeripheralManagerProxy peripheralManagerProxy ;
3442
@@ -134,7 +142,30 @@ public class AppceleratorBleModule extends KrollModule
134142 public AppceleratorBleModule ()
135143 {
136144 super ();
137- btAdapter = BluetoothAdapter .getDefaultAdapter ();
145+
146+ // Fetch bluetooth adapter.
147+ final Context context = TiApplication .getInstance ();
148+ BluetoothManager bluetoothManager = (BluetoothManager ) context .getSystemService (Context .BLUETOOTH_SERVICE );
149+ if (bluetoothManager != null ) {
150+ this .btAdapter = bluetoothManager .getAdapter ();
151+ }
152+
153+ // Release resources when the JS runtime is about to terminate.
154+ KrollRuntime .addOnDisposingListener (new KrollRuntime .OnDisposingListener () {
155+ @ Override
156+ public void onDisposing (KrollRuntime runtime )
157+ {
158+ KrollRuntime .removeOnDisposingListener (this );
159+ if (centralManagerProxy != null ) {
160+ centralManagerProxy .cleanup ();
161+ centralManagerProxy = null ;
162+ }
163+ if (peripheralManagerProxy != null ) {
164+ peripheralManagerProxy .cleanup ();
165+ peripheralManagerProxy = null ;
166+ }
167+ }
168+ });
138169 }
139170
140171 @ Override
@@ -164,45 +195,121 @@ public BufferProxy getDisableNotificationValue()
164195 @ Kroll .method
165196 public boolean isBluetoothAndBluetoothAdminPermissionsGranted ()
166197 {
167- return getActivity ().getPackageManager ().checkPermission (Manifest .permission .BLUETOOTH ,
168- getActivity ().getPackageName ())
169- == PackageManager .PERMISSION_GRANTED
170- && getActivity ().getPackageManager ().checkPermission (Manifest .permission .BLUETOOTH_ADMIN ,
171- getActivity ().getPackageName ())
172- == PackageManager .PERMISSION_GRANTED ;
198+ // Create the permission list.
199+ ArrayList <String > permissionList = new ArrayList <>(3 );
200+ if (Build .VERSION .SDK_INT >= 31 ) {
201+ permissionList .add (Manifest .permission .BLUETOOTH_ADVERTISE );
202+ permissionList .add (Manifest .permission .BLUETOOTH_CONNECT );
203+ permissionList .add (Manifest .permission .BLUETOOTH_SCAN );
204+ } else {
205+ permissionList .add (Manifest .permission .BLUETOOTH );
206+ permissionList .add (Manifest .permission .BLUETOOTH_ADMIN );
207+ }
208+
209+ // Determine if permissions are granted.
210+ // Note: On OS versions older than Android 6.0, check if permission is defined in manifest.
211+ TiApplication context = TiApplication .getInstance ();
212+ PackageManager packageManager = context .getPackageManager ();
213+ String packageName = context .getPackageName ();
214+ for (String permissionName : permissionList ) {
215+ if (Build .VERSION .SDK_INT >= 23 ) {
216+ if (context .checkSelfPermission (permissionName ) != PackageManager .PERMISSION_GRANTED ) {
217+ return false ;
218+ }
219+ } else if (packageManager .checkPermission (permissionName , packageName )
220+ != PackageManager .PERMISSION_GRANTED ) {
221+ return false ;
222+ }
223+ }
224+ return true ;
225+ }
226+
227+ @ Kroll .method
228+ public KrollPromise <KrollDict >
229+ requestBluetoothPermissions (@ Kroll .argument (optional = true ) KrollFunction permissionCallback )
230+ {
231+ final KrollObject krollObject = getKrollObject ();
232+ return KrollPromise .create ((promise ) -> {
233+ // Do not continue if we already have permission.
234+ if (isBluetoothAndBluetoothAdminPermissionsGranted ()) {
235+ KrollDict responseData = new KrollDict ();
236+ responseData .putCodeAndMessage (0 , null );
237+ if (permissionCallback != null ) {
238+ permissionCallback .callAsync (krollObject , responseData );
239+ }
240+ promise .resolve (responseData );
241+ return ;
242+ } else if (Build .VERSION .SDK_INT < 31 ) {
243+ KrollDict responseData = new KrollDict ();
244+ responseData .putCodeAndMessage (-1 , "Bluetooth permissions not defined in manifest." );
245+ if (permissionCallback != null ) {
246+ permissionCallback .callAsync (krollObject , responseData );
247+ }
248+ promise .reject (new Throwable (responseData .getString (TiC .EVENT_PROPERTY_ERROR )));
249+ return ;
250+ }
251+
252+ // Do not continue if there is no activity to host the request dialog.
253+ Activity activity = TiApplication .getInstance ().getCurrentActivity ();
254+ if (activity == null ) {
255+ KrollDict responseData = new KrollDict ();
256+ responseData .putCodeAndMessage (-1 , "There are no activities to host the permission request dialog." );
257+ if (permissionCallback != null ) {
258+ permissionCallback .callAsync (krollObject , responseData );
259+ }
260+ promise .reject (new Throwable (responseData .getString (TiC .EVENT_PROPERTY_ERROR )));
261+ return ;
262+ }
263+
264+ // Show dialog requesting permission.
265+ String [] permissionsArray = { Manifest .permission .BLUETOOTH_ADVERTISE ,
266+ Manifest .permission .BLUETOOTH_CONNECT , Manifest .permission .BLUETOOTH_SCAN };
267+ TiBaseActivity .registerPermissionRequestCallback (TiC .PERMISSION_CODE_LOCATION , permissionCallback ,
268+ krollObject , promise );
269+ activity .requestPermissions (permissionsArray , TiC .PERMISSION_CODE_LOCATION );
270+ });
173271 }
174272
175273 @ Kroll .method
176274 public boolean isSupported ()
177275 {
178- return getActivity ().getPackageManager ().hasSystemFeature (PackageManager .FEATURE_BLUETOOTH_LE );
276+ return TiApplication . getInstance ().getPackageManager ().hasSystemFeature (PackageManager .FEATURE_BLUETOOTH_LE );
179277 }
180278
181279 @ Kroll .method
182280 public boolean isEnabled ()
183281 {
282+ if (btAdapter == null ) {
283+ return false ;
284+ }
184285 return btAdapter .isEnabled ();
185286 }
186287
187288 @ Kroll .method
188289 public boolean enable ()
189290 {
291+ if (btAdapter == null ) {
292+ return false ;
293+ }
190294 return btAdapter .enable ();
191295 }
192296
193297 @ Kroll .method
194298 public boolean disable ()
195299 {
300+ if (btAdapter == null ) {
301+ return false ;
302+ }
196303 return btAdapter .disable ();
197304 }
198305
199306 @ Kroll .method
200307 public boolean isAdvertisingSupported ()
201308 {
202- if (Build . VERSION . SDK_INT >= Build . VERSION_CODES . LOLLIPOP ) {
203- return btAdapter . getBluetoothLeAdvertiser () != null ;
309+ if (btAdapter == null ) {
310+ return false ;
204311 }
205- return false ;
312+ return btAdapter . getBluetoothLeAdvertiser () != null ;
206313 }
207314
208315 @ Kroll .method
@@ -217,18 +324,6 @@ public TiBLEPeripheralManagerProxy initPeripheralManager(@Kroll.argument(optiona
217324 return peripheralManagerProxy = new TiBLEPeripheralManagerProxy ();
218325 }
219326
220- @ Override
221- public void onDestroy (Activity activity )
222- {
223- super .onDestroy (activity );
224- if (centralManagerProxy != null && activity == TiApplication .getInstance ().getRootActivity ()) {
225- centralManagerProxy .cleanup ();
226- }
227- if (peripheralManagerProxy != null && activity == TiApplication .getInstance ().getRootActivity ()) {
228- peripheralManagerProxy .cleanup ();
229- }
230- }
231-
232327 @ Kroll .method
233328 public TiBLEMutableCharacteristicProxy createMutableCharacteristic (KrollDict dict )
234329 {
0 commit comments