├── app ├── .gitignore ├── src │ ├── main │ │ ├── res │ │ │ ├── values │ │ │ │ ├── dimens.xml │ │ │ │ ├── ic_launcher_background.xml │ │ │ │ ├── colors.xml │ │ │ │ ├── themes.xml │ │ │ │ └── strings.xml │ │ │ ├── mipmap-hdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-mdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xhdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xxhdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xxxhdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-anydpi-v26 │ │ │ │ ├── ic_launcher.xml │ │ │ │ └── ic_launcher_round.xml │ │ │ ├── xml │ │ │ │ └── network_security_config.xml │ │ │ ├── drawable │ │ │ │ ├── ic_delete_24.xml │ │ │ │ ├── ic_content_copy_24.xml │ │ │ │ ├── ic_logo.xml │ │ │ │ └── ic_launcher_foreground.xml │ │ │ ├── menu │ │ │ │ ├── menu_delete.xml │ │ │ │ └── menu_main.xml │ │ │ ├── values-night │ │ │ │ └── themes.xml │ │ │ └── layout │ │ │ │ ├── item_app.xml │ │ │ │ ├── activity_main.xml │ │ │ │ ├── content_main.xml │ │ │ │ └── activity_start.xml │ │ ├── ic_launcher-playstore.png │ │ ├── java │ │ │ └── org │ │ │ │ └── unifiedpush │ │ │ │ └── distributor │ │ │ │ └── nextpush │ │ │ │ ├── api │ │ │ │ ├── response │ │ │ │ │ ├── ApiResponse.kt │ │ │ │ │ └── SSEResponse.kt │ │ │ │ ├── provider │ │ │ │ │ ├── ApiProviderFactory.kt │ │ │ │ │ ├── ApiProvider.kt │ │ │ │ │ ├── ApiDirectFactory.kt │ │ │ │ │ └── ApiSSOFactory.kt │ │ │ │ ├── SSEListener.kt │ │ │ │ └── Api.kt │ │ │ │ ├── utils │ │ │ │ ├── Tag.kt │ │ │ │ ├── DebugInformation.kt │ │ │ │ ├── PackageUtils.kt │ │ │ │ └── Notifications.kt │ │ │ │ ├── AppCompanion.kt │ │ │ │ ├── account │ │ │ │ ├── AccountFactory.kt │ │ │ │ ├── Account.kt │ │ │ │ ├── SSOAccountFactory.kt │ │ │ │ └── DirectAccountFactory.kt │ │ │ │ ├── receivers │ │ │ │ ├── StartReceiver.kt │ │ │ │ └── RegisterBroadcastReceiver.kt │ │ │ │ ├── distributor │ │ │ │ ├── UnifiedPushConstants.kt │ │ │ │ └── Distributor.kt │ │ │ │ ├── LocalNotification.kt │ │ │ │ ├── activities │ │ │ │ ├── PermissionsRequest.kt │ │ │ │ ├── AppListAdapter.kt │ │ │ │ ├── StartActivity.kt │ │ │ │ └── MainActivity.kt │ │ │ │ ├── services │ │ │ │ ├── RestartWorker.kt │ │ │ │ ├── RestartNetworkCallback.kt │ │ │ │ ├── StartService.kt │ │ │ │ └── FailureHandler.kt │ │ │ │ └── Database.kt │ │ └── AndroidManifest.xml │ └── debug │ │ └── res │ │ └── xml │ │ └── network_security_config.xml ├── proguard-rules.pro └── build.gradle ├── fastlane └── metadata │ └── android │ └── en-US │ ├── title.txt │ ├── changelogs │ ├── 1.txt │ ├── 10.txt │ ├── 3.txt │ ├── 14.txt │ ├── 22.txt │ ├── 6.txt │ ├── 13.txt │ ├── 28.txt │ ├── 11.txt │ ├── 12.txt │ ├── 17.txt │ ├── 23.txt │ ├── 24.txt │ ├── 15.txt │ ├── 2.txt │ ├── 21.txt │ ├── 26.txt │ ├── 8.txt │ ├── 7.txt │ ├── 18.txt │ ├── 20.txt │ ├── 4.txt │ ├── 16.txt │ ├── 5.txt │ ├── 19.txt │ ├── 25.txt │ ├── 9.txt │ └── 27.txt │ ├── short_description.txt │ ├── images │ ├── icon.png │ └── phoneScreenshots │ │ ├── 1.png │ │ ├── 2.png │ │ └── 3.png │ └── full_description.txt ├── settings.gradle ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── .github ├── dependabot.yml └── workflows │ └── main.yml ├── .gitignore ├── .woodpecker └── main.yml ├── gradle.properties ├── README.md ├── gradlew.bat ├── gradlew └── LICENSE /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/title.txt: -------------------------------------------------------------------------------- 1 | NextPush 2 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | rootProject.name = "NextPush" -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/changelogs/1.txt: -------------------------------------------------------------------------------- 1 | Initial version 2 | -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/changelogs/10.txt: -------------------------------------------------------------------------------- 1 | - Fix restarting worker 2 | -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/changelogs/3.txt: -------------------------------------------------------------------------------- 1 | * Fix Re-registration 2 | -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/changelogs/14.txt: -------------------------------------------------------------------------------- 1 | Improve how failures are handled. 2 | -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/changelogs/22.txt: -------------------------------------------------------------------------------- 1 | - Avoid false positive warning 2 | -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/changelogs/6.txt: -------------------------------------------------------------------------------- 1 | * Fix popup overlay night theme 2 | -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/changelogs/13.txt: -------------------------------------------------------------------------------- 1 | Fix Restart worker when ping is missing 2 | -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/changelogs/28.txt: -------------------------------------------------------------------------------- 1 | - Avoid concurrent (un)registration 2 | -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/short_description.txt: -------------------------------------------------------------------------------- 1 | UnifiedPush provider with Nextcloud 2 | -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/changelogs/11.txt: -------------------------------------------------------------------------------- 1 | Use saved keepalive to check last message 2 | -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/changelogs/12.txt: -------------------------------------------------------------------------------- 1 | Fix network callback for restarted service 2 | -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/changelogs/17.txt: -------------------------------------------------------------------------------- 1 | Fix crash : nextcloud sso doesn't work minified 2 | -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/changelogs/23.txt: -------------------------------------------------------------------------------- 1 | - Reduce again false positive buffered responses 2 | -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/changelogs/24.txt: -------------------------------------------------------------------------------- 1 | - Avoid again false positive buffered response 2 | -------------------------------------------------------------------------------- /app/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 16dp 3 | -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/changelogs/15.txt: -------------------------------------------------------------------------------- 1 | Bump dependencies 2 | Target SDK 33 3 | Avoid failures 4 | -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/changelogs/2.txt: -------------------------------------------------------------------------------- 1 | * Fix onFailure of the listener 2 | * Fix restart service function 3 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UP-NextPush/android/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /app/src/main/ic_launcher-playstore.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UP-NextPush/android/HEAD/app/src/main/ic_launcher-playstore.png -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/changelogs/21.txt: -------------------------------------------------------------------------------- 1 | - Improve feedback to the user 2 | - Some optimizations 3 | - Minor corrections 4 | -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/changelogs/26.txt: -------------------------------------------------------------------------------- 1 | - Minify the build 2 | - Update dependencies 3 | - Fix potential UI crashes 4 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UP-NextPush/android/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UP-NextPush/android/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/changelogs/8.txt: -------------------------------------------------------------------------------- 1 | - Improve service restarting 2 | - Optimize registration flow 3 | - Fix manual restart 4 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UP-NextPush/android/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UP-NextPush/android/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/changelogs/7.txt: -------------------------------------------------------------------------------- 1 | - Improve battery management 2 | - Change foreground notif wording 3 | - Follow spec 2.0.0 4 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UP-NextPush/android/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/images/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UP-NextPush/android/HEAD/fastlane/metadata/android/en-US/images/icon.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UP-NextPush/android/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UP-NextPush/android/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UP-NextPush/android/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UP-NextPush/android/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UP-NextPush/android/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/changelogs/18.txt: -------------------------------------------------------------------------------- 1 | - Add themed icon for Android 13 2 | - Set different notification channel names 3 | - Update dependencies 4 | -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/changelogs/20.txt: -------------------------------------------------------------------------------- 1 | - Fix device deletion at logout 2 | - Prevent restart when logout 3 | - Some optimizations 4 | - Bump dependencies -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/changelogs/4.txt: -------------------------------------------------------------------------------- 1 | * Retry every 10min on failure 2 | * Register only one network callback 3 | * Do not re-notify warning 4 | 5 | -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/changelogs/16.txt: -------------------------------------------------------------------------------- 1 | * Reduce build size 2 | * Avoid a null pointer exception 3 | * Restart service when POST_NOTIFICATIONS is granted 4 | -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/changelogs/5.txt: -------------------------------------------------------------------------------- 1 | * First major release 2 | * Open market app if Nextcloud App isn't installed 3 | * Increase wait time with failures 4 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: gradle 4 | directory: "/" 5 | schedule: 6 | interval: daily 7 | open-pull-requests-limit: 10 8 | -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/images/phoneScreenshots/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UP-NextPush/android/HEAD/fastlane/metadata/android/en-US/images/phoneScreenshots/1.png -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/images/phoneScreenshots/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UP-NextPush/android/HEAD/fastlane/metadata/android/en-US/images/phoneScreenshots/2.png -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/images/phoneScreenshots/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UP-NextPush/android/HEAD/fastlane/metadata/android/en-US/images/phoneScreenshots/3.png -------------------------------------------------------------------------------- /app/src/main/res/values/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #0F9AE6 4 | -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/changelogs/19.txt: -------------------------------------------------------------------------------- 1 | - Add possibility to use without Nextcloud app 2 | - New login UI 3 | - Fix restart worker 4 | - Some optimizations and bugfixes 5 | -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/changelogs/25.txt: -------------------------------------------------------------------------------- 1 | - Add support to show notification on push (non-UnifiedPush) 2 | - Improve app list UI 3 | - Add debug ttlFails 4 | - Bump dependencies 5 | -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/changelogs/9.txt: -------------------------------------------------------------------------------- 1 | - Implement last UnifiedPush Android specifications (AND_2.0.0) 2 | - Use single periodic worker to restart 3 | - Increase wakelock timeout 4 | -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/changelogs/27.txt: -------------------------------------------------------------------------------- 1 | - Inform user when a new app is registered 2 | - Avoid false positive warning 3 | - Avoid useless restart 4 | - Internal optimizations 5 | - Bump dependencies 6 | -------------------------------------------------------------------------------- /app/src/main/java/org/unifiedpush/distributor/nextpush/api/response/ApiResponse.kt: -------------------------------------------------------------------------------- 1 | package org.unifiedpush.distributor.nextpush.api.response 2 | 3 | data class ApiResponse( 4 | val success: Boolean = false, 5 | val deviceId: String = "", 6 | val token: String = "" 7 | ) 8 | -------------------------------------------------------------------------------- /app/src/main/java/org/unifiedpush/distributor/nextpush/utils/Tag.kt: -------------------------------------------------------------------------------- 1 | package org.unifiedpush.distributor.nextpush.utils 2 | 3 | val Any.TAG: String 4 | get() { 5 | val tag = javaClass.simpleName 6 | return if (tag.length <= 23) tag else tag.substring(0, 23) 7 | } 8 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Sun Jan 22 18:36:40 CET 2023 2 | distributionBase=GRADLE_USER_HOME 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.0-bin.zip 4 | distributionPath=wrapper/dists 5 | zipStorePath=wrapper/dists 6 | zipStoreBase=GRADLE_USER_HOME 7 | -------------------------------------------------------------------------------- /app/src/main/java/org/unifiedpush/distributor/nextpush/api/response/SSEResponse.kt: -------------------------------------------------------------------------------- 1 | package org.unifiedpush.distributor.nextpush.api.response 2 | 3 | data class SSEResponse( 4 | val type: String = "", 5 | val token: String = "", 6 | val message: String = "", 7 | val keepalive: Int = 900 8 | ) 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/caches 5 | /.idea/libraries 6 | /.idea/modules.xml 7 | /.idea/workspace.xml 8 | /.idea/navEditor.xml 9 | /.idea/assetWizardSettings.xml 10 | .DS_Store 11 | /build 12 | /captures 13 | .externalNativeBuild 14 | .cxx 15 | local.properties 16 | .idea 17 | -------------------------------------------------------------------------------- /app/src/main/java/org/unifiedpush/distributor/nextpush/api/provider/ApiProviderFactory.kt: -------------------------------------------------------------------------------- 1 | package org.unifiedpush.distributor.nextpush.api.provider 2 | 3 | class NoProviderException(message: String) : Exception(message) 4 | interface ApiProviderFactory { 5 | fun getProviderAndExecute(block: (ApiProvider, then: () -> Unit) -> Unit) 6 | } 7 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # keep classes used for Json deserializing 2 | -keep class org.unifiedpush.distributor.nextpush.api.response.** { *; } 3 | # keep classes used for Nextcloud SSO 4 | -keep class org.unifiedpush.distributor.nextpush.api.provider.** { *; } 5 | 6 | # preserve line numbers for crash reporting 7 | -keepattributes SourceFile,LineNumberTable 8 | -renamesourcefileattribute SourceFile -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/xml/network_security_config.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_delete_24.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/debug/res/xml/network_security_config.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/full_description.txt: -------------------------------------------------------------------------------- 1 | This application is a UnifiedPush distributor that works with the Nextcloud application associated (https://github.com/UP-NextPush/server-app). 2 | 3 | The SSO login requires the Nextcloud App to work : https://f-droid.org/packages/com.nextcloud.client/ . 4 | You can also connect directly to Nextcloud. We recommend using an application password. 5 | 6 | More information about UnifiedPush at https://unifiedpush.org . 7 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_content_copy_24.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/menu/menu_delete.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #FFBB86FC 4 | #FF6200EE 5 | #FF3700B3 6 | #FF03DAC5 7 | #FF018786 8 | #FF000000 9 | #FFFFFFFF 10 | #FFEFEFEF 11 | #FF4F4F4F 12 | #0F9AE6 13 | -------------------------------------------------------------------------------- /app/src/main/java/org/unifiedpush/distributor/nextpush/AppCompanion.kt: -------------------------------------------------------------------------------- 1 | package org.unifiedpush.distributor.nextpush 2 | 3 | import java.util.Calendar 4 | import java.util.concurrent.atomic.AtomicBoolean 5 | import java.util.concurrent.atomic.AtomicInteger 6 | 7 | object AppCompanion { 8 | val booting = AtomicBoolean(false) 9 | val hasInternet = AtomicBoolean(true) 10 | val started = AtomicBoolean(false) 11 | val pinged = AtomicBoolean(false) 12 | val bufferedResponseChecked = AtomicBoolean(false) 13 | val keepalive = AtomicInteger(900) 14 | var lastEventDate: Calendar? = null 15 | } 16 | -------------------------------------------------------------------------------- /app/src/main/java/org/unifiedpush/distributor/nextpush/account/AccountFactory.kt: -------------------------------------------------------------------------------- 1 | package org.unifiedpush.distributor.nextpush.account 2 | 3 | import android.app.Activity 4 | import android.content.Context 5 | import android.content.Intent 6 | 7 | interface AccountFactory { 8 | var name: String? 9 | var url: String? 10 | fun initAccount(context: Context): Boolean 11 | fun connect(activity: Activity) 12 | fun onActivityResult(activity: Activity, requestCode: Int, resultCode: Int, data: Intent?, block: (success: Boolean) -> Unit) 13 | fun getAccount(context: Context): Any? 14 | fun logout(context: Context) 15 | } 16 | -------------------------------------------------------------------------------- /app/src/main/java/org/unifiedpush/distributor/nextpush/receivers/StartReceiver.kt: -------------------------------------------------------------------------------- 1 | package org.unifiedpush.distributor.nextpush.receivers 2 | 3 | import android.content.BroadcastReceiver 4 | import android.content.Context 5 | import android.content.Intent 6 | import org.unifiedpush.distributor.nextpush.AppCompanion 7 | import org.unifiedpush.distributor.nextpush.services.RestartWorker 8 | 9 | class StartReceiver : BroadcastReceiver() { 10 | 11 | override fun onReceive(context: Context, intent: Intent) { 12 | AppCompanion.booting.set(true) 13 | if (intent.action == Intent.ACTION_BOOT_COMPLETED) { 14 | RestartWorker.startPeriodic(context) 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /app/src/main/java/org/unifiedpush/distributor/nextpush/utils/DebugInformation.kt: -------------------------------------------------------------------------------- 1 | package org.unifiedpush.distributor.nextpush.utils 2 | 3 | import org.unifiedpush.distributor.nextpush.AppCompanion 4 | import org.unifiedpush.distributor.nextpush.services.FailureHandler 5 | import org.unifiedpush.distributor.nextpush.services.StartService 6 | import java.text.SimpleDateFormat 7 | 8 | fun getDebugInfo(): String { 9 | val date = AppCompanion.lastEventDate?.let { 10 | SimpleDateFormat.getDateTimeInstance().format(it.time) 11 | } ?: "None" 12 | return "ServiceStarted: ${StartService.isServiceStarted}\n" + 13 | "Last Event: $date\n" + 14 | "Keepalive: ${AppCompanion.keepalive.get()}\n" + 15 | "SSE started: ${AppCompanion.started}\n" + 16 | "SSE pinged: ${AppCompanion.pinged}\n" + 17 | FailureHandler.getDebugInfo() 18 | } 19 | -------------------------------------------------------------------------------- /app/src/main/res/menu/menu_main.xml: -------------------------------------------------------------------------------- 1 | 5 | 10 | 15 | 20 | -------------------------------------------------------------------------------- /app/src/main/res/values-night/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 17 | -------------------------------------------------------------------------------- /app/src/main/java/org/unifiedpush/distributor/nextpush/distributor/UnifiedPushConstants.kt: -------------------------------------------------------------------------------- 1 | package org.unifiedpush.distributor.nextpush.distributor 2 | 3 | /** 4 | * Constants as defined on the specs 5 | * https://github.com/UnifiedPush/UP-spec/blob/main/specifications.md 6 | */ 7 | 8 | const val ACTION_NEW_ENDPOINT = "org.unifiedpush.android.connector.NEW_ENDPOINT" 9 | const val ACTION_REGISTRATION_FAILED = "org.unifiedpush.android.connector.REGISTRATION_FAILED" 10 | const val ACTION_UNREGISTERED = "org.unifiedpush.android.connector.UNREGISTERED" 11 | const val ACTION_MESSAGE = "org.unifiedpush.android.connector.MESSAGE" 12 | 13 | const val ACTION_REGISTER = "org.unifiedpush.android.distributor.REGISTER" 14 | const val ACTION_UNREGISTER = "org.unifiedpush.android.distributor.UNREGISTER" 15 | 16 | const val EXTRA_APPLICATION = "application" 17 | const val EXTRA_TOKEN = "token" 18 | const val EXTRA_ENDPOINT = "endpoint" 19 | const val EXTRA_MESSAGE = "message" 20 | const val EXTRA_BYTES_MESSAGE = "bytesMessage" 21 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_logo.xml: -------------------------------------------------------------------------------- 1 | 6 | 10 | 14 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /app/src/main/java/org/unifiedpush/distributor/nextpush/utils/PackageUtils.kt: -------------------------------------------------------------------------------- 1 | package org.unifiedpush.distributor.nextpush.utils 2 | 3 | import android.content.Context 4 | import android.content.pm.PackageManager 5 | import android.os.Build 6 | import android.util.Log 7 | 8 | private const val U_TAG = "PackageUtils" 9 | 10 | fun Context.getApplicationName(packageId: String): String? { 11 | try { 12 | val ai = if (Build.VERSION.SDK_INT >= 33) { 13 | this.packageManager.getApplicationInfo( 14 | packageId, 15 | PackageManager.ApplicationInfoFlags.of( 16 | PackageManager.GET_META_DATA.toLong() 17 | ) 18 | ) 19 | } else { 20 | this.packageManager.getApplicationInfo(packageId, 0) 21 | } 22 | return this.packageManager.getApplicationLabel(ai).toString() 23 | } catch (e: PackageManager.NameNotFoundException) { 24 | Log.e(U_TAG, "Could not resolve app name", e) 25 | return null 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 6 | 10 | 14 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /app/src/main/java/org/unifiedpush/distributor/nextpush/api/provider/ApiProvider.kt: -------------------------------------------------------------------------------- 1 | package org.unifiedpush.distributor.nextpush.api.provider 2 | 3 | import io.reactivex.rxjava3.core.Observable 4 | import org.unifiedpush.distributor.nextpush.api.response.ApiResponse 5 | import retrofit2.http.Body 6 | import retrofit2.http.DELETE 7 | import retrofit2.http.PUT 8 | import retrofit2.http.Path 9 | 10 | interface ApiProvider { 11 | 12 | @PUT("device/") 13 | fun createDevice( 14 | @Body subscribeMap: Map 15 | ): Observable? 16 | 17 | @DELETE("device/{deviceId}") 18 | fun deleteDevice(@Path("deviceId") deviceId: String): Observable? 19 | 20 | @PUT("app/") 21 | fun createApp( 22 | @Body authorizeMap: Map 23 | ): Observable? 24 | 25 | @DELETE("app/{token}") 26 | fun deleteApp(@Path("token") token: String): Observable? 27 | 28 | companion object { 29 | const val mApiEndpoint = "/index.php/apps/uppush/" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /app/src/main/res/layout/item_app.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 15 | 21 | -------------------------------------------------------------------------------- /app/src/main/java/org/unifiedpush/distributor/nextpush/LocalNotification.kt: -------------------------------------------------------------------------------- 1 | package org.unifiedpush.distributor.nextpush 2 | 3 | import android.content.Context 4 | import org.unifiedpush.distributor.nextpush.Database.Companion.getDb 5 | import org.unifiedpush.distributor.nextpush.api.Api 6 | import org.unifiedpush.distributor.nextpush.utils.FromPushNotification 7 | import java.util.UUID 8 | 9 | object LocalNotification { 10 | fun createChannel(context: Context, title: String, block: () -> Unit) { 11 | Api(context).apiCreateApp(context.getString(R.string.local_notif_title).format(title)) { nextpushToken -> 12 | nextpushToken?.let { 13 | getDb(context).registerApp(context.packageName, UUID.randomUUID().toString(), it, title) 14 | } 15 | block() 16 | } 17 | } 18 | 19 | fun showNotification(context: Context, connectorToken: String, content: String) { 20 | val title = getDb(context).getNotificationTitle(connectorToken) ?: context.getString(R.string.app_name) 21 | FromPushNotification(context, title, content).show() 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /.woodpecker/main.yml: -------------------------------------------------------------------------------- 1 | steps: 2 | check: 3 | image: runmymind/docker-android-sdk:latest 4 | when: 5 | branch: main 6 | event: [push, pull_request] 7 | commands: 8 | - ./gradlew assembleRelease ktlintCheck --stacktrace 9 | 10 | build: 11 | image: runmymind/docker-android-sdk:latest 12 | when: 13 | branch: main 14 | event: tag 15 | commands: 16 | - export RELEASE_STORE_FILE=$PWD/release-key.jks 17 | - echo $RELEASE_KEY | base64 -d > $RELEASE_STORE_FILE 18 | - ./gradlew -Psign assembleRelease --stacktrace 19 | - mv app/build/outputs/apk/release/app-release.apk app/build/outputs/apk/nextpush.apk 20 | environment: 21 | - RELEASE_KEY_ALIAS=nextpush 22 | secrets: [ release_key, release_store_password, release_key_password ] 23 | 24 | upload: 25 | image: codeberg.org/s1m/woodpecker-upload:latest 26 | when: 27 | branch: main 28 | event: tag 29 | settings: 30 | token: 31 | from_secret: codeberg_token 32 | file: app/build/outputs/apk/nextpush.apk 33 | fastlane: true 34 | 35 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 13 | 14 | 20 | 21 | 22 | 23 | 26 | 27 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | on: [push, pull_request] 2 | 3 | name: Build 4 | 5 | jobs: 6 | check: 7 | name: Check 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/checkout@v2 11 | - uses: actions/setup-java@v1 12 | with: 13 | java-version: 17 14 | - if: ${{ !startsWith(github.ref, 'refs/tags/') }} 15 | run: ./gradlew build --stacktrace 16 | - if: ${{ startsWith(github.ref, 'refs/tags/') }} 17 | run: | 18 | export RELEASE_STORE_FILE=$(pwd)/release-key.jks 19 | echo $RELEASE_KEY | base64 -d > $RELEASE_STORE_FILE 20 | ./gradlew -Psign build --stacktrace 21 | cp app/build/outputs/apk/release/app-release.apk app/build/outputs/apk/release/Nextpush.apk 22 | env: 23 | RELEASE_KEY: ${{ secrets.RELEASE_KEY }} 24 | RELEASE_STORE_PASSWORD: ${{ secrets.RELEASE_STORE_PASSWORD }} 25 | RELEASE_KEY_ALIAS: nextpush 26 | RELEASE_KEY_PASSWORD: ${{ secrets.RELEASE_KEY_PASSWORD }} 27 | - if: ${{ startsWith(github.ref, 'refs/tags/') }} 28 | uses: svenstaro/upload-release-action@v2 29 | with: 30 | repo_token: ${{ secrets.GITHUB_TOKEN }} 31 | file: app/build/outputs/apk/release/Nextpush.apk 32 | tag: ${{ github.ref }} 33 | overwrite: true 34 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | # IDE (e.g. Android Studio) users: 3 | # Gradle settings configured through the IDE *will override* 4 | # any settings specified in this file. 5 | # For more details on how to configure your build environment visit 6 | # http://www.gradle.org/docs/current/userguide/build_environment.html 7 | # Specifies the JVM arguments used for the daemon process. 8 | # The setting is particularly useful for tweaking memory settings. 9 | org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 10 | # When configured, Gradle will run in incubating parallel mode. 11 | # This option should only be used with decoupled projects. More details, visit 12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 13 | # org.gradle.parallel=true 14 | # AndroidX package structure to make it clearer which packages are bundled with the 15 | # Android operating system, and which are packaged with your app"s APK 16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn 17 | android.useAndroidX=true 18 | # Automatically convert third-party libraries to use AndroidX 19 | # android.enableJetifier=false 20 | # Kotlin code style for this project: "official" or "obsolete": 21 | kotlin.code.style=official 22 | org.gradle.unsafe.configuration-cache=true 23 | android.defaults.buildfeatures.buildconfig=true 24 | android.nonTransitiveRClass=true 25 | android.nonFinalResIds=false -------------------------------------------------------------------------------- /app/src/main/java/org/unifiedpush/distributor/nextpush/api/provider/ApiDirectFactory.kt: -------------------------------------------------------------------------------- 1 | package org.unifiedpush.distributor.nextpush.api.provider 2 | 3 | import android.content.Context 4 | import okhttp3.* // ktlint-disable no-wildcard-imports 5 | import org.unifiedpush.distributor.nextpush.account.Account.getAccount 6 | import org.unifiedpush.distributor.nextpush.api.provider.ApiProvider.Companion.mApiEndpoint 7 | import retrofit2.Retrofit 8 | import retrofit2.adapter.rxjava3.RxJava3CallAdapterFactory 9 | import retrofit2.converter.gson.GsonConverterFactory 10 | 11 | class ApiDirectFactory(val context: Context) : ApiProviderFactory { 12 | override fun getProviderAndExecute(block: (ApiProvider, then: () -> Unit) -> Unit) { 13 | val account = getAccount(context) ?: run { 14 | throw NoProviderException("No account found") 15 | } 16 | val url = account.url ?: run { 17 | throw NoProviderException("No url found") 18 | } 19 | val client = account.getAccount(context) as OkHttpClient? ?: run { 20 | throw NoProviderException("No client found") 21 | } 22 | Retrofit.Builder() 23 | .client(client) 24 | .addConverterFactory(GsonConverterFactory.create()) 25 | .addCallAdapterFactory(RxJava3CallAdapterFactory.create()) 26 | .baseUrl("$url$mApiEndpoint").build() 27 | .create(ApiProvider::class.java).let { 28 | block(it) {} 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # NextPush - Android 2 | 3 | Moved to 4 | 5 | UnifiedPush provider for Nextcloud - android application 6 | 7 | [Get it on F-Droid](https://f-droid.org/packages/org.unifiedpush.distributor.nextpush/) 10 | 11 | ## Requirements 12 | 13 | **Nextcloud Server** 14 | 15 | [Server App Install](https://github.com/UP-NextPush/server-app) 16 | 17 | **Android Apps** 18 | 19 | [0] [Nextcloud Application](https://f-droid.org/packages/com.nextcloud.client/) - For SSO login (recommanded) 20 | 21 | [1] [NextPush Client](https://f-droid.org/en/packages/org.unifiedpush.distributor.nextpush/) - This app 22 | 23 | [2] [Applications supporting UnifiedPush](https://unifiedpush.org/users/apps/) 24 | 25 | [3] [UP-Example](https://f-droid.org/en/packages/org.unifiedpush.example/) - UnifiedPush Test Client - For testing purposes only. Not required for operation. 26 | 27 | ## Usage 28 | 29 | 1. (Recommanded) Install and sign into your Nextcloud account using the official Nextcloud Application [0]. 30 | 2. Install the NextPush client [1] and sign into your Nextcloud account. 31 | a. (Recommanded) With the Nextcloud file application (SSO) 32 | b. Manually, you will need to create an application password for NextPush. 33 | 3. Install one application supporting UnifiedPush [2], or UP-Example [3]. Login into the application if you need to, for instance with your mastodon account or with your matrix account. 34 | 4. The application will automatically detect NextPush and use it to send notifications. 35 | 36 | ## Credit 37 | 38 | This application has been inspired by [Nextcloud Push Notifier](https://gitlab.com/Nextcloud-Push/nextcloud-push-notifier) 39 | -------------------------------------------------------------------------------- /app/src/main/java/org/unifiedpush/distributor/nextpush/activities/PermissionsRequest.kt: -------------------------------------------------------------------------------- 1 | package org.unifiedpush.distributor.nextpush.activities 2 | 3 | import android.Manifest 4 | import android.content.pm.PackageManager 5 | import android.os.Build 6 | import android.util.Log 7 | import androidx.activity.result.contract.ActivityResultContracts 8 | import androidx.appcompat.app.AlertDialog 9 | import androidx.appcompat.app.AppCompatActivity 10 | import org.unifiedpush.distributor.nextpush.R 11 | import org.unifiedpush.distributor.nextpush.utils.TAG 12 | 13 | object PermissionsRequest { 14 | 15 | fun AppCompatActivity.requestAppPermissions() { 16 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { 17 | if (checkSelfPermission(Manifest.permission.POST_NOTIFICATIONS) 18 | != PackageManager.PERMISSION_GRANTED 19 | ) { 20 | Log.d(TAG, "Requesting POST_NOTIFICATIONS permission") 21 | this.registerForActivityResult( 22 | ActivityResultContracts.RequestPermission() 23 | ) { granted -> 24 | Log.d(TAG, "POST_NOTIFICATIONS permission granted: $granted") 25 | if (!granted) { 26 | if (shouldShowRequestPermissionRationale(Manifest.permission.POST_NOTIFICATIONS)) { 27 | Log.d(TAG, "Show POST_NOTIFICATIONS permission rationale") 28 | AlertDialog.Builder(this) 29 | .setTitle(getString(R.string.no_notification_dialog_title)) 30 | .setMessage(R.string.no_notification_dialog_message) 31 | .show() 32 | } 33 | } 34 | }.launch( 35 | Manifest.permission.POST_NOTIFICATIONS 36 | ) 37 | } 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /app/src/main/java/org/unifiedpush/distributor/nextpush/api/provider/ApiSSOFactory.kt: -------------------------------------------------------------------------------- 1 | package org.unifiedpush.distributor.nextpush.api.provider 2 | 3 | import android.content.Context 4 | import android.util.Log 5 | import com.google.gson.GsonBuilder 6 | import com.nextcloud.android.sso.api.NextcloudAPI 7 | import com.nextcloud.android.sso.model.SingleSignOnAccount 8 | import org.unifiedpush.distributor.nextpush.account.Account.getAccount 9 | import retrofit2.NextcloudRetrofitApiBuilder 10 | 11 | class ApiSSOFactory(val context: Context) : ApiProviderFactory { 12 | 13 | private val TAG = ApiSSOFactory::class.java.simpleName 14 | 15 | override fun getProviderAndExecute(block: (ApiProvider, then: () -> Unit) -> Unit) { 16 | var nextcloudAPI: NextcloudAPI? = null 17 | val account = getAccount(context) ?: run { 18 | throw NoProviderException("No account found") 19 | } 20 | val client = account.getAccount(context) as SingleSignOnAccount? ?: run { 21 | throw NoProviderException("No client found") 22 | } 23 | val ssoApiCallback = object : NextcloudAPI.ApiConnectedListener { 24 | override fun onConnected() { 25 | Log.d(TAG, "Api connected.") 26 | nextcloudAPI?.let { nextcloudAPI -> 27 | NextcloudRetrofitApiBuilder(nextcloudAPI, ApiProvider.mApiEndpoint) 28 | .create(ApiProvider::class.java).let { 29 | block(it) { 30 | nextcloudAPI.close() 31 | } 32 | } 33 | } 34 | } 35 | 36 | override fun onError(ex: Exception) { 37 | Log.d(TAG, "Cannot connect to API: ex = [$ex]") 38 | } 39 | } 40 | nextcloudAPI = NextcloudAPI( 41 | context, 42 | client, 43 | GsonBuilder().create(), 44 | ssoApiCallback 45 | ) 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'com.android.application' 3 | id 'kotlin-android' 4 | } 5 | 6 | android { 7 | compileSdk 33 8 | 9 | defaultConfig { 10 | applicationId "org.unifiedpush.distributor.nextpush" 11 | minSdk 24 12 | targetSdk 33 13 | versionCode 28 14 | versionName "1.8.1" 15 | 16 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 17 | } 18 | 19 | compileOptions { 20 | sourceCompatibility JavaVersion.VERSION_17 21 | targetCompatibility JavaVersion.VERSION_17 22 | } 23 | 24 | buildTypes { 25 | release { 26 | resValue "string", "app_name", "NextPush" 27 | minifyEnabled true 28 | shrinkResources true 29 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 30 | } 31 | debug { 32 | resValue "string", "app_name", "NextPush-dbg" 33 | applicationIdSuffix ".debug" 34 | debuggable true 35 | } 36 | } 37 | 38 | namespace 'org.unifiedpush.distributor.nextpush' 39 | } 40 | 41 | if (project.hasProperty('sign')) { 42 | android { 43 | signingConfigs { 44 | release { 45 | storeFile file(System.getenv("RELEASE_STORE_FILE")) 46 | storePassword System.getenv("RELEASE_STORE_PASSWORD") 47 | keyAlias System.getenv("RELEASE_KEY_ALIAS") 48 | keyPassword System.getenv("RELEASE_KEY_PASSWORD") 49 | } 50 | } 51 | } 52 | android.buildTypes.release.signingConfig android.signingConfigs.release 53 | } 54 | 55 | ext { 56 | retrofitVersion = "2.9.0" 57 | } 58 | 59 | dependencies { 60 | implementation("org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version") 61 | implementation("androidx.appcompat:appcompat:1.6.1") 62 | implementation("com.google.android.material:material:1.9.0") 63 | implementation("androidx.constraintlayout:constraintlayout:2.1.4") 64 | implementation("androidx.coordinatorlayout:coordinatorlayout:1.2.0") 65 | implementation("com.squareup.okhttp3:okhttp-sse:4.11.0") 66 | implementation("com.github.nextcloud:Android-SingleSignOn:0.8.1") 67 | implementation("com.squareup.retrofit2:retrofit:$retrofitVersion") 68 | implementation("com.squareup.retrofit2:converter-gson:$retrofitVersion") 69 | implementation("com.squareup.retrofit2:adapter-rxjava3:$retrofitVersion") 70 | implementation('io.reactivex.rxjava3:rxjava:3.1.7') 71 | implementation("io.reactivex.rxjava3:rxandroid:3.0.2") 72 | implementation("androidx.work:work-runtime-ktx:2.8.1") 73 | } 74 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 25 | 30 | 31 | 32 | 33 | 34 | 35 | 38 | 39 | 40 | 43 | 44 | 48 | 49 | 50 | 51 | 52 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | -------------------------------------------------------------------------------- /app/src/main/java/org/unifiedpush/distributor/nextpush/services/RestartWorker.kt: -------------------------------------------------------------------------------- 1 | package org.unifiedpush.distributor.nextpush.services 2 | 3 | import android.content.Context 4 | import android.util.Log 5 | import androidx.work.* // ktlint-disable no-wildcard-imports 6 | import org.unifiedpush.distributor.nextpush.AppCompanion 7 | import org.unifiedpush.distributor.nextpush.account.Account.getAccount 8 | import org.unifiedpush.distributor.nextpush.utils.TAG 9 | import java.util.Calendar 10 | import java.util.concurrent.TimeUnit 11 | 12 | private const val UNIQUE_PERIODIC_WORK_TAG = "nextpush::RestartWorker::unique_periodic" 13 | private const val UNIQUE_ONETIME_WORK_TAG = "nextpush::RestartWorker::unique_onetime" 14 | 15 | class RestartWorker(ctx: Context, params: WorkerParameters) : Worker(ctx, params) { 16 | 17 | override fun doWork(): Result { 18 | Log.d(TAG, "Working") 19 | if (!AppCompanion.hasInternet.get()) { 20 | Log.d(TAG, "Aborting, no internet.") 21 | return Result.success() 22 | } 23 | val currentDate = Calendar.getInstance() 24 | val restartDate = Calendar.getInstance() 25 | AppCompanion.lastEventDate?.let { 26 | restartDate.time = it.time 27 | restartDate.add(Calendar.SECOND, AppCompanion.keepalive.get()) 28 | Log.d(TAG, "restartDate: ${restartDate.time}") 29 | if (currentDate.after(restartDate)) { 30 | Log.d(TAG, "Restarting") 31 | FailureHandler.setMaxFails(applicationContext) // Max, will keep using the current worker 32 | StartService.startListener(applicationContext) 33 | } 34 | } ?: run { 35 | Log.d(TAG, "Restarting") 36 | StartService.startListener(applicationContext) 37 | } 38 | return Result.success() 39 | } 40 | 41 | companion object { 42 | 43 | fun startPeriodic(context: Context) { 44 | getAccount(context) ?: return 45 | val work = PeriodicWorkRequestBuilder(16, TimeUnit.MINUTES) 46 | WorkManager.getInstance(context) 47 | .enqueueUniquePeriodicWork( 48 | UNIQUE_PERIODIC_WORK_TAG, 49 | ExistingPeriodicWorkPolicy.UPDATE, 50 | work.build() 51 | ) 52 | } 53 | fun run(context: Context, delay: Long) { 54 | val work = OneTimeWorkRequestBuilder().apply { 55 | setInitialDelay(delay, TimeUnit.SECONDS) 56 | } 57 | AppCompanion.lastEventDate = null 58 | WorkManager.getInstance(context).enqueueUniqueWork( 59 | UNIQUE_ONETIME_WORK_TAG, 60 | ExistingWorkPolicy.REPLACE, 61 | work.build() 62 | ) 63 | } 64 | 65 | fun stopPeriodic(context: Context) { 66 | WorkManager.getInstance(context).cancelAllWorkByTag(UNIQUE_PERIODIC_WORK_TAG) 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto execute 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto execute 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :execute 68 | @rem Setup the command line 69 | 70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 71 | 72 | 73 | @rem Execute Gradle 74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 75 | 76 | :end 77 | @rem End local scope for the variables with windows NT shell 78 | if "%ERRORLEVEL%"=="0" goto mainEnd 79 | 80 | :fail 81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 82 | rem the _cmd.exe /c_ return code! 83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 84 | exit /b 1 85 | 86 | :mainEnd 87 | if "%OS%"=="Windows_NT" endlocal 88 | 89 | :omega 90 | -------------------------------------------------------------------------------- /app/src/main/java/org/unifiedpush/distributor/nextpush/services/RestartNetworkCallback.kt: -------------------------------------------------------------------------------- 1 | package org.unifiedpush.distributor.nextpush.services 2 | 3 | import android.app.Service 4 | import android.content.Context 5 | import android.net.ConnectivityManager 6 | import android.net.Network 7 | import android.net.NetworkCapabilities 8 | import android.util.Log 9 | import org.unifiedpush.distributor.nextpush.AppCompanion 10 | import org.unifiedpush.distributor.nextpush.utils.TAG 11 | import java.lang.Exception 12 | import java.util.concurrent.atomic.AtomicBoolean 13 | import java.util.concurrent.atomic.AtomicReference 14 | 15 | class RestartNetworkCallback(val context: Context) : ConnectivityManager.NetworkCallback() { 16 | private val connectivityManager: AtomicReference = AtomicReference(null) 17 | 18 | override fun onAvailable(network: Network) { 19 | Log.d(TAG, "Network is CONNECTED") 20 | if (FailureHandler.hasFailed(orNeverStart = false)) { 21 | Log.d(TAG, "Available: restarting worker") 22 | RestartWorker.run(context, delay = 0) 23 | } 24 | } 25 | 26 | override fun onCapabilitiesChanged( 27 | network: Network, 28 | networkCapabilities: NetworkCapabilities 29 | ) { 30 | if (networkCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)) { 31 | if (AppCompanion.hasInternet.getAndSet(true)) { 32 | Log.d(TAG, "Network Capabilities changed") 33 | if (FailureHandler.hasFailed(orNeverStart = false)) { 34 | Log.d(TAG, "Internet Cap: restarting worker") 35 | RestartWorker.run(context, delay = 0) 36 | } // else, it retries in max 2sec 37 | } 38 | } 39 | } 40 | 41 | override fun onLost(network: Network) { 42 | Log.d(TAG, "Network unavailable") 43 | AppCompanion.hasInternet.set(false) 44 | } 45 | 46 | fun register() { 47 | if (!registered.getAndSet(true)) { 48 | connectivityManager.get()?.let { 49 | Log.d(TAG, "ConnectivityManager already registered") 50 | } ?: run { 51 | Log.d(TAG, "Registering new ConnectivityManager") 52 | try { 53 | connectivityManager.set( 54 | ( 55 | context.getSystemService(Service.CONNECTIVITY_SERVICE) 56 | as ConnectivityManager 57 | ).apply { 58 | registerDefaultNetworkCallback(this@RestartNetworkCallback) 59 | } 60 | ) 61 | } catch (e: Exception) { 62 | e.printStackTrace() 63 | } 64 | } 65 | } 66 | } 67 | 68 | fun unregister() { 69 | Log.d(TAG, "Unregistering ConnectivityManager") 70 | connectivityManager.getAndSet(null)?.unregisterNetworkCallback(this) 71 | registered.set(false) 72 | AppCompanion.hasInternet.set(true) // reset default value 73 | } 74 | 75 | companion object { 76 | private val registered = AtomicBoolean(false) 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /app/src/main/java/org/unifiedpush/distributor/nextpush/activities/AppListAdapter.kt: -------------------------------------------------------------------------------- 1 | package org.unifiedpush.distributor.nextpush.activities 2 | 3 | import android.content.Context 4 | import android.util.SparseBooleanArray 5 | import android.view.LayoutInflater 6 | import android.view.View 7 | import android.view.ViewGroup 8 | import android.widget.ArrayAdapter 9 | import android.widget.TextView 10 | import androidx.core.view.isGone 11 | import com.google.android.material.color.MaterialColors 12 | import org.unifiedpush.distributor.nextpush.Database.Companion.getDb 13 | import org.unifiedpush.distributor.nextpush.R 14 | import org.unifiedpush.distributor.nextpush.utils.getApplicationName 15 | 16 | data class App( 17 | val token: String, 18 | val packageId: String 19 | ) 20 | 21 | class AppListAdapter(context: Context, private val resource: Int, apps: List) : ArrayAdapter(context, resource, apps) { 22 | private var selectedItemsIds = SparseBooleanArray() 23 | private val inflater = LayoutInflater.from(context) 24 | private val db = getDb(context) 25 | 26 | private class ViewHolder { 27 | var name: TextView? = null 28 | var description: TextView? = null 29 | } 30 | 31 | override fun getView(position: Int, pConvertView: View?, parent: ViewGroup): View { 32 | var viewHolder: ViewHolder? = null 33 | val convertView = pConvertView?.apply { 34 | viewHolder = tag as ViewHolder 35 | } ?: run { 36 | val rConvertView = inflater.inflate(resource, parent, false) 37 | viewHolder = ViewHolder().apply { 38 | this.name = rConvertView.findViewById(R.id.item_app_name) as TextView 39 | this.description = rConvertView.findViewById(R.id.item_description) as TextView 40 | } 41 | rConvertView.apply { 42 | tag = viewHolder 43 | } 44 | } 45 | getItem(position)?.let { 46 | if (it.packageId == context.packageName) { 47 | setViewHolderForLocalChannel(viewHolder, it) 48 | } else { 49 | setViewHolderForUnifiedPushApp(viewHolder, it) 50 | } 51 | } 52 | if (selectedItemsIds.get(position)) { 53 | convertView?.setBackgroundColor( 54 | MaterialColors.getColor(convertView, com.google.android.material.R.attr.colorOnTertiary) 55 | ) 56 | } else { 57 | convertView?.setBackgroundResource(0) 58 | } 59 | return convertView 60 | } 61 | 62 | private fun setViewHolderForUnifiedPushApp(viewHolder: ViewHolder?, app: App) { 63 | context.getApplicationName(app.packageId)?.let { 64 | viewHolder?.name?.text = it 65 | viewHolder?.description?.text = app.packageId 66 | } ?: run { 67 | viewHolder?.name?.text = app.packageId 68 | viewHolder?.description?.isGone = true 69 | } 70 | } 71 | 72 | private fun setViewHolderForLocalChannel(viewHolder: ViewHolder?, app: App) { 73 | val title = db.getNotificationTitle(app.token) 74 | viewHolder?.name?.text = context.getString(R.string.local_notif_title).format(title) 75 | viewHolder?.description?.text = context.getString(R.string.local_notif_description) 76 | } 77 | 78 | fun toggleSelection(position: Int) { 79 | selectView(position, !selectedItemsIds.get(position)) 80 | } 81 | 82 | fun removeSelection() { 83 | selectedItemsIds = SparseBooleanArray() 84 | notifyDataSetChanged() 85 | } 86 | 87 | private fun selectView(position: Int, value: Boolean) { 88 | selectedItemsIds.put(position, value) 89 | notifyDataSetChanged() 90 | } 91 | 92 | fun getSelectedIds(): SparseBooleanArray { 93 | return selectedItemsIds 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /app/src/main/res/values/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 17 | 18 | 22 | 23 | 31 | 32 | 36 | 37 | 44 | 45 | 61 | 62 | 66 | 67 | 75 | 76 | -------------------------------------------------------------------------------- /app/src/main/java/org/unifiedpush/distributor/nextpush/activities/StartActivity.kt: -------------------------------------------------------------------------------- 1 | package org.unifiedpush.distributor.nextpush.activities 2 | 3 | import android.app.Activity 4 | import android.content.Context 5 | import android.content.Intent 6 | import android.os.Bundle 7 | import android.util.Log 8 | import android.widget.Button 9 | import android.widget.EditText 10 | import android.widget.LinearLayout 11 | import android.widget.TextView 12 | import android.widget.Toast 13 | import androidx.appcompat.app.AppCompatActivity 14 | import androidx.core.view.isGone 15 | import org.unifiedpush.distributor.nextpush.R 16 | import org.unifiedpush.distributor.nextpush.account.Account 17 | import org.unifiedpush.distributor.nextpush.account.Account.setTypeDirect 18 | import org.unifiedpush.distributor.nextpush.account.Account.setTypeSSO 19 | import org.unifiedpush.distributor.nextpush.activities.MainActivity.Companion.goToMainActivity 20 | import org.unifiedpush.distributor.nextpush.activities.PermissionsRequest.requestAppPermissions 21 | import org.unifiedpush.distributor.nextpush.utils.TAG 22 | 23 | class StartActivity : AppCompatActivity() { 24 | private var onResult: ((activity: Activity, requestCode: Int, resultCode: Int, data: Intent?, block: (success: Boolean) -> Unit) -> Unit)? = null 25 | 26 | override fun onCreate(savedInstanceState: Bundle?) { 27 | super.onCreate(savedInstanceState) 28 | setContentView(R.layout.activity_start) 29 | this.requestAppPermissions() 30 | if (Account.isConnected(this)) { 31 | goToMainActivity(this) 32 | finish() 33 | } 34 | findViewById