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" /> +