diff --git a/app/build.gradle b/app/build.gradle
index a3b74dd9d..1556e3bff 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -55,6 +55,7 @@ android {
ext {
supportLibraryVersion = '26.1.0'
+ googleLibraryVersion = '11.4.2'
}
dependencies {
@@ -69,7 +70,7 @@ dependencies {
implementation 'com.bluelinelabs:conductor:2.1.4'
implementation 'com.bluelinelabs:conductor-support:2.1.4'
- implementation 'com.squareup.okhttp3:okhttp:3.8.1'
+ implementation 'com.squareup.okhttp3:okhttp:3.9.0'
implementation 'com.squareup.okhttp3:okhttp-urlconnection:3.6.0'
implementation 'com.squareup.okhttp3:logging-interceptor:3.6.0'
@@ -121,6 +122,13 @@ dependencies {
implementation 'org.webrtc:google-webrtc:1.0.+'
implementation "org.jetbrains.kotlin:kotlin-stdlib:${kotlinVersion}"
+ implementation 'com.evernote:android-job:1.2.0'
+
+ implementation "com.google.firebase:firebase-messaging:${googleLibraryVersion}"
+ implementation "com.google.android.gms:play-services-base:${googleLibraryVersion}"
+ implementation "com.google.android.gms:play-services-gcm:${googleLibraryVersion}"
+ implementation "com.google.firebase:firebase-core:${googleLibraryVersion}"
+
testImplementation 'junit:junit:4.12'
androidTestImplementation ('com.android.support.test.espresso:espresso-core:3.0.1', {
exclude group: 'com.android.support', module: 'support-annotations'
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 9adb78439..d2c87f5f4 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -38,5 +38,19 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/java/com/nextcloud/talk/api/NcApi.java b/app/src/main/java/com/nextcloud/talk/api/NcApi.java
index bad72474a..54e3537e6 100644
--- a/app/src/main/java/com/nextcloud/talk/api/NcApi.java
+++ b/app/src/main/java/com/nextcloud/talk/api/NcApi.java
@@ -24,6 +24,7 @@ import com.nextcloud.talk.api.models.json.call.CallOverall;
import com.nextcloud.talk.api.models.json.generic.Status;
import com.nextcloud.talk.api.models.json.participants.AddParticipantOverall;
import com.nextcloud.talk.api.models.json.participants.ParticipantsOverall;
+import com.nextcloud.talk.api.models.json.push.PushRegistrationOverall;
import com.nextcloud.talk.api.models.json.rooms.RoomOverall;
import com.nextcloud.talk.api.models.json.rooms.RoomsOverall;
import com.nextcloud.talk.api.models.json.sharees.ShareesOverall;
@@ -33,6 +34,8 @@ import java.util.Map;
import io.reactivex.Observable;
import retrofit2.http.DELETE;
+import retrofit2.http.FieldMap;
+import retrofit2.http.FormUrlEncoded;
import retrofit2.http.GET;
import retrofit2.http.Header;
import retrofit2.http.POST;
@@ -176,4 +179,29 @@ public interface NcApi {
*/
@GET
Observable getServerStatus(@Url String url);
+
+
+ /*
+ QueryMap items are as follows:
+ - "format" : "json"
+ - "pushTokenHash" : ""
+ - "devicePublicKey" : ""
+ - "proxyServer" : ""
+
+ Server URL is: baseUrl + ocsApiVersion + "/apps/notifications/api/v2/push
+ */
+
+ @POST
+ Observable registerDeviceForNotificationsWithNextcloud(@Header("Authorization")
+ String authorization,
+ @Url String url,
+ @QueryMap Map options);
+
+ @FormUrlEncoded
+ @POST
+ Observable registerDeviceForNotificationsWithProxy(@Header("Authorization") String authorization,
+ @Url String url,
+ @FieldMap Map fields);
+
}
diff --git a/app/src/main/java/com/nextcloud/talk/api/helpers/api/ApiHelper.java b/app/src/main/java/com/nextcloud/talk/api/helpers/api/ApiHelper.java
index 7637ce4ec..eb746f17e 100644
--- a/app/src/main/java/com/nextcloud/talk/api/helpers/api/ApiHelper.java
+++ b/app/src/main/java/com/nextcloud/talk/api/helpers/api/ApiHelper.java
@@ -22,7 +22,9 @@ package com.nextcloud.talk.api.helpers.api;
import android.net.Uri;
import com.nextcloud.talk.BuildConfig;
+import com.nextcloud.talk.R;
import com.nextcloud.talk.api.models.RetrofitBucket;
+import com.nextcloud.talk.application.NextcloudTalkApplication;
import java.util.HashMap;
import java.util.Map;
@@ -138,4 +140,13 @@ public class ApiHelper {
public static String getCredentials(String username, String token) {
return Credentials.basic(username, token);
}
+
+ public static String getUrlNextcloudPush(String baseUrl) {
+ return baseUrl + ocsApiVersion + "/apps/notifications/api/v2/push";
+ }
+
+ public static String getUrlPushProxy(String baseUrl) {
+ return NextcloudTalkApplication.getSharedApplication().
+ getApplicationContext().getResources().getString(R.string.nc_push_server_url) + "/devices";
+ }
}
diff --git a/app/src/main/java/com/nextcloud/talk/api/models/PushConfigurationState.java b/app/src/main/java/com/nextcloud/talk/api/models/PushConfigurationState.java
new file mode 100644
index 000000000..034dcbf16
--- /dev/null
+++ b/app/src/main/java/com/nextcloud/talk/api/models/PushConfigurationState.java
@@ -0,0 +1,36 @@
+/*
+ * Nextcloud Talk application
+ *
+ * @author Mario Danic
+ * Copyright (C) 2017 Mario Danic
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package com.nextcloud.talk.api.models;
+
+import org.parceler.Parcel;
+
+import lombok.Data;
+
+@Parcel
+@Data
+public class PushConfigurationState {
+ String pushToken;
+ String deviceIdentifier;
+ String deviceIdentifierSignature;
+ String userPublicKey;
+ boolean shouldBeDeleted;
+ boolean usesRegularPass;
+}
diff --git a/app/src/main/java/com/nextcloud/talk/api/models/json/push/PushRegistration.java b/app/src/main/java/com/nextcloud/talk/api/models/json/push/PushRegistration.java
new file mode 100644
index 000000000..2e8547a86
--- /dev/null
+++ b/app/src/main/java/com/nextcloud/talk/api/models/json/push/PushRegistration.java
@@ -0,0 +1,44 @@
+/*
+ * Nextcloud Talk application
+ *
+ * @author Mario Danic
+ * Copyright (C) 2017 Mario Danic
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package com.nextcloud.talk.api.models.json.push;
+
+
+import com.bluelinelabs.logansquare.annotation.JsonField;
+import com.bluelinelabs.logansquare.annotation.JsonObject;
+
+import org.parceler.Parcel;
+
+import lombok.Data;
+
+@Data
+@Parcel
+@JsonObject
+public class PushRegistration {
+ @JsonField(name = "publicKey")
+ String publicKey;
+
+ @JsonField(name = "deviceIdentifier")
+ String deviceIdentifier;
+
+ @JsonField(name = "signature")
+ String signature;
+}
+
diff --git a/app/src/main/java/com/nextcloud/talk/api/models/json/push/PushRegistrationOCS.java b/app/src/main/java/com/nextcloud/talk/api/models/json/push/PushRegistrationOCS.java
new file mode 100644
index 000000000..3387493d7
--- /dev/null
+++ b/app/src/main/java/com/nextcloud/talk/api/models/json/push/PushRegistrationOCS.java
@@ -0,0 +1,37 @@
+/*
+ * Nextcloud Talk application
+ *
+ * @author Mario Danic
+ * Copyright (C) 2017 Mario Danic
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package com.nextcloud.talk.api.models.json.push;
+
+import com.bluelinelabs.logansquare.annotation.JsonField;
+import com.bluelinelabs.logansquare.annotation.JsonObject;
+import com.nextcloud.talk.api.models.json.generic.GenericOCS;
+
+import org.parceler.Parcel;
+
+import lombok.Data;
+
+@Data
+@Parcel
+@JsonObject
+public class PushRegistrationOCS extends GenericOCS {
+ @JsonField(name = "data")
+ PushRegistration data;
+}
diff --git a/app/src/main/java/com/nextcloud/talk/api/models/json/push/PushRegistrationOverall.java b/app/src/main/java/com/nextcloud/talk/api/models/json/push/PushRegistrationOverall.java
new file mode 100644
index 000000000..7ebc4f1a0
--- /dev/null
+++ b/app/src/main/java/com/nextcloud/talk/api/models/json/push/PushRegistrationOverall.java
@@ -0,0 +1,36 @@
+/*
+ * Nextcloud Talk application
+ *
+ * @author Mario Danic
+ * Copyright (C) 2017 Mario Danic
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package com.nextcloud.talk.api.models.json.push;
+
+import com.bluelinelabs.logansquare.annotation.JsonField;
+import com.bluelinelabs.logansquare.annotation.JsonObject;
+
+import org.parceler.Parcel;
+
+import lombok.Data;
+
+@Data
+@Parcel
+@JsonObject
+public class PushRegistrationOverall {
+ @JsonField(name = "ocs")
+ PushRegistrationOCS ocs;
+}
diff --git a/app/src/main/java/com/nextcloud/talk/application/NextcloudTalkApplication.java b/app/src/main/java/com/nextcloud/talk/application/NextcloudTalkApplication.java
index b6b702879..8a0333ebe 100644
--- a/app/src/main/java/com/nextcloud/talk/application/NextcloudTalkApplication.java
+++ b/app/src/main/java/com/nextcloud/talk/application/NextcloudTalkApplication.java
@@ -24,11 +24,15 @@ import android.content.Context;
import android.support.multidex.MultiDex;
import android.support.multidex.MultiDexApplication;
+import com.evernote.android.job.JobManager;
+import com.evernote.android.job.JobRequest;
import com.nextcloud.talk.BuildConfig;
import com.nextcloud.talk.dagger.modules.BusModule;
import com.nextcloud.talk.dagger.modules.ContextModule;
import com.nextcloud.talk.dagger.modules.DatabaseModule;
import com.nextcloud.talk.dagger.modules.RestModule;
+import com.nextcloud.talk.jobs.PushRegistrationJob;
+import com.nextcloud.talk.jobs.creator.MagicJobCreator;
import com.nextcloud.talk.utils.database.cache.CacheModule;
import com.nextcloud.talk.utils.database.user.UserModule;
import com.squareup.leakcanary.LeakCanary;
@@ -76,6 +80,8 @@ public class NextcloudTalkApplication extends MultiDexApplication {
@Override
public void onCreate() {
super.onCreate();
+ JobManager.create(this).addJobCreator(new MagicJobCreator());
+
sharedApplication = this;
try {
@@ -88,6 +94,9 @@ public class NextcloudTalkApplication extends MultiDexApplication {
componentApplication.inject(this);
refWatcher = LeakCanary.install(this);
+
+ new JobRequest.Builder(PushRegistrationJob.TAG).setUpdateCurrent(true).startNow();
+
}
diff --git a/app/src/main/java/com/nextcloud/talk/controllers/AccountVerificationController.java b/app/src/main/java/com/nextcloud/talk/controllers/AccountVerificationController.java
index d424a0794..db5da55c7 100644
--- a/app/src/main/java/com/nextcloud/talk/controllers/AccountVerificationController.java
+++ b/app/src/main/java/com/nextcloud/talk/controllers/AccountVerificationController.java
@@ -132,7 +132,7 @@ public class AccountVerificationController extends BaseController {
if (!TextUtils.isEmpty(displayName)) {
dbQueryDisposable = userUtils.createOrUpdateUser(username, token,
- baseUrl, displayName)
+ baseUrl, displayName, null)
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(userEntity -> {
diff --git a/app/src/main/java/com/nextcloud/talk/controllers/WebViewLoginController.java b/app/src/main/java/com/nextcloud/talk/controllers/WebViewLoginController.java
index 9694f6163..1337ea910 100644
--- a/app/src/main/java/com/nextcloud/talk/controllers/WebViewLoginController.java
+++ b/app/src/main/java/com/nextcloud/talk/controllers/WebViewLoginController.java
@@ -41,6 +41,7 @@ import com.nextcloud.talk.api.helpers.api.ApiHelper;
import com.nextcloud.talk.application.NextcloudTalkApplication;
import com.nextcloud.talk.controllers.base.BaseController;
import com.nextcloud.talk.models.LoginData;
+import com.nextcloud.talk.persistence.entities.UserEntity;
import com.nextcloud.talk.utils.bundle.BundleBuilder;
import com.nextcloud.talk.utils.bundle.BundleKeys;
import com.nextcloud.talk.utils.database.user.UserUtils;
@@ -134,6 +135,7 @@ public class WebViewLoginController extends BaseController {
webView.setWebViewClient(new WebViewClient() {
private boolean basePageLoaded;
+
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
if (url.startsWith(assembledPrefix)) {
@@ -160,7 +162,7 @@ public class WebViewLoginController extends BaseController {
SslCertificate sslCertificate = error.getCertificate();
Field f = sslCertificate.getClass().getDeclaredField("mX509Certificate");
f.setAccessible(true);
- X509Certificate cert = (X509Certificate)f.get(sslCertificate);
+ X509Certificate cert = (X509Certificate) f.get(sslCertificate);
if (cert == null) {
handler.cancel();
@@ -201,25 +203,34 @@ public class WebViewLoginController extends BaseController {
if (loginData != null) {
dispose();
+ UserEntity currentUser = userUtils.getCurrentUser();
+
+ String displayName = null;
+ String pushConfiguration = null;
+
+ if (currentUser != null) {
+ displayName = currentUser.getDisplayName();
+ pushConfiguration = currentUser.getPushConfigurationState();
+ }
// We use the URL user entered because one provided by the server is NOT reliable
userQueryDisposable = userUtils.createOrUpdateUser(loginData.getUsername(), loginData.getToken(),
- baseUrl, null).subscribe(userEntity -> {
- if (!isPasswordUpdate) {
-
- BundleBuilder bundleBuilder = new BundleBuilder(new Bundle());
- bundleBuilder.putString(BundleKeys.KEY_USERNAME, userEntity.getUsername());
- bundleBuilder.putString(BundleKeys.KEY_TOKEN, userEntity.getToken());
- bundleBuilder.putString(BundleKeys.KEY_BASE_URL, userEntity.getBaseUrl());
- getRouter().pushController(RouterTransaction.with(new AccountVerificationController
- (bundleBuilder.build())).pushChangeHandler(new HorizontalChangeHandler())
- .popChangeHandler(new HorizontalChangeHandler()));
- } else {
- getRouter().setRoot(RouterTransaction.with(new BottomNavigationController(R.menu.menu_navigation))
- .pushChangeHandler(new HorizontalChangeHandler())
- .popChangeHandler(new HorizontalChangeHandler()));
- }
- }, throwable -> dispose(),
- this::dispose);
+ baseUrl, displayName, pushConfiguration).
+ subscribe(userEntity -> {
+ if (!isPasswordUpdate) {
+ BundleBuilder bundleBuilder = new BundleBuilder(new Bundle());
+ bundleBuilder.putString(BundleKeys.KEY_USERNAME, userEntity.getUsername());
+ bundleBuilder.putString(BundleKeys.KEY_TOKEN, userEntity.getToken());
+ bundleBuilder.putString(BundleKeys.KEY_BASE_URL, userEntity.getBaseUrl());
+ getRouter().pushController(RouterTransaction.with(new AccountVerificationController
+ (bundleBuilder.build())).pushChangeHandler(new HorizontalChangeHandler())
+ .popChangeHandler(new HorizontalChangeHandler()));
+ } else {
+ getRouter().setRoot(RouterTransaction.with(new BottomNavigationController(R.menu.menu_navigation))
+ .pushChangeHandler(new HorizontalChangeHandler())
+ .popChangeHandler(new HorizontalChangeHandler()));
+ }
+ }, throwable -> dispose(),
+ this::dispose);
}
}
diff --git a/app/src/main/java/com/nextcloud/talk/jobs/PushRegistrationJob.java b/app/src/main/java/com/nextcloud/talk/jobs/PushRegistrationJob.java
new file mode 100644
index 000000000..d989226bb
--- /dev/null
+++ b/app/src/main/java/com/nextcloud/talk/jobs/PushRegistrationJob.java
@@ -0,0 +1,41 @@
+/*
+ * Nextcloud Talk application
+ *
+ * @author Mario Danic
+ * Copyright (C) 2017 Mario Danic
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package com.nextcloud.talk.jobs;
+
+import android.support.annotation.NonNull;
+
+import com.evernote.android.job.Job;
+import com.nextcloud.talk.utils.PushUtils;
+
+public class PushRegistrationJob extends Job {
+ public static final String TAG = "PushRegistrationJob";
+
+ @NonNull
+ @Override
+ protected Result onRunJob(Params params) {
+ PushUtils pushUtils = new PushUtils();
+
+ pushUtils.generateRsa2048KeyPair();
+ pushUtils.pushRegistrationToServer();
+
+ return Result.SUCCESS;
+ }
+}
diff --git a/app/src/main/java/com/nextcloud/talk/jobs/creator/MagicJobCreator.java b/app/src/main/java/com/nextcloud/talk/jobs/creator/MagicJobCreator.java
new file mode 100644
index 000000000..f2bb82c75
--- /dev/null
+++ b/app/src/main/java/com/nextcloud/talk/jobs/creator/MagicJobCreator.java
@@ -0,0 +1,45 @@
+/*
+ * Nextcloud Talk application
+ *
+ * @author Mario Danic
+ * Copyright (C) 2017 Mario Danic
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package com.nextcloud.talk.jobs.creator;
+
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+
+import com.evernote.android.job.Job;
+import com.evernote.android.job.JobCreator;
+import com.nextcloud.talk.jobs.PushRegistrationJob;
+
+public class MagicJobCreator implements JobCreator {
+ private static final String TAG = "MagicJobCreator";
+
+ @Nullable
+ @Override
+ public Job create(@NonNull String tag) {
+ switch (tag) {
+ case PushRegistrationJob.TAG:
+ break;
+ default:
+ return null;
+ }
+
+ return null;
+ }
+}
diff --git a/app/src/main/java/com/nextcloud/talk/persistence/entities/User.java b/app/src/main/java/com/nextcloud/talk/persistence/entities/User.java
index 6a3967d1f..4e39e6c16 100644
--- a/app/src/main/java/com/nextcloud/talk/persistence/entities/User.java
+++ b/app/src/main/java/com/nextcloud/talk/persistence/entities/User.java
@@ -42,4 +42,6 @@ public interface User extends Parcelable, Persistable, Serializable {
String getToken();
String getDisplayName();
+
+ String getPushConfigurationState();
}
diff --git a/app/src/main/java/com/nextcloud/talk/services/firebase/MagicFirebaseInstanceIDService.java b/app/src/main/java/com/nextcloud/talk/services/firebase/MagicFirebaseInstanceIDService.java
new file mode 100644
index 000000000..6298f5868
--- /dev/null
+++ b/app/src/main/java/com/nextcloud/talk/services/firebase/MagicFirebaseInstanceIDService.java
@@ -0,0 +1,52 @@
+/*
+ * Nextcloud Talk application
+ *
+ * @author Mario Danic
+ * Copyright (C) 2017 Mario Danic
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package com.nextcloud.talk.services.firebase;
+
+import com.evernote.android.job.JobRequest;
+import com.google.firebase.iid.FirebaseInstanceId;
+import com.google.firebase.iid.FirebaseInstanceIdService;
+import com.nextcloud.talk.application.NextcloudTalkApplication;
+import com.nextcloud.talk.jobs.PushRegistrationJob;
+import com.nextcloud.talk.utils.preferences.AppPreferences;
+
+import javax.inject.Inject;
+
+import autodagger.AutoInjector;
+
+@AutoInjector(NextcloudTalkApplication.class)
+public class MagicFirebaseInstanceIDService extends FirebaseInstanceIdService {
+ private static final String TAG = "MagicFirebaseInstanceIDService";
+
+ @Inject
+ AppPreferences appPreferences;
+
+ public MagicFirebaseInstanceIDService() {
+ super();
+ NextcloudTalkApplication.getSharedApplication().getComponentApplication().inject(this);
+ }
+
+ @Override
+ public void onTokenRefresh() {
+ appPreferences.setPushtoken(FirebaseInstanceId.getInstance().getToken());
+
+ new JobRequest.Builder(PushRegistrationJob.TAG).setUpdateCurrent(true).startNow();
+ }
+}
diff --git a/app/src/main/java/com/nextcloud/talk/services/firebase/MagicFirebaseMessagingService.java b/app/src/main/java/com/nextcloud/talk/services/firebase/MagicFirebaseMessagingService.java
new file mode 100644
index 000000000..1b32c4b7a
--- /dev/null
+++ b/app/src/main/java/com/nextcloud/talk/services/firebase/MagicFirebaseMessagingService.java
@@ -0,0 +1,34 @@
+/*
+ * Nextcloud Talk application
+ *
+ * @author Mario Danic
+ * Copyright (C) 2017 Mario Danic
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package com.nextcloud.talk.services.firebase;
+
+import com.google.firebase.messaging.FirebaseMessagingService;
+import com.google.firebase.messaging.RemoteMessage;
+
+public class MagicFirebaseMessagingService extends FirebaseMessagingService {
+ private static final String TAG = "MagicFirebaseMessagingService";
+
+ @Override
+ public void onMessageReceived(RemoteMessage remoteMessage) {
+ super.onMessageReceived(remoteMessage);
+ }
+
+}
diff --git a/app/src/main/java/com/nextcloud/talk/utils/PushUtils.java b/app/src/main/java/com/nextcloud/talk/utils/PushUtils.java
new file mode 100644
index 000000000..e67d6fb10
--- /dev/null
+++ b/app/src/main/java/com/nextcloud/talk/utils/PushUtils.java
@@ -0,0 +1,317 @@
+/*
+ * Nextcloud Talk application
+ *
+ * @author Mario Danic
+ * Copyright (C) 2017 Mario Danic
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package com.nextcloud.talk.utils;
+
+import android.content.Context;
+import android.text.TextUtils;
+import android.util.Base64;
+import android.util.Log;
+
+import com.bluelinelabs.logansquare.LoganSquare;
+import com.nextcloud.talk.R;
+import com.nextcloud.talk.api.NcApi;
+import com.nextcloud.talk.api.helpers.api.ApiHelper;
+import com.nextcloud.talk.api.models.PushConfigurationState;
+import com.nextcloud.talk.application.NextcloudTalkApplication;
+import com.nextcloud.talk.persistence.entities.UserEntity;
+import com.nextcloud.talk.utils.database.user.UserUtils;
+import com.nextcloud.talk.utils.preferences.AppPreferences;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.security.Key;
+import java.security.KeyFactory;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.security.PublicKey;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.PKCS8EncodedKeySpec;
+import java.security.spec.X509EncodedKeySpec;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.inject.Inject;
+
+import autodagger.AutoInjector;
+import io.reactivex.functions.Consumer;
+import io.reactivex.schedulers.Schedulers;
+
+@AutoInjector(NextcloudTalkApplication.class)
+public class PushUtils {
+ private static final String TAG = "PushUtils";
+
+ @Inject
+ UserUtils userUtils;
+
+ @Inject
+ AppPreferences appPreferences;
+
+ @Inject
+ NcApi ncApi;
+
+ private File keysFile;
+ private File publicKeyFile;
+ private File privateKeyFile;
+
+ private String proxyServer;
+
+ public PushUtils() {
+ NextcloudTalkApplication.getSharedApplication().getComponentApplication().inject(this);
+
+ keysFile = NextcloudTalkApplication.getSharedApplication().getDir("PushKeyStore", Context.MODE_PRIVATE);
+ publicKeyFile = new File(NextcloudTalkApplication.getSharedApplication().getDir("PushKeystore",
+ Context.MODE_PRIVATE), "push_key.pub");
+ privateKeyFile = new File(NextcloudTalkApplication.getSharedApplication().getDir("PushKeystore",
+ Context.MODE_PRIVATE), "push_key.priv");
+ proxyServer = NextcloudTalkApplication.getSharedApplication().getResources().
+ getString(R.string.nc_push_server_url);
+
+ }
+
+ private static int saveKeyToFile(Key key, String path) {
+ byte[] encoded = key.getEncoded();
+ FileOutputStream keyFileOutputStream = null;
+ try {
+ if (!new File(path).exists()) {
+ new File(path).createNewFile();
+ }
+ keyFileOutputStream = new FileOutputStream(path);
+ keyFileOutputStream.write(encoded);
+ keyFileOutputStream.close();
+ return 0;
+ } catch (FileNotFoundException e) {
+ Log.d(TAG, "Failed to save key to file");
+ } catch (IOException e) {
+ Log.d(TAG, "Failed to save key to file via IOException");
+ }
+
+ return -1;
+ }
+
+ public String generateSHA512Hash(String pushToken) {
+ MessageDigest messageDigest = null;
+ try {
+ messageDigest = MessageDigest.getInstance("SHA-512");
+ messageDigest.update(pushToken.getBytes());
+ return bytesToHex(messageDigest.digest());
+ } catch (NoSuchAlgorithmException e) {
+ Log.d(TAG, "SHA-512 algorithm not supported");
+ }
+ return "";
+ }
+
+ public String bytesToHex(byte[] bytes) {
+ StringBuilder result = new StringBuilder();
+ for (byte individualByte : bytes) {
+ result.append(Integer.toString((individualByte & 0xff) + 0x100, 16)
+ .substring(1));
+ }
+ return result.toString();
+ }
+
+ private void deleteRegistrationForAccount(UserEntity userEntity) {
+ }
+
+ public int generateRsa2048KeyPair() {
+ if (!publicKeyFile.exists() && !privateKeyFile.exists()) {
+ if (!keysFile.exists()) {
+ keysFile.mkdirs();
+ }
+
+ KeyPairGenerator keyGen = null;
+ try {
+ keyGen = KeyPairGenerator.getInstance("RSA");
+ keyGen.initialize(2048);
+
+ KeyPair pair = keyGen.generateKeyPair();
+ int statusPrivate = saveKeyToFile(pair.getPrivate(), privateKeyFile.getAbsolutePath());
+ int statusPublic = saveKeyToFile(pair.getPublic(), publicKeyFile.getAbsolutePath());
+
+ if (statusPrivate == 0 && statusPublic == 0) {
+ // all went well
+ return 0;
+ } else {
+ return -2;
+ }
+
+ } catch (NoSuchAlgorithmException e) {
+ Log.d(TAG, "RSA algorithm not supported");
+ }
+ } else {
+ // We already have the key
+ return -1;
+ }
+
+ // we failed to generate the key
+ return -2;
+ }
+
+ public void pushRegistrationToServer() {
+ String token = appPreferences.getPushToken();
+
+ if (!TextUtils.isEmpty(token)) {
+ String pushTokenHash = generateSHA512Hash(token).toLowerCase();
+ PublicKey devicePublicKey = (PublicKey) readKeyFromFile(true);
+ if (devicePublicKey != null) {
+ byte[] publicKeyBytes = Base64.encode(devicePublicKey.getEncoded(), Base64.NO_WRAP);
+ String publicKey = new String(publicKeyBytes);
+ publicKey = publicKey.replaceAll("(.{64})", "$1\n");
+
+ publicKey = "-----BEGIN PUBLIC KEY-----\n" + publicKey + "\n-----END PUBLIC KEY-----\n";
+
+ if (userUtils.anyUserExists()) {
+ String providerValue;
+ PushConfigurationState accountPushData = null;
+ for (UserEntity userEntity : userUtils.getUsers()) {
+ providerValue = userEntity.getPushConfigurationState();
+ if (!TextUtils.isEmpty(providerValue)) {
+ try {
+ accountPushData = LoganSquare.parse(providerValue, PushConfigurationState.class);
+ } catch (IOException e) {
+ Log.d(TAG, "Failed to parse account push data");
+ accountPushData = null;
+ }
+ } else {
+ accountPushData = null;
+ }
+
+ if (accountPushData != null && !accountPushData.getPushToken().equals(token) &&
+ !accountPushData.isShouldBeDeleted() ||
+ TextUtils.isEmpty(providerValue)) {
+
+
+ Map queryMap = new HashMap<>();
+ queryMap.put("format", "json");
+ queryMap.put("pushTokenHash", pushTokenHash);
+ queryMap.put("devicePublicKey", publicKey);
+ queryMap.put("proxyServer", proxyServer);
+
+ ncApi.registerDeviceForNotificationsWithNextcloud(
+ ApiHelper.getCredentials(userEntity.getUsername(), userEntity.getToken()),
+ ApiHelper.getUrlNextcloudPush(userEntity.getBaseUrl()), queryMap)
+ .subscribeOn(Schedulers.newThread())
+ .subscribe(pushRegistrationOverall -> {
+ Map proxyMap = new HashMap<>();
+ proxyMap.put("pushToken", token);
+ proxyMap.put("deviceIdentifier", pushRegistrationOverall.getOcs().getData().
+ getDeviceIdentifier());
+ proxyMap.put("deviceIdentifierSignature", pushRegistrationOverall.getOcs()
+ .getData().getSignature());
+ proxyMap.put("userPublicKey", pushRegistrationOverall.getOcs()
+ .getData().getPublicKey());
+
+
+ ncApi.registerDeviceForNotificationsWithProxy(ApiHelper.getCredentials
+ (userEntity.getUsername(), userEntity.getToken()),
+ ApiHelper.getUrlPushProxy(userEntity.getBaseUrl()), proxyMap)
+ .subscribeOn(Schedulers.newThread())
+ .subscribe(new Consumer() {
+ @Override
+ public void accept(Void aVoid) throws Exception {
+
+ PushConfigurationState pushConfigurationState =
+ new PushConfigurationState();
+ pushConfigurationState.setPushToken(token);
+ pushConfigurationState.setDeviceIdentifier(
+ pushRegistrationOverall.getOcs()
+ .getData().getDeviceIdentifier());
+ pushConfigurationState.setDeviceIdentifierSignature(
+ pushRegistrationOverall
+ .getOcs().getData().getSignature());
+ pushConfigurationState.setUserPublicKey(
+ pushRegistrationOverall.getOcs()
+ .getData().getPublicKey());
+ pushConfigurationState.setShouldBeDeleted(false);
+ pushConfigurationState.setUsesRegularPass(false);
+
+ userUtils.createOrUpdateUser(userEntity.getUsername(),
+ userEntity.getToken(), userEntity.getBaseUrl(),
+ userEntity.getDisplayName(),
+ LoganSquare.serialize(pushConfigurationState));
+
+ }
+ }, new Consumer() {
+ @Override
+ public void accept(Throwable throwable) throws Exception {
+ // something went wrong
+ }
+ });
+
+
+ }, new Consumer() {
+ @Override
+ public void accept(Throwable throwable) throws Exception {
+ // TODO: If 400, we're using regular token
+ }
+ });
+
+ }
+ }
+ }
+ }
+ }
+ }
+
+ private Key readKeyFromFile(boolean readPublicKey) {
+ String path;
+
+ if (readPublicKey) {
+ path = publicKeyFile.getAbsolutePath();
+ } else {
+ path = privateKeyFile.getAbsolutePath();
+ }
+
+ FileInputStream fileInputStream = null;
+ try {
+ fileInputStream = new FileInputStream(path);
+ byte[] bytes = new byte[fileInputStream.available()];
+ fileInputStream.read(bytes);
+ fileInputStream.close();
+
+ KeyFactory keyFactory = KeyFactory.getInstance("RSA");
+
+ if (readPublicKey) {
+ X509EncodedKeySpec keySpec = new X509EncodedKeySpec(bytes);
+ return keyFactory.generatePublic(keySpec);
+ } else {
+ PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(bytes);
+ return keyFactory.generatePrivate(keySpec);
+ }
+
+ } catch (FileNotFoundException e) {
+ Log.d(TAG, "Failed to find path while reading the Key");
+ } catch (IOException e) {
+ Log.d(TAG, "IOException while reading the key");
+ } catch (InvalidKeySpecException e) {
+ Log.d(TAG, "InvalidKeySpecException while reading the key");
+ } catch (NoSuchAlgorithmException e) {
+ Log.d(TAG, "RSA algorithm not supported");
+ }
+
+ return null;
+ }
+
+}
diff --git a/app/src/main/java/com/nextcloud/talk/utils/database/user/UserUtils.java b/app/src/main/java/com/nextcloud/talk/utils/database/user/UserUtils.java
index a485c798a..bbdc8fefc 100644
--- a/app/src/main/java/com/nextcloud/talk/utils/database/user/UserUtils.java
+++ b/app/src/main/java/com/nextcloud/talk/utils/database/user/UserUtils.java
@@ -22,10 +22,15 @@ package com.nextcloud.talk.utils.database.user;
import android.support.annotation.Nullable;
import android.text.TextUtils;
+import android.util.Log;
+import com.bluelinelabs.logansquare.LoganSquare;
import com.nextcloud.talk.persistence.entities.User;
import com.nextcloud.talk.persistence.entities.UserEntity;
+import java.io.IOException;
+import java.util.List;
+
import io.reactivex.Completable;
import io.reactivex.Observable;
import io.reactivex.android.schedulers.AndroidSchedulers;
@@ -35,6 +40,7 @@ import io.requery.query.Result;
import io.requery.reactivex.ReactiveEntityStore;
public class UserUtils {
+ private static final String TAG = "UserUtils";
private ReactiveEntityStore dataStore;
UserUtils(ReactiveEntityStore dataStore) {
@@ -46,6 +52,12 @@ public class UserUtils {
return (dataStore.count(User.class).limit(1).get().value() > 0);
}
+ public List getUsers() {
+ Result findUsersQueryResult = dataStore.select(User.class).get();
+
+ return findUsersQueryResult.toList();
+ }
+
// temporary method while we only support 1 user
public UserEntity getCurrentUser() {
Result findUserQueryResult = dataStore.select(User.class).limit(1).get();
@@ -66,7 +78,8 @@ public class UserUtils {
}
public Observable createOrUpdateUser(String username, String token, String serverUrl,
- @Nullable String displayName) {
+ @Nullable String displayName,
+ @Nullable String pushConfigurationState) {
Result findUserQueryResult = dataStore.select(User.class).where(UserEntity.USERNAME.eq(username).
and(UserEntity.BASE_URL.eq(serverUrl.toLowerCase()))).limit(1).get();
@@ -82,6 +95,14 @@ public class UserUtils {
user.setDisplayName(displayName);
}
+ if (pushConfigurationState != null) {
+ try {
+ user.setPushConfigurationState(LoganSquare.serialize(pushConfigurationState));
+ } catch (IOException e) {
+ Log.d(TAG, "Failed to serialize push configuration state");
+ }
+ }
+
} else {
if (!token.equals(user.getToken())) {
user.setToken(token);
@@ -90,6 +111,14 @@ public class UserUtils {
if (!TextUtils.isEmpty(displayName) && !displayName.equals(user.getDisplayName())) {
user.setDisplayName(displayName);
}
+
+ if (pushConfigurationState != null && !pushConfigurationState.equals(user.getPushConfigurationState())) {
+ try {
+ user.setPushConfigurationState(LoganSquare.serialize(pushConfigurationState));
+ } catch (IOException e) {
+ Log.d(TAG, "Failed to serialize push configuration state");
+ }
+ }
}
return dataStore.upsert(user)
diff --git a/app/src/main/java/com/nextcloud/talk/utils/preferences/AppPreferences.java b/app/src/main/java/com/nextcloud/talk/utils/preferences/AppPreferences.java
index 2d8476a80..114adc9b7 100644
--- a/app/src/main/java/com/nextcloud/talk/utils/preferences/AppPreferences.java
+++ b/app/src/main/java/com/nextcloud/talk/utils/preferences/AppPreferences.java
@@ -45,6 +45,15 @@ public interface AppPreferences {
@RemoveMethod
void removeProxyServer();
+ @KeyByString("push_token")
+ String getPushToken();
+
+ @KeyByString("push_token")
+ void setPushtoken(String pushToken);
+
+ @KeyByString("push_token")
+ void removePushToken();
+
@ClearMethod
void clear();
}
diff --git a/app/src/main/java/com/nextcloud/talk/utils/ssl/SSLSocketFactoryCompat.kt b/app/src/main/java/com/nextcloud/talk/utils/ssl/SSLSocketFactoryCompat.kt
index 282bba40d..4e91374ae 100644
--- a/app/src/main/java/com/nextcloud/talk/utils/ssl/SSLSocketFactoryCompat.kt
+++ b/app/src/main/java/com/nextcloud/talk/utils/ssl/SSLSocketFactoryCompat.kt
@@ -19,7 +19,7 @@ import javax.net.ssl.SSLSocket
import javax.net.ssl.SSLSocketFactory
import javax.net.ssl.X509TrustManager
-class SSLSocketFactoryCompat(trustManager: X509TrustManager): SSLSocketFactory() {
+class SSLSocketFactoryCompat(trustManager: X509TrustManager) : SSLSocketFactory() {
private var delegate: SSLSocketFactory
@@ -29,6 +29,7 @@ class SSLSocketFactoryCompat(trustManager: X509TrustManager): SSLSocketFactory()
// https://developer.android.com/reference/javax/net/ssl/SSLSocket.html
var protocols: Array? = null
var cipherSuites: Array? = null
+
init {
if (Build.VERSION.SDK_INT >= 23) {
// Since Android 6.0 (API level 23),
@@ -87,7 +88,7 @@ class SSLSocketFactoryCompat(trustManager: X509TrustManager): SSLSocketFactory()
cipherSuites = _cipherSuites.toTypedArray()
}
- } catch(e: IOException) {
+ } catch (e: IOException) {
} finally {
socket?.close() // doesn't implement Closeable on all supported Android versions
}
diff --git a/app/src/main/res/layout/controller_account_verification.xml b/app/src/main/res/layout/controller_account_verification.xml
index cde4fc930..850ff4949 100644
--- a/app/src/main/res/layout/controller_account_verification.xml
+++ b/app/src/main/res/layout/controller_account_verification.xml
@@ -23,7 +23,7 @@
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
- android:background="@color/background_color">
+ android:background="@color/nc_background_color">
+ android:background="@color/nc_background_color">
+ android:background="@color/nc_background_color">
Nextcloud Talk
Nextcloud
- @color/per70white
+ @color/per70white
+
+ https://push-notifications.nextcloud.com
+
\ No newline at end of file