Commit a1a6b192 authored by Rafael Kellermann Streit's avatar Rafael Kellermann Streit Committed by GitHub

Merge branch 'develop' into avatar

parents 5b4d6f1f 3bcbbfbd
...@@ -3,26 +3,29 @@ package chat.rocket.android_ddp; ...@@ -3,26 +3,29 @@ package chat.rocket.android_ddp;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
import android.text.TextUtils; import android.text.TextUtils;
import io.reactivex.Flowable;
import io.reactivex.disposables.CompositeDisposable;
import org.json.JSONArray; import org.json.JSONArray;
import org.json.JSONException; import org.json.JSONException;
import org.json.JSONObject; import org.json.JSONObject;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException; import java.util.concurrent.TimeoutException;
import bolts.Task; import bolts.Task;
import bolts.TaskCompletionSource; import bolts.TaskCompletionSource;
import chat.rocket.android.log.RCLog; import chat.rocket.android.log.RCLog;
import chat.rocket.android_ddp.rx.RxWebSocket; import chat.rocket.android_ddp.rx.RxWebSocket;
import chat.rocket.android_ddp.rx.RxWebSocketCallback; import chat.rocket.android_ddp.rx.RxWebSocketCallback;
import io.reactivex.Flowable;
import io.reactivex.disposables.CompositeDisposable;
import okhttp3.OkHttpClient; import okhttp3.OkHttpClient;
public class DDPClientImpl { public class DDPClientImpl {
private final DDPClient client; private final DDPClient client;
private final RxWebSocket websocket; private RxWebSocket websocket;
private Flowable<RxWebSocketCallback.Base> flowable; private Flowable<RxWebSocketCallback.Base> flowable;
private CompositeDisposable subscriptions; private CompositeDisposable disposables;
private String currentSession;
public DDPClientImpl(DDPClient self, OkHttpClient client) { public DDPClientImpl(DDPClient self, OkHttpClient client) {
websocket = new RxWebSocket(client); websocket = new RxWebSocket(client);
...@@ -51,26 +54,24 @@ public class DDPClientImpl { ...@@ -51,26 +54,24 @@ public class DDPClientImpl {
public void connect(final TaskCompletionSource<DDPClientCallback.Connect> task, final String url, public void connect(final TaskCompletionSource<DDPClientCallback.Connect> task, final String url,
String session) { String session) {
try { try {
flowable = websocket.connect(url).autoConnect(); flowable = websocket.connect(url).autoConnect(2);
CompositeDisposable subscriptions = new CompositeDisposable(); CompositeDisposable disposables = new CompositeDisposable();
subscriptions.add( disposables.add(
flowable.filter(callback -> callback instanceof RxWebSocketCallback.Open) flowable.retry().filter(callback -> callback instanceof RxWebSocketCallback.Open)
.subscribe( .subscribe(
callback -> { callback ->
sendMessage("connect", sendMessage("connect",
json -> (TextUtils.isEmpty(session) ? json : json.put("session", session)) json -> (TextUtils.isEmpty(session) ? json : json.put("session", DDPClientImpl.this.currentSession))
.put( .put(
"version", "pre2") "version", "pre2")
.put("support", new JSONArray().put("pre2").put("pre1")), .put("support", new JSONArray().put("pre2").put("pre1")),
task); task),
}, RCLog::e
err -> {
}
) )
); );
subscriptions.add( disposables.add(
flowable.filter( flowable.filter(
callback -> callback instanceof RxWebSocketCallback.Message) callback -> callback instanceof RxWebSocketCallback.Message)
.map(callback -> ((RxWebSocketCallback.Message) callback).responseBodyString) .map(callback -> ((RxWebSocketCallback.Message) callback).responseBodyString)
...@@ -79,24 +80,25 @@ public class DDPClientImpl { ...@@ -79,24 +80,25 @@ public class DDPClientImpl {
.subscribe(response -> { .subscribe(response -> {
String msg = extractMsg(response); String msg = extractMsg(response);
if ("connected".equals(msg) && !response.isNull("session")) { if ("connected".equals(msg) && !response.isNull("session")) {
currentSession = response.optString("session");
task.trySetResult( task.trySetResult(
new DDPClientCallback.Connect(client, response.optString("session"))); new DDPClientCallback.Connect(client, response.optString("session")));
subscriptions.dispose(); disposables.clear();
} else if ("error".equals(msg) && "Already connected".equals( } else if ("error".equals(msg) && "Already connected".equals(
response.optString("reason"))) { response.optString("reason"))) {
task.trySetResult(new DDPClientCallback.Connect(client, null)); task.trySetResult(new DDPClientCallback.Connect(client, null));
subscriptions.dispose(); disposables.clear();
} else if ("failed".equals(msg)) { } else if ("failed".equals(msg)) {
task.trySetError( task.trySetError(
new DDPClientCallback.Connect.Failed(client, response.optString("version"))); new DDPClientCallback.Connect.Failed(client, response.optString("version")));
subscriptions.dispose(); disposables.clear();
} }
}, },
err -> task.trySetError(new DDPClientCallback.Connect.Timeout(client)) err -> task.trySetError(new DDPClientCallback.Connect.Timeout(client))
) )
); );
addErrorCallback(subscriptions, task); addErrorCallback(disposables, task);
subscribeBaseListeners(); subscribeBaseListeners();
} catch (Exception e) { } catch (Exception e) {
...@@ -112,34 +114,35 @@ public class DDPClientImpl { ...@@ -112,34 +114,35 @@ public class DDPClientImpl {
sendMessage("ping", json -> json.put("id", id)); sendMessage("ping", json -> json.put("id", id));
if (requested) { if (requested) {
CompositeDisposable subscriptions = new CompositeDisposable(); CompositeDisposable disposables = new CompositeDisposable();
subscriptions.add( disposables.add(
flowable.filter(callback -> callback instanceof RxWebSocketCallback.Message) flowable.filter(callback -> callback instanceof RxWebSocketCallback.Message)
.timeout(8, TimeUnit.SECONDS)
.map(callback -> ((RxWebSocketCallback.Message) callback).responseBodyString) .map(callback -> ((RxWebSocketCallback.Message) callback).responseBodyString)
.map(DDPClientImpl::toJson) .map(DDPClientImpl::toJson)
.timeout(4, TimeUnit.SECONDS)
.subscribe( .subscribe(
response -> { response -> {
String msg = extractMsg(response); String msg = extractMsg(response);
if ("pong".equals(msg)) { if ("pong".equals(msg)) {
if (response.isNull("id")) { if (response.isNull("id")) {
task.setResult(new DDPClientCallback.Ping(client, null)); task.setResult(new DDPClientCallback.Ping(client, null));
subscriptions.dispose(); disposables.clear();
} else { } else {
String _id = response.optString("id"); String _id = response.optString("id");
if (id.equals(_id)) { if (id.equals(_id)) {
task.setResult(new DDPClientCallback.Ping(client, id)); task.setResult(new DDPClientCallback.Ping(client, id));
subscriptions.dispose(); disposables.clear();
} }
} }
disposables.clear();
} }
}, },
err -> task.setError(new DDPClientCallback.Ping.Timeout(client)) err -> task.setError(new DDPClientCallback.Ping.Timeout(client))
) )
); );
addErrorCallback(subscriptions, task); addErrorCallback(disposables, task);
} else { } else {
task.trySetError(new DDPClientCallback.Closed(client)); task.trySetError(new DDPClientCallback.Closed(client));
} }
...@@ -151,9 +154,9 @@ public class DDPClientImpl { ...@@ -151,9 +154,9 @@ public class DDPClientImpl {
sendMessage("sub", json -> json.put("id", id).put("name", name).put("params", params)); sendMessage("sub", json -> json.put("id", id).put("name", name).put("params", params));
if (requested) { if (requested) {
CompositeDisposable subscriptions = new CompositeDisposable(); CompositeDisposable disposables = new CompositeDisposable();
subscriptions.add( disposables.add(
flowable.filter(callback -> callback instanceof RxWebSocketCallback.Message) flowable.filter(callback -> callback instanceof RxWebSocketCallback.Message)
.map(callback -> ((RxWebSocketCallback.Message) callback).responseBodyString) .map(callback -> ((RxWebSocketCallback.Message) callback).responseBodyString)
.map(DDPClientImpl::toJson) .map(DDPClientImpl::toJson)
...@@ -166,7 +169,7 @@ public class DDPClientImpl { ...@@ -166,7 +169,7 @@ public class DDPClientImpl {
String _id = ids.optString(i); String _id = ids.optString(i);
if (id.equals(_id)) { if (id.equals(_id)) {
task.setResult(new DDPSubscription.Ready(client, id)); task.setResult(new DDPSubscription.Ready(client, id));
subscriptions.dispose(); disposables.clear();
break; break;
} }
} }
...@@ -176,16 +179,15 @@ public class DDPClientImpl { ...@@ -176,16 +179,15 @@ public class DDPClientImpl {
if (id.equals(_id)) { if (id.equals(_id)) {
task.setError(new DDPSubscription.NoSub.Error(client, id, task.setError(new DDPSubscription.NoSub.Error(client, id,
response.optJSONObject("error"))); response.optJSONObject("error")));
subscriptions.dispose(); disposables.clear();
} }
} }
}, },
err -> { RCLog::e
}
) )
); );
addErrorCallback(subscriptions, task); addErrorCallback(disposables, task);
} else { } else {
task.trySetError(new DDPClientCallback.Closed(client)); task.trySetError(new DDPClientCallback.Closed(client));
} }
...@@ -197,9 +199,9 @@ public class DDPClientImpl { ...@@ -197,9 +199,9 @@ public class DDPClientImpl {
final boolean requested = sendMessage("unsub", json -> json.put("id", id)); final boolean requested = sendMessage("unsub", json -> json.put("id", id));
if (requested) { if (requested) {
CompositeDisposable subscriptions = new CompositeDisposable(); CompositeDisposable disposables = new CompositeDisposable();
subscriptions.add( disposables.add(
flowable.filter(callback -> callback instanceof RxWebSocketCallback.Message) flowable.filter(callback -> callback instanceof RxWebSocketCallback.Message)
.map(callback -> ((RxWebSocketCallback.Message) callback).responseBodyString) .map(callback -> ((RxWebSocketCallback.Message) callback).responseBodyString)
.map(DDPClientImpl::toJson) .map(DDPClientImpl::toJson)
...@@ -210,7 +212,7 @@ public class DDPClientImpl { ...@@ -210,7 +212,7 @@ public class DDPClientImpl {
String _id = response.optString("id"); String _id = response.optString("id");
if (id.equals(_id)) { if (id.equals(_id)) {
task.setResult(new DDPSubscription.NoSub(client, id)); task.setResult(new DDPSubscription.NoSub(client, id));
subscriptions.dispose(); disposables.clear();
} }
} }
}, },
...@@ -219,7 +221,7 @@ public class DDPClientImpl { ...@@ -219,7 +221,7 @@ public class DDPClientImpl {
) )
); );
addErrorCallback(subscriptions, task); addErrorCallback(disposables, task);
} else { } else {
task.trySetError(new DDPClientCallback.Closed(client)); task.trySetError(new DDPClientCallback.Closed(client));
} }
...@@ -232,9 +234,9 @@ public class DDPClientImpl { ...@@ -232,9 +234,9 @@ public class DDPClientImpl {
json -> json.put("method", method).put("params", params).put("id", id)); json -> json.put("method", method).put("params", params).put("id", id));
if (requested) { if (requested) {
CompositeDisposable subscriptions = new CompositeDisposable(); CompositeDisposable disposables = new CompositeDisposable();
subscriptions.add( disposables.add(
flowable.filter(callback -> callback instanceof RxWebSocketCallback.Message) flowable.filter(callback -> callback instanceof RxWebSocketCallback.Message)
.map(callback -> ((RxWebSocketCallback.Message) callback).responseBodyString) .map(callback -> ((RxWebSocketCallback.Message) callback).responseBodyString)
.map(DDPClientImpl::toJson) .map(DDPClientImpl::toJson)
...@@ -252,7 +254,7 @@ public class DDPClientImpl { ...@@ -252,7 +254,7 @@ public class DDPClientImpl {
String result = response.optString("result"); String result = response.optString("result");
task.setResult(new DDPClientCallback.RPC(client, id, result)); task.setResult(new DDPClientCallback.RPC(client, id, result));
} }
subscriptions.dispose(); disposables.clear();
} }
} }
}, },
...@@ -264,20 +266,20 @@ public class DDPClientImpl { ...@@ -264,20 +266,20 @@ public class DDPClientImpl {
) )
); );
addErrorCallback(subscriptions, task); addErrorCallback(disposables, task);
} else { } else {
task.trySetError(new DDPClientCallback.Closed(client)); task.trySetError(new DDPClientCallback.Closed(client));
} }
} }
private void subscribeBaseListeners() { private void subscribeBaseListeners() {
if (subscriptions != null && if (disposables != null &&
subscriptions.size() > 0 && !subscriptions.isDisposed()) { disposables.size() > 0 && !disposables.isDisposed()) {
return; return;
} }
subscriptions = new CompositeDisposable(); disposables = new CompositeDisposable();
subscriptions.add( disposables.add(
flowable.filter(callback -> callback instanceof RxWebSocketCallback.Message) flowable.filter(callback -> callback instanceof RxWebSocketCallback.Message)
.map(callback -> ((RxWebSocketCallback.Message) callback).responseBodyString) .map(callback -> ((RxWebSocketCallback.Message) callback).responseBodyString)
.map(DDPClientImpl::toJson) .map(DDPClientImpl::toJson)
...@@ -292,8 +294,7 @@ public class DDPClientImpl { ...@@ -292,8 +294,7 @@ public class DDPClientImpl {
} }
} }
}, },
err -> { RCLog::e
}
) )
); );
} }
...@@ -342,8 +343,8 @@ public class DDPClientImpl { ...@@ -342,8 +343,8 @@ public class DDPClientImpl {
} }
public void unsubscribeBaseListeners() { public void unsubscribeBaseListeners() {
if (subscriptions.size() > 0 || !subscriptions.isDisposed()) { if (disposables.size() > 0 || !disposables.isDisposed()) {
subscriptions.dispose(); disposables.clear();
} }
} }
...@@ -354,13 +355,7 @@ public class DDPClientImpl { ...@@ -354,13 +355,7 @@ public class DDPClientImpl {
.cast(RxWebSocketCallback.Close.class) .cast(RxWebSocketCallback.Close.class)
.subscribe( .subscribe(
task::setResult, task::setResult,
err -> { err -> setTaskError(task, err)
if (err instanceof Exception) {
task.setError((Exception) err);
} else {
task.setError(new Exception(err));
}
}
); );
return task.getTask().onSuccessTask(_task -> { return task.getTask().onSuccessTask(_task -> {
...@@ -373,28 +368,29 @@ public class DDPClientImpl { ...@@ -373,28 +368,29 @@ public class DDPClientImpl {
try { try {
JSONObject origJson = new JSONObject().put("msg", msg); JSONObject origJson = new JSONObject().put("msg", msg);
String msg2 = (json == null ? origJson : json.create(origJson)).toString(); String msg2 = (json == null ? origJson : json.create(origJson)).toString();
return websocket.sendText(msg2); websocket.sendText(msg2);
} catch (Exception e) { } catch (Exception e) {
RCLog.e(e); RCLog.e(e);
return false;
} }
return true; // ignore exception here. return true; // ignore exception here.
} }
private void sendMessage(String msg, @Nullable JSONBuilder json, private void sendMessage(String msg, @Nullable JSONBuilder json,
TaskCompletionSource<?> taskForSetError) { TaskCompletionSource<?> taskForSetError) {
if (!sendMessage(msg, json)) { if (!sendMessage(msg, json) && taskForSetError != null) {
taskForSetError.trySetError(new DDPClientCallback.Closed(client)); taskForSetError.trySetError(new DDPClientCallback.Closed(client));
} }
} }
private void addErrorCallback(CompositeDisposable subscriptions, TaskCompletionSource<?> task) { private void addErrorCallback(CompositeDisposable disposables, TaskCompletionSource<?> task) {
subscriptions.add( disposables.add(
flowable.subscribe( flowable.subscribe(
base -> { base -> {
}, },
err -> { err -> {
task.trySetError(new Exception(err)); task.trySetError(new Exception(err));
subscriptions.dispose(); disposables.clear();
} }
) )
); );
...@@ -408,6 +404,14 @@ public class DDPClientImpl { ...@@ -408,6 +404,14 @@ public class DDPClientImpl {
} }
} }
private void setTaskError(TaskCompletionSource<? extends RxWebSocketCallback.Base> task, Throwable throwable) {
if (throwable instanceof Exception) {
task.setError((Exception) throwable);
} else {
task.setError(new Exception(throwable));
}
}
private interface JSONBuilder { private interface JSONBuilder {
@NonNull @NonNull
JSONObject create(JSONObject root) throws JSONException; JSONObject create(JSONObject root) throws JSONException;
......
package chat.rocket.android_ddp.rx; package chat.rocket.android_ddp.rx;
import java.io.IOException;
import java.util.concurrent.TimeUnit;
import chat.rocket.android.log.RCLog;
import io.reactivex.BackpressureStrategy; import io.reactivex.BackpressureStrategy;
import io.reactivex.Flowable; import io.reactivex.Flowable;
import io.reactivex.FlowableOnSubscribe; import io.reactivex.FlowableOnSubscribe;
import io.reactivex.exceptions.OnErrorNotImplementedException; import io.reactivex.exceptions.OnErrorNotImplementedException;
import io.reactivex.flowables.ConnectableFlowable; import io.reactivex.flowables.ConnectableFlowable;
import java.io.IOException;
import chat.rocket.android.log.RCLog;
import okhttp3.OkHttpClient; import okhttp3.OkHttpClient;
import okhttp3.Request; import okhttp3.Request;
import okhttp3.Response; import okhttp3.Response;
...@@ -15,8 +16,10 @@ import okhttp3.WebSocket; ...@@ -15,8 +16,10 @@ import okhttp3.WebSocket;
import okhttp3.WebSocketListener; import okhttp3.WebSocketListener;
public class RxWebSocket { public class RxWebSocket {
public static final int REASON_NETWORK_ERROR = 100;
private OkHttpClient httpClient; private OkHttpClient httpClient;
private WebSocket webSocket; private WebSocket webSocket;
private boolean hadErrorsBefore;
public RxWebSocket(OkHttpClient client) { public RxWebSocket(OkHttpClient client) {
httpClient = client; httpClient = client;
...@@ -30,6 +33,7 @@ public class RxWebSocket { ...@@ -30,6 +33,7 @@ public class RxWebSocket {
.newWebSocket(request, new WebSocketListener() { .newWebSocket(request, new WebSocketListener() {
@Override @Override
public void onOpen(WebSocket webSocket, Response response) { public void onOpen(WebSocket webSocket, Response response) {
hadErrorsBefore = false;
RxWebSocket.this.webSocket = webSocket; RxWebSocket.this.webSocket = webSocket;
emitter.onNext(new RxWebSocketCallback.Open(RxWebSocket.this.webSocket, response)); emitter.onNext(new RxWebSocketCallback.Open(RxWebSocket.this.webSocket, response));
} }
...@@ -37,7 +41,11 @@ public class RxWebSocket { ...@@ -37,7 +41,11 @@ public class RxWebSocket {
@Override @Override
public void onFailure(WebSocket webSocket, Throwable err, Response response) { public void onFailure(WebSocket webSocket, Throwable err, Response response) {
try { try {
emitter.onError(new RxWebSocketCallback.Failure(webSocket, err, response)); if (!hadErrorsBefore) {
hadErrorsBefore = true;
emitter.onNext(new RxWebSocketCallback.Close(webSocket, REASON_NETWORK_ERROR, err.getMessage()));
emitter.onComplete();
}
} catch (OnErrorNotImplementedException ex) { } catch (OnErrorNotImplementedException ex) {
RCLog.w(ex, "OnErrorNotImplementedException ignored"); RCLog.w(ex, "OnErrorNotImplementedException ignored");
} }
...@@ -51,11 +59,10 @@ public class RxWebSocket { ...@@ -51,11 +59,10 @@ public class RxWebSocket {
@Override @Override
public void onClosed(WebSocket webSocket, int code, String reason) { public void onClosed(WebSocket webSocket, int code, String reason) {
emitter.onNext(new RxWebSocketCallback.Close(webSocket, code, reason)); emitter.onNext(new RxWebSocketCallback.Close(webSocket, code, reason));
emitter.onComplete();
} }
}), }),
BackpressureStrategy.BUFFER BackpressureStrategy.BUFFER
).publish(); ).delay(4, TimeUnit.SECONDS).publish();
} }
public boolean sendText(String message) throws IOException { public boolean sendText(String message) throws IOException {
......
package chat.rocket.android_ddp.rx; package chat.rocket.android_ddp.rx;
import static android.R.attr.type;
import chat.rocket.android.log.RCLog; import chat.rocket.android.log.RCLog;
import okhttp3.Response; import okhttp3.Response;
import okhttp3.WebSocket; import okhttp3.WebSocket;
...@@ -28,25 +26,8 @@ public class RxWebSocketCallback { ...@@ -28,25 +26,8 @@ public class RxWebSocketCallback {
public Open(WebSocket websocket, Response response) { public Open(WebSocket websocket, Response response) {
super("Open", websocket); super("Open", websocket);
this.response = response; this.response = response;
} if (response != null && response.body() != null) {
} this.response.body().close();
public static class Failure extends Exception {
public WebSocket ws;
public Response response;
public Failure(WebSocket websocket, Throwable err, Response response) {
super(err);
this.ws = websocket;
this.response = response;
}
@Override
public String toString() {
if (response != null) {
return "[" + type + "] " + response.message();
} else {
return super.toString();
} }
} }
} }
......
...@@ -35,7 +35,7 @@ android { ...@@ -35,7 +35,7 @@ android {
applicationId "chat.rocket.android" applicationId "chat.rocket.android"
minSdkVersion 16 minSdkVersion 16
targetSdkVersion rootProject.ext.targetSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 26 versionCode 27
versionName "1.0.16" versionName "1.0.16"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
......
...@@ -16,11 +16,11 @@ import chat.rocket.android.fragment.chatroom.HomeFragment; ...@@ -16,11 +16,11 @@ import chat.rocket.android.fragment.chatroom.HomeFragment;
import chat.rocket.android.fragment.chatroom.RoomFragment; import chat.rocket.android.fragment.chatroom.RoomFragment;
import chat.rocket.android.fragment.sidebar.SidebarMainFragment; import chat.rocket.android.fragment.sidebar.SidebarMainFragment;
import chat.rocket.android.helper.KeyboardHelper; import chat.rocket.android.helper.KeyboardHelper;
import chat.rocket.android.service.ConnectivityManager;
import chat.rocket.android.widget.RoomToolbar;
import chat.rocket.core.interactors.CanCreateRoomInteractor; import chat.rocket.core.interactors.CanCreateRoomInteractor;
import chat.rocket.core.interactors.RoomInteractor; import chat.rocket.core.interactors.RoomInteractor;
import chat.rocket.core.interactors.SessionInteractor; import chat.rocket.core.interactors.SessionInteractor;
import chat.rocket.android.service.ConnectivityManager;
import chat.rocket.android.widget.RoomToolbar;
import chat.rocket.persistence.realm.repositories.RealmRoomRepository; import chat.rocket.persistence.realm.repositories.RealmRoomRepository;
import chat.rocket.persistence.realm.repositories.RealmSessionRepository; import chat.rocket.persistence.realm.repositories.RealmSessionRepository;
import chat.rocket.persistence.realm.repositories.RealmUserRepository; import chat.rocket.persistence.realm.repositories.RealmUserRepository;
...@@ -34,6 +34,7 @@ public class MainActivity extends AbstractAuthedActivity implements MainContract ...@@ -34,6 +34,7 @@ public class MainActivity extends AbstractAuthedActivity implements MainContract
private StatusTicker statusTicker; private StatusTicker statusTicker;
private MainContract.Presenter presenter; private MainContract.Presenter presenter;
private RoomFragment roomFragment;
@Override @Override
protected int getLayoutContainerForFragment() { protected int getLayoutContainerForFragment() {
...@@ -179,7 +180,8 @@ public class MainActivity extends AbstractAuthedActivity implements MainContract ...@@ -179,7 +180,8 @@ public class MainActivity extends AbstractAuthedActivity implements MainContract
@Override @Override
public void showRoom(String hostname, String roomId) { public void showRoom(String hostname, String roomId) {
showFragment(RoomFragment.create(hostname, roomId)); roomFragment = RoomFragment.create(hostname, roomId);
showFragment(roomFragment);
closeSidebarIfNeeded(); closeSidebarIfNeeded();
KeyboardHelper.hideSoftKeyboard(this); KeyboardHelper.hideSoftKeyboard(this);
} }
...@@ -222,6 +224,9 @@ public class MainActivity extends AbstractAuthedActivity implements MainContract ...@@ -222,6 +224,9 @@ public class MainActivity extends AbstractAuthedActivity implements MainContract
@Override @Override
public void showConnectionOk() { public void showConnectionOk() {
statusTicker.updateStatus(StatusTicker.STATUS_DISMISS, null); statusTicker.updateStatus(StatusTicker.STATUS_DISMISS, null);
if (roomFragment != null) {
roomFragment.refreshRoom();
}
} }
//TODO: consider this class to define in layouthelper for more complicated operation. //TODO: consider this class to define in layouthelper for more complicated operation.
......
...@@ -3,22 +3,23 @@ package chat.rocket.android.activity; ...@@ -3,22 +3,23 @@ package chat.rocket.android.activity;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import android.support.v4.util.Pair; import android.support.v4.util.Pair;
import io.reactivex.Flowable;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.Disposable;
import chat.rocket.android.BackgroundLooper; import chat.rocket.android.BackgroundLooper;
import chat.rocket.android.RocketChatCache; import chat.rocket.android.RocketChatCache;
import chat.rocket.android.api.MethodCallHelper; import chat.rocket.android.api.MethodCallHelper;
import chat.rocket.android.helper.LogIfError; import chat.rocket.android.helper.LogIfError;
import chat.rocket.android.helper.Logger; import chat.rocket.android.helper.Logger;
import chat.rocket.android.service.ConnectivityManagerApi; import chat.rocket.android.service.ConnectivityManagerApi;
import chat.rocket.android.service.ServerConnectivity;
import chat.rocket.android.shared.BasePresenter; import chat.rocket.android.shared.BasePresenter;
import chat.rocket.core.interactors.CanCreateRoomInteractor; import chat.rocket.core.interactors.CanCreateRoomInteractor;
import chat.rocket.core.interactors.RoomInteractor; import chat.rocket.core.interactors.RoomInteractor;
import chat.rocket.core.interactors.SessionInteractor; import chat.rocket.core.interactors.SessionInteractor;
import chat.rocket.core.models.Session; import chat.rocket.core.models.Session;
import chat.rocket.core.models.User; import chat.rocket.core.models.User;
import hu.akarnokd.rxjava.interop.RxJavaInterop;
import io.reactivex.Flowable;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.Disposable;
public class MainPresenter extends BasePresenter<MainContract.View> public class MainPresenter extends BasePresenter<MainContract.View>
implements MainContract.Presenter { implements MainContract.Presenter {
...@@ -63,6 +64,7 @@ public class MainPresenter extends BasePresenter<MainContract.View> ...@@ -63,6 +64,7 @@ public class MainPresenter extends BasePresenter<MainContract.View>
openRoom(); openRoom();
subscribeToNetworkChanges();
subscribeToUnreadCount(); subscribeToUnreadCount();
subscribeToSession(); subscribeToSession();
setUserOnline(); setUserOnline();
...@@ -96,10 +98,8 @@ public class MainPresenter extends BasePresenter<MainContract.View> ...@@ -96,10 +98,8 @@ public class MainPresenter extends BasePresenter<MainContract.View>
@Override @Override
public void onRetryLogin() { public void onRetryLogin() {
final Disposable subscription = sessionInteractor.retryLogin() view.showConnecting();
.subscribe(); connectivityManagerApi.keepAliveServer();
addSubscription(subscription);
} }
private void openRoom() { private void openRoom() {
...@@ -161,6 +161,23 @@ public class MainPresenter extends BasePresenter<MainContract.View> ...@@ -161,6 +161,23 @@ public class MainPresenter extends BasePresenter<MainContract.View>
addSubscription(subscription); addSubscription(subscription);
} }
private void subscribeToNetworkChanges() {
Disposable disposable = RxJavaInterop.toV2Flowable(connectivityManagerApi.getServerConnectivityAsObservable())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
connectivity -> {
if (connectivity.state == ServerConnectivity.STATE_CONNECTED) {
view.showConnectionOk();
return;
}
view.showConnecting();
},
Logger::report
);
addSubscription(disposable);
}
private void setUserOnline() { private void setUserOnline() {
methodCallHelper.setUserPresence(User.STATUS_ONLINE) methodCallHelper.setUserPresence(User.STATUS_ONLINE)
.continueWith(new LogIfError()); .continueWith(new LogIfError());
......
package chat.rocket.android.api; package chat.rocket.android.api;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
import io.reactivex.Flowable;
import org.json.JSONArray; import org.json.JSONArray;
import org.json.JSONException; import org.json.JSONException;
import java.util.UUID; import java.util.UUID;
import bolts.Task; import bolts.Task;
import chat.rocket.android.helper.OkHttpHelper; import chat.rocket.android.helper.OkHttpHelper;
import chat.rocket.android.helper.TextUtils; import chat.rocket.android.helper.TextUtils;
...@@ -13,6 +14,7 @@ import chat.rocket.android.log.RCLog; ...@@ -13,6 +14,7 @@ import chat.rocket.android.log.RCLog;
import chat.rocket.android_ddp.DDPClient; import chat.rocket.android_ddp.DDPClient;
import chat.rocket.android_ddp.DDPClientCallback; import chat.rocket.android_ddp.DDPClientCallback;
import chat.rocket.android_ddp.DDPSubscription; import chat.rocket.android_ddp.DDPSubscription;
import io.reactivex.Flowable;
/** /**
* DDP client wrapper. * DDP client wrapper.
......
...@@ -49,5 +49,7 @@ public interface RoomContract { ...@@ -49,5 +49,7 @@ public interface RoomContract {
void onUnreadCount(); void onUnreadCount();
void onMarkAsRead(); void onMarkAsRead();
void refreshRoom();
} }
} }
...@@ -21,6 +21,14 @@ import android.support.v7.widget.LinearLayoutManager; ...@@ -21,6 +21,14 @@ import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView; import android.support.v7.widget.RecyclerView;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import com.fernandocejas.arrow.optional.Optional;
import com.jakewharton.rxbinding2.support.v4.widget.RxDrawerLayout;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
import chat.rocket.android.BackgroundLooper; import chat.rocket.android.BackgroundLooper;
import chat.rocket.android.R; import chat.rocket.android.R;
import chat.rocket.android.api.MethodCallHelper; import chat.rocket.android.api.MethodCallHelper;
...@@ -70,15 +78,10 @@ import chat.rocket.persistence.realm.repositories.RealmSessionRepository; ...@@ -70,15 +78,10 @@ import chat.rocket.persistence.realm.repositories.RealmSessionRepository;
import chat.rocket.persistence.realm.repositories.RealmSpotlightRoomRepository; import chat.rocket.persistence.realm.repositories.RealmSpotlightRoomRepository;
import chat.rocket.persistence.realm.repositories.RealmSpotlightUserRepository; import chat.rocket.persistence.realm.repositories.RealmSpotlightUserRepository;
import chat.rocket.persistence.realm.repositories.RealmUserRepository; import chat.rocket.persistence.realm.repositories.RealmUserRepository;
import com.fernandocejas.arrow.optional.Optional;
import com.jakewharton.rxbinding2.support.v4.widget.RxDrawerLayout;
import io.reactivex.Single; import io.reactivex.Single;
import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.CompositeDisposable; import io.reactivex.disposables.CompositeDisposable;
import io.reactivex.disposables.Disposable; import io.reactivex.disposables.Disposable;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
import permissions.dispatcher.NeedsPermission; import permissions.dispatcher.NeedsPermission;
import permissions.dispatcher.RuntimePermissions; import permissions.dispatcher.RuntimePermissions;
...@@ -251,8 +254,8 @@ public class RoomFragment extends AbstractChatRoomFragment implements ...@@ -251,8 +254,8 @@ public class RoomFragment extends AbstractChatRoomFragment implements
protected Snackbar getUnreadCountIndicatorView(int count) { protected Snackbar getUnreadCountIndicatorView(int count) {
// TODO: replace with another custom View widget, not to hide message composer. // TODO: replace with another custom View widget, not to hide message composer.
final String caption = getResources().getString( final String caption = getResources().getQuantityString(
R.string.fmt_dialog_view_latest_message_title, count); R.plurals.fmt_dialog_view_latest_message_title, count, count);
return Snackbar.make(rootView, caption, Snackbar.LENGTH_LONG) return Snackbar.make(rootView, caption, Snackbar.LENGTH_LONG)
.setAction(R.string.dialog_view_latest_message_action, view -> scrollToLatestMessage()); .setAction(R.string.dialog_view_latest_message_action, view -> scrollToLatestMessage());
...@@ -599,4 +602,8 @@ public class RoomFragment extends AbstractChatRoomFragment implements ...@@ -599,4 +602,8 @@ public class RoomFragment extends AbstractChatRoomFragment implements
edittingMessage = message; edittingMessage = message;
messageFormManager.setEditMessage(message.getMessage()); messageFormManager.setEditMessage(message.getMessage());
} }
public void refreshRoom() {
presenter.loadMessages();
}
} }
\ No newline at end of file
...@@ -55,7 +55,11 @@ public class RoomPresenter extends BasePresenter<RoomContract.View> ...@@ -55,7 +55,11 @@ public class RoomPresenter extends BasePresenter<RoomContract.View>
@Override @Override
public void bindView(@NonNull RoomContract.View view) { public void bindView(@NonNull RoomContract.View view) {
super.bindView(view); super.bindView(view);
refreshRoom();
}
@Override
public void refreshRoom() {
getRoomRoles(); getRoomRoles();
getRoomInfo(); getRoomInfo();
getRoomHistoryStateInfo(); getRoomHistoryStateInfo();
......
...@@ -192,7 +192,7 @@ public class UsersOfRoomDialogFragment extends AbstractChatRoomDialogFragment { ...@@ -192,7 +192,7 @@ public class UsersOfRoomDialogFragment extends AbstractChatRoomDialogFragment {
*/ */
private void onRenderTotalCount(long total) { private void onRenderTotalCount(long total) {
TextView userCount = (TextView) getDialog().findViewById(R.id.room_user_count); TextView userCount = (TextView) getDialog().findViewById(R.id.room_user_count);
userCount.setText(getString(R.string.fmt_room_user_count, total)); userCount.setText(getResources().getQuantityString(R.plurals.fmt_room_user_count, (int) total, total));
} }
/** /**
......
package chat.rocket.android.push; package chat.rocket.android.push;
import android.app.Notification; import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager; import android.app.NotificationManager;
import android.app.PendingIntent; import android.app.PendingIntent;
import android.content.ContentResolver; import android.content.ContentResolver;
...@@ -13,13 +14,16 @@ import android.graphics.Bitmap; ...@@ -13,13 +14,16 @@ import android.graphics.Bitmap;
import android.graphics.BitmapFactory; import android.graphics.BitmapFactory;
import android.graphics.Color; import android.graphics.Color;
import android.net.Uri; import android.net.Uri;
import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.support.annotation.RequiresApi;
import android.support.v4.app.NotificationCompat; import android.support.v4.app.NotificationCompat;
import android.support.v4.app.RemoteInput; import android.support.v4.app.RemoteInput;
import android.support.v4.util.SparseArrayCompat; import android.support.v4.util.SparseArrayCompat;
import android.text.Html; import android.text.Html;
import android.text.Spanned; import android.text.Spanned;
import android.util.Log; import android.util.Log;
import org.json.JSONArray; import org.json.JSONArray;
import org.json.JSONException; import org.json.JSONException;
import org.json.JSONObject; import org.json.JSONObject;
...@@ -31,6 +35,7 @@ import java.net.URL; ...@@ -31,6 +35,7 @@ import java.net.URL;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Random; import java.util.Random;
import chat.rocket.android.activity.MainActivity; import chat.rocket.android.activity.MainActivity;
import chat.rocket.android.helper.ServerPolicyHelper; import chat.rocket.android.helper.ServerPolicyHelper;
import chat.rocket.android.service.ConnectivityManager; import chat.rocket.android.service.ConnectivityManager;
...@@ -93,7 +98,7 @@ public class PushNotificationHandler implements PushConstants { ...@@ -93,7 +98,7 @@ public class PushNotificationHandler implements PushConstants {
} }
public void createNotification(Context context, Bundle extras) { public void createNotification(Context context, Bundle extras) {
NotificationManager mNotificationManager = NotificationManager notificationManager =
(NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
String appName = getAppName(context); String appName = getAppName(context);
String packageName = context.getPackageName(); String packageName = context.getPackageName();
...@@ -117,13 +122,6 @@ public class PushNotificationHandler implements PushConstants { ...@@ -117,13 +122,6 @@ public class PushNotificationHandler implements PushConstants {
PendingIntent contentIntent = PendingIntent PendingIntent contentIntent = PendingIntent
.getActivity(context, requestCode, notificationIntent, PendingIntent.FLAG_UPDATE_CURRENT); .getActivity(context, requestCode, notificationIntent, PendingIntent.FLAG_UPDATE_CURRENT);
NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(context)
.setWhen(System.currentTimeMillis())
.setContentTitle(fromHtml(extras.getString(TITLE)))
.setTicker(fromHtml(extras.getString(TITLE)))
.setContentIntent(contentIntent)
.setAutoCancel(true);
SharedPreferences prefs = context SharedPreferences prefs = context
.getSharedPreferences(PushConstants.COM_ADOBE_PHONEGAP_PUSH, Context.MODE_PRIVATE); .getSharedPreferences(PushConstants.COM_ADOBE_PHONEGAP_PUSH, Context.MODE_PRIVATE);
String localIcon = prefs.getString(ICON, null); String localIcon = prefs.getString(ICON, null);
...@@ -135,91 +133,62 @@ public class PushNotificationHandler implements PushConstants { ...@@ -135,91 +133,62 @@ public class PushNotificationHandler implements PushConstants {
Log.d(LOG_TAG, "stored sound=" + soundOption); Log.d(LOG_TAG, "stored sound=" + soundOption);
Log.d(LOG_TAG, "stored vibrate=" + vibrateOption); Log.d(LOG_TAG, "stored vibrate=" + vibrateOption);
/* if (Build.VERSION.SDK_INT >= 26) {
* Notification Vibration String channelId = "rocket-chat-channel";
*/ CharSequence name = "RocketChatMessage";
int importance = NotificationManager.IMPORTANCE_HIGH;
setNotificationVibration(extras, vibrateOption, notificationBuilder); NotificationChannel channel = new NotificationChannel(channelId, name, importance);
channel.enableLights(true);
notificationManager.createNotificationChannel(channel);
/* Notification.Builder notificationBuilder = new Notification.Builder(context, channelId)
* Notification Icon Color .setWhen(System.currentTimeMillis())
* .setContentTitle(fromHtml(extras.getString(TITLE)))
* Sets the small-icon background color of the notification. .setTicker(fromHtml(extras.getString(TITLE)))
* To use, add the `iconColor` key to plugin android options .setContentIntent(contentIntent)
* .setChannelId(channelId)
*/ .setAutoCancel(true);
setNotificationIconColor(extras.getString("color"), notificationBuilder, localIconColor);
/* setNotificationImportance(extras, channel);
* Notification Icon setNotificationVibration(extras, vibrateOption, channel);
* setNotificationMessage(notId, extras, notificationBuilder);
* Sets the small-icon of the notification. setNotificationCount(context, extras, notificationBuilder);
* setNotificationSmallIcon(extras, packageName, resources, notificationBuilder,
* - checks the plugin options for `icon` key
* - if none, uses the application icon
*
* The icon value must be a string that maps to a drawable resource.
* If no resource is found, falls
*
*/
setNotificationSmallIcon(context, extras, packageName, resources, notificationBuilder,
localIcon); localIcon);
/*
* Notification Large-Icon
*
* Sets the large-icon of the notification
*
* - checks the gcm data for the `image` key
* - checks to see if remote image, loads it.
* - checks to see if assets image, Loads It.
* - checks to see if resource image, LOADS IT!
* - if none, we don't set the large icon
*
*/
setNotificationLargeIcon(context, extras, packageName, resources, notificationBuilder); setNotificationLargeIcon(context, extras, packageName, resources, notificationBuilder);
setNotificationLedColor(extras, channel);
if (soundOption) {
setNotificationSound(context, extras, channel);
}
createActions(context, extras, notificationBuilder, resources, packageName, notId);
notificationManager.notify(notId, notificationBuilder.build());
} else {
NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(context)
.setWhen(System.currentTimeMillis())
.setContentTitle(fromHtml(extras.getString(TITLE)))
.setTicker(fromHtml(extras.getString(TITLE)))
.setContentIntent(contentIntent)
.setAutoCancel(true);
/* setNotificationCount(context, extras, notificationBuilder);
* Notification Sound setNotificationVibration(extras, vibrateOption, notificationBuilder);
*/ setNotificationIconColor(extras.getString("color"), notificationBuilder, localIconColor);
setNotificationSmallIcon(extras, packageName, resources, notificationBuilder,
localIcon);
setNotificationLargeIcon(context, extras, packageName, resources, notificationBuilder);
if (soundOption) { if (soundOption) {
setNotificationSound(context, extras, notificationBuilder); setNotificationSound(context, extras, notificationBuilder);
} }
/*
* LED Notification
*/
setNotificationLedColor(extras, notificationBuilder); setNotificationLedColor(extras, notificationBuilder);
/*
* Priority Notification
*/
setNotificationPriority(extras, notificationBuilder); setNotificationPriority(extras, notificationBuilder);
/*
* Notification message
*/
setNotificationMessage(notId, extras, notificationBuilder); setNotificationMessage(notId, extras, notificationBuilder);
/*
* Notification count
*/
setNotificationCount(context, extras, notificationBuilder);
/*
* Notification count
*/
setVisibility(context, extras, notificationBuilder); setVisibility(context, extras, notificationBuilder);
/*
* Notification add actions
*/
createActions(context, extras, notificationBuilder, resources, packageName, notId); createActions(context, extras, notificationBuilder, resources, packageName, notId);
notificationManager.notify(appName, notId, notificationBuilder.build());
mNotificationManager.notify(appName, notId, notificationBuilder.build()); }
} }
private void createActions(Context context, Bundle extras, NotificationCompat.Builder mBuilder, private void createActions(Context context, Bundle extras, NotificationCompat.Builder builder,
Resources resources, String packageName, int notId) { Resources resources, String packageName, int notId) {
Log.d(LOG_TAG, "create actions: with in-line"); Log.d(LOG_TAG, "create actions: with in-line");
String actions = extras.getString(ACTIONS); String actions = extras.getString(ACTIONS);
...@@ -298,16 +267,112 @@ public class PushNotificationHandler implements PushConstants { ...@@ -298,16 +267,112 @@ public class PushNotificationHandler implements PushConstants {
wActions.add(actionBuilder.build()); wActions.add(actionBuilder.build());
if (inline) { if (inline) {
mBuilder.addAction(wAction); builder.addAction(wAction);
} else {
builder.addAction(
resources.getIdentifier(action.optString(ICON, ""), DRAWABLE, packageName),
action.getString(TITLE), pIntent);
}
wAction = null;
pIntent = null;
}
builder.extend(new NotificationCompat.WearableExtender().addActions(wActions));
wActions.clear();
} catch (JSONException e) {
// nope
}
}
@RequiresApi(api = Build.VERSION_CODES.KITKAT_WATCH)
private void createActions(Context context, Bundle extras, Notification.Builder builder,
Resources resources, String packageName, int notId) {
Log.d(LOG_TAG, "create actions: with in-line");
String actions = extras.getString(ACTIONS);
if (actions == null) {
return;
}
try {
JSONArray actionsArray = new JSONArray(actions);
ArrayList<Notification.Action> wActions = new ArrayList<>();
for (int i = 0; i < actionsArray.length(); i++) {
int min = 1;
int max = 2000000000;
Random random = new Random();
int uniquePendingIntentRequestCode = random.nextInt((max - min) + 1) + min;
Log.d(LOG_TAG, "adding action");
JSONObject action = actionsArray.getJSONObject(i);
Log.d(LOG_TAG, "adding callback = " + action.getString(CALLBACK));
boolean foreground = action.optBoolean(FOREGROUND, true);
boolean inline = action.optBoolean("inline", false);
Intent intent;
PendingIntent pIntent;
if (inline) {
Log.d(LOG_TAG, "Version: " + android.os.Build.VERSION.SDK_INT + " = "
+ android.os.Build.VERSION_CODES.M);
if (android.os.Build.VERSION.SDK_INT <= android.os.Build.VERSION_CODES.M) {
Log.d(LOG_TAG, "push activity");
intent = new Intent(context, MainActivity.class);
} else {
Log.d(LOG_TAG, "push receiver");
intent = new Intent(context, BackgroundActionButtonHandler.class);
}
updateIntent(intent, action.getString(CALLBACK), extras, foreground, notId);
if (android.os.Build.VERSION.SDK_INT <= android.os.Build.VERSION_CODES.M) {
Log.d(LOG_TAG, "push activity for notId " + notId);
pIntent =
PendingIntent.getActivity(context, uniquePendingIntentRequestCode, intent,
PendingIntent.FLAG_ONE_SHOT);
} else {
Log.d(LOG_TAG, "push receiver for notId " + notId);
pIntent = PendingIntent
.getBroadcast(context, uniquePendingIntentRequestCode, intent,
PendingIntent.FLAG_ONE_SHOT);
}
} else if (foreground) {
intent = new Intent(context, MainActivity.class);
updateIntent(intent, action.getString(CALLBACK), extras, foreground, notId);
pIntent = PendingIntent
.getActivity(context, uniquePendingIntentRequestCode, intent,
PendingIntent.FLAG_UPDATE_CURRENT);
} else {
intent = new Intent(context, BackgroundActionButtonHandler.class);
updateIntent(intent, action.getString(CALLBACK), extras, foreground, notId);
pIntent = PendingIntent
.getBroadcast(context, uniquePendingIntentRequestCode, intent,
PendingIntent.FLAG_UPDATE_CURRENT);
}
Notification.Action.Builder actionBuilder = new Notification.Action.Builder(
resources.getIdentifier(action.optString(ICON, ""), DRAWABLE, packageName),
action.getString(TITLE), pIntent);
android.app.RemoteInput remoteInput;
if (inline) {
Log.d(LOG_TAG, "create remote input");
String replyLabel = "Enter your reply here";
remoteInput = new android.app.RemoteInput.Builder(INLINE_REPLY)
.setLabel(replyLabel)
.build();
actionBuilder.addRemoteInput(remoteInput);
}
Notification.Action wAction = actionBuilder.build();
wActions.add(actionBuilder.build());
if (inline) {
builder.addAction(wAction);
} else { } else {
mBuilder.addAction( builder.addAction(
resources.getIdentifier(action.optString(ICON, ""), DRAWABLE, packageName), resources.getIdentifier(action.optString(ICON, ""), DRAWABLE, packageName),
action.getString(TITLE), pIntent); action.getString(TITLE), pIntent);
} }
wAction = null; wAction = null;
pIntent = null; pIntent = null;
} }
mBuilder.extend(new NotificationCompat.WearableExtender().addActions(wActions)); builder.extend(new Notification.WearableExtender().addActions(wActions));
wActions.clear(); wActions.clear();
} catch (JSONException e) { } catch (JSONException e) {
// nope // nope
...@@ -315,15 +380,24 @@ public class PushNotificationHandler implements PushConstants { ...@@ -315,15 +380,24 @@ public class PushNotificationHandler implements PushConstants {
} }
private void setNotificationCount(Context context, Bundle extras, private void setNotificationCount(Context context, Bundle extras,
NotificationCompat.Builder mBuilder) { NotificationCompat.Builder builder) {
int count = extractBadgeCount(extras);
if (count >= 0) {
Log.d(LOG_TAG, "count =[" + count + "]");
builder.setNumber(count);
}
}
private void setNotificationCount(Context context, Bundle extras,
Notification.Builder builder) {
int count = extractBadgeCount(extras); int count = extractBadgeCount(extras);
if (count >= 0) { if (count >= 0) {
Log.d(LOG_TAG, "count =[" + count + "]"); Log.d(LOG_TAG, "count =[" + count + "]");
mBuilder.setNumber(count); builder.setNumber(count);
} }
} }
private void setVisibility(Context context, Bundle extras, NotificationCompat.Builder mBuilder) { private void setVisibility(Context context, Bundle extras, NotificationCompat.Builder builder) {
String visibilityStr = extras.getString(VISIBILITY); String visibilityStr = extras.getString(VISIBILITY);
if (visibilityStr == null) { if (visibilityStr == null) {
return; return;
...@@ -333,7 +407,7 @@ public class PushNotificationHandler implements PushConstants { ...@@ -333,7 +407,7 @@ public class PushNotificationHandler implements PushConstants {
Integer visibility = Integer.parseInt(visibilityStr); Integer visibility = Integer.parseInt(visibilityStr);
if (visibility >= NotificationCompat.VISIBILITY_SECRET if (visibility >= NotificationCompat.VISIBILITY_SECRET
&& visibility <= NotificationCompat.VISIBILITY_PUBLIC) { && visibility <= NotificationCompat.VISIBILITY_PUBLIC) {
mBuilder.setVisibility(visibility); builder.setVisibility(visibility);
} else { } else {
Log.e(LOG_TAG, "Visibility parameter must be between -1 and 1"); Log.e(LOG_TAG, "Visibility parameter must be between -1 and 1");
} }
...@@ -343,7 +417,28 @@ public class PushNotificationHandler implements PushConstants { ...@@ -343,7 +417,28 @@ public class PushNotificationHandler implements PushConstants {
} }
private void setNotificationVibration(Bundle extras, Boolean vibrateOption, private void setNotificationVibration(Bundle extras, Boolean vibrateOption,
NotificationCompat.Builder mBuilder) { NotificationCompat.Builder builder) {
String vibrationPattern = extras.getString(VIBRATION_PATTERN);
if (vibrationPattern != null) {
String[] items = vibrationPattern.replaceAll("\\[", "").replaceAll("\\]", "").split(",");
long[] results = new long[items.length];
for (int i = 0; i < items.length; i++) {
try {
results[i] = Long.parseLong(items[i].trim());
} catch (NumberFormatException nfe) {
}
}
builder.setVibrate(results);
} else {
if (vibrateOption) {
builder.setDefaults(Notification.DEFAULT_VIBRATE);
}
}
}
@RequiresApi(api = 26)
private void setNotificationVibration(Bundle extras, Boolean vibrateOption,
NotificationChannel channel) {
String vibrationPattern = extras.getString(VIBRATION_PATTERN); String vibrationPattern = extras.getString(VIBRATION_PATTERN);
if (vibrationPattern != null) { if (vibrationPattern != null) {
String[] items = vibrationPattern.replaceAll("\\[", "").replaceAll("\\]", "").split(","); String[] items = vibrationPattern.replaceAll("\\[", "").replaceAll("\\]", "").split(",");
...@@ -354,23 +449,23 @@ public class PushNotificationHandler implements PushConstants { ...@@ -354,23 +449,23 @@ public class PushNotificationHandler implements PushConstants {
} catch (NumberFormatException nfe) { } catch (NumberFormatException nfe) {
} }
} }
mBuilder.setVibrate(results); channel.setVibrationPattern(results);
} else { } else {
if (vibrateOption) { if (vibrateOption) {
mBuilder.setDefaults(Notification.DEFAULT_VIBRATE); channel.enableVibration(true);
} }
} }
} }
private void setNotificationMessage(int notId, Bundle extras, private void setNotificationMessage(int notId, Bundle extras,
NotificationCompat.Builder mBuilder) { NotificationCompat.Builder builder) {
String message = extras.getString(MESSAGE); String message = extras.getString(MESSAGE);
String style = extras.getString(STYLE, STYLE_TEXT); String style = extras.getString(STYLE, STYLE_TEXT);
if (STYLE_INBOX.equals(style)) { if (STYLE_INBOX.equals(style)) {
setNotification(notId, message); setNotification(notId, message);
mBuilder.setContentText(fromHtml(message)); builder.setContentText(fromHtml(message));
ArrayList<String> messageList = getMessageList(notId); ArrayList<String> messageList = getMessageList(notId);
Integer sizeList = messageList.size(); Integer sizeList = messageList.size();
...@@ -389,13 +484,13 @@ public class PushNotificationHandler implements PushConstants { ...@@ -389,13 +484,13 @@ public class PushNotificationHandler implements PushConstants {
notificationInbox.addLine(fromHtml(messageList.get(i))); notificationInbox.addLine(fromHtml(messageList.get(i)));
} }
mBuilder.setStyle(notificationInbox); builder.setStyle(notificationInbox);
} else { } else {
NotificationCompat.BigTextStyle bigText = new NotificationCompat.BigTextStyle(); NotificationCompat.BigTextStyle bigText = new NotificationCompat.BigTextStyle();
if (message != null) { if (message != null) {
bigText.bigText(fromHtml(message)); bigText.bigText(fromHtml(message));
bigText.setBigContentTitle(fromHtml(extras.getString(TITLE))); bigText.setBigContentTitle(fromHtml(extras.getString(TITLE)));
mBuilder.setStyle(bigText); builder.setStyle(bigText);
} }
} }
} else if (STYLE_PICTURE.equals(style)) { } else if (STYLE_PICTURE.equals(style)) {
...@@ -406,17 +501,17 @@ public class PushNotificationHandler implements PushConstants { ...@@ -406,17 +501,17 @@ public class PushNotificationHandler implements PushConstants {
bigPicture.setBigContentTitle(fromHtml(extras.getString(TITLE))); bigPicture.setBigContentTitle(fromHtml(extras.getString(TITLE)));
bigPicture.setSummaryText(fromHtml(extras.getString(SUMMARY_TEXT))); bigPicture.setSummaryText(fromHtml(extras.getString(SUMMARY_TEXT)));
mBuilder.setContentTitle(fromHtml(extras.getString(TITLE))); builder.setContentTitle(fromHtml(extras.getString(TITLE)));
mBuilder.setContentText(fromHtml(message)); builder.setContentText(fromHtml(message));
mBuilder.setStyle(bigPicture); builder.setStyle(bigPicture);
} else { } else {
setNotification(notId, ""); setNotification(notId, "");
NotificationCompat.BigTextStyle bigText = new NotificationCompat.BigTextStyle(); NotificationCompat.BigTextStyle bigText = new NotificationCompat.BigTextStyle();
if (message != null) { if (message != null) {
mBuilder.setContentText(fromHtml(message)); builder.setContentText(fromHtml(message));
bigText.bigText(fromHtml(message)); bigText.bigText(fromHtml(message));
bigText.setBigContentTitle(fromHtml(extras.getString(TITLE))); bigText.setBigContentTitle(fromHtml(extras.getString(TITLE)));
...@@ -426,30 +521,145 @@ public class PushNotificationHandler implements PushConstants { ...@@ -426,30 +521,145 @@ public class PushNotificationHandler implements PushConstants {
bigText.setSummaryText(fromHtml(summaryText)); bigText.setSummaryText(fromHtml(summaryText));
} }
mBuilder.setStyle(bigText); builder.setStyle(bigText);
} }
} }
} }
private void setNotificationMessage(int notId, Bundle extras,
Notification.Builder builder) {
String message = extras.getString(MESSAGE);
String style = extras.getString(STYLE, STYLE_TEXT);
if (STYLE_INBOX.equals(style)) {
setNotification(notId, message);
builder.setContentText(fromHtml(message));
ArrayList<String> messageList = getMessageList(notId);
Integer sizeList = messageList.size();
if (sizeList > 1) {
String sizeListMessage = sizeList.toString();
String stacking = sizeList + " more";
if (extras.getString(SUMMARY_TEXT) != null) {
stacking = extras.getString(SUMMARY_TEXT);
stacking = stacking.replace("%n%", sizeListMessage);
}
Notification.InboxStyle notificationInbox = new Notification.InboxStyle()
.setBigContentTitle(fromHtml(extras.getString(TITLE)))
.setSummaryText(fromHtml(stacking));
for (int i = messageList.size() - 1; i >= 0; i--) {
notificationInbox.addLine(fromHtml(messageList.get(i)));
}
builder.setStyle(notificationInbox);
} else {
Notification.BigTextStyle bigText = new Notification.BigTextStyle();
if (message != null) {
bigText.bigText(fromHtml(message));
bigText.setBigContentTitle(fromHtml(extras.getString(TITLE)));
builder.setStyle(bigText);
}
}
} else if (STYLE_PICTURE.equals(style)) {
setNotification(notId, "");
Notification.BigPictureStyle bigPicture = new Notification.BigPictureStyle();
bigPicture.bigPicture(getBitmapFromURL(extras.getString(PICTURE)));
bigPicture.setBigContentTitle(fromHtml(extras.getString(TITLE)));
bigPicture.setSummaryText(fromHtml(extras.getString(SUMMARY_TEXT)));
builder.setContentTitle(fromHtml(extras.getString(TITLE)));
builder.setContentText(fromHtml(message));
builder.setStyle(bigPicture);
} else {
setNotification(notId, "");
Notification.BigTextStyle bigText = new Notification.BigTextStyle();
if (message != null) {
builder.setContentText(fromHtml(message));
bigText.bigText(fromHtml(message));
bigText.setBigContentTitle(fromHtml(extras.getString(TITLE)));
String summaryText = extras.getString(SUMMARY_TEXT);
if (summaryText != null) {
bigText.setSummaryText(fromHtml(summaryText));
}
builder.setStyle(bigText);
}
}
}
private void setNotificationSound(Context context, Bundle extras,
NotificationCompat.Builder builder) {
String soundname = extras.getString(SOUNDNAME);
if (soundname == null) {
soundname = extras.getString(SOUND);
}
if (SOUND_RINGTONE.equals(soundname)) {
builder.setSound(android.provider.Settings.System.DEFAULT_RINGTONE_URI);
} else if (soundname != null && !soundname.contentEquals(SOUND_DEFAULT)) {
Uri sound = Uri.parse(ContentResolver.SCHEME_ANDROID_RESOURCE
+ "://" + context.getPackageName() + "/raw/" + soundname);
Log.d(LOG_TAG, sound.toString());
builder.setSound(sound);
} else {
builder.setSound(android.provider.Settings.System.DEFAULT_NOTIFICATION_URI);
}
}
@RequiresApi(api = 26)
private void setNotificationSound(Context context, Bundle extras, private void setNotificationSound(Context context, Bundle extras,
NotificationCompat.Builder mBuilder) { NotificationChannel channel) {
String soundname = extras.getString(SOUNDNAME); String soundname = extras.getString(SOUNDNAME);
if (soundname == null) { if (soundname == null) {
soundname = extras.getString(SOUND); soundname = extras.getString(SOUND);
} }
if (SOUND_RINGTONE.equals(soundname)) { if (SOUND_RINGTONE.equals(soundname)) {
mBuilder.setSound(android.provider.Settings.System.DEFAULT_RINGTONE_URI); channel.setSound(android.provider.Settings.System.DEFAULT_RINGTONE_URI,
Notification.AUDIO_ATTRIBUTES_DEFAULT);
} else if (soundname != null && !soundname.contentEquals(SOUND_DEFAULT)) { } else if (soundname != null && !soundname.contentEquals(SOUND_DEFAULT)) {
Uri sound = Uri.parse(ContentResolver.SCHEME_ANDROID_RESOURCE Uri sound = Uri.parse(ContentResolver.SCHEME_ANDROID_RESOURCE
+ "://" + context.getPackageName() + "/raw/" + soundname); + "://" + context.getPackageName() + "/raw/" + soundname);
Log.d(LOG_TAG, sound.toString()); Log.d(LOG_TAG, sound.toString());
mBuilder.setSound(sound); channel.setSound(sound, Notification.AUDIO_ATTRIBUTES_DEFAULT);
} else {
channel.setSound(android.provider.Settings.System.DEFAULT_NOTIFICATION_URI,
Notification.AUDIO_ATTRIBUTES_DEFAULT);
}
}
private void setNotificationLedColor(Bundle extras, NotificationCompat.Builder builder) {
String ledColor = extras.getString(LED_COLOR);
if (ledColor == null) {
return;
}
// Converts parse Int Array from ledColor
String[] items = ledColor.replaceAll("\\[", "").replaceAll("\\]", "").split(",");
int[] results = new int[items.length];
for (int i = 0; i < items.length; i++) {
try {
results[i] = Integer.parseInt(items[i].trim());
} catch (NumberFormatException nfe) {
}
}
if (results.length == 4) {
builder.setLights(Color.argb(results[0], results[1], results[2], results[3]), 500, 500);
} else { } else {
mBuilder.setSound(android.provider.Settings.System.DEFAULT_NOTIFICATION_URI); Log.e(LOG_TAG, "ledColor parameter must be an array of length == 4 (ARGB)");
} }
} }
private void setNotificationLedColor(Bundle extras, NotificationCompat.Builder mBuilder) { @RequiresApi(api = 26)
private void setNotificationLedColor(Bundle extras, NotificationChannel channel) {
String ledColor = extras.getString(LED_COLOR); String ledColor = extras.getString(LED_COLOR);
if (ledColor == null) { if (ledColor == null) {
return; return;
...@@ -466,14 +676,14 @@ public class PushNotificationHandler implements PushConstants { ...@@ -466,14 +676,14 @@ public class PushNotificationHandler implements PushConstants {
} }
if (results.length == 4) { if (results.length == 4) {
mBuilder.setLights(Color.argb(results[0], results[1], results[2], results[3]), 500, 500); channel.setLightColor(Color.argb(results[0], results[1], results[2], results[3]));
} else { } else {
Log.e(LOG_TAG, "ledColor parameter must be an array of length == 4 (ARGB)"); Log.e(LOG_TAG, "ledColor parameter must be an array of length == 4 (ARGB)");
} }
} }
private void setNotificationPriority(Bundle extras, NotificationCompat.Builder mBuilder) { private void setNotificationPriority(Bundle extras, NotificationCompat.Builder builder) {
String priorityStr = extras.getString(PRIORITY); String priorityStr = extras.getString(PRIORITY);
if (priorityStr == null) { if (priorityStr == null) {
return; return;
...@@ -483,7 +693,28 @@ public class PushNotificationHandler implements PushConstants { ...@@ -483,7 +693,28 @@ public class PushNotificationHandler implements PushConstants {
Integer priority = Integer.parseInt(priorityStr); Integer priority = Integer.parseInt(priorityStr);
if (priority >= NotificationCompat.PRIORITY_MIN if (priority >= NotificationCompat.PRIORITY_MIN
&& priority <= NotificationCompat.PRIORITY_MAX) { && priority <= NotificationCompat.PRIORITY_MAX) {
mBuilder.setPriority(priority); builder.setPriority(priority);
} else {
Log.e(LOG_TAG, "Priority parameter must be between -2 and 2");
}
} catch (NumberFormatException e) {
e.printStackTrace();
}
}
@RequiresApi(api = 26)
private void setNotificationImportance(Bundle extras, NotificationChannel channel) {
String priorityStr = extras.getString(PRIORITY);
if (priorityStr == null) {
return;
}
try {
Integer priority = Integer.parseInt(priorityStr);
if (priority >= NotificationCompat.PRIORITY_MIN
&& priority <= NotificationCompat.PRIORITY_MAX) {
channel.setImportance(priority);
} else { } else {
Log.e(LOG_TAG, "Priority parameter must be between -2 and 2"); Log.e(LOG_TAG, "Priority parameter must be between -2 and 2");
} }
...@@ -494,14 +725,14 @@ public class PushNotificationHandler implements PushConstants { ...@@ -494,14 +725,14 @@ public class PushNotificationHandler implements PushConstants {
} }
private void setNotificationLargeIcon(Context context, Bundle extras, String packageName, private void setNotificationLargeIcon(Context context, Bundle extras, String packageName,
Resources resources, NotificationCompat.Builder mBuilder) { Resources resources, NotificationCompat.Builder builder) {
String gcmLargeIcon = extras.getString(IMAGE); // from gcm String gcmLargeIcon = extras.getString(IMAGE); // from gcm
if (gcmLargeIcon == null || "".equals(gcmLargeIcon)) { if (gcmLargeIcon == null || "".equals(gcmLargeIcon)) {
return; return;
} }
if (gcmLargeIcon.startsWith("http://") || gcmLargeIcon.startsWith("https://")) { if (gcmLargeIcon.startsWith("http://") || gcmLargeIcon.startsWith("https://")) {
mBuilder.setLargeIcon(getBitmapFromURL(gcmLargeIcon)); builder.setLargeIcon(getBitmapFromURL(gcmLargeIcon));
Log.d(LOG_TAG, "using remote large-icon from gcm"); Log.d(LOG_TAG, "using remote large-icon from gcm");
} else { } else {
AssetManager assetManager = context.getAssets(); AssetManager assetManager = context.getAssets();
...@@ -509,13 +740,13 @@ public class PushNotificationHandler implements PushConstants { ...@@ -509,13 +740,13 @@ public class PushNotificationHandler implements PushConstants {
try { try {
istr = assetManager.open(gcmLargeIcon); istr = assetManager.open(gcmLargeIcon);
Bitmap bitmap = BitmapFactory.decodeStream(istr); Bitmap bitmap = BitmapFactory.decodeStream(istr);
mBuilder.setLargeIcon(bitmap); builder.setLargeIcon(bitmap);
Log.d(LOG_TAG, "using assets large-icon from gcm"); Log.d(LOG_TAG, "using assets large-icon from gcm");
} catch (IOException e) { } catch (IOException e) {
int largeIconId = resources.getIdentifier(gcmLargeIcon, DRAWABLE, packageName); int largeIconId = resources.getIdentifier(gcmLargeIcon, DRAWABLE, packageName);
if (largeIconId != 0) { if (largeIconId != 0) {
Bitmap largeIconBitmap = BitmapFactory.decodeResource(resources, largeIconId); Bitmap largeIconBitmap = BitmapFactory.decodeResource(resources, largeIconId);
mBuilder.setLargeIcon(largeIconBitmap); builder.setLargeIcon(largeIconBitmap);
Log.d(LOG_TAG, "using resources large-icon from gcm"); Log.d(LOG_TAG, "using resources large-icon from gcm");
} else { } else {
Log.d(LOG_TAG, "Not setting large icon"); Log.d(LOG_TAG, "Not setting large icon");
...@@ -524,8 +755,59 @@ public class PushNotificationHandler implements PushConstants { ...@@ -524,8 +755,59 @@ public class PushNotificationHandler implements PushConstants {
} }
} }
private void setNotificationSmallIcon(Context context, Bundle extras, String packageName, private void setNotificationLargeIcon(Context context, Bundle extras, String packageName,
Resources resources, NotificationCompat.Builder mBuilder, Resources resources, Notification.Builder builder) {
String gcmLargeIcon = extras.getString(IMAGE); // from gcm
if (gcmLargeIcon == null || "".equals(gcmLargeIcon)) {
return;
}
if (gcmLargeIcon.startsWith("http://") || gcmLargeIcon.startsWith("https://")) {
builder.setLargeIcon(getBitmapFromURL(gcmLargeIcon));
Log.d(LOG_TAG, "using remote large-icon from gcm");
} else {
AssetManager assetManager = context.getAssets();
InputStream istr;
try {
istr = assetManager.open(gcmLargeIcon);
Bitmap bitmap = BitmapFactory.decodeStream(istr);
builder.setLargeIcon(bitmap);
Log.d(LOG_TAG, "using assets large-icon from gcm");
} catch (IOException e) {
int largeIconId = resources.getIdentifier(gcmLargeIcon, DRAWABLE, packageName);
if (largeIconId != 0) {
Bitmap largeIconBitmap = BitmapFactory.decodeResource(resources, largeIconId);
builder.setLargeIcon(largeIconBitmap);
Log.d(LOG_TAG, "using resources large-icon from gcm");
} else {
Log.d(LOG_TAG, "Not setting large icon");
}
}
}
}
private void setNotificationSmallIcon(Bundle extras, String packageName,
Resources resources, NotificationCompat.Builder builder,
String localIcon) {
int iconId = 0;
String icon = extras.getString(ICON);
if (icon != null && !"".equals(icon)) {
iconId = resources.getIdentifier(icon, DRAWABLE, packageName);
Log.d(LOG_TAG, "using icon from plugin options");
} else if (localIcon != null && !"".equals(localIcon)) {
iconId = resources.getIdentifier(localIcon, DRAWABLE, packageName);
Log.d(LOG_TAG, "using icon from plugin options");
}
if (iconId == 0) {
Log.d(LOG_TAG, "no icon resource found - using default icon");
iconId = resources.getIdentifier("rocket_chat_notification", DRAWABLE, packageName);
}
builder.setSmallIcon(iconId);
}
private void setNotificationSmallIcon(Bundle extras, String packageName,
Resources resources, Notification.Builder builder,
String localIcon) { String localIcon) {
int iconId = 0; int iconId = 0;
String icon = extras.getString(ICON); String icon = extras.getString(ICON);
...@@ -540,10 +822,10 @@ public class PushNotificationHandler implements PushConstants { ...@@ -540,10 +822,10 @@ public class PushNotificationHandler implements PushConstants {
Log.d(LOG_TAG, "no icon resource found - using default icon"); Log.d(LOG_TAG, "no icon resource found - using default icon");
iconId = resources.getIdentifier("rocket_chat_notification", DRAWABLE, packageName); iconId = resources.getIdentifier("rocket_chat_notification", DRAWABLE, packageName);
} }
mBuilder.setSmallIcon(iconId); builder.setSmallIcon(iconId);
} }
private void setNotificationIconColor(String color, NotificationCompat.Builder mBuilder, private void setNotificationIconColor(String color, NotificationCompat.Builder builder,
String localIconColor) { String localIconColor) {
int iconColor = 0; int iconColor = 0;
if (color != null && !"".equals(color)) { if (color != null && !"".equals(color)) {
...@@ -560,7 +842,7 @@ public class PushNotificationHandler implements PushConstants { ...@@ -560,7 +842,7 @@ public class PushNotificationHandler implements PushConstants {
} }
} }
if (iconColor != 0) { if (iconColor != 0) {
mBuilder.setColor(iconColor); builder.setColor(iconColor);
} }
} }
......
...@@ -23,4 +23,6 @@ import chat.rocket.core.models.ServerInfo; ...@@ -23,4 +23,6 @@ import chat.rocket.core.models.ServerInfo;
void notifyConnectionEstablished(String hostname, String session); void notifyConnectionEstablished(String hostname, String session);
void notifyConnectionLost(String hostname, int reason); void notifyConnectionLost(String hostname, int reason);
void notifyConnecting(String hostname);
} }
...@@ -133,6 +133,14 @@ import rx.subjects.PublishSubject; ...@@ -133,6 +133,14 @@ import rx.subjects.PublishSubject;
new ServerConnectivity(hostname, ServerConnectivity.STATE_DISCONNECTED)); new ServerConnectivity(hostname, ServerConnectivity.STATE_DISCONNECTED));
} }
@DebugLog
@Override
public void notifyConnecting(String hostname) {
serverConnectivityList.put(hostname, ServerConnectivity.STATE_CONNECTING);
connectivitySubject.onNext(
new ServerConnectivity(hostname, ServerConnectivity.STATE_CONNECTING));
}
@Override @Override
public Observable<ServerConnectivity> getServerConnectivityAsObservable() { public Observable<ServerConnectivity> getServerConnectivityAsObservable() {
return Observable.concat(Observable.from(getCurrentConnectivityList()), connectivitySubject); return Observable.concat(Observable.from(getCurrentConnectivityList()), connectivitySubject);
......
...@@ -9,6 +9,7 @@ import android.os.IBinder; ...@@ -9,6 +9,7 @@ import android.os.IBinder;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
import java.util.HashMap; import java.util.HashMap;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import chat.rocket.android.activity.MainActivity; import chat.rocket.android.activity.MainActivity;
...@@ -24,6 +25,7 @@ public class RocketChatService extends Service implements ConnectivityServiceInt ...@@ -24,6 +25,7 @@ public class RocketChatService extends Service implements ConnectivityServiceInt
private ConnectivityManagerInternal connectivityManager; private ConnectivityManagerInternal connectivityManager;
private HashMap<String, RocketChatWebSocketThread> webSocketThreads; private HashMap<String, RocketChatWebSocketThread> webSocketThreads;
private Semaphore webSocketThreadLock = new Semaphore(1);
public class LocalBinder extends Binder { public class LocalBinder extends Binder {
ConnectivityServiceInterface getServiceInterface() { ConnectivityServiceInterface getServiceInterface() {
...@@ -49,6 +51,7 @@ public class RocketChatService extends Service implements ConnectivityServiceInt ...@@ -49,6 +51,7 @@ public class RocketChatService extends Service implements ConnectivityServiceInt
context.unbindService(serviceConnection); context.unbindService(serviceConnection);
} }
@DebugLog
@Override @Override
public void onCreate() { public void onCreate() {
super.onCreate(); super.onCreate();
...@@ -57,6 +60,7 @@ public class RocketChatService extends Service implements ConnectivityServiceInt ...@@ -57,6 +60,7 @@ public class RocketChatService extends Service implements ConnectivityServiceInt
webSocketThreads = new HashMap<>(); webSocketThreads = new HashMap<>();
} }
@DebugLog
@Override @Override
public int onStartCommand(Intent intent, int flags, int startId) { public int onStartCommand(Intent intent, int flags, int startId) {
connectivityManager.ensureConnections(); connectivityManager.ensureConnections();
...@@ -106,12 +110,17 @@ public class RocketChatService extends Service implements ConnectivityServiceInt ...@@ -106,12 +110,17 @@ public class RocketChatService extends Service implements ConnectivityServiceInt
@DebugLog @DebugLog
private Single<RocketChatWebSocketThread> getOrCreateWebSocketThread(String hostname) { private Single<RocketChatWebSocketThread> getOrCreateWebSocketThread(String hostname) {
return Single.defer(() -> { return Single.defer(() -> {
webSocketThreadLock.acquire();
if (webSocketThreads.containsKey(hostname)) { if (webSocketThreads.containsKey(hostname)) {
RocketChatWebSocketThread thread = webSocketThreads.get(hostname); RocketChatWebSocketThread thread = webSocketThreads.get(hostname);
webSocketThreadLock.release();
return Single.just(thread); return Single.just(thread);
} }
return RocketChatWebSocketThread.getStarted(getApplicationContext(), hostname) return RocketChatWebSocketThread.getStarted(getApplicationContext(), hostname)
.doOnSuccess(thread -> webSocketThreads.put(hostname, thread)); .doOnSuccess(thread -> {
webSocketThreads.put(hostname, thread);
webSocketThreadLock.release();
});
}); });
} }
......
...@@ -3,24 +3,26 @@ package chat.rocket.android.service; ...@@ -3,24 +3,26 @@ package chat.rocket.android.service;
import android.content.Context; import android.content.Context;
import android.os.Handler; import android.os.Handler;
import android.os.HandlerThread; import android.os.HandlerThread;
import org.json.JSONObject; import org.json.JSONObject;
import java.lang.reflect.Constructor; import java.lang.reflect.Constructor;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Iterator; import java.util.Iterator;
import java.util.concurrent.TimeUnit;
import bolts.Task; import bolts.Task;
import chat.rocket.android.RocketChatCache;
import chat.rocket.android.api.DDPClientWrapper; import chat.rocket.android.api.DDPClientWrapper;
import chat.rocket.android.api.MethodCallHelper; import chat.rocket.android.api.MethodCallHelper;
import chat.rocket.android.helper.LogIfError; import chat.rocket.android.helper.LogIfError;
import chat.rocket.android.helper.RxHelper;
import chat.rocket.android.helper.TextUtils; import chat.rocket.android.helper.TextUtils;
import chat.rocket.android.log.RCLog; import chat.rocket.android.log.RCLog;
import chat.rocket.core.models.ServerInfo;
import chat.rocket.persistence.realm.models.internal.RealmSession;
import chat.rocket.persistence.realm.RealmHelper;
import chat.rocket.persistence.realm.RealmStore;
import chat.rocket.android.service.ddp.base.ActiveUsersSubscriber; import chat.rocket.android.service.ddp.base.ActiveUsersSubscriber;
import chat.rocket.android.service.ddp.base.LoginServiceConfigurationSubscriber; import chat.rocket.android.service.ddp.base.LoginServiceConfigurationSubscriber;
import chat.rocket.android.service.ddp.base.UserDataSubscriber; import chat.rocket.android.service.ddp.base.UserDataSubscriber;
import chat.rocket.android.service.ddp.stream.StreamRoomMessage;
import chat.rocket.android.service.observer.CurrentUserObserver; import chat.rocket.android.service.observer.CurrentUserObserver;
import chat.rocket.android.service.observer.FileUploadingToUrlObserver; import chat.rocket.android.service.observer.FileUploadingToUrlObserver;
import chat.rocket.android.service.observer.FileUploadingWithUfsObserver; import chat.rocket.android.service.observer.FileUploadingWithUfsObserver;
...@@ -32,8 +34,13 @@ import chat.rocket.android.service.observer.NewMessageObserver; ...@@ -32,8 +34,13 @@ import chat.rocket.android.service.observer.NewMessageObserver;
import chat.rocket.android.service.observer.PushSettingsObserver; import chat.rocket.android.service.observer.PushSettingsObserver;
import chat.rocket.android.service.observer.SessionObserver; import chat.rocket.android.service.observer.SessionObserver;
import chat.rocket.android.service.observer.TokenLoginObserver; import chat.rocket.android.service.observer.TokenLoginObserver;
import chat.rocket.core.models.ServerInfo;
import chat.rocket.persistence.realm.RealmHelper;
import chat.rocket.persistence.realm.RealmStore;
import chat.rocket.persistence.realm.models.internal.RealmSession;
import hugo.weaving.DebugLog; import hugo.weaving.DebugLog;
import rx.Single; import rx.Single;
import rx.subscriptions.CompositeSubscription;
/** /**
* Thread for handling WebSocket connection. * Thread for handling WebSocket connection.
...@@ -62,6 +69,7 @@ public class RocketChatWebSocketThread extends HandlerThread { ...@@ -62,6 +69,7 @@ public class RocketChatWebSocketThread extends HandlerThread {
private final ArrayList<Registrable> listeners = new ArrayList<>(); private final ArrayList<Registrable> listeners = new ArrayList<>();
private DDPClientWrapper ddpClient; private DDPClientWrapper ddpClient;
private boolean listenersRegistered; private boolean listenersRegistered;
private RocketChatCache rocketChatCache;
private final DDPClientRef ddpClientRef = new DDPClientRef() { private final DDPClientRef ddpClientRef = new DDPClientRef() {
@Override @Override
public DDPClientWrapper get() { public DDPClientWrapper get() {
...@@ -69,7 +77,6 @@ public class RocketChatWebSocketThread extends HandlerThread { ...@@ -69,7 +77,6 @@ public class RocketChatWebSocketThread extends HandlerThread {
} }
}; };
private static class KeepAliveTimer { private static class KeepAliveTimer {
private long lastTime; private long lastTime;
private final long thresholdMs; private final long thresholdMs;
...@@ -96,6 +103,7 @@ public class RocketChatWebSocketThread extends HandlerThread { ...@@ -96,6 +103,7 @@ public class RocketChatWebSocketThread extends HandlerThread {
this.hostname = hostname; this.hostname = hostname;
this.realmHelper = RealmStore.getOrCreate(hostname); this.realmHelper = RealmStore.getOrCreate(hostname);
this.connectivityManager = ConnectivityManager.getInstanceForInternal(appContext); this.connectivityManager = ConnectivityManager.getInstanceForInternal(appContext);
this.rocketChatCache = new RocketChatCache(appContext);
} }
/** /**
...@@ -147,7 +155,7 @@ public class RocketChatWebSocketThread extends HandlerThread { ...@@ -147,7 +155,7 @@ public class RocketChatWebSocketThread extends HandlerThread {
return Single.fromEmitter(emitter -> { return Single.fromEmitter(emitter -> {
new Handler(getLooper()).post(() -> { new Handler(getLooper()).post(() -> {
RCLog.d("thread %s: terminated()", Thread.currentThread().getId()); RCLog.d("thread %s: terminated()", Thread.currentThread().getId());
unregisterListeners(); unregisterListenersAndClose();
connectivityManager.notifyConnectionLost(hostname, connectivityManager.notifyConnectionLost(hostname,
ConnectivityManagerInternal.REASON_CLOSED_BY_USER); ConnectivityManagerInternal.REASON_CLOSED_BY_USER);
RocketChatWebSocketThread.super.quit(); RocketChatWebSocketThread.super.quit();
...@@ -196,9 +204,9 @@ public class RocketChatWebSocketThread extends HandlerThread { ...@@ -196,9 +204,9 @@ public class RocketChatWebSocketThread extends HandlerThread {
public void run() { public void run() {
ddpClient.ping().continueWith(task -> { ddpClient.ping().continueWith(task -> {
if (task.isFaulted()) { if (task.isFaulted()) {
RCLog.e(task.getError()); Exception error = task.getError();
RCLog.e(error);
emitter.onSuccess(false); emitter.onSuccess(false);
ddpClient.close();
} else { } else {
keepAliveTimer.update(); keepAliveTimer.update();
emitter.onSuccess(true); emitter.onSuccess(true);
...@@ -232,9 +240,26 @@ public class RocketChatWebSocketThread extends HandlerThread { ...@@ -232,9 +240,26 @@ public class RocketChatWebSocketThread extends HandlerThread {
// handling WebSocket#onClose() callback. // handling WebSocket#onClose() callback.
task.getResult().client.getOnCloseCallback().onSuccess(_task -> { task.getResult().client.getOnCloseCallback().onSuccess(_task -> {
if (listenersRegistered) { ddpClient.close();
terminate(); forceInvalidateTokens();
} connectivityManager.notifyConnecting(hostname);
// Needed to use subscriptions because of legacy code.
// TODO: Should update to RxJava 2
final CompositeSubscription subscriptions = new CompositeSubscription();
subscriptions.add(
connect().retryWhen(RxHelper.exponentialBackoff(3, 500, TimeUnit.MILLISECONDS))
.subscribe(
connected -> {
if (!connected) {
connectivityManager.notifyConnectionLost(
hostname, ConnectivityManagerInternal.REASON_NETWORK_ERROR
);
}
subscriptions.clear();
},
err -> logErrorAndUnsubscribe(subscriptions, err)
)
);
return null; return null;
}); });
...@@ -265,6 +290,11 @@ public class RocketChatWebSocketThread extends HandlerThread { ...@@ -265,6 +290,11 @@ public class RocketChatWebSocketThread extends HandlerThread {
})); }));
} }
private void logErrorAndUnsubscribe(CompositeSubscription subscriptions, Throwable err) {
RCLog.e(err);
subscriptions.clear();
}
@DebugLog @DebugLog
private Single<Boolean> connect() { private Single<Boolean> connect() {
return connectDDPClient() return connectDDPClient()
...@@ -284,7 +314,7 @@ public class RocketChatWebSocketThread extends HandlerThread { ...@@ -284,7 +314,7 @@ public class RocketChatWebSocketThread extends HandlerThread {
return new MethodCallHelper(realmHelper, ddpClientRef).getPermissions(); return new MethodCallHelper(realmHelper, ddpClientRef).getPermissions();
} }
//@DebugLog @DebugLog
private void registerListeners() { private void registerListeners() {
if (!Thread.currentThread().getName().equals("RC_thread_" + hostname)) { if (!Thread.currentThread().getName().equals("RC_thread_" + hostname)) {
// execute in Looper. // execute in Looper.
...@@ -293,7 +323,7 @@ public class RocketChatWebSocketThread extends HandlerThread { ...@@ -293,7 +323,7 @@ public class RocketChatWebSocketThread extends HandlerThread {
} }
if (listenersRegistered) { if (listenersRegistered) {
return; unregisterListeners();
} }
listenersRegistered = true; listenersRegistered = true;
...@@ -308,12 +338,30 @@ public class RocketChatWebSocketThread extends HandlerThread { ...@@ -308,12 +338,30 @@ public class RocketChatWebSocketThread extends HandlerThread {
registrable.register(); registrable.register();
listeners.add(registrable); listeners.add(registrable);
} }
// Register for room stream messages
String roomId = rocketChatCache.getSelectedRoomId();
if (roomId != null && !roomId.isEmpty()) {
StreamRoomMessage streamRoomMessage = new StreamRoomMessage(
appContext, hostname, realmHelper, ddpClientRef, roomId
);
streamRoomMessage.register();
listeners.add(streamRoomMessage);
}
} catch (Exception exception) { } catch (Exception exception) {
RCLog.w(exception, "Failed to register listeners!!"); RCLog.w(exception, "Failed to register listeners!!");
} }
} }
} }
@DebugLog
private void unregisterListenersAndClose() {
unregisterListeners();
if (ddpClient != null) {
ddpClient.close();
ddpClient = null;
}
}
@DebugLog @DebugLog
private void unregisterListeners() { private void unregisterListeners() {
Iterator<Registrable> iterator = listeners.iterator(); Iterator<Registrable> iterator = listeners.iterator();
...@@ -323,9 +371,5 @@ public class RocketChatWebSocketThread extends HandlerThread { ...@@ -323,9 +371,5 @@ public class RocketChatWebSocketThread extends HandlerThread {
iterator.remove(); iterator.remove();
} }
listenersRegistered = false; listenersRegistered = false;
if (ddpClient != null) {
ddpClient.close();
ddpClient = null;
}
} }
} }
...@@ -6,7 +6,7 @@ package chat.rocket.android.service; ...@@ -6,7 +6,7 @@ package chat.rocket.android.service;
public class ServerConnectivity { public class ServerConnectivity {
public static final int STATE_CONNECTED = 1; public static final int STATE_CONNECTED = 1;
public static final int STATE_DISCONNECTED = 2; public static final int STATE_DISCONNECTED = 2;
/*package*/ static final int STATE_CONNECTING = 3; public static final int STATE_CONNECTING = 3;
/*package*/ static final int STATE_DISCONNECTING = 4; /*package*/ static final int STATE_DISCONNECTING = 4;
public final String hostname; public final String hostname;
......
...@@ -185,7 +185,7 @@ public abstract class AbstractDDPDocEventSubscriber implements Registrable { ...@@ -185,7 +185,7 @@ public abstract class AbstractDDPDocEventSubscriber implements Registrable {
if (rxSubscription != null) { if (rxSubscription != null) {
rxSubscription.dispose(); rxSubscription.dispose();
} }
if (!TextUtils.isEmpty(subscriptionId)) { if (!TextUtils.isEmpty(subscriptionId) && ddpClientRef.get() != null) {
ddpClientRef.get().unsubscribe(subscriptionId).continueWith(new LogIfError()); ddpClientRef.get().unsubscribe(subscriptionId).continueWith(new LogIfError());
} }
} }
......
...@@ -2,6 +2,7 @@ package chat.rocket.android.service.internal; ...@@ -2,6 +2,7 @@ package chat.rocket.android.service.internal;
import android.content.Context; import android.content.Context;
import chat.rocket.android.log.RCLog;
import io.reactivex.disposables.CompositeDisposable; import io.reactivex.disposables.CompositeDisposable;
import chat.rocket.android.RocketChatCache; import chat.rocket.android.RocketChatCache;
...@@ -47,7 +48,7 @@ public abstract class AbstractRocketChatCacheObserver implements Registrable { ...@@ -47,7 +48,7 @@ public abstract class AbstractRocketChatCacheObserver implements Registrable {
compositeDisposable.add( compositeDisposable.add(
new RocketChatCache(context) new RocketChatCache(context)
.getSelectedRoomIdPublisher() .getSelectedRoomIdPublisher()
.subscribe(this::updateRoomIdWith) .subscribe(this::updateRoomIdWith, RCLog::e)
); );
} }
......
package chat.rocket.android.service.observer; package chat.rocket.android.service.observer;
import android.content.Context; import android.content.Context;
import io.realm.Realm;
import io.realm.RealmResults;
import org.json.JSONObject; import org.json.JSONObject;
import java.util.List; import java.util.List;
import chat.rocket.android.helper.CheckSum; import chat.rocket.android.helper.CheckSum;
import chat.rocket.android.helper.LogIfError; import chat.rocket.android.helper.LogIfError;
import chat.rocket.android_ddp.rx.RxWebSocketCallback;
import chat.rocket.core.SyncState;
import chat.rocket.persistence.realm.models.internal.MethodCall;
import chat.rocket.persistence.realm.RealmHelper;
import chat.rocket.android.service.DDPClientRef; import chat.rocket.android.service.DDPClientRef;
import chat.rocket.android_ddp.DDPClientCallback; import chat.rocket.android_ddp.DDPClientCallback;
import chat.rocket.core.SyncState;
import chat.rocket.persistence.realm.RealmHelper;
import chat.rocket.persistence.realm.models.internal.MethodCall;
import io.realm.Realm;
import io.realm.RealmResults;
/** /**
* Observing MethodCall record, executing RPC if needed. * Observing MethodCall record, executing RPC if needed.
...@@ -117,9 +118,6 @@ public class MethodCallObserver extends AbstractModelObserver<MethodCall> { ...@@ -117,9 +118,6 @@ public class MethodCallObserver extends AbstractModelObserver<MethodCall> {
} else if (exception instanceof DDPClientCallback.RPC.Timeout) { } else if (exception instanceof DDPClientCallback.RPC.Timeout) {
// temp "fix"- we need to rewrite the connection layer a bit // temp "fix"- we need to rewrite the connection layer a bit
errMessage = "{\"message\": \"Connection Timeout\"}"; errMessage = "{\"message\": \"Connection Timeout\"}";
} else if (exception instanceof RxWebSocketCallback.Failure) {
// temp "fix"- we need to rewrite the connection layer a bit
errMessage = "{\"message\": \"Connection Failure\"}";
} else { } else {
errMessage = exception.getMessage(); errMessage = exception.getMessage();
} }
......
...@@ -14,14 +14,20 @@ ...@@ -14,14 +14,20 @@
<string name="start_of_conversation">Start of conversation</string> <string name="start_of_conversation">Start of conversation</string>
<string name="users_of_room_title">Members List</string> <string name="users_of_room_title">Members List</string>
<string name="fmt_room_user_count">Total: %,d users</string> <plurals name="fmt_room_user_count">
<item quantity="one">Total: %,d user</item>
<item quantity="other">Total: %,d users</item>
</plurals>
<string name="sending">Sending…</string> <string name="sending">Sending…</string>
<string name="not_synced">Not synced</string> <string name="not_synced">Not synced</string>
<string name="failed_to_sync">Failed to sync</string> <string name="failed_to_sync">Failed to sync</string>
<string name="resend">Resend</string> <string name="resend">Resend</string>
<string name="discard">Discard</string> <string name="discard">Discard</string>
<string name="fmt_dialog_view_latest_message_title">New %d messages</string> <plurals name="fmt_dialog_view_latest_message_title">
<item quantity="one">New %d message</item>
<item quantity="other">New %d messages</item>
</plurals>
<string name="dialog_view_latest_message_action">View</string> <string name="dialog_view_latest_message_action">View</string>
<string name="file_uploading_title">Uploading…</string> <string name="file_uploading_title">Uploading…</string>
......
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
machine: machine:
environment: environment:
ANDROID_HOME: /usr/local/android-sdk-linux ANDROID_HOME: /usr/local/android-sdk-linux
GRADLE_OPTS: '-Dorg.gradle.jvmargs="-Xmx2048m -XX:+HeapDumpOnOutOfMemoryError"' GRADLE_OPTS: '-Dorg.gradle.jvmargs="-Xmx1536m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError"'
dependencies: dependencies:
pre: pre:
......
package chat.rocket.android.widget; package chat.rocket.android.widget;
import android.content.Context; import android.content.Context;
import chat.rocket.android.widget.fresco.CustomImageFormatConfigurator;
import com.facebook.common.logging.FLog; import com.facebook.common.logging.FLog;
import com.facebook.drawee.backends.pipeline.DraweeConfig; import com.facebook.drawee.backends.pipeline.DraweeConfig;
import com.facebook.drawee.backends.pipeline.Fresco; import com.facebook.drawee.backends.pipeline.Fresco;
...@@ -9,8 +9,11 @@ import com.facebook.imagepipeline.backends.okhttp3.OkHttpImagePipelineConfigFact ...@@ -9,8 +9,11 @@ import com.facebook.imagepipeline.backends.okhttp3.OkHttpImagePipelineConfigFact
import com.facebook.imagepipeline.core.ImagePipelineConfig; import com.facebook.imagepipeline.core.ImagePipelineConfig;
import com.facebook.imagepipeline.listener.RequestListener; import com.facebook.imagepipeline.listener.RequestListener;
import com.facebook.imagepipeline.listener.RequestLoggingListener; import com.facebook.imagepipeline.listener.RequestLoggingListener;
import java.util.HashSet; import java.util.HashSet;
import java.util.Set; import java.util.Set;
import chat.rocket.android.widget.fresco.CustomImageFormatConfigurator;
import okhttp3.OkHttpClient; import okhttp3.OkHttpClient;
public class RocketChatWidgets { public class RocketChatWidgets {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment