AsyncTask only executing when stepping through in debugger - android

First off, let me say that I'm just starting my Android adventure and am learning on the code posted below.
So i have a Zebra barcode scanner and an Android device, which is supposed to handle the scanned barcodes. The two devices communicate with each other via BT connection (I got it working). Scanned barcodes are being handled by JsonObjectRequest (also working). Depending on the response (or lack of) from external service, scanner has to react in a certain way:
green/red LED on - beeper - green/red LED off
And here is where I am struggling:
If I have only beeper - everything works. If I have a LED on/off - only LED on works. If I have all 3 actions - none gets executed.
Now, strange thing is, that debugger shows those actions received and executed
D/MainActivity: Barcode Received
I/ViewRootImpl: CPU Rendering VSync enable = false
I/BluetoothScanner: executeCommand started. opcode = DCSSDK_SET_ACTION inXML = <inArgs><scannerID>5</scannerID><cmdArgs><arg-int>45</arg-int></cmdArgs></inArgs>
I/BluetoothScanner: 7 SSI bytes sent: 0x05 0xE7 0x04 0x00 0x04 0xFF 0x0C
I/BluetoothScanner: executeCommand returningDCSSDK_RESULT_SUCCESS
I/ViewRootImpl: CPU Rendering VSync enable = false
I/BluetoothScanner: executeCommand started. opcode = DCSSDK_SET_ACTION inXML = <inArgs><scannerID>5</scannerID><cmdArgs><arg-int>17</arg-int></cmdArgs></inArgs>
I/BluetoothScanner: 7 SSI bytes sent: 0x05 0xE6 0x04 0x00 0x11 0xFF 0x00
I/BluetoothScanner: soundBeeper command write successful. Wait for Status.
executeCommand returningDCSSDK_RESULT_SUCCESS
I/ViewRootImpl: CPU Rendering VSync enable = false
I/BluetoothScanner: executeCommand started. opcode = DCSSDK_SET_ACTION inXML = <inArgs><scannerID>5</scannerID><cmdArgs><arg-int>46</arg-int></cmdArgs></inArgs>
I/BluetoothScanner: 7 SSI bytes sent: 0x05 0xE8 0x04 0x00 0x04 0xFF 0x0B
executeCommand returningDCSSDK_RESULT_SUCCESS
Code, that I am using to construct those requests is based on an example app and documentation provided by Zebra see here the Zebra Android SDK and this is how I am calling those actions:
private class MyAsyncTask extends AsyncTask<String,Integer,Boolean> {
int scannerId;
StringBuilder outXML;
DCSSDKDefs.DCSSDK_COMMAND_OPCODE opcode;
private CustomProgressDialog progressDialog;
public MyAsyncTask(int scannerId, DCSSDKDefs.DCSSDK_COMMAND_OPCODE opcode){
this.scannerId=scannerId;
this.opcode=opcode;
this.outXML = outXML;
}
#Override
protected void onPreExecute() {
super.onPreExecute();
progressDialog = new CustomProgressDialog(MainActivity.this, "Execute Command...");
progressDialog.show();
}
#Override
protected Boolean doInBackground(String... strings) {
return executeCommand(opcode,strings[0],null,scannerId);
}
#Override
protected void onPostExecute(Boolean b) {
super.onPostExecute(b);
if (progressDialog != null && progressDialog.isShowing())
progressDialog.dismiss();
if(!b){
Toast.makeText(MainActivity.this, "Cannot perform the Action", Toast.LENGTH_SHORT).show();
}
}
}
#Override
public boolean executeCommand(DCSSDKDefs.DCSSDK_COMMAND_OPCODE opCode, String inXML, StringBuilder outXML, int scannerID) {
if (Application.sdkHandler != null)
{
if(outXML == null){
outXML = new StringBuilder();
}
DCSSDKDefs.DCSSDK_RESULT result=Application.sdkHandler.dcssdkExecuteCommandOpCodeInXMLForScanner(opCode,inXML,outXML,scannerID);
if(result== DCSSDKDefs.DCSSDK_RESULT.DCSSDK_RESULT_SUCCESS)
return true;
else if(result==DCSSDKDefs.DCSSDK_RESULT.DCSSDK_RESULT_FAILURE)
return false;
}
return false;
}
private final Handler dataHandler = new Handler(new Handler.Callback() {
#Override
public boolean handleMessage(Message msg) {
switch(msg.what){
case Constants.BARCODE_RECEIVED:
Barcode barcode = (Barcode) msg.obj;
sendApiHttpRequest(new String(barcode.getBarcodeData()));
break;
}
return false;
}
});
private void sendApiHttpRequest(String ticketId){
String url = "https://#################################/" + ticketId;
JsonObjectRequest jsonObjectRequest = new JsonObjectRequest(Request.Method.GET, url, null, myJsonListener(), myJsonErrorListener());
// tag the request for ease of debugging
jsonObjectRequest.setTag(TAG);
// Access the RequestQueue through your singleton class.
MySingleton.getInstance(this).addToRequestQueue(jsonObjectRequest);
}
private Response.Listener<JSONObject> myJsonListener() {
return new Response.Listener<JSONObject>() {
#Override
public void onResponse(JSONObject response) {
boolean status;
try {
status = response.getBoolean("status");
if (status){
setScanResultOK();
}else{
setScanResultERR();
}
}catch(JSONException e){
setScanResultERR();
Log.e(TAG, "Failure", e);
}
}
};
}
private Response.ErrorListener myJsonErrorListener() {
return new Response.ErrorListener() {
#Override
public void onErrorResponse(VolleyError error) {
setScanResultERR();
Log.i(TAG, "Error : " + error.getLocalizedMessage());
}
};
}
private void setScanResultOK(){
prepareInXML(RMDAttributes.RMD_ATTR_VALUE_ACTION_LED_GREEN_ON);
prepareInXML(RMDAttributes.RMD_ATTR_VALUE_ACTION_FAST_WARBLE_BEEP);
prepareInXML(RMDAttributes.RMD_ATTR_VALUE_ACTION_LED_GREEN_OFF);
TextView textViewScanResult = findViewById(R.id.txt_scan_result);
textViewScanResult.setText(R.string.scan_res_ok);
textViewScanResult.setTextAppearance(getApplicationContext(), R.style.roboto_medium_96dp_green);
}
private void setScanResultERR(){
prepareInXML(RMDAttributes.RMD_ATTR_VALUE_ACTION_LED_RED_ON);
prepareInXML(RMDAttributes.RMD_ATTR_VALUE_ACTION_LOW_LONG_BEEP_3);
prepareInXML(RMDAttributes.RMD_ATTR_VALUE_ACTION_LED_RED_OFF);
TextView textViewScanResult = findViewById(R.id.txt_scan_result);
textViewScanResult.setText(R.string.scan_res_err);
textViewScanResult.setTextAppearance(getApplicationContext(), R.style.roboto_medium_96dp_red);
}
private void performOpcodeAction(String inXML) {
if (scannerID != -1) {
new MyAsyncTask(scannerID, DCSSDKDefs.DCSSDK_COMMAND_OPCODE.DCSSDK_SET_ACTION).execute(new String[]{inXML});
} else {
Toast.makeText(this, "Invalid scanner ID", Toast.LENGTH_SHORT).show();
}
}
private void prepareInXML(int value){
String inXML = "<inArgs><scannerID>" + scannerID + "</scannerID><cmdArgs><arg-int>" +
value + "</arg-int></cmdArgs></inArgs>";
performOpcodeAction(inXML);
}
When I set up breakpoints and step through the code, all actions are executed and as soon as I run the app, I get those issues.
Can anyone please help me?

Here's what I understood from your code. Your are sending an HTTP request to a server and based on the response, your are going to perform a sequence of events such as LED and sound state toggling.
As a background, Asynctask is used to execute a piece of code on a background thread. In your case, you want to perform the commands. As from the name, they are asynchronous and will run in parallel with your main thread (at least the doInBackground).
In setScanResultOK and setScanResultERR, you are potentially instantiating 3 asynctasks. Only one of them will run as by default, asynctasks run on a single execution thread. If you want to run them altogether, execute them in a thread pool executor.
Now, you mentioned that you want to run them in sequence. I propose to refactor your code as such.
Create 2 asynctasks, 1 for success and 1 for error.
Perform the multiple prepareInXML calls in doInBackground
Instantiate an asynctask based on the response, and execute.
As an example:
#Override
protected Boolean doInBackground(String... strings) {
if (!prepareInXML(RMDAttributes.RMD_ATTR_VALUE_ACTION_LED_GREEN_ON)) {
return false;
}
if (!prepareInXML(RMDAttributes.RMD_ATTR_VALUE_ACTION_FAST_WARBLE_BEEP)) {
return false;
}
return prepareInXML(RMDAttributes.RMD_ATTR_VALUE_ACTION_LED_GREEN_OFF);
}
Of course this will require you to change some function signature to accommodate. Then process all UI changes in onPostExecute.
#Override
protected void onPostExecute(Boolean b) {
super.onPostExecute(b);
if (progressDialog != null && progressDialog.isShowing())
progressDialog.dismiss();
if(!b){
Toast.makeText(MainActivity.this, "Cannot perform the Action", Toast.LENGTH_SHORT).show();
}
/* perform UI changes particular to the type of task (success/fail) */
}
UPDATE:
Try adding a delay in between commands. The no op might be something that is actually a on-sound-off sequence happening real fast for us to notice.

Related

requestNetworkScan - returns invalid cell identity - Android P

I'm developing a privileged system app to scan the network. After executing the API, the results does not contain a valid cell identity information. All values return either as 0, null or max int.
Granted relevant system privileged permissions.
An extract of the code:
public class ScannerActivity extends Activity implements View.OnClickListener {
private final int PHONE_STATE_REQUEST = 1;
private Button scanButton;
private TextView resultsTextView;
private class RadioCallback extends TelephonyScanManager.NetworkScanCallback {
private List<CellInfo> mCellInfoResults;
private int mScanError;
#Override
public void onResults(List<CellInfo> cellInfoResults) {
mCellInfoResults = cellInfoResults;
ScannerActivity.this.runOnUiThread(new Runnable() {
#Override
public void run() {
for (CellInfo cellInfo:mCellInfoResults) {
resultsTextView.append(" " + cellInfo.toString() + " ");
}
}
});
}
#Override
public void onError(int error) {
mScanError = error;
ScannerActivity.this.runOnUiThread(new Runnable() {
#Override
public void run() {
resultsTextView.append(" Error: " + mScanError);
}
});
}
#Override
public void onComplete() {
ScannerActivity.this.runOnUiThread(new Runnable() {
#Override
public void run() {
resultsTextView.append(" Scan Completed! ");
}
});
}
}
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_scanner);
scanButton = (Button) findViewById(R.id.scan_button);
scanButton.setOnClickListener(this);
resultsTextView = (TextView) findViewById(R.id.results_text_view);
}
public void onClick(View view) {
NetworkScanRequest networkScanRequest;
RadioAccessSpecifier radioAccessSpecifiers[];
TelephonyManager telephonyManager = (TelephonyManager) getApplicationContext()
.getSystemService(Context.TELEPHONY_SERVICE);
radioAccessSpecifiers = new RadioAccessSpecifier[1];
radioAccessSpecifiers[0] = new RadioAccessSpecifier(
AccessNetworkConstants.AccessNetworkType.UTRAN,
null,
null);
networkScanRequest = new NetworkScanRequest(
NetworkScanRequest.SCAN_TYPE_ONE_SHOT,
radioAccessSpecifiers,
30,
300,
true,
3,
null);
telephonyManager.requestNetworkScan(networkScanRequest, AsyncTask.SERIAL_EXECUTOR,new RadioCallback());
}
Any idea why this happens?
Tried on Pixel 2.
You can check if radio layer has provided a valid cell identity info in response to your requestNetworkScan or not. Get radio log by cmd "adb logcat -v time -b radio" and check any occurrence of UNSOL_NETWORK_SCAN_RESULT API in this log. Below is the description of this unsolicited response.
/**
* RIL_UNSOL_NETWORK_SCAN_RESULT
*
* Returns incremental result for the network scan which is started by
* RIL_REQUEST_START_NETWORK_SCAN, sent to report results, status, or errors.
*
* "data" is NULL
* "response" is a const RIL_NetworkScanResult *
*/
#define RIL_UNSOL_NETWORK_SCAN_RESULT 1049
Response struct RIL_NetworkScanResult has below fields:
typedef struct {
RIL_ScanStatus status; // The status of the scan
uint32_t network_infos_length; // Total length of RIL_CellInfo
RIL_CellInfo_v12* network_infos; // List of network information
RIL_Errno error;
} RIL_NetworkScanResult;
If this UNSOL_NETWORK_SCAN_RESULT response is either returning NULL struct or no UNSOL_NETWORK_SCAN_RESULT response at all then probably radio HAL is not supporting this API.
The requestNetworkScan has similar functionality to the getAvailableNetworks. These functions are doing high-level network scans to find nearby carriers. The modem is only looking for a set of unique PLMNs (i.e. a carrier identifier) and doesn't dwell on the cells long enough to find more detailed information such as the cell identity.
The RIL should be able to return some basic information about the cell such as the frequency channel (ARFCN for GSM, UARFCN for UMTS, and EARFCN for LTE) and a physical cell identity (BSIC for GSM, PSC for UMTS, PCI for LTE) but it doesn't seem to return any valid information for those values currently.

Programmatically Auto Accept Incoming Bluetooth Files

I am trying to find a way to have a tablet basically auto-accept/give permission to accept an incoming Bluetooth Share file transferred from a laptop to my Nexus Android device. Since the Android bluetooth system itself does not support this feature, I was wondering if this could be done programmatically using an Android application that listened for the notification and accepted it for me.
MUCH EASIER WAY
If you have a rooted device and use XPosed Framework, your goal can be achieved much easier.
You Need not implement your own bluetooth server nor kill the original BT service, which are very bothering!!!
xposed tutorial link.
Try this code.
import android.util.*;
import de.robv.android.xposed.*;
import de.robv.android.xposed.callbacks.XC_LoadPackage.*;
import static de.robv.android.xposed.XposedHelpers.findAndHookMethod;
public class Tutorial implements IXposedHookLoadPackage
{
private String TAG="TUTORIAL";
public void handleLoadPackage(final LoadPackageParam lpparam) throws Throwable {
if (!lpparam.packageName.equals("com.android.bluetooth"))
{
Log.i(TAG,"Not: "+lpparam.packageName);
return;
}
Log.i(TAG,"Yes "+lpparam.packageName);
findAndHookMethod("com.android.bluetooth.opp.BluetoothOppManager", lpparam.classLoader, "isWhitelisted", String.class,new XC_MethodHook() {
#Override
protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
Log.v(TAG,"HOOK DONE");
param.setResult(true); /* you can compare the sender address(String) with your computer and determine if you return true or just allow the original method to be called after this returns.*/
}
});
}
}
I tested and it works fine:)
Links
Dropbox link of the auto accepting app
Dropbox link of the project files (zip)
Xposed apk site
Towelroot site to root your phone
Background(Original answer)
As I commented above, you bay be able to, and I tried and succeeded in blocking (though not receiving) with this code.
import android.util.*;
import de.robv.android.xposed.*;
import de.robv.android.xposed.callbacks.XC_LoadPackage.*;
import java.io.*;
import static de.robv.android.xposed.XposedHelpers.findAndHookMethod;
public class Tutorial implements IXposedHookLoadPackage
{
private String TAG="TUTORIAL";
public void handleLoadPackage(final LoadPackageParam lpparam) throws Throwable {
if (!lpparam.packageName.equals("com.android.bluetooth"))
{
Log.i(TAG,"Not: "+lpparam.packageName);
return;
}
Log.i(TAG,"Yes "+lpparam.packageName);
findAndHookMethod("com.android.bluetooth.opp.BluetoothOppService", lpparam.classLoader, "startSocketListener", new XC_MethodHook() {
#Override
protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
Log.v(TAG,"HOOK DONE");
param.setResult(null);
}
});
}
}
The code above hooks the method startListenerSocket() of com.android.bluetooth.BluetoothOppService and prevents the original method from being called by the line param.setResult(null);
Refer to here to see the full code of com.android.bluetooth.BluetoothOppService.java and you will understand the operation.
And the code you can start from is shown below.
import android.util.*;
import de.robv.android.xposed.*;
import de.robv.android.xposed.callbacks.XC_LoadPackage.*;
import static de.robv.android.xposed.XposedHelpers.findAndHookMethod;
public class Tutorial implements IXposedHookLoadPackage
{
private String TAG="TUTORIAL";
public void handleLoadPackage(final LoadPackageParam lpparam) throws Throwable {
if (!lpparam.packageName.equals("com.android.bluetooth"))
{
Log.i(TAG,"Not: "+lpparam.packageName);
return;
}
Log.i(TAG,"Yes "+lpparam.packageName);
findAndHookMethod("com.android.bluetooth.opp.BluetoothOppObexServerSession", lpparam.classLoader, "onPut", new XC_MethodHook() {
#Override
protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
Log.v(TAG,"HOOK DONE");
Class c=param.thisObject.getClass();
}
});
}
}
This code hooks the onPut method of com.android.bluetooth. BluetoothOppObexServerSession linked here. I either am newbie to xposed framework but I hope my answer helped.
I had the same issues you asked and partially solved the problem by implementing my custom OBEX server and manually / programmatically(with ps|grep and su kill pid) killing the native BluetoothOppService. But I will either try the idea of hooking and directly executing my code.
And to help you customize OBEX server session I post my implementation below.
#Override
public int onPut(Operation op)
{
if (D)
{
Log.d(TAG, "onPut " + op.toString());
}
HeaderSet request;
String name, mimeType;
Long length;
String extension=null;// type;
int obexResponse = ResponseCodes.OBEX_HTTP_OK;
String destination;
if (mTransport instanceof BluetoothObexTransport)
{
destination = ((BluetoothObexTransport) mTransport).getRemoteAddress();
}
else
{
destination = "FF:FF:FF:00:00:00";
}
boolean isWhitelisted =IsWhitelisted(destination);
try
{
boolean preReject = false;
request = op.getReceivedHeader();
if (V)
{
// Constants.logHeader(request);
}
name = (String) request.getHeader(HeaderSet.NAME);
length = (Long) request.getHeader(HeaderSet.LENGTH);
mimeType = (String) request.getHeader(HeaderSet.TYPE);
if (length == 0)
{
if (D)
{
Log.w(TAG, "length is 0, reject the transfer");
}
preReject = true;
obexResponse = ResponseCodes.OBEX_HTTP_LENGTH_REQUIRED;
}
if (name == null || name.isEmpty())
{
if (D)
{
Log.w(TAG, "name is null or empty, reject the transfer");
}
preReject = true;
obexResponse = ResponseCodes.OBEX_HTTP_BAD_REQUEST;
}
int dotIndex = name.lastIndexOf(".");
if (dotIndex > 0)
{
extension = name.substring(dotIndex + 1).toLowerCase();
}
// Reject policy: anything outside the "white list" plus unspecified
// MIME Types. Also reject everything in the "black list".
// if (!preReject && (mimeType == null || (!isWhitelisted && !Constants.mimeTypeMatches(
// mimeType, Constants.ACCEPTABLE_SHARE_INBOUND_TYPES))
// || Constants.mimeTypeMatches(mimeType,
// Constants.UNACCEPTABLE_SHARE_INBOUND_TYPES))) {
// if (D) {
// Log.w(TAG, "mimeType is null or in unacceptable list, reject the transfer");
// }
// preReject = true;
// obexResponse = ResponseCodes.OBEX_HTTP_UNSUPPORTED_TYPE;
// }
if (preReject && obexResponse != ResponseCodes.OBEX_HTTP_OK)
{
// some bad implemented client won't send disconnect
return obexResponse;
}
}
catch (IOException e)
{
Log.e(TAG, "get getReceivedHeaders error " + e);
return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
}
int status = receiveFile(destination, name, extension, length, op);
/*
* TODO map status to obex response code
*/
if (status != BluetoothShare.STATUS_SUCCESS)
{
obexResponse = ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
}
Log.d(TAG, "MIME TYPE)" + mimeType);
return obexResponse;
}
I just removed some rejecting codes from the original one.
Also to look at my full code please refer to my git repository.
I also thank the contributors to the android project!

Socket Dies When Reestablishing Later

I have a client server model where the client runs on android. It establishes its tls sockets using the following code:.
(Everything the client does to login and relogin)
public class LoginAsync extends AsyncTask<Boolean, String, Boolean>
protected Boolean doInBackground(Boolean... params)
{
try
{
//only handle 1 login request at a time
synchronized(loginLock)
{
if(tryingLogin)
{
Utils.logcat(Const.LOGW, tag, "already trying a login. ignoring request");
onPostExecute(false);
return false;
}
tryingLogin = true;
}
//http://stackoverflow.com/a/34228756
//check if server is available first before committing to anything
// otherwise this process will stall. host not available trips timeout exception
Socket diag = new Socket();
diag.connect(new InetSocketAddress(Vars.serverAddress, Vars.commandPort), TIMEOUT);
diag.close();
//send login command
Vars.commandSocket = Utils.mkSocket(Vars.serverAddress, Vars.commandPort, Vars.expectedCertDump);
String login = Utils.currentTimeSeconds() + "|login|" + uname + "|" + passwd;
Vars.commandSocket.getOutputStream().write(login.getBytes());
//read response
byte[] responseRaw = new byte[Const.BUFFERSIZE];
int length = Vars.commandSocket.getInputStream().read(responseRaw);
//on the off chance the socket crapped out right from the get go, now you'll know
if(length < 0)
{
Utils.logcat(Const.LOGE, tag, "Socket closed before a response could be read");
onPostExecute(false);
return false;
}
//there's actual stuff to process, process it!
String loginresp = new String(responseRaw, 0, length);
Utils.logcat(Const.LOGD, tag, loginresp);
//process login response
String[] respContents = loginresp.split("\\|");
if(respContents.length != 4)
{
Utils.logcat(Const.LOGW, tag, "Server response imporoperly formatted");
onPostExecute(false); //not a legitimate server response
return false;
}
if(!(respContents[1].equals("resp") && respContents[2].equals("login")))
{
Utils.logcat(Const.LOGW, tag, "Server response CONTENTS imporperly formated");
onPostExecute(false); //server response doesn't make sense
return false;
}
long ts = Long.valueOf(respContents[0]);
if(!Utils.validTS(ts))
{
Utils.logcat(Const.LOGW, tag, "Server had an unacceptable timestamp");
onPostExecute(false);
return false;
}
Vars.sessionid = Long.valueOf(respContents[3]);
//establish media socket
Vars.mediaSocket = Utils.mkSocket(Vars.serverAddress, Vars.mediaPort, Vars.expectedCertDump);
String associateMedia = Utils.currentTimeSeconds() + "|" + Vars.sessionid;
Vars.mediaSocket.getOutputStream().write(associateMedia.getBytes());
Intent cmdListenerIntent = new Intent(Vars.applicationContext, CmdListener.class);
Vars.applicationContext.startService(cmdListenerIntent);
onPostExecute(true);
return true;
}
catch (CertificateException c)
{
Utils.logcat(Const.LOGE, tag, "server certificate didn't match the expected");
onPostExecute(false);
return false;
}
catch (Exception i)
{
Utils.dumpException(tag, i);
onPostExecute(false);
return false;
}
}
with the mksocket utility function being:
public static Socket mkSocket(String host, int port, final String expected64) throws CertificateException
{
TrustManager[] trustOnlyServerCert = new TrustManager[]
{new X509TrustManager()
{
#Override
public void checkClientTrusted(X509Certificate[] chain, String alg)
{
}
#Override
public void checkServerTrusted(X509Certificate[] chain, String alg) throws CertificateException
{
//Get the certificate encoded as ascii text. Normally a certificate can be opened
// by a text editor anyways.
byte[] serverCertDump = chain[0].getEncoded();
String server64 = Base64.encodeToString(serverCertDump, Base64.NO_PADDING & Base64.NO_WRAP);
//Trim the expected and presented server ceritificate ascii representations to prevent false
// positive of not matching because of randomly appended new lines or tabs or both.
server64 = server64.trim();
String expected64Trimmed = expected64.trim();
if(!expected64Trimmed.equals(server64))
{
throw new CertificateException("Server certificate does not match expected one.");
}
}
#Override
public X509Certificate[] getAcceptedIssuers()
{
return null;
}
}
};
try
{
SSLContext context;
context = SSLContext.getInstance("TLSv1.2");
context.init(new KeyManager[0], trustOnlyServerCert, new SecureRandom());
SSLSocketFactory mkssl = context.getSocketFactory();
Socket socket = mkssl.createSocket(host, port);
socket.setKeepAlive(true);
return socket;
}
catch (Exception e)
{
dumpException(tag, e);
return null;
}
}
Here is the command listener service that gets started on successful login:
public class CmdListener extends IntentService
protected void onHandleIntent(Intent workIntent)
{
// don't want this to catch the login resposne
Utils.logcat(Const.LOGD, tag, "command listener INTENT SERVICE started");
while(inputValid)
{
String logd = ""; //accumulate all the diagnostic message together to prevent multiple entries of diagnostics in log ui just for cmd listener
try
{//the async magic here... it will patiently wait until something comes in
byte[] rawString = new byte[Const.BUFFERSIZE];
int length = Vars.commandSocket.getInputStream().read(rawString);
if(length < 0)
{
throw new Exception("input stream read failed");
}
String fromServer = new String(rawString, 0, length);
String[] respContents = fromServer.split("\\|");
logd = logd + "Server response raw: " + fromServer + "\n";
//check for properly formatted command
if(respContents.length != 4)
{
Utils.logcat(Const.LOGW, tag, "invalid server response");
continue;
}
//verify timestamp
long ts = Long.valueOf(respContents[0]);
if(!Utils.validTS(ts))
{
Utils.logcat(Const.LOGW, tag, "Rejecting server response for bad timestamp");
continue;
}
//just parse and process commands here. not much to see
}
catch (IOException e)
{
Utils.logcat(Const.LOGE, tag, "Command socket closed...");
Utils.dumpException(tag, e);
inputValid = false;
}
catch(NumberFormatException n)
{
Utils.logcat(Const.LOGE, tag, "string --> # error: ");
}
catch(NullPointerException n)
{
Utils.logcat(Const.LOGE, tag, "Command socket null pointer exception");
inputValid = false;
}
catch(Exception e)
{
Utils.logcat(Const.LOGE, tag, "Other exception");
inputValid = false;
}
}
//only 1 case where you don't want to restart the command listener: quitting the app.
//the utils.quit function disables BackgroundManager first before killing the sockets
//that way when this dies, nobody will answer the command listener dead broadcast
Utils.logcat(Const.LOGE, tag, "broadcasting dead command listner");
try
{
Intent deadBroadcast = new Intent(Const.BROADCAST_BK_CMDDEAD);
sendBroadcast(deadBroadcast);
}
catch (Exception e)
{
Utils.logcat(Const.LOGE, tag, "couldn't broadcast dead command listener... leftover broadacast from java socket stupidities?");
Utils.dumpException(tag, e);
}
}
And here is the background manager that signs you in when you switch from wifi to lte, lte to wifi, or when you come out of the subway from nothing to lte:
public class BackgroundManager extends BroadcastReceiver
{
private static final String tag = "BackgroundManager";
#Override
public void onReceive(final Context context, Intent intent)
{
if(Vars.applicationContext == null)
{
//sometimes intents come in when the app is in the process of shutting down so all the contexts won't work.
//it's shutting down anyways. no point of starting something
return;
}
AlarmManager manager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
if(Vars.uname == null || Vars.passwd == null)
{
//if the person hasn't logged in then there's no way to start the command listener
// since you won't have a command socket to listen on
Utils.logcat(Const.LOGW, tag, "user name and password aren't available?");
}
String action = intent.getAction();
if(action.equals(ConnectivityManager.CONNECTIVITY_ACTION))
{
manager.cancel(Vars.pendingRetries);
new KillSocketsAsync().execute();
if(Utils.hasInternet())
{
//internet reconnected case
Utils.logcat(Const.LOGD, tag, "internet was reconnected");
new LoginAsync(Vars.uname, Vars.passwd).execute();
}
else
{
Utils.logcat(Const.LOGD, tag, "android detected internet loss");
}
//command listener does a better of job of figuring when the internet died than android's connectivity manager.
//android's connectivity manager doesn't always get subway internet loss
}
else if (action.equals(Const.BROADCAST_BK_CMDDEAD))
{
String loge = "command listener dead received\n";
//cleanup the pending intents and make sure the old sockets are gone before making new ones
manager.cancel(Vars.pendingRetries);
new KillSocketsAsync().execute(); //make sure everything is good and dead
//all of this just to address the stupid java socket issue where it might just endlessly die/reconnect
//initialize the quick dead count and timestamp if this is the first time
long now = System.currentTimeMillis();
long deadDiff = now - Vars.lastDead;
Vars.lastDead = now;
if(deadDiff < Const.QUICK_DEAD_THRESHOLD)
{
Vars.quickDeadCount++;
loge = loge + "Another quick death (java socket stupidity) occured. Current count: " + Vars.quickDeadCount + "\n";
}
//with the latest quick death, was it 1 too many? if so restart the app
//https://stackoverflow.com/questions/6609414/how-to-programatically-restart-android-app
if(Vars.quickDeadCount == Const.QUICK_DEAD_MAX)
{
loge = loge + "Too many quick deaths (java socket stupidities). Restarting the app\n";
Utils.logcat(Const.LOGE, tag, loge);
//self restart, give it a 5 seconds to quit
Intent selfStart = new Intent(Vars.applicationContext, InitialServer.class);
int pendingSelfId = 999;
PendingIntent selfStartPending = PendingIntent.getActivity(Vars.applicationContext, pendingSelfId, selfStart, PendingIntent.FLAG_CANCEL_CURRENT);
manager.set(AlarmManager.RTC_WAKEUP, System.currentTimeMillis()+Const.RESTART_DELAY, selfStartPending);
//hopefully 5 seconds will be enough to get out
Utils.quit();
return;
}
else
{ //app does not need to restart. still record the accumulated error messages
Utils.logcat(Const.LOGE, tag, loge);
}
//if the network is dead then don't bother
if(!Utils.hasInternet())
{
Utils.logcat(Const.LOGD, tag, "No internet detected from commnad listener dead");
return;
}
new LoginAsync(Vars.uname, Vars.passwd).execute();
}
else if (action.equals(Const.ALARM_ACTION_RETRY))
{
Utils.logcat(Const.LOGD, tag, "login retry received");
//no point of a retry if there is no internet to try on
if(!Utils.hasInternet())
{
Utils.logcat(Const.LOGD, tag, "no internet for sign in retry");
manager.cancel(Vars.pendingRetries);
return;
}
new LoginAsync(Vars.uname, Vars.passwd).execute();
}
else if(action.equals(Const.BROADCAST_LOGIN_BG))
{
boolean ok = intent.getBooleanExtra(Const.BROADCAST_LOGIN_RESULT, false);
Utils.logcat(Const.LOGD, tag, "got login result of: " + ok);
Intent loginResult = new Intent(Const.BROADCAST_LOGIN_FG);
loginResult.putExtra(Const.BROADCAST_LOGIN_RESULT, ok);
context.sendBroadcast(loginResult);
if(!ok)
{
Utils.setExactWakeup(Const.RETRY_FREQ, Vars.pendingRetries);
}
}
}
}
The server is on a select system call to listen to its established sockets. It accepts new sockets using this code (C on Linux)
incomingCmd = accept(cmdFD, (struct sockaddr *) &cli_addr, &clilen);
if(incomingCmd < 0)
{
string error = "accept system call error";
postgres->insertLog(DBLog(Utils::millisNow(), TAG_INCOMINGCMD, error, SELF, ERRORLOG, DONTKNOW, relatedKey));
perror(error.c_str());
goto skipNewCmd;
}
string ip = inet_ntoa(cli_addr.sin_addr);
//setup ssl connection
SSL *connssl = SSL_new(sslcontext);
SSL_set_fd(connssl, incomingCmd);
returnValue = SSL_accept(connssl);
//in case something happened before the incoming connection can be made ssl.
if(returnValue <= 0)
{
string error = "Problem initializing new command tls connection from " + ip;
postgres->insertLog(DBLog(Utils::millisNow(), TAG_INCOMINGCMD, error, SELF, ERRORLOG, ip, relatedKey));
SSL_shutdown(connssl);
SSL_free(connssl);
shutdown(incomingCmd, 2);
close(incomingCmd);
}
else
{
//add the new socket descriptor to the client self balancing tree
string message = "new command socket from " + ip;
postgres->insertLog(DBLog(Utils::millisNow(), TAG_INCOMINGCMD, message, SELF, INBOUNDLOG, ip, relatedKey));
clientssl[incomingCmd] = connssl;
sdinfo[incomingCmd] = SOCKCMD;
failCount[incomingCmd] = 0;
}
The problem I'm having is when the client reconnects to the server from an ip address it has used recently, the socket on the client always seems to die after creation. If I retry again, it dies again. The only way to get it to connect is for the android app to kill and restart itself.
Example: on wifi at home with address 192.168.1.101. Connection ok. Switch to LTE on address 24.157.18.90. Reconnects me to the server ok. Come back home and get 192.168.1.101. The socket always dies until the app kills itself. Or if while I'm outside, I loose LTE because I take the subway, when I come out, I get the same problem. Note that each time, it will make a new socket. It will not somehow try to salvage the old one. The socket creation also seems to succeed. It's just as soon as the client wants to do a read on it, java says the socket is closed.
I put all the relevant code in its unobfuscated original form since it's my hobby project. I am out of ideas why this happens.
// http://stackoverflow.com/a/34228756
//check if server is available first before committing to anything
// otherwise this process will stall. host not available trips timeout exception
Socket diag = new Socket();
diag.connect(new InetSocketAddress(Vars.serverAddress, Vars.commandPort), TIMEOUT);
diag.close();
It is caused by these three pointless lines of code. The server gets a connection and an immediate read() result of zero.
There is no value in establishing a connection only to close it and then assume you can open another one. You should use the conection you just established. In general the correct way to establish whether any resource is available is to try to use it in the normal way. Techniques like the above are indistinguishable from attempts to predict the future.

Android Google Saved Games unexpected conflicts

In my game I have in-game currency and I want to save its value to the cloud. I decided to use Google Saved Games API. Everything works great but when I'm saving data to the Snapshots and then reading it when the game launches again, I'm getting conflicts, even when I'm on the same device. Now I'm saving currency's state after every change, so when player spents or gets some "coins". I'm thinking that this could be very often and services can't handle it because when I'm offline (without connection to the network) everything works nice and fast but when I'm online (connected to Wi-fi) work with Snapshots is slower and as I said I'm getting conflicts with data last saved and previous data I saved (I'm loggging all values...). Sometimes I get even 5 conflicts. I have 3 functions to work with Saved Games. One for reading data, one for saving data and one for checking for conflicts:
Reading data:
private void readSavedGame(final String snapshotName) {
AsyncTask<Void, Void, Boolean> readingTask = new AsyncTask<Void, Void, Boolean>() {
#Override
protected Boolean doInBackground(Void... params) {
Snapshots.OpenSnapshotResult result = Games.Snapshots.open(mGoogleApiClient, snapshotName, false).await();
Snapshot snapshot = processSnapshotOpenResult(result, 0);
if(snapshot != null) {
try {
updateGameData(snapshot.getSnapshotContents().readFully());
Log.d(TAG, "Updating game: "+String.valueOf(coins)+"...");
return true;
} catch (IOException e) {
Log.d(TAG, "Error: " + e.getMessage());
}
}
return false;
}
#Override
protected void onPostExecute(Boolean result) {
super.onPostExecute(result);
if(result) Log.d(TAG, "Game state read successfully...");
else Log.d(TAG, "Error while reading game state...");
updateUi();
}
};
readingTask.execute();
}
Saving data:
private void writeSavedGame(final String snapshotName, final byte[] data) {
AsyncTask<Void, Void, Boolean> updateTask = new AsyncTask<Void, Void, Boolean>() {
#Override
protected Boolean doInBackground(Void... params) {
Snapshots.OpenSnapshotResult result = Games.Snapshots.open(
mGoogleApiClient, snapshotName, false).await();
Snapshot snapshot = processSnapshotOpenResult(result, 0);
if(snapshot != null) {
snapshot.getSnapshotContents().writeBytes(getGameData());
Log.d(TAG, "Saving: "+String.valueOf(coins)+"...");
Snapshots.CommitSnapshotResult commitSnapshotResult = Games.Snapshots.commitAndClose(mGoogleApiClient, snapshot, SnapshotMetadataChange.EMPTY_CHANGE).await();
if(commitSnapshotResult.getStatus().isSuccess()) return true;
}
return false;
}
#Override
protected void onPostExecute(Boolean result) {
if (result) Log.d(TAG, "Game was saved successfully....");
else Log.d(TAG, "Error while saving game state...");
}
};
updateTask.execute();
}
Checking for conflicts or handling OpenSnapshotResult
Snapshot processSnapshotOpenResult(Snapshots.OpenSnapshotResult result, int retryCount) {
Snapshot mResolvedSnapshot = null;
retryCount++;
int status = result.getStatus().getStatusCode();
Log.i(TAG, "Save Result status: " + status);
if (status == GamesStatusCodes.STATUS_OK) {
Log.d(TAG, "No conflict, SNAPSHOT is OK");
return result.getSnapshot();
} else if (status == GamesStatusCodes.STATUS_SNAPSHOT_CONTENTS_UNAVAILABLE) {
return result.getSnapshot();
}
else if (status == GamesStatusCodes.STATUS_SNAPSHOT_CONFLICT) {
Log.d(TAG, "Conflict: "+String.valueOf(retryCount));
Snapshot snapshot = result.getSnapshot();
Snapshot conflictSnapshot = result.getConflictingSnapshot();
// Resolve between conflicts by selecting the newest of the conflicting snapshots.
mResolvedSnapshot = snapshot;
if (snapshot.getMetadata().getLastModifiedTimestamp() <
conflictSnapshot.getMetadata().getLastModifiedTimestamp()) {
mResolvedSnapshot = conflictSnapshot;
}
try {
Log.d(TAG, "Snapshot data: "+new String(snapshot.getSnapshotContents().readFully()));
Log.d(TAG, "Conflicting data: "+new String(conflictSnapshot.getSnapshotContents().readFully()));
} catch (IOException e) {
Log.e(TAG, "ERROR WHILE READING SPAPSHOTS CONTENTS...");
}
Snapshots.OpenSnapshotResult resolveResult = Games.Snapshots.resolveConflict(
mGoogleApiClient, result.getConflictId(), mResolvedSnapshot).await();
if (retryCount < MAX_SNAPSHOT_RESOLVE_RETRIES) {
// Recursively attempt again
return processSnapshotOpenResult(resolveResult, retryCount);
} else {
// Failed, log error and show Toast to the user
String message = "Could not resolve snapshot conflicts";
Log.e(TAG, message);
//Toast.makeText(getBaseContext(), message, Toast.LENGTH_LONG).show();
}
}
// Fail, return null.
return null;
}
Conflict principes are explained nicely here.
My code is based on official docs implementations and samples.
So when offline, everything works excellent but when connected, I'm getting conflicts on the same device... Maybe I'm updating my saved game very often and services can't handle it. Any ideas? Thanks.
As discussed in Saved Games - Conflict resolution
Typically, data conflicts occur when an instance of your application is unable to reach the Saved Games service while attempting to load data or save it. In general, the best way to avoid data conflicts is to always load the latest data from the service when your application starts up or resumes, and save data to the service with reasonable frequency.
In addition to that, it is also recommended to follow Best practices for implementing saved games to deliver the best possible product to your players.
Also, to learn how to implement Saved Games for your platform, see the resources given in Client implementations.

SignalR HTTP status 400 multiple clients

I'm running an application with SignalR 2.2.0 on server side and signalr-java-client (self compiled, last GitHub version) on Android as client.
Currently, there are 4 clients connected to my hub. From time to time, it happens, that all 4 clients simultaneously receive the HTTP status 400 with the message "The connection id is in the incorrect format" (the clients were connected before). I analyzed this multiple times and am not able to find any information/pattern when or why this happens.
The connecten is secured via JWT, the token is definitely valid. When retrieving a new token, the connection is stopped and started again. Apart from this, it is very unlikely that the error is device-related, because the error is thrown at all 4 clients the same time.
I know, this error can occur when the client's Identity changes, but an Identity change for 4 clients the same time seems very unlikely to me.
This is the server-code used for authentication (Deepak asked).
The following method gets called in my Startup.cs:
public static void ConfigureOAuth(IAppBuilder app, string audienceID, string sharedSecret)
{
byte[] secret = TextEncodings.Base64Url.Decode(sharedSecret);
app.UseJwtBearerAuthentication(
new JwtBearerAuthenticationOptions
{
Provider = new MyOAuthBearerAuthenticationProvider(),
AuthenticationMode = AuthenticationMode.Active,
AllowedAudiences = new[] { audienceID },
IssuerSecurityTokenProviders = new IIssuerSecurityTokenProvider[]
{
new SymmetricKeyIssuerSecurityTokenProvider(Issuer, secret)
}
});
}
Here's the code of MyOAuthBearerAuthenticationProvider class:
class MyOAuthBearerAuthenticationProvider : OAuthBearerAuthenticationProvider
{
/// <summary>
/// Get's a JWT from querysting and puts it to context
/// </summary>
public override Task RequestToken(OAuthRequestTokenContext context)
{
if (context.Token == null)
{
string value = context.Request.Query.Get("auth_token");
if (!string.IsNullOrEmpty(value)) //token from queryString
{
context.Token = value;
}
}
return Task.FromResult<object>(null);
}
}
I have to retrieve the token from query string, because additionally to the java-client, a javascript client is used, which is not able to set headers.
Lastly, I secure my hub and some of it's methods with the Authorization attribute:
[Authorize(Roles = "MyExampleRole")]
This is the client-code for connection:
public boolean connect(String url, String token) {
if (connected) {
return true;
}
try {
this.hubConnection = new HubConnection(url, "auth_token=" + token, true, logger);
this.hubProxy = hubConnection.createHubProxy("MyHub");
this.hubProxy.subscribe(this.signalRMethodProvider);
this.hubConnection.stateChanged(stateChangedCallback);
SignalRFuture<Void> awaitConnection = this.hubConnection.start();
awaitConnection.get(10000, TimeUnit.MILLISECONDS);
return true;
}
catch (InterruptedException | TimeoutException | ExecutionException e) {
log.error("connect", e);
return false;
}
}
Does anybody have an Idea, how to fix this problem or where I may receive further information?
Thank you very much
-Lukas
seems fine...
possible alteration you can do is change
awaitConnection.get(10000, TimeUnit.MILLISECONDS);
to
awaitConnection.done(new Action<Void>() {
#Override
public void run(Void obj) throws Exception {
Log.d(TAG, "Hub Connected");
}
}).onError(new ErrorCallback() {
#Override
public void onError(Throwable error) {
error.printStackTrace();
Log.d(TAG, "SignalRServiceHub Cancelled");
}
}).onCancelled(new Runnable() {
#Override
public void run() {
Log.d(TAG, "SignalRServiceHub Cancelled");
}
});

Categories

Resources