diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index 7c5764b7af6b..9f355452f2b6 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -338,6 +338,10 @@ tasks.named("check").configure {
}
dependencies {
+ // unified push
+ implementation(libs.unifiedpush.connector)
+ implementation(libs.unifiedpush.connector.ui)
+
// region Nextcloud library
implementation(libs.android.library) {
exclude(group = "org.ogce", module = "xpp3") // unused in Android and brings wrong Junit version
diff --git a/app/src/generic/AndroidManifest.xml b/app/src/generic/AndroidManifest.xml
new file mode 100644
index 000000000000..a3b2570c2780
--- /dev/null
+++ b/app/src/generic/AndroidManifest.xml
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/generic/java/com/nextcloud/unifiedpush/UnifiedPush.kt b/app/src/generic/java/com/nextcloud/unifiedpush/UnifiedPush.kt
new file mode 100644
index 000000000000..77f4b65de85e
--- /dev/null
+++ b/app/src/generic/java/com/nextcloud/unifiedpush/UnifiedPush.kt
@@ -0,0 +1,132 @@
+/*
+ * Nextcloud - Android Client
+ *
+ * SPDX-FileCopyrightText: 2024 Your Name
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+package com.nextcloud.unifiedpush
+
+import android.content.Context
+import android.os.PowerManager
+import androidx.work.WorkManager
+import com.google.gson.Gson
+import com.nextcloud.client.core.ClockImpl
+import com.nextcloud.client.jobs.BackgroundJobManagerImpl
+import com.nextcloud.client.jobs.NotificationWork
+import com.nextcloud.client.preferences.AppPreferencesImpl
+import com.owncloud.android.datamodel.ArbitraryDataProviderImpl
+import com.owncloud.android.datamodel.PushConfigurationState
+import com.owncloud.android.lib.common.utils.Log_OC
+import com.owncloud.android.ui.activity.DrawerActivity
+import com.owncloud.android.utils.PushUtils
+import org.unifiedpush.android.connector.FailedReason
+import org.unifiedpush.android.connector.PushService
+import org.unifiedpush.android.connector.UnifiedPush
+import org.unifiedpush.android.connector.data.PushEndpoint
+import org.unifiedpush.android.connector.data.PushMessage
+import org.unifiedpush.android.connector.ui.SelectDistributorDialogsBuilder
+import org.unifiedpush.android.connector.ui.UnifiedPushFunctions
+
+class UnifiedPush : PushService() {
+ companion object {
+ private val TAG: String? = UnifiedPush::class.java.simpleName
+ private const val WAKELOCK_TIMEOUT = 5000L
+
+ fun registerForPushMessaging(activity: DrawerActivity?, accountName: String, forceChoose: Boolean) {
+ if ((activity === null) || (activity.mHandler === null) || activity.isFinishing)
+ return
+
+ // run on ui thread
+ activity.mHandler.post {
+ SelectDistributorDialogsBuilder(
+ activity,
+ object : UnifiedPushFunctions {
+ override fun tryUseDefaultDistributor(callback: (Boolean) -> Unit) =
+ UnifiedPush.tryUseDefaultDistributor(activity, callback).also {
+ Log_OC.d(TAG, "tryUseDefaultDistributor()")
+ }
+
+ override fun getAckDistributor(): String? =
+ UnifiedPush.getAckDistributor(activity).also {
+ Log_OC.d(TAG, "getAckDistributor() = $it")
+ }
+
+ override fun getDistributors(): List =
+ UnifiedPush.getDistributors(activity).also {
+ Log_OC.d(TAG, "getDistributors() = $it")
+ }
+
+ override fun register(instance: String) =
+ UnifiedPush.register(activity, instance).also {
+ Log_OC.d(TAG, "register($instance)")
+ }
+
+ override fun saveDistributor(distributor: String) =
+ UnifiedPush.saveDistributor(activity, distributor).also {
+ Log_OC.d(TAG, "saveDistributor($distributor)")
+ }
+ }
+ ).apply {
+ instances = listOf(accountName)
+ mayUseCurrent = !forceChoose
+ mayUseDefault = !forceChoose
+ }.run()
+ }
+ }
+
+ fun unregisterForPushMessaging(context: Context, accountName: String) {
+ // unregister with distributor
+ UnifiedPush.unregister(context, accountName)
+
+ // delete locally saved endpoint value
+ ArbitraryDataProviderImpl(context).deleteKeyForAccount(accountName, PushUtils.KEY_PUSH)
+ }
+ }
+
+ override fun onMessage(message: PushMessage, instance: String) {
+ // get a wake lock to 'help' background job run more promptly since it can take minutes to run if phone is
+ // sleeping/dozing - 5 secs should be well long enough to get the notification displayed
+ val wakeLock = (getSystemService(POWER_SERVICE) as PowerManager)
+ .newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "nc:timedPartialwakelock")
+ wakeLock?.acquire(WAKELOCK_TIMEOUT)
+
+ // called when a new message is received. The message contains the full POST body of the push message
+ Log_OC.d(TAG, "unified push message received")
+
+ BackgroundJobManagerImpl(
+ WorkManager.getInstance(this), ClockImpl(), AppPreferencesImpl.fromContext(this)
+ ).startNotificationJob(
+ message.content.toString(Charsets.UTF_8),
+ instance,
+ NotificationWork.BACKEND_TYPE_UNIFIED_PUSH
+ )
+ }
+
+ override fun onNewEndpoint(endpoint: PushEndpoint, instance: String) {
+ Log_OC.d(TAG, "onNewEndpoint(${endpoint.url}, $instance)")
+
+ val newAccountPushData = PushConfigurationState()
+ newAccountPushData.setPushToken(endpoint.url)
+ ArbitraryDataProviderImpl(this).storeOrUpdateKeyValue(
+ instance,
+ PushUtils.KEY_PUSH,
+ Gson().toJson(newAccountPushData)
+ )
+ }
+
+ override fun onRegistrationFailed(reason: FailedReason, instance: String) =
+ // the registration is not possible, eg. no network
+ // force unregister to make sure cleaned up. re-register will be re-attempted next time
+ UnifiedPush.unregister(this, instance).also {
+ Log_OC.d(TAG, "onRegistrationFailed(${reason.name}, $instance)")
+ }
+
+ override fun onUnregistered(instance: String) =
+ // this application is unregistered by the distributor from receiving push messages
+ // force unregister to make sure cleaned up. re-register will be re-attempted next time
+ UnifiedPush.unregister(this, instance).also {
+ Log_OC.d(TAG, "onUnregistered($instance)")
+ }
+
+}
diff --git a/app/src/generic/java/com/owncloud/android/utils/PushUtils.java b/app/src/generic/java/com/owncloud/android/utils/PushUtils.java
index 139377f210d9..2a8f322066f6 100644
--- a/app/src/generic/java/com/owncloud/android/utils/PushUtils.java
+++ b/app/src/generic/java/com/owncloud/android/utils/PushUtils.java
@@ -9,10 +9,18 @@
import android.content.Context;
+import android.accounts.Account;
+import android.text.TextUtils;
+
+import com.google.gson.Gson;
import com.nextcloud.client.account.UserAccountManager;
import com.nextcloud.client.preferences.AppPreferencesImpl;
import com.owncloud.android.MainApp;
+import com.owncloud.android.datamodel.ArbitraryDataProviderImpl;
+import com.owncloud.android.datamodel.PushConfigurationState;
import com.owncloud.android.datamodel.SignatureVerification;
+import com.nextcloud.unifiedpush.UnifiedPush;
+import com.owncloud.android.ui.activity.DrawerActivity;
import java.security.Key;
@@ -22,13 +30,31 @@ public final class PushUtils {
private PushUtils() {
}
- public static void pushRegistrationToServer(final UserAccountManager accountManager, final String pushToken) {
- // do nothing
+ public static void updateRegistrationsWithServer(
+ final DrawerActivity activity,
+ final UserAccountManager accountManager,
+ final String pushToken) {
+ for (Account account : accountManager.getAccounts()) {
+ String providerValue = new ArbitraryDataProviderImpl(MainApp.getAppContext()).getValue(account.name, KEY_PUSH);
+ PushConfigurationState accountPushData = new Gson().fromJson(providerValue, PushConfigurationState.class);
+
+ if ((accountPushData != null) && accountPushData.isShouldBeDeleted()) {
+ // unregister push notifications
+ UnifiedPush.Companion.unregisterForPushMessaging(MainApp.getAppContext(), account.name);
+ } else {
+ // else, (re-)register for push notifications
+ if (activity != null) {
+ UnifiedPush.Companion.registerForPushMessaging(
+ activity,
+ account.name,
+ ((accountPushData == null) || TextUtils.isEmpty(accountPushData.getPushToken())));
+ }
+ }
+ }
}
public static void reinitKeys(UserAccountManager accountManager) {
- Context context = MainApp.getAppContext();
- AppPreferencesImpl.fromContext(context).setKeysReInitEnabled();
+ AppPreferencesImpl.fromContext(MainApp.getAppContext()).setKeysReInitEnabled();
}
public static Key readKeyFromFile(boolean readPublicKey) {
@@ -39,8 +65,7 @@ public static SignatureVerification verifySignature(
final Context context,
final UserAccountManager accountManager,
final byte[] signatureBytes,
- final byte[] subjectBytes
- ) {
+ final byte[] subjectBytes) {
return null;
}
diff --git a/app/src/gplay/java/com/owncloud/android/services/firebase/NCFirebaseMessagingService.java b/app/src/gplay/java/com/owncloud/android/services/firebase/NCFirebaseMessagingService.java
index ce6beea9b7e9..fee1b4bace90 100644
--- a/app/src/gplay/java/com/owncloud/android/services/firebase/NCFirebaseMessagingService.java
+++ b/app/src/gplay/java/com/owncloud/android/services/firebase/NCFirebaseMessagingService.java
@@ -86,7 +86,9 @@ public void onMessageReceived(@NonNull RemoteMessage remoteMessage) {
final String subject = data.get(NotificationWork.KEY_NOTIFICATION_SUBJECT);
final String signature = data.get(NotificationWork.KEY_NOTIFICATION_SIGNATURE);
if (subject != null && signature != null) {
- backgroundJobManager.startNotificationJob(subject, signature);
+ backgroundJobManager.startNotificationJob(subject,
+ signature,
+ NotificationWork.BACKEND_TYPE_FIREBASE_CLOUD_MESSAGING);
}
}
@@ -97,7 +99,7 @@ public void onNewToken(@NonNull String newToken) {
if (!TextUtils.isEmpty(getResources().getString(R.string.push_server_url))) {
preferences.setPushToken(newToken);
- PushUtils.pushRegistrationToServer(accountManager, preferences.getPushToken());
+ PushUtils.updateRegistrationsWithServer(null, accountManager, preferences.getPushToken());
}
}
}
diff --git a/app/src/gplay/java/com/owncloud/android/utils/PushUtils.java b/app/src/gplay/java/com/owncloud/android/utils/PushUtils.java
index 4e77b401556e..9edcf9bbe19b 100644
--- a/app/src/gplay/java/com/owncloud/android/utils/PushUtils.java
+++ b/app/src/gplay/java/com/owncloud/android/utils/PushUtils.java
@@ -35,6 +35,7 @@
import com.owncloud.android.lib.resources.notifications.UnregisterAccountDeviceForNotificationsOperation;
import com.owncloud.android.lib.resources.notifications.UnregisterAccountDeviceForProxyOperation;
import com.owncloud.android.lib.resources.notifications.models.PushResponse;
+import com.owncloud.android.ui.activity.DrawerActivity;
import org.apache.commons.httpclient.HttpStatus;
import org.apache.commons.io.FileUtils;
@@ -169,7 +170,10 @@ private static void deleteRegistrationForAccount(Account account) {
}
}
- public static void pushRegistrationToServer(final UserAccountManager accountManager, final String token) {
+ public static void updateRegistrationsWithServer(
+ final Context unusedContext,
+ final UserAccountManager accountManager,
+ final String token) {
arbitraryDataProvider = new ArbitraryDataProviderImpl(MainApp.getAppContext());
if (!TextUtils.isEmpty(MainApp.getAppContext().getResources().getString(R.string.push_server_url)) &&
@@ -352,7 +356,7 @@ public static void reinitKeys(final UserAccountManager accountManager) {
AppPreferences preferences = AppPreferencesImpl.fromContext(context);
String pushToken = preferences.getPushToken();
- pushRegistrationToServer(accountManager, pushToken);
+ updateRegistrationsWithServer(null, accountManager, pushToken);
preferences.setKeysReInitEnabled();
}
diff --git a/app/src/huawei/java/com/owncloud/android/utils/PushUtils.java b/app/src/huawei/java/com/owncloud/android/utils/PushUtils.java
index bf1949a33a72..c2a59ca09384 100644
--- a/app/src/huawei/java/com/owncloud/android/utils/PushUtils.java
+++ b/app/src/huawei/java/com/owncloud/android/utils/PushUtils.java
@@ -13,6 +13,7 @@
import com.nextcloud.client.preferences.AppPreferencesImpl;
import com.owncloud.android.MainApp;
import com.owncloud.android.datamodel.SignatureVerification;
+import com.owncloud.android.ui.activity.DrawerActivity;
import java.security.Key;
@@ -22,7 +23,10 @@ public final class PushUtils {
private PushUtils() {
}
- public static void pushRegistrationToServer(final UserAccountManager accountManager, final String pushToken) {
+ public static void updateRegistrationsWithServer(
+ final Context context,
+ final UserAccountManager accountManager,
+ final String pushToken) {
// do nothing
}
diff --git a/app/src/main/java/com/nextcloud/client/jobs/AccountRemovalWork.kt b/app/src/main/java/com/nextcloud/client/jobs/AccountRemovalWork.kt
index ebcb5f7dd4d3..bc8a1c36a0a7 100644
--- a/app/src/main/java/com/nextcloud/client/jobs/AccountRemovalWork.kt
+++ b/app/src/main/java/com/nextcloud/client/jobs/AccountRemovalWork.kt
@@ -166,7 +166,10 @@ class AccountRemovalWork(
PushUtils.KEY_PUSH,
gson.toJson(pushArbitraryData)
)
- PushUtils.pushRegistrationToServer(userAccountManager, pushArbitraryData.getPushToken())
+ PushUtils.updateRegistrationsWithServer(
+ null, // can pass null for context because this is guaranteed to be an 'unregister' operation
+ userAccountManager,
+ pushArbitraryData.getPushToken())
}
}
diff --git a/app/src/main/java/com/nextcloud/client/jobs/BackgroundJobManager.kt b/app/src/main/java/com/nextcloud/client/jobs/BackgroundJobManager.kt
index 219de803ecda..5d70528380e8 100644
--- a/app/src/main/java/com/nextcloud/client/jobs/BackgroundJobManager.kt
+++ b/app/src/main/java/com/nextcloud/client/jobs/BackgroundJobManager.kt
@@ -135,7 +135,7 @@ interface BackgroundJobManager {
fun scheduleMediaFoldersDetectionJob()
fun startMediaFoldersDetectionJob()
- fun startNotificationJob(subject: String, signature: String)
+ fun startNotificationJob(subject: String, signature: String, backendType: Int)
fun startAccountRemovalJob(accountName: String, remoteWipe: Boolean)
fun startFilesUploadJob(user: User, uploadIds: LongArray, showSameFileAlreadyExistsNotification: Boolean)
fun getFileUploads(user: User): LiveData>
diff --git a/app/src/main/java/com/nextcloud/client/jobs/BackgroundJobManagerImpl.kt b/app/src/main/java/com/nextcloud/client/jobs/BackgroundJobManagerImpl.kt
index d476a92b16cb..0bbe22c98fff 100644
--- a/app/src/main/java/com/nextcloud/client/jobs/BackgroundJobManagerImpl.kt
+++ b/app/src/main/java/com/nextcloud/client/jobs/BackgroundJobManagerImpl.kt
@@ -611,10 +611,11 @@ internal class BackgroundJobManagerImpl(
)
}
- override fun startNotificationJob(subject: String, signature: String) {
+ override fun startNotificationJob(subject: String, signature: String, backendType: Int) {
val data = Data.Builder()
.putString(NotificationWork.KEY_NOTIFICATION_SUBJECT, subject)
.putString(NotificationWork.KEY_NOTIFICATION_SIGNATURE, signature)
+ .putInt(NotificationWork.KEY_NOTIFICATION_TYPE, backendType)
.build()
val request = oneTimeRequestBuilder(NotificationWork::class, JOB_NOTIFICATION)
diff --git a/app/src/main/java/com/nextcloud/client/jobs/NotificationWork.kt b/app/src/main/java/com/nextcloud/client/jobs/NotificationWork.kt
index f95e110ff491..a7eb669bd5b2 100644
--- a/app/src/main/java/com/nextcloud/client/jobs/NotificationWork.kt
+++ b/app/src/main/java/com/nextcloud/client/jobs/NotificationWork.kt
@@ -73,53 +73,75 @@ class NotificationWork constructor(
const val KEY_NOTIFICATION_ACCOUNT = "KEY_NOTIFICATION_ACCOUNT"
const val KEY_NOTIFICATION_SUBJECT = "subject"
const val KEY_NOTIFICATION_SIGNATURE = "signature"
+ const val KEY_NOTIFICATION_TYPE = "type"
private const val KEY_NOTIFICATION_ACTION_LINK = "KEY_NOTIFICATION_ACTION_LINK"
private const val KEY_NOTIFICATION_ACTION_TYPE = "KEY_NOTIFICATION_ACTION_TYPE"
private const val PUSH_NOTIFICATION_ID = "PUSH_NOTIFICATION_ID"
private const val NUMERIC_NOTIFICATION_ID = "NUMERIC_NOTIFICATION_ID"
+ const val BACKEND_TYPE_FIREBASE_CLOUD_MESSAGING = 1
+ const val BACKEND_TYPE_UNIFIED_PUSH = 2
}
@Suppress("TooGenericExceptionCaught", "NestedBlockDepth", "ComplexMethod", "LongMethod") // legacy code
override fun doWork(): Result {
- val subject = inputData.getString(KEY_NOTIFICATION_SUBJECT) ?: ""
- val signature = inputData.getString(KEY_NOTIFICATION_SIGNATURE) ?: ""
- if (!TextUtils.isEmpty(subject) && !TextUtils.isEmpty(signature)) {
- try {
- val base64DecodedSubject = Base64.decode(subject, Base64.DEFAULT)
- val base64DecodedSignature = Base64.decode(signature, Base64.DEFAULT)
- val privateKey = PushUtils.readKeyFromFile(false) as PrivateKey
- try {
- val signatureVerification = PushUtils.verifySignature(
- context,
- accountManager,
- base64DecodedSignature,
- base64DecodedSubject
- )
- if (signatureVerification != null && signatureVerification.signatureValid) {
- val cipher = Cipher.getInstance("RSA/None/PKCS1Padding")
- cipher.init(Cipher.DECRYPT_MODE, privateKey)
- val decryptedSubject = cipher.doFinal(base64DecodedSubject)
- val gson = Gson()
- val decryptedPushMessage = gson.fromJson(
- String(decryptedSubject),
- DecryptedPushMessage::class.java
- )
- if (decryptedPushMessage.delete) {
- notificationManager.cancel(decryptedPushMessage.nid)
- } else if (decryptedPushMessage.deleteAll) {
- notificationManager.cancelAll()
- } else {
- val user = accountManager.getUser(signatureVerification.account?.name)
- .orElseThrow { RuntimeException() }
- fetchCompleteNotification(user, decryptedPushMessage)
+ try {
+ val messageData = inputData.getString(KEY_NOTIFICATION_SUBJECT) ?: ""
+ val signatureOrUser = inputData.getString(KEY_NOTIFICATION_SIGNATURE) ?: ""
+
+ if (!TextUtils.isEmpty(messageData) && !TextUtils.isEmpty(signatureOrUser)) {
+ val decryptedMessageData: StringBuilder = StringBuilder()
+ val accountName: StringBuilder = StringBuilder()
+
+ val type = inputData.getInt(KEY_NOTIFICATION_TYPE, -1)
+
+ // if using firebase cloud messaging (via nextcloud proxy) for push notifications...
+ when (type) {
+ BACKEND_TYPE_FIREBASE_CLOUD_MESSAGING -> {
+ val base64DecodedSubject = Base64.decode(messageData, Base64.DEFAULT)
+ val base64DecodedSignature = Base64.decode(signatureOrUser, Base64.DEFAULT)
+ val privateKey = PushUtils.readKeyFromFile(false) as PrivateKey
+ try {
+ val signatureVerification = PushUtils.verifySignature(
+ context,
+ accountManager,
+ base64DecodedSignature,
+ base64DecodedSubject
+ )
+ if (signatureVerification != null && signatureVerification.signatureValid) {
+ accountName.append(signatureVerification.account?.name)
+ val cipher = Cipher.getInstance("RSA/None/PKCS1Padding")
+ cipher.init(Cipher.DECRYPT_MODE, privateKey)
+ decryptedMessageData.append(String(cipher.doFinal(base64DecodedSubject)))
+ }
+ } catch (e1: GeneralSecurityException) {
+ Log_OC.d(TAG, "Error decrypting message ${e1.javaClass.name} ${e1.localizedMessage}")
+ return Result.success()
}
+ // else, if using unified push messaging...
+ }
+ BACKEND_TYPE_UNIFIED_PUSH -> {
+ decryptedMessageData.append(messageData)
+ accountName.append(signatureOrUser)
}
- } catch (e1: GeneralSecurityException) {
- Log_OC.d(TAG, "Error decrypting message ${e1.javaClass.name} ${e1.localizedMessage}")
+ else -> return Result.success()
+ }
+
+ // transform string message to object
+ val decryptedPushMessage =
+ Gson().fromJson(decryptedMessageData.toString(), DecryptedPushMessage::class.java)
+
+ if (decryptedPushMessage.delete) {
+ notificationManager.cancel(decryptedPushMessage.nid)
+ } else if (decryptedPushMessage.deleteAll) {
+ notificationManager.cancelAll()
+ } else {
+ val user = accountManager.getUser(accountName)
+ .orElseThrow { RuntimeException() }
+ fetchCompleteNotification(user, decryptedPushMessage)
}
- } catch (exception: Exception) {
- Log_OC.d(TAG, "Something went very wrong" + exception.localizedMessage)
}
+ } catch (exception: Exception) {
+ Log_OC.d(TAG, "Something went very wrong" + exception.localizedMessage)
}
return Result.success()
}
diff --git a/app/src/main/java/com/owncloud/android/ui/activity/DrawerActivity.java b/app/src/main/java/com/owncloud/android/ui/activity/DrawerActivity.java
index 2b0d1c0d2c2e..fe822dee84ac 100644
--- a/app/src/main/java/com/owncloud/android/ui/activity/DrawerActivity.java
+++ b/app/src/main/java/com/owncloud/android/ui/activity/DrawerActivity.java
@@ -208,6 +208,8 @@ public abstract class DrawerActivity extends ToolbarActivity
@Inject
ClientFactory clientFactory;
+ public Handler mHandler;
+
@Override
public void onCreate(@Nullable Bundle savedInstanceState, @Nullable PersistableBundle persistentState) {
super.onCreate(savedInstanceState, persistentState);
diff --git a/app/src/main/java/com/owncloud/android/ui/activity/FileActivity.java b/app/src/main/java/com/owncloud/android/ui/activity/FileActivity.java
index 1e1727283d18..cf721041d04c 100644
--- a/app/src/main/java/com/owncloud/android/ui/activity/FileActivity.java
+++ b/app/src/main/java/com/owncloud/android/ui/activity/FileActivity.java
@@ -159,9 +159,6 @@ public abstract class FileActivity extends DrawerActivity
/** Flag to signal if the activity is launched by a notification */
private boolean mFromNotification;
- /** Messages handler associated to the main thread and the life cycle of the activity */
- private Handler mHandler;
-
private FileOperationsHelper mFileOperationsHelper;
private ServiceConnection mOperationsServiceConnection;
diff --git a/app/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.kt b/app/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.kt
index 82dc1eaa77f4..d064ad18829b 100644
--- a/app/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.kt
+++ b/app/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.kt
@@ -2772,7 +2772,7 @@ class FileDisplayActivity :
if (!preferences.isKeysReInitEnabled()) {
PushUtils.reinitKeys(userAccountManager)
} else {
- PushUtils.pushRegistrationToServer(userAccountManager, preferences.getPushToken())
+ PushUtils.updateRegistrationsWithServer(this, userAccountManager, preferences.getPushToken());
}
}
diff --git a/app/src/main/java/com/owncloud/android/ui/activity/NotificationsActivity.kt b/app/src/main/java/com/owncloud/android/ui/activity/NotificationsActivity.kt
index dc24fb140963..1697e1cf32bd 100644
--- a/app/src/main/java/com/owncloud/android/ui/activity/NotificationsActivity.kt
+++ b/app/src/main/java/com/owncloud/android/ui/activity/NotificationsActivity.kt
@@ -181,18 +181,22 @@ class NotificationsActivity :
}
} else {
val pushUrl = resources.getString(R.string.push_server_url)
-
- if (pushUrl.isEmpty() && isFlavourGPlay()) {
- // branded client without push server
- return
+ val arbitraryDataProvider: ArbitraryDataProvider = ArbitraryDataProviderImpl(this)
+ val accountName: String = if (optionalUser?.isPresent == true) {
+ optionalUser?.get()?.accountName ?: ""
+ } else {
+ ""
}
+ // if using unified push...
if (pushUrl.isEmpty()) {
- snackbar = Snackbar.make(
- binding.emptyList.emptyListView,
- R.string.push_notifications_not_implemented,
- Snackbar.LENGTH_INDEFINITE
- )
+ // if a unified push distributor is not set, show snackbar alerting user to unified push capability
+ if (arbitraryDataProvider.getValue(accountName, PushUtils.KEY_PUSH).isEmpty())
+ snackbar = Snackbar.make(
+ binding.emptyList.emptyListView,
+ R.string.push_notifications_no_distributor,
+ Snackbar.LENGTH_INDEFINITE
+ )
} else {
val arbitraryDataProvider: ArbitraryDataProvider = ArbitraryDataProviderImpl(this)
val accountName: String = if (optionalUser?.isPresent == true) {
diff --git a/app/src/main/java/com/owncloud/android/ui/activity/SettingsActivity.java b/app/src/main/java/com/owncloud/android/ui/activity/SettingsActivity.java
index 96c482fae9d8..473c89262712 100644
--- a/app/src/main/java/com/owncloud/android/ui/activity/SettingsActivity.java
+++ b/app/src/main/java/com/owncloud/android/ui/activity/SettingsActivity.java
@@ -15,6 +15,7 @@
*/
package com.owncloud.android.ui.activity;
+import android.accounts.Account;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.Intent;
@@ -62,6 +63,7 @@
import com.owncloud.android.datamodel.ArbitraryDataProvider;
import com.owncloud.android.datamodel.ArbitraryDataProviderImpl;
import com.owncloud.android.datamodel.ExternalLinksProvider;
+import com.owncloud.android.datamodel.PushConfigurationState;
import com.owncloud.android.lib.common.ExternalLink;
import com.owncloud.android.lib.common.ExternalLinkType;
import com.owncloud.android.lib.common.utils.Log_OC;
@@ -93,6 +95,9 @@
import androidx.core.content.ContextCompat;
import androidx.core.content.res.ResourcesCompat;
+import static com.owncloud.android.utils.PushUtils.KEY_PUSH;
+import com.google.gson.Gson;
+
/**
* An Activity that allows the user to change the application's settings.
* It proxies the necessary calls via {@link androidx.appcompat.app.AppCompatDelegate} to be used with AppCompat.
@@ -384,6 +389,8 @@ private void setupMoreCategory() {
setupBackupPreference();
+ setupResetPushPreference();
+
setupE2EPreference(preferenceCategoryMore);
setupE2EKeysExist(preferenceCategoryMore);
@@ -637,6 +644,27 @@ private void setupBackupPreference() {
}
}
+ private void setupResetPushPreference() {
+ Preference pResetPushNotifications = findPreference("reset_push_notifications");
+ if (pResetPushNotifications == null)
+ return;
+
+ pResetPushNotifications.setOnPreferenceClickListener(preference -> {
+ Gson gson = new Gson();
+ for (Account account : accountManager.getAccounts()) {
+ String providerValue = arbitraryDataProvider.getValue(account.name, KEY_PUSH);
+ if (!TextUtils.isEmpty(providerValue)) {
+ PushConfigurationState accountPushData = gson.fromJson(providerValue, PushConfigurationState.class);
+ accountPushData.shouldBeDeleted = true; // set this so push account will be reset
+ arbitraryDataProvider.storeOrUpdateKeyValue(account.name, KEY_PUSH, gson.toJson(accountPushData));
+ }
+ }
+
+ DisplayUtils.showSnackMessage(this, R.string.prefs_reset_push_done);
+ return true;
+ });
+ }
+
private void setupCalendarPreference(PreferenceCategory preferenceCategoryMore) {
boolean calendarContactsEnabled = getResources().getBoolean(R.bool.davdroid_integration_enabled);
Preference pCalendarContacts = findPreference("calendar_contacts");
diff --git a/app/src/main/java/com/owncloud/android/ui/activity/UserInfoActivity.java b/app/src/main/java/com/owncloud/android/ui/activity/UserInfoActivity.java
index 11adefe3fe57..6936cddfdccd 100644
--- a/app/src/main/java/com/owncloud/android/ui/activity/UserInfoActivity.java
+++ b/app/src/main/java/com/owncloud/android/ui/activity/UserInfoActivity.java
@@ -16,6 +16,7 @@
import android.graphics.drawable.Drawable;
import android.graphics.drawable.LayerDrawable;
import android.os.Bundle;
+import android.os.Handler;
import android.text.TextUtils;
import android.view.LayoutInflater;
import android.view.Menu;
@@ -89,6 +90,8 @@ public class UserInfoActivity extends DrawerActivity implements Injectable {
public void onCreate(Bundle savedInstanceState) {
Log_OC.v(TAG, "onCreate() start");
super.onCreate(savedInstanceState);
+ mHandler = new Handler();
+
Bundle bundle = getIntent().getExtras();
if (bundle == null) {
@@ -359,7 +362,7 @@ protected void onSaveInstanceState(@NonNull Bundle outState) {
@Subscribe(threadMode = ThreadMode.BACKGROUND)
public void onMessageEvent(TokenPushEvent event) {
- PushUtils.pushRegistrationToServer(getUserAccountManager(), preferences.getPushToken());
+ PushUtils.updateRegistrationsWithServer(this, getUserAccountManager(), preferences.getPushToken());
}
diff --git a/app/src/main/res/values-eo/strings.xml b/app/src/main/res/values-eo/strings.xml
index de8cac3e8f0f..6b730667f9ef 100644
--- a/app/src/main/res/values-eo/strings.xml
+++ b/app/src/main/res/values-eo/strings.xml
@@ -415,7 +415,6 @@
Ne eblas montri bildon
Pardonu
Privateco
- Tujaj sciigoj estas malebligitaj pro dependeco al proprietaj „Google Play“-servoj.
Neniu tuja sciigo pro malaktuala ensaluto-seanco. Bonvolu eble re-aldoni vian konton.
Tujaj sciigoj momente ne disponeblas.
Provu %1$s sur via aparato!
diff --git a/app/src/main/res/values-es-rCL/strings.xml b/app/src/main/res/values-es-rCL/strings.xml
index 15249d53fee4..565538872c37 100644
--- a/app/src/main/res/values-es-rCL/strings.xml
+++ b/app/src/main/res/values-es-rCL/strings.xml
@@ -403,7 +403,6 @@
No es posible mostrar la imagen
Disculpa
Privacidad
- Las notificaciones push están deshabilitadas debido a dependencias de servicios propietarios de Google Play.
No hay notificaciones push debido a un inicio de sesión caduco. Por favor vuelve a ingresar a tu cuenta.
En este momento las notificaciones push no están disponibles.
¡Prueba %1$s en tu dispositivo!
diff --git a/app/src/main/res/values-es-rCO/strings.xml b/app/src/main/res/values-es-rCO/strings.xml
index 9b7501114900..4fa2e20886ef 100644
--- a/app/src/main/res/values-es-rCO/strings.xml
+++ b/app/src/main/res/values-es-rCO/strings.xml
@@ -549,7 +549,6 @@
No es posible mostrar la imagen
Disculpa
Privacidad
- Las notificaciones push están deshabilitadas debido a dependencias de servicios propietarios de Google Play.
No hay notificaciones push debido a un inicio de sesión caduco. Por favor vuelve a ingresar a tu cuenta.
En este momento las notificaciones push no están disponibles.
¡Prueba %1$s en tu dispositivo!
diff --git a/app/src/main/res/values-iw/strings.xml b/app/src/main/res/values-iw/strings.xml
index e547b1be7ec8..7a86082d642e 100644
--- a/app/src/main/res/values-iw/strings.xml
+++ b/app/src/main/res/values-iw/strings.xml
@@ -500,7 +500,6 @@
לא ניתן להציג תמונה
אנו מתנצלים
פרטיות
- התראות בדחיפה מושבתות עקב תלות בשירותי Google Play קנייניים.
אין התראות בדחיפה עקב כניסה מיושנת. מוטב לשקול להוסיף את החשבון שלך מחדש.
כרגע התרעות בדחיפה אינן זמינות.
לנסות את %1$s בהתקן שלך!
diff --git a/app/src/main/res/values-sq/strings.xml b/app/src/main/res/values-sq/strings.xml
index 8a7bb4b9b32f..d62b30f032dc 100644
--- a/app/src/main/res/values-sq/strings.xml
+++ b/app/src/main/res/values-sq/strings.xml
@@ -448,7 +448,6 @@
Kjo figurë nuk mund të shfaqet
Më vjen keq
Privatësi
- Njoftimet push janë të çaktivizuara për shkak të varësive nga shërbimet e patentuara të Google Play.
Nuk ka asnjë njoftim për shkak se sesioni i identifikimit nuk ishte i përditësuar. Ju lutem konsideroni të shtoni përsëri llogarinë tuaj.
Njoftimet push nuk ofrohen për momentin.
Provojeni %1$s në pajisjen tuaj!
diff --git a/app/src/main/res/values-sr-rSP/strings.xml b/app/src/main/res/values-sr-rSP/strings.xml
index f751cb9fb46f..0a9dec076db6 100644
--- a/app/src/main/res/values-sr-rSP/strings.xml
+++ b/app/src/main/res/values-sr-rSP/strings.xml
@@ -437,7 +437,6 @@
Ne mogu da prikažem sliku
Izvinite
Privatnost
- Brza obaveštenja isključena jer zavise od vlasničkog Google Play servisa.
Nema brzih obaveštenja zato što vam je sesija zastarela. Izbrišite i dodajte ponovo nalog.
Brza obaveštenja trenutno nisu dostupna.
Isprobaj %1$s na svom uređaju!
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 1ee682c51846..e5a21b67ff44 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -841,7 +841,7 @@
Get source code
License
GNU General Public License, version 2
- Push notifications disabled due to dependencies on proprietary Google Play services.
+ Push notifications are available using Unified Push. See https://apps.nextcloud.com/apps/uppush.
No push notifications due to outdated login session. Please consider re-adding your account.
Push notifications currently not available.
@@ -1463,4 +1463,7 @@
Sort folders before files
Sort favorites first
Files
+ Reset push notifications
+ Reset push notifications in case of push messaging issues or changing distributors
+ Push notifications reset
diff --git a/app/src/main/res/xml/preferences.xml b/app/src/main/res/xml/preferences.xml
index 8ab1f37e7090..c0cb75266bfe 100644
--- a/app/src/main/res/xml/preferences.xml
+++ b/app/src/main/res/xml/preferences.xml
@@ -92,6 +92,10 @@
android:title="@string/backup_title"
android:key="backup"
android:summary="@string/prefs_daily_backup_summary" />
+