I am using below code to schedule a job service.
JobScheduler jobScheduler = (JobScheduler) mContext.getApplicationContext().getSystemService(Context.JOB_SCHEDULER_SERVICE);
if (jobScheduler != null) {
try {
jobScheduler.schedule(AppJobService.createJobInfo(mContext.getApplicationContext(), account));
} catch (IllegalArgumentException e) {
CrashLogger.logException(e);
}
}
public static JobInfo createJobInfo(#NonNull Context context, Account account) {
Gson g = new Gson();
String json = g.toJson(account);
PersistableBundle bundle = new PersistableBundle();
bundle.putString("Account", json);
JobInfo.Builder builder = new JobInfo.Builder(3, new ComponentName(context, AppJobService.class))
.setExtras(bundle)
.setRequiredNetworkType(NETWORK_TYPE_NONE)
.setRequiresDeviceIdle(false).setPersisted(false);
return builder.build();
}
But getting below exception
2018-12-03 17:51:22.360 5032-5557/? W/System.err:
java.lang.IllegalArgumentException: You're trying to build a job with
no constraints, this is not allowed.
But when I change setRequiredNetworkType(NETWORK_TYPE_NONE) to setRequiredNetworkType(NETWORK_TYPE_ANY) it works fine.But I want my job service to run even when there is no network connection.Why I am getting exception with NETWORK_TYPE_NONE?
You must have some kind of constraint, or it will always throw an IllegalArgumentException, put any kind of constraint or just use AlarmManager or WorkManager
Check out the snippet, this is from the Android source code
public JobInfo build() {
// Allow jobs with no constraints - What am I, a database?
if (!mHasEarlyConstraint && !mHasLateConstraint && mConstraintFlags == 0 &&
mNetworkRequest == null &&
mTriggerContentUris == null) {
throw new IllegalArgumentException("You're trying to build a job with no " +
"constraints, this is not allowed.");
}
I had the same problem and I just used AlarmManager instead
Related
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.
I am trying to learn work manager chaining with passing output of one to another
Here are my objectives,
I have two work request WR1 (gets me the url) and WR2 (sends request to the url)
I can't and should not start the WR2 until the WR1 completes.
WR1 is supposed to return a url to which i have to send to WR2 as inputData
I can mostly do this without chaining. But i would to like explore it in the chaining.
Here is my snippet in progress. Please help.
WorkManager mWorkManager = WorkManager.getInstance(this);
//WR1
OneTimeWorkRequest urlRequest = new
OneTimeWorkRequest.Builder(UriResolveWorker.class).build();
//WR2
OneTimeWorkRequest pullRequest = new
OneTimeWorkRequest.Builder(PullReplicator.class).build();
btnStart.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
mWorkManager.beginWith(urlRequest)
.then(pullRequest) // I should be able to pass the result of urlRequest.
.enqueue();
}
});
mWorkManager.getWorkInfoByIdLiveData(urlRequest.getId()).observe(this, new
Observer<WorkInfo>() {
#Override
public void onChanged(WorkInfo workInfo) {
if (workInfo != null) {
WorkInfo.State state = workInfo.getState();
// I will get the URL here and i want to pass this to WR2
message = workInfo.getOutputData().getString("work_result");
tvStatus.append("\n"+"state : "+state.toString() + "message : " +message + "\n");
}
}
});
mWorkManager.getWorkInfoByIdLiveData(pullRequest.getId()).observe(this, new Observer<WorkInfo>() {
#Override
public void onChanged(WorkInfo workInfo) {
if (workInfo != null) {
WorkInfo.State state = workInfo.getState();
String count = workInfo.getOutputData().getString("work_result");
tvStatus.append("\n"+"state : "+state.toString() + " No of Documents : " +count + "\n");o
}
}
});
Before returning the Result object in doWork() method of UriResolveWorker class, You can pass the url to Result.success().
First create object of Data.Builder() and then put the url into:
Data.Builder outputDataBuilder = Data.Builder();
outputDataBuilder.putString(KEY_URL_STRING, url.toString());
after that, create Data object with outputDataBuilder:
Data outputData = outputDataBuilder.build();
now you can return the Result with outputData :
return Result.success(outputData);
Workmanager sends the data to pullRequest when the first one has been done.
Please check the state of the WorkRequest before getting the data.
For example:
private final Observer<WorkInfo> urlWorkInfo = workInfo -> {
if (workInfo == null) {
return;
}
WorkInfo.State state = workInfo.getState();
if (state.isFinished()) {
String url = workInfo.getOutputData()
.getString(KEY_URL)
if (url != null) {
Log.d(TAG_MAIN_ACTIVITY, url);
} else {
Log.d(TAG_MAIN_ACTIVITY, "Url not found!");
}
} else if (state == WorkInfo.State.RUNNING) {
Log.d(TAG_MAIN_ACTIVITY, "Associated WorkRequest is being executed");
}
};
I know it's been a while since you asked this question, but maybe it might help someone in the feature:
In WR1 (Worker 1) you will have output with specific key - data that you want in your second worker (WR2):
Result.success(workDataOf(MY_TEST_KEY to someData.toString()))
Now if you want to get that data in WR2 you cannot send it trough setInputData() method when creating your OneTimeWorkRequest and chaining it.
As everything is forwarded from WR1 to WR2, in WR2 you have to fetch that data like:
inputData.getString(WR1.MY_TEST_KEY)
And than use it as you want in your WR2.
Hopefully this will help someone in the future, official documentation can be found here: https://developer.android.com/topic/libraries/architecture/workmanager/how-to/chain-work
Since I'm struggling a lot with the Android Oreo background restriction, I was asking myself if working with the AlarmManager is even the best way of timing Job execution to e.g. 03:00 AM. I saw some people use JobScheduler, but it seems it's not that suitable for executing tasks every day at a given time.
I was trying just AlarmManager with a BroadcastReceiver, then inserted the BroadcastReceiver in a (in theory) self-starting service, but since an app isn't able to call startService() when in background this also doesn't work the way it should (and also seems kinda wrong).
Am I missing something? What's the current way to go?
Obviously there are ways, because otherwise Messengers, Games and other Apps won't be able to work the way they do.
public class BackgroundTaskWorker extends Worker {
public BackgroundTaskWorker(#NonNull Context context, #NonNull WorkerParameters workerParams) {
super(context, workerParams);
}
#Override
public Result doWork() {
Log.i("WORKING","DOING SOME WORK");
Context con = getApplicationContext();
SharedPreferences preferences = con.getSharedPreferences(MainActivity.sharedPrefs, Context.MODE_PRIVATE);
SharedPreferences.Editor editPrefs = preferences.edit();
int day = Calendar.getInstance().get(Calendar.DAY_OF_MONTH);
String s_day = preferences.getString("DAY","0");
int old_day = Integer.parseInt(s_day);
if(old_day == 0){
Log.i("WORKING","old_day default");
editPrefs.putString("DAY",Integer.toString(day));
editPrefs.commit();
return Result.success();
}
else if(day == old_day) {
Log.i("WORKING", "day=old_day default");
return Result.success();
}
else {
Log.i("WORKING","old_day change");
editPrefs.putString("DAY",Integer.toString(day));
editPrefs.commit();
Log.d("BISASAM","triggered");
DateFormat date = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss", Locale.GERMANY);
Date dat = new Date();
Log.d("TRIGGERDATE",date.format(dat));
editPrefs.putString("REC", "Receiver called "+date.format(dat));
NotificationCompat.Builder builder= new NotificationCompat.Builder(con,"ID");
builder.setContentTitle("ALARM FIRED");
builder.setContentText("WORKER");
builder.setPriority(NotificationCompat.PRIORITY_DEFAULT);
builder.setSmallIcon(R.drawable.kreuz);
if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.O) {
Log.d("BUILDCHECK","correct");
CharSequence name = "NotChannel";
String desc = "Test Channel for Planer";
int importance = NotificationManager.IMPORTANCE_DEFAULT;
NotificationChannel channel = new NotificationChannel("NOT",name,importance);
channel.setDescription(desc);
NotificationManager notManager = con.getSystemService(NotificationManager.class);
notManager.createNotificationChannel(channel);
NotificationManagerCompat notificationManager = NotificationManagerCompat.from(con);
builder.setChannelId("NOT");
notificationManager.notify(1,builder.build());
}
//TODO Test Tageswechsel Wiederholende Tasks
String today = preferences.getString("0",null);
String tomorrow = preferences.getString("1",null);
String next_week = preferences.getString("7",null);
String next_month = preferences.getString("30",null);
if(today != null) {
String[] repetitive = today.split(" ");
for (int j = 1; j < repetitive.length; j += 2) {
Log.d("PIKACHU",repetitive[j-1]);
switch(repetitive[j]){
case "1":
if(tomorrow!=null)
tomorrow += ","+ repetitive[j-1]+" "+repetitive[j];
else
tomorrow=repetitive[j-1]+" "+repetitive[j];
break;
case "7":
if(next_week!=null)
next_week += ","+ repetitive[j-1]+" "+repetitive[j];
else
next_week=repetitive[j-1]+" "+repetitive[j];
break;
case "30":
if(next_month!=null)
next_month += ","+ repetitive[j-1]+" "+repetitive[j];
else
next_month=repetitive[j-1]+" "+repetitive[j];
break;
default:
}
}
}
Log.d("PUTTING",tomorrow);
Log.d("PUTTING",next_week);
Log.d("PUTTING",next_month);
editPrefs.putString("1",tomorrow);
editPrefs.putString("7",next_week);
editPrefs.putString("30",next_month);
editPrefs.commit();
ArrayList<String> month = new ArrayList<>();
for (int i = 0; i < Jobs.month_length; i++) {
month.add(preferences.getString(Integer.toString(i),""));
}
for (int i=1;i<Jobs.month_length;i++){
month.set(i-1,month.get(i));
}
month.set(30,"");
for(int i=0;i<Jobs.month_length;i++){
editPrefs.putString(Integer.toString(i),month.get(i));
}
Log.d("COMMITED",month.toString());
editPrefs.commit();
}
// Indicate success or failure with your return value:
return Result.success();
}
}
private void registerWorker(){
unregisterWorker();
PeriodicWorkRequest request= new PeriodicWorkRequest.Builder(BackgroundTaskWorker.class,
20, TimeUnit.MINUTES)
.addTag("AUFGABEN_PLANER_BACK")
.build();
WorkManager.getInstance().enqueueUniquePeriodicWork("AUFGABEN_PLANER_BACK", ExistingPeriodicWorkPolicy.KEEP, request);
}
private void unregisterWorker(){
WorkManager.getInstance().cancelAllWorkByTag("AUFGABEN_PLANER_BACK");
}
registerWorker is called everytime MainActivity gets started (=> at the app start)
Use WorkManager for Scheduling task in background and foreground
Example for Periodic Request
PeriodicWorkRequest request= new PeriodicWorkRequest.Builder(WorkerClass.class,
24, TimeUnit.HOURS).setInitialDelay(THE_DELAY,TimeUnit.SECONDS).addTag("TAG").build()
WorkManager.getInstance().enqueueUniquePeriodicWork("TAG", ExistingPeriodicWorkPolicy.KEEP, request);
Create a worker class
public class WorkerClass extends Worker {
#Override
public Worker.WorkerResult doWork() {
// Do the work here
// Indicate success or failure with your return value:
return WorkerResult.SUCCESS;
// (Returning RETRY tells WorkManager to try this task again
// later; FAILURE says not to try again.)
}
}
Now call this class by
Example for One Time request
OneTimeWorkRequest request= new OneTimeWorkRequest.Builder(WorkerClass .class)
.setInitialDelay(delayedTime, TimeUnit.MILLISECONDS)
.addTag("TAG")
.build();
WorkManager.getInstance().enqueueUniquePeriodicWork("TAG", ExistingPeriodicWorkPolicy.KEEP, request);
where delayedTime is calculated time to do task
add this in build.gradle
implementation 'android.arch.work:work-runtime:2.1.0-alpha02'
check the latest release doc
https://developer.android.com/jetpack/androidx/releases/work
also you can convert your time
Android / Java convert String date to long type
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.
Anyone know how to fix this NullPointerException on the start of setup of the IabHelper?
java.lang.NullPointerException
at android.app.ApplicationPackageManager.queryIntentServicesAsUser(ApplicationPackageManager.java:559)
at android.app.ApplicationPackageManager.queryIntentServices(ApplicationPackageManager.java:571)
at IabHelper.startSetup(IabHelper.java:266)
at MyApplication.createIABHelper(MyApplication.java:256)
at MyApplication.onCreate(MyApplication.java:156)
at android.app.Instrumentation.callApplicationOnCreate(Instrumentation.java:1000)
at android.app.ActivityThread.handleBindApplication(ActivityThread.java:4391)
at android.app.ActivityThread.access$1300(ActivityThread.java:141)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1294)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:137)
at android.app.ActivityThread.main(ActivityThread.java:5041)
at java.lang.reflect.Method.invokeNative(Method.java)
at java.lang.reflect.Method.invoke(Method.java:511)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:816)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:582)
at dalvik.system.NativeStart.main(NativeStart.java)
My IabHelper code is slightly modified because of other issues with it so here is the startSetup method:
public void startSetup(final OnIabSetupFinishedListener listener) {
// If already set up, can't do it again.
checkNotDisposed();
if (mSetupDone) throw new IllegalStateException("IAB helper is already set up.");
// Connection to IAB service
logDebug("Starting in-app billing setup.");
mServiceConn = new ServiceConnection() {
#Override
public void onServiceDisconnected(ComponentName name) {
logDebug("Billing service disconnected.");
mService = null;
}
#Override
public void onServiceConnected(ComponentName name, IBinder service) {
if (mDisposed) return;
logDebug("Billing service connected.");
mService = IInAppBillingService.Stub.asInterface(service);
String packageName = mContext.getPackageName();
try {
logDebug("Checking for in-app billing 3 support.");
// check for in-app billing v3 support
int response = mService.isBillingSupported(3, packageName, ITEM_TYPE_INAPP);
if (response != BILLING_RESPONSE_RESULT_OK) {
if (listener != null) listener.onIabSetupFinished(new IabResult(response,
"Error checking for billing v3 support."));
// if in-app purchases aren't supported, neither are subscriptions.
mSubscriptionsSupported = false;
return;
}
logDebug("In-app billing version 3 supported for " + packageName);
// check for v3 subscriptions support
response = mService.isBillingSupported(3, packageName, ITEM_TYPE_SUBS);
if (response == BILLING_RESPONSE_RESULT_OK) {
logDebug("Subscriptions AVAILABLE.");
mSubscriptionsSupported = true;
} else {
logDebug("Subscriptions NOT AVAILABLE. Response: " + response);
}
mSetupDone = true;
} catch (RemoteException e) {
if (listener != null) {
listener.onIabSetupFinished(new IabResult(IABHELPER_REMOTE_EXCEPTION,
"RemoteException while setting up in-app billing."));
}
e.printStackTrace();
return;
}
if (listener != null) {
listener.onIabSetupFinished(new IabResult(BILLING_RESPONSE_RESULT_OK, "Setup successful."));
}
}
};
Intent serviceIntent = getExplicitIapIntent();
PackageManager pm = mContext.getPackageManager();
List<ResolveInfo> intentServices = pm.queryIntentServices(serviceIntent, 0);
if (intentServices != null && !intentServices.isEmpty()) {
//this was replaced per this comment http://stackoverflow.com/a/24202135/704836
//if (!mContext.getPackageManager().queryIntentServices(serviceIntent, 0).isEmpty()) {
// service available to handle that Intent
mContext.bindService(serviceIntent, mServiceConn, Context.BIND_AUTO_CREATE);
} else {
// no service available to handle that Intent
if (listener != null) {
listener.onIabSetupFinished(
new IabResult(BILLING_RESPONSE_RESULT_BILLING_UNAVAILABLE,
"Billing service unavailable on device."));
}
}
}
The NullPointerException is happening at List<ResolveInfo> intentServices = pm.queryIntentServices(serviceIntent, 0);
Any ideas?
Thanks.
EDIT: here is the code for getExplicitIapIntent:
/**
* From http://stackoverflow.com/a/26318757/704836
* #return
*/
private Intent getExplicitIapIntent() {
PackageManager pm = mContext.getPackageManager();
Intent implicitIntent = new Intent("com.android.vending.billing.InAppBillingService.BIND");
implicitIntent.setPackage("com.android.vending");
List<ResolveInfo> resolveInfos = pm.queryIntentServices(implicitIntent, 0);
// Is somebody else trying to intercept our IAP call?
if (resolveInfos == null || resolveInfos.size() != 1) {
return null;
}
ResolveInfo serviceInfo = resolveInfos.get(0);
String packageName = serviceInfo.serviceInfo.packageName;
String className = serviceInfo.serviceInfo.name;
ComponentName component = new ComponentName(packageName, className);
Intent iapIntent = new Intent();
iapIntent.setComponent(component);
return iapIntent;
}
EDIT: I should also point out that according to crashlytics, 100% of the devices giving this error are rooted. So maybe it is something to do with people trying to get around having to pay for features.
EDIT: I tried passing null instead of serviceIntent and got the following exception:
java.lang.NullPointerException: Attempt to invoke virtual method 'java.lang.String android.content.Intent.resolveTypeIfNeeded(android.content.ContentResolver)' on a null object reference
at android.app.ApplicationPackageManager.queryIntentServicesAsUser(ApplicationPackageManager.java:644)
at android.app.ApplicationPackageManager.queryIntentServices(ApplicationPackageManager.java:656)
at IabHelper.startSetup(IabHelper.java:266)
On that exception the line numbers are different from the reports I've gotten, so I'm not certain it is the same.
EDIT: I think the exception I got on the last edit might be pretty much the same as the exception I am getting for 5.0.2 devices. Here is one of the 5.0.2 reports:
java.lang.NullPointerException
Attempt to invoke virtual method 'java.lang.String android.content.Intent.resolveTypeIfNeeded(android.content.ContentResolver)' on a null object reference
android.app.ApplicationPackageManager.queryIntentServicesAsUser (ApplicationPackageManager.java:645)
android.app.ApplicationPackageManager.queryIntentServices (ApplicationPackageManager.java:657)
IabHelper.startSetup (IabHelper.java:266)
EDIT: I went ahead and modified the code to throw an exception when serviceIntent is null and I've already had a few reports come back from my beta testers. All 100% rooted devices so I am guessing their devices don't have the correct com.android.vending.billing.InAppBillingService.BIND.
EDIT: Once the code was released the 100% rooted devices dropped to about 80%. Anyways I got a chance to troubleshoot with a user and it turned out that the getExplicitIntent method can return null sometimes under kitkat (not sure which other versions) so I went ahead and added an answer with how I changed the code.
Do you have the code to the method queryIntentServicesAsUser()? After taking a closer look, it seems like one of the variables you are sending the queryIntentServices could be causing the NullPointer.
So finally what is the right way to fix this problem? serviceIntent is null - that means someone wants to hack? And we let app crash in this situation?
I finally found a user getting a null intent who was not doing anything dodgy with in-app purchases. He was on kitkat. The fix was this:
if (OSUtils.lollipopOrHigher) {
serviceIntent = getExplicitIapIntent();
} else {
serviceIntent = new Intent("com.android.vending.billing.InAppBillingService.BIND");
serviceIntent.setPackage("com.android.vending");
}
Another option would be to check for null and then try the non lollipop method.