├── 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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 | [
](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 |
24 |
25 |
26 |
27 |
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