How to connect android app to node server? - android

I am trying out the WebSockets with Fallbacks transports for Android, Node.js and Atmosphere example. I get an the following error:
/home/mofa/NetBeansProjects/App/src/com/jullio/advisor/wAsyncChat.java:87: error: cannot access JsonParseException
return mapper.readValue(data, Message.class);
class file for org.codehaus.jackson.JsonParseException not found
/home/mofa/NetBeansProjects/App/src/com/jullio/advisor/wAsyncChat.java:68: error: cannot access ObjectCodec
return mapper.writeValueAsString(data);
class file for org.codehaus.jackson.ObjectCodec not found
Here is the androidchat code:
import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.os.StrictMode;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import org.atmosphere.wasync.ClientFactory;
import org.atmosphere.wasync.Decoder;
import org.atmosphere.wasync.Encoder;
import org.atmosphere.wasync.Event;
import org.atmosphere.wasync.Function;
import org.atmosphere.wasync.Request;
import org.atmosphere.wasync.RequestBuilder;
import org.atmosphere.wasync.impl.AtmosphereClient;
import org.codehaus.jackson.map.ObjectMapper;
import java.io.IOException;
import java.util.Date;
public class wAsyncChat extends Activity {
private Button bt;
private TextView tv;
private String serverIpAddress = "http://10.0.2.2:8080";
private final static ObjectMapper mapper = new ObjectMapper();
private final Handler uiHandler = new Handler();
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
StrictMode.ThreadPolicy policy = new StrictMode.ThreadPolicy.Builder().permitAll().build();
StrictMode.setThreadPolicy(policy);
setContentView(R.layout.main);
bt = (Button) findViewById(R.id.myButton);
tv = (TextView) findViewById(R.id.myTextView);
try {
AtmosphereClient client = ClientFactory.getDefault().newClient(AtmosphereClient.class);
RequestBuilder request = client.newRequestBuilder()
.method(Request.METHOD.GET)
.uri(serverIpAddress + "/chat")
.trackMessageLength(true)
.encoder(new Encoder<Message, String>() {
#Override
public String encode(Message data) {
try {
return mapper.writeValueAsString(data);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
})
.decoder(new Decoder<String, Message>() {
#Override
public Message decode(Event type, String data) {
data = data.trim();
// Padding
if (data.length() == 0) {
return null;
}
if (type.equals(Event.MESSAGE)) {
try {
return mapper.readValue(data, Message.class);
} catch (IOException e) {
e.printStackTrace();
return null;
}
} else {
return null;
}
}
})
.transport(Request.TRANSPORT.WEBSOCKET);
final org.atmosphere.wasync.Socket socket = client.create();
socket.on("message", new Function<Message>() {
#Override
public void on(final Message t) {
uiHandler.post(new Runnable() {
#Override
public void run() {
Date d = new Date(t.getTime());
tv.append("Author " + t.getAuthor() + "# " + d.getHours() + ":" + d.getMinutes() + ": " + t.getMessage() + "\n");
}
});
}
}).on(new Function<Throwable>() {
#Override
public void on(Throwable t) {
tv.setText("ERROR 3: " + t.getMessage());
t.printStackTrace();
}
}).open(request.build());
bt.setOnClickListener(new OnClickListener() {
String name = null;
public void onClick(View v) {
try {
EditText et = (EditText) findViewById(R.id.EditText01);
String str = et.getText().toString();
if (name == null) {
name = str;
}
socket.fire(new Message(name, str));
et.setText("");
Log.d("Client", "Client sent message");
} catch (Throwable e) {
tv.setText("ERROR 3: " + e.getMessage());
e.printStackTrace();
}
}
});
} catch (Throwable e) {
tv.setText("Unable to connect: " + e.getMessage());
e.printStackTrace();
}
}
}

I have the library for nodeserver connection. You can use it from git
SocketIO socketio = new SocketIO() {
#Override
public void onConnect() {
}
#Override
public void onDisconnect() {
}
#Override
public void onMessage(String message) {
Log.d("===Server Answer====",message);
}
};
socketio.Connect("192.168.0.1", 9000);
after onConnect() send the message:
socketio.send("Your message to socket");
it work with latest socketIO, and use RFC 6455 websocket protocol

Related

Sending information to ubidots only if the condition are met

The Image shows the dashboard of the application
package com.example.leeyueloong.proximityapplication;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.AsyncTask;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.widget.Toast;
import com.estimote.mustard.rx_goodness.rx_requirements_wizard.Requirement;
import com.estimote.mustard.rx_goodness.rx_requirements_wizard.RequirementsWizardFactory;
import com.estimote.proximity_sdk.proximity.EstimoteCloudCredentials;
import com.estimote.proximity_sdk.proximity.ProximityContext;
import com.estimote.proximity_sdk.proximity.ProximityObserver;
import com.estimote.proximity_sdk.proximity.ProximityObserverBuilder;
import com.estimote.proximity_sdk.proximity.ProximityZone;
import com.ubidots.ApiClient;
import com.ubidots.Variable;
import java.util.ArrayList;
import java.util.List;
import kotlin.Unit;
import kotlin.jvm.functions.Function0;
import kotlin.jvm.functions.Function1;
public class MainActivity extends AppCompatActivity {
private ProximityObserver proximityObserver;
private int send;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
EstimoteCloudCredentials cloudCredentials =
new EstimoteCloudCredentials("tan-kok-hui-s-proximity-fo-07q", "d18623351c273ce3e09ffcc1f6201861");
// 2. Create the Proximity Observer
this.proximityObserver =
new ProximityObserverBuilder(getApplicationContext(), cloudCredentials)
.withOnErrorAction(new Function1<Throwable, Unit>() {
#Override
public Unit invoke(Throwable throwable) {
Log.e("app", "proximity observer error: " + throwable);
return null;
}
})
.withLowLatencyPowerMode()
.build();
// add this below:
ProximityZone zone = this.proximityObserver.zoneBuilder()
.forTag("desks")
.inNearRange()
.withOnEnterAction(new Function1<ProximityContext, Unit>() {
#Override
public Unit invoke(ProximityContext context) {
String deskOwner = context.getAttachments().get("desk-owner");
Log.d("app", "Welcome to " + deskOwner + "'s desk");
return null;
}
})
.withOnExitAction(new Function1<ProximityContext, Unit>() {
#Override
public Unit invoke(ProximityContext context) {
return null;
}
})
.withOnChangeAction(new Function1<List<? extends ProximityContext>, Unit>() {
#Override
public Unit invoke(List<? extends ProximityContext> contexts) {
List<String> deskOwners = new ArrayList<>();
for (ProximityContext context : contexts) {
deskOwners.add(context.getAttachments().get("desk-owner"));
}
for (int i = 0; i < deskOwners.size(); i++) {
if (deskOwners.get(i).contains("Peter")) {
send = 1;
Toast.makeText(MainActivity.this, "Alert:Unauthorized Zone", Toast.LENGTH_SHORT).show();
Toast.makeText(MainActivity.this, "Sending information to ubidots..........", Toast.LENGTH_LONG).show();
} else if (deskOwners.get(i).contains("Alex")) {
send = 0;
Toast.makeText(MainActivity.this, "Device has entered this range", Toast.LENGTH_SHORT).show();
}
else if (deskOwners.get(i).contains("Paul"))
{
}
}
Log.d("app", "In range of desks: " + deskOwners);
return null;
}
})
.create();
this.proximityObserver.addProximityZone(zone);
ProximityZone innerZone = this.proximityObserver.zoneBuilder()
.forTag("treasure")
.inCustomRange(3.0)
.create();
ProximityZone outerZone = this.proximityObserver.zoneBuilder()
.forTag("treasure")
.inCustomRange(9.0)
.create();
RequirementsWizardFactory
.createEstimoteRequirementsWizard()
.fulfillRequirements(MainActivity.this,
// onRequirementsFulfilled
new Function0<Unit>() {
#Override
public Unit invoke() {
Log.d("app", "requirements fulfilled");
proximityObserver.start();
return null;
}
},
// onRequirementsMissing
new Function1<List<? extends Requirement>, Unit>() {
#Override
public Unit invoke(List<? extends Requirement> requirements) {
Log.e("app", "requirements missing: " + requirements);
return null;
}
},
// onError
new Function1<Throwable, Unit>() {
#Override
public Unit invoke(Throwable throwable) {
Log.e("app", "requirements error: " + throwable);
return null;
}
});
}
private BroadcastReceiver mBatteryReceiver = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
if (send == 1 || send == 0) {
new ApiUbidots().execute(send);
}
}
};
#Override
protected void onStart() {
super.onStart();
registerReceiver(mBatteryReceiver, new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
}
// Stop the BroadcastReceiver
#Override
protected void onStop() {
unregisterReceiver(mBatteryReceiver);
super.onStop();
}
// In Charge of sending variable to ubidots
public class ApiUbidots extends AsyncTask<Integer, Void, Void> {
private final String API_KEY = "A1E-0109ebb46b4990a4ed82f74d2912a9e6b481";
private final String VARIABLE_ID = "5b62ab7ac03f97418ba1c28e";
#Override
protected Void doInBackground(Integer... params) {
ApiClient apiClient = new ApiClient(API_KEY);
Variable batteryLevel = apiClient.getVariable(VARIABLE_ID);
batteryLevel.saveValue(params[0]);
return null;
}
}
}
The problem is i am able to sending the data to the ubidots however i want to make it so that only if the condition has met, it will then send to the cloud. Other than that, it would not have send any data to the cloud.

Android get attribute value from xml

I am trying to make an app which uses last.fm's web API, sends a query for similar artists and returns all the names of the similar artists. It seems as though I manage to connect and get the xml response properly. However, I cannot extract the value of the name-attribute. I am using artistName = xmlData.getAttributeValue(null, "name"); but all it gives me is null.
import android.app.Activity;
import android.os.Bundle;
import android.text.TextUtils;
import android.util.Log;
import android.view.View;
import android.widget.ArrayAdapter;
import android.widget.EditText;
import android.widget.ListView;
import android.widget.Toast;
import java.util.*;
#SuppressWarnings("FieldCanBeLocal")
public class MainActivity extends Activity implements Observer {
private final String INPUTERROR = "Invalid/missing artist name.";
private NetworkCommunication nc;
private ArrayList<String> list;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
nc = new NetworkCommunication();
nc.register(this);
list = new ArrayList<>();
ListView lv = (ListView)findViewById(R.id.ListView_similarArtistsList);
ArrayAdapter adapter = new ArrayAdapter<>(this, android.R.layout.simple_list_item_1, list);
lv.setAdapter(adapter);
}
public void searchButton_Clicked(View v){
EditText inputField = (EditText)findViewById(R.id.editText_artistName);
String searchString = inputField.getText().toString();
searchString = cleanSearchString(searchString);
if(validateSearchString(searchString)){
nc.setSearchString(searchString);
nc.execute();
}
else{
Toast.makeText(MainActivity.this, INPUTERROR, Toast.LENGTH_SHORT).show();
}
}
private String cleanSearchString(String oldSearchString){
String newString = oldSearchString.trim();
newString = newString.replace(" ", "");
return newString;
}
private boolean validateSearchString(String searchString){
boolean rValue = true;
if(TextUtils.isEmpty(searchString)){
rValue = false;
}
return rValue;
}
#Override
public void update(String artistName) {
list.add(artistName);
}
}
Here is my Network Communications class:
import android.os.AsyncTask;
import android.util.Log;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlPullParserFactory;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
#SuppressWarnings("FieldCanBeLocal")
class NetworkCommunication extends AsyncTask<Object, String, Integer> implements Subject {
private final String MYAPIKEY = "--------------------------";
private final String ROOT = "http://ws.audioscrobbler.com/2.0/";
private final String METHOD = "?method=artist.getsimilar";
private ArrayList<Observer> observers;
private int amountOfArtists = 0;
private String foundArtistName;
private String searchString;
NetworkCommunication(){
observers = new ArrayList<>();
}
void setSearchString(String newSearchString){
searchString = newSearchString;
}
private XmlPullParser sendRequest(){
try{
URL url = new URL(ROOT + METHOD + "&artist=" + searchString + "&api_key=" + MYAPIKEY);
XmlPullParser receivedData = XmlPullParserFactory.newInstance().newPullParser();
receivedData.setInput(url.openStream(), null);
return receivedData;
}
catch (IOException | XmlPullParserException e){
Log.e("ERROR", e.getMessage(), e);
}
return null;
}
private int tryProcessData(XmlPullParser xmlData){
int artistsFound = 0;
String artistName;
int eventType;
try{
while ((eventType = xmlData.next()) != XmlPullParser.END_DOCUMENT) {
if (eventType == XmlPullParser.START_TAG) {
if(xmlData.getName().equals("name")){
artistName = xmlData.getAttributeValue(null, "name");
publishProgress(artistName);
artistsFound++;
}
}
}
}
catch (IOException | XmlPullParserException e){
Log.e("ERROR", e.getMessage(), e);
}
if (artistsFound == 0) {
publishProgress();
}
return artistsFound;
}
#Override
protected Integer doInBackground(Object... params) {
XmlPullParser data = sendRequest();
if(data != null){
return tryProcessData(data);
}
else{
return null;
}
}
#Override
protected void onProgressUpdate(String... values){
/*
if (values.length == 0) {
//No data found...
}
*/
if (values.length == 1) {
setFoundArtistName(values[0]);
notifyObserver();
}
super.onProgressUpdate(values);
}
private void setFoundArtistName(String newArtistName){
foundArtistName = newArtistName;
}
#Override
public void register(Observer newObserver) {
observers.add(newObserver);
}
#Override
public void unregister(Observer deleteObserver) {
observers.remove(deleteObserver);
}
#Override
public void notifyObserver() {
for (Observer o : observers) {
Log.i("my tag.... ", "name = " + foundArtistName);
o.update(foundArtistName);
}
}
}
Here's a screenshot of the xml response in Google Chrome:
The only thing I am interested in extracting at this moment is the the value of the Name-Element.
I am logging the value of foundArtistName (in the method notifyObserver) it gives me A LOT of "my tag.... name = null my tag.... name = null my tag.... name = null etc.."
EDIT: I tried using getText() instead of getAttributeValue(), but it also gives me null.
I found the solution. I was using: artistName = xmlData.getAttributeValue(null, "name");, when I really should've used: artistName = xmlData.nextText();

Telegram API RPC Timeout Exception

I am using telegram API to develop my application. Here is the whole code.
app_info.java:
package com.example.mytelegram;
import org.telegram.api.engine.AppInfo;
public class app_info extends AppInfo {
public app_info(int apiId, String deviceModel, String systemVersion,
String appVersion, String langCode) {
super(apiId, deviceModel, systemVersion, appVersion, langCode);
// TODO Auto-generated constructor stub
}
MemoryApiState.java:
package com.example.mytelegram;
import org.telegram.api.TLConfig;
import org.telegram.api.TLDcOption;
import org.telegram.api.engine.storage.AbsApiState;
import org.telegram.mtproto.state.AbsMTProtoState;
import org.telegram.mtproto.state.ConnectionInfo;
import org.telegram.mtproto.state.KnownSalt;
import java.util.ArrayList;
import java.util.HashMap;
public class MemoryApiState implements AbsApiState {
private HashMap<Integer, ConnectionInfo[]> connections = new HashMap<Integer, ConnectionInfo[]>();
private HashMap<Integer, byte[]> keys = new HashMap<Integer, byte[]>();
private HashMap<Integer, Boolean> isAuth = new HashMap<Integer, Boolean>();
private int primaryDc = 2; // I have tested application with primaryDc=0 to 10
public MemoryApiState(boolean isTest) {
connections.put(1, new ConnectionInfo[]{
new ConnectionInfo(1, 0, isTest ? "149.154.167.40" : "149.154.167.50", 443)
});
}
#Override
public synchronized int getPrimaryDc() {
return primaryDc;
}
#Override
public synchronized void setPrimaryDc(int dc) {
primaryDc = dc;
}
#Override
public synchronized boolean isAuthenticated(int dcId) {
if (isAuth.containsKey(dcId)) {
return isAuth.get(dcId);
}
return false;
}
#Override
public synchronized void setAuthenticated(int dcId, boolean auth) {
isAuth.put(dcId, auth);
}
#Override
public synchronized void updateSettings(TLConfig config) {
connections.clear();
HashMap<Integer, ArrayList<ConnectionInfo>> tConnections = new HashMap<Integer, ArrayList<ConnectionInfo>>();
int id = 0;
for (TLDcOption option : config.getDcOptions()) {
if (!tConnections.containsKey(option.getId())) {
tConnections.put(option.getId(), new ArrayList<ConnectionInfo>());
}
tConnections.get(option.getId()).add(new ConnectionInfo(id++, 0, option.getIpAddress(), option.getPort()));
}
for (Integer dc : tConnections.keySet()) {
connections.put(dc, tConnections.get(dc).toArray(new ConnectionInfo[0]));
}
}
#Override
public synchronized byte[] getAuthKey(int dcId) {
return keys.get(dcId);
}
#Override
public synchronized void putAuthKey(int dcId, byte[] key) {
keys.put(dcId, key);
}
#Override
public synchronized ConnectionInfo[] getAvailableConnections(int dcId) {
if (!connections.containsKey(dcId)) {
return new ConnectionInfo[0];
}
return connections.get(dcId);
}
#Override
public synchronized AbsMTProtoState getMtProtoState(final int dcId) {
return new AbsMTProtoState() {
private KnownSalt[] knownSalts = new KnownSalt[0];
#Override
public byte[] getAuthKey() {
return MemoryApiState.this.getAuthKey(dcId);
}
#Override
public ConnectionInfo[] getAvailableConnections() {
return MemoryApiState.this.getAvailableConnections(dcId);
}
#Override
public KnownSalt[] readKnownSalts() {
return knownSalts;
}
#Override
protected void writeKnownSalts(KnownSalt[] salts) {
knownSalts = salts;
}
};
}
#Override
public synchronized void resetAuth() {
isAuth.clear();
}
#Override
public synchronized void reset() {
isAuth.clear();
keys.clear();
}
}
MainActivity.java:
package com.example.mytelegram;
import java.io.IOException;
import org.telegram.api.TLAbsUpdates;
import org.telegram.api.TLConfig;
import org.telegram.api.TLNearestDc;
import org.telegram.api.auth.TLAuthorization;
import org.telegram.api.auth.TLSentCode;
import org.telegram.api.engine.ApiCallback;
import org.telegram.api.engine.AppInfo;
import org.telegram.api.engine.LoggerInterface;
import org.telegram.api.engine.RpcException;
import org.telegram.api.engine.TelegramApi;
import org.telegram.api.requests.TLRequestAuthSendCall;
import org.telegram.api.requests.TLRequestAuthSendCode;
import org.telegram.api.requests.TLRequestAuthSignIn;
import org.telegram.api.requests.TLRequestHelpGetConfig;
import org.telegram.api.requests.TLRequestHelpGetNearestDc;
import org.telegram.api.requests.TLRequestUpdatesGetState;
import org.telegram.api.updates.TLState;
import android.support.v7.app.ActionBarActivity;
import android.content.Context;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.os.Bundle;
import android.util.Log;
public class MainActivity extends ActionBarActivity {
static Context context;
private static AppInfo ai;
private static void createApi() throws IOException {
String res = "No test";
boolean useTest = res.equals("test");
apiState = new MemoryApiState(useTest);
ai=new AppInfo(App_ID, "Sony", "???", "???", "en");
api = new TelegramApi(apiState, ai, new ApiCallback() {
#Override
public void onAuthCancelled(TelegramApi api) {
}
#Override
public void onUpdatesInvalidated(TelegramApi api) {
}
#Override
public void onUpdate(TLAbsUpdates updates) {
Log.d("tlg", "onUpdate(TLAbsUpdates updates)");
}
});
}
private static void login() throws IOException {
//TLNearestDc dcInfo = api.doRpcCallNonAuth(new TLRequestHelpGetNearestDc());
// api.switchToDc(dcInfo.getNearestDc());
//************ Exception raises here ************
TLConfig config = api.doRpcCallNonAuth(new TLRequestHelpGetConfig()); // I have An IOException Here (e=timeout Exception)
apiState.updateSettings(config);
Log.d("tlg","completed.");
String phone = "+9891********"; // I have tested app with the phone without plus too
Log.d("tlg","Sending sms to phone " + phone + "...");
TLSentCode sentCode = null;
try {
api.doRpcCallNonAuth(new TLRequestAuthSendCode(phone, 1, app_id, "__hash__", "en"));
} catch (RpcException e) {
if (e.getErrorCode() == 303) {
int destDC;
if (e.getErrorTag().startsWith("NETWORK_MIGRATE_")) {
destDC = Integer.parseInt(e.getErrorTag().substring("NETWORK_MIGRATE_".length()));
} else if (e.getErrorTag().startsWith("PHONE_MIGRATE_")) {
destDC = Integer.parseInt(e.getErrorTag().substring("PHONE_MIGRATE_".length()));
} else if (e.getErrorTag().startsWith("USER_MIGRATE_")) {
destDC = Integer.parseInt(e.getErrorTag().substring("USER_MIGRATE_".length()));
} else {
throw e;
}
api.switchToDc(destDC);
sentCode = api.doRpcCallNonAuth(new TLRequestAuthSendCode(phone, 0, app_id, "__hash__", "en"));
} else {
throw e;
}
}
Log.d("tlg","Activation code:");
String code = "123";
TLAuthorization auth = api.doRpcCallNonAuth(new TLRequestAuthSignIn(phone, sentCode.getPhoneCodeHash(), code));
apiState.setAuthenticated(apiState.getPrimaryDc(), true);
Log.d("tlg","Activation complete.");
TLState state = api.doRpcCall(new TLRequestUpdatesGetState());
Log.d("tlg","loaded.");
}
static MemoryApiState apiState;
static TelegramApi api;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
context=getApplicationContext();
try {
createApi();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
try {
login();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
//workLoop();
}
}
I have added telegram-api library (telegram-api-1.1.127-shadow.jar) in my eclipse 'libs' folder. The problem is Timeout Exception in MainActivity.java when I want to call RPC. Where is the problem? What should I do? My OS is windows 8.1 . I am searching for 2 whole days. I have read github codes too. They are very complicated for me. I can not figure out what should I do?
I have added INTERNET and ACCESS_NETWORK_STATE permisions to AndroidManifest.xml. I allways check the network connection when my application runs.
I found the problem in your code:
In MemoryApiState you should put, in the 'connections' map, 'primaryDc' as key and not '1'.

Can't create handler inside thread that has not called Looper.prepare()

EDIT: My solution was to have a constant class with this code:
static EditText Port = (EditText) MainActivity.mDialog.findViewById(R.id.txtPort); static int mPort = Integer.parseInt(Port.getText().toString()); public static final int PORT = mPort;
I've tried to see the other questions and I understand that I've gotta do something with the UIThread? Honestly, I understand nothing. Im new with android.
The app (chat app) worked fine until I wanted to have multiple ports options depending if I'm hosting (port 5050) or joining (port 80) a chat room. From the beginning i just had a constant value of the port (public static final int PORT) but now i cant have that ofc. If you guys have any other suggestions on how i can have two values of PORT, please share your tips.
Anyway, after trying EVERYTHING I decided to put a method in my main class, and just declare it in other classes. This is the mothod for the value of the port:
public int getPORT() {
txtPORT = (EditText) findViewById(R.id.txtPort);
//String txtPORTa = txtPORT.getText().toString();
int dennaPORT = 0;
if (mJoin.isChecked()) {
dennaPORT = Integer.parseInt(txtPORT.getText().toString());
return dennaPORT;
}
else if (mHost.isChecked()) {
dennaPORT = 5050;
return dennaPORT;
}
return dennaPORT;
}
This is my MainActivity
package chat.chris.android.se.chatup;
import android.app.Activity;
import android.app.Dialog;
import android.os.AsyncTask;
import android.os.Bundle;
import android.view.KeyEvent;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.inputmethod.EditorInfo;
import android.widget.Button;
import android.widget.CompoundButton;
import android.widget.CompoundButton.OnCheckedChangeListener;
import android.widget.EditText;
import android.widget.ListView;
import android.widget.RadioButton;
import android.widget.TextView;
import android.widget.TextView.OnEditorActionListener;
import android.widget.Toast;
import java.util.ArrayList;
import chat.chris.android.se.chatup.Listeners.ChatListener;
import chat.chris.android.se.chatup.Listeners.ConnectionListener;
import chat.chris.android.se.chatup.Networking.Client;
import chat.chris.android.se.chatup.Networking.Server;
import chat.chris.android.se.chatup.Utilities.ServerUtils;
import static chat.chris.android.se.chatup.R.id.rdioHost;
import static chat.chris.android.se.chatup.R.id.rdioJoin;
public class MainActivity extends Activity {
private Adapter chatAdapter;
private Button btnSendMessage;
private EditText txtMessage;
private ListView lstMessages;
private static Dialog mDialog;
private Client mClient;
private Server mServer;
private EditText txtPORT;
private RadioButton mJoin;
private RadioButton mHost;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
showChatSelection();
chatAdapter = new Adapter(this, new ArrayList<ChatItem>());
lstMessages = (ListView) findViewById(R.id.lstChat);
lstMessages.setAdapter(chatAdapter);
txtMessage = (EditText) findViewById(R.id.txtSay);
txtMessage.setOnEditorActionListener(txtMessageEditorActionListener);
btnSendMessage = (Button) findViewById(R.id.btnSend);
btnSendMessage.setOnClickListener(btnSendMessageClickListener);
Client.setOnChatListener(chatListener);
Client.setOnConnectionListener(connectionListener);
}
public void showChatSelection() {
mDialog = new Dialog(this);
mDialog.setContentView(R.layout.layout_chat_choose);
mDialog.setTitle("Chat Room");
mDialog.setCancelable(false);
final EditText txtServer = (EditText) mDialog.findViewById(R.id.txtAddress);
final TextView lblServer = (TextView) mDialog.findViewById(R.id.lblAddress);
final TextView txtPort = (EditText) mDialog.findViewById(R.id.txtPort);
final RadioButton mHost = (RadioButton) mDialog.findViewById(rdioHost);
final RadioButton mJoin = (RadioButton) mDialog.findViewById(rdioJoin);
try {
lblServer.setText(ServerUtils.getLocalIp(this));
} catch (NullPointerException e) {
mHost.setEnabled(false);
mHost.setChecked(false);
lblServer.setText("Wifi must be enabled to host");
mJoin.setChecked(true);
txtServer.setVisibility(View.VISIBLE);
txtPort.setVisibility(View.VISIBLE);
}
mDialog.findViewById(R.id.btnChoose).setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
mDialog.findViewById(R.id.progLoading).setVisibility(View.VISIBLE);
new SetupChat().execute(mHost.isChecked(), mJoin.isChecked() ? txtServer.getText().toString() : "");
}
});
mHost.setOnCheckedChangeListener(new OnCheckedChangeListener() {
#Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
if (isChecked) {
mJoin.setChecked(false);
txtServer.setVisibility(View.INVISIBLE);
txtPort.setVisibility(View.INVISIBLE);
lblServer.setVisibility(View.VISIBLE);
}
}
});
mJoin.setOnCheckedChangeListener(new OnCheckedChangeListener() {
#Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
if (isChecked) {
mHost.setChecked(false);
txtServer.setVisibility(View.VISIBLE);
txtPort.setVisibility(View.VISIBLE);
lblServer.setVisibility(View.INVISIBLE);
}
}
});
mDialog.show();
}
private final OnClickListener btnSendMessageClickListener = new OnClickListener() {
#Override
public void onClick(View v) {
sendMessage();
}
};
private final OnEditorActionListener txtMessageEditorActionListener = new OnEditorActionListener() {
#Override
public boolean onEditorAction(TextView v, int id, KeyEvent event) {
if (id == EditorInfo.IME_ACTION_NEXT || id == EditorInfo.IME_ACTION_DONE)
sendMessage();
return true;
}
};
private final ChatListener chatListener = new ChatListener() {
#Override
public void onChat(String message) {
chatAdapter.addItem(new ChatItem("<html>" + message + "</html>", "Friend"));
}
};
private final ConnectionListener connectionListener = new ConnectionListener() {
#Override
public void onDisconnect(Client client) {
chatAdapter.addItem(new ChatItem(client.getName() + " left the chat room", ""));
}
#Override
public void onJoin(Client client) {
chatAdapter.addItem(new ChatItem(client.getName() + " joined the chat room", ""));
}
};
public void sendMessage() {
String message = txtMessage.getText().toString();
if(message == null || message.isEmpty())
return;
message = message.replace(">", ">");
message = message.replace("<", "<");
try {
if (mServer != null) {
mServer.sendMessage(message);
chatAdapter.addItem(new ChatItem(message, "You"));
} else if (mClient != null) {
mClient.sendMessage(message);
chatAdapter.addItem(new ChatItem(message, "You"));
} else {
return;
}
} catch (Exception e) {
chatAdapter.addItem(new ChatItem(e.getMessage(), "<font color='red'>Error</font>"));
return;
}
txtMessage.setText("");
}
public int getPORT() {
txtPORT = (EditText) findViewById(R.id.txtPort);
//String txtPORTa = txtPORT.getText().toString();
int dennaPORT = 0;
if (mJoin.isChecked()) {
dennaPORT = Integer.parseInt(txtPORT.getText().toString());
return dennaPORT;
}
else if (mHost.isChecked()) {
dennaPORT = 5050;
return dennaPORT;
}
return dennaPORT;
}
private class SetupChat extends AsyncTask<Object,Void, Boolean> {
#Override
protected Boolean doInBackground(Object... args) {
try {
if ((Boolean)args[0]) {
mServer = new Server();
new Thread(mServer).start();
} else {
String address = args[1].toString();
mClient = Client.connect(address);
if (mClient == null)
return true;
new Thread(mClient).start();
}
} catch (Exception e) {
e.printStackTrace();
return true;
}
return false;
}
#Override
protected void onPostExecute(Boolean errors) {
if (errors)
Toast.makeText(getApplicationContext(), "Någonting gick fel\nSkrev du in allting rätt?", Toast.LENGTH_LONG).show();
else
mDialog.dismiss();
}
}
}
This is the client class
package chat.chris.android.se.chatup.Networking;
import java.io.BufferedReader;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.Socket;
import chat.chris.android.se.chatup.Listeners.ChatListener;
import chat.chris.android.se.chatup.Listeners.ConnectionListener;
import chat.chris.android.se.chatup.MainActivity;
import chat.chris.android.se.chatup.Utilities.Crypto;
public class Client implements Runnable {
private BufferedReader reader;
private DataOutputStream writer;
private boolean disconnecting;
private byte[] cryptoKey;
private String name;
private static ChatListener chatListener;
private static ConnectionListener connectionListener;
//Instansierar ny klient
public Client(Socket s) throws IOException {
cryptoKey = new byte[16];
try {
reader = new BufferedReader(new InputStreamReader(s.getInputStream()));
writer = new DataOutputStream(s.getOutputStream());
} catch (IOException e) {
disconnect();
return;
}
}
//Ger klienten ett namn
public String getName() {
return name;
}
//Sätter namnet
public void setName(String name) {
this.name = name;
}
public static void setOnChatListener(ChatListener listener) {
chatListener = listener;
}
public static void setOnConnectionListener(ConnectionListener listener) {
connectionListener = listener;
}
public BufferedReader getReader() {
return reader;
}
public byte[] getKey(){
return cryptoKey;
}
public void setKey(byte[] key) {
cryptoKey = key;
}
#Override
public void run() {
if (connectionListener != null) {
connectionListener.onJoin(this);
}
try {
while (!disconnecting) {
String read;
if ((read = reader.readLine()) != null) {
if (chatListener != null) {
chatListener.onChat(Crypto.decrypt(read, cryptoKey));
}
}
else{
return;
}
Thread.sleep(5);
}
} catch (IOException e) {
disconnect();
} catch (Exception e) {
e.printStackTrace();
disconnect();
}
}
//Connectar till adressen och returnerar klienten
public static Client connect(String address) throws IOException {
MainActivity porten = new MainActivity();
int PORT;
PORT = porten.getPORT();
InetAddress localAddress = InetAddress.getByName(address);
InetSocketAddress localSocketAddress = new InetSocketAddress(localAddress, PORT);
Socket socket = new Socket();
socket.connect(localSocketAddress, 5000);
Client client = new Client(socket);
socket.getInputStream().read(client.cryptoKey, 0, 16);
System.out.println("Client -> " + new String(client.cryptoKey));
return client;
}
public void sendMessage(String message) throws Exception {
if(message == null || message.isEmpty())
return;
writer.writeUTF(Crypto.encrypt(message, cryptoKey));
}
public void disconnect() {
if (connectionListener != null) {
connectionListener.onDisconnect(this);
}
try {
reader.close();
} catch (IOException e) {
e.printStackTrace();
}
try {
writer.close();
} catch (IOException e) {
e.printStackTrace();
}
reader = null;
writer = null;
}
}
And this is the server class
package chat.chris.android.se.chatup.Networking;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.Random;
import chat.chris.android.se.chatup.MainActivity;
import static chat.chris.android.se.chatup.Utilities.Constants.MAX_USERS;
//import static chat.chris.android.se.chatup.Utilities.Constants.PORT;
public class Server implements Runnable {
private ArrayList<Client> clientList;
private ServerSocket mSocket;
private byte[] cryptoKey;
private boolean shuttingDown;
MainActivity porten = new MainActivity();
//Instansierar en ny server chatt
public Server() throws IOException {
int PORT = porten.getPORT();
mSocket = new ServerSocket(PORT);
clientList = new ArrayList<>();
Random mRand = new SecureRandom();
cryptoKey = new byte[16];
mRand.nextBytes(cryptoKey);
System.out.println("Server ->" + new String(cryptoKey));
}
public boolean isShuttingDown() {
return shuttingDown;
}
public void setShuttingDown(boolean shuttingDown) {
this.shuttingDown = shuttingDown;
}
#Override
public void run() {
while (!shuttingDown) {
Socket socket = null;
Client client;
try {
socket = this.mSocket.accept();
if (clientList.size() >= MAX_USERS) {
socket.close();
return;
}
socket.getOutputStream().write(cryptoKey);
client = new Client(socket);
client.setKey(cryptoKey);
new Thread(client).start();
clientList.add(client);
} catch (IOException e) {
e.printStackTrace();
try {
if (socket != null)
socket.close();
} catch (IOException e1) {
e1.printStackTrace();
}
}
}
}
public void sendMessage(String message) throws Exception {
for (Client client : clientList) {
if (shuttingDown)
return;
client.sendMessage(message);
}
}
public void shutdown() {
shuttingDown = true;
try {
mSocket.close();
} catch (IOException e) {
} finally {
mSocket = null;
}
}
}
With this setup im getting these errors:
05-04 00:41:58.969 12319-12370/chat.chris.android.se.chatup W/System.err﹕ java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare()
05-04 00:41:58.970 12319-12370/chat.chris.android.se.chatup W/System.err﹕ at android.os.Handler.<init>(Handler.java:200)
05-04 00:41:58.975 12319-12370/chat.chris.android.se.chatup W/System.err﹕ at android.os.Handler.<init>(Handler.java:114)
05-04 00:41:58.975 12319-12370/chat.chris.android.se.chatup W/System.err﹕ at android.app.Activity.<init>(Activity.java:793)
05-04 00:41:58.975 12319-12370/chat.chris.android.se.chatup W/System.err﹕ at chat.chris.android.se.chatup.MainActivity.<init>(MainActivity.java:32)
05-04 00:41:58.976 12319-12370/chat.chris.android.se.chatup W/System.err﹕ at chat.chris.android.se.chatup.Networking.Server.<init>(Server.java:21)
05-04 00:41:58.976 12319-12370/chat.chris.android.se.chatup W/System.err﹕ at chat.chris.android.se.chatup.MainActivity$SetupChat.doInBackground(MainActivity.java:229)
05-04 00:41:58.976 12319-12370/chat.chris.android.se.chatup W/System.err﹕ at chat.chris.android.se.chatup.MainActivity$SetupChat.doInBackground(MainActivity.java:221)
05-04 00:41:58.976 12319-12370/chat.chris.android.se.chatup W/System.err﹕ at android.os.AsyncTask$2.call(AsyncTask.java:288)
05-04 00:41:58.976 12319-12370/chat.chris.android.se.chatup W/System.err﹕ at java.util.concurrent.FutureTask.run(FutureTask.java:237)
05-04 00:41:58.976 12319-12370/chat.chris.android.se.chatup W/System.err﹕ at android.os.AsyncTask$SerialExecutor$1.run(AsyncTask.java:231)
05-04 00:41:58.976 12319-12370/chat.chris.android.se.chatup W/System.err﹕ at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1112)
05-04 00:41:58.976 12319-12370/chat.chris.android.se.chatup W/System.err﹕ at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:587)
05-04 00:41:58.976 12319-12370/chat.chris.android.se.chatup W/System.err﹕ at java.lang.Thread.run(Thread.java:818)
NEVER create an instance of an Activity, Service, or ContentProvider yourself. Those are always created by the framework. Delete:
MainActivity porten = new MainActivity();
Pass the port into the strangely-named Server class by some other means, such as a constructor parameter or setter method.

android onSaveInstanceState usage

Please guide in the following class what I should save in.
Please remember I am using only one string which is I am retriving from getextra method and there is nothing which I think I should store in the following overridden method.
protected void onSaveInstanceState(Bundle outState) {
// TODO Auto-generated method stub
super.onSaveInstanceState(outState);
}
Can you guide me what to store in onsave instance method and what not to?
package com.wozzon.activity;
import java.util.ArrayList;
import org.json.JSONArray;
import org.json.JSONObject;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.ProgressDialog;
import android.content.Intent;
import android.graphics.Typeface;
import android.os.Bundle;
import android.text.Html;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.View.OnClickListener;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.AdapterView.OnItemClickListener;
import com.wozzon.json.JSONService;
import com.wozzon.model.SearchCriteria;
import com.wozzon.pl.UIFactory;
import com.wozzon.util.Util;
import com.wozzon.util.WLConstants;
public class HomeBrowse extends Activity implements OnItemClickListener{
private final static String TAG = HomeBrowse.class.getSimpleName();
private ArrayList<BrowseRow> model=new ArrayList<BrowseRow>();
private BrowseAdapter adapter;
private ListView list;
private TextView browseTitle;
private TextView changeLink;
private SearchCriteria sc =SearchCriteria.getInstance();
private JSONArray jsonArray;
private ProgressDialog m_ProgressDialog = null;
private Runnable progressRunnable;
#Override
public void onCreate(Bundle savedInstanceState) {
try {
super.onCreate(savedInstanceState);
setContentView(R.layout.browse);
String errorMsg =Util.getExtraValue(getIntent().getExtras(), WLConstants.ERROR_MSG);
list =(ListView)findViewById(R.id.browse_cats);
if(errorMsg!= null){
UIFactory.displayAlert(new AlertDialog.Builder(HomeBrowse.this), "Status", errorMsg);
}
progressRunnable = new Runnable(){
#Override
public void run() {
try {
loadData();
} catch (Throwable e) {
Log.e(TAG, e.getMessage());
}
runOnUiThread(returnRes);
}
};
m_ProgressDialog = ProgressDialog.show(HomeBrowse.this,
"Please wait...", "Loading ...", true);
Thread thread = new Thread(progressRunnable);
thread.start();
} catch (Throwable e) {
Log.e(TAG, e.getMessage());
Util.handleError(HomeBrowse.this, HomeBrowse.class, e.getMessage());
//UIFactory.displayAlert(new AlertDialog.Builder(HomeBrowse.this), "Error Loading categories", e.getMessage());
}
}
private void loadData()throws Throwable{//loading object
jsonArray = JSONService.getJsonArray(getResources().getString(R.string.catJson));
}
private void fillResultRows(JSONArray jsonArray, BrowseAdapter adapter)throws Throwable{
BrowseRow br = null;
try {
for(int a=0; a<jsonArray.length(); a++){
JSONObject jsonObj = jsonArray.getJSONObject(a);
br = new BrowseRow();
br.name=jsonObj.getString("title");
br.image=jsonObj.getString("image");
br.searchType = jsonObj.getInt("searchType");
if(jsonObj.has("categoryIds")){
br.categoryIds = jsonObj.getString("categoryIds").replaceAll("-", ",");
}
if(jsonObj.has("subCategoryIds")){
br.subCategoryIds = jsonObj.getString("subCategoryIds").replaceAll("-", ",");
}
adapter.add(br);
}
} catch (Throwable e) {
throw e;
}
}
class BrowseAdapter extends ArrayAdapter<BrowseRow> {
BrowseAdapter() {
super(HomeBrowse.this, android.R.layout.simple_list_item_1, model);
}
public View getView(int position, View convertView,
ViewGroup parent) {
View row=convertView;
ResultWrapper wrapper=null;
if (row==null) {
LayoutInflater inflater=getLayoutInflater();
row=inflater.inflate(R.layout.browse_row, null);
wrapper=new ResultWrapper(row);
row.setTag(wrapper);
}
else {
wrapper=(ResultWrapper)row.getTag();
}
wrapper.populateFrom(model.get(position));
return(row);
}
}
class ResultWrapper {
private TextView name;
private String catIds="0";
private String subCatIds="0";
private ImageView image;
private int searchType;
private View row=null;
ResultWrapper(View row) {
this.row=row;
}
void populateFrom(BrowseRow r) {
getName().setText(r.name);
getIcon().setImageResource(getImageIcon(Integer.parseInt(r.image)));
catIds =r.categoryIds;
subCatIds = r.subCategoryIds;
searchType =r.searchType;
}
TextView getName() {
if (name==null) {
name=(TextView)row.findViewById(R.id.browse_row_name);
}
return name;
}
ImageView getIcon() {
if (image==null) {
image=(ImageView)row.findViewById(R.id.browse_row_icon);
}
return image;
}
}
private int getImageIcon(int catId){
int imageSource=R.drawable.all_cats;
switch(catId){
case WLConstants.CATEGORY_FILM: imageSource =R.drawable.film; break;
case WLConstants.CATEGORY_MUSIC: imageSource =R.drawable.music; break;
case WLConstants.CATEGORY_ARTS: imageSource =R.drawable.art; break;
case WLConstants.CATEGORY_KIDS: imageSource =R.drawable.museum; break;
case WLConstants.CATEGORY_GALLERY_MUSEUM: imageSource =R.drawable.kids; break;
case WLConstants.CATEGORY_COMEDY: imageSource =R.drawable.comedy; break;
case WLConstants.CATEGORY_NIGHT_CLUBS: imageSource =R.drawable.clubs; break;
case WLConstants.CATEGORY_ATTRACTION: imageSource =R.drawable.touristattractions; break;
case WLConstants.CATEGORY_VARIOUS_EVENTS: imageSource =R.drawable.all_cats; break;
case WLConstants.CATEGORY_ALL_FOOD_DRINK: imageSource =R.drawable.restaurants; break;
}
return imageSource;
}
#Override
public void onItemClick(AdapterView<?> adp, View view, int item, long arg3) {
try {
if("".equals(sc.getSearchLocation()) && (!(Util.locationFound(sc.getGeoLocation()) && sc.isUK()))){
UIFactory.displayAlert(new AlertDialog.Builder(HomeBrowse.this), "Error", "Please provide location");
return;
}
ResultWrapper wrap = (ResultWrapper)view.getTag();
sc.setCategoryIds(wrap.catIds);
sc.setSubCategoryIds(wrap.subCatIds);
sc.setSearchType(wrap.searchType);
goSearch();
} catch (Throwable e) {
Log.e(TAG, e.getMessage());
Util.handleError(HomeBrowse.this, HomeBrowse.class, e.getMessage());
//UIFactory.displayAlert(new AlertDialog.Builder(this), "Error", e.getMessage());
}
}
private void applyListener(){
list.setOnItemClickListener(this);
changeLink.setOnClickListener(onclickListener);
}
private final void goSearch(){
try {
sc.setSearchString("");
Intent mainIntent = new Intent(HomeBrowse.this,SearchResults.class);
startActivity(mainIntent);
} catch (Throwable e) {
Log.e(TAG, e.getMessage());
Util.handleError(HomeBrowse.this, HomeBrowse.class, e.getMessage());
}
}
public OnClickListener onclickListener = new OnClickListener(){
// #Override
public void onClick(View aView) {
try {
int componentId =aView.getId();
switch(componentId){
case R.id.tv_changeDateType:
Intent intent = new Intent(HomeBrowse.this,TimeLocationSettings.class);
startActivity(intent);
break;
}
} catch (Exception e) {
Log.e(TAG, e.getMessage());
Util.handleError(HomeBrowse.this, HomeBrowse.class, e.getMessage());
}
}
};
private void configComponents(){
String titleStr,location = "";
String dateCrtStr = Util.getDateCritieraStr(sc.getDateCriteria());
titleStr= dateCrtStr;
if(!"".equals(sc.getSearchLocation())){
location = sc.getSearchLocation();
}else if(sc.isNearMe()){
location="Near me";
}else{
location = "Postcode or town";
}
titleStr = titleStr + " - "+ location;
browseTitle.setText(titleStr);
changeLink.setTypeface(Typeface.DEFAULT, Typeface.BOLD);
}
public class BrowseRow {
private String name = "";
private String image = "";
private String categoryIds="0";
private String subCategoryIds="0";
private int searchType=1;
}
private Runnable returnRes = new Runnable() {
#Override
public void run() {
try {
browseTitle = (TextView)findViewById(R.id.browsePageTite);
changeLink = (TextView)findViewById(R.id.tv_changeDateType);
changeLink.setText(Html.fromHtml("<a href='#'>Change</a>"));
adapter=new BrowseAdapter();
applyListener();
configComponents();
fillResultRows(jsonArray, adapter);
if(jsonArray == null){
UIFactory.displayAlert(new AlertDialog.Builder(HomeBrowse.this), "Error", "Error Loading categories");
return;
}
list.setAdapter(adapter);
} catch (Throwable e) {
e.printStackTrace();
}
m_ProgressDialog.dismiss();
}
};
}
Not necessarily everything, because
The default implementation takes care of most of the UI per-instance state for you by calling onSaveInstanceState() on each view in the hierarchy that has an id, and by saving the id of the currently focused view (all of which is restored by the default implementation of onRestoreInstanceState(Bundle)).
via http://developer.android.com/reference/android/app/Activity.html#onSaveInstanceState(android.os.Bundle)
but if you override, you have to handle entire state by yourself.
You should be saving everything that describes the view's state that isn't saved elsewhere.
Save where the user is in a EditText, what value the EditText has, what dialog is currently displayed, etc. Your application should be able to reconstruct the exact state it was in from the bundle returned to in when it is paused by the user leaving the application and returning to it.

Categories

Resources