Commit 5509fb5b authored by Lucio Maciel's avatar Lucio Maciel

Rocket Chat Android 2.x branch

parent 17eccf30
apply plugin: 'com.android.library'
apply plugin: 'me.tatarka.retrolambda'
buildscript {
repositories {
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:2.3.3'
classpath 'me.tatarka:gradle-retrolambda:3.6.1'
classpath 'me.tatarka.retrolambda.projectlombok:lombok.ast:0.2.3.a2'
}
}
android {
compileSdkVersion rootProject.ext.compileSdkVersion
buildToolsVersion rootProject.ext.buildToolsVersion
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
defaultConfig {
minSdkVersion 16
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 1
versionName "0.0.8"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
compile project(':log-wrapper')
compile "com.android.support:support-annotations:$rootProject.ext.supportLibraryVersion"
compile 'com.squareup.okhttp3:okhttp:3.8.0'
compile 'io.reactivex.rxjava2:rxjava:2.1.0'
compile 'com.parse.bolts:bolts-tasks:1.4.0'
}
\ No newline at end of file
# -*- coding:utf-8 -*-
a='''
@Override
public void onOpen(WebSocket webSocket, Response response) {
}
@Override
public void onFailure(IOException e, Response response) {
}
@Override
public void onMessage(ResponseBody responseBody) throws IOException {
}
@Override
public void onPong(Buffer payload) {
}
@Override
public void onClose(int code, String reason) {
}
'''.strip().split('@Override')
for m in a[1:]:
m= " @Override\n "+m.strip()
mn = m.split("\n")[1].strip().split(" ")[2].split("(")[0]
if mn.startswith("on"):
d=dict()
d["classname"]=mn[2:]
params = [p for p in " ".join(m.split("\n")[1].strip()[:-1].split(" throws ")[0].split(" ")[2:]).strip()[len(mn)+1:-1].split(", ") if p.split(" ")[0]!="WebSocket"]
d["params"]="".join([", "+p for p in params])
paramnames = [p.split(" ")[-1] for p in params]
d["paramdefs"]="\n".join([" public "+p+";" for p in params])
d["thisis"]="\n".join([" this.{param} = {param};".format(param=p) for p in paramnames])
# print '''
# public static class {classname} extends Base {{
# {paramdefs}
# public {classname}(WebSocket websocket{params}) {{
# super("{classname}", websocket);
# {thisis}
# }}
# }}'''.format(**d)
######################
x=m.split("\n")
x[2]=''' mSubscriber.onNext(new RxWebSocketCallback.{classname}(mWebSocket, {params}));'''.format(classname=mn[2:],params=", ".join(paramnames))
print "\n".join(x)
<lint>
<issue id="InvalidPackage">
<ignore regexp="okio.*jar"/>
</issue>
</lint>
\ No newline at end of file
# Add project specific ProGuard rules here.
# By default, the flags in this file are appended to flags specified
# in /Users/yi01/Library/Android/sdk/tools/proguard/proguard-android.txt
# You can edit the include path and order by changing the proguardFiles
# directive in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# Add any project specific keep options here:
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="chat.rocket.android_ddp">
<application
android:allowBackup="true"
android:label="@string/app_name"
android:supportsRtl="true">
</application>
</manifest>
package chat.rocket.android_ddp;
import android.support.annotation.Nullable;
import org.json.JSONArray;
import bolts.Task;
import bolts.TaskCompletionSource;
import chat.rocket.android_ddp.rx.RxWebSocketCallback;
import io.reactivex.Flowable;
import io.reactivex.Maybe;
import okhttp3.OkHttpClient;
public class DDPClient {
// reference: https://github.com/eddflrs/meteor-ddp/blob/master/meteor-ddp.js
private final DDPClientImpl impl;
public DDPClient(OkHttpClient client) {
impl = new DDPClientImpl(this, client);
}
public Task<DDPClientCallback.Connect> connect(String url) {
return connect(url, null);
}
public Task<DDPClientCallback.Connect> connect(String url, String session) {
TaskCompletionSource<DDPClientCallback.Connect> task = new TaskCompletionSource<>();
impl.connect(task, url, session);
return task.getTask();
}
public Task<DDPClientCallback.Ping> ping(@Nullable String id) {
TaskCompletionSource<DDPClientCallback.Ping> task = new TaskCompletionSource<>();
impl.ping(task, id);
return task.getTask();
}
public Maybe<DDPClientCallback.Base> doPing(@Nullable String id) {
return impl.ping(id);
}
public Task<DDPClientCallback.RPC> rpc(String method, JSONArray params, String id,
long timeoutMs) {
TaskCompletionSource<DDPClientCallback.RPC> task = new TaskCompletionSource<>();
impl.rpc(task, method, params, id, timeoutMs);
return task.getTask();
}
public Task<DDPSubscription.Ready> sub(String id, String name, JSONArray params) {
TaskCompletionSource<DDPSubscription.Ready> task = new TaskCompletionSource<>();
impl.sub(task, name, params, id);
return task.getTask();
}
public Task<DDPSubscription.NoSub> unsub(String id) {
TaskCompletionSource<DDPSubscription.NoSub> task = new TaskCompletionSource<>();
impl.unsub(task, id);
return task.getTask();
}
public Flowable<DDPSubscription.Event> getSubscriptionCallback() {
return impl.getDDPSubscription();
}
public Task<RxWebSocketCallback.Close> getOnCloseCallback() {
return impl.getOnCloseCallback();
}
public void close() {
impl.close(1000, "closed by DDPClient#close()");
}
}
package chat.rocket.android_ddp;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import org.json.JSONObject;
public class DDPClientCallback {
public static abstract class Base {
public DDPClient client;
public Base(DDPClient client) {
this.client = client;
}
}
public static abstract class BaseException extends Exception {
public DDPClient client;
public BaseException(Class<? extends BaseException> clazz, DDPClient client) {
super(clazz.getName());
this.client = client;
}
}
public static class Connect extends Base {
public String session;
public Connect(DDPClient client, String session) {
super(client);
this.session = session;
}
public static class Failed extends BaseException {
public String version;
public Failed(DDPClient client, String version) {
super(Failed.class, client);
this.version = version;
}
}
public static class Timeout extends BaseException {
public Timeout(DDPClient client) {
super(Timeout.class, client);
}
}
}
public static class Ping extends Base {
@Nullable public String id;
public Ping(DDPClient client, @Nullable String id) {
super(client);
this.id = id;
}
public static class UnMatched extends Base {
@NonNull public String id;
public UnMatched(DDPClient client, @NonNull String id) {
super(client);
this.id = id;
}
}
public static class Timeout extends BaseException {
public Timeout(DDPClient client) {
super(Timeout.class, client);
}
}
}
public static class RPC extends Base {
public String id;
public String result;
public RPC(DDPClient client, String id, String result) {
super(client);
this.id = id;
this.result = result;
}
public static class Error extends BaseException {
public String id;
public JSONObject error;
public Error(DDPClient client, String id, JSONObject error) {
super(Error.class, client);
this.id = id;
this.error = error;
}
}
public static class Timeout extends BaseException {
public Timeout(DDPClient client) {
super(Timeout.class, client);
}
}
}
public static class Closed extends BaseException {
public Closed(DDPClient client) {
super(Closed.class, client);
}
}
}
package chat.rocket.android_ddp;
import android.support.annotation.NonNull;
import org.json.JSONArray;
import org.json.JSONObject;
public class DDPSubscription {
public static abstract class Event {
public final DDPClient client;
public Event(DDPClient client) {
this.client = client;
}
}
public static abstract class BaseException extends Exception {
public final DDPClient client;
public BaseException(DDPClient client) {
this.client = client;
}
}
public static class NoSub extends Event {
public String id;
public NoSub(DDPClient client, String id) {
super(client);
this.id = id;
}
@Override
public String toString() {
return "NoSub[id=" + id + "]";
}
public static class Error extends BaseException {
String id;
JSONObject error;
public Error(DDPClient client, String id, JSONObject error) {
super(client);
this.id = id;
this.error = error;
}
}
}
public static class Ready extends Event {
public String id;
public Ready(DDPClient client, String id) {
super(client);
this.id = id;
}
@Override
public String toString() {
return "Ready[id=" + id + "]";
}
}
public static class DocEvent extends Event {
public String collection;
public String docID;
public DocEvent(DDPClient client, String collection, String docID) {
super(client);
this.collection = collection;
this.docID = docID;
}
@Override
public String toString() {
return "DocEvent[id=" + docID + ", collection=" + collection + "]";
}
}
public static class Added extends DocEvent {
public JSONObject fields;
public Added(DDPClient client, String collection, String docID, JSONObject fields) {
super(client, collection, docID);
this.fields = fields;
}
public static class Before extends Added {
public String before;
public Before(DDPClient client, String collection, String docID, JSONObject fields,
String before) {
super(client, collection, docID, fields);
this.before = before;
}
}
}
public static class Changed extends DocEvent {
public JSONObject fields;
public JSONArray cleared;
public Changed(DDPClient client, String collection, String docID, JSONObject fields,
@NonNull JSONArray cleared) {
super(client, collection, docID);
this.fields = fields;
this.cleared = cleared;
}
}
public static class Removed extends DocEvent {
public Removed(DDPClient client, String collection, String docID) {
super(client, collection, docID);
}
}
public static class MovedBefore extends DocEvent {
public String before;
public MovedBefore(DDPClient client, String collection, String docID, String before) {
super(client, collection, docID);
this.before = before;
}
}
}
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.Flowable;
import io.reactivex.FlowableOnSubscribe;
import io.reactivex.exceptions.OnErrorNotImplementedException;
import io.reactivex.flowables.ConnectableFlowable;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import okhttp3.WebSocket;
import okhttp3.WebSocketListener;
public class RxWebSocket {
public static final int REASON_NETWORK_ERROR = 100;
private OkHttpClient httpClient;
private WebSocket webSocket;
private boolean hadErrorsBefore;
public RxWebSocket(OkHttpClient client) {
httpClient = client;
}
public ConnectableFlowable<RxWebSocketCallback.Base> connect(String url) {
final Request request = new Request.Builder().url(url).build();
return Flowable.create(
(FlowableOnSubscribe<RxWebSocketCallback.Base>) emitter -> httpClient
.newWebSocket(request, new WebSocketListener() {
@Override
public void onOpen(WebSocket webSocket, Response response) {
hadErrorsBefore = false;
RxWebSocket.this.webSocket = webSocket;
emitter.onNext(new RxWebSocketCallback.Open(RxWebSocket.this.webSocket, response));
}
@Override
public void onFailure(WebSocket webSocket, Throwable err, Response response) {
try {
if (!hadErrorsBefore) {
hadErrorsBefore = true;
emitter.onNext(new RxWebSocketCallback.Close(webSocket, REASON_NETWORK_ERROR, err.getMessage()));
emitter.onComplete();
}
} catch (OnErrorNotImplementedException ex) {
RCLog.w(ex, "OnErrorNotImplementedException ignored");
}
}
@Override
public void onMessage(WebSocket webSocket, String text) {
emitter.onNext(new RxWebSocketCallback.Message(webSocket, text));
}
@Override
public void onClosed(WebSocket webSocket, int code, String reason) {
emitter.onNext(new RxWebSocketCallback.Close(webSocket, code, reason));
}
}),
BackpressureStrategy.BUFFER
).publish();
}
public boolean sendText(String message) throws IOException {
if (webSocket == null) {
return false;
}
return webSocket.send(message);
}
public boolean close(int code, String reason) throws IOException {
if (webSocket == null) {
return false;
}
return webSocket.close(code, reason);
}
}
package chat.rocket.android_ddp.rx;
import chat.rocket.android.log.RCLog;
import okhttp3.Response;
import okhttp3.WebSocket;
public class RxWebSocketCallback {
public static abstract class Base {
public String type;
public WebSocket ws;
public Base(String type, WebSocket ws) {
this.type = type;
this.ws = ws;
}
@Override
public String toString() {
return "[" + type + "]";
}
}
public static class Open extends Base {
public Response response;
public Open(WebSocket websocket, Response response) {
super("Open", websocket);
this.response = response;
if (response != null && response.body() != null) {
this.response.body().close();
}
}
}
public static class Message extends Base {
public String responseBodyString;
public Message(WebSocket websocket, String responseBody) {
super("Message", websocket);
try {
this.responseBodyString = responseBody;
} catch (Exception e) {
RCLog.e(e, "error in reading response(Message)");
}
}
@Override
public String toString() {
return "[" + type + "] " + responseBodyString;
}
}
public static class Close extends Base {
public int code;
public String reason;
public Close(WebSocket websocket, int code, String reason) {
super("Close", websocket);
this.code = code;
this.reason = reason;
}
@Override
public String toString() {
return "[" + type + "] code=" + code + ", reason=" + reason;
}
}
}
<resources>
<string name="app_name">android-ddp</string>
</resources>
apply plugin: 'com.android.application'
apply plugin: 'io.fabric'
repositories {
maven { url 'https://maven.fabric.io/public' }
maven { url 'https://github.com/uPhyca/stetho-realm/raw/master/maven-repo' }
}
apply plugin: 'kotlin-android'
apply plugin: 'me.tatarka.retrolambda'
apply plugin: 'com.jakewharton.hugo'
apply plugin: 'com.github.triplet.play'
apply from: '../config/quality/quality.gradle'
buildscript {
repositories {
jcenter()
mavenCentral()
maven { url 'https://maven.fabric.io/public' }
}
dependencies {
classpath 'com.android.tools.build:gradle:2.3.3'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$rootProject.ext.kotlinVersion"
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
classpath 'me.tatarka:gradle-retrolambda:3.5.0'
classpath 'me.tatarka.retrolambda.projectlombok:lombok.ast:0.2.3.a2'
classpath 'io.realm:realm-gradle-plugin:2.3.1'
classpath 'com.jakewharton.hugo:hugo-plugin:1.2.1'
classpath 'com.google.gms:google-services:3.0.0'
classpath 'com.github.triplet.gradle:play-publisher:1.1.5'
classpath 'io.fabric.tools:gradle:1.+'
}
// Exclude the version that the android plugin depends on.
configurations.classpath.exclude group: 'com.android.tools.external.lombok'
}
android {
compileSdkVersion rootProject.ext.compileSdkVersion
buildToolsVersion rootProject.ext.buildToolsVersion
defaultConfig {
applicationId "chat.rocket.android"
minSdkVersion 16
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 35
versionName "1.0.18"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
vectorDrawables.useSupportLibrary = true
multiDexEnabled true
}
signingConfigs {
release {
storeFile project.rootProject.file('Rocket.jks').getCanonicalFile()
storePassword System.getenv("KEYSTORE_PASSWORD")
keyAlias System.getenv("KEY_ALIAS")
keyPassword System.getenv("KEY_PASSWORD")
}
}
buildTypes {
debug {
debuggable true
versionNameSuffix '-DEBUG'
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
signingConfig signingConfigs.debug
}
release {
debuggable false
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
signingConfig signingConfigs.release
}
}
packagingOptions {
exclude 'META-INF/LICENSE.txt'
exclude 'META-INF/NOTICE.txt'
exclude 'META-INF/rxjava.properties'
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
lintOptions {
//avoiding okio error: https://github.com/square/okhttp/issues/896
lintConfig file("lint.xml")
}
sourceSets {
debug {
manifest.srcFile 'src/debug/AndroidManifest.xml'
}
release {
manifest.srcFile 'src/release/AndroidManifest.xml'
}
}
}
play {
jsonFile = file('rocket-chat.json')
track = "${track}"
}
ext {
playLibVersion = '11.0.4'
stethoVersion = '1.5.0'
stethoOkhttp3Version = '1.5.0'
stethoRealmVersion = '2.1.0'
rxbindingVersion = '2.0.0'
rxlifecycleVersion = '2.1.0'
icepickVersion = '3.2.0'
permissionsdispatcherVersion = '2.4.0'
}
dependencies {
compile project(':log-wrapper')
compile project(':android-ddp')
compile project(':rocket-chat-core')
compile project(':rocket-chat-android-widgets')
compile project(':persistence-realm')
compile "com.android.support:appcompat-v7:$rootProject.ext.supportLibraryVersion"
compile "com.android.support:design:$rootProject.ext.supportLibraryVersion"
compile "com.android.support:support-annotations:$rootProject.ext.supportLibraryVersion"
compile "com.android.support.constraint:constraint-layout:$rootProject.ext.constraintLayoutVersion"
compile 'com.android.support:multidex:1.0.1'
compile "org.jetbrains.kotlin:kotlin-stdlib-jre7:$rootProject.ext.kotlinVersion"
compile "com.google.firebase:firebase-core:$playLibVersion"
compile "com.google.firebase:firebase-crash:$playLibVersion"
compile "com.google.android.gms:play-services-gcm:$playLibVersion"
compile 'com.squareup.okhttp3:okhttp:3.8.0'
debugCompile "com.facebook.stetho:stetho:$stethoVersion"
debugCompile "com.facebook.stetho:stetho-okhttp3:$stethoOkhttp3Version"
debugCompile "com.uphyca:stetho_realm:$stethoRealmVersion"
compile "com.jakewharton.rxbinding2:rxbinding:$rxbindingVersion"
compile "com.jakewharton.rxbinding2:rxbinding-support-v4:$rxbindingVersion"
compile "com.jakewharton.rxbinding2:rxbinding-appcompat-v7:$rxbindingVersion"
compile "com.trello.rxlifecycle2:rxlifecycle:$rxlifecycleVersion"
compile "com.trello.rxlifecycle2:rxlifecycle-android:$rxlifecycleVersion"
compile "com.trello.rxlifecycle2:rxlifecycle-components:$rxlifecycleVersion"
compile 'nl.littlerobots.rxlint:rxlint:1.2'
compile 'com.amulyakhare:com.amulyakhare.textdrawable:1.0.1'
compile "frankiesardo:icepick:$icepickVersion"
provided "frankiesardo:icepick-processor:$icepickVersion"
compile "com.github.hotchemi:permissionsdispatcher:$permissionsdispatcherVersion"
annotationProcessor "com.github.hotchemi:permissionsdispatcher-processor:$permissionsdispatcherVersion"
compile('com.crashlytics.sdk.android:crashlytics:2.6.8@aar') {
transitive = true;
}
provided 'com.parse.bolts:bolts-tasks:1.4.0'
provided 'io.reactivex.rxjava2:rxjava:2.1.0'
provided 'io.reactivex:rxjava:1.3.0'
provided "com.github.akarnokd:rxjava2-interop:0.10.2"
provided 'com.hadisatrio:Optional:v1.0.1'
}
apply plugin: 'com.google.gms.google-services'
{
"project_info": {
"project_number": "361979207101",
"firebase_url": "https://rocketchatandroid-92e1e.firebaseio.com",
"project_id": "rocketchatandroid-92e1e",
"storage_bucket": "rocketchatandroid-92e1e.appspot.com"
},
"client": [
{
"client_info": {
"mobilesdk_app_id": "1:361979207101:android:16da2e50aff9f0c9",
"android_client_info": {
"package_name": "chat.rocket.android"
}
},
"oauth_client": [
{
"client_id": "361979207101-68jt4s85vqfidsgtb0jircio1s4l0la6.apps.googleusercontent.com",
"client_type": 1,
"android_info": {
"package_name": "chat.rocket.android",
"certificate_hash": "5540F34145397BBDE62DEE1433BE7FF9D991D5A2"
}
},
{
"client_id": "361979207101-tvvl8a3s98vd933svlepieo81mul17da.apps.googleusercontent.com",
"client_type": 3
}
],
"api_key": [
{
"current_key": "AIzaSyCjInoCWiVXbC02aKgBPeH3EqiHGt6vGyE"
}
],
"services": {
"analytics_service": {
"status": 1
},
"appinvite_service": {
"status": 2,
"other_platform_oauth_client": [
{
"client_id": "361979207101-tvvl8a3s98vd933svlepieo81mul17da.apps.googleusercontent.com",
"client_type": 3
}
]
},
"ads_service": {
"status": 2
}
}
}
],
"configuration_version": "1"
}
\ No newline at end of file
<lint>
<issue id="InvalidPackage">
<ignore regexp="okio.*jar"/>
</issue>
<issue id="NewApi">
<ignore regexp="Try-with-resources requires API level 19"/>
</issue>
</lint>
\ No newline at end of file
# Add project specific ProGuard rules here.
# By default, the flags in this file are appended to flags specified
# in /Users/yi01/Library/Android/sdk/tools/proguard/proguard-android.txt
# You can edit the include path and order by changing the proguardFiles
# directive in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# Add any project specific keep options here:
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Icepick
-dontwarn icepick.**
-keep class icepick.** { *; }
-keep class **$$Icepick { *; }
-keepclasseswithmembernames class * {
@icepick.* <fields>;
}
-keepnames class * { @icepick.State *;}
\ No newline at end of file
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="chat.rocket.android"
xmlns:tools="http://schemas.android.com/tools">
<application
tools:replace="android:name"
android:name=".RocketChatApplicationDebug"/>
</manifest>
\ No newline at end of file
package chat.rocket.android;
import android.os.StrictMode;
import com.facebook.stetho.Stetho;
import com.uphyca.stetho_realm.RealmInspectorModulesProvider;
public class RocketChatApplicationDebug extends RocketChatApplication {
@Override
public void onCreate() {
super.onCreate();
enableStrictMode();
enableStetho();
}
private void enableStrictMode() {
StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
.detectDiskReads()
.detectDiskWrites()
.detectNetwork() // or .detectAll() for all detectable problems
.penaltyLog()
.build());
StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder()
.detectLeakedSqlLiteObjects()
.detectLeakedClosableObjects()
.penaltyLog()
.build());
}
private void enableStetho() {
Stetho.initialize(Stetho.newInitializerBuilder(this)
.enableDumpapp(Stetho.defaultDumperPluginsProvider(this))
.enableWebKitInspector(RealmInspectorModulesProvider.builder(this).build())
.build());
}
}
\ No newline at end of file
package chat.rocket.android.helper
import android.content.Context
import chat.rocket.android.RocketChatCache
import chat.rocket.android.api.rest.CookieInterceptor
import chat.rocket.android.api.rest.DefaultCookieProvider
import com.facebook.stetho.okhttp3.StethoInterceptor
import java.util.concurrent.TimeUnit
import okhttp3.OkHttpClient
object OkHttpHelper {
fun getClientForUploadFile(): OkHttpClient {
if (httpClientForUploadFile == null) {
httpClientForUploadFile = OkHttpClient.Builder().build()
}
return httpClientForUploadFile ?: throw AssertionError("httpClientForUploadFile set to null by another thread")
}
fun getClientForDownloadFile(context: Context): OkHttpClient {
if(httpClientForDownloadFile == null) {
httpClientForDownloadFile = OkHttpClient.Builder()
.addNetworkInterceptor(StethoInterceptor())
.addInterceptor(CookieInterceptor(DefaultCookieProvider(RocketChatCache(context))))
.build()
}
return httpClientForDownloadFile ?: throw AssertionError("httpClientForDownloadFile set to null by another thread")
}
/**
* Returns the OkHttpClient instance for WebSocket connection.
* @return The OkHttpClient WebSocket connection instance.
*/
fun getClientForWebSocket(): OkHttpClient {
if (httpClientForWS == null) {
httpClientForWS = OkHttpClient.Builder()
.readTimeout(0, TimeUnit.NANOSECONDS)
.build()
}
return httpClientForWS ?: throw AssertionError("httpClientForWS set to null by another thread")
}
private var httpClientForUploadFile: OkHttpClient? = null
private var httpClientForDownloadFile: OkHttpClient? = null
private var httpClientForWS: OkHttpClient? = null
}
\ No newline at end of file
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="chat.rocket.android">
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WAKE_LOCK"/>
<permission
android:name="chat.rocket.android.permission.C2D_MESSAGE"
android:protectionLevel="signature"/>
<uses-permission android:name="chat.rocket.android.permission.C2D_MESSAGE"/>
<application
android:name=".RocketChatApplication"
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity
android:name=".activity.MainActivity"
android:configChanges="orientation|screenSize"
android:windowSoftInputMode="adjustResize">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<activity
android:name=".activity.AddServerActivity"
android:configChanges="orientation|screenSize"
android:windowSoftInputMode="adjustResize"/>
<activity
android:name=".activity.LoginActivity"
android:configChanges="orientation|screenSize"
android:windowSoftInputMode="adjustResize"/>
<service android:name=".service.RocketChatService"/>
<receiver
android:name="com.google.android.gms.gcm.GcmReceiver"
android:exported="true"
android:permission="com.google.android.c2dm.permission.SEND">
<intent-filter>
<action android:name="com.google.android.c2dm.intent.RECEIVE"/>
<category android:name="com.example.gcm"/>
</intent-filter>
</receiver>
<service
android:name=".push.gcm.GCMIntentService"
android:exported="false">
<intent-filter>
<action android:name="com.google.android.c2dm.intent.RECEIVE"/>
</intent-filter>
</service>
<service
android:name=".push.gcm.GcmInstanceIDListenerService"
android:exported="false">
<intent-filter>
<action android:name="com.google.android.gms.iid.InstanceID"/>
</intent-filter>
</service>
<meta-data
android:name="io.fabric.ApiKey"
android:value="12ac6e94f850aaffcdff52001af77ca415d06a43" />
</application>
</manifest>
\ No newline at end of file
package chat.rocket.android;
import android.os.HandlerThread;
import android.os.Looper;
import android.os.Process;
public class BackgroundLooper {
private static HandlerThread handlerThread;
public static Looper get() {
if (handlerThread == null) {
handlerThread = new HandlerThread(
"BackgroundHandlerThread", Process.THREAD_PRIORITY_BACKGROUND);
handlerThread.start();
}
return handlerThread.getLooper();
}
}
package chat.rocket.android;
import android.content.Context;
import android.content.Intent;
import chat.rocket.android.activity.AddServerActivity;
import chat.rocket.android.activity.LoginActivity;
import chat.rocket.android.activity.MainActivity;
/**
* utility class for launching Activity.
*/
public class LaunchUtil {
/**
* launch MainActivity with proper flags.
*/
public static void showMainActivity(Context context) {
Intent intent = new Intent(context, MainActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT | Intent.FLAG_ACTIVITY_CLEAR_TOP);
context.startActivity(intent);
}
/**
* launch AddServerActivity with proper flags.
*/
public static void showAddServerActivity(Context context) {
Intent intent = new Intent(context, AddServerActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT | Intent.FLAG_ACTIVITY_CLEAR_TOP);
context.startActivity(intent);
}
/**
* launch ServerConfigActivity with proper flags.
*/
public static void showLoginActivity(Context context, String hostname) {
Intent intent = new Intent(context, LoginActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT | Intent.FLAG_ACTIVITY_CLEAR_TOP);
intent.putExtra(LoginActivity.KEY_HOSTNAME, hostname);
context.startActivity(intent);
}
}
package chat.rocket.android;
import android.support.multidex.MultiDexApplication;
import chat.rocket.android.helper.OkHttpHelper;
import com.crashlytics.android.Crashlytics;
import io.fabric.sdk.android.Fabric;
import java.util.List;
import chat.rocket.persistence.realm.RealmStore;
import chat.rocket.android.service.ConnectivityManager;
import chat.rocket.core.models.ServerInfo;
import chat.rocket.android.widget.RocketChatWidgets;
import chat.rocket.persistence.realm.RocketChatPersistenceRealm;
/**
* Customized Application-class for Rocket.Chat
*/
public class RocketChatApplication extends MultiDexApplication {
@Override
public void onCreate() {
super.onCreate();
Fabric.with(this, new Crashlytics());
RocketChatPersistenceRealm.init(this);
List<ServerInfo> serverInfoList = ConnectivityManager.getInstance(this).getServerList();
for (ServerInfo serverInfo : serverInfoList) {
RealmStore.put(serverInfo.getHostname());
}
RocketChatWidgets.initialize(this, OkHttpHelper.INSTANCE.getClientForDownloadFile(this));
}
}
\ No newline at end of file
package chat.rocket.android;
import android.content.Context;
import android.content.SharedPreferences;
import com.hadisatrio.optional.Optional;
import java.util.UUID;
import io.reactivex.BackpressureStrategy;
import io.reactivex.Flowable;
/**
* sharedpreference-based cache.
*/
public class RocketChatCache {
private static final String KEY_SELECTED_SERVER_HOSTNAME = "selectedServerHostname";
private static final String KEY_SELECTED_ROOM_ID = "selectedRoomId";
private static final String KEY_PUSH_ID = "pushId";
private Context context;
public RocketChatCache(Context context) {
this.context = context.getApplicationContext();
}
public String getSelectedServerHostname() {
return getString(KEY_SELECTED_SERVER_HOSTNAME, null);
}
public void setSelectedServerHostname(String hostname) {
setString(KEY_SELECTED_SERVER_HOSTNAME, hostname);
}
public String getSelectedRoomId() {
return getString(KEY_SELECTED_ROOM_ID, null);
}
public void setSelectedRoomId(String roomId) {
setString(KEY_SELECTED_ROOM_ID, roomId);
}
public String getOrCreatePushId() {
SharedPreferences preferences = getSharedPreferences();
if (!preferences.contains(KEY_PUSH_ID)) {
// generates one and save
String newId = UUID.randomUUID().toString().replace("-", "");
preferences.edit()
.putString(KEY_PUSH_ID, newId)
.apply();
return newId;
}
return preferences.getString(KEY_PUSH_ID, null);
}
public Flowable<Optional<String>> getSelectedServerHostnamePublisher() {
return getValuePublisher(KEY_SELECTED_SERVER_HOSTNAME);
}
public Flowable<Optional<String>> getSelectedRoomIdPublisher() {
return getValuePublisher(KEY_SELECTED_ROOM_ID);
}
private SharedPreferences getSharedPreferences() {
return context.getSharedPreferences("cache", Context.MODE_PRIVATE);
}
private SharedPreferences.Editor getEditor() {
return getSharedPreferences().edit();
}
private String getString(String key, String defaultValue) {
return getSharedPreferences().getString(key, defaultValue);
}
private void setString(String key, String value) {
getEditor().putString(key, value).apply();
}
private Flowable<Optional<String>> getValuePublisher(final String key) {
return Flowable.create(emitter -> {
SharedPreferences.OnSharedPreferenceChangeListener
listener = (sharedPreferences, changedKey) -> {
if (key.equals(changedKey) && !emitter.isCancelled()) {
String value = getString(key, null);
emitter.onNext(Optional.ofNullable(value));
}
};
emitter.setCancellable(() -> getSharedPreferences()
.unregisterOnSharedPreferenceChangeListener(listener));
getSharedPreferences().registerOnSharedPreferenceChangeListener(listener);
}, BackpressureStrategy.LATEST);
}
}
package chat.rocket.android.activity;
import android.content.Intent;
import android.os.Bundle;
import android.support.annotation.Nullable;
import com.hadisatrio.optional.Optional;
import java.util.List;
import chat.rocket.android.LaunchUtil;
import chat.rocket.android.RocketChatCache;
import chat.rocket.android.helper.Logger;
import chat.rocket.android.push.PushConstants;
import chat.rocket.android.push.PushNotificationHandler;
import chat.rocket.android.service.ConnectivityManager;
import chat.rocket.core.models.ServerInfo;
import chat.rocket.persistence.realm.RealmStore;
import chat.rocket.persistence.realm.models.ddp.RealmRoom;
import icepick.State;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.CompositeDisposable;
import io.reactivex.schedulers.Schedulers;
abstract class AbstractAuthedActivity extends AbstractFragmentActivity {
@State protected String hostname;
@State protected String roomId;
private RocketChatCache rocketChatCache;
private CompositeDisposable compositeDisposable = new CompositeDisposable();
private boolean isNotification;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
rocketChatCache = new RocketChatCache(this);
if (savedInstanceState == null) {
handleIntent(getIntent());
}
updateHostnameIfNeeded(rocketChatCache.getSelectedServerHostname());
updateRoomIdIfNeeded(rocketChatCache.getSelectedRoomId());
}
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
handleIntent(intent);
}
private void handleIntent(Intent intent) {
if (intent == null) {
return;
}
if (intent.hasExtra(PushConstants.HOSTNAME)) {
rocketChatCache.setSelectedServerHostname(intent.getStringExtra(PushConstants.HOSTNAME));
if (intent.hasExtra(PushConstants.ROOM_ID)) {
rocketChatCache.setSelectedRoomId(intent.getStringExtra(PushConstants.ROOM_ID));
}
}
if (intent.hasExtra(PushConstants.NOT_ID)) {
isNotification = true;
PushNotificationHandler
.cleanUpNotificationStack(intent.getIntExtra(PushConstants.NOT_ID, 0));
}
}
private void updateHostnameIfNeeded(String newHostname) {
if (hostname == null) {
if (newHostname != null && assertServerRealmStoreExists(newHostname)) {
updateHostname(newHostname);
} else {
recoverFromHostnameError();
}
} else {
if (hostname.equals(newHostname)) {
updateHostname(newHostname);
return;
}
if (assertServerRealmStoreExists(newHostname)) {
updateHostname(newHostname);
} else {
recoverFromHostnameError();
}
}
}
private boolean assertServerRealmStoreExists(String hostname) {
return RealmStore.get(hostname) != null;
}
private void updateHostname(String hostname) {
this.hostname = hostname;
onHostnameUpdated();
}
private void recoverFromHostnameError() {
final List<ServerInfo> serverInfoList =
ConnectivityManager.getInstance(getApplicationContext()).getServerList();
if (serverInfoList == null || serverInfoList.size() == 0) {
LaunchUtil.showAddServerActivity(this);
return;
}
// just connect to the first available
final ServerInfo serverInfo = serverInfoList.get(0);
rocketChatCache.setSelectedServerHostname(serverInfo.getHostname());
rocketChatCache.setSelectedRoomId(null);
}
private void updateRoomIdIfNeeded(String newRoomId) {
if (roomId == null) {
if (newRoomId != null && assertRoomSubscriptionExists(newRoomId)) {
updateRoomId(newRoomId);
}
} else {
if (!roomId.equals(newRoomId) && assertRoomSubscriptionExists(newRoomId)) {
updateRoomId(newRoomId);
}
}
}
private boolean assertRoomSubscriptionExists(String roomId) {
if (!assertServerRealmStoreExists(hostname)) {
return false;
}
RealmRoom room = RealmStore.get(hostname).executeTransactionForRead(realm ->
realm.where(RealmRoom.class).equalTo(RealmRoom.ROOM_ID, roomId).findFirst());
if (room == null) {
rocketChatCache.setSelectedRoomId(null);
return false;
}
return true;
}
private void updateRoomId(String roomId) {
this.roomId = roomId;
onRoomIdUpdated();
}
protected void onHostnameUpdated() {
}
protected void onRoomIdUpdated() {
}
@Override
protected void onResume() {
super.onResume();
subscribeToConfigChanges();
ConnectivityManager.getInstance(getApplicationContext()).keepAliveServer();
if (isNotification) {
updateHostnameIfNeeded(rocketChatCache.getSelectedServerHostname());
updateRoomIdIfNeeded(rocketChatCache.getSelectedRoomId());
isNotification = false;
}
}
@Override
protected void onPause() {
compositeDisposable.clear();
super.onPause();
}
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
}
private void subscribeToConfigChanges() {
compositeDisposable.add(
rocketChatCache.getSelectedServerHostnamePublisher()
.map(Optional::get)
.distinctUntilChanged()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
this::updateHostnameIfNeeded,
Logger::report
)
);
compositeDisposable.add(
rocketChatCache.getSelectedRoomIdPublisher()
.map(Optional::get)
.distinctUntilChanged()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
this::updateRoomIdIfNeeded,
Logger::report
)
);
}
}
package chat.rocket.android.activity;
import android.os.Bundle;
import android.support.annotation.IdRes;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import com.trello.rxlifecycle2.components.support.RxAppCompatActivity;
import chat.rocket.android.helper.OnBackPressListener;
import icepick.Icepick;
abstract class AbstractFragmentActivity extends RxAppCompatActivity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Icepick.restoreInstanceState(this, savedInstanceState);
}
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
Icepick.saveInstanceState(this, outState);
}
@IdRes
protected abstract int getLayoutContainerForFragment();
@Override
public final void onBackPressed() {
if (!onBackPress()) {
onBackPressedNotHandled();
}
}
protected boolean onBackPress() {
FragmentManager fragmentManager = getSupportFragmentManager();
Fragment fragment = fragmentManager.findFragmentById(getLayoutContainerForFragment());
if (fragment instanceof OnBackPressListener
&& ((OnBackPressListener) fragment).onBackPressed()) {
return true;
}
if (fragmentManager.getBackStackEntryCount() > 0) {
fragmentManager.popBackStack();
return true;
}
return false;
}
protected void onBackPressedNotHandled() {
super.onBackPressed();
}
protected void showFragment(Fragment fragment) {
getSupportFragmentManager().beginTransaction()
.replace(getLayoutContainerForFragment(), fragment)
.commit();
}
protected void showFragmentWithBackStack(Fragment fragment) {
getSupportFragmentManager().beginTransaction()
.replace(getLayoutContainerForFragment(), fragment)
.addToBackStack(null)
.commit();
}
}
package chat.rocket.android.activity;
import android.os.Bundle;
import android.support.annotation.Nullable;
import chat.rocket.android.R;
import chat.rocket.android.fragment.add_server.InputHostnameFragment;
public class AddServerActivity extends AbstractFragmentActivity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.simple_screen);
showFragment(new InputHostnameFragment());
}
@Override
protected int getLayoutContainerForFragment() {
return R.id.content;
}
@Override
protected void onBackPressedNotHandled() {
moveTaskToBack(true);
}
}
package chat.rocket.android.activity;
import android.content.Intent;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import chat.rocket.android.R;
import chat.rocket.android.fragment.server_config.LoginFragment;
import chat.rocket.android.fragment.server_config.RetryLoginFragment;
import chat.rocket.core.interactors.SessionInteractor;
import chat.rocket.android.service.ConnectivityManager;
import chat.rocket.persistence.realm.repositories.RealmSessionRepository;
/**
* Activity for Login, Sign-up, and Retry connecting...
*/
public class LoginActivity extends AbstractFragmentActivity implements LoginContract.View {
public static final String KEY_HOSTNAME = "hostname";
private LoginContract.Presenter presenter;
@Override
protected int getLayoutContainerForFragment() {
return R.id.content;
}
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
String hostname = null;
Intent intent = getIntent();
if (intent != null && intent.getExtras() != null) {
hostname = intent.getStringExtra(KEY_HOSTNAME);
}
presenter = new LoginPresenter(
hostname,
new SessionInteractor(new RealmSessionRepository(hostname)),
ConnectivityManager.getInstance(getApplicationContext())
);
}
@Override
protected void onResume() {
super.onResume();
presenter.bindView(this);
}
@Override
protected void onDestroy() {
presenter.release();
super.onDestroy();
}
private void showFragment(Fragment fragment, String hostname) {
setContentView(R.layout.simple_screen);
injectHostnameArgTo(fragment, hostname);
super.showFragment(fragment);
}
private void injectHostnameArgTo(Fragment fragment, String hostname) {
Bundle args = fragment.getArguments();
if (args == null) {
args = new Bundle();
}
args.putString(LoginActivity.KEY_HOSTNAME, hostname);
fragment.setArguments(args);
}
@Override
protected void onBackPressedNotHandled() {
moveTaskToBack(true);
}
@Override
public void showLogin(String hostname) {
showFragment(new LoginFragment(), hostname);
}
@Override
public void showRetryLogin(String hostname) {
showFragment(new RetryLoginFragment(), hostname);
}
@Override
public void closeView() {
finish();
overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out);
}
}
package chat.rocket.android.activity;
import chat.rocket.android.shared.BaseContract;
public interface LoginContract {
interface View extends BaseContract.View {
void showLogin(String hostname);
void showRetryLogin(String hostname);
void closeView();
}
interface Presenter extends BaseContract.Presenter<View> {
}
}
package chat.rocket.android.activity;
import android.support.annotation.NonNull;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.Disposable;
import chat.rocket.android.BackgroundLooper;
import chat.rocket.android.helper.Logger;
import chat.rocket.android.service.ConnectivityManagerApi;
import chat.rocket.android.shared.BasePresenter;
import chat.rocket.core.interactors.SessionInteractor;
public class LoginPresenter extends BasePresenter<LoginContract.View>
implements LoginContract.Presenter {
private final String hostname;
private final SessionInteractor sessionInteractor;
private final ConnectivityManagerApi connectivityManagerApi;
private boolean isLogging = false;
public LoginPresenter(String hostname,
SessionInteractor sessionInteractor,
ConnectivityManagerApi connectivityManagerApi) {
this.hostname = hostname;
this.sessionInteractor = sessionInteractor;
this.connectivityManagerApi = connectivityManagerApi;
}
@Override
public void bindView(@NonNull LoginContract.View view) {
super.bindView(view);
connectivityManagerApi.keepAliveServer();
if (hostname == null || hostname.length() == 0) {
view.closeView();
return;
}
if (isLogging) {
return;
}
loadSessionState();
}
private void loadSessionState() {
final Disposable subscription = sessionInteractor.getSessionState()
.distinctUntilChanged()
.subscribeOn(AndroidSchedulers.from(BackgroundLooper.get()))
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
state -> {
switch (state) {
case UNAVAILABLE:
isLogging = true;
view.showLogin(hostname);
break;
case INVALID:
isLogging = false;
view.showRetryLogin(hostname);
break;
case VALID:
isLogging = false;
view.closeView();
}
},
Logger::report
);
addSubscription(subscription);
}
}
package chat.rocket.android.activity;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.design.widget.Snackbar;
import android.support.v4.widget.SlidingPaneLayout;
import chat.rocket.android.LaunchUtil;
import chat.rocket.android.R;
import chat.rocket.android.RocketChatCache;
import chat.rocket.android.api.MethodCallHelper;
import chat.rocket.android.fragment.chatroom.HomeFragment;
import chat.rocket.android.fragment.chatroom.RoomFragment;
import chat.rocket.android.fragment.sidebar.SidebarMainFragment;
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.RoomInteractor;
import chat.rocket.core.interactors.SessionInteractor;
import chat.rocket.persistence.realm.repositories.RealmRoomRepository;
import chat.rocket.persistence.realm.repositories.RealmSessionRepository;
import chat.rocket.persistence.realm.repositories.RealmUserRepository;
import hugo.weaving.DebugLog;
/**
* Entry-point for Rocket.Chat.Android application.
*/
public class MainActivity extends AbstractAuthedActivity implements MainContract.View {
private RoomToolbar toolbar;
private StatusTicker statusTicker;
private MainContract.Presenter presenter;
@Override
protected int getLayoutContainerForFragment() {
return R.id.activity_main_container;
}
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
toolbar = (RoomToolbar) findViewById(R.id.activity_main_toolbar);
statusTicker = new StatusTicker();
}
@Override
protected void onResume() {
super.onResume();
if (presenter != null) {
presenter.bindViewOnly(this);
}
}
@Override
protected void onPause() {
if (presenter != null) {
presenter.release();
}
super.onPause();
}
private boolean closeSidebarIfNeeded() {
// REMARK: Tablet UI doesn't have SlidingPane!
SlidingPaneLayout pane = (SlidingPaneLayout) findViewById(R.id.sliding_pane);
if (pane != null && pane.isSlideable() && pane.isOpen()) {
pane.closePane();
return true;
}
return false;
}
@DebugLog
@Override
protected void onHostnameUpdated() {
super.onHostnameUpdated();
if (presenter != null) {
presenter.release();
}
RoomInteractor roomInteractor = new RoomInteractor(new RealmRoomRepository(hostname));
CanCreateRoomInteractor createRoomInteractor = new CanCreateRoomInteractor(
new RealmUserRepository(hostname),
new SessionInteractor(new RealmSessionRepository(hostname))
);
SessionInteractor sessionInteractor = new SessionInteractor(
new RealmSessionRepository(hostname)
);
presenter = new MainPresenter(
roomInteractor,
createRoomInteractor,
sessionInteractor,
new MethodCallHelper(this, hostname),
ConnectivityManager.getInstance(getApplicationContext()),
new RocketChatCache(this)
);
updateSidebarMainFragment();
presenter.bindView(this);
}
private void updateSidebarMainFragment() {
getSupportFragmentManager().beginTransaction()
.replace(R.id.sidebar_fragment_container, SidebarMainFragment.create(hostname))
.commit();
}
@Override
protected void onRoomIdUpdated() {
super.onRoomIdUpdated();
presenter.onOpenRoom(hostname, roomId);
}
@Override
protected boolean onBackPress() {
return closeSidebarIfNeeded() || super.onBackPress();
}
@Override
public void showHome() {
showFragment(new HomeFragment());
}
@Override
public void showRoom(String hostname, String roomId) {
showFragment(RoomFragment.create(hostname, roomId));
closeSidebarIfNeeded();
KeyboardHelper.hideSoftKeyboard(this);
}
@Override
public void showUnreadCount(long roomsCount, int mentionsCount) {
toolbar.setUnreadBudge((int) roomsCount, mentionsCount);
}
@Override
public void showAddServerScreen() {
LaunchUtil.showAddServerActivity(this);
}
@Override
public void showLoginScreen() {
LaunchUtil.showLoginActivity(this, hostname);
statusTicker.updateStatus(StatusTicker.STATUS_DISMISS, null);
}
@Override
public void showConnectionError() {
statusTicker.updateStatus(StatusTicker.STATUS_CONNECTION_ERROR,
Snackbar.make(findViewById(getLayoutContainerForFragment()),
R.string.fragment_retry_login_error_title, Snackbar.LENGTH_INDEFINITE)
.setAction(R.string.fragment_retry_login_retry_title, view ->
presenter.onRetryLogin()));
}
@Override
public void showConnecting() {
statusTicker.updateStatus(StatusTicker.STATUS_TOKEN_LOGIN,
Snackbar.make(findViewById(getLayoutContainerForFragment()),
R.string.server_config_activity_authenticating, Snackbar.LENGTH_INDEFINITE));
}
@Override
public void showConnectionOk() {
statusTicker.updateStatus(StatusTicker.STATUS_DISMISS, null);
}
//TODO: consider this class to define in layouthelper for more complicated operation.
private static class StatusTicker {
public static final int STATUS_DISMISS = 0;
public static final int STATUS_CONNECTION_ERROR = 1;
public static final int STATUS_TOKEN_LOGIN = 2;
private int status;
private Snackbar snackbar;
public StatusTicker() {
status = STATUS_DISMISS;
}
public void updateStatus(int status, Snackbar snackbar) {
if (status == this.status) {
return;
}
this.status = status;
if (this.snackbar != null) {
this.snackbar.dismiss();
}
if (status != STATUS_DISMISS) {
this.snackbar = snackbar;
if (this.snackbar != null) {
this.snackbar.show();
}
}
}
}
}
package chat.rocket.android.activity;
import chat.rocket.android.shared.BaseContract;
public interface MainContract {
interface View extends BaseContract.View {
void showHome();
void showRoom(String hostname, String roomId);
void showUnreadCount(long roomsCount, int mentionsCount);
void showAddServerScreen();
void showLoginScreen();
void showConnectionError();
void showConnecting();
void showConnectionOk();
}
interface Presenter extends BaseContract.Presenter<View> {
void onOpenRoom(String hostname, String roomId);
void onRetryLogin();
void bindViewOnly(View view);
}
}
package chat.rocket.android.activity;
import android.support.annotation.NonNull;
import android.support.v4.util.Pair;
import chat.rocket.android.BackgroundLooper;
import chat.rocket.android.RocketChatCache;
import chat.rocket.android.api.MethodCallHelper;
import chat.rocket.android.helper.LogIfError;
import chat.rocket.android.helper.Logger;
import chat.rocket.android.service.ConnectivityManagerApi;
import chat.rocket.android.service.ServerConnectivity;
import chat.rocket.android.shared.BasePresenter;
import chat.rocket.core.interactors.CanCreateRoomInteractor;
import chat.rocket.core.interactors.RoomInteractor;
import chat.rocket.core.interactors.SessionInteractor;
import chat.rocket.core.models.Session;
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>
implements MainContract.Presenter {
private final CanCreateRoomInteractor canCreateRoomInteractor;
private final RoomInteractor roomInteractor;
private final SessionInteractor sessionInteractor;
private final MethodCallHelper methodCallHelper;
private final ConnectivityManagerApi connectivityManagerApi;
private final RocketChatCache rocketChatCache;
public MainPresenter(RoomInteractor roomInteractor,
CanCreateRoomInteractor canCreateRoomInteractor,
SessionInteractor sessionInteractor,
MethodCallHelper methodCallHelper,
ConnectivityManagerApi connectivityManagerApi,
RocketChatCache rocketChatCache) {
this.roomInteractor = roomInteractor;
this.canCreateRoomInteractor = canCreateRoomInteractor;
this.sessionInteractor = sessionInteractor;
this.methodCallHelper = methodCallHelper;
this.connectivityManagerApi = connectivityManagerApi;
this.rocketChatCache = rocketChatCache;
}
@Override
public void bindViewOnly(@NonNull MainContract.View view) {
super.bindView(view);
subscribeToUnreadCount();
subscribeToSession();
setUserOnline();
}
@Override
public void bindView(@NonNull MainContract.View view) {
super.bindView(view);
if (shouldLaunchAddServerActivity()) {
view.showAddServerScreen();
return;
}
openRoom();
subscribeToNetworkChanges();
subscribeToUnreadCount();
subscribeToSession();
setUserOnline();
}
@Override
public void release() {
setUserAway();
super.release();
}
@Override
public void onOpenRoom(String hostname, String roomId) {
final Disposable subscription = canCreateRoomInteractor.canCreate(roomId)
.subscribeOn(AndroidSchedulers.from(BackgroundLooper.get()))
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
allowed -> {
if (allowed) {
view.showRoom(hostname, roomId);
} else {
view.showHome();
}
},
Logger::report
);
addSubscription(subscription);
}
@Override
public void onRetryLogin() {
final Disposable subscription = sessionInteractor.retryLogin()
.subscribe();
addSubscription(subscription);
}
private void openRoom() {
String hostname = rocketChatCache.getSelectedServerHostname();
String roomId = rocketChatCache.getSelectedRoomId();
if (roomId == null || roomId.length() == 0) {
view.showHome();
return;
}
onOpenRoom(hostname, roomId);
}
private void subscribeToUnreadCount() {
final Disposable subscription = Flowable.combineLatest(
roomInteractor.getTotalUnreadRoomsCount(),
roomInteractor.getTotalUnreadMentionsCount(),
(Pair::new)
)
.subscribeOn(AndroidSchedulers.from(BackgroundLooper.get()))
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
pair -> view.showUnreadCount(pair.first, pair.second),
Logger::report
);
addSubscription(subscription);
}
private void subscribeToSession() {
final Disposable subscription = sessionInteractor.getDefault()
.subscribeOn(AndroidSchedulers.from(BackgroundLooper.get()))
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
sessionOptional -> {
Session session = sessionOptional.orNull();
if (session == null || session.getToken() == null) {
view.showLoginScreen();
return;
}
String error = session.getError();
if (error != null && error.length() != 0) {
view.showConnectionError();
return;
}
if (!session.isTokenVerified()) {
view.showConnecting();
return;
}
view.showConnectionOk();
},
Logger::report
);
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() {
methodCallHelper.setUserPresence(User.STATUS_ONLINE)
.continueWith(new LogIfError());
}
private void setUserAway() {
methodCallHelper.setUserPresence(User.STATUS_AWAY)
.continueWith(new LogIfError());
}
private boolean shouldLaunchAddServerActivity() {
return connectivityManagerApi.getServerList().isEmpty();
}
}
package chat.rocket.android.api;
import android.support.annotation.Nullable;
import chat.rocket.android.helper.OkHttpHelper;
import org.json.JSONArray;
import org.json.JSONException;
import java.util.UUID;
import bolts.Task;
import chat.rocket.android.helper.TextUtils;
import chat.rocket.android.log.RCLog;
import chat.rocket.android_ddp.DDPClient;
import chat.rocket.android_ddp.DDPClientCallback;
import chat.rocket.android_ddp.DDPSubscription;
import io.reactivex.Flowable;
import io.reactivex.Maybe;
/**
* DDP client wrapper.
*/
public class DDPClientWrapper {
private final DDPClient ddpClient;
private final String hostname;
private DDPClientWrapper(String hostname) {
ddpClient = new DDPClient(OkHttpHelper.INSTANCE.getClientForWebSocket());
this.hostname = hostname;
}
/**
* create new API client instance.
*/
public static DDPClientWrapper create(String hostname) {
return new DDPClientWrapper(hostname);
}
/**
* Connect to WebSocket server with DDP client.
*/
public Task<DDPClientCallback.Connect> connect(@Nullable String session,
boolean usesSecureConnection) {
final String protocol = usesSecureConnection ? "wss://" : "ws://";
return ddpClient.connect(protocol + hostname + "/websocket", session);
}
/**
* close connection.
*/
public void close() {
ddpClient.close();
}
/**
* Subscribe with DDP client.
*/
public Task<DDPSubscription.Ready> subscribe(final String name, JSONArray param) {
final String subscriptionId = UUID.randomUUID().toString();
RCLog.d("sub:[%s]> %s(%s)", subscriptionId, name, param);
return ddpClient.sub(subscriptionId, name, param);
}
/**
* Unsubscribe with DDP client.
*/
public Task<DDPSubscription.NoSub> unsubscribe(final String subscriptionId) {
RCLog.d("unsub:[%s]>", subscriptionId);
return ddpClient.unsub(subscriptionId);
}
/**
* Returns Observable for handling DDP subscription.
*/
public Flowable<DDPSubscription.Event> getSubscriptionCallback() {
return ddpClient.getSubscriptionCallback();
}
/**
* Execute raw RPC.
*/
public Task<DDPClientCallback.RPC> rpc(String methodCallId, String methodName, String params,
long timeoutMs) {
RCLog.d("rpc:[%s]> %s(%s) timeout=%d", methodCallId, methodName, params, timeoutMs);
if (TextUtils.isEmpty(params)) {
return ddpClient.rpc(methodName, null, methodCallId, timeoutMs).continueWithTask(task -> {
if (task.isFaulted()) {
RCLog.d("rpc:[%s]< error = %s", methodCallId, task.getError());
} else {
RCLog.d("rpc:[%s]< result = %s", methodCallId, task.getResult().result);
}
return task;
});
}
try {
return ddpClient.rpc(methodName, new JSONArray(params), methodCallId, timeoutMs)
.continueWithTask(task -> {
if (task.isFaulted()) {
RCLog.d("rpc:[%s]< error = %s", methodCallId, task.getError());
} else {
RCLog.d("rpc:[%s]< result = %s", methodCallId, task.getResult().result);
}
return task;
});
} catch (JSONException exception) {
return Task.forError(exception);
}
}
/**
* check WebSocket connectivity with ping.
*/
public Task<Void> ping() {
final String pingId = UUID.randomUUID().toString();
RCLog.d("ping[%s] >", pingId);
return ddpClient.ping(pingId)
.continueWithTask(task -> {
if (task.isFaulted()) {
RCLog.d(task.getError(), "ping[%s] xxx failed xxx", pingId);
return Task.forError(task.getError());
} else {
RCLog.d("pong[%s] <", pingId);
return Task.forResult(null);
}
});
}
/**
* check WebSocket connectivity with ping.
*/
public Maybe<DDPClientCallback.Base> doPing() {
final String pingId = UUID.randomUUID().toString();
RCLog.d("ping[%s] >", pingId);
return ddpClient.doPing(pingId);
}
}
package chat.rocket.android.api;
import android.content.Context;
import org.json.JSONArray;
import org.json.JSONObject;
import bolts.Task;
import chat.rocket.android.helper.TextUtils;
import chat.rocket.persistence.realm.RealmHelper;
import chat.rocket.android.service.DDPClientRef;
/**
* MethodCall for uploading file.
*/
public class FileUploadingHelper extends MethodCallHelper {
public FileUploadingHelper(Context context, String hostname) {
super(context, hostname);
}
public FileUploadingHelper(RealmHelper realmHelper, DDPClientRef ddpClientRef) {
super(realmHelper, ddpClientRef);
}
public Task<JSONObject> uploadS3Request(String filename, long filesize, String mimeType,
String roomId) {
return uploadRequest("rocketchat-uploads", filename, filesize, mimeType, roomId);
}
public Task<JSONObject> uploadGoogleRequest(String filename, long filesize, String mimeType,
String roomId) {
return uploadRequest("rocketchat-uploads-gs", filename, filesize, mimeType, roomId);
}
public Task<Void> sendFileMessage(String roomId, String storageType, JSONObject fileObj) {
return call("sendFileMessage", TIMEOUT_MS, () -> new JSONArray()
.put(roomId)
.put(TextUtils.isEmpty(storageType) ? JSONObject.NULL : storageType)
.put(fileObj))
.onSuccessTask(task -> Task.forResult(null));
}
public Task<JSONObject> ufsCreate(String filename, long filesize, String mimeType, String store,
String roomId) {
return call("ufsCreate", TIMEOUT_MS, () -> new JSONArray().put(new JSONObject()
.put("name", filename)
.put("size", filesize)
.put("type", mimeType)
.put("store", store)
.put("rid", roomId)
)).onSuccessTask(CONVERT_TO_JSON_OBJECT);
}
public Task<JSONObject> ufsComplete(String fileId, String token, String store) {
return call("ufsComplete", TIMEOUT_MS, () -> new JSONArray()
.put(fileId)
.put(store)
.put(token)
).onSuccessTask(CONVERT_TO_JSON_OBJECT);
}
private Task<JSONObject> uploadRequest(String uploadType, String filename,
long filesize, String mimeType,
String roomId) {
return call("slingshot/uploadRequest", TIMEOUT_MS, () -> new JSONArray()
.put(uploadType)
.put(new JSONObject()
.put("name", filename)
.put("size", filesize)
.put("type", mimeType))
.put(new JSONObject().put("rid", roomId)))
.onSuccessTask(CONVERT_TO_JSON_OBJECT);
}
}
package chat.rocket.android.api;
import android.content.Context;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import org.json.JSONArray;
import org.json.JSONObject;
import bolts.Task;
import chat.rocket.persistence.realm.RealmHelper;
import chat.rocket.android.service.DDPClientRef;
public class RaixPushHelper extends MethodCallHelper {
public RaixPushHelper(Context context, String hostname) {
super(context, hostname);
}
public RaixPushHelper(RealmHelper realmHelper,
DDPClientRef ddpClientRef) {
super(realmHelper, ddpClientRef);
}
public Task<Void> pushUpdate(@NonNull String pushId, @NonNull String gcmToken,
@Nullable String userId) {
return call("raix:push-update", TIMEOUT_MS, () ->
new JSONArray().put(new JSONObject()
.put("id", pushId)
.put("appName", "main")
.put("userId", userId != null ? userId : JSONObject.NULL)
.put("metadata", new JSONObject())
.put("token", new JSONObject().put("gcm", gcmToken))))
.onSuccessTask(task -> Task.forResult(null));
}
public Task<Void> pushSetUser(String pushId) {
return call("raix:push-setuser", TIMEOUT_MS, () -> new JSONArray().put(pushId))
.onSuccessTask(task -> Task.forResult(null));
}
}
package chat.rocket.android.api;
public class TwoStepAuthException extends Exception {
public static final String TYPE = "totp-required";
private static final long serialVersionUID = 7063596902054234189L;
public TwoStepAuthException() {
super();
}
public TwoStepAuthException(String message) {
super(message);
}
}
package chat.rocket.android.api.rest;
import java.io.IOException;
import chat.rocket.android.helper.TextUtils;
import okhttp3.Interceptor;
import okhttp3.Request;
import okhttp3.Response;
public class CookieInterceptor implements Interceptor {
private final CookieProvider cookieProvider;
public CookieInterceptor(CookieProvider cookieProvider) {
this.cookieProvider = cookieProvider;
}
@Override
public Response intercept(Chain chain) throws IOException {
if (chain.request().url().host().equals(cookieProvider.getHostname())) {
final String cookie = cookieProvider.getCookie();
if (!TextUtils.isEmpty(cookie)) {
Request newRequest = chain.request().newBuilder()
.header("Cookie", cookie)
.build();
return chain.proceed(newRequest);
}
}
return chain.proceed(chain.request());
}
}
package chat.rocket.android.api.rest;
public interface CookieProvider {
String getHostname();
String getCookie();
}
package chat.rocket.android.api.rest;
import chat.rocket.android.RocketChatCache;
import chat.rocket.persistence.realm.models.ddp.RealmUser;
import chat.rocket.persistence.realm.models.internal.RealmSession;
import chat.rocket.persistence.realm.RealmHelper;
import chat.rocket.persistence.realm.RealmStore;
public class DefaultCookieProvider implements CookieProvider {
private RocketChatCache rocketChatCache;
public DefaultCookieProvider(RocketChatCache rocketChatCache) {
this.rocketChatCache = rocketChatCache;
}
@Override
public String getHostname() {
return getHostnameFromCache();
}
@Override
public String getCookie() {
final String hostname = getHostnameFromCache();
if (hostname == null) {
return "";
}
final RealmHelper realmHelper = RealmStore.get(getHostnameFromCache());
if (realmHelper == null) {
return "";
}
final RealmUser user = realmHelper.executeTransactionForRead(realm ->
RealmUser.queryCurrentUser(realm).findFirst());
final RealmSession session = realmHelper.executeTransactionForRead(realm ->
RealmSession.queryDefaultSession(realm).findFirst());
if (user == null || session == null) {
return "";
}
return "rc_uid=" + user.getId() + ";rc_token=" + session.getToken();
}
private String getHostnameFromCache() {
return rocketChatCache.getSelectedServerHostname();
}
}
package chat.rocket.android.api.rest;
import android.support.annotation.NonNull;
import io.reactivex.BackpressureStrategy;
import io.reactivex.Flowable;
import io.reactivex.FlowableEmitter;
import org.json.JSONObject;
import java.io.IOException;
import okhttp3.Call;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.ResponseBody;
public class DefaultServerPolicyApi implements ServerPolicyApi {
private static final String API_INFO_PATH = "/api/info";
private final OkHttpClient client;
private final String host;
public DefaultServerPolicyApi(@NonNull OkHttpClient client, @NonNull String host) {
this.client = client;
this.host = host;
}
@Override
public Flowable<Response<JSONObject>> getApiInfoSecurely() {
return getApiInfo(SECURE_PROTOCOL);
}
@Override
public Flowable<Response<JSONObject>> getApiInfoInsecurely() {
return getApiInfo(INSECURE_PROTOCOL);
}
private Flowable<Response<JSONObject>> getApiInfo(@NonNull String protocol) {
return Flowable.create(responseEmitter -> {
final Call call = client.newCall(createRequest(protocol));
call.enqueue(getOkHttpCallback(responseEmitter, protocol));
responseEmitter.setCancellable(call::cancel);
}, BackpressureStrategy.LATEST);
}
private Request createRequest(@NonNull String protocol) {
return new Request.Builder()
.url(protocol + host + API_INFO_PATH)
.get()
.build();
}
private okhttp3.Callback getOkHttpCallback(@NonNull FlowableEmitter<Response<JSONObject>> emitter,
@NonNull String protocol) {
return new okhttp3.Callback() {
@Override
public void onFailure(Call call, IOException ioException) {
if (emitter.isCancelled()) {
return;
}
emitter.onError(ioException);
}
@Override
public void onResponse(Call call, okhttp3.Response response) throws IOException {
if (emitter.isCancelled()) {
return;
}
if (!response.isSuccessful()) {
emitter.onNext(new Response<>(false, protocol, null));
emitter.onComplete();
return;
}
final ResponseBody body = response.body();
if (body == null || body.contentLength() == 0) {
emitter.onNext(new Response<>(false, protocol, null));
emitter.onComplete();
return;
}
try {
emitter.onNext(new Response<>(true, protocol, new JSONObject(body.string())));
} catch (Exception e) {
emitter.onNext(new Response<>(false, protocol, null));
}
emitter.onComplete();
}
};
}
}
package chat.rocket.android.api.rest;
public class Response<T> {
private final boolean successful;
private final String protocol;
private final T data;
public Response(boolean successful, String protocol, T data) {
this.successful = successful;
this.protocol = protocol;
this.data = data;
}
public boolean isSuccessful() {
return successful;
}
public String getProtocol() {
return protocol;
}
public T getData() {
return data;
}
}
package chat.rocket.android.api.rest;
import io.reactivex.Flowable;
import org.json.JSONObject;
public interface ServerPolicyApi {
String SECURE_PROTOCOL = "https://";
String INSECURE_PROTOCOL = "http://";
Flowable<Response<JSONObject>> getApiInfoSecurely();
Flowable<Response<JSONObject>> getApiInfoInsecurely();
}
package chat.rocket.android.fragment;
import android.os.Bundle;
import android.support.annotation.LayoutRes;
import android.support.annotation.Nullable;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import com.trello.rxlifecycle2.components.support.RxFragment;
/**
* Fragment base class for this Application.
*/
public abstract class AbstractFragment extends RxFragment {
protected View rootView;
@LayoutRes
protected abstract int getLayout();
protected abstract void onSetupView();
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
rootView = inflater.inflate(getLayout(), container, false);
onSetupView();
return rootView;
}
protected void finish() {
if (getFragmentManager().getBackStackEntryCount() == 0) {
getActivity().finish();
} else {
getFragmentManager().popBackStack();
}
}
}
package chat.rocket.android.fragment;
import android.graphics.Bitmap;
import android.os.Build;
import android.os.Message;
import android.view.View;
import android.webkit.WebResourceError;
import android.webkit.WebResourceRequest;
import android.webkit.WebSettings;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import chat.rocket.android.R;
import chat.rocket.android.helper.OnBackPressListener;
import hugo.weaving.DebugLog;
public abstract class AbstractWebViewFragment extends AbstractFragment
implements OnBackPressListener {
private boolean isSet = false;
private WebView webview;
private WebViewClient webviewClient = new WebViewClient() {
private boolean error;
@Override
public void onPageStarted(WebView webview, String url, Bitmap favicon) {
error = false;
}
@Override
public void onPageFinished(WebView webview, String url) {
if (!error) {
onPageLoaded(webview, url);
}
}
@Override
public void onReceivedError(WebView view, WebResourceRequest request, WebResourceError error) {
super.onReceivedError(view, request, error);
this.error = true;
}
@Override
public boolean shouldOverrideUrlLoading(WebView webview, String url) {
return (shouldOverride(webview, url) && onHandleCallback(webview, url))
|| super.shouldOverrideUrlLoading(webview, url);
}
@DebugLog
@Override
public void onFormResubmission(WebView view, Message dontResend, Message resend) {
//resend POST request without confirmation.
resend.sendToTarget();
}
};
@Override
protected int getLayout() {
return R.layout.webview;
}
@Override
protected void onSetupView() {
webview = (WebView) rootView.findViewById(R.id.webview);
setupWebView();
navigateToInitialPage(webview);
}
private void setupWebView() {
if (isSet) {
return;
}
WebSettings settings = webview.getSettings();
if (settings != null) {
settings.setJavaScriptEnabled(true);
}
webview.setHorizontalScrollBarEnabled(false);
webview.setWebViewClient(webviewClient);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
WebView.setWebContentsDebuggingEnabled(true);
}
if (Build.VERSION.SDK_INT == Build.VERSION_CODES.JELLY_BEAN) {
//refs: https://code.google.com/p/android/issues/detail?id=35288
webview.setLayerType(View.LAYER_TYPE_SOFTWARE, null);
}
isSet = true;
}
@Override
public boolean onBackPressed() {
if (webview.canGoBack()) {
webview.goBack();
return true;
} else {
return false;
}
}
protected WebView getWebview() {
if (webview == null) {
return null;
}
setupWebView();
return webview;
}
protected abstract void navigateToInitialPage(WebView webview);
protected void onPageLoaded(WebView webview, String url) {
}
protected boolean shouldOverride(WebView webview, String url) {
return false;
}
protected boolean onHandleCallback(WebView webview, String url) {
return false;
}
}
package chat.rocket.android.fragment.add_server;
import chat.rocket.android.shared.BaseContract;
public interface InputHostnameContract {
interface View extends BaseContract.View {
void showLoader();
void hideLoader();
void showInvalidServerError();
void showConnectionError();
void showHome();
}
interface Presenter extends BaseContract.Presenter<View> {
void connectTo(String hostname);
}
}
package chat.rocket.android.fragment.add_server;
import android.content.Context;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.constraint.ConstraintLayout;
import android.support.design.widget.Snackbar;
import android.view.View;
import android.widget.TextView;
import chat.rocket.android.BuildConfig;
import chat.rocket.android.LaunchUtil;
import chat.rocket.android.R;
import chat.rocket.android.RocketChatCache;
import chat.rocket.android.fragment.AbstractFragment;
import chat.rocket.android.helper.TextUtils;
import chat.rocket.android.service.ConnectivityManager;
/**
* Input server host.
*/
public class InputHostnameFragment extends AbstractFragment implements InputHostnameContract.View {
private InputHostnameContract.Presenter presenter;
private ConstraintLayout container;
private View waitingView;
public InputHostnameFragment() {}
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Context appContext = getContext().getApplicationContext();
presenter = new InputHostnamePresenter(new RocketChatCache(appContext), ConnectivityManager.getInstance(appContext));
}
@Override
protected int getLayout() {
return R.layout.fragment_input_hostname;
}
@Override
protected void onSetupView() {
setupVersionInfo();
container = rootView.findViewById(R.id.container);
waitingView = rootView.findViewById(R.id.waiting);
rootView.findViewById(R.id.btn_connect).setOnClickListener(view -> handleConnect());
}
private void setupVersionInfo() {
TextView versionInfoView = (TextView) rootView.findViewById(R.id.version_info);
versionInfoView.setText(getString(R.string.version_info_text, BuildConfig.VERSION_NAME));
}
private void handleConnect() {
presenter.connectTo(getHostname());
}
@Override
public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
presenter.bindView(this);
}
@Override
public void onDestroyView() {
presenter.release();
super.onDestroyView();
}
private String getHostname() {
final TextView editor = (TextView) rootView.findViewById(R.id.editor_hostname);
return TextUtils.or(TextUtils.or(editor.getText(), editor.getHint()), "").toString();
}
private void showError(String errString) {
Snackbar.make(rootView, errString, Snackbar.LENGTH_LONG).show();
}
@Override
public void showLoader() {
container.setVisibility(View.GONE);
waitingView.setVisibility(View.VISIBLE);
}
@Override
public void hideLoader() {
waitingView.setVisibility(View.GONE);
container.setVisibility(View.VISIBLE);
}
@Override
public void showInvalidServerError() {
showError(getString(R.string.input_hostname_invalid_server_message));
}
@Override
public void showConnectionError() {
showError(getString(R.string.connection_error_try_later));
}
@Override
public void showHome() {
LaunchUtil.showMainActivity(getContext());
getActivity().overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out);
}
}
package chat.rocket.android.fragment.add_server;
import chat.rocket.android.BackgroundLooper;
import chat.rocket.android.helper.OkHttpHelper;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.Disposable;
import chat.rocket.android.RocketChatCache;
import chat.rocket.android.api.rest.DefaultServerPolicyApi;
import chat.rocket.android.api.rest.ServerPolicyApi;
import chat.rocket.android.helper.ServerPolicyApiValidationHelper;
import chat.rocket.android.helper.ServerPolicyHelper;
import chat.rocket.android.service.ConnectivityManagerApi;
import chat.rocket.android.shared.BasePresenter;
public class InputHostnamePresenter extends BasePresenter<InputHostnameContract.View> implements InputHostnameContract.Presenter {
private final RocketChatCache rocketChatCache;
private final ConnectivityManagerApi connectivityManager;
public InputHostnamePresenter(RocketChatCache rocketChatCache, ConnectivityManagerApi connectivityManager) {
this.rocketChatCache = rocketChatCache;
this.connectivityManager = connectivityManager;
}
@Override
public void connectTo(final String hostname) {
view.showLoader();
connectToEnforced(ServerPolicyHelper.enforceHostname(hostname));
}
public void connectToEnforced(final String hostname) {
final ServerPolicyApi serverPolicyApi = new DefaultServerPolicyApi(OkHttpHelper.INSTANCE.getClientForUploadFile(), hostname);
final ServerPolicyApiValidationHelper validationHelper = new ServerPolicyApiValidationHelper(serverPolicyApi);
clearSubscriptions();
final Disposable subscription = ServerPolicyHelper.isApiVersionValid(validationHelper)
.subscribeOn(AndroidSchedulers.from(BackgroundLooper.get()))
.observeOn(AndroidSchedulers.mainThread())
.doOnTerminate(() -> view.hideLoader())
.subscribe(
serverValidation -> {
if (serverValidation.isValid()) {
onServerValid(hostname, serverValidation.usesSecureConnection());
} else {
view.showInvalidServerError();
}
},
throwable -> view.showConnectionError());
addSubscription(subscription);
}
private void onServerValid(String hostname, boolean usesSecureConnection) {
rocketChatCache.setSelectedServerHostname(hostname);
String server = hostname.replace("/", ".");
connectivityManager.addOrUpdateServer(server, server, !usesSecureConnection);
connectivityManager.keepAliveServer();
view.showHome();
}
}
\ No newline at end of file
package chat.rocket.android.fragment.chatroom;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import chat.rocket.android.R;
import chat.rocket.android.fragment.AbstractFragment;
import chat.rocket.android.widget.RoomToolbar;
import chat.rocket.core.models.User;
abstract class AbstractChatRoomFragment extends AbstractFragment {
private RoomToolbar roomToolbar;
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
roomToolbar = getActivity().findViewById(R.id.activity_main_toolbar);
return super.onCreateView(inflater, container, savedInstanceState);
}
protected void setToolbarTitle(CharSequence title) {
roomToolbar.setTitle(title);
}
protected void showToolbarPrivateChannelIcon() {
roomToolbar.showPrivateChannelIcon();
}
protected void showToolbarPublicChannelIcon() {
roomToolbar.showPublicChannelIcon();
}
protected void showToolbarLivechatChannelIcon() {
roomToolbar.showLivechatChannelIcon();
}
protected void showToolbarUserStatuslIcon(@Nullable String status) {
if (status == null) {
roomToolbar.showUserStatusIcon(RoomToolbar.STATUS_OFFLINE);
} else {
switch (status) {
case User.STATUS_ONLINE:
roomToolbar.showUserStatusIcon(RoomToolbar.STATUS_ONLINE);
break;
case User.STATUS_BUSY:
roomToolbar.showUserStatusIcon(RoomToolbar.STATUS_BUSY);
break;
case User.STATUS_AWAY:
roomToolbar.showUserStatusIcon(RoomToolbar.STATUS_AWAY);
break;
default:
roomToolbar.showUserStatusIcon(RoomToolbar.STATUS_OFFLINE);
break;
}
}
}
}
\ No newline at end of file
package chat.rocket.android.fragment.chatroom;
import chat.rocket.android.R;
public class HomeFragment extends AbstractChatRoomFragment {
public HomeFragment() {}
@Override
protected int getLayout() {
return R.layout.fragment_home;
}
@Override
protected void onSetupView() {
setToolbarTitle(getText(R.string.home_fragment_title));
}
}
\ No newline at end of file
package chat.rocket.android.fragment.chatroom;
import chat.rocket.android.widget.AbsoluteUrl;
import chat.rocket.core.models.ServerInfo;
import chat.rocket.core.models.Session;
import chat.rocket.core.models.User;
public class RocketChatAbsoluteUrl implements AbsoluteUrl {
private final String baseUrl;
private final String userId;
private final String token;
public RocketChatAbsoluteUrl(ServerInfo info, User user, Session session) {
baseUrl = (info.isSecure() ? "https://" : "http://") + info.getHostname();
userId = user.getId();
token = session.getToken();
}
@Override
public String from(String url) {
return url.startsWith("/") ? baseUrl + url + "?rc_uid=" + userId + "&rc_token=" + token : url;
}
}
package chat.rocket.android.fragment.chatroom;
import android.support.annotation.Nullable;
import chat.rocket.core.models.User;
import java.util.List;
import chat.rocket.android.shared.BaseContract;
import chat.rocket.core.models.Message;
import chat.rocket.core.models.Room;
public interface RoomContract {
interface View extends BaseContract.View {
void setupWith(RocketChatAbsoluteUrl rocketChatAbsoluteUrl);
void render(Room room);
void showUserStatus(User user);
void updateHistoryState(boolean hasNext, boolean isLoaded);
void onMessageSendSuccessfully();
void showUnreadCount(int count);
void showMessages(List<Message> messages);
void showMessageSendFailure(Message message);
void autoloadImages();
void manualLoadImages();
}
interface Presenter extends BaseContract.Presenter<View> {
void loadMessages();
void loadMoreMessages();
void onMessageSelected(@Nullable Message message);
void sendMessage(String messageText);
void resendMessage(Message message);
void updateMessage(Message message, String content);
void deleteMessage(Message message);
void onUnreadCount();
void onMarkAsRead();
void refreshRoom();
}
}
package chat.rocket.android.fragment.chatroom.dialog;
import android.app.Dialog;
import android.os.Bundle;
import android.support.annotation.LayoutRes;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.design.widget.BottomSheetDialogFragment;
import chat.rocket.persistence.realm.RealmHelper;
import chat.rocket.persistence.realm.RealmStore;
abstract class AbstractChatRoomDialogFragment extends BottomSheetDialogFragment {
protected RealmHelper realmHelper;
protected String roomId;
@LayoutRes
protected abstract int getLayout();
protected abstract void onSetupDialog();
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Bundle args = getArguments();
if (args != null) {
handleArgs(args);
}
}
protected void handleArgs(@NonNull Bundle args) {
String hostname = args.getString("hostname");
realmHelper = RealmStore.get(hostname);
roomId = args.getString("roomId");
}
@Override
public final void setupDialog(Dialog dialog, int style) {
super.setupDialog(dialog, style);
dialog.setContentView(getLayout());
onSetupDialog();
}
}
package chat.rocket.android.fragment.chatroom.dialog;
import android.content.DialogInterface;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.widget.ProgressBar;
import android.widget.TextView;
import android.widget.Toast;
import chat.rocket.android.R;
import chat.rocket.core.SyncState;
import chat.rocket.persistence.realm.models.internal.FileUploading;
import chat.rocket.persistence.realm.RealmObjectObserver;
import chat.rocket.android.renderer.FileUploadingRenderer;
/**
* dialog fragment to display progress of file uploading.
*/
public class FileUploadProgressDialogFragment extends AbstractChatRoomDialogFragment {
private String uplId;
private RealmObjectObserver<FileUploading> fileUploadingObserver;
public FileUploadProgressDialogFragment() {
}
public static FileUploadProgressDialogFragment create(String hostname,
String roomId, String uplId) {
Bundle args = new Bundle();
args.putString("hostname", hostname);
args.putString("roomId", roomId);
args.putString("uplId", uplId);
FileUploadProgressDialogFragment fragment = new FileUploadProgressDialogFragment();
fragment.setArguments(args);
return fragment;
}
@Override
protected void handleArgs(@NonNull Bundle args) {
super.handleArgs(args);
uplId = args.getString("uplId");
}
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
fileUploadingObserver = realmHelper
.createObjectObserver(realm -> realm.where(FileUploading.class).equalTo("uplId", uplId))
.setOnUpdateListener(this::onRenderFileUploadingState);
}
@Override
protected int getLayout() {
return R.layout.dialog_file_uploading;
}
@Override
protected void onSetupDialog() {
}
private void onRenderFileUploadingState(FileUploading state) {
if (state == null) {
return;
}
int syncstate = state.getSyncState();
if (syncstate == SyncState.SYNCED) {
dismiss();
} else if (syncstate == SyncState.FAILED) {
Toast.makeText(getContext(), state.getError(), Toast.LENGTH_SHORT).show();
//TODO: prompt retry.
dismiss();
} else {
new FileUploadingRenderer(getContext(), state)
.progressInto((ProgressBar) getDialog().findViewById(R.id.progressBar))
.progressTextInto(
(TextView) getDialog().findViewById(R.id.txt_filesize_uploaded),
(TextView) getDialog().findViewById(R.id.txt_filesize_total));
}
}
@Override
public void onResume() {
super.onResume();
fileUploadingObserver.sub();
}
@Override
public void onPause() {
fileUploadingObserver.unsub();
super.onPause();
}
@Override
public void onCancel(DialogInterface dialog) {
//TODO: should cancel uploading? or continue with showing notification with progress?
}
}
package chat.rocket.android.fragment.oauth;
import chat.rocket.core.models.LoginServiceConfiguration;
public class TwitterOAuthFragment extends AbstractOAuthFragment {
@Override
protected String getOAuthServiceName() {
return "twitter";
}
@Override
protected String generateURL(LoginServiceConfiguration oauthConfig) {
return "https://" + hostname + "/_oauth/twitter/"
+ "?requestTokenAndRedirect=true&state=" + getStateString();
}
}
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
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