private boolean internalConnect(@NonNull final BluetoothDevice device,
									@Nullable final ConnectRequest connectRequest) {
		final boolean bluetoothEnabled = BluetoothAdapter.getDefaultAdapter().isEnabled();
		if (connected || !bluetoothEnabled) {
			final BluetoothDevice currentDevice = bluetoothDevice;
			if (bluetoothEnabled && currentDevice != null && currentDevice.equals(device)) {
				if (this.connectRequest != null) {
					this.connectRequest.notifySuccess(device);
				}
			} else {
				// We can't return false here, as the request would be notified with
				// bluetoothDevice instance instead, and that may be null or a wrong device.
				if (this.connectRequest != null) {
					this.connectRequest.notifyFail(device,
							bluetoothEnabled ?
									FailCallback.REASON_REQUEST_FAILED :
									FailCallback.REASON_BLUETOOTH_DISABLED);
				} // else, the request was already failed by the Bluetooth state receiver
			}
			this.connectRequest = null;
			nextRequest(true);
			return true;
		}

		final Context context = manager.getContext();
		synchronized (LOCK) {
			if (bluetoothGatt != null) {
				// There are 2 ways of reconnecting to the same device:
				// 1. Reusing the same BluetoothGatt object and calling connect() - this will force
				//    the autoConnect flag to true
				// 2. Closing it and reopening a new instance of BluetoothGatt object.
				// The gatt.close() is an asynchronous method. It requires some time before it's
				// finished and device.connectGatt(...) can't be called immediately or service
				// discovery may never finish on some older devices (Nexus 4, Android 5.0.1).
				// If shouldAutoConnect() method returned false we can't call gatt.connect() and
				// have to close gatt and open it again.
				if (!initialConnection) {
					log(Log.DEBUG, () -> "gatt.close()");
					try {
						bluetoothGatt.close();
					} catch (final Throwable t) {
						// ignore
					}
					bluetoothGatt = null;
					try {
						log(Log.DEBUG, () -> "wait(200)");
						Thread.sleep(200); // Is 200 ms enough?
						// If the connection was closed or cancelled during this 200 ms, assume success.
						if (bluetoothGatt != null || (connectRequest != null && connectRequest.finished))
							return true;
					} catch (final InterruptedException e) {
						// Ignore
					}
				} else {
					initialConnection = false;
					connectionTime = 0L; // no timeout possible when autoConnect used
					connectionState = BluetoothGatt.STATE_CONNECTING;
					log(Log.VERBOSE, () -> "Connecting...");
					postCallback(c -> c.onDeviceConnecting(device));
					postConnectionStateChange(o -> o.onDeviceConnecting(device));
					if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
						int preferredPhy = PhyRequest.PHY_LE_1M_MASK;
						if(connectRequest != null) {
							preferredPhy = connectRequest.getPreferredPhy();
						}
						final int finalPreferredPhy = preferredPhy;
						var gatt = bluetoothGatt;
						log(Log.DEBUG, () -> "gatt.close()");
						gatt.close();
						log(Log.DEBUG, () ->
								"gatt = device.connectGatt(autoConnect = true, TRANSPORT_LE, "
										+ ParserUtils.phyMaskToString(finalPreferredPhy) + ")");
						bluetoothGatt = device.connectGatt(context, true, gattCallback,
								BluetoothDevice.TRANSPORT_LE, preferredPhy, handler);
					} else {
						// Instead, the gatt.connect() method will be used to reconnect to the same device.
						// This method forces autoConnect = true (except on Android 14) even if the gatt was
						// created with this flag set to false.
						log(Log.DEBUG, () -> "gatt.connect()");
						bluetoothGatt.connect();
					}
					return true;
				}
			} else {
				if (connectRequest != null) {
					// Register bonding broadcast receiver
					ContextCompat.registerReceiver(context, bluetoothStateBroadcastReceiver,
							new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED), ContextCompat.RECEIVER_EXPORTED);
					ContextCompat.registerReceiver(context, mBondingBroadcastReceiver,
							new IntentFilter(BluetoothDevice.ACTION_BOND_STATE_CHANGED), ContextCompat.RECEIVER_EXPORTED);
				}
			}
		}

		// This should not happen in normal circumstances, but may, when Bluetooth was turned off
		// when retrying to create a connection.
		if (connectRequest == null)
			return false;

		final boolean shouldAutoConnect = connectRequest.shouldAutoConnect();
		final boolean autoConnect;
		if (shouldAutoConnect) {
			// If shouldAutoConnectCreateDirectConnectionFirst() returns true, the first connection
			// will always be done with autoConnect = false to make the connection quick.
			// If the shouldAutoConnect() method returned true, the manager will automatically try
			// to reconnect to this device on link loss.
			initialConnection = connectRequest.shouldAutoConnectCreateDirectConnectionFirst();
			autoConnect = !initialConnection;
		} else {
			autoConnect = false;
		}
		// We will receive Link Loss events only when the device is connected with autoConnect=true.
		userDisconnected = !shouldAutoConnect;

		bluetoothDevice = device;
		if (!autoConnect) {
			log(Log.VERBOSE, () -> connectRequest.isFirstAttempt() ? "Connecting..." : "Retrying...");
			connectionState = BluetoothGatt.STATE_CONNECTING;
			postCallback(c -> c.onDeviceConnecting(device));
			postConnectionStateChange(o -> o.onDeviceConnecting(device));
		}
		connectionTime = SystemClock.elapsedRealtime();
		if (Build.VERSION.SDK_INT > Build.VERSION_CODES.O) {
			// connectRequest will never be null here.
			final int preferredPhy = connectRequest.getPreferredPhy();
			log(Log.DEBUG, () ->
					"gatt = device.connectGatt(autoConnect = " + autoConnect + ", TRANSPORT_LE, "
							+ ParserUtils.phyMaskToString(preferredPhy) + ")");

			bluetoothGatt = device.connectGatt(context, autoConnect, gattCallback,
					BluetoothDevice.TRANSPORT_LE, preferredPhy, handler);
		} else if (Build.VERSION.SDK_INT == Build.VERSION_CODES.O) {
			// connectRequest will never be null here.
			final int preferredPhy = connectRequest.getPreferredPhy();
			log(Log.DEBUG, () ->
					"gatt = device.connectGatt(autoConnect = " + autoConnect + ", TRANSPORT_LE, "
							+ ParserUtils.phyMaskToString(preferredPhy) + ")");
			// A variant of connectGatt with Handled can't be used here.
			// Check https://github.com/NordicSemiconductor/Android-BLE-Library/issues/54
			// This bug specifically occurs in SDK 26 and is fixed in SDK 27
			bluetoothGatt = device.connectGatt(context, autoConnect, gattCallback,
					BluetoothDevice.TRANSPORT_LE, preferredPhy/*, handler*/);
		} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
			log(Log.DEBUG, () -> "gatt = device.connectGatt(autoConnect = " + autoConnect + ", TRANSPORT_LE)");
			bluetoothGatt = device.connectGatt(context, autoConnect, gattCallback,
					BluetoothDevice.TRANSPORT_LE);
		} else {
			log(Log.DEBUG, () -> "gatt = device.connectGatt(autoConnect = " + autoConnect + ")");
			bluetoothGatt = device.connectGatt(context, autoConnect, gattCallback);
		}

		if (autoConnect && this.connectRequest != null) {
			this.connectRequest.notifySuccess(device);
			this.connectRequest = null;
		}
		return true;
	}
Returns the value of the server descriptor. For descriptor that are not shared,
the value may be different for each connected device.

@param serverDescriptor The descriptor to get value of.
@return The value.
ble/src/main/java/no/nordicsemi/android/ble/BleManagerHandler.java