Autobahn Android : How to disconnect to the server - java

I'm using Jetty WebSockets for the server side and Autobahn Android for the client one.
A simple connection between server and client works fine. But I'm having some trouble when I try to handle the loss of connection.
On Android, what I have is this:
private void connectToServer() {
try {
mConnection.connect(getString(R.string.server_addr), new WebSocketHandler(){
// connection au serveur
#Override
public void onOpen(){
Log.d(TAG, "Connected");
Log.d(TAG, "First connexion, sending MAC #");
Log.d(TAG, "My MAC Addr: "+ macAddr);
mConnection.sendTextMessage(macAddr);
}
// reception d'un message text
#Override
public void onTextMessage(String payload) {
//TODO
}
// fermeture de la connexion
#Override
public void onClose(int code, String reason) {
Log.d(TAG, "Connection lost. "+reason);
if(mConnection.isConnected()){
Log.d(TAG, "Still connected, disconnect!");
mConnection.disconnect();
}
if(code<4000){
int totalWaitTime = 0;
int waitTime = 0;
Log.d(TAG, "Should be disconnected");
while(!mConnection.isConnected()){
try {
waitTime= random.nextInt(MAX_TO_WAIT - MIN_TO_WAIT + 1) + MIN_TO_WAIT;
Log.d(TAG, "I'll wait "+waitTime+"ms");
totalWaitTime +=waitTime;
Log.d(TAG, "Waiting for "+totalWaitTime+"ms");
if(totalWaitTime <= HOUR_TO_MS){
Thread.sleep(waitTime);
Log.d(TAG, "Trying to reconnect");
connectToServer();
}else{
throw new InterruptedException("Attempt to connect to the server during 1 hours without success");
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
});
} catch (WebSocketException e) {
Log.e(TAG, "Error on connect: "+e.toString());
Log.d(TAG, "is connected: "+mConnection.isConnected());
if(mConnection.isConnected())
mConnection.disconnect();
connectToServer();
}
}
And I always, always have the same error:
06-26 10:36:07.823: E/wingo.stb.qos.AutoStartService(1842): Error on connect: de.tavendo.autobahn.WebSocketException: already connected
But like you can see, I close the connection in onClose(), and when a WebSocketException is catched. Does this method really works ? Or am I doing it wrong ?
By the way, mConnection is final. So maybe the problem comes here ?
private final WebSocketConnection mConnection = new WebSocketConnection();
On the server, when I have a connection loss I manually close the session:
#OnWebSocketClose
public void onClose(Session session, int closeCode, String closeReason){
try {
System.out.println("connexion closed. Reason: "+closeReason);
pingPongTimer.cancel();
if(session.isOpen())
session.close();
WebSocketsCentralisation.getInstance().leave(this);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
Thanks in advance awesome people!

So, what I dit was a bit messy. What I have now works:
private void connectToServer() {
try {
mConnection.connect(getString(R.string.server_addr), new WebSocketHandler(){
// connection au serveur
#Override
public void onOpen(){
Log.d(TAG, "Connected");
Log.d(TAG, "First connexion, sending MAC #");
Log.d(TAG, "My MAC Addr: "+ macAddr);
mConnection.sendTextMessage(macAddr);
}
// reception d'un message text
#Override
public void onTextMessage(String payload) {
//TODO
}
// fermeture de la connexion
#Override
public void onClose(int code, String reason) {
Log.d(TAG, "Connection lost. "+reason+" error code : "+code);
if(code<4000){
reconnectToServer();
}
}
});
} catch (WebSocketException e) {
Log.e(TAG, "Error on connect: "+e.toString());
Log.d(TAG, "is connected: "+mConnection.isConnected());
}
}
private void reconnectToServer() {
try {
if(goConnect){
goConnect = false;
Thread.sleep(1000);
Log.d(TAG, "DISCONNECT:");
// mConnection.disconnect();
Log.d(TAG, "ReconnectTimer Launched");
new ReconnectTask().run();
}
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
private class ReconnectTask extends TimerTask{
#Override
public void run() {
try{
if(totalWaitTime<HOUR_TO_MS){
if(!mConnection.isConnected()){
int waitTime= random.nextInt(MAX_TO_WAIT - MIN_TO_WAIT + 1) + MIN_TO_WAIT;
Log.d(TAG, "Next tentative to connect in "+waitTime+" ms");
totalWaitTime +=waitTime;
reconnectTimer.schedule(new ReconnectTask(), waitTime);
connectToServer();
}else{
Log.d(TAG, "Connected to the server again");
reinitializeReconnection();
}
}else throw new InterruptedException("Attempt to connect to the server during 1 hours without success");
}catch(InterruptedException e){
Log.d(TAG, e.getMessage());
}
}
}
private void reinitializeReconnection(){
reconnectTimer.purge();
goConnect = true;
totalWaitTime = 0;
}
Basically, I tried to get everything out of the WebSocketHandler. And what I didn't understand is that if you try to connect to the server in "onClose" and the server is down, it will go on "onClose" again. So I was doing recursive calls and it was really messy.

Related

Application are not entering "Public void run()" section

I've been create an app for Bluetooth control.
Everything is fine until I open BluetoothServerSocket to listen to an incoming connection.
here is my code:
public class ListeningThread extends Thread {
private final BluetoothServerSocket bluetoothServerSocket;
public ListeningThread() {
BluetoothServerSocket temp = null;
try {
temp = myBluetoothAdapter.listenUsingRfcommWithServiceRecord(getString(R.string.app_name), uuid);
Toast.makeText(getApplicationContext(), "Listening",
Toast.LENGTH_SHORT).show();
} catch (IOException e) {
e.printStackTrace();
}
bluetoothServerSocket = temp;
}
public void run() {
BluetoothSocket bluetoothSocket;
// This will block while listening until a BluetoothSocket is returned
// or an exception occurs
while (true) {
try {
bluetoothSocket = bluetoothServerSocket.accept();
Toast.makeText(getApplicationContext(), "Alert", <-------//the code is not run through here, there is no toast coming out
Toast.LENGTH_SHORT).show();
} catch (IOException e) {
break;
}
// If a connection is accepted
if (bluetoothSocket != null) {
runOnUiThread(new Runnable() {
public void run() {
Toast.makeText(getApplicationContext(), "A connection has been accepted.",
Toast.LENGTH_SHORT).show();
}
});
// Manage the connection in a separate thread
try {
bluetoothServerSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
break;
}
}
}
// Cancel the listening socket and terminate the thread
public void cancel() {
try {
bluetoothServerSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
there is no error shown but when i triggered the ListeningThread() function, this line of code is running Toast.makeText(getApplicationContext(), "Listening" it is shown that my server is listening to incoming connection
but when I'm using other client to connect to this server, then client are showing "paired" but on the server there is nothing shown, but the connection was actually successfull.
from the line
//This will block while listening until a BluetoothSocket is returned//or an exception occurs and //If a connection is acceptedis not running, because the toast did not coming out.
this function is called from MainActivity Threads and was putted on a button. I don't think pasting all the code here will be good, but here is the button codes. if there is anything you need to see, simply ask me.
serverBtn.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
ListeningThread t = new ListeningThread();
t.start();
}
});

Use a service to send/receive data through a socket connection.

I have created a client-server application that the two parties communicate using a socket connection and input/output streams to send/receive data. It all works great but only if I have just one activity that uses the connection. Since I will be having more than just one activities in my application, and after some searching around, I've concluded that I have to create a Service which will handle the socket connection and sending/receiving from the socket streams. I've never worked with Services before and I thought I'd start one step at a time. At the moment I've just created a Service that creates an instance of my Client class(which extends Thread) and starts that thread, which establishes the connection to the server.
My Client class
T_Client:
public class T_Client extends Thread {
private static final String TAG = "T_Client";
private static String serverIP = "192.168.2.3";
private static int port = 4444;
private InetAddress serverAddr = null;
private Socket sock = null;
private boolean running = false;
private ObjectInputStream in;
private ObjectOutputStream out;
private Object objIn;
public void send(MessageCustom _msg) {
if (out != null) {
try {
out.writeObject(_msg);
out.flush();
Log.i("Send Method", "Outgoing : " + _msg.toString());
} catch (IOException ex) {
Log.e("Send Method", ex.toString());
}
}
}
public void stopClient() {
Log.v(TAG,"stopClient method run");
running = false;
}
#Override
public void run() {
running = true;
try {
serverAddr = InetAddress.getByName(serverIP);
Log.i("TCP Client", "C: Connecting...");
sock = new Socket(serverAddr, port);
try {
out = new ObjectOutputStream(sock.getOutputStream());
in = new ObjectInputStream(sock.getInputStream());
Log.i(TAG, "C: Connected.");
while (running) {
objIn = in.readObject();
Log.i("Object Read Class", objIn.getClass().toString());
Log.i("Object Read", objIn.toString());
}
Log.e("RESPONSE FROM SERVER", "S: Received Message: '" + objIn + "'");
} catch (Exception e) {
Log.e(TAG, "S: Error", e);
} finally {
out.close();
in.close();
sock.close();
Log.i(TAG, "Closing socket: " + sock);
}
} catch (Exception e) {
Log.e(TAG, "C: Error", e);
}
}
}
My Service Class
MyService:
public class MyService extends Service {
private static final String TAG = "MyService";
private T_Client client;
#Override
public void onDestroy() {
Log.v(TAG, "onDestroy");
if (client != null) {
try {
client.stopClient();
} catch (Exception e) {
Log.e(TAG, "Error on close: " + e);
}
}
super.onDestroy();
Toast.makeText(this, "Service stopped", Toast.LENGTH_LONG).show();
}
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.v(TAG, "onStartCommand");
client = new T_Client();
client.start();
Toast.makeText(this, "Service started", Toast.LENGTH_LONG).show();
return START_STICKY;
}
#Override
public IBinder onBind(Intent intent) {
// TODO Auto-generated method stub
return null;
}
}
All I do in my main activity at the moment is use a start and a stop button to start/stop the service
public void start(View v){
Log.i(TAG,"Start pressed");
Intent intent=new Intent(getBaseContext(),MyService.class);
startService(intent);
}
public void stop(View v){
Log.i(TAG,"Stop pressed");
Intent intent=new Intent(getBaseContext(),MyService.class);
stopService(intent);
}
So my questions are:
What do I have to do to send something using my Service?
How do I handle the objects that I read from the server so I can let the application know that something has been received, in real time?
Any input that could guide me towards resolving my issue is greatly appreciated.
Thank you in advance.
In case anyone else comes across this question, that has a similar problem, I managed to solve my problem by playing around with code from these two posts
Example: Communication between Activity and Service using
Messaging
How to keep the android client connected to the
server even on activity changes and send data to server?

Maintaining a TCP Connection in an AsyncTask

I'm using an AsyncTask to establish a TCP Connection and sending/receiving data through it.
My current Code looks like this at the moment:
public class NetworkTask extends AsyncTask<Void, byte[], Boolean> {
Socket nsocket; //Network Socket
InputStream nis; //Network Input Stream
OutputStream nos; //Network Output Stream
boolean bSocketStarted = false;
byte[] buffer = new byte[4096];
#Override
protected void onPreExecute() {
Log.i(TAG, "onPreExecute");
}
#Override
protected Boolean doInBackground(Void... params) { //This runs on a different thread
boolean result = false;
try {
// Connect to address
Log.i(TAG, "doInBackground: Creating socket");
SocketAddress sockaddr = new InetSocketAddress("google.de", 80);
nsocket = new Socket();
nsocket.connect(sockaddr, 5000); //10 second connection timeout
if (nsocket.isConnected()) {
bSocketStarted = true;
nis = nsocket.getInputStream();
nos = nsocket.getOutputStream();
Log.i("AsyncTask", "doInBackground: Socket created, streams assigned");
Log.i("AsyncTask", "doInBackground: Waiting for inital data...");
int read = nis.read(buffer, 0, 4096); //This is blocking
while(bSocketStarted) {
if (read > 0){
byte[] tempdata = new byte[read];
System.arraycopy(buffer, 0, tempdata, 0, read);
publishProgress(tempdata);
Log.i(TAG, "doInBackground: Got some data");
}
}
}
} catch (IOException e) {
e.printStackTrace();
Log.i(TAG, "doInBackground: IOException");
result = true;
} catch (Exception e) {
e.printStackTrace();
Log.i(TAG, "doInBackground: Exception");
result = true;
} finally {
try {
nis.close();
nos.close();
nsocket.close();
} catch (IOException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
Log.i(TAG, "doInBackground: Finished");
}
return result;
}
public boolean SendDataToNetwork(final byte[] cmd) { //You run this from the main thread.
// Wait until socket is open and ready to use
waitForSocketToConnect();
if (nsocket.isConnected()) {
Log.i(TAG, "SendDataToNetwork: Writing received message to socket");
new Thread(new Runnable() {
public void run() {
try {
nos.write(cmd);
}
catch (Exception e) {
e.printStackTrace();
Log.i(TAG, "SendDataToNetwork: Message send failed. Caught an exception");
}
}
}
).start();
return true;
}
else
Log.i(TAG, "SendDataToNetwork: Cannot send message. Socket is closed");
return false;
}
public boolean waitForSocketToConnect() {
// immediately return if socket is already open
if (bSocketStarted)
return true;
// Wait until socket is open and ready to use
int count = 0;
while (!bSocketStarted && count < 10000) {
try {
Thread.sleep(500);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
count += 500;
}
return bSocketStarted;
}
#Override
protected void onProgressUpdate(byte[]... values) {
try {
if (values.length > 0) {
Log.i(TAG, "onProgressUpdate: " + values[0].length + " bytes received.");
String str = new String(buffer, "UTF8");
Log.i(TAG,str);
tv.setText(str);
tv.setMovementMethod(new ScrollingMovementMethod());
}
} catch (Exception e) {
e.printStackTrace();
}
finally {}
}
#Override
protected void onCancelled() {
Log.i(TAG, "Cancelled.");
}
#Override
protected void onPostExecute(Boolean result) {
if (result) {
Log.i(TAG, "onPostExecute: Completed with an Error.");
} else {
Log.i(TAG, "onPostExecute: Completed.");
}
}
}
I can instantiate the Task and call SendDataToNetwork from my activity. However, all the text I pass to SendDataToNetwork, for example, 'GET / HTTP/1.1' is continously sent to the server.
How can I modify my Code to maintain the connection in doInBackground and do nothing until I call SendDataToNetwork and after sending bytes to the server just wait until new data is ready to be sent? Basically I want to run the AsyncTask until I explicitly cancel (= close the connection) it.
nsocket.connect(sockaddr, 5000); //10 second connection timeout
if (nsocket.isConnected()) {
The test is pointless. If the socket wasn't connected, connect() would have thrown an exception.
Your read loop is also fundamentally flawed, in that it doesn't keep reading. There are standard solutions as to how to read a stream, e.g.:
while ((count = in.read(buffer)) > 0)
{
out.write(buffer, 0, count);
}
Your waitForSocketToConnect() method doesn't really do anything useful either.
You need to rethink all this.

ObjectOutputStream method writeObject hangs on android

I write some client-server communication.
My server:
public class Server {
public synchronized static void sendPacket(Packet packet,
ObjectOutputStream server) {
try {
server.writeObject(packet);
server.flush();
} catch (IOException e) {
Log.d(TAG, "Error while sending a packet. Output stream is unaviable.");
}
}
public synchronized static Packet readPacket(ObjectInputStream sourceStream) {
Packet recivedPacket = null;
try {
recivedPacket = (Packet) sourceStream.readObject();
} catch (StreamCorruptedException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return recivedPacket;
}
/** Register user on the server */
private User registerUser(Socket socket) {
ClientUserLoginPacket newUserPacket = null;
ObjectInputStream ois = null;
ObjectOutputStream oos = null;
try {
Log.i(TAG, "Opening output stream...");
oos = new ObjectOutputStream(socket.getOutputStream());
if (oos != null)
Log.d(TAG, "Output stream opened");
Log.i(TAG, "Opening input stream...");
ois = new ObjectInputStream(socket.getInputStream());
if (ois != null)
Log.d(TAG, "Input stream opened");
} catch (StreamCorruptedException e1) {
Log.e(TAG, "Error while opening stream");
} catch (IOException e1) {
e1.printStackTrace();
}
// First packet MUST be register request
try {
Log.d(TAG, "Waiting for login packet from client...");
newUserPacket = (ClientUserLoginPacket) readPacket(ois);
Log.d(TAG, "Login packet from recived...");
} catch (Exception e) {
Log.e(TAG, "Can't recive login packet.");
}
User newUserInstance = null;
// TODO check if exists. or to map in the future
if (newUserPacket != null) {
newUserInstance = new User(socket, ois, oos, newUserPacket.nick);
users.add(newUserInstance);
Log.d(TAG, "User " + newUserPacket.nick + " registered.");
Server.sendPacket(new ServerLoginAcceptedPacket(), oos);
Log.d(TAG, "User accept confirmation sent.");
}
return newUserInstance;
}
#Override
public void run() {
Log.i(TAG, "Starting server...");
ServerSocket server;
try {
server = new ServerSocket(PORT);
Log.i(TAG, "Server started.");
server.setSoTimeout(0);
while (true) {
Log.i(TAG, "Waiting for players...");
final Socket socket = server.accept();
Log.i(TAG, "New player connected.");
new Thread(new Runnable() {
#Override
public void run() {
Log.i(TAG, "Try to register new player.");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
User user = registerUser(socket);
while (true) {
Log.i(TAG, "Waiting for packets from " + user.nick+"...");
Packet packet = readPacket(user.ois);
Log.i(TAG, "Packet from " + user.nick + " recived.");
if (packet instanceof ...) {
...
}
}
}
}).start();
}
} catch (IOException e) {
Log.i(TAG, "Port is busy.");
}
}
private class User {
public Socket connection;
public ObjectInputStream ois;
public ObjectOutputStream oos;
public String nick;
public boolean inGame;
public User(Socket socket, ObjectInputStream ois,
ObjectOutputStream oos, String nick) {
this.connection = socket;
this.ois = ois;
this.oos = oos;
this.nick = nick;
}
// ...
}
My client:
public class Client {
callbackHandler = new Thread(new Runnable() {
#Override
public void run() {
while (true) {
Log.e(TAG, "Waiting for incomeing packets...");
Packet packet = (Packet) Server.readPacket(serverInput);
Log.e(TAG, "Packet recived.");
if (packet instanceof ServerLoginAcceptedPacket) {
Log.e(TAG, "Recived packet is "
+ packet.getClass().toString());
Intent intent = new Intent(MyActivity.this,
MainMenuActivity.class);
MyActivity.this.startActivity(intent);
}
}
}
});
public void connectToServer() {
SocketAddress sockaddr = new InetSocketAddress(mEditTextIp.getText()
.toString(), Server.PORT);
server = new Socket();
try {
server.setSoTimeout(1000);
Log.d(TAG, "Connecting to server.");
server.connect(sockaddr, Server.PORT);
Log.d(TAG, "Connected to server.");
} catch (IOException e) {
Log.e(TAG, "Can't connect to server.");
server = null;
}
if (server != null)
try {
server.setSoTimeout(0);
Log.d(TAG, "Opening output stream...");
serverOutput = new ObjectOutputStream(server.getOutputStream());
if (serverOutput != null)
Log.d(TAG, "Output stream opened");
else
Log.e(TAG, "Error while opening output stream");
} catch (IOException e) {
Log.e(TAG, "Server socket probably closed");
}
}
public void requestLogin() {
new Thread(new Runnable() {
#Override
public void run() {
Log.e(TAG, "Sending login packet...");
Server.sendPacket(new ClientUserLoginPacket(mEditTextLogin
.getText().toString(), ""), serverOutput); // TODO send
// pass and
// email
Log.e(TAG, "Login packet send");
}
}).start();
}
public void authenticate(View v) {
if (server == null)
connectToServer();
if (server != null) {
requestLogin();
try {
Thread.sleep(1000);
} catch (InterruptedException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
try {
serverInput = new ObjectInputStream(server.getInputStream());
} catch (StreamCorruptedException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
if (serverInput != null) {
Log.e(TAG, "Start reciving callbacks...");
callbackHandler.start();
} else {
Log.d(TAG, "Can't open input stream to server.");
}
}
}
public void runServer(View v) {
new Thread(new Server()).start();
Toast.makeText(this, "Server running...", 1000).show();
}
}
Where runServer() and authenticate() functions are triggered with button.
Problem is that after server recive ClientLoginPacket, all subsequent sentPacket functions hangs on oos.writeObject().
I think the order of reading/writing from/to streams may be wrong.
What should be correct order of opening streams and writing objects to them?
Do I have to write something to ObjectOutputStream before opening ObjectInputStream?
After few hours I found that keywords synchronized before my methods readPacket() and sendPacket() were problem. ;)

Java ServerSocket and Android LocalServerSocket

I have implemented my own android service as follows
public class MyService extends Service {
private static final String TAG = "MyService";
private Server mServer;
private LocalServerSocket server;
#Override
public IBinder onBind(Intent intent) {
return null;
}
#Override
public void onCreate() {
Log.d(TAG, "onCreate");
mServer = new Server();
mServer.start();
}
#Override
public void onDestroy() {
Log.d(TAG, "onDestroy");
if(server != null){
try {
server.close();
} catch (IOException e) {
Log.d(TAG, "exception in server close");
e.printStackTrace();
}
}
}
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.d(TAG, "onStart");
return START_STICKY;
}
class Server extends Thread {
#Override
public void run() {
try {
server = new LocalServerSocket("my.socket");
while (true) {
LocalSocket receiver;
try{
receiver = server.accept();
}catch(SocketException e){
Log.d(TAG, "SocketException");
break;
}
catch(IOException e){
Log.d(TAG, "IOException");
break;
}
if (receiver != null) {
Log.d(TAG, "Got Data in receiver");
}
receiver.close();
}
} catch (IOException e) {
Log.d(TAG, "one more");
e.printStackTrace();
}
}
}
}
The problem I am facing is that, if my LocalServerSocket is blocking in accept(), then a call to server.close() in OnDestroy() will not throw a SocketException. Hence, next time I start the service, I get "address already in use exception". If instead of LocalServerSocket, I use java.net.Socket, then i get the required behavior. I would like to know why LocalServerSocket behaves differently from Java Sockets. In my case, how do I come out of the while loop.
I had the same problem and "solved" it this way. The thread run() method is checking for "!isInterrupted()". The method "stopSocketServer()" which I added to my Listener-Thread marks the thread for interrupt() and then made a connect request to itself to trigger the accept() method.
/**
* Executed if thread is started.
*/
public void run() {
try {
// leave while loop if thread is marked for interrupt.
while (!isInterrupted()) {
LocalSocket clientSocket = serverSocket.accept();
if (!isInterrupted()) {
threadPool.execute(new ClientProcessor(clientSocket));
}
}
} catch (IOException e) {
if (!isInterrupted()) {
Log.e(TAG, "socket listener terminated", e);
}
} finally {
try {
if (serverSocket != null) {
serverSocket.close();
}
if (threadPool != null) {
threadPool.shutdownNow();
}
Log.i(TAG, "socket listener stopped");
} catch (IOException e) {
}
}
}
public void stopSocketServer() {
if (serverSocket != null) {
try {
// mark thread as interrupted
interrupt();
// now send connect request to myself to trigger leaving accept()
LocalSocket ls = new LocalSocket();
ls.connect(serverSocket.getLocalSocketAddress());
ls.close();
} catch (IOException e) {
Log.e(TAG, "stopSocketServer failed", e);
}
}
}
From the code you've posted it looks like your LocalServerSocket server will remain null forever and onDestroy() won't close it. Also, closing the socket normally shouldn't throw IOException - but the accept() will do so if the socket is closed concurrently.

Categories