├── nutshellfcm ├── rxworker │ ├── gradle.properties │ ├── src │ │ ├── main │ │ │ ├── res │ │ │ │ └── values │ │ │ │ │ └── strings.xml │ │ │ ├── AndroidManifest.xml │ │ │ └── java │ │ │ │ └── com │ │ │ │ └── nutshellfcm │ │ │ │ └── rxworkframework │ │ │ │ ├── Subscriber.kt │ │ │ │ ├── CancelledObserver.kt │ │ │ │ ├── Cancelable.kt │ │ │ │ ├── Observer.kt │ │ │ │ ├── works │ │ │ │ ├── WorkEmitter.kt │ │ │ │ ├── runnables │ │ │ │ │ ├── EmittedRunnable.kt │ │ │ │ │ ├── WorkRunnable.kt │ │ │ │ │ ├── NotifyObserversRunnable.kt │ │ │ │ │ ├── WorkMapRunnable.kt │ │ │ │ │ ├── WorkFilterRunnable.kt │ │ │ │ │ ├── NotifyOnCancelledRunnable.kt │ │ │ │ │ └── BlockingRunnable.kt │ │ │ │ ├── ParentWork.kt │ │ │ │ ├── CompositeCancellable.kt │ │ │ │ ├── MapWork.kt │ │ │ │ ├── ScheduledWork.kt │ │ │ │ ├── FilterWork.kt │ │ │ │ ├── BlockingWork.kt │ │ │ │ ├── FlatMapWork.kt │ │ │ │ └── Work.kt │ │ │ │ ├── scheudlers │ │ │ │ ├── Scheduler.kt │ │ │ │ ├── Schedulers.kt │ │ │ │ ├── MainScheduler.kt │ │ │ │ └── FuturesScheduler.kt │ │ │ │ ├── RxWorkCreator.kt │ │ │ │ ├── tasks │ │ │ │ ├── HandlerTask.kt │ │ │ │ └── FutureTask.kt │ │ │ │ └── RxWorkerThreadFactory.kt │ │ └── test │ │ │ └── java │ │ │ └── com │ │ │ └── nutshellfcm │ │ │ └── rxworkframework │ │ │ └── ExampleUnitTest.java │ ├── proguard-rules.pro │ └── build.gradle ├── nuthsellfcm-common │ ├── gradle.properties │ ├── src │ │ ├── main │ │ │ ├── res │ │ │ │ └── values │ │ │ │ │ └── strings.xml │ │ │ ├── AndroidManifest.xml │ │ │ └── java │ │ │ │ └── com │ │ │ │ └── nutshellfcm │ │ │ │ └── common │ │ │ │ └── PersistentAdapterContract.kt │ │ └── test │ │ │ └── java │ │ │ └── com │ │ │ └── nutshellfcm │ │ │ └── persistentadapters │ │ │ └── ExampleUnitTest.java │ ├── build.gradle │ └── proguard-rules.pro ├── gradle │ ├── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties │ ├── java_mvn_push.gradle │ └── android_mvn_push.gradle ├── nuthsellfcm-framework │ ├── gradle.properties │ ├── src │ │ ├── main │ │ │ ├── res │ │ │ │ └── values │ │ │ │ │ └── strings.xml │ │ │ ├── java │ │ │ │ └── com │ │ │ │ │ └── nutshellfcm │ │ │ │ │ └── framework │ │ │ │ │ ├── DefaultCaseProvider.kt │ │ │ │ │ ├── DefaultForegroundServicesBinder.kt │ │ │ │ │ ├── NutshellHandler.kt │ │ │ │ │ ├── CleanupCase.kt │ │ │ │ │ ├── application │ │ │ │ │ ├── contexts │ │ │ │ │ │ ├── ContextContract.kt │ │ │ │ │ │ ├── ActivityContext.kt │ │ │ │ │ │ └── ApplicationContext.kt │ │ │ │ │ ├── ApplicationLifeCycleWrapper.kt │ │ │ │ │ ├── ActivityLifecycleCallback.kt │ │ │ │ │ └── ForegroundService.kt │ │ │ │ │ ├── extensions │ │ │ │ │ └── CommonExtensions.kt │ │ │ │ │ ├── NutshellNotificationHandler.kt │ │ │ │ │ ├── NutshellLocalMessagesNotifier.kt │ │ │ │ │ ├── model │ │ │ │ │ ├── NotificationMessage.kt │ │ │ │ │ └── AndroidNotification.kt │ │ │ │ │ ├── NotificationsRouterBroadcastReceiver.kt │ │ │ │ │ ├── SilentNotificationHandleService.kt │ │ │ │ │ ├── ImportanceTranslator.kt │ │ │ │ │ ├── DefaultNotificationFactory.kt │ │ │ │ │ ├── sources │ │ │ │ │ ├── CacheSource.kt │ │ │ │ │ └── PersistedSource.kt │ │ │ │ │ ├── NotificationsRepository.kt │ │ │ │ │ ├── NutshellFCMEngine.kt │ │ │ │ │ ├── NotificationCasesManager.kt │ │ │ │ │ ├── AndroidNotificationBuilder.kt │ │ │ │ │ ├── NutshellFirebaseMessagingService.kt │ │ │ │ │ ├── PersistedMessageToNotificationMessageConverter.kt │ │ │ │ │ ├── NotificationNotifier.kt │ │ │ │ │ ├── NotificationsMessageRouter.kt │ │ │ │ │ ├── NotificationsReceiversRegisterer.kt │ │ │ │ │ ├── NotificationsConsumer.kt │ │ │ │ │ ├── di │ │ │ │ │ ├── NutshellFirebaseComponents.kt │ │ │ │ │ └── Injections.kt │ │ │ │ │ ├── NotificationsInteractor.kt │ │ │ │ │ ├── AndroidNotificationsManager.kt │ │ │ │ │ └── NutshellFCMContract.kt │ │ │ └── AndroidManifest.xml │ │ └── test │ │ │ └── java │ │ │ └── com │ │ │ └── nutshellfcm │ │ │ └── framework │ │ │ └── ExampleUnitTest.java │ ├── proguard-rules.pro │ └── build.gradle ├── samples │ └── app │ │ ├── src │ │ ├── main │ │ │ ├── res │ │ │ │ ├── values │ │ │ │ │ ├── strings.xml │ │ │ │ │ ├── colors.xml │ │ │ │ │ └── styles.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 │ │ │ │ ├── layout │ │ │ │ │ └── example_activity_layout.xml │ │ │ │ ├── drawable-v24 │ │ │ │ │ └── ic_launcher_foreground.xml │ │ │ │ └── drawable │ │ │ │ │ └── ic_launcher_background.xml │ │ │ ├── java │ │ │ │ └── com │ │ │ │ │ └── nutshellfcm │ │ │ │ │ └── sample_app │ │ │ │ │ ├── ExampleForegroundServicesBinder.kt │ │ │ │ │ ├── notifications_use_cases │ │ │ │ │ ├── Action4ExampleCase.kt │ │ │ │ │ ├── Action1ExampleCase.kt │ │ │ │ │ └── Action2Action3ExampleCase.kt │ │ │ │ │ ├── ExampleForegroundService.kt │ │ │ │ │ ├── ExampleApplication.kt │ │ │ │ │ ├── ExampleCaseProvider.kt │ │ │ │ │ ├── ExampleActivity.kt │ │ │ │ │ └── ExampleNotificationFactory.kt │ │ │ └── AndroidManifest.xml │ │ └── test │ │ │ └── java │ │ │ └── com │ │ │ └── nutshellfcm │ │ │ └── framework │ │ │ └── ExampleUnitTest.kt │ │ ├── proguard-rules.pro │ │ ├── google-services.json │ │ └── build.gradle ├── nuthsellfcm-room-persistent-adapter │ ├── src │ │ ├── main │ │ │ ├── res │ │ │ │ └── values │ │ │ │ │ └── strings.xml │ │ │ ├── AndroidManifest.xml │ │ │ └── java │ │ │ │ └── com │ │ │ │ └── nutshellfcm │ │ │ │ └── room_persistent_adapter │ │ │ │ ├── converters │ │ │ │ ├── DateTimeConverter.kt │ │ │ │ └── MapConverter.kt │ │ │ │ ├── NutshellPersistentRoomAdapter.kt │ │ │ │ ├── Database.kt │ │ │ │ ├── RoomAdapterContract.kt │ │ │ │ ├── PersistNotificationMessageToRoomNotificationMessageConverter.kt │ │ │ │ ├── dao │ │ │ │ └── RoomNotificationMessageDao.kt │ │ │ │ ├── RoomAdapter.kt │ │ │ │ └── model │ │ │ │ └── RoomNotificationMessage.kt │ │ └── test │ │ │ └── java │ │ │ └── com │ │ │ └── nutshellfcm │ │ │ └── room_persistent_adapter │ │ │ └── ExampleUnitTest.java │ ├── gradle.properties │ ├── proguard-rules.pro │ └── build.gradle ├── settings.gradle ├── versions.gradle ├── build.gradle ├── gradle.properties ├── gradlew.bat └── gradlew ├── CODE_OF_CONDUCT.md ├── README.md └── LICENSE /nutshellfcm/rxworker/gradle.properties: -------------------------------------------------------------------------------- 1 | POM_ARTIFACT_ID=rxworker 2 | POM_NAME=RxWorker 3 | POM_PACKAGING=aar 4 | -------------------------------------------------------------------------------- /nutshellfcm/nuthsellfcm-common/gradle.properties: -------------------------------------------------------------------------------- 1 | POM_ARTIFACT_ID=nuthsellfcm-common 2 | POM_NAME=NuthsellFCMCommon 3 | POM_PACKAGING=aar 4 | -------------------------------------------------------------------------------- /nutshellfcm/rxworker/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | rxworkframework 3 | 4 | -------------------------------------------------------------------------------- /nutshellfcm/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dor558/NutshellFCM/HEAD/nutshellfcm/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /nutshellfcm/nuthsellfcm-common/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | roomadapter 3 | 4 | -------------------------------------------------------------------------------- /nutshellfcm/nuthsellfcm-framework/gradle.properties: -------------------------------------------------------------------------------- 1 | POM_ARTIFACT_ID=nutshellfcm-framework 2 | POM_NAME=NutshellFCMFramework 3 | POM_PACKAGING=aar 4 | -------------------------------------------------------------------------------- /nutshellfcm/nuthsellfcm-framework/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | framework 3 | 4 | -------------------------------------------------------------------------------- /nutshellfcm/samples/app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | FirebaseNotifications 3 | 4 | -------------------------------------------------------------------------------- /nutshellfcm/nuthsellfcm-room-persistent-adapter/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | room_persistent_adapter 3 | 4 | -------------------------------------------------------------------------------- /nutshellfcm/nuthsellfcm-common/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | -------------------------------------------------------------------------------- /nutshellfcm/rxworker/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | -------------------------------------------------------------------------------- /nutshellfcm/samples/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dor558/NutshellFCM/HEAD/nutshellfcm/samples/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /nutshellfcm/samples/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dor558/NutshellFCM/HEAD/nutshellfcm/samples/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /nutshellfcm/samples/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dor558/NutshellFCM/HEAD/nutshellfcm/samples/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /nutshellfcm/samples/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dor558/NutshellFCM/HEAD/nutshellfcm/samples/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /nutshellfcm/samples/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dor558/NutshellFCM/HEAD/nutshellfcm/samples/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /nutshellfcm/nuthsellfcm-room-persistent-adapter/gradle.properties: -------------------------------------------------------------------------------- 1 | POM_ARTIFACT_ID=nuthsellfcm-room-persistent-adapter 2 | POM_NAME=NuthsellFCMRoomPersistentAdapter 3 | POM_PACKAGING=aar 4 | -------------------------------------------------------------------------------- /nutshellfcm/rxworker/src/main/java/com/nutshellfcm/rxworkframework/Subscriber.kt: -------------------------------------------------------------------------------- 1 | package com.nutshellfcm.rxworkframework 2 | 3 | 4 | internal data class Subscriber(val onSubscribe: () -> Unit) -------------------------------------------------------------------------------- /nutshellfcm/samples/app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dor558/NutshellFCM/HEAD/nutshellfcm/samples/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /nutshellfcm/samples/app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dor558/NutshellFCM/HEAD/nutshellfcm/samples/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /nutshellfcm/samples/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dor558/NutshellFCM/HEAD/nutshellfcm/samples/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /nutshellfcm/samples/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dor558/NutshellFCM/HEAD/nutshellfcm/samples/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /nutshellfcm/samples/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dor558/NutshellFCM/HEAD/nutshellfcm/samples/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /nutshellfcm/rxworker/src/main/java/com/nutshellfcm/rxworkframework/CancelledObserver.kt: -------------------------------------------------------------------------------- 1 | package com.nutshellfcm.rxworkframework 2 | 3 | 4 | internal data class CancelledObserver(val onCancelled: () -> Unit) -------------------------------------------------------------------------------- /nutshellfcm/nuthsellfcm-room-persistent-adapter/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | -------------------------------------------------------------------------------- /nutshellfcm/rxworker/src/main/java/com/nutshellfcm/rxworkframework/Cancelable.kt: -------------------------------------------------------------------------------- 1 | package com.nutshellfcm.rxworkframework 2 | 3 | 4 | interface Cancelable { 5 | 6 | val isCancelled: Boolean 7 | fun cancel() 8 | } -------------------------------------------------------------------------------- /nutshellfcm/rxworker/src/main/java/com/nutshellfcm/rxworkframework/Observer.kt: -------------------------------------------------------------------------------- 1 | package com.nutshellfcm.rxworkframework 2 | 3 | 4 | internal data class Observer(val onResult: (T) -> Unit, val onError: (Throwable) -> Unit) -------------------------------------------------------------------------------- /nutshellfcm/rxworker/src/main/java/com/nutshellfcm/rxworkframework/works/WorkEmitter.kt: -------------------------------------------------------------------------------- 1 | package com.nutshellfcm.rxworkframework.works 2 | 3 | interface WorkEmitter { 4 | fun onResult(result: T) 5 | fun onError(error: Throwable) 6 | } -------------------------------------------------------------------------------- /nutshellfcm/settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app', ':nuthsellfcm-framework', ':nuthsellfcm-common', ':nuthsellfcm-room-persistent-adapter', ':rxworker' 2 | project(':rxworker').projectDir = file('rxworker') 3 | project(':app').projectDir = file('samples/app') -------------------------------------------------------------------------------- /nutshellfcm/samples/app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #008577 4 | #00574B 5 | #D81B60 6 | 7 | -------------------------------------------------------------------------------- /nutshellfcm/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Mon Aug 19 15:26:15 IDT 2019 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-5.1.1-all.zip 7 | -------------------------------------------------------------------------------- /nutshellfcm/nuthsellfcm-framework/src/main/java/com/nutshellfcm/framework/DefaultCaseProvider.kt: -------------------------------------------------------------------------------- 1 | package com.nutshellfcm.framework 2 | 3 | 4 | class DefaultCaseProvider : NutshellFCMContract.CasesProvider { 5 | 6 | override val cases: List 7 | get() = emptyList() 8 | } -------------------------------------------------------------------------------- /nutshellfcm/nuthsellfcm-framework/src/main/java/com/nutshellfcm/framework/DefaultForegroundServicesBinder.kt: -------------------------------------------------------------------------------- 1 | package com.nutshellfcm.framework 2 | 3 | 4 | class DefaultForegroundServicesBinder : NutshellFCMContract.ForegroundServicesBinder { 5 | 6 | override fun bind(actionId: String): Class<*>? = null 7 | } -------------------------------------------------------------------------------- /nutshellfcm/rxworker/src/main/java/com/nutshellfcm/rxworkframework/works/runnables/EmittedRunnable.kt: -------------------------------------------------------------------------------- 1 | package com.nutshellfcm.rxworkframework.works.runnables 2 | 3 | import com.nutshellfcm.rxworkframework.works.WorkEmitter 4 | 5 | 6 | internal interface EmittedRunnable: Runnable { 7 | var emitter: WorkEmitter? 8 | } -------------------------------------------------------------------------------- /nutshellfcm/nuthsellfcm-framework/src/main/java/com/nutshellfcm/framework/NutshellHandler.kt: -------------------------------------------------------------------------------- 1 | package com.nutshellfcm.framework 2 | 3 | import android.os.Handler 4 | import android.os.HandlerThread 5 | 6 | 7 | internal object NutshellHandler: Handler(HandlerThread("NutshellHandler", Thread.MAX_PRIORITY) 8 | .also { it.start() }.looper) -------------------------------------------------------------------------------- /nutshellfcm/rxworker/src/main/java/com/nutshellfcm/rxworkframework/scheudlers/Scheduler.kt: -------------------------------------------------------------------------------- 1 | package com.nutshellfcm.rxworkframework.scheudlers 2 | 3 | import com.nutshellfcm.rxworkframework.Cancelable 4 | 5 | 6 | interface Scheduler { 7 | 8 | fun start() 9 | fun shutdown() 10 | fun schedule(runnable: Runnable): Cancelable 11 | } -------------------------------------------------------------------------------- /nutshellfcm/samples/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /nutshellfcm/samples/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /nutshellfcm/nuthsellfcm-framework/src/main/java/com/nutshellfcm/framework/CleanupCase.kt: -------------------------------------------------------------------------------- 1 | package com.nutshellfcm.framework 2 | 3 | import com.nutshellfcm.framework.model.NotificationMessage 4 | 5 | 6 | internal class CleanupCase : NutshellFCMContract.Case { 7 | 8 | override val actionIds: List = emptyList() 9 | 10 | override fun consume(caseMessages: List) {} 11 | } -------------------------------------------------------------------------------- /nutshellfcm/nuthsellfcm-framework/src/main/java/com/nutshellfcm/framework/application/contexts/ContextContract.kt: -------------------------------------------------------------------------------- 1 | package com.nutshellfcm.framework.application.contexts 2 | 3 | import android.content.Context 4 | 5 | 6 | interface ContextContract { 7 | 8 | interface AndroidContext { 9 | fun get(): Context 10 | fun getApplicationContext(): ApplicationContext 11 | } 12 | 13 | } -------------------------------------------------------------------------------- /nutshellfcm/versions.gradle: -------------------------------------------------------------------------------- 1 | ext { 2 | MAJOR_VERSION = 0 3 | MINOR_VERSION = 1 4 | HOTFIX_VERSION = 14 5 | 6 | VERSION_NAME = "${MAJOR_VERSION}.${MINOR_VERSION}.${HOTFIX_VERSION}" 7 | VERSION_CODE = MAJOR_VERSION * 10000 + MINOR_VERSION * 100 + HOTFIX_VERSION 8 | 9 | // Android SDK 10 | COMPILE_SDK_VERSION = 28 11 | MIN_SDK_VERSION = 16 12 | TARGET_SDK_VERSION = 28 13 | } -------------------------------------------------------------------------------- /nutshellfcm/nuthsellfcm-framework/src/main/java/com/nutshellfcm/framework/extensions/CommonExtensions.kt: -------------------------------------------------------------------------------- 1 | package com.nutshellfcm.framework.extensions 2 | 3 | import android.os.Bundle 4 | 5 | 6 | val Any.TAG: String get() = this::class.java.simpleName 7 | 8 | fun Map.toBundle(): Bundle { 9 | val bundle = Bundle() 10 | this.keys.forEach { key -> 11 | bundle.putString(key, this.getValue(key)) 12 | } 13 | 14 | return bundle 15 | } -------------------------------------------------------------------------------- /nutshellfcm/nuthsellfcm-room-persistent-adapter/src/main/java/com/nutshellfcm/room_persistent_adapter/converters/DateTimeConverter.kt: -------------------------------------------------------------------------------- 1 | package com.nutshellfcm.room_persistent_adapter.converters 2 | 3 | import androidx.room.TypeConverter 4 | import java.util.* 5 | 6 | class DateTimeConverter { 7 | @TypeConverter 8 | fun dateTimeToMillis(dataTime: Date): Long = dataTime.time 9 | 10 | @TypeConverter 11 | fun millisToDateTime(value: Long): Date = Date(value) 12 | } -------------------------------------------------------------------------------- /nutshellfcm/samples/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /nutshellfcm/nuthsellfcm-framework/src/main/java/com/nutshellfcm/framework/NutshellNotificationHandler.kt: -------------------------------------------------------------------------------- 1 | package com.nutshellfcm.framework 2 | 3 | import androidx.lifecycle.LiveData 4 | import androidx.lifecycle.MutableLiveData 5 | import com.nutshellfcm.framework.model.NotificationMessage 6 | 7 | 8 | object NutshellNotificationHandler : NutshellFCMContract.HandledNotificationsNotifier { 9 | 10 | override val handledNotifications: LiveData> = MutableLiveData() 11 | } -------------------------------------------------------------------------------- /nutshellfcm/samples/app/src/test/java/com/nutshellfcm/framework/ExampleUnitTest.kt: -------------------------------------------------------------------------------- 1 | package com.nutshellfcm.framework 2 | 3 | import org.junit.Test 4 | 5 | import org.junit.Assert.* 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * See [testing documentation](http://d.android.com/tools/testing). 11 | */ 12 | class ExampleUnitTest { 13 | @Test 14 | fun addition_isCorrect() { 15 | assertEquals(4, 2 + 2) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /nutshellfcm/samples/app/src/main/java/com/nutshellfcm/sample_app/ExampleForegroundServicesBinder.kt: -------------------------------------------------------------------------------- 1 | package com.nutshellfcm.sample_app 2 | 3 | import com.nutshellfcm.framework.NutshellFCMContract 4 | 5 | 6 | class ExampleForegroundServicesBinder : NutshellFCMContract.ForegroundServicesBinder { 7 | 8 | override fun bind(actionId: String): Class<*>? { 9 | return when (actionId) { 10 | "Action 4" -> ExampleForegroundService::class.java 11 | else -> null 12 | } 13 | } 14 | } -------------------------------------------------------------------------------- /nutshellfcm/nuthsellfcm-framework/src/main/java/com/nutshellfcm/framework/application/contexts/ActivityContext.kt: -------------------------------------------------------------------------------- 1 | package com.nutshellfcm.framework.application.contexts 2 | 3 | import android.content.Context 4 | import androidx.appcompat.app.AppCompatActivity 5 | 6 | 7 | class ActivityContext(private val activity: AppCompatActivity): ContextContract.AndroidContext { 8 | 9 | override fun get(): Context = activity 10 | 11 | override fun getApplicationContext() = ApplicationContext(activity.applicationContext) 12 | } -------------------------------------------------------------------------------- /nutshellfcm/nuthsellfcm-framework/src/main/java/com/nutshellfcm/framework/application/contexts/ApplicationContext.kt: -------------------------------------------------------------------------------- 1 | package com.nutshellfcm.framework.application.contexts 2 | 3 | import android.content.Context 4 | 5 | class ApplicationContext(private val context: Context) : ContextContract.AndroidContext { 6 | 7 | override fun get(): Context = context.applicationContext 8 | 9 | override fun getApplicationContext(): ApplicationContext { 10 | return ApplicationContext(context.applicationContext) 11 | } 12 | } -------------------------------------------------------------------------------- /nutshellfcm/nuthsellfcm-framework/src/test/java/com/nutshellfcm/framework/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package com.nutshellfcm.framework; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.junit.Assert.*; 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * @see Testing documentation 11 | */ 12 | public class ExampleUnitTest { 13 | @Test 14 | public void addition_isCorrect() { 15 | assertEquals(4, 2 + 2); 16 | } 17 | } -------------------------------------------------------------------------------- /nutshellfcm/rxworker/src/test/java/com/nutshellfcm/rxworkframework/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package com.nutshellfcm.rxworkframework; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.junit.Assert.*; 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * @see Testing documentation 11 | */ 12 | public class ExampleUnitTest { 13 | @Test 14 | public void addition_isCorrect() { 15 | assertEquals(4, 2 + 2); 16 | } 17 | } -------------------------------------------------------------------------------- /nutshellfcm/samples/app/src/main/java/com/nutshellfcm/sample_app/notifications_use_cases/Action4ExampleCase.kt: -------------------------------------------------------------------------------- 1 | package com.nutshellfcm.sample_app.notifications_use_cases 2 | 3 | import com.nutshellfcm.framework.NutshellFCMContract 4 | import com.nutshellfcm.framework.model.NotificationMessage 5 | 6 | 7 | class Action4ExampleCase : NutshellFCMContract.Case { 8 | 9 | override val actionIds: List = listOf("Action 4") 10 | 11 | override fun consume(caseMessages: List) { 12 | //Do something 13 | } 14 | } -------------------------------------------------------------------------------- /nutshellfcm/nuthsellfcm-common/src/test/java/com/nutshellfcm/persistentadapters/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package com.nutshellfcm.persistentadapters; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.junit.Assert.*; 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * @see Testing documentation 11 | */ 12 | public class ExampleUnitTest { 13 | @Test 14 | public void addition_isCorrect() { 15 | assertEquals(4, 2 + 2); 16 | } 17 | } -------------------------------------------------------------------------------- /nutshellfcm/rxworker/src/main/java/com/nutshellfcm/rxworkframework/works/ParentWork.kt: -------------------------------------------------------------------------------- 1 | package com.nutshellfcm.rxworkframework.works 2 | 3 | import com.nutshellfcm.rxworkframework.scheudlers.Scheduler 4 | import com.nutshellfcm.rxworkframework.works.runnables.EmittedRunnable 5 | 6 | 7 | internal interface ParentWork: ScheduledWork { 8 | var subscribeOnScheduler: Scheduler 9 | var observeOnScheduler: Scheduler 10 | val compositeCancellable: CompositeCancellable 11 | val emittedRunnable: EmittedRunnable 12 | 13 | fun onWorkCancelled() 14 | } -------------------------------------------------------------------------------- /nutshellfcm/nuthsellfcm-room-persistent-adapter/src/test/java/com/nutshellfcm/room_persistent_adapter/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package com.nutshellfcm.room_persistent_adapter; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.junit.Assert.*; 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * @see Testing documentation 11 | */ 12 | public class ExampleUnitTest { 13 | @Test 14 | public void addition_isCorrect() { 15 | assertEquals(4, 2 + 2); 16 | } 17 | } -------------------------------------------------------------------------------- /nutshellfcm/rxworker/src/main/java/com/nutshellfcm/rxworkframework/RxWorkCreator.kt: -------------------------------------------------------------------------------- 1 | package com.nutshellfcm.rxworkframework 2 | 3 | import com.nutshellfcm.rxworkframework.works.ScheduledWork 4 | import com.nutshellfcm.rxworkframework.works.Work 5 | import com.nutshellfcm.rxworkframework.works.WorkEmitter 6 | import com.nutshellfcm.rxworkframework.works.runnables.WorkRunnable 7 | 8 | 9 | object RxWorkCreator { 10 | 11 | fun create(workEmitterInvokable: (WorkEmitter) -> Unit): ScheduledWork { 12 | return Work(WorkRunnable(workEmitterInvokable)) 13 | } 14 | } -------------------------------------------------------------------------------- /nutshellfcm/samples/app/src/main/java/com/nutshellfcm/sample_app/ExampleForegroundService.kt: -------------------------------------------------------------------------------- 1 | package com.nutshellfcm.sample_app 2 | 3 | import android.content.Intent 4 | import android.os.Bundle 5 | import android.os.IBinder 6 | import com.nutshellfcm.framework.application.ForegroundService 7 | 8 | 9 | class ExampleForegroundService : ForegroundService() { 10 | 11 | override fun onForegroundStarted(payload: Bundle) { 12 | //Do something with the payload 13 | } 14 | 15 | override fun onBind(intent: Intent?): IBinder? { 16 | return null 17 | } 18 | } -------------------------------------------------------------------------------- /nutshellfcm/nuthsellfcm-framework/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 6 | 7 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /nutshellfcm/samples/app/src/main/java/com/nutshellfcm/sample_app/ExampleApplication.kt: -------------------------------------------------------------------------------- 1 | package com.nutshellfcm.sample_app 2 | 3 | import android.app.Application 4 | import com.nutshellfcm.framework.NutshellFCMEngine 5 | 6 | class ExampleApplication : Application() { 7 | 8 | 9 | override fun onCreate() { 10 | super.onCreate() 11 | NutshellFCMEngine.start(this, 12 | ExampleNotificationFactory(this), 13 | ExampleCaseProvider(), 14 | ExampleForegroundServicesBinder()) 15 | } 16 | 17 | 18 | } -------------------------------------------------------------------------------- /nutshellfcm/rxworker/src/main/java/com/nutshellfcm/rxworkframework/tasks/HandlerTask.kt: -------------------------------------------------------------------------------- 1 | package com.nutshellfcm.rxworkframework.tasks 2 | 3 | import android.os.Handler 4 | import com.nutshellfcm.rxworkframework.Cancelable 5 | 6 | internal class HandlerTask(private val runnable: Runnable, 7 | private val handler: Handler) : Runnable, Cancelable { 8 | 9 | override var isCancelled: Boolean = false 10 | 11 | override fun run() { 12 | runnable.run() 13 | } 14 | 15 | override fun cancel() { 16 | handler.removeCallbacks(this) 17 | isCancelled = true 18 | } 19 | } -------------------------------------------------------------------------------- /nutshellfcm/rxworker/src/main/java/com/nutshellfcm/rxworkframework/works/runnables/WorkRunnable.kt: -------------------------------------------------------------------------------- 1 | package com.nutshellfcm.rxworkframework.works.runnables 2 | 3 | import com.nutshellfcm.rxworkframework.works.WorkEmitter 4 | 5 | internal class WorkRunnable( 6 | val workEmitterInvokable: (WorkEmitter) -> Unit) : 7 | EmittedRunnable { 8 | 9 | override var emitter: WorkEmitter? = null 10 | 11 | override fun run() { 12 | runCatching { 13 | workEmitterInvokable.invoke(emitter!!) 14 | }.getOrElse { 15 | emitter!!.onError(it) 16 | } 17 | 18 | } 19 | } -------------------------------------------------------------------------------- /nutshellfcm/nuthsellfcm-framework/src/main/java/com/nutshellfcm/framework/NutshellLocalMessagesNotifier.kt: -------------------------------------------------------------------------------- 1 | package com.nutshellfcm.framework 2 | 3 | import com.nutshellfcm.framework.model.NotificationMessage 4 | import com.nutshellfcm.framework.di.NutshellFirebaseComponents 5 | 6 | 7 | object NutshellLocalMessagesNotifier { 8 | 9 | fun notify(notificationMessage: NotificationMessage) { 10 | NutshellFirebaseComponents.notificationNotifier.notifyMessage(notificationMessage) 11 | } 12 | 13 | fun notifyDismiss(actionId: String) { 14 | NutshellFirebaseComponents.notificationsConsumer.consume(actionId) 15 | } 16 | 17 | } -------------------------------------------------------------------------------- /nutshellfcm/rxworker/src/main/java/com/nutshellfcm/rxworkframework/tasks/FutureTask.kt: -------------------------------------------------------------------------------- 1 | package com.nutshellfcm.rxworkframework.tasks 2 | 3 | import com.nutshellfcm.rxworkframework.Cancelable 4 | import java.util.concurrent.Callable 5 | import java.util.concurrent.Future 6 | 7 | 8 | internal class FutureTask(private val runnable: Runnable) : Callable , Cancelable { 9 | 10 | lateinit var future: Future 11 | 12 | override val isCancelled by lazy { future.isCancelled } 13 | 14 | override fun call() { 15 | runnable.run() 16 | } 17 | 18 | override fun cancel() { 19 | future.cancel(true) 20 | } 21 | } -------------------------------------------------------------------------------- /nutshellfcm/rxworker/src/main/java/com/nutshellfcm/rxworkframework/scheudlers/Schedulers.kt: -------------------------------------------------------------------------------- 1 | package com.nutshellfcm.rxworkframework.scheudlers 2 | 3 | import com.nutshellfcm.rxworkframework.scheudlers.FuturesScheduler.Companion.Type.* 4 | 5 | object Schedulers { 6 | 7 | val main = MainScheduler().apply { start() } 8 | val single = FuturesScheduler(FIXED_SINGLE).apply { start() } 9 | val unbounded = FuturesScheduler(UNBOUNDED_CACHE).apply { start() } 10 | val bounded = FuturesScheduler(BOUNDED_CACHE).apply { start() } 11 | 12 | internal val defaultScheduler = FuturesScheduler(UNBOUNDED_CACHE).apply { start() } 13 | } 14 | 15 | 16 | -------------------------------------------------------------------------------- /nutshellfcm/samples/app/src/main/java/com/nutshellfcm/sample_app/notifications_use_cases/Action1ExampleCase.kt: -------------------------------------------------------------------------------- 1 | package com.nutshellfcm.sample_app.notifications_use_cases 2 | 3 | import com.nutshellfcm.framework.NutshellFCMContract 4 | import com.nutshellfcm.framework.model.NotificationMessage 5 | 6 | class Action1ExampleCase : NutshellFCMContract.Case { 7 | 8 | override val actionIds: List = listOf("Action 1") 9 | 10 | override fun consume(caseMessages: List) { 11 | caseMessages.forEach { 12 | val payload = it.payload 13 | //Do something with the payload 14 | } 15 | } 16 | } -------------------------------------------------------------------------------- /nutshellfcm/samples/app/src/main/java/com/nutshellfcm/sample_app/ExampleCaseProvider.kt: -------------------------------------------------------------------------------- 1 | package com.nutshellfcm.sample_app 2 | 3 | import com.nutshellfcm.sample_app.notifications_use_cases.Action1ExampleCase 4 | import com.nutshellfcm.sample_app.notifications_use_cases.Action2Action3ExampleCase 5 | import com.nutshellfcm.sample_app.notifications_use_cases.Action4ExampleCase 6 | import com.nutshellfcm.framework.NutshellFCMContract 7 | 8 | class ExampleCaseProvider : NutshellFCMContract.CasesProvider { 9 | 10 | override val cases: List = 11 | listOf(Action1ExampleCase(), Action2Action3ExampleCase(), Action4ExampleCase()) 12 | } -------------------------------------------------------------------------------- /nutshellfcm/nuthsellfcm-framework/src/main/java/com/nutshellfcm/framework/model/NotificationMessage.kt: -------------------------------------------------------------------------------- 1 | package com.nutshellfcm.framework.model 2 | 3 | import com.nutshellfcm.framework.NutshellFCMContract.Companion.KEY_ACTION_ID 4 | import com.nutshellfcm.framework.NutshellFCMContract.NotificationType 5 | import java.util.* 6 | 7 | data class NotificationMessage( 8 | 9 | val actionId: String, 10 | 11 | val type: NotificationType = NotificationType.NOTIFICATION, 12 | 13 | val payload: Map = mapOf(KEY_ACTION_ID to actionId), 14 | 15 | val timeStamp: Date = Date() 16 | ) { 17 | 18 | val notificationId = actionId.hashCode() 19 | } -------------------------------------------------------------------------------- /nutshellfcm/nuthsellfcm-framework/src/main/java/com/nutshellfcm/framework/application/ApplicationLifeCycleWrapper.kt: -------------------------------------------------------------------------------- 1 | package com.nutshellfcm.framework.application 2 | 3 | import android.app.Application 4 | 5 | internal class ApplicationLifeCycleWrapper(private val application: Application) { 6 | 7 | fun registerLifecycle(activityLifecycleCallback: ActivityLifecycleCallback) { 8 | application.registerActivityLifecycleCallbacks(activityLifecycleCallback) 9 | } 10 | 11 | fun unregisterLifeCycle(activityLifecycleCallback: ActivityLifecycleCallback) { 12 | application.unregisterActivityLifecycleCallbacks(activityLifecycleCallback) 13 | } 14 | 15 | } -------------------------------------------------------------------------------- /nutshellfcm/rxworker/src/main/java/com/nutshellfcm/rxworkframework/works/runnables/NotifyObserversRunnable.kt: -------------------------------------------------------------------------------- 1 | package com.nutshellfcm.rxworkframework.works.runnables 2 | 3 | import com.nutshellfcm.rxworkframework.Observer 4 | 5 | internal class NotifyObserversRunnable(private val observers: Set>): Runnable { 6 | 7 | var result: T? = null 8 | 9 | var throwable: Throwable? = null 10 | 11 | override fun run() { 12 | if (result != null) { 13 | observers.forEach { it.onResult(result!!) } 14 | } 15 | 16 | if (throwable != null) { 17 | observers.forEach { it.onError(throwable!!) } 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /nutshellfcm/samples/app/src/main/java/com/nutshellfcm/sample_app/notifications_use_cases/Action2Action3ExampleCase.kt: -------------------------------------------------------------------------------- 1 | package com.nutshellfcm.sample_app.notifications_use_cases 2 | 3 | import com.nutshellfcm.framework.NutshellFCMContract 4 | import com.nutshellfcm.framework.model.NotificationMessage 5 | 6 | 7 | class Action2Action3ExampleCase : NutshellFCMContract.Case { 8 | 9 | override val actionIds: List = listOf("Action 2", "Action 3") 10 | 11 | override fun consume(caseMessages: List) { 12 | caseMessages.forEach { 13 | val payload = it.payload 14 | //Do something with the payload 15 | } 16 | } 17 | } -------------------------------------------------------------------------------- /nutshellfcm/samples/app/src/main/res/layout/example_activity_layout.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 15 | 16 | -------------------------------------------------------------------------------- /nutshellfcm/rxworker/src/main/java/com/nutshellfcm/rxworkframework/works/CompositeCancellable.kt: -------------------------------------------------------------------------------- 1 | package com.nutshellfcm.rxworkframework.works 2 | 3 | import com.nutshellfcm.rxworkframework.Cancelable 4 | 5 | 6 | internal class CompositeCancellable(private val parentWork: ParentWork<*>) : Cancelable { 7 | 8 | private val set: MutableSet = mutableSetOf() 9 | 10 | override val isCancelled: Boolean = set.all { it.isCancelled } 11 | 12 | override fun cancel() { 13 | set.forEach { it.cancel() } 14 | set.clear() 15 | parentWork.onWorkCancelled() 16 | } 17 | 18 | fun append(cancelable: Cancelable) { 19 | set.add(cancelable) 20 | } 21 | } -------------------------------------------------------------------------------- /nutshellfcm/nuthsellfcm-room-persistent-adapter/src/main/java/com/nutshellfcm/room_persistent_adapter/converters/MapConverter.kt: -------------------------------------------------------------------------------- 1 | package com.nutshellfcm.room_persistent_adapter.converters 2 | 3 | import androidx.room.TypeConverter 4 | import com.google.common.reflect.TypeToken 5 | import com.google.gson.Gson 6 | 7 | 8 | class MapConverter { 9 | 10 | @TypeConverter 11 | fun toJson(map: Map): String { 12 | return Gson().toJson(map) 13 | } 14 | 15 | @TypeConverter 16 | fun toMap(json: String): Map { 17 | val typeOfHashMap = object : TypeToken>() {}.type 18 | return Gson().fromJson(json, typeOfHashMap) 19 | } 20 | } -------------------------------------------------------------------------------- /nutshellfcm/rxworker/src/main/java/com/nutshellfcm/rxworkframework/works/runnables/WorkMapRunnable.kt: -------------------------------------------------------------------------------- 1 | package com.nutshellfcm.rxworkframework.works.runnables 2 | 3 | import com.nutshellfcm.rxworkframework.works.WorkEmitter 4 | 5 | 6 | internal class WorkMapRunnable( 7 | private val transition: (T) -> S 8 | ) : EmittedRunnable { 9 | 10 | override var emitter: WorkEmitter? = null 11 | var input: T? = null 12 | 13 | override fun run() { 14 | runCatching { 15 | val mappedScheduledWork = transition.invoke(input!!) 16 | emitter!!.onResult(mappedScheduledWork) 17 | }.getOrElse { 18 | emitter!!.onError(it) 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /nutshellfcm/nuthsellfcm-framework/src/main/java/com/nutshellfcm/framework/NotificationsRouterBroadcastReceiver.kt: -------------------------------------------------------------------------------- 1 | package com.nutshellfcm.framework 2 | 3 | import android.content.BroadcastReceiver 4 | import android.content.Context 5 | import android.content.Intent 6 | import com.nutshellfcm.framework.di.NutshellFirebaseComponents 7 | 8 | 9 | internal class NotificationsRouterBroadcastReceiver: BroadcastReceiver() { 10 | 11 | private val notificationMessageRouter: NutshellFCMContract.NotificationsMessageRouter = 12 | NutshellFirebaseComponents.notificationsMessageRouter 13 | 14 | override fun onReceive(context: Context, intent: Intent) { 15 | notificationMessageRouter.onRouteNotificationsMessage(intent) 16 | } 17 | } -------------------------------------------------------------------------------- /nutshellfcm/rxworker/src/main/java/com/nutshellfcm/rxworkframework/works/runnables/WorkFilterRunnable.kt: -------------------------------------------------------------------------------- 1 | package com.nutshellfcm.rxworkframework.works.runnables 2 | 3 | import com.nutshellfcm.rxworkframework.works.WorkEmitter 4 | 5 | 6 | internal class WorkFilterRunnable(private val filterInvokable: (T) -> Boolean): EmittedRunnable { 7 | 8 | override var emitter: WorkEmitter? = null 9 | 10 | var input: T? = null 11 | 12 | override fun run() { 13 | runCatching { 14 | val isFiltered = filterInvokable.invoke(input!!) 15 | if (isFiltered) { 16 | emitter!!.onResult(input!!) 17 | } 18 | 19 | }.getOrElse { 20 | emitter!!.onError(it) 21 | } 22 | } 23 | } -------------------------------------------------------------------------------- /nutshellfcm/nuthsellfcm-framework/src/main/java/com/nutshellfcm/framework/model/AndroidNotification.kt: -------------------------------------------------------------------------------- 1 | package com.nutshellfcm.framework.model 2 | 3 | import android.app.PendingIntent 4 | import android.graphics.Bitmap 5 | import com.nutshellfcm.framework.NutshellFCMContract 6 | 7 | data class AndroidNotification( 8 | val payload: Map = emptyMap(), 9 | val contentTitle: CharSequence, 10 | val contentText: CharSequence, 11 | val color: Int = -1, 12 | val largeIcon: Bitmap? = null, 13 | val smallIcon: Int, 14 | val contentIntent: PendingIntent? = null, 15 | val deleteIntent: PendingIntent? = null, 16 | val importance: NutshellFCMContract.Importance, 17 | val channel: NutshellFCMContract.NotificationChannel 18 | ) -------------------------------------------------------------------------------- /nutshellfcm/nuthsellfcm-room-persistent-adapter/src/main/java/com/nutshellfcm/room_persistent_adapter/NutshellPersistentRoomAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.nutshellfcm.room_persistent_adapter 2 | 3 | import android.app.Application 4 | import androidx.room.Room 5 | import com.nutshellfcm.common.PersistentAdapterContract 6 | 7 | 8 | object NutshellPersistentRoomAdapter { 9 | 10 | fun build(application: Application): PersistentAdapterContract.Adapter { 11 | val database = Room.databaseBuilder(application, Database::class.java, DATABASE_NAME) 12 | .fallbackToDestructiveMigration() 13 | .build() 14 | 15 | return RoomAdapter(PersistNotificationMessageToRoomNotificationMessageConverter(), 16 | database.notificationMessageDao()) 17 | } 18 | } -------------------------------------------------------------------------------- /nutshellfcm/rxworker/src/main/java/com/nutshellfcm/rxworkframework/works/runnables/NotifyOnCancelledRunnable.kt: -------------------------------------------------------------------------------- 1 | package com.nutshellfcm.rxworkframework.works.runnables 2 | 3 | import com.nutshellfcm.rxworkframework.Observer 4 | import com.nutshellfcm.rxworkframework.CancelledObserver 5 | import com.nutshellfcm.rxworkframework.Subscriber 6 | 7 | 8 | internal class NotifyOnCancelledRunnable(private val cancelledObserver: Set, 9 | private val observers: MutableSet>, 10 | private val subscriber: MutableSet): Runnable { 11 | 12 | override fun run() { 13 | cancelledObserver.forEach { it.onCancelled.invoke() } 14 | observers.clear() 15 | subscriber.clear() 16 | } 17 | } -------------------------------------------------------------------------------- /nutshellfcm/rxworker/src/main/java/com/nutshellfcm/rxworkframework/works/MapWork.kt: -------------------------------------------------------------------------------- 1 | package com.nutshellfcm.rxworkframework.works 2 | 3 | import com.nutshellfcm.rxworkframework.works.runnables.NotifyObserversRunnable 4 | import com.nutshellfcm.rxworkframework.works.runnables.WorkMapRunnable 5 | 6 | 7 | internal class MapWork( 8 | workMapRunnable: WorkMapRunnable, 9 | parentWork: ParentWork 10 | ) 11 | : FlatMapWork(workMapRunnable, parentWork), ParentWork { 12 | 13 | override fun onResult(result: S) { 14 | mapWork(result) 15 | } 16 | 17 | private fun mapWork(result: S) { 18 | val notifyObserversRunnable = NotifyObserversRunnable(observers) 19 | notifyObserversRunnable.result = result 20 | observeOnScheduler.schedule(notifyObserversRunnable) 21 | } 22 | 23 | } -------------------------------------------------------------------------------- /nutshellfcm/nuthsellfcm-common/src/main/java/com/nutshellfcm/common/PersistentAdapterContract.kt: -------------------------------------------------------------------------------- 1 | package com.nutshellfcm.common 2 | 3 | import java.util.* 4 | 5 | 6 | class PersistentAdapterContract { 7 | 8 | 9 | interface Adapter { 10 | 11 | fun insert(notificationMessages: List) 12 | fun insert(notificationMessage: PersistNotificationMessage) 13 | fun get(actionId: String): PersistNotificationMessage 14 | fun get(): List 15 | fun remove(actionId: String) 16 | fun removeGroup(actionIds: List) 17 | fun purge() 18 | } 19 | 20 | interface PersistNotificationMessage { 21 | val actionId: String 22 | val type: String 23 | val payload: Map 24 | val timeStamp: Date 25 | } 26 | 27 | } -------------------------------------------------------------------------------- /nutshellfcm/rxworker/src/main/java/com/nutshellfcm/rxworkframework/RxWorkerThreadFactory.kt: -------------------------------------------------------------------------------- 1 | package com.nutshellfcm.rxworkframework 2 | 3 | import java.util.concurrent.ThreadFactory 4 | import java.util.concurrent.atomic.AtomicLong 5 | 6 | 7 | class RxWorkerThreadFactory(val schedulerName: String) : ThreadFactory { 8 | 9 | private val counter = AtomicLong() 10 | 11 | private val KEY_PRIORITY = "rxworker.priority" 12 | 13 | private val priority = Thread.NORM_PRIORITY 14 | 15 | override fun newThread(r: Runnable): Thread { 16 | val thread = CustomThread(r,"$schedulerName RxWorker Thread - ${counter.incrementAndGet()}") 17 | thread.isDaemon = true 18 | thread.priority = priority 19 | return thread 20 | } 21 | 22 | inner class CustomThread(r: Runnable, threadName: String) : Thread(r, threadName) 23 | } -------------------------------------------------------------------------------- /nutshellfcm/nuthsellfcm-common/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | apply plugin: 'kotlin-android' 3 | apply plugin: 'kotlin-kapt' 4 | apply plugin: 'kotlin-android-extensions' 5 | apply from: '../versions.gradle' 6 | 7 | android { 8 | compileSdkVersion COMPILE_SDK_VERSION 9 | 10 | defaultConfig { 11 | minSdkVersion MIN_SDK_VERSION 12 | targetSdkVersion TARGET_SDK_VERSION 13 | versionCode VERSION_CODE 14 | versionName VERSION_NAME 15 | } 16 | } 17 | 18 | apply from: rootProject.file('gradle/android_mvn_push.gradle') 19 | 20 | dependencies { 21 | implementation fileTree(dir: 'libs', include: ['*.jar']) 22 | 23 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 24 | implementation 'androidx.core:core-ktx:1.0.2' 25 | } 26 | repositories { 27 | mavenCentral() 28 | } 29 | -------------------------------------------------------------------------------- /nutshellfcm/nuthsellfcm-framework/src/main/java/com/nutshellfcm/framework/SilentNotificationHandleService.kt: -------------------------------------------------------------------------------- 1 | package com.nutshellfcm.framework 2 | 3 | import android.content.Intent 4 | import androidx.core.app.JobIntentService 5 | import com.nutshellfcm.framework.di.NutshellFirebaseComponents 6 | 7 | const val SILENT_NOTIFICATION_SERVICE_ID = 135 8 | 9 | class SilentNotificationHandleService: JobIntentService() { 10 | 11 | private val notificationsConsumer: NutshellFCMContract.NotificationsConsumer = NutshellFirebaseComponents.notificationsConsumer 12 | 13 | override fun onHandleWork(intent: Intent) { 14 | val actionId = intent.extras?.getString(NutshellFCMContract.KEY_ACTION_ID) 15 | ?: throw NutshellFCMContract.Error.UnknownNotificationActionIdThrowable(null) 16 | notificationsConsumer.consume(actionId) 17 | } 18 | 19 | } -------------------------------------------------------------------------------- /nutshellfcm/rxworker/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /nutshellfcm/samples/app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /nutshellfcm/nuthsellfcm-common/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /nutshellfcm/rxworker/src/main/java/com/nutshellfcm/rxworkframework/works/ScheduledWork.kt: -------------------------------------------------------------------------------- 1 | package com.nutshellfcm.rxworkframework.works 2 | 3 | import com.nutshellfcm.rxworkframework.Cancelable 4 | import com.nutshellfcm.rxworkframework.scheudlers.Scheduler 5 | 6 | interface ScheduledWork { 7 | fun subscribe(onResult: (T) -> Unit = {}, onError: (Throwable) -> Unit = {}): Cancelable 8 | fun blockingGet(): T? 9 | fun flatMap(transition: (T) -> ScheduledWork): ScheduledWork 10 | fun map(transition: (T) -> S): ScheduledWork 11 | fun filter(filter: (T) -> Boolean): ScheduledWork 12 | fun subscribeOn(scheduler: Scheduler): ScheduledWork 13 | fun observeOn(scheduler: Scheduler): ScheduledWork 14 | fun doOnSubscribe(onSubscribe: () -> Unit): ScheduledWork 15 | fun doOnCancelled(onCancelled: () -> Unit): ScheduledWork 16 | } -------------------------------------------------------------------------------- /nutshellfcm/nuthsellfcm-framework/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /nutshellfcm/nuthsellfcm-framework/src/main/java/com/nutshellfcm/framework/application/ActivityLifecycleCallback.kt: -------------------------------------------------------------------------------- 1 | package com.nutshellfcm.framework.application 2 | 3 | import android.app.Activity 4 | import android.app.Application 5 | import android.os.Bundle 6 | 7 | 8 | interface ActivityLifecycleCallback : Application.ActivityLifecycleCallbacks { 9 | 10 | override fun onActivityPaused(activity: Activity) {} 11 | 12 | override fun onActivityResumed(activity: Activity) {} 13 | 14 | override fun onActivityStarted(activity: Activity) {} 15 | 16 | override fun onActivityDestroyed(activity: Activity) {} 17 | 18 | override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle) {} 19 | 20 | override fun onActivityStopped(activity: Activity) {} 21 | 22 | override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) {} 23 | } -------------------------------------------------------------------------------- /nutshellfcm/nuthsellfcm-room-persistent-adapter/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /nutshellfcm/nuthsellfcm-room-persistent-adapter/src/main/java/com/nutshellfcm/room_persistent_adapter/Database.kt: -------------------------------------------------------------------------------- 1 | package com.nutshellfcm.room_persistent_adapter 2 | 3 | import androidx.room.Database 4 | import androidx.room.RoomDatabase 5 | import androidx.room.TypeConverters 6 | import com.nutshellfcm.room_persistent_adapter.converters.DateTimeConverter 7 | import com.nutshellfcm.room_persistent_adapter.converters.MapConverter 8 | import com.nutshellfcm.room_persistent_adapter.dao.RoomNotificationMessageDao 9 | import com.nutshellfcm.room_persistent_adapter.model.RoomNotificationMessage 10 | 11 | const val DATABASE_NAME = "notifications_db" 12 | @Database(entities = [RoomNotificationMessage::class], version = 3) 13 | @TypeConverters( 14 | DateTimeConverter::class, 15 | MapConverter::class 16 | ) 17 | internal abstract class Database: RoomDatabase() { 18 | abstract fun notificationMessageDao(): RoomNotificationMessageDao 19 | } -------------------------------------------------------------------------------- /nutshellfcm/nuthsellfcm-room-persistent-adapter/src/main/java/com/nutshellfcm/room_persistent_adapter/RoomAdapterContract.kt: -------------------------------------------------------------------------------- 1 | package com.nutshellfcm.room_persistent_adapter 2 | 3 | import com.nutshellfcm.common.PersistentAdapterContract.* 4 | import com.nutshellfcm.room_persistent_adapter.model.RoomNotificationMessage 5 | 6 | 7 | interface RoomAdapterContract { 8 | 9 | companion object { 10 | const val KEY_ACTION_ID = "action_id" 11 | const val KEY_TIMESTAMP = "timestamp" 12 | const val KEY_PAYLOAD = "payload" 13 | const val KEY_TYPE = "type" 14 | const val ROOM_TABLE_NOTIFICATION_MESSAGE = "notification_messages" 15 | } 16 | 17 | interface Converter { 18 | fun convert(persistNotificationMessage: PersistNotificationMessage): RoomNotificationMessage 19 | fun convert(persistNotificationMessages: List): List 20 | } 21 | 22 | } -------------------------------------------------------------------------------- /nutshellfcm/rxworker/src/main/java/com/nutshellfcm/rxworkframework/scheudlers/MainScheduler.kt: -------------------------------------------------------------------------------- 1 | package com.nutshellfcm.rxworkframework.scheudlers 2 | 3 | import android.os.Build 4 | import android.os.Handler 5 | import android.os.Looper 6 | import android.os.Message 7 | import com.nutshellfcm.rxworkframework.Cancelable 8 | import com.nutshellfcm.rxworkframework.tasks.HandlerTask 9 | 10 | 11 | class MainScheduler: Scheduler { 12 | 13 | private val handler = Handler(Looper.getMainLooper()) 14 | 15 | override fun start() {} 16 | 17 | override fun shutdown() {} 18 | 19 | override fun schedule(runnable: Runnable): Cancelable { 20 | val handlerWork = HandlerTask(runnable, handler) 21 | val message = Message.obtain(handler, handlerWork) 22 | if (Build.VERSION.SDK_INT > 22) { 23 | message.isAsynchronous = false 24 | } 25 | 26 | handler.sendMessage(message) 27 | return handlerWork 28 | } 29 | 30 | } -------------------------------------------------------------------------------- /nutshellfcm/rxworker/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | apply plugin: 'kotlin-android' 3 | apply plugin: 'kotlin-android-extensions' 4 | apply from: '../versions.gradle' 5 | 6 | android { 7 | compileSdkVersion COMPILE_SDK_VERSION 8 | 9 | defaultConfig { 10 | minSdkVersion MIN_SDK_VERSION 11 | targetSdkVersion TARGET_SDK_VERSION 12 | versionCode VERSION_CODE 13 | versionName VERSION_NAME 14 | } 15 | } 16 | 17 | apply from: rootProject.file('gradle/android_mvn_push.gradle') 18 | 19 | dependencies { 20 | implementation fileTree(dir: 'libs', include: ['*.jar']) 21 | 22 | implementation 'com.android.support:appcompat-v7:28.0.0' 23 | testImplementation 'junit:junit:4.12' 24 | androidTestImplementation 'com.android.support.test:runner:1.0.2' 25 | androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' 26 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 27 | } 28 | -------------------------------------------------------------------------------- /nutshellfcm/nuthsellfcm-framework/src/main/java/com/nutshellfcm/framework/ImportanceTranslator.kt: -------------------------------------------------------------------------------- 1 | package com.nutshellfcm.framework 2 | 3 | import androidx.core.app.NotificationCompat.* 4 | import androidx.core.app.NotificationManagerCompat.* 5 | import com.nutshellfcm.framework.NutshellFCMContract.Importance.* 6 | 7 | internal class ImportanceTranslator: NutshellFCMContract.Translators.ImportanceTranslator { 8 | 9 | override fun getAndroidPriority(importance: NutshellFCMContract.Importance) = 10 | when(importance) { 11 | LOW -> PRIORITY_MIN 12 | MEDIUM -> PRIORITY_LOW 13 | HIGH -> PRIORITY_DEFAULT 14 | URGENT -> PRIORITY_MAX 15 | } 16 | 17 | override fun getAndroidImportance(importance: NutshellFCMContract.Importance) = 18 | when(importance) { 19 | LOW -> IMPORTANCE_MIN 20 | MEDIUM -> IMPORTANCE_LOW 21 | HIGH -> IMPORTANCE_DEFAULT 22 | URGENT -> IMPORTANCE_HIGH 23 | } 24 | } -------------------------------------------------------------------------------- /nutshellfcm/rxworker/src/main/java/com/nutshellfcm/rxworkframework/works/runnables/BlockingRunnable.kt: -------------------------------------------------------------------------------- 1 | package com.nutshellfcm.rxworkframework.works.runnables 2 | 3 | import com.nutshellfcm.rxworkframework.works.WorkEmitter 4 | import java.util.concurrent.CountDownLatch 5 | 6 | 7 | internal class BlockingRunnable(private val emittedRunnable: EmittedRunnable) 8 | : CountDownLatch(1), EmittedRunnable, WorkEmitter { 9 | 10 | override var emitter: WorkEmitter? = this 11 | 12 | internal var result: T? = null 13 | 14 | internal var error: Throwable? = null 15 | 16 | override fun run() { 17 | runCatching { 18 | emittedRunnable.emitter = this 19 | emittedRunnable.run() 20 | }.getOrElse { throwable -> 21 | emitter?.onError(throwable) 22 | } 23 | } 24 | 25 | override fun onResult(result: T) { 26 | this.result = result 27 | countDown() 28 | } 29 | 30 | override fun onError(error: Throwable) { 31 | this.error = error 32 | countDown() 33 | } 34 | } -------------------------------------------------------------------------------- /nutshellfcm/nuthsellfcm-framework/src/main/java/com/nutshellfcm/framework/DefaultNotificationFactory.kt: -------------------------------------------------------------------------------- 1 | package com.nutshellfcm.framework 2 | 3 | import com.nutshellfcm.framework.model.AndroidNotification 4 | import com.nutshellfcm.framework.model.NotificationMessage 5 | 6 | 7 | class DefaultNotificationFactory : NutshellFCMContract.AndroidNotificationsFactory { 8 | 9 | override fun create(notificationMessage: NotificationMessage): AndroidNotification { 10 | return AndroidNotification( 11 | payload = notificationMessage.payload, 12 | contentIntent = null, 13 | contentTitle = "Example notification title", 14 | contentText = "Override AndroidNotificationFactory to custom your own notification", 15 | importance = NutshellFCMContract.Importance.HIGH, 16 | smallIcon = android.R.drawable.gallery_thumb, 17 | channel = NutshellFCMContract.NotificationChannel( 18 | "example channel id", 19 | "example channel", 20 | null 21 | ) 22 | ) 23 | } 24 | } -------------------------------------------------------------------------------- /nutshellfcm/nuthsellfcm-room-persistent-adapter/src/main/java/com/nutshellfcm/room_persistent_adapter/PersistNotificationMessageToRoomNotificationMessageConverter.kt: -------------------------------------------------------------------------------- 1 | package com.nutshellfcm.room_persistent_adapter 2 | 3 | import com.nutshellfcm.common.PersistentAdapterContract.PersistNotificationMessage 4 | import com.nutshellfcm.room_persistent_adapter.model.RoomNotificationMessage 5 | 6 | internal class PersistNotificationMessageToRoomNotificationMessageConverter : RoomAdapterContract.Converter { 7 | 8 | 9 | override fun convert(persistNotificationMessage: PersistNotificationMessage): RoomNotificationMessage { 10 | return RoomNotificationMessage().apply { 11 | actionId = persistNotificationMessage.actionId 12 | type = persistNotificationMessage.type 13 | timeStamp = persistNotificationMessage.timeStamp 14 | payload = persistNotificationMessage.payload 15 | } 16 | } 17 | 18 | override fun convert(persistNotificationMessages: List): List { 19 | return persistNotificationMessages.map { convert(it) } 20 | } 21 | 22 | } -------------------------------------------------------------------------------- /nutshellfcm/nuthsellfcm-framework/src/main/java/com/nutshellfcm/framework/sources/CacheSource.kt: -------------------------------------------------------------------------------- 1 | package com.nutshellfcm.framework.sources 2 | 3 | import com.nutshellfcm.framework.NutshellFCMContract 4 | import com.nutshellfcm.framework.model.NotificationMessage 5 | 6 | class CacheSource : NutshellFCMContract.Sources.CacheSource { 7 | 8 | private val map: MutableMap = mutableMapOf() 9 | 10 | override fun clearCache() { 11 | map.clear() 12 | } 13 | 14 | @Synchronized 15 | override fun removeFromCache(id: String) { 16 | map.remove(id) 17 | } 18 | 19 | override fun removeFromCache(ids: List) { 20 | ids.forEach { map.remove(it) } 21 | } 22 | 23 | @Synchronized 24 | override fun readCache(): List? { 25 | return map.values.toList() 26 | } 27 | 28 | @Synchronized 29 | override fun readCache(id: String): NotificationMessage? { 30 | return map[id] 31 | } 32 | 33 | @Synchronized 34 | override fun writeCache(notificationMessage: NotificationMessage) { 35 | map[notificationMessage.actionId] = notificationMessage 36 | } 37 | } -------------------------------------------------------------------------------- /nutshellfcm/nuthsellfcm-framework/src/main/java/com/nutshellfcm/framework/NotificationsRepository.kt: -------------------------------------------------------------------------------- 1 | package com.nutshellfcm.framework 2 | 3 | import com.nutshellfcm.framework.model.NotificationMessage 4 | import com.nutshellfcm.rxworkframework.works.ScheduledWork 5 | 6 | internal class NotificationsRepository(private val notificationsInteractor: NutshellFCMContract.Interactor) : 7 | NutshellFCMContract.Repository, 8 | NutshellFCMContract.NotificationMessageWriter { 9 | 10 | override fun write(notificationMessage: NotificationMessage): ScheduledWork = notificationsInteractor.writeNotification(notificationMessage) 11 | 12 | override fun read(): ScheduledWork> = notificationsInteractor.readNotifications() 13 | 14 | override fun read(id: String): ScheduledWork = notificationsInteractor.readNotification(id) 15 | 16 | override fun purge(): ScheduledWork = notificationsInteractor.purgeNotifications() 17 | 18 | override fun remove(id: String): ScheduledWork = notificationsInteractor.removeNotification(id) 19 | 20 | override fun remove(ids: List): ScheduledWork = notificationsInteractor.removeNotifications(ids) 21 | } -------------------------------------------------------------------------------- /nutshellfcm/build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | 3 | buildscript { 4 | ext.kotlin_version = '1.3.50' 5 | repositories { 6 | google() 7 | jcenter() 8 | 9 | } 10 | dependencies { 11 | classpath 'com.android.tools.build:gradle:3.4.2' 12 | classpath 'com.github.dcendents:android-maven-gradle-plugin:2.1' 13 | classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.8.4' 14 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 15 | classpath 'com.google.gms:google-services:4.2.0' 16 | // NOTE: Do not place your application dependencies here; they belong 17 | // in the individual module build.gradle files 18 | } 19 | } 20 | 21 | allprojects { 22 | repositories { 23 | google() 24 | jcenter() 25 | 26 | } 27 | } 28 | 29 | task clean(type: Delete) { 30 | delete rootProject.buildDir 31 | } 32 | 33 | task publishSdk { 34 | dependsOn 'clean' 35 | dependsOn 'rxworker:bintrayUpload' 36 | dependsOn 'nuthsellfcm-common:bintrayUpload' 37 | dependsOn 'nuthsellfcm-room-persistent-adapter:bintrayUpload' 38 | dependsOn 'nuthsellfcm-framework:bintrayUpload' 39 | } 40 | 41 | 42 | -------------------------------------------------------------------------------- /nutshellfcm/nuthsellfcm-room-persistent-adapter/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | apply plugin: 'kotlin-android' 3 | apply plugin: 'kotlin-kapt' 4 | apply plugin: 'kotlin-android-extensions' 5 | apply from: '../versions.gradle' 6 | 7 | android { 8 | compileSdkVersion COMPILE_SDK_VERSION 9 | 10 | defaultConfig { 11 | minSdkVersion MIN_SDK_VERSION 12 | targetSdkVersion TARGET_SDK_VERSION 13 | versionCode VERSION_CODE 14 | versionName VERSION_NAME 15 | } 16 | } 17 | 18 | apply from: rootProject.file('gradle/android_mvn_push.gradle') 19 | 20 | 21 | ext { 22 | room_version = '2.2.0-beta01' 23 | gson_version = '2.8.2' 24 | } 25 | 26 | dependencies { 27 | implementation fileTree(dir: 'libs', include: ['*.jar']) 28 | implementation project(':nuthsellfcm-common') 29 | 30 | implementation 'androidx.appcompat:appcompat:1.0.2' 31 | testImplementation 'junit:junit:4.12' 32 | androidTestImplementation 'androidx.test:runner:1.2.0' 33 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' 34 | 35 | implementation "androidx.room:room-runtime:$room_version" 36 | kapt "androidx.room:room-compiler:$room_version" 37 | implementation "androidx.room:room-ktx:$room_version" 38 | implementation "androidx.room:room-guava:$room_version" 39 | implementation "com.google.code.gson:gson:$gson_version" 40 | } 41 | -------------------------------------------------------------------------------- /nutshellfcm/nuthsellfcm-room-persistent-adapter/src/main/java/com/nutshellfcm/room_persistent_adapter/dao/RoomNotificationMessageDao.kt: -------------------------------------------------------------------------------- 1 | package com.nutshellfcm.room_persistent_adapter.dao 2 | 3 | import androidx.room.Dao 4 | import androidx.room.Insert 5 | import androidx.room.OnConflictStrategy 6 | import androidx.room.Query 7 | import com.nutshellfcm.room_persistent_adapter.RoomAdapterContract.Companion.ROOM_TABLE_NOTIFICATION_MESSAGE 8 | import com.nutshellfcm.room_persistent_adapter.model.RoomNotificationMessage 9 | 10 | @Dao 11 | interface RoomNotificationMessageDao { 12 | 13 | @Insert(onConflict = OnConflictStrategy.REPLACE) 14 | fun insert(persistNotificationMessage: RoomNotificationMessage) 15 | 16 | @Insert(onConflict = OnConflictStrategy.REPLACE) 17 | fun insert(persistNotificationMessages: List) 18 | 19 | @Query("SELECT * FROM $ROOM_TABLE_NOTIFICATION_MESSAGE") 20 | fun getAll(): List 21 | 22 | @Query("SELECT * FROM $ROOM_TABLE_NOTIFICATION_MESSAGE WHERE action_id == :id") 23 | fun getById(id: String): RoomNotificationMessage 24 | 25 | @Query("DELETE FROM $ROOM_TABLE_NOTIFICATION_MESSAGE") 26 | fun deleteAll() 27 | 28 | @Query("DELETE FROM $ROOM_TABLE_NOTIFICATION_MESSAGE WHERE action_id == :id") 29 | fun deleteByActionId(id: String) 30 | 31 | @Query("DELETE FROM $ROOM_TABLE_NOTIFICATION_MESSAGE WHERE action_id in (:list)") 32 | fun deleteGroup(list: List) 33 | } -------------------------------------------------------------------------------- /nutshellfcm/nuthsellfcm-room-persistent-adapter/src/main/java/com/nutshellfcm/room_persistent_adapter/RoomAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.nutshellfcm.room_persistent_adapter 2 | 3 | import com.nutshellfcm.common.PersistentAdapterContract.* 4 | import com.nutshellfcm.room_persistent_adapter.dao.RoomNotificationMessageDao 5 | 6 | internal class RoomAdapter( 7 | private val converter: RoomAdapterContract.Converter, 8 | private val roomNotificationMessageDao: RoomNotificationMessageDao 9 | ) : Adapter { 10 | 11 | override fun insert(notificationMessages: List) { 12 | roomNotificationMessageDao.insert(converter.convert(notificationMessages)) 13 | } 14 | 15 | override fun insert(notificationMessage: PersistNotificationMessage) { 16 | roomNotificationMessageDao.insert(converter.convert(notificationMessage)) 17 | } 18 | 19 | override fun get(actionId: String): PersistNotificationMessage { 20 | return roomNotificationMessageDao.getById(actionId) 21 | } 22 | 23 | override fun get(): List { 24 | return roomNotificationMessageDao.getAll() 25 | } 26 | 27 | override fun remove(actionId: String) { 28 | roomNotificationMessageDao.deleteByActionId(actionId) 29 | } 30 | 31 | override fun removeGroup(actionIds: List) { 32 | roomNotificationMessageDao.deleteGroup(actionIds) 33 | } 34 | 35 | override fun purge() { 36 | roomNotificationMessageDao.deleteAll() 37 | } 38 | } -------------------------------------------------------------------------------- /nutshellfcm/nuthsellfcm-room-persistent-adapter/src/main/java/com/nutshellfcm/room_persistent_adapter/model/RoomNotificationMessage.kt: -------------------------------------------------------------------------------- 1 | package com.nutshellfcm.room_persistent_adapter.model 2 | 3 | import androidx.room.ColumnInfo 4 | import androidx.room.Entity 5 | import androidx.room.Ignore 6 | import androidx.room.PrimaryKey 7 | import com.nutshellfcm.common.PersistentAdapterContract 8 | import com.nutshellfcm.room_persistent_adapter.RoomAdapterContract.Companion.KEY_ACTION_ID 9 | import com.nutshellfcm.room_persistent_adapter.RoomAdapterContract.Companion.KEY_PAYLOAD 10 | import com.nutshellfcm.room_persistent_adapter.RoomAdapterContract.Companion.KEY_TIMESTAMP 11 | import com.nutshellfcm.room_persistent_adapter.RoomAdapterContract.Companion.KEY_TYPE 12 | import com.nutshellfcm.room_persistent_adapter.RoomAdapterContract.Companion.ROOM_TABLE_NOTIFICATION_MESSAGE 13 | import java.util.* 14 | 15 | @Entity(tableName = ROOM_TABLE_NOTIFICATION_MESSAGE) 16 | class RoomNotificationMessage: PersistentAdapterContract.PersistNotificationMessage { 17 | 18 | @PrimaryKey(autoGenerate = false) 19 | @ColumnInfo(name = KEY_ACTION_ID) 20 | override var actionId: String = "" 21 | 22 | @ColumnInfo(name = KEY_TYPE) 23 | override var type: String = "notification" 24 | 25 | @ColumnInfo(name = KEY_PAYLOAD) 26 | override var payload: Map = mapOf(KEY_ACTION_ID to actionId) 27 | 28 | @ColumnInfo(name = KEY_TIMESTAMP) 29 | override var timeStamp: Date = Date() 30 | 31 | @Ignore 32 | val notificationId = actionId.hashCode() 33 | } -------------------------------------------------------------------------------- /nutshellfcm/samples/app/src/main/java/com/nutshellfcm/sample_app/ExampleActivity.kt: -------------------------------------------------------------------------------- 1 | package com.nutshellfcm.sample_app 2 | 3 | import android.os.Bundle 4 | import androidx.appcompat.app.AppCompatActivity 5 | import androidx.lifecycle.Observer 6 | import com.nutshellfcm.framework.NutshellLocalMessagesNotifier 7 | import com.nutshellfcm.framework.NutshellFCMContract 8 | import com.nutshellfcm.framework.NutshellNotificationHandler 9 | import com.nutshellfcm.framework.model.NotificationMessage 10 | import kotlinx.android.synthetic.main.example_activity_layout.* 11 | 12 | 13 | class ExampleActivity : AppCompatActivity() { 14 | 15 | override fun onCreate(savedInstanceState: Bundle?) { 16 | super.onCreate(savedInstanceState) 17 | setContentView(R.layout.example_activity_layout) 18 | 19 | val sb = StringBuilder() 20 | NutshellNotificationHandler 21 | .handledNotifications 22 | .observe(this, Observer { handledNotifications -> 23 | text_view_messages.text = sb.append("handled $handledNotifications").append("\n") 24 | }) 25 | } 26 | 27 | override fun onResume() { 28 | super.onResume() 29 | NutshellLocalMessagesNotifier.notifyDismiss("Action 4") 30 | } 31 | 32 | 33 | override fun onPause() { 34 | super.onPause() 35 | NutshellLocalMessagesNotifier.notify( 36 | NotificationMessage( 37 | "Action 4", 38 | NutshellFCMContract.NotificationType.FOREGROUND_NOTIFICATION 39 | ) 40 | ) 41 | } 42 | 43 | } -------------------------------------------------------------------------------- /nutshellfcm/samples/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | 8 | 15 | 16 | 17 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /nutshellfcm/gradle/java_mvn_push.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: "com.jfrog.bintray" 2 | apply plugin: 'maven-publish' 3 | apply plugin: 'maven' 4 | 5 | // custom tasks for creating source/javadoc jars 6 | task sourcesJar(type: Jar, dependsOn: classes) { 7 | classifier = 'sources' 8 | from sourceSets.main.allSource 9 | } 10 | 11 | task javadocJar(type: Jar, dependsOn: javadoc) { 12 | classifier = 'javadoc' 13 | from javadoc.destinationDir 14 | } 15 | 16 | // add javadoc/source jar tasks as artifacts 17 | artifacts { 18 | archives sourcesJar, javadocJar 19 | } 20 | 21 | publishing { 22 | publications { 23 | MyPublication(MavenPublication) { 24 | from components.java 25 | groupId = GROUP 26 | artifactId = POM_ARTIFACT_ID 27 | version = VERSION_NAME 28 | 29 | artifact(sourcesJar) 30 | artifact(javadocJar) 31 | } 32 | } 33 | } 34 | Properties properties = new Properties() 35 | properties.load(project.rootProject.file('local.properties').newDataInputStream()) 36 | 37 | bintray { 38 | user = properties.getProperty('bintray.user') 39 | key = properties.getProperty('bintray.apikey') 40 | publications = ["MyPublication"] 41 | publish = true 42 | pkg { 43 | repo = REP 44 | name = POM_ARTIFACT_ID 45 | userOrg = properties.getProperty('bintray.org') 46 | licenses = [POM_LICENCE] 47 | vcsUrl = GIT_URL 48 | version { 49 | name = VERSION_NAME 50 | desc = POM_DESCRIPTION 51 | released = new Date() 52 | } 53 | } 54 | } -------------------------------------------------------------------------------- /nutshellfcm/samples/app/google-services.json: -------------------------------------------------------------------------------- 1 | { 2 | "project_info": { 3 | "project_number": "345284412512", 4 | "firebase_url": "https://fir-notifications-215d9.firebaseio.com", 5 | "project_id": "fir-notifications-215d9", 6 | "storage_bucket": "fir-notifications-215d9.appspot.com" 7 | }, 8 | "client": [ 9 | { 10 | "client_info": { 11 | "mobilesdk_app_id": "1:345284412512:android:d897750f301179ce", 12 | "android_client_info": { 13 | "package_name": "com.example.firebasenotifications" 14 | } 15 | }, 16 | "oauth_client": [ 17 | { 18 | "client_id": "345284412512-o6fihpqbhgnfg5qn2k5no5v9t2q02jfi.apps.googleusercontent.com", 19 | "client_type": 1, 20 | "android_info": { 21 | "package_name": "com.example.firebasenotifications", 22 | "certificate_hash": "36827e8ea75e899e3ff8fe31806d77cdb6a15a87" 23 | } 24 | }, 25 | { 26 | "client_id": "345284412512-l5sv2gsrava7n6c9dkg3is9k4r9th61q.apps.googleusercontent.com", 27 | "client_type": 3 28 | } 29 | ], 30 | "api_key": [ 31 | { 32 | "current_key": "AIzaSyBfvMKmApCosmzRoxL7pMFALXw_3jyRFbw" 33 | } 34 | ], 35 | "services": { 36 | "appinvite_service": { 37 | "other_platform_oauth_client": [ 38 | { 39 | "client_id": "345284412512-l5sv2gsrava7n6c9dkg3is9k4r9th61q.apps.googleusercontent.com", 40 | "client_type": 3 41 | } 42 | ] 43 | } 44 | } 45 | } 46 | ], 47 | "configuration_version": "1" 48 | } -------------------------------------------------------------------------------- /nutshellfcm/samples/app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | apply plugin: 'kotlin-android' 3 | apply plugin: 'kotlin-android-extensions' 4 | apply plugin: 'com.google.gms.google-services' 5 | apply from: '../../versions.gradle' 6 | 7 | 8 | android { 9 | compileSdkVersion 28 10 | defaultConfig { 11 | applicationId "com.example.firebasenotifications" 12 | minSdkVersion MIN_SDK_VERSION 13 | targetSdkVersion TARGET_SDK_VERSION 14 | versionCode VERSION_CODE 15 | versionName VERSION_NAME 16 | multiDexEnabled true 17 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 18 | } 19 | buildTypes { 20 | debug { 21 | minifyEnabled false 22 | } 23 | 24 | release { 25 | minifyEnabled false 26 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 27 | } 28 | } 29 | compileOptions { 30 | sourceCompatibility = '1.8' 31 | targetCompatibility = '1.8' 32 | } 33 | buildToolsVersion = '28.0.3' 34 | } 35 | 36 | dependencies { 37 | implementation fileTree(dir: 'libs', include: ['*.jar']) 38 | implementation project(':nuthsellfcm-framework') 39 | 40 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 41 | implementation 'androidx.appcompat:appcompat:1.0.2' 42 | implementation 'androidx.core:core-ktx:1.0.2' 43 | testImplementation 'junit:junit:4.12' 44 | androidTestImplementation 'androidx.test:runner:1.2.0' 45 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' 46 | } 47 | -------------------------------------------------------------------------------- /nutshellfcm/nuthsellfcm-framework/src/main/java/com/nutshellfcm/framework/NutshellFCMEngine.kt: -------------------------------------------------------------------------------- 1 | package com.nutshellfcm.framework 2 | 3 | import android.app.Application 4 | import android.util.Log 5 | import com.nutshellfcm.framework.di.NutshellFirebaseComponents 6 | import com.nutshellfcm.framework.extensions.TAG 7 | import com.nutshellfcm.common.PersistentAdapterContract 8 | import com.google.firebase.iid.FirebaseInstanceId 9 | 10 | 11 | object NutshellFCMEngine { 12 | 13 | var isInitialized = false 14 | private set 15 | 16 | var firebaseToken = "" 17 | 18 | val firebaseInstanceId = FirebaseInstanceId.getInstance() 19 | 20 | fun start(application: Application, 21 | androidNotificationsFactory: NutshellFCMContract.AndroidNotificationsFactory = DefaultNotificationFactory(), 22 | casesProvider: NutshellFCMContract.CasesProvider = DefaultCaseProvider(), 23 | foregroundServicesBinder: NutshellFCMContract.ForegroundServicesBinder = DefaultForegroundServicesBinder(), 24 | persistentAdapter: PersistentAdapterContract.Adapter? = null 25 | ) { 26 | 27 | if (isInitialized) { 28 | return 29 | } 30 | 31 | isInitialized = NutshellFirebaseComponents.init( 32 | application, 33 | androidNotificationsFactory, 34 | casesProvider, 35 | foregroundServicesBinder, 36 | persistentAdapter 37 | ) 38 | 39 | firebaseInstanceId.instanceId.addOnSuccessListener { instanceIdResult -> 40 | firebaseToken = instanceIdResult.token 41 | Log.d(TAG, "FirebaseToken = $firebaseToken") 42 | } 43 | } 44 | } -------------------------------------------------------------------------------- /nutshellfcm/nuthsellfcm-framework/src/main/java/com/nutshellfcm/framework/NotificationCasesManager.kt: -------------------------------------------------------------------------------- 1 | package com.nutshellfcm.framework 2 | 3 | import androidx.lifecycle.MutableLiveData 4 | import com.nutshellfcm.framework.NutshellFCMContract.Case 5 | import com.nutshellfcm.framework.model.NotificationMessage 6 | 7 | internal class NotificationCasesManager(private val casesProvider: NutshellFCMContract.CasesProvider, 8 | private val handledNotificationsNotifier: NutshellFCMContract.HandledNotificationsNotifier 9 | ): 10 | NutshellFCMContract.CasesManager { 11 | 12 | private var iterator: Iterator? = null 13 | 14 | override fun init() { 15 | val cases = mutableListOf().apply { 16 | addAll(casesProvider.cases) 17 | add(CleanupCase()) 18 | } 19 | 20 | iterator = cases.iterator() 21 | } 22 | 23 | override fun hasRemainingCases(): Boolean = iterator?.hasNext() == true 24 | 25 | override fun handleNextCase(notifications: List): List { 26 | val nextCase = iterator?.next() 27 | return nextCase?.let { case -> 28 | 29 | if (case is CleanupCase) { 30 | return notifications //Consume all unhandled notifications 31 | } 32 | 33 | val caseMessages = notifications.filter { case.actionIds.contains(it.actionId) } 34 | if (caseMessages.isNotEmpty()) { 35 | case.consume(caseMessages) 36 | (handledNotificationsNotifier.handledNotifications as MutableLiveData).value = caseMessages 37 | } 38 | 39 | caseMessages 40 | } ?: notifications 41 | } 42 | } -------------------------------------------------------------------------------- /nutshellfcm/nuthsellfcm-framework/src/main/java/com/nutshellfcm/framework/AndroidNotificationBuilder.kt: -------------------------------------------------------------------------------- 1 | package com.nutshellfcm.framework 2 | 3 | import android.content.Intent 4 | import androidx.core.os.bundleOf 5 | import com.nutshellfcm.framework.NutshellFCMContract.Error.UnknownServiceBindActionIdThrowable 6 | import com.nutshellfcm.framework.application.contexts.ApplicationContext 7 | import com.nutshellfcm.framework.model.NotificationMessage 8 | 9 | 10 | internal class AndroidNotificationBuilder( 11 | private val applicationContext: ApplicationContext, 12 | private val foregroundServicesBinder: NutshellFCMContract.ForegroundServicesBinder, 13 | private val notificationManager: NutshellFCMContract.AndroidNotificationsManager, 14 | private val notificationsFactory: NutshellFCMContract.AndroidNotificationsFactory 15 | ) : NutshellFCMContract.AndroidNotificationBuilder { 16 | 17 | 18 | override fun build(notificationMessage: NotificationMessage) { 19 | val androidNotification = notificationsFactory.create(notificationMessage) 20 | notificationManager.show( 21 | applicationContext.get(), 22 | notificationMessage.notificationId, 23 | androidNotification 24 | ) 25 | } 26 | 27 | override fun buildForeground(notificationMessage: NotificationMessage) { 28 | val intent = Intent( 29 | applicationContext.get(), 30 | foregroundServicesBinder.bind(notificationMessage.actionId) ?: throw UnknownServiceBindActionIdThrowable( 31 | notificationMessage.actionId 32 | ) 33 | ) 34 | val extras = bundleOf(NutshellFCMContract.KEY_ACTION_ID to notificationMessage.actionId) 35 | intent.putExtras(extras) 36 | applicationContext.get().startService(intent) 37 | } 38 | } -------------------------------------------------------------------------------- /nutshellfcm/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=-Xmx1536m 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=true 20 | # Kotlin code style for this project: "official" or "obsolete": 21 | kotlin.code.style=official 22 | 23 | REP=dorrepo 24 | GROUP=com.dorbrauner.nutshellfcm 25 | 26 | POM_DESCRIPTION=NutshellFirebase allows you to quickly integrate firebase notifications into your project 27 | 28 | SITE_URL=https://github.com/Dor558/NutshellFirebase 29 | GIT_URL=https://github.com/Dor558/NutshellFirebase.git 30 | 31 | POM_LICENCE=The Apache Software License, Version 2.0 32 | POM_LICENCE_NAME=Apache License 33 | POM_LICENCE_URL=http://www.apache.org/licenses/LICENSE-2.0.txt 34 | POM_LICENCE_DIST=repo 35 | 36 | POM_DEVELOPER_ID=dor558 37 | POM_DEVELOPER_NAME=Dor Brauner 38 | POM_DEVELOPER_EMAIL=dor30179@gmail.com -------------------------------------------------------------------------------- /nutshellfcm/nuthsellfcm-framework/src/main/java/com/nutshellfcm/framework/NutshellFirebaseMessagingService.kt: -------------------------------------------------------------------------------- 1 | package com.nutshellfcm.framework 2 | 3 | import android.util.Log 4 | import com.nutshellfcm.framework.di.NutshellFirebaseComponents 5 | import com.nutshellfcm.framework.extensions.TAG 6 | import com.google.firebase.messaging.FirebaseMessagingService 7 | import com.google.firebase.messaging.RemoteMessage 8 | import com.nutshellfcm.framework.NutshellFCMContract.Companion.KEY_ACTION_ID 9 | import com.nutshellfcm.framework.NutshellFCMContract.Companion.KEY_TYPE 10 | import com.nutshellfcm.framework.NutshellFCMContract.NotificationType.Companion.NotificationType 11 | import com.nutshellfcm.framework.model.NotificationMessage 12 | import java.util.* 13 | 14 | 15 | open class NutshellFirebaseMessagingService : FirebaseMessagingService() { 16 | 17 | private val notificationsNotifier: NutshellFCMContract.NotificationsNotifier = NutshellFirebaseComponents.notificationNotifier 18 | 19 | override fun onMessageReceived(remoteMessage: RemoteMessage) { 20 | val data = remoteMessage.data 21 | val actionId = data[KEY_ACTION_ID] 22 | val type = NotificationType(data[KEY_TYPE] ?: "") 23 | 24 | // Check if message contains a valid data payload. 25 | if (data.isEmpty() || actionId.isNullOrEmpty()) { 26 | return 27 | } 28 | 29 | notificationsNotifier.notifyMessage( 30 | NotificationMessage( 31 | actionId, 32 | type, 33 | data, 34 | Date() 35 | ) 36 | ) 37 | } 38 | 39 | override fun onNewToken(firebaseToken: String) { 40 | super.onNewToken(firebaseToken) 41 | Log.d(TAG, "FirebaseToken = $firebaseToken") 42 | NutshellFCMEngine.firebaseToken = firebaseToken 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /nutshellfcm/rxworker/src/main/java/com/nutshellfcm/rxworkframework/works/FilterWork.kt: -------------------------------------------------------------------------------- 1 | package com.nutshellfcm.rxworkframework.works 2 | 3 | import com.nutshellfcm.rxworkframework.Cancelable 4 | import com.nutshellfcm.rxworkframework.Observer 5 | import com.nutshellfcm.rxworkframework.scheudlers.Scheduler 6 | import com.nutshellfcm.rxworkframework.works.runnables.WorkFilterRunnable 7 | 8 | 9 | internal class FilterWork(private val runnable: WorkFilterRunnable, 10 | private val parentWork: ParentWork 11 | ) : Work(runnable) { 12 | 13 | private val parentObserver = Observer( 14 | onResult = { 15 | runnable.input = it 16 | compositeCancellable 17 | .append(parentWork 18 | .observeOnScheduler.schedule(runnable)) 19 | }, 20 | 21 | onError = { throwable -> 22 | observers.forEach { it.onError.invoke(throwable) } 23 | } 24 | ) 25 | 26 | override fun onWorkCancelled() { 27 | parentWork.onWorkCancelled() 28 | } 29 | 30 | override fun subscribe(onResult: (T) -> Unit, onError: (Throwable) -> Unit): Cancelable { 31 | observers.add(Observer(onResult, onError)) 32 | parentWork.subscribe(parentObserver.onResult, parentObserver.onError) 33 | return compositeCancellable 34 | } 35 | 36 | override fun subscribeOn(scheduler: Scheduler): ScheduledWork { 37 | parentWork.subscribeOn(scheduler) 38 | return super.subscribeOn(scheduler) 39 | } 40 | 41 | override fun doOnSubscribe(onSubscribe: () -> Unit): ScheduledWork { 42 | parentWork.doOnSubscribe(onSubscribe) 43 | return this 44 | } 45 | 46 | override fun doOnCancelled(onCancelled: () -> Unit): ScheduledWork { 47 | parentWork.doOnCancelled(onCancelled) 48 | return this 49 | } 50 | 51 | } -------------------------------------------------------------------------------- /nutshellfcm/nuthsellfcm-framework/src/main/java/com/nutshellfcm/framework/PersistedMessageToNotificationMessageConverter.kt: -------------------------------------------------------------------------------- 1 | package com.nutshellfcm.framework 2 | 3 | import com.nutshellfcm.framework.NutshellFCMContract.NotificationType.Companion.NotificationType 4 | import com.nutshellfcm.framework.model.NotificationMessage 5 | import com.nutshellfcm.common.PersistentAdapterContract.* 6 | import java.util.* 7 | 8 | 9 | internal class PersistedMessageToNotificationMessageConverter 10 | : NutshellFCMContract.Sources.PersistedSource.PersistedMessageToNotificationMessageConverter 11 | { 12 | 13 | override fun convert(persistNotificationMessage: PersistNotificationMessage): NotificationMessage { 14 | return NotificationMessage( 15 | actionId = persistNotificationMessage.actionId, 16 | type = NotificationType(persistNotificationMessage.type), 17 | timeStamp = persistNotificationMessage.timeStamp, 18 | payload = persistNotificationMessage.payload 19 | ) 20 | } 21 | 22 | override fun convert(persistNotificationMessages: List): List { 23 | return persistNotificationMessages.map { convert(it) } 24 | } 25 | 26 | override fun convertBack(notificationMessage: NotificationMessage): PersistNotificationMessage { 27 | return object : PersistNotificationMessage { 28 | override val actionId: String = notificationMessage.actionId 29 | override val type: String = notificationMessage.type.value 30 | override val payload: Map = notificationMessage.payload 31 | override val timeStamp: Date = notificationMessage.timeStamp 32 | } 33 | } 34 | 35 | override fun convertBack(notificationMessages: List): List { 36 | return notificationMessages.map { convertBack(it) } 37 | } 38 | } -------------------------------------------------------------------------------- /nutshellfcm/nuthsellfcm-framework/src/main/java/com/nutshellfcm/framework/NotificationNotifier.kt: -------------------------------------------------------------------------------- 1 | package com.nutshellfcm.framework 2 | 3 | import android.app.Activity 4 | import android.app.Application 5 | import android.content.Intent 6 | import android.content.Intent.FLAG_RECEIVER_FOREGROUND 7 | import android.util.Log 8 | import com.nutshellfcm.framework.NutshellFCMContract.Companion.ACTION_BROADCAST_REGISTRATION_NOTIFICATION 9 | import com.nutshellfcm.framework.model.NotificationMessage 10 | import com.nutshellfcm.framework.extensions.TAG 11 | import com.nutshellfcm.framework.extensions.toBundle 12 | import com.nutshellfcm.rxworkframework.scheudlers.Schedulers 13 | 14 | class NotificationNotifier( 15 | private val application: Application, 16 | private val notificationMessageWriter: NutshellFCMContract.NotificationMessageWriter 17 | ) : NutshellFCMContract.NotificationsNotifier { 18 | 19 | override fun notifyMessage(notificationMessage: NotificationMessage) { 20 | notificationMessageWriter.write(notificationMessage) 21 | .subscribeOn(Schedulers.single) 22 | .subscribe( 23 | onResult = { 24 | sendBroadcast(notificationMessage) 25 | }, 26 | onError = { 27 | Log.e(TAG, "failed to write android notification", it) 28 | } 29 | ) 30 | } 31 | 32 | private fun sendBroadcast(notificationMessage: NotificationMessage) { 33 | val broadcast = Intent(ACTION_BROADCAST_REGISTRATION_NOTIFICATION) 34 | broadcast.flags = FLAG_RECEIVER_FOREGROUND; 35 | broadcast.putExtras(notificationMessage.payload.toBundle()) 36 | application.sendOrderedBroadcast( 37 | broadcast, 38 | null, 39 | null, 40 | NutshellHandler, 41 | Activity.RESULT_OK, 42 | null, 43 | broadcast.extras 44 | ) 45 | } 46 | 47 | } -------------------------------------------------------------------------------- /nutshellfcm/nuthsellfcm-framework/src/main/java/com/nutshellfcm/framework/application/ForegroundService.kt: -------------------------------------------------------------------------------- 1 | package com.nutshellfcm.framework.application 2 | 3 | import android.app.Service 4 | import android.content.Intent 5 | import android.os.Bundle 6 | import android.util.Log 7 | import com.nutshellfcm.framework.NutshellFCMContract 8 | import com.nutshellfcm.framework.di.NutshellFirebaseComponents 9 | import com.nutshellfcm.framework.extensions.TAG 10 | import com.nutshellfcm.framework.extensions.toBundle 11 | import com.nutshellfcm.rxworkframework.scheudlers.Schedulers 12 | 13 | 14 | abstract class ForegroundService : Service() { 15 | 16 | private val androidNotificationsFactory = NutshellFirebaseComponents.androidNotificationsFactory 17 | 18 | private val notificationsRepository = NutshellFirebaseComponents.notificationsRepository 19 | 20 | private val androidNotificationsManager = NutshellFirebaseComponents.notificationsManager 21 | 22 | override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { 23 | val actionId = intent?.extras?.getString(NutshellFCMContract.KEY_ACTION_ID) 24 | ?: throw NutshellFCMContract.Error.UnknownNotificationActionIdThrowable(null) 25 | 26 | notificationsRepository.read(actionId) 27 | .subscribeOn(Schedulers.unbounded) 28 | .subscribe( 29 | onResult = { notificationMessage -> 30 | val androidNotification = androidNotificationsFactory.create(notificationMessage) 31 | androidNotificationsManager.showForeground(this, notificationMessage.notificationId, androidNotification) 32 | onForegroundStarted(notificationMessage.payload.toBundle()) 33 | }, 34 | 35 | onError = { 36 | Log.e(TAG, "failed to start foreground service $TAG ") 37 | } 38 | ) 39 | 40 | return super.onStartCommand(intent, flags, startId) 41 | } 42 | 43 | abstract fun onForegroundStarted(payload: Bundle) 44 | 45 | } -------------------------------------------------------------------------------- /nutshellfcm/samples/app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 12 | 13 | 19 | 22 | 25 | 26 | 27 | 28 | 34 | 35 | -------------------------------------------------------------------------------- /nutshellfcm/rxworker/src/main/java/com/nutshellfcm/rxworkframework/scheudlers/FuturesScheduler.kt: -------------------------------------------------------------------------------- 1 | package com.nutshellfcm.rxworkframework.scheudlers 2 | 3 | import com.nutshellfcm.rxworkframework.Cancelable 4 | import com.nutshellfcm.rxworkframework.RxWorkerThreadFactory 5 | import com.nutshellfcm.rxworkframework.tasks.FutureTask 6 | import java.util.concurrent.* 7 | import java.util.concurrent.atomic.AtomicReference 8 | 9 | 10 | class FuturesScheduler(private val type: Type) : Scheduler { 11 | 12 | private val executor = AtomicReference() 13 | 14 | companion object { 15 | 16 | enum class Type { 17 | BOUNDED_CACHE, 18 | UNBOUNDED_CACHE, 19 | FIXED_SINGLE 20 | } 21 | } 22 | 23 | @Synchronized 24 | override fun start() { 25 | var next: ExecutorService? = null 26 | val current = executor.get() 27 | if (current == null) { 28 | next = executorInstance() 29 | } 30 | 31 | if (executor.compareAndSet(current, next)) { 32 | return 33 | } 34 | } 35 | 36 | private fun executorInstance(): ExecutorService { 37 | return when (type) { 38 | Companion.Type.BOUNDED_CACHE -> Executors.newFixedThreadPool(5, RxWorkerThreadFactory("Bounded")) 39 | Companion.Type.UNBOUNDED_CACHE -> Executors.newCachedThreadPool(RxWorkerThreadFactory("Unbound")) 40 | Companion.Type.FIXED_SINGLE -> Executors.newFixedThreadPool(1, RxWorkerThreadFactory("Single")) 41 | } 42 | } 43 | 44 | 45 | @Synchronized 46 | override fun shutdown() { 47 | val current: ExecutorService? = executor.get() 48 | runCatching { 49 | while (current?.awaitTermination(800, TimeUnit.MILLISECONDS) == false) { 50 | current.shutdownNow() 51 | } 52 | 53 | }.getOrElse { 54 | current?.shutdownNow() 55 | } 56 | } 57 | 58 | @Synchronized 59 | override fun schedule(runnable: Runnable): Cancelable { 60 | val futureWork = FutureTask(runnable) 61 | val future = executor.get().submit(futureWork) 62 | futureWork.future = future 63 | return futureWork 64 | } 65 | } -------------------------------------------------------------------------------- /nutshellfcm/rxworker/src/main/java/com/nutshellfcm/rxworkframework/works/BlockingWork.kt: -------------------------------------------------------------------------------- 1 | package com.nutshellfcm.rxworkframework.works 2 | 3 | import com.nutshellfcm.rxworkframework.Observer 4 | import com.nutshellfcm.rxworkframework.scheudlers.Scheduler 5 | import com.nutshellfcm.rxworkframework.works.runnables.BlockingRunnable 6 | import java.lang.Exception 7 | import java.lang.RuntimeException 8 | import java.util.concurrent.TimeUnit 9 | 10 | 11 | internal class BlockingWork(private val blockingRunnable: BlockingRunnable, 12 | private val parentWork: ParentWork) : Work(blockingRunnable) { 13 | 14 | override val compositeCancellable: CompositeCancellable = parentWork.compositeCancellable 15 | 16 | private val parentObserver = Observer( 17 | onResult = { 18 | compositeCancellable.append(observeOnScheduler.schedule(blockingRunnable)) 19 | }, 20 | 21 | onError = { throwable -> 22 | observers.forEach { it.onError.invoke(throwable) } 23 | } 24 | ) 25 | 26 | init { 27 | observeOnScheduler = parentWork.observeOnScheduler 28 | subscribeOnScheduler = parentWork.subscribeOnScheduler 29 | } 30 | 31 | @Throws(Exception::class) 32 | override fun blockingGet(): T? { 33 | parentWork.subscribe(parentObserver.onResult, parentObserver.onError) 34 | runCatching { 35 | blockingRunnable.await(800, TimeUnit.MILLISECONDS) 36 | }.getOrElse { 37 | Thread.currentThread().interrupt() 38 | } 39 | 40 | if (blockingRunnable.error != null) { 41 | throw RuntimeException("Failed to block and get value ${blockingRunnable.error}") 42 | } 43 | 44 | return blockingRunnable.result 45 | } 46 | 47 | override fun subscribeOn(scheduler: Scheduler): ScheduledWork { 48 | parentWork.subscribeOn(scheduler) 49 | return super.subscribeOn(scheduler) 50 | } 51 | 52 | override fun doOnSubscribe(onSubscribe: () -> Unit): ScheduledWork { 53 | parentWork.doOnSubscribe(onSubscribe) 54 | return this 55 | } 56 | 57 | override fun doOnCancelled(onCancelled: () -> Unit): ScheduledWork { 58 | parentWork.doOnCancelled(onCancelled) 59 | return this 60 | } 61 | } -------------------------------------------------------------------------------- /nutshellfcm/nuthsellfcm-framework/src/main/java/com/nutshellfcm/framework/NotificationsMessageRouter.kt: -------------------------------------------------------------------------------- 1 | package com.nutshellfcm.framework 2 | 3 | import android.content.Intent 4 | import android.util.Log 5 | import androidx.core.app.JobIntentService 6 | import com.nutshellfcm.framework.application.contexts.ApplicationContext 7 | import com.nutshellfcm.framework.extensions.TAG 8 | import com.nutshellfcm.rxworkframework.scheudlers.Schedulers 9 | 10 | internal class NotificationsMessageRouter( 11 | private val applicationContext: ApplicationContext, 12 | private val notificationsRepository: NutshellFCMContract.Repository, 13 | private val androidNotificationBuilder: NutshellFCMContract.AndroidNotificationBuilder 14 | ) : NutshellFCMContract.NotificationsMessageRouter { 15 | 16 | override fun onRouteNotificationsMessage(intent: Intent) { 17 | val actionId = intent.extras?.getString(NutshellFCMContract.KEY_ACTION_ID) 18 | ?: throw NutshellFCMContract.Error.UnknownNotificationActionIdThrowable(null) 19 | 20 | notificationsRepository 21 | .read(actionId) 22 | .subscribeOn(Schedulers.bounded) 23 | .subscribe( 24 | onResult = { notificationMessage -> 25 | when (notificationMessage.type) { 26 | 27 | NutshellFCMContract.NotificationType.NOTIFICATION -> { 28 | androidNotificationBuilder.build(notificationMessage) 29 | } 30 | 31 | NutshellFCMContract.NotificationType.SILENT_NOTIFICATION -> { 32 | JobIntentService.enqueueWork( 33 | applicationContext.get(), 34 | SilentNotificationHandleService::class.java, 35 | SILENT_NOTIFICATION_SERVICE_ID, 36 | intent 37 | ) 38 | } 39 | 40 | NutshellFCMContract.NotificationType.FOREGROUND_NOTIFICATION -> { 41 | androidNotificationBuilder.buildForeground(notificationMessage) 42 | } 43 | } 44 | }, 45 | 46 | onError = { 47 | Log.e(TAG, "Failed to read notification message", it) 48 | } 49 | ) 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /nutshellfcm/gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /nutshellfcm/rxworker/src/main/java/com/nutshellfcm/rxworkframework/works/FlatMapWork.kt: -------------------------------------------------------------------------------- 1 | package com.nutshellfcm.rxworkframework.works 2 | 3 | import com.nutshellfcm.rxworkframework.Cancelable 4 | import com.nutshellfcm.rxworkframework.Observer 5 | import com.nutshellfcm.rxworkframework.scheudlers.Scheduler 6 | import com.nutshellfcm.rxworkframework.works.runnables.WorkMapRunnable 7 | 8 | 9 | internal open class FlatMapWork( 10 | mapRunnable: WorkMapRunnable, 11 | private val parentWork: ParentWork 12 | ) : Work(mapRunnable), ParentWork { 13 | 14 | override val compositeCancellable: CompositeCancellable = parentWork.compositeCancellable 15 | 16 | private val parentObserver = Observer( 17 | onResult = { 18 | mapRunnable.input = it 19 | compositeCancellable 20 | .append(parentWork 21 | .observeOnScheduler.schedule(mapRunnable)) 22 | }, 23 | 24 | onError = { throwable -> 25 | observers.forEach { it.onError.invoke(throwable) } 26 | } 27 | ) 28 | 29 | init { 30 | subscribeOnScheduler = parentWork.subscribeOnScheduler 31 | observeOnScheduler = parentWork.observeOnScheduler 32 | } 33 | 34 | override fun onResult(result: S) { 35 | flatMapWork(result) 36 | } 37 | 38 | @Suppress("UNCHECKED_CAST") 39 | private fun flatMapWork(mappedWork: S) { 40 | (mappedWork as Work).subscribeOn(parentWork.subscribeOnScheduler) 41 | (mappedWork as Work).observeOn(observeOnScheduler) 42 | compositeCancellable.append((mappedWork as Work).subscribeOnObservers(observers, 43 | parentWork.observeOnScheduler)) 44 | } 45 | 46 | override fun onWorkCancelled() { 47 | parentWork.onWorkCancelled() 48 | } 49 | 50 | override fun subscribe(onResult: (S) -> Unit, onError: (Throwable) -> Unit): Cancelable { 51 | observers.add(Observer(onResult, onError)) 52 | parentWork.subscribe(parentObserver.onResult, parentObserver.onError) 53 | return compositeCancellable 54 | } 55 | 56 | override fun subscribeOn(scheduler: Scheduler): ScheduledWork { 57 | parentWork.subscribeOn(scheduler) 58 | return super.subscribeOn(scheduler) 59 | } 60 | 61 | override fun doOnSubscribe(onSubscribe: () -> Unit): ScheduledWork { 62 | parentWork.doOnSubscribe(onSubscribe) 63 | return this 64 | } 65 | 66 | override fun doOnCancelled(onCancelled: () -> Unit): ScheduledWork { 67 | parentWork.doOnCancelled(onCancelled) 68 | return this 69 | } 70 | } -------------------------------------------------------------------------------- /nutshellfcm/nuthsellfcm-framework/src/main/java/com/nutshellfcm/framework/sources/PersistedSource.kt: -------------------------------------------------------------------------------- 1 | package com.nutshellfcm.framework.sources 2 | 3 | import com.nutshellfcm.framework.NutshellFCMContract 4 | import com.nutshellfcm.framework.model.NotificationMessage 5 | import com.nutshellfcm.common.PersistentAdapterContract 6 | import com.nutshellfcm.rxworkframework.RxWorkCreator 7 | import com.nutshellfcm.rxworkframework.works.ScheduledWork 8 | 9 | 10 | internal class PersistedSource( 11 | private val persistentAdapter: PersistentAdapterContract.Adapter, 12 | private val converter: NutshellFCMContract.Sources.PersistedSource.PersistedMessageToNotificationMessageConverter 13 | ) : NutshellFCMContract.Sources.PersistedSource 14 | { 15 | 16 | override fun purge(): ScheduledWork { 17 | return RxWorkCreator.create { emitter -> 18 | runCatching { 19 | emitter.onResult(persistentAdapter.purge()) 20 | }.getOrElse { 21 | emitter.onError(it) 22 | } 23 | } 24 | } 25 | 26 | override fun remove(id: String): ScheduledWork { 27 | return RxWorkCreator.create { emitter -> 28 | runCatching { 29 | emitter.onResult(persistentAdapter.remove(id)) 30 | }.getOrElse { 31 | emitter.onError(it) 32 | } 33 | } 34 | } 35 | 36 | override fun remove(ids: List): ScheduledWork { 37 | return RxWorkCreator.create { emitter -> 38 | runCatching { 39 | emitter.onResult(persistentAdapter.removeGroup(ids)) 40 | }.getOrElse { 41 | emitter.onError(it) 42 | } 43 | } 44 | } 45 | 46 | override fun read(): ScheduledWork> { 47 | return RxWorkCreator.create { emitter -> 48 | runCatching { 49 | emitter.onResult(converter.convert(persistentAdapter.get())) 50 | }.getOrElse { 51 | emitter.onError(it) 52 | } 53 | } 54 | } 55 | 56 | override fun read(id: String): ScheduledWork{ 57 | return RxWorkCreator.create { emitter -> 58 | runCatching { 59 | emitter.onResult(converter.convert(persistentAdapter.get(id))) 60 | }.getOrElse { 61 | emitter.onError(it) 62 | } 63 | } 64 | } 65 | 66 | override fun write(notificationMessage: NotificationMessage): ScheduledWork { 67 | return RxWorkCreator.create { emitter -> 68 | runCatching { 69 | persistentAdapter.insert(converter.convertBack(notificationMessage)) 70 | emitter.onResult(Unit) 71 | }.getOrElse { 72 | emitter.onError(it) 73 | } 74 | } 75 | } 76 | } -------------------------------------------------------------------------------- /nutshellfcm/gradle/android_mvn_push.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.github.dcendents.android-maven' 2 | apply plugin: "com.jfrog.bintray" 3 | 4 | version = VERSION_NAME 5 | group= GROUP 6 | 7 | install { 8 | repositories.mavenInstaller { 9 | // This generates POM.xml with proper parameters 10 | pom { 11 | project { 12 | packaging POM_PACKAGING 13 | 14 | // Add your description here 15 | name POM_ARTIFACT_ID 16 | description = POM_DESCRIPTION 17 | url SITE_URL 18 | 19 | // Set your license 20 | licenses { 21 | license { 22 | name POM_LICENCE_NAME 23 | url POM_LICENCE_URL 24 | } 25 | } 26 | developers { 27 | developer { 28 | id POM_DEVELOPER_ID 29 | name POM_DEVELOPER_NAME 30 | email POM_DEVELOPER_EMAIL 31 | } 32 | } 33 | scm { 34 | connection GIT_URL 35 | developerConnection GIT_URL 36 | url SITE_URL 37 | } 38 | } 39 | 40 | whenConfigured { pom -> 41 | pom.dependencies.forEach { dep -> 42 | if (dep.getVersion() == "unspecified" && dep.groupId == rootProject.name) { 43 | dep.setGroupId(GROUP) 44 | dep.setVersion(VERSION_NAME) 45 | } 46 | } 47 | } 48 | } 49 | } 50 | } 51 | 52 | task sourcesJar(type: Jar) { 53 | from android.sourceSets.main.java.srcDirs 54 | classifier = 'sources' 55 | } 56 | 57 | task javadoc(type: Javadoc) { 58 | failOnError = false 59 | source = android.sourceSets.main.java.srcDirs 60 | classpath += project.files(android.getBootClasspath().join(File.pathSeparator)) 61 | } 62 | 63 | task javadocJar(type: Jar, dependsOn: javadoc) { 64 | classifier = 'javadoc' 65 | from javadoc.destinationDir 66 | } 67 | artifacts { 68 | archives javadocJar 69 | archives sourcesJar 70 | } 71 | 72 | Properties properties = new Properties() 73 | properties.load(project.rootProject.file('local.properties').newDataInputStream()) 74 | 75 | // https://github.com/bintray/gradle-bintray-plugin 76 | bintray { 77 | user = properties.getProperty("bintray.user") 78 | key = properties.getProperty("bintray.apikey") 79 | 80 | configurations = ['archives'] 81 | pkg { 82 | repo = REP 83 | name = POM_ARTIFACT_ID 84 | userOrg = properties.getProperty("bintray.org") 85 | websiteUrl = SITE_URL 86 | vcsUrl = GIT_URL 87 | licenses = [POM_LICENCE] 88 | publish = true 89 | } 90 | } -------------------------------------------------------------------------------- /nutshellfcm/nuthsellfcm-framework/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | apply plugin: 'kotlin-android' 3 | apply plugin: 'kotlin-kapt' 4 | apply plugin: 'kotlin-android-extensions' 5 | apply plugin: 'com.github.dcendents.android-maven' 6 | apply plugin: 'com.jfrog.bintray' 7 | apply from: '../versions.gradle' 8 | 9 | android { 10 | compileSdkVersion COMPILE_SDK_VERSION 11 | buildToolsVersion = '28.0.3' 12 | 13 | defaultConfig { 14 | minSdkVersion MIN_SDK_VERSION 15 | targetSdkVersion TARGET_SDK_VERSION 16 | versionCode VERSION_CODE 17 | versionName VERSION_NAME 18 | } 19 | 20 | buildTypes { 21 | release { 22 | minifyEnabled false 23 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 24 | } 25 | } 26 | compileOptions { 27 | sourceCompatibility = '1.8' 28 | targetCompatibility = '1.8' 29 | } 30 | } 31 | 32 | apply from: rootProject.file('gradle/android_mvn_push.gradle') 33 | 34 | afterEvaluate { 35 | javadoc.classpath += project.android.libraryVariants.toList().first().javaCompile.classpath 36 | } 37 | 38 | buildscript { 39 | repositories { 40 | jcenter() 41 | mavenCentral() 42 | } 43 | 44 | dependencies { 45 | classpath 'com.github.dcendents:android-maven-gradle-plugin:1.4.1' 46 | classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.7.3' 47 | } 48 | } 49 | 50 | ext { 51 | appcompat_version = '1.0.2' 52 | lifecycle_extenstions_version = '2.0.0' 53 | lifecycle_runtime_version = '2.0.0' 54 | junit_version = '4.12' 55 | test_runner_version = '1.2.0' 56 | espresso_version = '3.2.0' 57 | android_ktx_version = '1.0.2' 58 | firebase_messaging = '20.0.0' 59 | firebase_config = '19.0.0' 60 | firebase_crash = '16.2.1' 61 | firebase_core = '17.1.0' 62 | } 63 | 64 | 65 | dependencies { 66 | implementation fileTree(dir: 'libs', include: ['*.jar']) 67 | implementation project(':rxworker') 68 | api project(':nuthsellfcm-common') 69 | 70 | implementation "androidx.appcompat:appcompat:$appcompat_version" 71 | testImplementation "junit:junit:$junit_version" 72 | androidTestImplementation "androidx.test:runner:$test_runner_version" 73 | androidTestImplementation "androidx.test.espresso:espresso-core:$espresso_version" 74 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 75 | 76 | implementation "androidx.lifecycle:lifecycle-extensions:$lifecycle_extenstions_version" 77 | implementation "androidx.lifecycle:lifecycle-runtime:$lifecycle_runtime_version" 78 | kapt "androidx.lifecycle:lifecycle-compiler:$lifecycle_runtime_version" 79 | 80 | implementation "androidx.core:core-ktx:$android_ktx_version" 81 | api "com.google.firebase:firebase-messaging:$firebase_messaging" 82 | api "com.google.firebase:firebase-config:$firebase_config" 83 | api "com.google.firebase:firebase-crash:$firebase_crash" 84 | api "com.google.firebase:firebase-core:$firebase_core" 85 | } -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, sex characteristics, gender identity and expression, 9 | level of experience, education, socio-economic status, nationality, personal 10 | appearance, race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at . All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | 75 | For answers to common questions about this code of conduct, see 76 | https://www.contributor-covenant.org/faq 77 | -------------------------------------------------------------------------------- /nutshellfcm/nuthsellfcm-framework/src/main/java/com/nutshellfcm/framework/NotificationsReceiversRegisterer.kt: -------------------------------------------------------------------------------- 1 | package com.nutshellfcm.framework 2 | 3 | import android.app.Activity 4 | import android.app.Application 5 | import android.content.BroadcastReceiver 6 | import android.content.Context 7 | import android.content.Intent 8 | import android.content.IntentFilter 9 | import android.util.Log 10 | import com.nutshellfcm.framework.NutshellFCMContract.* 11 | import com.nutshellfcm.framework.NutshellFCMContract.NotificationReceiversRegisterer.Companion.BACKGROUND_PRIORITY 12 | import com.nutshellfcm.framework.NutshellFCMContract.NotificationReceiversRegisterer.Companion.FOREGROUND_PRIORITY 13 | import com.nutshellfcm.framework.application.ActivityLifecycleCallback 14 | import com.nutshellfcm.framework.application.ApplicationLifeCycleWrapper 15 | import com.nutshellfcm.framework.di.NutshellFirebaseComponents 16 | import com.nutshellfcm.framework.extensions.TAG 17 | import com.nutshellfcm.rxworkframework.scheudlers.Schedulers 18 | 19 | internal class NotificationsReceiversRegisterer( 20 | applicationLifeCycleWrapper: ApplicationLifeCycleWrapper, 21 | application: Application, 22 | private val notificationsConsumer: NutshellFCMContract.NotificationsConsumer 23 | ): NotificationReceiversRegisterer { 24 | 25 | private val notificationsRepository = NutshellFirebaseComponents.notificationsRepository 26 | 27 | private val applicationReceiver = NotificationsRouterBroadcastReceiver() 28 | 29 | private val activitiesReceiver = object : BroadcastReceiver() { 30 | 31 | override fun onReceive(context: Context, intent: Intent) { 32 | val actionId = intent.extras?.getString(NutshellFCMContract.KEY_ACTION_ID) 33 | ?: throw Error.UnknownNotificationActionIdThrowable(null) 34 | 35 | runCatching { 36 | val notificationMessage = notificationsRepository.read(actionId) 37 | .subscribeOn(Schedulers.single) 38 | .blockingGet() 39 | 40 | Log.i(TAG, "Recevied notification message $notificationMessage") 41 | when (notificationMessage?.type) { 42 | NotificationType.NOTIFICATION -> { 43 | notificationsConsumer.consumeNotificationsMessages() 44 | abortBroadcast() 45 | } 46 | else -> { 47 | //Ignore and forward broadcast to the messages router 48 | } 49 | } 50 | }.getOrElse { 51 | Log.e(TAG, "failed to consume notification with action id $actionId") 52 | } 53 | } 54 | } 55 | 56 | private val activitiesLifeCycleCallback = object : ActivityLifecycleCallback { 57 | 58 | override fun onActivityStarted(activity: Activity) { 59 | val intentFilter = IntentFilter(NutshellFCMContract.ACTION_BROADCAST_REGISTRATION_NOTIFICATION) 60 | intentFilter.priority = FOREGROUND_PRIORITY 61 | activity.registerReceiver(activitiesReceiver, intentFilter) 62 | } 63 | 64 | override fun onActivityResumed(activity: Activity) { 65 | notificationsConsumer.consumeNotificationsMessages() 66 | } 67 | 68 | override fun onActivityStopped(activity: Activity) { 69 | activity.unregisterReceiver(activitiesReceiver) 70 | } 71 | } 72 | 73 | init { 74 | val intentFilter = IntentFilter(NutshellFCMContract.ACTION_BROADCAST_REGISTRATION_NOTIFICATION) 75 | intentFilter.priority = BACKGROUND_PRIORITY 76 | application.registerReceiver(applicationReceiver, intentFilter) 77 | applicationLifeCycleWrapper.registerLifecycle(activitiesLifeCycleCallback) 78 | } 79 | 80 | } -------------------------------------------------------------------------------- /nutshellfcm/nuthsellfcm-framework/src/main/java/com/nutshellfcm/framework/NotificationsConsumer.kt: -------------------------------------------------------------------------------- 1 | package com.nutshellfcm.framework 2 | 3 | import android.app.NotificationManager 4 | import android.content.Intent 5 | import android.util.Log 6 | import com.nutshellfcm.framework.NutshellFCMContract.* 7 | import com.nutshellfcm.framework.NutshellFCMContract.Error.* 8 | import com.nutshellfcm.framework.application.contexts.ApplicationContext 9 | import com.nutshellfcm.framework.model.NotificationMessage 10 | import com.nutshellfcm.framework.extensions.TAG 11 | import com.nutshellfcm.rxworkframework.scheudlers.Schedulers 12 | 13 | internal class NotificationsConsumer( 14 | private val applicationContext: ApplicationContext, 15 | private val systemNotificationManager: NotificationManager, 16 | private val notificationsRepository: Repository, 17 | private val foregroundServicesBinder: ForegroundServicesBinder, 18 | private val casesManager: CasesManager 19 | ) : NutshellFCMContract.NotificationsConsumer { 20 | 21 | 22 | override fun consume(actionId: String) { 23 | notificationsRepository.read(actionId) 24 | .doOnSubscribe { 25 | casesManager.init() 26 | } 27 | .observeOn(Schedulers.main) 28 | .map { 29 | consumeRecursive(listOf(it)) 30 | } 31 | .observeOn(Schedulers.unbounded) 32 | .flatMap { 33 | notificationsRepository.remove(actionId) 34 | } 35 | .subscribeOn(Schedulers.unbounded) 36 | .observeOn(Schedulers.main) 37 | .subscribe( 38 | onResult = { 39 | Log.d(TAG, "notification of id $actionId consumed") 40 | }, 41 | onError = { 42 | Log.e(TAG, "error consuming notification of $actionId", it) 43 | } 44 | ) 45 | } 46 | 47 | override fun consumeNotificationsMessages() { 48 | notificationsRepository.read() 49 | .doOnSubscribe { 50 | casesManager.init() 51 | } 52 | .map { notificationMessages -> 53 | notificationMessages.filter { it.type == NotificationType.NOTIFICATION } 54 | } 55 | .observeOn(Schedulers.main) 56 | .map { notificationMessages -> 57 | consumeRecursive(notificationMessages) 58 | notificationMessages.map { it.actionId } 59 | } 60 | .observeOn(Schedulers.unbounded) 61 | .flatMap { notificationMessages -> 62 | notificationsRepository.remove(notificationMessages) 63 | } 64 | .subscribeOn(Schedulers.unbounded) 65 | .observeOn(Schedulers.main) 66 | .subscribe( 67 | onResult = { 68 | Log.d(TAG, "all notifications consumed") 69 | }, 70 | onError = { 71 | Log.e(TAG, "error consuming all notifications", it) 72 | } 73 | ) 74 | } 75 | 76 | private fun consumeRecursive(notificationMessages: List) { 77 | if (!casesManager.hasRemainingCases() || notificationMessages.isEmpty()) { 78 | return 79 | } 80 | 81 | val consumedNotifications = casesManager.handleNextCase(notificationMessages) 82 | consumedNotifications.forEach { notificationMessage -> 83 | when (notificationMessage.type) { 84 | NotificationType.FOREGROUND_NOTIFICATION -> { 85 | applicationContext.get().stopService(Intent( 86 | applicationContext.get(), 87 | foregroundServicesBinder.bind(notificationMessage.actionId) 88 | ?: throw UnknownServiceBindActionIdThrowable(notificationMessage.actionId) 89 | )) 90 | } 91 | 92 | else -> { 93 | systemNotificationManager.cancel(notificationMessage.notificationId) 94 | } 95 | } 96 | } 97 | 98 | consumeRecursive(notificationMessages - consumedNotifications) 99 | } 100 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ![alt tag](https://i.imgur.com/8WhCFvw.jpg) ![alt tag](https://i.imgur.com/1dVlVDC.png) ![alt tag](https://i.imgur.com/UNTyNoE.png) 2 | 3 | NutshellFirebase allows you to quickly integrate firebase notifications into your project, saving you a lot of boilerplate code that is usually required in order to have them run. 4 | NutshellFirebase allows you to focus on what to do with your notification data instead of how and when to retrieve the payload. moreover, it suggests a solid solution to how an activity should process the notification and whether or not notification should be displayed to the user. 5 | 6 | *Please note that NutshellFirebase intent to work with data notification only. 7 | 8 | # Highlights 9 | - Allow to have custom android notifications in a nutshell. 10 | - Allow to use custom silent notifications. 11 | - Allow to start foreground services via push. 12 | 13 | # How-to 14 | 1) Add the depnedency to you project 15 | ### Gradle 16 | ``` 17 | compile 'com.dorbrauner.nutshellfirebase:framework:0.1' 18 | ``` 19 | 20 | 2) Use the firebase wizard to integrate with firebase and generate the firebase services json file 21 | -Tools-> Firebase -> Cloud messaging -> set up Cloud Messgeing -> Connect to Firebase && Add FCM to your App 22 | ![alt tag](https://i.imgur.com/yMWh5zB.png) 23 | ![alt tag](https://i.imgur.com/ze0gq47.png) 24 | ![alt tag](https://i.imgur.com/pPBTYmQ.png) 25 | 26 | 3) Add the below lines to your application onCreate method 27 | ``` 28 | NutshellFirebaseEngine.start(this, 29 | ExampleNotificationFactory(this), 30 | ExampleCaseProvider(), 31 | ExampleForegroundServicesBinder()) 32 | ``` 33 | 34 | 4) Add the below lines to your manifest 35 | ``` 36 | 37 | 38 | 39 | 40 | 41 | 42 | ``` 43 | 44 | 5) Create your android notification factory 45 | ``` 46 | class ExampleNotificationFactory(private val application: Application) : NutshellFirebaseContract.AndroidNotificationsFactory { 47 | 48 | override fun create(notificationMessage: NotificationMessage): NutshellFirebaseContract.AndroidNotification { 49 | return when (notificationMessage.actionId) { 50 | "Some Action Id" -> { 51 | NutshellFirebaseContract.AndroidNotification( 52 | [Fill your custom notification] 53 | ) 54 | } 55 | 56 | else -> { 57 | throw Throwable("Unsupported notification id") 58 | } 59 | } 60 | } 61 | } 62 | ``` 63 | 6) Optional, Create your cases provider to handle app notifications use cases 64 | ``` 65 | class ExampleCaseProvider : NutshellFirebaseContract.NotificationsHandling.CasesProvider { 66 | 67 | override val cases: List = 68 | listOf(Action1ExampleCase(), Action2Action3ExampleCase(), Action4ExampleCase()) 69 | } 70 | ``` 71 | 7) Optional, Create your foreground services binder to start foreground services via firebase push. 72 | ``` 73 | class ExampleForegroundServicesBinder : NotificationsFrameworkContract.ForegroundServicesBinder { 74 | 75 | override fun bind(actionId: String): Class<*>? { 76 | return when (actionId) { 77 | "Some action id to start foreground service" -> ExampleForegroundService::class.java 78 | else -> null 79 | } 80 | } 81 | } 82 | ``` 83 | 8) Optional, You can extend from android notification builder to build your own notifications style. 84 | 85 | ## Usage 86 | 87 | Send notification remotely: 88 | ``` 89 | { 90 | "to": "[Firebase token]", 91 | "data":{ 92 | "action_id":"[Notification action id as it is defined in your android notifications factory]", 93 | "type": "[choose between = [notification|foreground|silent]]" 94 | ..//add your notification payload here... 95 | } 96 | } 97 | ``` 98 | 99 | Send notification locally: 100 | ``` 101 | LocalMessagesNotifier.notify( 102 | NotificationMessage( 103 | [Notification action id as it is defined in your android notifications factory], 104 | [choose between = [NOTIFICATION|FOREGROUND_NOTIFICATION|SILENT_NOTIFICATION] 105 | ..//add your notification payload here... 106 | ) 107 | ) 108 | ``` 109 | 110 | -------------------------------------------------------------------------------- /nutshellfcm/rxworker/src/main/java/com/nutshellfcm/rxworkframework/works/Work.kt: -------------------------------------------------------------------------------- 1 | package com.nutshellfcm.rxworkframework.works 2 | 3 | import com.nutshellfcm.rxworkframework.Cancelable 4 | import com.nutshellfcm.rxworkframework.Observer 5 | import com.nutshellfcm.rxworkframework.scheudlers.Scheduler 6 | import com.nutshellfcm.rxworkframework.scheudlers.Schedulers 7 | import com.nutshellfcm.rxworkframework.CancelledObserver 8 | import com.nutshellfcm.rxworkframework.Subscriber 9 | import com.nutshellfcm.rxworkframework.works.runnables.* 10 | import java.util.concurrent.atomic.AtomicReference 11 | 12 | 13 | internal open class Work( 14 | final override val emittedRunnable: EmittedRunnable 15 | ) : ScheduledWork, ParentWork, WorkEmitter { 16 | 17 | 18 | protected val observers: MutableSet> = mutableSetOf() 19 | 20 | protected val subscribers: MutableSet = mutableSetOf() 21 | 22 | protected val cancelledObservers: MutableSet = mutableSetOf() 23 | 24 | final override var subscribeOnScheduler: Scheduler = Schedulers.defaultScheduler 25 | 26 | final override var observeOnScheduler: Scheduler = Schedulers.defaultScheduler 27 | 28 | protected val isScheduled = AtomicReference() 29 | 30 | override val compositeCancellable: CompositeCancellable = CompositeCancellable(this) 31 | 32 | init { 33 | emittedRunnable.emitter = this 34 | } 35 | 36 | override fun onResult(result: T) { 37 | val notifyObserversRunnable = NotifyObserversRunnable(observers) 38 | notifyObserversRunnable.result = result 39 | observeOnScheduler.schedule(notifyObserversRunnable) 40 | } 41 | 42 | override fun onError(error: Throwable) { 43 | val notifyObserversRunnable = NotifyObserversRunnable(observers) 44 | notifyObserversRunnable.throwable = error 45 | observeOnScheduler.schedule(notifyObserversRunnable) 46 | } 47 | 48 | @Suppress("UNCHECKED_CAST") 49 | override fun onWorkCancelled() { 50 | observeOnScheduler.schedule( 51 | NotifyOnCancelledRunnable( 52 | cancelledObservers, 53 | observers as MutableSet>, 54 | subscribers 55 | ) 56 | ) 57 | } 58 | 59 | override fun subscribe(onResult: (T) -> Unit, onError: (Throwable) -> Unit): Cancelable { 60 | observers.add(Observer(onResult, onError)) 61 | return subscribeActual(subscribeOnScheduler) 62 | } 63 | 64 | fun subscribeOnObservers(observers: Set>, scheduler: Scheduler = observeOnScheduler): Cancelable { 65 | this.observers.addAll(observers) 66 | return subscribeActual(scheduler) 67 | } 68 | 69 | private fun subscribeActual(scheduler: Scheduler): CompositeCancellable { 70 | subscribers.forEach { it.onSubscribe() } 71 | 72 | if (isScheduled.get() == null) { 73 | val cancellable = scheduler.schedule(emittedRunnable) 74 | compositeCancellable.append(cancellable) 75 | isScheduled.getAndSet(cancellable) 76 | } 77 | 78 | return compositeCancellable 79 | } 80 | 81 | override fun blockingGet(): T? = BlockingWork(BlockingRunnable(emittedRunnable), this).blockingGet() 82 | 83 | override fun subscribeOn(scheduler: Scheduler): ScheduledWork { 84 | this.subscribeOnScheduler = scheduler 85 | if (observeOnScheduler == Schedulers.defaultScheduler) { 86 | observeOnScheduler = scheduler 87 | } 88 | 89 | return this 90 | } 91 | 92 | override fun observeOn(scheduler: Scheduler): ScheduledWork { 93 | this.observeOnScheduler = scheduler 94 | return this 95 | } 96 | 97 | override fun map(transition: (T) -> S): ScheduledWork { 98 | return MapWork(WorkMapRunnable(transition), this) 99 | } 100 | 101 | @Suppress("UNCHECKED_CAST") 102 | override fun flatMap(transition: (T) -> ScheduledWork): ScheduledWork { 103 | return FlatMapWork(WorkMapRunnable(transition), this) as ScheduledWork 104 | } 105 | 106 | override fun filter(filter: (T) -> Boolean): ScheduledWork { 107 | return FilterWork(WorkFilterRunnable(filter), this) 108 | } 109 | 110 | override fun doOnSubscribe(onSubscribe: () -> Unit): ScheduledWork { 111 | subscribers.add(Subscriber(onSubscribe)) 112 | return this 113 | } 114 | 115 | override fun doOnCancelled(onCancelled: () -> Unit): ScheduledWork { 116 | cancelledObservers.add(CancelledObserver(onCancelled)) 117 | return this 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /nutshellfcm/nuthsellfcm-framework/src/main/java/com/nutshellfcm/framework/di/NutshellFirebaseComponents.kt: -------------------------------------------------------------------------------- 1 | package com.nutshellfcm.framework.di 2 | 3 | import android.app.Application 4 | import android.app.NotificationManager 5 | import com.nutshellfcm.framework.NotificationsReceiversRegisterer 6 | import com.nutshellfcm.framework.application.ApplicationLifeCycleWrapper 7 | import com.nutshellfcm.framework.NutshellFCMContract 8 | import com.nutshellfcm.common.PersistentAdapterContract 9 | 10 | 11 | internal object NutshellFirebaseComponents { 12 | 13 | lateinit var cacheSource: NutshellFCMContract.Sources.CacheSource 14 | lateinit var notificationsInteractor: NutshellFCMContract.Interactor 15 | lateinit var notificationsRepository: NutshellFCMContract.Repository 16 | lateinit var notificationsMessageRouter: NutshellFCMContract.NotificationsMessageRouter 17 | lateinit var notificationBuilder: NutshellFCMContract.AndroidNotificationBuilder 18 | lateinit var systemNotificationManager: NotificationManager 19 | lateinit var notificationsManager: NutshellFCMContract.AndroidNotificationsManager 20 | lateinit var importanceTranslator: NutshellFCMContract.Translators.ImportanceTranslator 21 | lateinit var notificationNotifier: NutshellFCMContract.NotificationsNotifier 22 | lateinit var notificationsWriter: NutshellFCMContract.NotificationMessageWriter 23 | lateinit var notificationsConsumer: NutshellFCMContract.NotificationsConsumer 24 | lateinit var casesManager: NutshellFCMContract.CasesManager 25 | lateinit var notificationsReceiversRegisterer: NotificationsReceiversRegisterer 26 | lateinit var androidNotificationsFactory: NutshellFCMContract.AndroidNotificationsFactory 27 | lateinit var handledNotificationsNotifier: NutshellFCMContract.HandledNotificationsNotifier 28 | lateinit var persistedMessageToNotificationMessageConverter: NutshellFCMContract.Sources.PersistedSource.PersistedMessageToNotificationMessageConverter 29 | var persistedSource: NutshellFCMContract.Sources.PersistedSource? = null 30 | 31 | fun init(application: Application, 32 | notificationsFactory: NutshellFCMContract.AndroidNotificationsFactory, 33 | casesProvider: NutshellFCMContract.CasesProvider, 34 | foregroundServicesBinder: NutshellFCMContract.ForegroundServicesBinder, 35 | persistentAdapter: PersistentAdapterContract.Adapter?): Boolean { 36 | val applicationContext = Injections.provideApplicationContext(application) 37 | androidNotificationsFactory = notificationsFactory 38 | systemNotificationManager = Injections.provideNotificationsManager(application) 39 | cacheSource = Injections.provideCacheSource() 40 | persistedMessageToNotificationMessageConverter = Injections.providePersistetMessageToNotificaitonMessageConverter() 41 | persistentAdapter?.let { 42 | persistedSource = Injections.providePersistentSource(persistentAdapter, persistedMessageToNotificationMessageConverter) 43 | } 44 | notificationsInteractor = Injections.provideNotificationInteractor(persistedSource, cacheSource) 45 | notificationsRepository = Injections.provideNotificationRepository(notificationsInteractor) 46 | importanceTranslator = Injections.provideImpotenceTranslator() 47 | notificationsManager = Injections.provideNotificationsManager(systemNotificationManager, importanceTranslator) 48 | notificationBuilder = Injections.provideNotificationBuilder( 49 | applicationContext, 50 | foregroundServicesBinder, 51 | notificationsManager, 52 | notificationsFactory 53 | ) 54 | notificationsMessageRouter = Injections.provideNotificationsMessageReceiver( 55 | applicationContext, 56 | notificationsRepository, 57 | notificationBuilder 58 | ) 59 | notificationsWriter = Injections.provideNotificationsNotificationsWriter(notificationsRepository) 60 | notificationNotifier = Injections.provideNotificationsNotifier(application, notificationsWriter) 61 | handledNotificationsNotifier = Injections.provideNotificationsHanlder() 62 | casesManager = Injections.provideCaseManager(casesProvider, handledNotificationsNotifier) 63 | notificationsConsumer = Injections.provideNotificationsConsumer( 64 | applicationContext, 65 | systemNotificationManager, 66 | foregroundServicesBinder, 67 | notificationsRepository, 68 | casesManager 69 | ) 70 | notificationsReceiversRegisterer = Injections.provideNotificationReceiversRegisterer( 71 | ApplicationLifeCycleWrapper(application), 72 | application, 73 | notificationsConsumer 74 | ) 75 | return true 76 | } 77 | 78 | } -------------------------------------------------------------------------------- /nutshellfcm/nuthsellfcm-framework/src/main/java/com/nutshellfcm/framework/NotificationsInteractor.kt: -------------------------------------------------------------------------------- 1 | package com.nutshellfcm.framework 2 | 3 | import com.nutshellfcm.framework.model.NotificationMessage 4 | import com.nutshellfcm.rxworkframework.RxWorkCreator 5 | import com.nutshellfcm.rxworkframework.scheudlers.Schedulers 6 | import com.nutshellfcm.rxworkframework.works.ScheduledWork 7 | 8 | 9 | internal class NotificationsInteractor( 10 | private val persistedSource: NutshellFCMContract.Sources.PersistedSource?, 11 | private val cacheSource: NutshellFCMContract.Sources.CacheSource 12 | ) : NutshellFCMContract.Interactor { 13 | 14 | override fun purgeNotifications(): ScheduledWork { 15 | return RxWorkCreator.create { emitter -> 16 | cacheSource.clearCache() 17 | persistedSource?.apply { 18 | purge() 19 | .subscribe( 20 | onResult = { 21 | emitter.onResult(Unit) 22 | }, 23 | onError = { 24 | emitter.onError(it) 25 | } 26 | ) 27 | } 28 | 29 | } 30 | } 31 | 32 | override fun removeNotification(id: String): ScheduledWork { 33 | return RxWorkCreator.create { emitter -> 34 | cacheSource.removeFromCache(id) 35 | persistedSource?.apply { 36 | remove(id).subscribe( 37 | onResult = { 38 | emitter.onResult(Unit) 39 | }, 40 | onError = { 41 | emitter.onError(it) 42 | } 43 | ) 44 | } 45 | } 46 | } 47 | 48 | override fun removeNotifications(ids: List): ScheduledWork { 49 | return RxWorkCreator.create { emitter -> 50 | cacheSource.removeFromCache(ids) 51 | persistedSource?.apply { 52 | remove(ids) 53 | .subscribe( 54 | onResult = { 55 | emitter.onResult(Unit) 56 | }, 57 | onError = { 58 | emitter.onError(it) 59 | } 60 | ) 61 | } 62 | } 63 | } 64 | 65 | override fun readNotification(id: String): ScheduledWork { 66 | return RxWorkCreator.create { emitter -> 67 | val cacheNotificationMessage = cacheSource.readCache(id) 68 | if (cacheNotificationMessage != null) { 69 | emitter.onResult(cacheNotificationMessage) 70 | return@create 71 | } 72 | 73 | persistedSource?.apply { 74 | read(id) 75 | .subscribe( 76 | onResult = { 77 | emitter.onResult(it) 78 | }, 79 | onError = { 80 | emitter.onError(it) 81 | } 82 | ) 83 | } 84 | } 85 | } 86 | 87 | override fun readNotifications(): ScheduledWork> { 88 | return RxWorkCreator.create { emitter -> 89 | val cacheNotificationMessage = cacheSource.readCache() 90 | if (!cacheNotificationMessage.isNullOrEmpty()) { 91 | emitter.onResult(cacheNotificationMessage) 92 | return@create 93 | } 94 | 95 | persistedSource?.apply { 96 | read() 97 | .subscribe( 98 | onResult = { 99 | emitter.onResult(it) 100 | }, 101 | onError = { 102 | emitter.onError(it) 103 | } 104 | ) 105 | } 106 | } 107 | } 108 | 109 | override fun writeNotification(notificationMessage: NotificationMessage): ScheduledWork { 110 | return RxWorkCreator.create { emitter -> 111 | cacheSource.writeCache(notificationMessage) 112 | persistedSource?.apply { 113 | write(notificationMessage) 114 | .subscribeOn(Schedulers.single) 115 | .subscribe( 116 | onResult = { 117 | emitter.onResult(Unit) 118 | }, 119 | onError = { 120 | emitter.onError(it) 121 | } 122 | ) 123 | } ?: emitter.onResult(Unit) 124 | } 125 | } 126 | } -------------------------------------------------------------------------------- /nutshellfcm/samples/app/src/main/java/com/nutshellfcm/sample_app/ExampleNotificationFactory.kt: -------------------------------------------------------------------------------- 1 | package com.nutshellfcm.sample_app 2 | 3 | 4 | import android.app.Application 5 | import android.app.PendingIntent 6 | import android.content.Intent 7 | import com.nutshellfcm.framework.model.AndroidNotification 8 | import com.nutshellfcm.framework.NutshellFCMContract 9 | import com.nutshellfcm.framework.model.NotificationMessage 10 | 11 | 12 | class ExampleNotificationFactory(private val application: Application) : NutshellFCMContract.AndroidNotificationsFactory { 13 | 14 | override fun create(notificationMessage: NotificationMessage): AndroidNotification { 15 | return when (notificationMessage.actionId) { 16 | "Action 1" -> { 17 | AndroidNotification( 18 | payload = notificationMessage.payload, 19 | contentIntent = PendingIntent.getActivity( 20 | application, 21 | 0, 22 | Intent(application, ExampleActivity::class.java), 23 | PendingIntent.FLAG_UPDATE_CURRENT 24 | ), 25 | contentTitle = "notification type 1", 26 | contentText = "This is an example content", 27 | importance = NutshellFCMContract.Importance.HIGH, 28 | smallIcon = android.R.drawable.gallery_thumb, 29 | channel = NutshellFCMContract.NotificationChannel( 30 | "example channel id", 31 | "example channel", 32 | null 33 | ) 34 | ) 35 | } 36 | 37 | "Action 2" -> { 38 | AndroidNotification( 39 | payload = notificationMessage.payload, 40 | contentIntent = PendingIntent.getActivity( 41 | application, 42 | 0, 43 | Intent(application, ExampleActivity::class.java), 44 | PendingIntent.FLAG_UPDATE_CURRENT 45 | ), 46 | contentTitle = "notification type 2", 47 | contentText = "This is an example content", 48 | importance = NutshellFCMContract.Importance.HIGH, 49 | smallIcon = android.R.drawable.gallery_thumb, 50 | channel = NutshellFCMContract.NotificationChannel( 51 | "example channel id", 52 | "example channel", 53 | null 54 | ) 55 | ) 56 | } 57 | 58 | "Action 3" -> { 59 | AndroidNotification( 60 | payload = notificationMessage.payload, 61 | contentIntent = PendingIntent.getActivity( 62 | application, 63 | 0, 64 | Intent(application, ExampleActivity::class.java), 65 | PendingIntent.FLAG_UPDATE_CURRENT 66 | ), 67 | contentTitle = "notification type 3", 68 | contentText = "This is an example content", 69 | importance = NutshellFCMContract.Importance.HIGH, 70 | smallIcon = android.R.drawable.gallery_thumb, 71 | channel = NutshellFCMContract.NotificationChannel( 72 | "example channel id", 73 | "example channel", 74 | null 75 | ) 76 | ) 77 | } 78 | 79 | "Action 4" -> { 80 | AndroidNotification( 81 | payload = notificationMessage.payload, 82 | contentIntent = PendingIntent.getActivity( 83 | application, 84 | 0, 85 | Intent(application, ExampleActivity::class.java), 86 | PendingIntent.FLAG_UPDATE_CURRENT 87 | ), 88 | contentTitle = "notification type 4 (foreground)", 89 | contentText = "This is an example content", 90 | importance = NutshellFCMContract.Importance.HIGH, 91 | smallIcon = android.R.drawable.gallery_thumb, 92 | channel = NutshellFCMContract.NotificationChannel( 93 | "example channel id", 94 | "example channel", 95 | null 96 | ) 97 | ) 98 | } 99 | 100 | else -> { 101 | throw Throwable("Unsupported notification id") 102 | } 103 | } 104 | } 105 | } -------------------------------------------------------------------------------- /nutshellfcm/samples/app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 10 | 12 | 14 | 16 | 18 | 20 | 22 | 24 | 26 | 28 | 30 | 32 | 34 | 36 | 38 | 40 | 42 | 44 | 46 | 48 | 50 | 52 | 54 | 56 | 58 | 60 | 62 | 64 | 66 | 68 | 70 | 72 | 74 | 75 | -------------------------------------------------------------------------------- /nutshellfcm/nuthsellfcm-framework/src/main/java/com/nutshellfcm/framework/AndroidNotificationsManager.kt: -------------------------------------------------------------------------------- 1 | package com.nutshellfcm.framework 2 | 3 | import android.annotation.TargetApi 4 | import android.app.NotificationChannel 5 | import android.app.NotificationManager 6 | import android.app.Service 7 | import android.content.Context 8 | import android.media.AudioAttributes 9 | import android.media.AudioManager 10 | import android.net.Uri 11 | import android.os.Build 12 | import androidx.core.app.NotificationCompat 13 | import com.nutshellfcm.framework.extensions.toBundle 14 | import com.nutshellfcm.framework.model.AndroidNotification 15 | 16 | internal class AndroidNotificationsManager(private val notificationManager: NotificationManager, 17 | private val importanceTranslator: NutshellFCMContract.Translators.ImportanceTranslator 18 | ) 19 | : NutshellFCMContract.AndroidNotificationsManager { 20 | 21 | override fun show(context: Context, id: Int, androidNotification: AndroidNotification) { 22 | val extras = androidNotification.payload.toBundle() 23 | 24 | 25 | val soundUri = androidNotification.channel.soundUri 26 | 27 | val builder = NotificationCompat.Builder(context, androidNotification.channel.id).apply { 28 | setSmallIcon(androidNotification.smallIcon) 29 | setContentTitle(androidNotification.contentTitle) 30 | setContentText(androidNotification.contentText) 31 | setAutoCancel(true) 32 | priority = importanceTranslator.getAndroidPriority(androidNotification.importance) 33 | setContentIntent(androidNotification.contentIntent) 34 | setDeleteIntent(androidNotification.deleteIntent) 35 | addExtras(extras) 36 | 37 | soundUri?.let { 38 | setSound(it, AudioManager.STREAM_NOTIFICATION) 39 | } 40 | } 41 | 42 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { 43 | androidNotification.channel.let { 44 | val channelId = androidNotification.channel.id 45 | val channelName = androidNotification.channel.channelName 46 | val importance = importanceTranslator.getAndroidImportance(androidNotification.importance) 47 | val notificationChannel = setNotificationChannel( 48 | channelId, 49 | channelName, 50 | importance, 51 | soundUri 52 | ) 53 | builder.setChannelId(notificationChannel.id) 54 | } 55 | } 56 | 57 | notificationManager.notify(id, builder.build()) 58 | } 59 | 60 | 61 | override fun showForeground(service: Service, id: Int, androidNotification: AndroidNotification) { 62 | val notificationBuilder = NotificationCompat.Builder(service, "default_importance") 63 | .setContentTitle(androidNotification.contentTitle) 64 | .setContentText(androidNotification.contentText) 65 | .setColor(androidNotification.color) 66 | .setPriority(importanceTranslator.getAndroidPriority(androidNotification.importance)) 67 | .setLargeIcon(androidNotification.largeIcon) 68 | .setSmallIcon(androidNotification.smallIcon) 69 | .setContentIntent(androidNotification.contentIntent) 70 | 71 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { 72 | val channelId = androidNotification.channel.id 73 | val channelName = androidNotification.channel.channelName 74 | val importance = importanceTranslator.getAndroidImportance(androidNotification.importance) 75 | val soundUri = androidNotification.channel.soundUri 76 | val notificationChannel = setNotificationChannel(channelId, channelName, importance, soundUri) 77 | 78 | notificationBuilder.setChannelId(notificationChannel.id) 79 | } 80 | 81 | 82 | service.startForeground(id, notificationBuilder.build()) 83 | } 84 | 85 | 86 | @TargetApi(Build.VERSION_CODES.O) 87 | private fun setNotificationChannel(channelId: String, 88 | channelName: String, 89 | importance: Int, 90 | soundUri: Uri?): NotificationChannel { 91 | val notificationChannel = NotificationChannel(channelId, channelName, importance) 92 | 93 | if (soundUri == null) { 94 | notificationChannel.setSound(null, null) 95 | } else { 96 | val audioAttributes = AudioAttributes.Builder() 97 | .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION) 98 | .setUsage(AudioAttributes.USAGE_NOTIFICATION_RINGTONE) 99 | .build() 100 | 101 | notificationChannel.setSound(soundUri, audioAttributes) 102 | } 103 | 104 | notificationManager.createNotificationChannel(notificationChannel) 105 | return notificationChannel 106 | } 107 | 108 | } -------------------------------------------------------------------------------- /nutshellfcm/gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS="" 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn () { 37 | echo "$*" 38 | } 39 | 40 | die () { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Escape application args 158 | save () { 159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 160 | echo " " 161 | } 162 | APP_ARGS=$(save "$@") 163 | 164 | # Collect all arguments for the java command, following the shell quoting and substitution rules 165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 166 | 167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then 169 | cd "$(dirname "$0")" 170 | fi 171 | 172 | exec "$JAVACMD" "$@" 173 | -------------------------------------------------------------------------------- /nutshellfcm/nuthsellfcm-framework/src/main/java/com/nutshellfcm/framework/di/Injections.kt: -------------------------------------------------------------------------------- 1 | package com.nutshellfcm.framework.di 2 | 3 | import android.app.Application 4 | import android.app.NotificationManager 5 | import android.content.Context 6 | import com.nutshellfcm.framework.* 7 | import com.nutshellfcm.framework.application.ApplicationLifeCycleWrapper 8 | import com.nutshellfcm.framework.application.contexts.ApplicationContext 9 | import com.nutshellfcm.framework.ImportanceTranslator 10 | import com.nutshellfcm.framework.NutshellFCMContract 11 | import com.nutshellfcm.framework.sources.CacheSource 12 | import com.nutshellfcm.framework.sources.PersistedSource 13 | import com.nutshellfcm.common.PersistentAdapterContract 14 | 15 | 16 | internal object Injections { 17 | 18 | fun provideApplicationContext(application: Application): ApplicationContext { 19 | return ApplicationContext(application) 20 | } 21 | 22 | fun provideNotificationsManager(application: Application): NotificationManager { 23 | return application.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager 24 | } 25 | 26 | fun providePersistetMessageToNotificaitonMessageConverter(): NutshellFCMContract.Sources.PersistedSource.PersistedMessageToNotificationMessageConverter { 27 | return PersistedMessageToNotificationMessageConverter() 28 | } 29 | 30 | fun providePersistentSource( 31 | persistedAdapter: PersistentAdapterContract.Adapter, 32 | converter: NutshellFCMContract.Sources.PersistedSource.PersistedMessageToNotificationMessageConverter): NutshellFCMContract.Sources.PersistedSource? { 33 | return PersistedSource(persistedAdapter, converter) 34 | } 35 | 36 | fun provideCacheSource(): NutshellFCMContract.Sources.CacheSource { 37 | return CacheSource() 38 | } 39 | 40 | fun provideNotificationInteractor( 41 | persistedSource: NutshellFCMContract.Sources.PersistedSource?, 42 | cacheSource: NutshellFCMContract.Sources.CacheSource 43 | ): NutshellFCMContract.Interactor { 44 | return NotificationsInteractor(persistedSource, cacheSource) 45 | } 46 | 47 | fun provideNotificationRepository(notificationsInteractor: NutshellFCMContract.Interactor): NutshellFCMContract.Repository { 48 | return NotificationsRepository(notificationsInteractor) 49 | } 50 | 51 | fun provideNotificationsHanlder(): NutshellFCMContract.HandledNotificationsNotifier { 52 | return NutshellNotificationHandler 53 | } 54 | 55 | fun provideCaseManager(casesProvider: NutshellFCMContract.CasesProvider, 56 | handledNotificationsNotifier: NutshellFCMContract.HandledNotificationsNotifier 57 | ): NutshellFCMContract.CasesManager { 58 | return NotificationCasesManager(casesProvider, handledNotificationsNotifier) 59 | } 60 | 61 | fun provideNotificationsConsumer( 62 | applicationContext: ApplicationContext, 63 | notificationManager: NotificationManager, 64 | foregroundServicesBinder: NutshellFCMContract.ForegroundServicesBinder, 65 | notificationsRepository: NutshellFCMContract.Repository, 66 | casesManager: NutshellFCMContract.CasesManager 67 | ): NutshellFCMContract.NotificationsConsumer { 68 | return NotificationsConsumer( 69 | applicationContext, 70 | notificationManager, 71 | notificationsRepository, 72 | foregroundServicesBinder, 73 | casesManager 74 | ) 75 | } 76 | 77 | fun provideNotificationReceiversRegisterer( 78 | applicationLifeCycleWrapper: ApplicationLifeCycleWrapper, 79 | application: Application, 80 | notificationsConsumer: NutshellFCMContract.NotificationsConsumer 81 | ): NotificationsReceiversRegisterer { 82 | return NotificationsReceiversRegisterer(applicationLifeCycleWrapper, application, notificationsConsumer) 83 | } 84 | 85 | fun provideNotificationBuilder( 86 | applicationContext: ApplicationContext, 87 | foregroundServicesBinder: NutshellFCMContract.ForegroundServicesBinder, 88 | androidNotificationsManager: NutshellFCMContract.AndroidNotificationsManager, 89 | notificationsFactory: NutshellFCMContract.AndroidNotificationsFactory 90 | ): NutshellFCMContract.AndroidNotificationBuilder { 91 | return AndroidNotificationBuilder( 92 | applicationContext, 93 | foregroundServicesBinder, 94 | androidNotificationsManager, 95 | notificationsFactory 96 | ) 97 | } 98 | 99 | fun provideNotificationsMessageReceiver( 100 | applicationContext: ApplicationContext, 101 | notificationsRepository: NutshellFCMContract.Repository, 102 | notificationBuilder: NutshellFCMContract.AndroidNotificationBuilder 103 | ): NutshellFCMContract.NotificationsMessageRouter { 104 | return NotificationsMessageRouter(applicationContext, notificationsRepository, notificationBuilder) 105 | } 106 | 107 | fun provideImpotenceTranslator(): NutshellFCMContract.Translators.ImportanceTranslator { 108 | return ImportanceTranslator() 109 | } 110 | 111 | fun provideNotificationsManager( 112 | notificationManager: NotificationManager, 113 | importanceTranslator: NutshellFCMContract.Translators.ImportanceTranslator 114 | ): NutshellFCMContract.AndroidNotificationsManager { 115 | return AndroidNotificationsManager(notificationManager, importanceTranslator) 116 | } 117 | 118 | fun provideNotificationsNotificationsWriter(notificationsRepository: NutshellFCMContract.Repository): NutshellFCMContract.NotificationMessageWriter { 119 | return notificationsRepository as NutshellFCMContract.NotificationMessageWriter 120 | } 121 | 122 | fun provideNotificationsNotifier( 123 | application: Application, 124 | notificationMessageWriter: NutshellFCMContract.NotificationMessageWriter 125 | ): NutshellFCMContract.NotificationsNotifier { 126 | return NotificationNotifier(application, notificationMessageWriter) 127 | } 128 | } -------------------------------------------------------------------------------- /nutshellfcm/nuthsellfcm-framework/src/main/java/com/nutshellfcm/framework/NutshellFCMContract.kt: -------------------------------------------------------------------------------- 1 | package com.nutshellfcm.framework 2 | 3 | import android.app.Service 4 | import android.content.Context 5 | import android.content.Intent 6 | import android.net.Uri 7 | import androidx.lifecycle.LiveData 8 | import com.nutshellfcm.framework.model.NotificationMessage 9 | import com.nutshellfcm.common.PersistentAdapterContract.* 10 | import com.nutshellfcm.framework.model.AndroidNotification 11 | import com.nutshellfcm.rxworkframework.works.ScheduledWork 12 | 13 | interface NutshellFCMContract { 14 | 15 | companion object { 16 | const val KEY_ACTION_ID = "action_id" 17 | const val KEY_TYPE = "type" 18 | const val ACTION_BROADCAST_REGISTRATION_NOTIFICATION = "ACTION_BROADCAST_REGISTRATION_NOTIFICATION" 19 | } 20 | 21 | interface NotificationMessageWriter { 22 | fun write(notificationMessage: NotificationMessage): ScheduledWork 23 | } 24 | 25 | interface NotificationMessageReader { 26 | fun read(): ScheduledWork> 27 | fun read(id: String): ScheduledWork 28 | } 29 | 30 | interface NotificationsNotifier { 31 | fun notifyMessage(notificationMessage: NotificationMessage) 32 | } 33 | 34 | interface NotificationsMessageRouter { 35 | fun onRouteNotificationsMessage(intent: Intent) 36 | } 37 | 38 | interface Repository : NotificationMessageReader { 39 | fun purge(): ScheduledWork 40 | fun remove(id: String): ScheduledWork 41 | fun remove(ids: List): ScheduledWork 42 | 43 | } 44 | 45 | interface Sources { 46 | 47 | interface PersistedSource { 48 | 49 | fun purge(): ScheduledWork 50 | fun remove(id: String): ScheduledWork 51 | fun remove(ids: List): ScheduledWork 52 | fun read(): ScheduledWork> 53 | fun read(id: String): ScheduledWork 54 | fun write(notificationMessage: NotificationMessage): ScheduledWork 55 | 56 | interface PersistedMessageToNotificationMessageConverter { 57 | 58 | fun convert(persistNotificationMessage: PersistNotificationMessage): NotificationMessage 59 | fun convert(persistNotificationMessages: List): List 60 | fun convertBack(notificationMessage: NotificationMessage): PersistNotificationMessage 61 | fun convertBack(notificationMessages: List): List 62 | } 63 | 64 | } 65 | 66 | interface CacheSource { 67 | 68 | fun clearCache() 69 | fun removeFromCache(id: String) 70 | fun removeFromCache(ids: List) 71 | fun readCache(): List? 72 | fun readCache(id: String): NotificationMessage? 73 | fun writeCache(notificationMessage: NotificationMessage) 74 | } 75 | } 76 | 77 | interface Interactor { 78 | 79 | fun purgeNotifications(): ScheduledWork 80 | fun removeNotification(id: String): ScheduledWork 81 | fun removeNotifications(ids: List): ScheduledWork 82 | fun readNotification(id: String): ScheduledWork 83 | fun readNotifications(): ScheduledWork> 84 | fun writeNotification(notificationMessage: NotificationMessage): ScheduledWork 85 | } 86 | 87 | interface CasesProvider { 88 | val cases: List 89 | } 90 | 91 | interface CasesManager { 92 | fun init() 93 | fun hasRemainingCases(): Boolean 94 | fun handleNextCase(notifications: List): List 95 | } 96 | 97 | interface Case { 98 | 99 | val actionIds: List 100 | 101 | fun consume(caseMessages: List) 102 | } 103 | 104 | interface HandledNotificationsNotifier { 105 | val handledNotifications: LiveData> 106 | } 107 | 108 | interface NotificationsConsumer { 109 | fun consume(actionId: String) 110 | fun consumeNotificationsMessages() 111 | } 112 | 113 | interface AndroidNotificationsManager { 114 | fun show(context: Context, 115 | id: Int, 116 | androidNotification: AndroidNotification) 117 | fun showForeground(service: Service, 118 | id: Int, 119 | androidNotification: AndroidNotification) 120 | } 121 | 122 | interface AndroidNotificationsFactory { 123 | fun create(notificationMessage: NotificationMessage): AndroidNotification 124 | } 125 | 126 | interface ForegroundServicesBinder { 127 | fun bind(actionId: String) : Class<*>? 128 | } 129 | 130 | interface AndroidNotificationBuilder { 131 | fun build(notificationMessage: NotificationMessage) 132 | fun buildForeground(notificationMessage: NotificationMessage) 133 | } 134 | 135 | interface NotificationReceiversRegisterer { 136 | 137 | companion object { 138 | const val BACKGROUND_PRIORITY = 0 139 | const val FOREGROUND_PRIORITY = 1 140 | } 141 | 142 | } 143 | 144 | enum class Importance { 145 | LOW, 146 | MEDIUM, 147 | HIGH, 148 | URGENT 149 | } 150 | 151 | interface Translators { 152 | interface ImportanceTranslator { 153 | fun getAndroidPriority(importance: Importance): Int 154 | 155 | fun getAndroidImportance(importance: Importance): Int 156 | } 157 | } 158 | 159 | data class NotificationChannel( 160 | val id: String, 161 | val channelName: String, 162 | val soundUri: Uri? 163 | ) 164 | 165 | enum class NotificationType(val value: String) { 166 | NOTIFICATION("notification"), 167 | SILENT_NOTIFICATION("silent"), 168 | FOREGROUND_NOTIFICATION("foreground"); 169 | 170 | companion object { 171 | fun NotificationType(value: String): NotificationType = values().find { it.value == value } ?: NOTIFICATION 172 | 173 | } 174 | } 175 | 176 | 177 | sealed class Error(msg: String) : Throwable(msg) { 178 | data class UnknownNotificationActionIdThrowable(val actionId: String? = null) : Error("Unknown action id $actionId") 179 | data class UnknownServiceBindActionIdThrowable(val actionId: String? = null) : kotlin.Error("Unknown foreground service bind for action id $actionId") 180 | } 181 | } 182 | 183 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | --------------------------------------------------------------------------------