Android location tracker crashes after a long time - android

I have created a location tracking app, which writes to a local SQLite database every time the location is changed.
The app unfortunately crashes after about 7-8 hours while tracking, unfortunately this does not happen when I have connected the device to a debugger, so there is no log which I can attach.
Some more maybe useful information:
The app crashes before it is waken up from the background(can see that clearly in the tracked data), so I can exclude this bug from other apps
Tried to write into textfiles instead of the database without any success(Just ran about 3 hours before crashing)
Changing tracking interval(5s normal 1s fastest interval): Same result app crashes also after 7-8 hours
Here are some code snippets:
Location change event
#Override
public void onLocationChanged(Location location) {
if(location == null){
location = LocationServices.FusedLocationApi.getLastLocation(mGoogleApiClient);
if(location == null) {
return;
}
}
Log.d(TAG, location.toString());
double currentLatitude = location.getLatitude();
double currentLongitude = location.getLongitude();
ActivitylogRepo activityRepo = new ActivitylogRepo(this);
Activitylog activity = new Activitylog();
activity.activity = "Position";
activity.timestamp = getDateTime();
activity.value2 = String.valueOf(currentLatitude);
activity.value3 = String.valueOf(currentLongitude);
activityRepo.insert(activity);
}
Database insert command
public int insert(Activitylog activitylog) {
//Open connection to write data
SQLiteDatabase db = dbHelper.getWritableDatabase();
ContentValues values = new ContentValues();
values.put(Activitylog.KEY_activity, activitylog.activity);
values.put(Activitylog.KEY_timestamp, activitylog.timestamp);
values.put(Activitylog.KEY_value1, activitylog.value1);
values.put(Activitylog.KEY_value2, activitylog.value2);
values.put(Activitylog.KEY_value3, activitylog.value3);
values.put(Activitylog.KEY_value4, activitylog.value4);
// Inserting Row
long activitylog_id = db.insert(Activitylog.TABLE, null, values);
db.close(); // Closing database connection
return (int) activitylog_id;
}
Initializing service
mGoogleApiClient = new GoogleApiClient.Builder(this)
.addConnectionCallbacks(this)
.addOnConnectionFailedListener(this)
.addApi(LocationServices.API)
.build();
mGoogleApiClient.connect();
mLocationRequest = LocationRequest.create()
.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY)
.setInterval(20 * 1000) // 20s in ms
.setFastestInterval(5 * 1000); // 5s in ms

I have something that will help you to catch your crash report even when you're not attached to the debugger - thus helping you to get to the root of the problem! I've posted this as an answer so I can format it nicely.
The class below will handle uncaught exceptions, packing them up into an email and placing a notification on your phone (you can adjust this to whatever you need)
public class UncaughtExceptionHandler implements Thread.UncaughtExceptionHandler {
private Context mContext;
private java.lang.Thread.UncaughtExceptionHandler mDefaultUEH;
public UncaughtExceptionHandler(Context context, java.lang.Thread.UncaughtExceptionHandler defaultUEH) {
mContext = context;
mDefaultUEH = defaultUEH;
}
#Override
public void uncaughtException(Thread thread, Throwable ex) {
// Make error into something more readable
String timestamp = android.text.format.DateFormat.getLongDateFormat(mContext).format(
new Date(System.currentTimeMillis()));
final Writer result = new StringWriter();
final PrintWriter printWriter = new PrintWriter(result);
ex.printStackTrace(printWriter);
String stacktrace = result.toString();
printWriter.close();
// Create email intent for error
Intent emailIntent = new Intent(Intent.ACTION_SEND);
emailIntent.setType("text/html");
emailIntent.putExtra(Intent.EXTRA_EMAIL, "email address");
emailIntent.putExtra(Intent.EXTRA_SUBJECT, "Crash Report " + timestamp);
emailIntent.putExtra(Intent.EXTRA_TEXT, stacktrace);
// Make into pending intent for notifcation
PendingIntent pendingIntent = PendingIntent.getActivity(mContext, 0,
Intent.createChooser(emailIntent, "Send report with.."),
Intent.FLAG_ACTIVITY_NEW_TASK);
// here create a notification for the user
NotificationCompat.Builder builder = new NotificationCompat.Builder(mContext);
builder.setContentTitle("Crash Caught");
builder.setContentText("Send to Developer");
builder.setContentIntent(pendingIntent);
builder.setAutoCancel(true);
builder.setSmallIcon(R.drawable.ic_notification);
// Finally display the notification!
NotificationManager mNotificationManager = (NotificationManager) mContext
.getSystemService(Context.NOTIFICATION_SERVICE);
mNotificationManager.notify(1337, builder.build());
// re-throw critical exception further to the os (important)
mDefaultUEH.uncaughtException(thread, ex);
}
}
Set this up in your Application class like this:
public class App extends Application {
#Override
public void onCreate() {
super.onCreate();
/*
* Set up uncaught exception handler
*/
java.lang.Thread.UncaughtExceptionHandler defaultUEH =
Thread.getDefaultUncaughtExceptionHandler();
Thread.setDefaultUncaughtExceptionHandler(new
UncaughtExceptionHandler(this, defaultUEH));
}
}
I don't recommend including this code in your release version! At that point, you can use the Developer Console to get crash reports.

Related

Android Target API 31 best way to run a service in background that ALWAYS keep listening to my messaging server (RabbitMQ)

I am trying to create a notification service in my android app that always keeps listening to my RabbitMQ server for new messages. I want it to be able to send notifications even from background. Basically I am trying to create a notification communication between two client side application (App1 and App2) through Rabbit MQ and send notifications to both the apps in case of an event.
I have implemented it using JOB Service class but it is not consistent and it stops after sometime. Can someone please help me in understanding the architecture in a better way. How can I achieve something like Firebase Messaging Service but through Rabbit MQ?
Sample code that I have used below:
public class StoreOrderJobService extends JobService {
private static final String TAG = "JobService";
Random random = new Random();
SharedPrefManager prefManager;
private boolean jobCancelled = false;
#Override
public boolean onStartJob(JobParameters jobParameters) {
Log.d(TAG, "Job Started");
prefManager = new SharedPrefManager(this);
subscribeStore(prefManager.getUserId(), jobParameters);
return true;
}
private void subscribeStore(String storeId, JobParameters parameters) {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost(HOST_IP);
factory.setAutomaticRecoveryEnabled(false);
String queueName = prefManager.getSessionId();
if (queueName != null) {
Thread subscribeStoreThread = new Thread(new Runnable() {
#Override
public void run() {
Log.d(TAG, "Job Started");
try {
if (jobCancelled) {
return;
}
Connection connection = factory.newConnection();
Channel channel = connection.createChannel();
Log.d("OrderService", "Session Id " + queueName);
channel.queueDeclare(queueName, false, false, false, null);
channel.queueBind(queueName, "store_test", storeId);
DeliverCallback deliverCallback = (consumerTag, delivery) -> {
String message = new String(delivery.getBody(), "UTF-8");
Log.d("OrderService", "Received message " + message);
Envelope envelope = delivery.getEnvelope();
String routingKey = envelope.getRoutingKey();
if (routingKey.equals(storeId)) {
channel.basicAck(envelope.getDeliveryTag(), true);
String message_new = new String(delivery.getBody(), "UTF-8");
Gson gson = new Gson();
OrderSubscribePayload payload = gson.fromJson(message_new, OrderSubscribePayload.class);
Log.d("order Service", "Order Id " + payload.getOrderId());
sendOrderNotification(random.nextInt(), payload);
}
};
channel.basicConsume(queueName, false, deliverCallback, consumerTag -> {
});
} catch (TimeoutException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (RuntimeException e) {
e.printStackTrace();
}
}
});
subscribeStoreThread.start();
}
}
private void sendOrderNotification(int id, OrderSubscribePayload payload) {
Log.d("Service", "sendOrderNotification " + payload.getOrderId());
Intent contextIntent = new Intent(this, OrderDetails.class);
Bundle args = new Bundle();
args.putSerializable("orderDetails", (Serializable) payload);
contextIntent.putExtra("Bundle", args);
int iUniqueId = (int) (System.currentTimeMillis() & 0xfffffff);
PendingIntent pIntent = PendingIntent.getActivity(this, iUniqueId, contextIntent, 0);
Notification n = new NotificationCompat.Builder(this, ManagedApplication.CHANNEL_ORDER_ID)
.setContentTitle("New Order")
.setContentText("Received New Order")
.setSmallIcon(R.drawable.ic_stat_name)
.setContentIntent(pIntent)
.setAutoCancel(true)
.setOnlyAlertOnce(true)
.setColor(getResources().getColor(R.color.color_primary))
.setCategory(NotificationCompat.CATEGORY_REMINDER)
.build();
NotificationManager notificationManager =
(NotificationManager) getSystemService(Service.NOTIFICATION_SERVICE);
notificationManager.notify(id, n);
}
#Override
public boolean onStopJob(JobParameters jobParameters) {
Log.d(TAG, "Job Cancelled");
jobCancelled = true;
return true;
}
}
I am calling this job on users login as below:
private void startNotificationJob() {
ComponentName componentName = new ComponentName(this, StoreOrderJobService.class);
JobInfo info = new JobInfo.Builder(123, componentName)
.setPersisted(true)
.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)
.setPeriodic(15 * 60 * 1000)
.build();
JobScheduler jobScheduler = (JobScheduler) getSystemService(JOB_SCHEDULER_SERVICE);
int result = jobScheduler.schedule(info);
if (result == JobScheduler.RESULT_SUCCESS) {
Log.d("JOB Scheduler", "Job Scheduled");
} else Log.d("JOB Scheduler", "Job Scheduled Failed");
}
I have implemented it using JOB Service class but it is not consistent and it stops after sometime.
Nothing will be keeping your process running, which you will need for your desired functionality. Nothing will be keeping the CPU powered on all the time, which also will be needed for your desired functionality.
Please bear in mind that what you want is rather harmful to the battery life.
How can I achieve something like Firebase Messaging Service but through Rabbit MQ?
Use a foreground service, with a continuous partial WakeLock, in a separate process (android:process manifest attribute) than the UI of your app. You may also need code to deal with re-establishing your MQ connection as the device changes connectivity (e.g., from WiFi to mobile data) or loses and then regains connectivity.
You will also need to ask your users to go into the Settings app and try to opt your app out of all battery optimizations. Note that this will not be possible on all devices.
You will also need to ask your users to never kill your app's task. The separate process may help on some devices if users forget, but that behavior seems to vary by device.
And you will need to take into account that your solution will not be reliable, because some device manufacturers to prevent developers from doing the sorts of things that you want to do, as those things are bad for battery life.

How to log the user activity for a certain duration after an exception occurs in android java?

I am trying to log the exception plus the user navigation for x amount of time. For example, when my application got an exception I will append it in a text file. Now from that point of time of time, I need to log only for a certain time. e.g., 1 hour. Is it possible to do it? This is the code I wrote to get the exception information and log it in a file.
Please someone help me with this. Thanks in advance.
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Thread.setDefaultUncaughtExceptionHandler(handleAppCrash);
}
private Thread.UncaughtExceptionHandler handleAppCrash =
new Thread.UncaughtExceptionHandler() {
#SuppressLint("LongLogTag")
#Override
public void uncaughtException(Thread thread, Throwable ex) {
if (!file.exists()) {
file.mkdir();
}
try {
data = android_version + "#" + Device + "#" + username + "#" + version + "#" + dates + "#" + Logtrace;
File gpxfile = new File(file, fname);
FileWriter writer = new FileWriter(gpxfile,true);
writer.append(data);
writer.flush();
writer.close();
} catch (
Exception e) {
e.printStackTrace();
}
}
};
You are looking for a way to
restart the app
write more logs to the same file that you created in the UncaughtExceptionHandler.
Firstly, to restart the app, you can take the following steps:
Created a pending intent, e.g., in your onCreate (where Intent intent has class scope, already defined, i.e., not just defined within onCreate):
intent = PendingIntent.getActivity(
YourApplication.getInstance().getBaseContext(),
0,
new Intent(getIntent()),
getIntent().getFlags());
After your try/catch in your UncaughtExceptionHandler, start an alarm to trigger your app in some amount of time, e.g., 1 second; and you must follow this with a System.exit();. This is so the current dying app will properly quit, so that in 1 second, when the alarm triggers, it will start the app again (but it won't if the app is still running).
AlarmManager mgr = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
mgr.set(AlarmManager.RTC, System.currentTimeMillis() + 1000, intent);
System.exit(2);
You could optionally store the name of the file (fname in your code) to SharedPreferences (for writing to the same file when the app restarts). Or it could be a fixed hardcoded name that your app knows, and doesn't need to save the name.
You could saved a Boolean in SharedPreferences to let your app know that you are restarting from an uncaught exception.
Secondly, whenever the app starts:
Check the Boolean in SharedPreferences; if it is a normal start, proceed like normal.
If it is a restart after uncaught exception, then retrieve the file name from SharedPreferences (or get it hard coded), then you can write more logs to the file. As in Android, a file is uniquely determined by path and file name. With the same file name, you can open the same file.
Define a background service
To do anything in the background for a prolonged time you should use a service. I'm using a JobService in this example.
<service android:name="org.example.LogService"
android:exported="false"
android:permission="android.permission.BIND_JOB_SERVICE"
android:process=":logprocess"/>
Note the process tag, it is important, because you'll want to kill your current process after an uncaught exception.
public class LogService extends JobService {
private Thread thread;
#Override
public boolean onStartJob(final JobParameters params) {
if (!file.exists()) {
file.mkdir();
}
thread = new Thread(() -> {
try {
data = params.getExtras().getString("data");
File gpxfile = new File(file, fname);
FileWriter writer = new FileWriter(gpxfile,true);
writer.append(data);
//continue to write your logs to the file here for as long as you want. you could copy logcat to the file for example.
writer.flush();
writer.close();
} catch (Exception e) {
e.printStackTrace();
}
jobFinished(params, false);
});
thread.start();
return true;
}
#Override
public boolean onStopJob(JobParameters params) {
thread.interrupt();
return true;
}
}
Call your background service
public void uncaughtException(Thread thread, Throwable ex) {
JobScheduler scheduler = (JobScheduler) context.getSystemService(Context.JOB_SCHEDULER_SERVICE);
PersistableBundle extras = new PersistableBundle();
extras.putString("data", android_version + "#" + Device + "#" + username + "#" + version + "#" + dates + "#" + Logtrace);
JobInfo.Builder builder = new JobInfo.Builder(0, new ComponentName(context, LogService.class))
builder.setExtras(extras);
builder.setOverrideDeadline(1000);
scheduler.schedule(builder.build());
//stop the current process.
Process.killProcess(Process.myPid());
System.exit(10);
}
Killing the current process is optional but recommended, the application could be in a bad state after an uncaught exception. If you want to restart your application you can call startActivity from the LogService.
Note:
You've not given any details on what exactly you want to log for x amount of time, so this code only has a comment where the log collection goes.
Note 2:
This code was adapted from the ACRA project of which I am a maintainer, specifically manifest JobSenderService DefaultSenderScheduler and ProcessFinisher

PJSUA2 Android - Incoming calls drop after 32 seconds

I'm building a PJSUA2 (PJSIP 2.8) Android app and I have some issues: i.e. only on incoming call, call state remains in "PJSIP_INV_STATE_CONNECTING" and after 32 seconds the call drops.
I'm looking for the cause of the issue since several days, I googled a lot and all what I found is: in most situations this issue is related to NAT management or network issues related to NAT. In a few words: in most cases the called party does not receive the ACK after answering the call.
Finally I was able to log all SIP messages between my app and the SIP server and found that my app receives the ACK from the server, so I suppose it's not a network related issue.
I compiled PJSIP 2.8 with OpenSSL and SRTP support, but without video support (I don't need it at least at the moment). If it makes any difference, the app has a target version 28 and minimum SDK version 19.
I tried several apps on the market and they work fine enough with and without SRTP and with all signaling transports (UDP, TCP, TLS), WebRTC works fine too (tested with SipML5), so I would exclude a server misconfiguration. My app does the same (except SRTP with which I have some issues at the moment).
I tried with a SIP provider too (MessageNet) using UDP and the behaviour is always the same. I tried to use compact SIP messages and it behaves the same, with and without uri parameters, with and without STUN and or ICE and nothing changes. Mobile network and WiFi networks give the same results.
I tried to debug inside PJSIP library too, but without any success, then I tried to follow the code, to understand what I was doing wrong, but it doesn't seem to me there is something evidently wrong.
The following is the code (last version) which initializes PJSIP:
public class SipService extends Service {
private Looper serviceLooper;
private ServiceHandler serviceHandler;
private final Messenger mMessenger = new Messenger(new IncomingHandler());
private LocalBroadcastManager localBroadcast;
private LifecycleBroadcastReceiver lifecycleBroadcastReceiver;
private boolean lastCheckConnected;
private Endpoint endpoint;
private LogWriter logWriter;
private EpConfig epConfig;
private final List<ManagedSipAccount> accounts = new ArrayList<>();
private final Map<String, Messenger> eventRegistrations = new HashMap<>();
#TargetApi(Build.VERSION_CODES.N)
#Override
public void onCreate() {
super.onCreate();
String userAgent = "MyApp";
try {
PackageInfo pInfo = getPackageManager().getPackageInfo(getPackageName(), 0);
String appLabel = (pInfo.applicationInfo.labelRes == 0 ? pInfo.applicationInfo.nonLocalizedLabel.toString() : getString(pInfo.applicationInfo.labelRes));
userAgent = appLabel + "/" + pInfo.versionName;
} catch (PackageManager.NameNotFoundException e) {
Log.e("SipService", "Unable to get app version", e);
}
try {
endpoint = new MyAppEndpoint();
endpoint.libCreate();
epConfig = new EpConfig();
// Logging
logWriter = new PJSIPToAndroidLogWriter();
epConfig.getLogConfig().setWriter(logWriter);
epConfig.getLogConfig().setLevel(5);
// UA
epConfig.getUaConfig().setMaxCalls(4);
epConfig.getUaConfig().setUserAgent(userAgent);
// STUN
StringVector stunServer = new StringVector();
stunServer.add("stun.pjsip.org");
epConfig.getUaConfig().setStunServer(stunServer);
// General Media
epConfig.getMedConfig().setSndClockRate(16000);
endpoint.libInit(epConfig);
// UDP transport
TransportConfig udpCfg = new TransportConfig();
udpCfg.setQosType(pj_qos_type.PJ_QOS_TYPE_VOICE);
endpoint.transportCreate(pjsip_transport_type_e.PJSIP_TRANSPORT_UDP, udpCfg);
// TCP transport
TransportConfig tcpCfg = new TransportConfig();
//tcpCfg.setPort(5060);
endpoint.transportCreate(pjsip_transport_type_e.PJSIP_TRANSPORT_TCP, tcpCfg);
// TLS transport
TransportConfig tlsCfg = new TransportConfig();
endpoint.transportCreate(pjsip_transport_type_e.PJSIP_TRANSPORT_TLS, tlsCfg);
endpoint.libStart();
} catch (Exception e) {
throw new RuntimeException("Unable to initialize and start PJSIP", e);
}
ConnectivityManager cm = (ConnectivityManager)getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo activeNetwork = cm.getActiveNetworkInfo();
lastCheckConnected = activeNetwork != null && activeNetwork.isConnected();
updateForegroundNotification();
startForeground(MyAppConstants.N_FOREGROUND_NOTIFICATION_ID, buildForegroundNotification());
localBroadcast = LocalBroadcastManager.getInstance(this);
HandlerThread thread = new HandlerThread("ServiceStartArguments",
Process.THREAD_PRIORITY_BACKGROUND);
thread.start();
// Get the HandlerThread's Looper and use it for our Handler
serviceLooper = thread.getLooper();
serviceHandler = new ServiceHandler(serviceLooper);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
// Register LifeCycleBroadcastReceiver to receive network change notification
// It seems it's mandatory to do it programmatically since Android N (24)
lifecycleBroadcastReceiver = new LifecycleBroadcastReceiver();
IntentFilter intentFilter = new IntentFilter("android.net.conn.CONNECTIVITY_CHANGE");
registerReceiver(lifecycleBroadcastReceiver, intentFilter);
}
// Initialization
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
if (prefs != null) {
try {
CodecInfoVector codecs = endpoint.codecEnum();
SharedPreferences.Editor editor = prefs.edit();
for (int i = 0; i < codecs.size(); i++) {
CodecInfo codec = codecs.get(i);
int priority = prefs.getInt("codecs.audio{" + codec.getCodecId() + "}", 0);
try {
endpoint.codecSetPriority(codec.getCodecId(), (short) priority);
codec.setPriority((short) priority);
} catch (Exception e) {
Log.e("SipService", "Unexpected error setting codec priority for codec " + codec.getCodecId(), e);
}
}
} catch (Exception e) {
Log.e("SipService", "Unexpected error loading codecs priorities", e);
}
}
}
#Override
public void onDestroy() {
for (Account acc : accounts) {
acc.delete();
}
accounts.clear();
try {
endpoint.libDestroy();
} catch (Exception e) {
e.printStackTrace();
}
endpoint.delete();
endpoint = null;
epConfig = null;
if (lifecycleBroadcastReceiver != null) {
unregisterReceiver(lifecycleBroadcastReceiver);
}
super.onDestroy();
}
.......
}
And the following is my Account class with creation and registration code:
public class ManagedSipAccount extends Account {
public final String TAG;
private final VoipAccount account;
private final PhoneAccountHandle handle;
private final SipService service;
private final AccountStatus status;
private final Map<Integer, VoipCall> calls = new HashMap<>();
private final Map<String, VoipBuddy> buddies = new HashMap<>();
private AccountConfig acfg;
private List<SrtpCrypto> srtpCryptos = new ArrayList<>();
private AuthCredInfo authCredInfo;
public ManagedSipAccount(SipService service, VoipAccount account, PhoneAccountHandle handle) {
super();
TAG = "ManagedSipAccount/" + account.getId();
this.service = service;
this.account = account;
this.handle = handle;
this.status = new AccountStatus(account.getUserName() + "#" + account.getHost());
acfg = new AccountConfig();
}
public void register(Map<String, String> contactParameters) throws Exception {
StringBuilder contactBuilder = new StringBuilder();
for (Map.Entry<String, String> entry : contactParameters.entrySet()) {
contactBuilder.append(';');
contactBuilder.append(URLEncoder.encode(entry.getKey(), "UTF-8"));
contactBuilder.append("=\"");
contactBuilder.append(URLEncoder.encode(entry.getValue(), "UTF-8"));
contactBuilder.append("\"");
}
StringBuilder logBuilder = new StringBuilder();
logBuilder.append("Registering: ");
logBuilder.append(account.getProtocol().name());
/*logBuilder.append('(');
logBuilder.append(service.getTransport(account.getProtocol()));
logBuilder.append(')');*/
if (account.isEncryptionSRTP()) {
logBuilder.append(" SRTP");
}
if (account.isIce()) {
logBuilder.append(" ICE");
}
Log.d(TAG, logBuilder.toString());
String idUri = "sip:" + account.getUserName();
if (!"*".equals(account.getRealm())) {
idUri += "#" + account.getRealm();
}
else {
idUri += "#127.0.0.1" /*+ account.getHost()*/;
}
acfg.setIdUri(idUri);
acfg.getRegConfig().setRegistrarUri("sip:" + account.getHost() + ":" + account.getPort() + ";transport=" + account.getProtocol().name().toLowerCase());
acfg.getRegConfig().setRetryIntervalSec(account.getRetryInterval());
acfg.getRegConfig().setRegisterOnAdd(false);
acfg.getSipConfig().setContactUriParams(contactBuilder.toString());
// NAT management
acfg.getNatConfig().setSipStunUse(pjsua_stun_use.PJSUA_STUN_USE_DEFAULT);
if (account.isIce()) {
acfg.getNatConfig().setIceEnabled(true);
acfg.getNatConfig().setIceAlwaysUpdate(true);
acfg.getNatConfig().setIceAggressiveNomination(true);
}
else {
acfg.getNatConfig().setSdpNatRewriteUse(1);
}
acfg.getMediaConfig().getTransportConfig().setQosType(pj_qos_type.PJ_QOS_TYPE_VOICE);
if (account.isEncryptionSRTP()) {
acfg.getMediaConfig().setSrtpUse(pjmedia_srtp_use.PJMEDIA_SRTP_MANDATORY);
acfg.getMediaConfig().setSrtpSecureSignaling(0);
//acfg.getMediaConfig().getSrtpOpt().setKeyings(new IntVector(2));
acfg.getMediaConfig().getSrtpOpt().getKeyings().clear();
acfg.getMediaConfig().getSrtpOpt().getKeyings().add(pjmedia_srtp_keying_method.PJMEDIA_SRTP_KEYING_SDES.swigValue());
acfg.getMediaConfig().getSrtpOpt().getKeyings().add(pjmedia_srtp_keying_method.PJMEDIA_SRTP_KEYING_DTLS_SRTP.swigValue());
acfg.getMediaConfig().getSrtpOpt().getCryptos().clear();
StringVector cryptos = Endpoint.instance().srtpCryptoEnum();
for (int i = 0; i < cryptos.size(); i++) {
SrtpCrypto crypto = new SrtpCrypto();
crypto.setName(cryptos.get(i));
crypto.setFlags(0);
srtpCryptos.add(crypto);
acfg.getMediaConfig().getSrtpOpt().getCryptos().add(crypto);
}
}
else {
acfg.getMediaConfig().setSrtpUse(pjmedia_srtp_use.PJMEDIA_SRTP_DISABLED);
acfg.getMediaConfig().setSrtpSecureSignaling(0);
}
authCredInfo = new AuthCredInfo("digest",
account.getRealm(),
account.getAuthenticationId() != null && account.getAuthenticationId().trim().length() > 0 ? account.getAuthenticationId() : account.getUserName(),
0,
account.getPassword());
acfg.getSipConfig().getAuthCreds().add( authCredInfo );
acfg.getIpChangeConfig().setHangupCalls(false);
acfg.getIpChangeConfig().setShutdownTp(true);
create(acfg);
ConnectivityManager cm = (ConnectivityManager)service.getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo activeNetwork = cm.getActiveNetworkInfo();
boolean isConnected = activeNetwork != null && activeNetwork.isConnected();
if (isConnected) {
setRegistration(true);
}
}
#Override
public void onRegStarted(OnRegStartedParam prm) {
super.onRegStarted(prm);
Log.d(TAG, "Status: Registering...");
status.setStatus(AccountStatus.Status.REGISTERING);
service.updateStatus(this);
}
#Override
public void onRegState(OnRegStateParam prm) {
super.onRegState(prm);
try {
Log.d(TAG, "Registration state: " + prm.getCode().swigValue() + " " + prm.getReason());
AccountInfo ai = getInfo();
status.setStatus(ai.getRegIsActive() ? AccountStatus.Status.REGISTERED : AccountStatus.Status.UNREGISTERED);
Log.d(TAG, "Status: " + status.getStatus().name() + " " + super.getInfo().getUri());
service.updateStatus(this);
} catch (Exception e) {
e.printStackTrace();
}
}
.....
}
Finally, how I answer the code at the moment in a class which extends the PJSIP's Call class:
#Override
public void answerCall() {
Log.d(TAG, "Answering call...");
CallOpParam prm = new CallOpParam(true);
prm.setStatusCode(pjsip_status_code.PJSIP_SC_OK);
prm.getOpt().setAudioCount(1);
prm.getOpt().setVideoCount(0);
try {
this.answer(prm);
} catch (Exception e) {
e.printStackTrace();
}
}
I also tried with new CallOpParam(); with just the status code and nothing else, but nothing changes.
One note: I created the IdUri as sip:username#127.0.0.1 because without the host the resulting contact was and I thought that the missing user part may be the cause of the issue or part of it.
The following is the trace of the app <-> my Asterisk server communication during call (linked because of content length exceed).
https://gist.github.com/ivano85/a212ddc9a808f3cd991234725c2bdb45
The ServerIp is an internet public IP, while the MyIp[5.XXX.XXX.XXX] is my phone's public IP.
As you can see from the log, my app sends a 100 Trying, then a 180 Ringing when the phone rings, then the user answers and the app sends a 200 OK. The server replies with a ACK message (I would say it's not a NAT issue, because PJSIP receives the ACK). I see the same from Asterisk.
After this I would expect the call goes from PJSIP_INV_STATE_CONNECTING to PJSIP_INV_STATE_CONFIRMED, but it does not happen, so PJSIP continues to send a 200 OK and receive the ACK every about 2 seconds, until the call times out after 32 seconds and PJSIP disconnects the call (sending a BYE).
I'm starting to think that PJSIP just ignores ACK messages and just has a wrong behaviour. Please help me to understand what is happening here. I would appreciate it so much!
Obviously let me know if you think that more details are needed.

Calling setProgress(0, 0, true) does not update Notification on > API 19

Update:
After doing a lot more debugging, I figured out the cause of my notification not updating: Calling setProgress(0, 0, false) upon completion of the download. For some reason when this method is called before notify(), the upcoming update doesn't actually go through. Adding a NotificationChannel doesn't do anything.
My current workaround is to call setProgress(100, 100, false) so that the user can tell the download is finished and will subsequently get updated.
Original question:
I've got a custom file downloading utility that creates a notification whenever a file is being downloaded. This notification gets updated as the download progresses.
However, I am getting some odd results on API levels above 19. When the download is finished, the NotificationManager doesn't update the notification to inform the user of this. The notification does get updated whenever the download progresses, strangely enough.
Also, when I have the debugger active, the notification does get updated when the download completes. This leads me to believe some sort of race condition is happening here but I can't really seem to find out where or how.
My FileDownloader class:
public static void startGetRequestDownload(final Context context,
String fileUrl,
#Nullable String fileName,
#Nullable Header[] headers,
#Nullable RequestParams requestParams,
final boolean showProgressNotification){
final int notificationID = 0;
final NotificationManager mNotifyManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
if(mNotifyManager == null) {
Log.e(TAG, "Failed to get NotificationManager service");
return;
}
final NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(context);
String notificationTitle = context.getString(R.string.default_filename);
if(fileName != null && !fileName.isEmpty()){
notificationTitle = fileName;
}
mBuilder.setContentTitle(notificationTitle)
.setContentText(context.getString(R.string.download_in_progress))
.setColor(ContextCompat.getColor(context, R.color.app_color_accent))
.setSmallIcon(R.drawable.ic_elab);
String uuid = "Temp_" + System.currentTimeMillis();
final File tempFile = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).toString(), uuid);
// Temporarily disable SSL as workaround.
RestClient.getClient().setSSLSocketFactory(MySSLSocketFactory.getFixedSocketFactory());
RestClient.getClient().get(context, fileUrl, headers, requestParams, new FileAsyncHttpResponseHandler(tempFile) {
#Override
public void onStart() {
super.onStart();
if(showProgressNotification){
mBuilder.setProgress(100, 0, false);
mNotifyManager.notify(notificationID, mBuilder.build());
}
}
#Override
public void onProgress(long bytesWritten, long totalSize) {
super.onProgress(bytesWritten, totalSize);
if(showProgressNotification){
int progress = (int) (bytesWritten / totalSize * 100);
mBuilder.setProgress(100, progress, false);
mNotifyManager.notify(notificationID, mBuilder.build());
}
}
#Override
public void onFailure(int statusCode, Header[] headers, Throwable throwable, File file) {
if(showProgressNotification){
mBuilder.setContentText(context.getString(R.string.download_failed));
mBuilder.setProgress(0,0,false);
mNotifyManager.notify(notificationID, mBuilder.build());
}
Log.w(TAG, "File download failed", throwable);
}
#Override
public void onSuccess(int statusCode, Header[] headers, File file) {
FileMetaData metaData = validateResponse(headers);
if(metaData != null){
final File downloadDirectory = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).toString());
File newFile = new File(downloadDirectory, metaData.fileName);
if(file.renameTo(newFile)){
Uri fileUri = FileProvider.getUriForFile(context, context.getApplicationContext().getPackageName() + ".fileprovider", newFile);
Intent actionViewIntent = new Intent(Intent.ACTION_VIEW);
actionViewIntent.setDataAndType(fileUri, metaData.contentType);
actionViewIntent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
if(showProgressNotification){
PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, actionViewIntent, PendingIntent.FLAG_ONE_SHOT);
mBuilder.setProgress(0,0,false);
mBuilder.setContentText(context.getString(R.string.download_completed));
mBuilder.setContentIntent(pendingIntent);
mBuilder.setSmallIcon(android.R.drawable.stat_sys_download_done);
mNotifyManager.notify(notificationID, mBuilder.build());
}
return;
}
}
//Failover
if(showProgressNotification){
mBuilder.setContentText(context.getString(R.string.download_failed));
mNotifyManager.notify(notificationID, mBuilder.build());
}
}
});
}
I figured out the actual reason as to why my notification wasn't getting updated. The Android documentation mentions the following in regards to notification updates:
Caution: The system applies a rate limit to updating notifications. If you post updates to a notification too frequently, the system may drop some notifications.
I was calling notify() every time the download progressed. Obviously, the download progress method was called very often, which resulted in several notifications getting blocked. However, I only saw the last notification getting dropped: The download completion notification.
Limiting the amount of calls to notify() in onProgress() fixed my issue.

BroadcastReceiver SharedPreferences just not reliable

I have an Android broadcast receiver (using Eclipse if it matters) that is started by a service that is suppose to check if an online data version is updated. If so, I want to trigger a notification in the notification bar.
Everything seems to work OK, however, the results it gets from SharedPreferences are just not reliable. The broadcast receiver is suppose to get the current value stored in the SharedPreferences and compare it to a value it gets from a web url. If it compares the value and sees an update has been made (i.e. the value in at the url is greater than the stored value) it triggers a notification and also updates the value stored in the SharedPreferences. The broadcast receiver checks this update once per day and this only is intended to notify the users, nothing more.
The code I am using to do this seems to work. But here and there, it pulls a default value from the SharedPreferences and triggers an update notification even though nothing has changed.
I realize that I may be trying to do too much in the onReceive of the broadcast receiver, however I am very confused on how to fire an activity to do the work from the broadcastreceiver.
Does anyone have any code examples of the best way to implement this? I have searched and searched but I cannot seem to find clear steps to do this either way. Here is what I have:
public class AlarmReceiver extends BroadcastReceiver {
public static JSONArray myJArray2;
public static InputStream is = null;
public static final String URL_DatabaseVersion = "http://www.mysite.com/mobile/version.php";
public static NotificationManager nm;
public static Boolean updated = false;
public static int latestVersion = 0;
public static int internalVersion = 2;
public static final String PREFS_NAME = "MyPrefsFile";
#Override
public void onReceive(Context context, Intent intent) {
updated = false;
SharedPreferences settings = context.getSharedPreferences(PREFS_NAME, 0);
internalVersion = settings.getInt("dataversion", 1);
InputStream is = null;
String result = "";
ArrayList<NameValuePair> nameValuePairs = new ArrayList<NameValuePair>();
nameValuePairs.add(new BasicNameValuePair("",""));
try{
HttpClient httpclient = new DefaultHttpClient();
HttpPost httppost = new HttpPost(URL_DatabaseVersion);
httppost.setEntity(new UrlEncodedFormEntity(nameValuePairs));
HttpResponse response = httpclient.execute(httppost);
HttpEntity entity = response.getEntity();
is = entity.getContent();
}catch(Exception e){
}
try{
BufferedReader reader = new BufferedReader(new InputStreamReader(is,"iso-8859-1"),8);
StringBuilder sb = new StringBuilder();
String line = null;
while ((line = reader.readLine()) != null)
{
sb.append(line + "\n");
}
is.close();
result=sb.toString();
}catch(Exception e){
}
try{
JSONArray myJArray = new JSONArray(result);
myJArray2 = myJArray;
}catch(JSONException e){
}
int mode = Activity.MODE_PRIVATE;
SharedPreferences mySharedPreferences = context.getSharedPreferences("MyPrefsFile",mode);
SharedPreferences.Editor editor = mySharedPreferences.edit();
editor.putInt ("dataversion", latestVersion);
editor.commit();
if (!Arrays.asList(myJArray2).contains(null))
{
try{
for(int i=0;i<myJArray2.length();i++)
{
JSONObject json_data = myJArray2.getJSONObject(i);
latestVersion = json_data.getInt("title");
if (internalVersion < latestVersion)
{
updated = true;
}
else
{
updated = false;
}
}
}
catch(Exception e)
{}
finally
{}
}
else
{
updated = false;
}
if (updated)
{
nm = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
CharSequence from = "Database Updated";
CharSequence message = "I: " + internalVersion + " L: " + latestVersion + "Click to open.";
PendingIntent contentIntent = PendingIntent.getActivity(context, 0, new Intent(), 0);
Notification notif = new Notification(R.drawable.icon, "Database Updated", System.currentTimeMillis());
notif.setLatestEventInfo(context, from, message, contentIntent);
nm.notify(1, notif);
}
}
}
Yes, I am probably trying to do way too much in a BroadcastReceiver and that might be causing the issue, however I cannot figure out how to do this any other way. When I log the results of this code, the first time through it works great. Second time through the internalVersion comes up 0. The value it gets from the URL is ALWAYS correct, it is the internalVersion, the one it pulls from SharedPreferences which is wrong many times.
Any thoughts? Sorry so long. Thanks.
Corey
SharedPreference.Editor.commit() is a blocking call. This will take time and could result in likely ANRs.
Use apply() on API 9 and up and thread this off before while storing the expected value locally on older API versions, move this code to a service, and log your exceptions so you can debug appropriately for race conditions like this.
You should write/commit your changes only when you realize, something has changed - that is in the "if (updated)" branch. And you should not use static members but commit the value you read from the server.
Say your Receiver has been started anew (in a new process). In this case static values are starting with their initial values, so latestversion is "0". You commit this value.
Next run you read the "0". Now internalversion is lower than the server version - even if no change has happened.
Try by using:
int mode = Context.MODE_PRIVATE;
instead of
int mode = Activity.MODE_PRIVATE;
It'll start saving values in SharedPreferences.

Categories

Resources