I have this class in java
public abstract class SimpleApiCallback<T> implements ApiCallback<T> {
private static final String LOG_TAG = "SimpleApiCallback";
private Activity mActivity;
private Context mContext = null;
private View mPostView = null;
/**
* Failure callback to pass on failures to.
*/
private ApiFailureCallback failureCallback = null;
/**
* Constructor
*/
public SimpleApiCallback() {
}
/**
* Constructor
*
* #param activity The context.
*/
public SimpleApiCallback(Activity activity) {
mActivity = activity;
}
/**
* Constructor
*
* #param context The context.
* #param postOnView the view to post the code to execute
*/
public SimpleApiCallback(Context context, View postOnView) {
mContext = context;
mPostView = postOnView;
}
/**
* Constructor to delegate failure callback to another object. This allows us to stack failure callback implementations
* in a decorator-type approach.
*
* #param failureCallback the failure callback implementation to delegate to
*/
public SimpleApiCallback(ApiFailureCallback failureCallback) {
this.failureCallback = failureCallback;
}
private void displayToast(final String message) {
if (null != mActivity) {
mActivity.runOnUiThread(new Runnable() {
#Override
public void run() {
Toast.makeText(mActivity, message, Toast.LENGTH_SHORT).show();
}
});
} else if ((null != mContext) && (null != mPostView)) {
mPostView.post(new Runnable() {
#Override
public void run() {
Toast.makeText(mContext, message, Toast.LENGTH_SHORT).show();
}
});
}
}
#Override
public void onNetworkError(Exception e) {
if (failureCallback != null) {
try {
failureCallback.onNetworkError(e);
} catch (Exception exception) {
Log.e(LOG_TAG, "## onNetworkError() failed" + exception.getMessage(), exception);
}
} else {
displayToast("Network Error");
}
}
#Override
public void onMatrixError(final MatrixError e) {
if (failureCallback != null) {
try {
failureCallback.onMatrixError(e);
} catch (Exception exception) {
Log.e(LOG_TAG, "## onMatrixError() failed" + exception.getMessage(), exception);
}
} else {
displayToast("Matrix Error : " + e.getLocalizedMessage());
}
}
#Override
public void onUnexpectedError(final Exception e) {
if (failureCallback != null) {
try {
failureCallback.onUnexpectedError(e);
} catch (Exception exception) {
Log.e(LOG_TAG, "## onUnexpectedError() failed" + exception.getMessage(), exception);
}
} else {
displayToast(e.getLocalizedMessage());
}
}
}
I can call it in java like that
new SimpleApiCallback<Void>(this) {
#Override
public void onSuccess(Void avoid) {
///
}
#Override
public void onNetworkError(Exception e) {
///
}
#Override
public void onUnexpectedError(Exception e) {
///
}
#Override
public void onMatrixError(MatrixError e) {
//
}
}
but in kotlin I tried many formats to call it but it doesn't work
like
val callback = object : SimpleApiCallback<>(activity){
fun onSuccess(avoid: Void) {
///
}
fun onNetworkError(e: Exception) {
///
}
fun onUnexpectedError(e: Exception) {
///
}
fun onMatrixError(e: MatrixError) {
//
}
}
can anyone advice please ?
Since you are using Java.lang.Void, you can still use it in Kotlin as it's not the same as a function's declarion void.
val callback = object : SimpleApiCallback<Void>(activity) {
fun onSuccess(avoid: Void) {
///
}
override fun onNetworkError(e: Exception) {
///
}
override fun onUnexpectedError(e: Exception) {
///
}
override fun onMatrixError(e: MatrixError) {
//
}
}
The above should work, but your onSuccess() is not part of the SimpleApiCallback if it is then you should override that too.
Recently I have been creating a new Android app to make/receive VoIP calls using Linphone lib. The first version of the app worked almost ok, I updated the lib and decided to do some code arrange, and for some reason, the app does not longer receive calls.
All the VoIP functionality is inside an android service:
package com.test.voice.linphone;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageInfo;
import android.os.Binder;
import android.os.IBinder;
import android.os.PowerManager;
import android.util.Log;
import com.test.voice.voice.R;
import org.linphone.core.AVPFMode;
import org.linphone.core.Address;
import org.linphone.core.AuthInfo;
import org.linphone.core.AuthMethod;
import org.linphone.core.Call;
import org.linphone.core.CallLog;
import org.linphone.core.CallStats;
import org.linphone.core.ChatMessage;
import org.linphone.core.ChatRoom;
import org.linphone.core.ConfiguringState;
import org.linphone.core.Content;
import org.linphone.core.Core;
import org.linphone.core.CoreException;
import org.linphone.core.CoreListener;
import org.linphone.core.EcCalibratorStatus;
import org.linphone.core.Event;
import org.linphone.core.Factory;
import org.linphone.core.Friend;
import org.linphone.core.FriendList;
import org.linphone.core.GlobalState;
import org.linphone.core.InfoMessage;
import org.linphone.core.PayloadType;
import org.linphone.core.PresenceModel;
import org.linphone.core.ProxyConfig;
import org.linphone.core.PublishState;
import org.linphone.core.Reason;
import org.linphone.core.RegistrationState;
import org.linphone.core.SubscriptionState;
import org.linphone.core.Transports;
import org.linphone.core.VersionUpdateCheckResult;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Timer;
import java.util.TimerTask;
public class LinphoneService extends Service {
// *************************************** CONSTANTS *************************************** //
private static final String TAG = "linphone_service";
private static final String WAKE_LOG_TAG = ":voicewakelock";
private static final String LIN_TAG = "libcore";
private static final String USER_AGENT = "VoiceAgent";
private static final long TIME_ITERATE = 200;
// ****************************************** VARS ***************************************** //
private final IBinder mBinder = new LocalBinder();
private PowerManager.WakeLock mWakeLock;
private Core mCore;
private String mLpConfig = null;
private String mConfigFactoryFile = null;
public String mLinphoneConfigFile = null;
private String mRootCaFile = null;
private String mRingSoundFile = null;
private String mRingBackSoundFile = null;
private String mPauseSoundFile = null;
private AuthInfo mAuthInfo;
private ProxyConfig mProxyConfig;
private Timer mIterateTimer;
private LinphoneCallback mCallback;
private RegistrationState mRegistrationState = RegistrationState.None;
// ************************************* INNER CLASSES ************************************* //
public class LocalBinder extends Binder {
public LinphoneService getService() {
return LinphoneService.this;
}
}
// *************************************** LIFECYCLE *************************************** //
#Override
public void onCreate() {
super.onCreate();
PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, WAKE_LOG_TAG);
mWakeLock.acquire();
String basePath = getFilesDir().getAbsolutePath();
mLpConfig = basePath + "/lpconfig.xsd";
mConfigFactoryFile = basePath + "/linphonerc";
mLinphoneConfigFile = basePath + "/.linphonerc";
mRootCaFile = basePath + "/rootca.pem";
mRingSoundFile = basePath + "/oldphone_mono.wav";
mRingBackSoundFile = basePath + "/ringback.wav";
mPauseSoundFile = basePath + "/toy_mono.wav";
}
#Override
public void onDestroy() {
super.onDestroy();
mWakeLock.release();
cancelIterateTimer();
}
#Override
public IBinder onBind(Intent intent) {
return mBinder;
}
// ************************************* PUBLIC METHODS ************************************ //
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
return START_NOT_STICKY;
}
/**
* Set a {#link LinphoneCallback} to receive event callbacks.
*
* #param callback {#link LinphoneCallback} to set.
*/
public void setCallback(LinphoneCallback callback) {
mCallback = callback;
}
/**
* Initiate Linphone library, set configuration.
*
* #throws Exception
*/
public void initLibrary() throws Exception {
copyAssetsFromPackage();
Factory.instance().setDebugMode(true, LIN_TAG);
mCore = Factory.instance().createCore(
mLinphoneConfigFile, mConfigFactoryFile, this);
mCore.addListener(getCoreListener());
mCore.enableIpv6(false);
PackageInfo packageInfo = getPackageManager().getPackageInfo(getPackageName(), 0);
mCore.setUserAgent(USER_AGENT, packageInfo.versionName);
mCore.setRemoteRingbackTone(mRingSoundFile);
mCore.setRing(mRingSoundFile);
mCore.setRootCa(mRootCaFile);
mCore.setPlayFile(mPauseSoundFile);
mCore.setNetworkReachable(true);
mCore.enableEchoCancellation(true);
mCore.enableEchoLimiter(true);
mCore.enableAdaptiveRateControl(true);
mCore.clearAllAuthInfo();
mCore.clearProxyConfig();
enablePayloads();
startIterateTimer();
}
/**
* Login agains the VoIP server.
*
* #param name Username.
* #param password Password.
* #param host Server.
* #param tcpPort Specify the TCP port to use, only TCP available.
* #throws CoreException
*/
public void login(String name, String password, String host, int tcpPort)
throws CoreException {
String identity = "sip:" + name + "#" + host;
String proxy = "sip:" + host;
Address proxyAddress = Factory.instance().createAddress(proxy);
Address identityAddress = Factory.instance().createAddress(identity);
if (proxyAddress == null || identityAddress == null) {
throw new CoreException("Proxy or Identity address is null.");
}
mProxyConfig = mCore.createProxyConfig();
mProxyConfig.setIdentityAddress(identityAddress);
mProxyConfig.setServerAddr(proxyAddress.asStringUriOnly());
mProxyConfig.setAvpfMode(AVPFMode.Disabled);
mProxyConfig.setAvpfRrInterval(0);
mProxyConfig.enableQualityReporting(false);
mProxyConfig.setQualityReportingCollector(null);
mProxyConfig.setQualityReportingInterval(0);
// mProxyConfig.setRoute(proxyAddress.asStringUriOnly());
mProxyConfig.enableRegister(true);
mAuthInfo = Factory.instance().createAuthInfo(
name, null, password, null, null, host);
Transports transports = mCore.getTransports();
transports.setUdpPort(-1);
transports.setTlsPort(-1);
transports.setTcpPort(tcpPort);
mCore.setTransports(transports);
mCore.addProxyConfig(mProxyConfig);
mCore.addAuthInfo(mAuthInfo);
mCore.setDefaultProxyConfig(mProxyConfig);
}
/**
* Disconnect from remote VoIP server.
*/
public void logout() {
if (mProxyConfig != null) {
mProxyConfig.edit();
mProxyConfig.enableRegister(false);
mProxyConfig.done();
}
}
/**
* Accept incoming call.
*/
public void acceptCall() {
mCore.acceptCall(mCore.getCurrentCall());
}
/**
* Decline current call.
*/
public void declineCall() {
mCore.declineCall(mCore.getCurrentCall(), Reason.Declined);
}
/**
* Hang up the current call.
*/
public void hangUp() {
Call currentCall = mCore.getCurrentCall();
if (currentCall != null) {
mCore.terminateCall(currentCall);
} else if (mCore.isInConference()) {
mCore.terminateConference();
} else {
mCore.terminateAllCalls();
}
}
// ************************************ PRIVATE METHODS ************************************ //
/**
* Copy resource files.
*
* #throws IOException If an I/O error occurrs.
*/
private void copyAssetsFromPackage() throws IOException {
copyIfNotExist(this, R.raw.oldphone_mono, mRingSoundFile);
copyIfNotExist(this, R.raw.ringback, mRingBackSoundFile);
copyIfNotExist(this, R.raw.toy_mono, mPauseSoundFile);
copyIfNotExist(this, R.raw.linphonerc_default, mLinphoneConfigFile);
copyIfNotExist(this, R.raw.linphonerc_factory, (
new File(this.mConfigFactoryFile)).getName());
copyIfNotExist(this, R.raw.lpconfig, mLpConfig);
copyIfNotExist(this, R.raw.rootca, mRootCaFile);
}
private void copyIfNotExist(Context context, int resourceId, String target) throws IOException {
File fileToCopy = new File(target);
if (!fileToCopy.exists()) {
copyFromPackage(context, resourceId, fileToCopy.getName());
}
}
private void copyFromPackage(Context context, int resourceId, String target) throws
IOException {
FileOutputStream outputStream = context.openFileOutput(target, 0);
InputStream inputStream = context.getResources().openRawResource(resourceId);
byte[] buff = new byte[8048];
int readByte;
while ((readByte = inputStream.read(buff)) != -1) {
outputStream.write(buff, 0, readByte);
}
outputStream.flush();
outputStream.close();
inputStream.close();
}
/**
* Get the {#link CoreListener}.
*
* #return Instance of {#link CoreListener}.
*/
private CoreListener getCoreListener() {
return new CoreListener() {
#Override
public void onGlobalStateChanged(Core core, GlobalState globalState, String s) {
Log.d(TAG, "Core listener - Global State Changed: " + s);
mCallback.onGlobalStateChanged(globalState);
}
#Override
public void onRegistrationStateChanged(Core core, ProxyConfig proxyConfig,
RegistrationState registrationState,
String state) {
Log.d(TAG, "Core listener - On Registration State Changed: " +
state);
if (registrationState != mRegistrationState) {
mCallback.onRegistrationStateChanged(registrationState);
}
mRegistrationState = registrationState;
}
#Override
public void onCallStateChanged(Core core, Call call, Call.State state, String s) {
Log.d(TAG, "Core listener - On Call State Changed");
mCallback.onCallStateChanged(call, state);
}
#Override
public void onNotifyPresenceReceived(Core core, Friend friend) {
Log.d(TAG, "Core listener - On Notify Presence Received");
}
#Override
public void onNotifyPresenceReceivedForUriOrTel(Core core, Friend friend, String s,
PresenceModel presenceModel) {
Log.d(TAG, "Core listener - On Notify Presence Received For Uri Or Tel");
}
#Override
public void onNewSubscriptionRequested(Core core, Friend friend, String s) {
Log.d(TAG, "Core listener - On New Subscription Requested");
}
#Override
public void onAuthenticationRequested(Core core, AuthInfo authInfo, AuthMethod
authMethod) {
Log.d(TAG, "Core listener - On Authentication Requested");
}
#Override
public void onCallLogUpdated(Core core, CallLog callLog) {
Log.d(TAG, "Core listener - On Call Log Updated");
}
#Override
public void onMessageReceived(Core core, ChatRoom chatRoom, ChatMessage chatMessage) {
Log.d(TAG, "Core listener - On Message Received");
}
#Override
public void onMessageReceivedUnableDecrypt(Core core, ChatRoom chatRoom, ChatMessage
chatMessage) {
Log.d(TAG, "Core listener - On Message Received Unable Decrypt");
}
#Override
public void onIsComposingReceived(Core core, ChatRoom chatRoom) {
Log.d(TAG, "Core listener - On Is Composing Received");
}
#Override
public void onDtmfReceived(Core core, Call call, int i) {
Log.d(TAG, "Core listener - On Dtmf Received");
}
#Override
public void onReferReceived(Core core, String s) {
Log.d(TAG, "Core listener - On Refer Received");
}
#Override
public void onCallEncryptionChanged(Core core, Call call, boolean b, String s) {
Log.d(TAG, "Core listener - On Call Encrypted Changed");
}
#Override
public void onTransferStateChanged(Core core, Call call, Call.State state) {
Log.d(TAG, "Core listener - On Transfer State Changed");
}
#Override
public void onBuddyInfoUpdated(Core core, Friend friend) {
Log.d(TAG, "Core listener - On Buddy Info Updated");
}
#Override
public void onCallStatsUpdated(Core core, Call call, CallStats callStats) {
Log.d(TAG, "Core listener - On Call Stats Updated");
}
#Override
public void onInfoReceived(Core core, Call call, InfoMessage infoMessage) {
Log.d(TAG, "Core listener - On Info Received");
}
#Override
public void onSubscriptionStateChanged(Core core, Event event, SubscriptionState
subscriptionState) {
Log.d(TAG, "Core listener - On Subscription State Changed");
}
#Override
public void onNotifyReceived(Core core, Event event, String s, Content content) {
Log.d(TAG, "Core listener - On Notify Received");
}
#Override
public void onSubscribeReceived(Core core, Event event, String s, Content content) {
Log.d(TAG, "Core listener - On Subscribe Received");
}
#Override
public void onPublishStateChanged(Core core, Event event, PublishState publishState) {
Log.d(TAG, "Core listener - On Publish State Changed");
}
#Override
public void onConfiguringStatus(Core core, ConfiguringState configuringState, String
s) {
Log.d(TAG, "Core listener - On Configuring Status");
}
#Override
public void onNetworkReachable(Core core, boolean b) {
Log.d(TAG, "Core listener - On Network Reachable");
}
#Override
public void onLogCollectionUploadStateChanged(Core core, Core
.LogCollectionUploadState logCollectionUploadState, String s) {
Log.d(TAG, "Core listener - On Log Collection Upload StateChanged");
}
#Override
public void onLogCollectionUploadProgressIndication(Core core, int i, int i1) {
Log.d(TAG, "Core listener - On Log Collection Upload ProgressIndication");
}
#Override
public void onFriendListCreated(Core core, FriendList friendList) {
Log.d(TAG, "Core listener - On Friend List Created");
}
#Override
public void onFriendListRemoved(Core core, FriendList friendList) {
Log.d(TAG, "Core listener - On Friend List Removed");
}
#Override
public void onCallCreated(Core core, Call call) {
Log.d(TAG, "Core listener - On Call Created");
}
#Override
public void onVersionUpdateCheckResultReceived(Core core, VersionUpdateCheckResult
versionUpdateCheckResult, String s, String s1) {
Log.d(TAG, "Core listener - On Version Update Check ResultReceived");
}
#Override
public void onChatRoomStateChanged(Core core, ChatRoom chatRoom, ChatRoom.State state) {
Log.d(TAG, "Core listener - On Chat Room State Changed");
}
#Override
public void onQrcodeFound(Core core, String s) {
Log.d(TAG, "Core listener - On QR Code Found");
}
#Override
public void onEcCalibrationResult(Core core, EcCalibratorStatus ecCalibratorStatus,
int i) {
Log.d(TAG, "Core listener - On EC Calibration Result");
}
#Override
public void onEcCalibrationAudioInit(Core core) {
Log.d(TAG, "Core listener - On EC Calibration Audio Init");
}
#Override
public void onEcCalibrationAudioUninit(Core core) {
Log.d(TAG, "Core listener - On EC Calibration Audio Uninit");
}
};
}
private void startIterateTimer() {
cancelIterateTimer();
mIterateTimer = new Timer();
mIterateTimer.scheduleAtFixedRate(new TimerTask() {
#Override
public void run() {
if (mCore != null) mCore.iterate();
}
}, 0, TIME_ITERATE);
}
/**
* Remove iterate timer.
*/
private void cancelIterateTimer() {
if (mIterateTimer != null) mIterateTimer.cancel();
mIterateTimer = null;
}
private void enablePayloads() {
PayloadType[] audioPayloads = mCore.getAudioPayloadTypes();
for (int i = 0; i < audioPayloads.length; ++i) {
PayloadType payloadType = audioPayloads[i];
payloadType.enable(true);
}
mCore.setAudioPayloadTypes(audioPayloads);
}
}
All the configurations and audio files are ok since they were working in the first implementation, but I guess I am doing something wrong on this class.
Any help?
Finally I solved the issue, I was missing Core.start():
/**
* Initiate Linphone library, set configuration.
*
* #throws Exception
*/
public void initLibrary() throws Exception {
copyAssetsFromPackage();
Factory.instance().setDebugMode(false, LIN_TAG);
mCore = Factory.instance().createCore(
mLinphoneConfigFile, mConfigFactoryFile, this);
mCore.addListener(getCoreListener());
mCore.start();
mCore.enableIpv6(false);
PackageInfo packageInfo = getPackageManager().getPackageInfo(getPackageName(), 0);
mCore.setUserAgent(USER_AGENT, packageInfo.versionName);
mCore.setRemoteRingbackTone(mRingSoundFile);
mCore.setRing(mRingSoundFile);
mCore.setRootCa(mRootCaFile);
mCore.setPlayFile(mPauseSoundFile);
mCore.setNetworkReachable(true);
mCore.enableEchoCancellation(true);
mCore.enableEchoLimiter(true);
mCore.enableAdaptiveRateControl(true);
mCore.clearAllAuthInfo();
mCore.clearProxyConfig();
enablePayloads();
startIterateTimer();
}
When you first open the app I want a screen where you can enter the broker information and click try and save.
When clicking try it should just show a Snackbar saying if the information makes for a successful connection.
This is the code I call when the try button is pressed:
private void tryConnection(View v){
if(verifyInputs()){
Snackbar.make(v, getString(R.string.trying_connection), Snackbar.LENGTH_LONG).show();
String clientId = MqttClient.generateClientId();
MqttAndroidClient client =
new MqttAndroidClient(this.getApplicationContext(), getServerAddress(),
clientId);
try {
IMqttToken token = client.connect();
final View vinner = v;
token.setActionCallback(new IMqttActionListener() {
#Override
public void onSuccess(IMqttToken asyncActionToken) {
// We are connected
Snackbar.make(vinner, "Success", Snackbar.LENGTH_LONG).show();
}
#Override
public void onFailure(IMqttToken asyncActionToken, Throwable exception) {
// Something went wrong e.g. connection timeout or firewall problems
Snackbar.make(vinner, "Fail", Snackbar.LENGTH_LONG).show();
}
});
} catch (MqttException e) {
e.printStackTrace();
}
}
}
The problem is, onFailure doesn't seem to be called when it cannot connect to the server, but when a connection to a server is lost.
How do I just test the connection, so I can store it and go back to the main activity?
Ok, so I can't see your full service, any other implementation or how/where you are using this so I'm providing a sample of my MQTT service.
Maybe you can compare it, find any issue and fix it.
Or you can just use my implementation. Up to you. Hope it helps.
import android.app.Service;
import android.content.Intent;
import android.os.Binder;
import android.os.Handler;
import android.os.IBinder;
import android.support.annotation.Nullable;
import android.util.Log;
import org.eclipse.paho.android.service.MqttAndroidClient;
import org.eclipse.paho.client.mqttv3.IMqttActionListener;
import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken;
import org.eclipse.paho.client.mqttv3.IMqttToken;
import org.eclipse.paho.client.mqttv3.MqttCallback;
import org.eclipse.paho.client.mqttv3.MqttConnectOptions;
import org.eclipse.paho.client.mqttv3.MqttException;
import org.eclipse.paho.client.mqttv3.MqttMessage;
import org.eclipse.paho.client.mqttv3.MqttSecurityException;
import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence;
import java.util.ArrayList;
public class MyMqttService extends Service implements MqttCallback, IMqttActionListener {
private final IBinder binder = new MyBinder();
private MqttAndroidClient mqttClient;
private MqttConnectOptions mqttConnectOptions;
private static final MemoryPersistence persistence = new MemoryPersistence();
private ArrayList<MqttAndroidClient> lostConnectionClients;
private String clientId = "";
private boolean isReady = false;
private boolean doConnectTask = true;
private boolean isConnectInvoked = false;
private Handler handler = new Handler();
private final int RECONNECT_INTERVAL = 10000; // 10 seconds
private final int DISCONNECT_INTERVAL = 20000; // 20 seconds
private final int CONNECTION_TIMEOUT = 60;
private final int KEEP_ALIVE_INTERVAL = 200;
private String broker_url = "my_broker";
public MyMqttService() {}
public class MyBinder extends Binder {
public MyMqttService getService() {
return MyMqttService.this;
}
}
#Nullable
#Override
public IBinder onBind(Intent intent) {
return binder;
}
#Override
public void onCreate() {
super.onCreate();
initMqttClient();
}
#Override
public void onDestroy() {
super.onDestroy();
disconnectClients();
if (isConnectInvoked && mqttClient != null && mqttClient.isConnected()) {
try {
// unsubscribe here
unsubscribe("¯\\_(ツ)_/¯");
mqttClient.disconnect();
} catch (MqttException e) {
Log.e("TAG", e.toString());
}
}
handler.removeCallbacks(connect);
handler.removeCallbacks(disconnect);
}
private void initMqttClient() {
if(mqttClient != null) {
mqttClient = null;
}
lostConnectionClients = new ArrayList<>();
mqttConnectOptions = new MqttConnectOptions();
mqttConnectOptions.setCleanSession(true);
mqttConnectOptions.setConnectionTimeout(CONNECTION_TIMEOUT);
mqttConnectOptions.setKeepAliveInterval(KEEP_ALIVE_INTERVAL);
setNewMqttClient();
handler.post(connect);
handler.postDelayed(disconnect, DISCONNECT_INTERVAL);
}
private void setNewMqttClient() {
mqttClient = new MqttAndroidClient(MyMqttService.this, broker_url, clientId, persistence);
mqttClient.setCallback(this);
}
public Runnable connect = new Runnable() {
public void run() {
connectClient();
handler.postDelayed(connect, RECONNECT_INTERVAL);
}
};
public Runnable disconnect = new Runnable() {
public void run() {
disconnectClients();
handler.postDelayed(disconnect, DISCONNECT_INTERVAL);
}
};
private void connectClient() {
if(doConnectTask) {
doConnectTask = false;
try {
isConnectInvoked = true;
mqttClient.connect(mqttConnectOptions, null, this);
} catch (MqttException ex) {
doConnectTask = true;
Log.e("TAG", ex.toString());
}
}
}
private void disconnectClients() {
if (lostConnectionClients.size() > 0) {
// Disconnect lost connection clients
for (MqttAndroidClient client : lostConnectionClients) {
if (client.isConnected()) {
try {
client.disconnect();
} catch (MqttException e) {
Log.e("TAG", e.toString());
}
}
}
// Close already disconnected clients
for (int i = lostConnectionClients.size() - 1; i >= 0; i--) {
try {
if (!lostConnectionClients.get(i).isConnected()) {
MqttAndroidClient client = lostConnectionClients.get(i);
client.close();
lostConnectionClients.remove(i);
}
} catch (IndexOutOfBoundsException e) {
Log.e("TAG", e.toString());
}
}
}
}
#Override
public void deliveryComplete(IMqttDeliveryToken token) {
Log.e("TAG", "deliveryComplete()");
}
#Override
public void messageArrived(String topic, MqttMessage message) throws Exception {
String payload = new String(message.getPayload());
// do something
}
#Override
public void connectionLost(Throwable cause) {
Log.e("TAG", cause.getMessage());
}
#Override
public void onSuccess(IMqttToken iMqttToken) {
isReady = true;
// subscribe here
subscribe("¯\\_(ツ)_/¯");
}
#Override
public void onFailure(IMqttToken iMqttToken, Throwable throwable) {
setNewMqttClient();
isReady = false;
doConnectTask = true;
isConnectInvoked = false;
}
private void subscribe(String topic) {
try {
mqttClient.subscribe(topic, 0);
isReady = true;
} catch (MqttSecurityException mqttSexEx) {
isReady = false;
} catch (MqttException mqttEx) {
isReady = false;
}
}
private void unsubscribe(String topic) {
try {
mqttClient.unsubscribe(topic);
} catch (MqttSecurityException mqttSecEx) {
Log.e("TAG", mqttSecEx.getMessage());
} catch (MqttException mqttEx) {
Log.e("TAG", mqttEx.getMessage());
}
}
private void publish(String topic, String jsonPayload) {
if(!isReady) {
return;
}
try {
MqttMessage msg = new MqttMessage();
msg.setQos(0);
msg.setPayload(jsonPayload.getBytes("UTF-8"));
mqttClient.publish(topic, msg);
} catch (Exception ex) {
Log.e("TAG", ex.toString());
}
}
}
My other suggestion would be to just setup local broadcast so when your activity loads and you start the service, if MQTT service is able to connect, you send a broadcast saying connected and you show a Snackbar. If connection failed, you send a different broadcast and show a different message.
From looking at okHttp source code, when call.execute() is called the body being transferred from server to the client.
It doesn't make sense because it makes impossible to set deadline to okio which means i cannot give timeout to the whole request but only readTimeout and connectTimeout which have effect only until the first byte is ready for read.
Am i missing something here?
There’s no way to give a deadline to the entire request. You should open a feature request on this! OkHttp’s use of Okio is one of it’s differentiating features, and exposing more Okio functionality through OkHttp’s API is a great way to put more power in OkHttp’s users.
This is on the schedule for the next version of okhttp (https://github.com/square/okhttp/issues/2840), but for now we successfully implemented a deadline for both the request and response body reading by subclassing Call in our application in production:
package com.pushd.util;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import java.io.IOException;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.logging.Logger;
import okhttp3.Call;
import okhttp3.Callback;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import okhttp3.ResponseBody;
import okhttp3.internal.http2.StreamResetException;
import okio.Buffer;
import okio.BufferedSource;
import okio.ForwardingSource;
import okio.Okio;
/**
* An okhttp3.Call with a deadline timeout from the start of isExecuted until ResponseBody.source() is closed or unused.
*/
public class DeadlineCall implements Call {
private final static Logger LOGGER = Logger.getLogger(DeadlineCall.class.getName());
private static AtomicInteger sFutures = new AtomicInteger();
private static final ScheduledExecutorService sHTTPCancelExecutorService = Executors.newSingleThreadScheduledExecutor(new ThreadFactory() {
#Override
public Thread newThread(Runnable r) {
Thread t = new Thread(r, "DeadlineCallCancel");
t.setDaemon(true);
t.setPriority(Thread.NORM_PRIORITY);
return t;
}
});
private final Call mUnderlying;
private final int mDeadlineTimeout;
private volatile ScheduledFuture mDeadline;
private volatile boolean mDeadlineHit;
private volatile boolean mCancelled;
private volatile BufferedSource mBodySource;
DeadlineCall(Call underlying, int deadlineTimeout) {
mUnderlying = underlying;
mDeadlineTimeout = deadlineTimeout;
}
/**
* Factory wrapper for OkHttpClient.newCall(request) to create a new DeadlineCall scheduled to cancel its underlying Call after the deadline.
* #param client
* #param request
* #param deadlineTimeout in ms
* #return Call
*/
public static DeadlineCall newDeadlineCall(#NonNull OkHttpClient client, #NonNull Request request, int deadlineTimeout) {
final Call underlying = client.newCall(request);
return new DeadlineCall(underlying, deadlineTimeout);
}
/**
* Shuts down thread that cancels calls when their deadline is hit.
*/
public static void shutdownNow() {
sHTTPCancelExecutorService.shutdownNow();
}
#Override
public Request request() {
return mUnderlying.request();
}
/**
* Response MUST be closed to clean up deadline even if body is not read, e.g. on !isSuccessful
* #return
* #throws IOException
*/
#Override
public Response execute() throws IOException {
startDeadline();
try {
return wrapResponse(mUnderlying.execute());
} catch (IOException e) {
cancelDeadline();
throw wrapIfDeadline(e);
}
}
/**
* Deadline is removed when onResponse returns unless response.body().source() or a method using
* it is called synchronously from onResponse to indicate caller's committment to close it themselves.
* This includes peekBody so prefer DeadlineResponseBody.peek unless you explicitly close after peekBody.
* #param responseCallback
*/
#Override
public void enqueue(final Callback responseCallback) {
startDeadline();
mUnderlying.enqueue(new Callback() {
#Override
public void onFailure(Call underlying, IOException e) {
cancelDeadline(); // there is no body to read so no need for deadline anymore
responseCallback.onFailure(DeadlineCall.this, wrapIfDeadline(e));
}
#Override
public void onResponse(Call underlying, Response response) throws IOException {
try {
responseCallback.onResponse(DeadlineCall.this, wrapResponse(response));
if (mBodySource == null) {
cancelDeadline(); // remove deadline if body was never opened
}
} catch (IOException e) {
cancelDeadline();
throw wrapIfDeadline(e);
}
}
});
}
private IOException wrapIfDeadline(IOException e) {
if (mDeadlineHit && isCancellationException(e)) {
return new DeadlineException(e);
}
return e;
}
public class DeadlineException extends IOException {
public DeadlineException(Throwable cause) {
super(cause);
}
}
/**
* Wraps response to cancelDeadline when response closed and throw correct DeadlineException when deadline happens during response reading.
* #param response
* #return
*/
private Response wrapResponse(final Response response) {
return response.newBuilder().body(new DeadlineResponseBody(response)).build();
}
public class DeadlineResponseBody extends ResponseBody {
private final Response mResponse;
DeadlineResponseBody(final Response response) {
mResponse = response;
}
#Override
public MediaType contentType() {
return mResponse.body().contentType();
}
#Override
public long contentLength() {
return mResponse.body().contentLength();
}
/**
* #return the body source indicating it will be closed later by the caller to cancel the deadline
*/
#Override
public BufferedSource source() {
if (mBodySource == null) {
mBodySource = Okio.buffer(new ForwardingSource(mResponse.body().source()) {
#Override
public long read(Buffer sink, long byteCount) throws IOException {
try {
return super.read(sink, byteCount);
} catch (IOException e) {
throw wrapIfDeadline(e);
}
}
#Override
public void close() throws IOException {
cancelDeadline();
super.close();
}
});
}
return mBodySource;
}
/**
* #return the body source without indicating it will be closed later by caller, e.g. to peekBody on unsucessful requests
*/
public BufferedSource peekSource() {
return mResponse.body().source();
}
/**
* Copy of https://square.github.io/okhttp/3.x/okhttp/okhttp3/Response.html#peekBody-long- that uses peekSource() since Response class is final
* #param byteCount
* #return
* #throws IOException
*/
public ResponseBody peek(long byteCount) throws IOException {
BufferedSource source = peekSource();
source.request(byteCount);
Buffer copy = source.buffer().clone();
// There may be more than byteCount bytes in source.buffer(). If there is, return a prefix.
Buffer result;
if (copy.size() > byteCount) {
result = new Buffer();
result.write(copy, byteCount);
copy.clear();
} else {
result = copy;
}
return ResponseBody.create(mResponse.body().contentType(), result.size(), result);
}
}
private void startDeadline() {
mDeadline = sHTTPCancelExecutorService.schedule(new Runnable() {
#Override
public void run() {
mDeadlineHit = true;
mUnderlying.cancel(); // calls onFailure or causes body read to throw
LOGGER.fine("Deadline hit for " + request()); // should trigger a subsequent wrapIfDeadline but if we see this log line without that it means the caller orphaned us without closing
}
}, mDeadlineTimeout, TimeUnit.MILLISECONDS);
LOGGER.fine("started deadline for " + request());
if (sFutures.incrementAndGet() == 1000) {
LOGGER.warning("1000 pending DeadlineCalls, may be leaking due to not calling close()");
}
}
private void cancelDeadline() {
if (mDeadline != null) {
mDeadline.cancel(false);
mDeadline = null;
sFutures.decrementAndGet();
LOGGER.fine("canceled deadline for " + request());
} else {
LOGGER.info("deadline already canceled for " + request());
}
}
#Override
public void cancel() {
mCancelled = true;
// should trigger onFailure or raise from execute or responseCallback.onResponse which will cancelDeadline
mUnderlying.cancel();
}
#Override
public boolean isExecuted() {
return mUnderlying.isExecuted();
}
#Override
public boolean isCanceled() {
return mCancelled;
}
#Override
public Call clone() {
return new DeadlineCall(mUnderlying.clone(), mDeadlineTimeout);
}
private static boolean isCancellationException(IOException e) {
// okhttp cancel from HTTP/2 calls
if (e instanceof StreamResetException) {
switch (((StreamResetException) e).errorCode) {
case CANCEL:
return true;
}
}
// https://android.googlesource.com/platform/external/okhttp/+/master/okhttp/src/main/java/com/squareup/okhttp/Call.java#281
if (e instanceof IOException &&
e.getMessage() != null && e.getMessage().equals("Canceled")) {
return true;
}
return false;
}
}
Note that we also have a separate interceptor to timeout DNS since even our deadline doesn't cover that:
/**
* Based on http://stackoverflow.com/questions/693997/how-to-set-httpresponse-timeout-for-android-in-java/31643186#31643186
* as per https://github.com/square/okhttp/issues/95
*/
private static class DNSTimeoutInterceptor implements Interceptor {
long mTimeoutMillis;
public DNSTimeoutInterceptor(long timeoutMillis) {
mTimeoutMillis = timeoutMillis;
}
#Override
public Response intercept(final Chain chain) throws IOException {
Request request = chain.request();
Log.SplitTimer timer = (request.tag() instanceof RequestTag ? ((RequestTag) request.tag()).getTimer() : null);
// underlying call should timeout after 2 tries of 5s: https://android.googlesource.com/platform/bionic/+/android-5.1.1_r38/libc/dns/include/resolv_private.h#137
// could use our own Dns implementation that falls back to public DNS servers: https://garage.easytaxi.com/tag/dns-android-okhttp/
if (!DNSResolver.isDNSReachable(request.url().host(), mTimeoutMillis)) {
throw new UnknownHostException("DNS timeout");
}
return chain.proceed(request);
}
private static class DNSResolver implements Runnable {
private String mDomain;
private InetAddress mAddress;
public static boolean isDNSReachable(String domain, long timeoutMillis) {
try {
DNSResolver dnsRes = new DNSResolver(domain);
Thread t = new Thread(dnsRes, "DNSResolver");
t.start();
t.join(timeoutMillis);
return dnsRes.get() != null;
} catch(Exception e) {
return false;
}
}
public DNSResolver(String domain) {
this.mDomain = domain;
}
public void run() {
try {
InetAddress addr = InetAddress.getByName(mDomain);
set(addr);
} catch (UnknownHostException e) {
}
}
public synchronized void set(InetAddress inetAddr) {
this.mAddress = inetAddr;
}
public synchronized InetAddress get() {
return mAddress;
}
}
}
I'm trying to have user registration for my android application. I'm able to successfully make user register and store their details in Cloudant. They can also login using the phone they had used to register.
However, when I try using another phone to login the account, it doesn't work. Is possible to replicate all data from Cloudant so that users can also login to other phones too? Here is my code:
public class CloudantConnect {
private static final String TAG = CloudantConnect.class.getSimpleName();
private static final String DATASTORE_DIRECTORY = "data";
private Datastore datastore;
private IndexManager indexManager;
private Replicator push_replicator;
private Replicator pull_replicator;
private Context context;
private final Handler handler;
private RegisterActivity register_listener;
public CloudantConnect(Context context, String datastore_name) {
this.context = context;
// Set up information within its own folder in the application
File path = this.context.getApplicationContext().getDir(DATASTORE_DIRECTORY, Context.MODE_PRIVATE);
DatastoreManager manager = new DatastoreManager(path.getAbsolutePath());
try {
this.datastore = manager.openDatastore(datastore_name);
} catch (DatastoreNotCreatedException e) {
Log.e(TAG, "Unable to open Datastore", e);
}
// Reach here if datastore successfully created
Log.d(TAG, "Successfully set up database at" + path.getAbsolutePath());
// Set up replicator objects
try {
this.reloadReplicationSettings();
} catch (URISyntaxException e) {
Log.e(TAG, "Unable to construct remote URI from configuration", e);
}
this.handler = new Handler(Looper.getMainLooper());
Log.d(TAG, "CloudantConnect set up " + path.getAbsolutePath());
}
/**
* Creates new document for user details database storage
* #param user to store user details into database
* #return document of user details stored
*/
public User createNewUserDocument(User user) {
MutableDocumentRevision revision = new MutableDocumentRevision();
revision.body = DocumentBodyFactory.create(user.asMap());
try {
BasicDocumentRevision created = this.datastore.createDocumentFromRevision(revision);
return User.fromRevision(created);
} catch (DocumentException e) {
return null;
}
}
/**
* Sets replication listener
*/
public void setReplicationListener(RegisterActivity listener) {
this.register_listener = listener;
}
/**
* Start push replication
*/
public void startPushReplication() {
if(this.push_replicator != null) {
this.push_replicator.start();
} else {
throw new RuntimeException("Push replication not set up correctly");
}
}
/**
* Start pull replication
*/
public void startPullReplication() {
if(this.pull_replicator != null) {
this.pull_replicator.start();
} else {
throw new RuntimeException("Pull replication not set up correctly");
}
}
/**
* Stop running replication
*/
public void stopAllReplication() {
if(this.push_replicator != null) {
this.push_replicator.stop();
}
if(this.pull_replicator != null) {
this.pull_replicator.stop();
}
}
/**
* Stop running replication and reloads replication settings
* from the app's preferences.
*/
public void reloadReplicationSettings() throws URISyntaxException {
this.stopAllReplication();
// Set up new replicator objects
URI uri = this.createServerURI();
// Push replication
PushReplication push = new PushReplication();
push.source = datastore;
push.target = uri;
push_replicator = ReplicatorFactory.oneway(push);
push_replicator.getEventBus().register(this);
// Pull replication
PullReplication pull = new PullReplication();
pull.source = uri;
pull.target = datastore;
pull_replicator = ReplicatorFactory.oneway(pull);
pull_replicator.getEventBus().register(this);
Log.d(TAG, "Set up replicators for URI:" + uri.toString());
}
/**
* Calls when replication is completed
*/
public void complete(ReplicationCompleted rc) {
handler.post(new Runnable() {
#Override
public void run() {
if(register_listener != null) {
register_listener.replicationComplete();
}
}
});
}
/**
* Calls when replication has error
*/
public void error(ReplicationErrored re) {
Log.e(TAG, "Replication error:", re.errorInfo.getException());
handler.post(new Runnable() {
#Override
public void run() {
if(register_listener != null) {
register_listener.replicationError();
}
}
});
}
}
It looks like you've got all the code there to do replication. Do you actually call startPullReplication() from somewhere?
If you want your complete and error callbacks to run when replication completes/fails, you will need to add the #Subscribe annotation on them both so they're triggered when the events are put on the EventBus.