From 64ca44e4cd30c71afef28c3adb34ff22c83fc59e Mon Sep 17 00:00:00 2001 From: LooKeR Date: Sat, 28 Jan 2023 01:22:31 +0530 Subject: [PATCH] Add a sync module to sync index [W.I.P] --- buildSrc/src/main/kotlin/Libs.kt | 1 + .../com/looker/core/data/di/DataModule.kt | 2 - .../data/fdroid/repository/AppRepository.kt | 19 ++++++ .../data/fdroid/repository/RepoRepository.kt | 12 ++++ .../com/looker/core/database/dao/AppDao.kt | 2 +- .../looker/core/database/model/AppEntity.kt | 12 ++-- .../core/database/model/PackageEntity.kt | 6 +- .../looker/core/database/model/RepoEntity.kt | 2 +- core/sync/.gitignore | 1 + core/sync/build.gradle.kts | 59 ++++++++++++++++++ core/sync/consumer-rules.pro | 0 core/sync/proguard-rules.pro | 21 +++++++ .../looker/sync/ExampleInstrumentedTest.kt | 24 +++++++ core/sync/src/main/AndroidManifest.xml | 4 ++ .../java/com/looker/sync/di/SyncModule.kt | 17 +++++ .../status/WorkManagerSyncStatusMonitor.kt | 28 +++++++++ .../looker/sync/worker/DelegatingWorker.kt | 61 ++++++++++++++++++ .../java/com/looker/sync/worker/SyncWorker.kt | 27 ++++++++ .../looker/sync/worker/SyncWorkerHelper.kt | 62 +++++++++++++++++++ .../java/com/looker/sync/ExampleUnitTest.kt | 17 +++++ settings.gradle.kts | 3 +- 21 files changed, 366 insertions(+), 14 deletions(-) create mode 100644 core/data/src/main/java/com/looker/core/data/fdroid/repository/AppRepository.kt create mode 100644 core/data/src/main/java/com/looker/core/data/fdroid/repository/RepoRepository.kt create mode 100644 core/sync/.gitignore create mode 100644 core/sync/build.gradle.kts create mode 100644 core/sync/consumer-rules.pro create mode 100644 core/sync/proguard-rules.pro create mode 100644 core/sync/src/androidTest/java/com/looker/sync/ExampleInstrumentedTest.kt create mode 100644 core/sync/src/main/AndroidManifest.xml create mode 100644 core/sync/src/main/java/com/looker/sync/di/SyncModule.kt create mode 100644 core/sync/src/main/java/com/looker/sync/status/WorkManagerSyncStatusMonitor.kt create mode 100644 core/sync/src/main/java/com/looker/sync/worker/DelegatingWorker.kt create mode 100644 core/sync/src/main/java/com/looker/sync/worker/SyncWorker.kt create mode 100644 core/sync/src/main/java/com/looker/sync/worker/SyncWorkerHelper.kt create mode 100644 core/sync/src/test/java/com/looker/sync/ExampleUnitTest.kt diff --git a/buildSrc/src/main/kotlin/Libs.kt b/buildSrc/src/main/kotlin/Libs.kt index 231b7ae3..46cb7eb9 100644 --- a/buildSrc/src/main/kotlin/Libs.kt +++ b/buildSrc/src/main/kotlin/Libs.kt @@ -66,6 +66,7 @@ object Kotlin { object Lifecycle { private const val lifecycleVersion = "2.5.1" const val runtime = "androidx.lifecycle:lifecycle-runtime-ktx:$lifecycleVersion" + const val livedata = "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycleVersion" const val viewmodel = "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycleVersion" const val fragment = "androidx.fragment:fragment-ktx:1.5.5" diff --git a/core/data/src/main/java/com/looker/core/data/di/DataModule.kt b/core/data/src/main/java/com/looker/core/data/di/DataModule.kt index dc3fc93e..943fb56a 100644 --- a/core/data/src/main/java/com/looker/core/data/di/DataModule.kt +++ b/core/data/src/main/java/com/looker/core/data/di/DataModule.kt @@ -10,10 +10,8 @@ import dagger.hilt.components.SingletonComponent @Module @InstallIn(SingletonComponent::class) interface DataModule { - @Binds fun bindNetworkMonitor( networkMonitor: ConnectivityManagerNetworkMonitor ): NetworkMonitor - } \ No newline at end of file diff --git a/core/data/src/main/java/com/looker/core/data/fdroid/repository/AppRepository.kt b/core/data/src/main/java/com/looker/core/data/fdroid/repository/AppRepository.kt new file mode 100644 index 00000000..03c657d7 --- /dev/null +++ b/core/data/src/main/java/com/looker/core/data/fdroid/repository/AppRepository.kt @@ -0,0 +1,19 @@ +package com.looker.core.data.fdroid.repository + +import com.looker.core.model.newer.App +import com.looker.core.model.newer.Author +import com.looker.core.model.newer.Package +import com.looker.core.model.newer.PackageName +import kotlinx.coroutines.flow.Flow + +interface AppRepository { + + fun getApps(): Flow> + + fun getApp(packageName: PackageName): Flow> + + fun getAppFromAuthor(author: Author): Flow> + + fun getPackages(packageName: PackageName): Flow> + +} \ No newline at end of file diff --git a/core/data/src/main/java/com/looker/core/data/fdroid/repository/RepoRepository.kt b/core/data/src/main/java/com/looker/core/data/fdroid/repository/RepoRepository.kt new file mode 100644 index 00000000..c474060d --- /dev/null +++ b/core/data/src/main/java/com/looker/core/data/fdroid/repository/RepoRepository.kt @@ -0,0 +1,12 @@ +package com.looker.core.data.fdroid.repository + +import com.looker.core.model.newer.Repo +import kotlinx.coroutines.flow.Flow + +interface RepoRepository { + + fun getRepos() : Flow> + + fun getRepo(id: Long) : Flow + +} \ No newline at end of file diff --git a/core/database/src/main/java/com/looker/core/database/dao/AppDao.kt b/core/database/src/main/java/com/looker/core/database/dao/AppDao.kt index 407f9d79..098c68c8 100644 --- a/core/database/src/main/java/com/looker/core/database/dao/AppDao.kt +++ b/core/database/src/main/java/com/looker/core/database/dao/AppDao.kt @@ -24,7 +24,7 @@ interface AppDao { fun getAppsFromAuthor(authorName: String): Flow> @Query(value = "SELECT * FROM apps WHERE packageName = :packageName") - fun getApp(packageName: String): List + fun getApp(packageName: String): Flow> @Query( value = """ diff --git a/core/database/src/main/java/com/looker/core/database/model/AppEntity.kt b/core/database/src/main/java/com/looker/core/database/model/AppEntity.kt index 50c17cbb..1c0ce1bc 100644 --- a/core/database/src/main/java/com/looker/core/database/model/AppEntity.kt +++ b/core/database/src/main/java/com/looker/core/database/model/AppEntity.kt @@ -2,12 +2,12 @@ package com.looker.core.database.model import androidx.room.ColumnInfo import androidx.room.Entity -import com.looker.core.model.new.App -import com.looker.core.model.new.Author -import com.looker.core.model.new.Donate -import com.looker.core.model.new.Localized -import com.looker.core.model.new.Metadata -import com.looker.core.model.new.toPackageName +import com.looker.core.model.newer.App +import com.looker.core.model.newer.Author +import com.looker.core.model.newer.Donate +import com.looker.core.model.newer.Localized +import com.looker.core.model.newer.Metadata +import com.looker.core.model.newer.toPackageName import kotlinx.serialization.Serializable @Entity(tableName = "apps", primaryKeys = ["repoId", "packageName"]) diff --git a/core/database/src/main/java/com/looker/core/database/model/PackageEntity.kt b/core/database/src/main/java/com/looker/core/database/model/PackageEntity.kt index ffd23c87..3d816702 100644 --- a/core/database/src/main/java/com/looker/core/database/model/PackageEntity.kt +++ b/core/database/src/main/java/com/looker/core/database/model/PackageEntity.kt @@ -1,8 +1,8 @@ package com.looker.core.database.model -import com.looker.core.model.new.Package -import com.looker.core.model.new.Permission -import com.looker.core.model.new.toPackageName +import com.looker.core.model.newer.Package +import com.looker.core.model.newer.Permission +import com.looker.core.model.newer.toPackageName import kotlinx.serialization.ExperimentalSerializationApi import kotlinx.serialization.KSerializer import kotlinx.serialization.Serializable diff --git a/core/database/src/main/java/com/looker/core/database/model/RepoEntity.kt b/core/database/src/main/java/com/looker/core/database/model/RepoEntity.kt index e26f05bb..d6f4e97e 100644 --- a/core/database/src/main/java/com/looker/core/database/model/RepoEntity.kt +++ b/core/database/src/main/java/com/looker/core/database/model/RepoEntity.kt @@ -2,7 +2,7 @@ package com.looker.core.database.model import androidx.room.Entity import androidx.room.PrimaryKey -import com.looker.core.model.new.Repo +import com.looker.core.model.newer.Repo @Entity(tableName = "repos") data class RepoEntity( diff --git a/core/sync/.gitignore b/core/sync/.gitignore new file mode 100644 index 00000000..42afabfd --- /dev/null +++ b/core/sync/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/core/sync/build.gradle.kts b/core/sync/build.gradle.kts new file mode 100644 index 00000000..613dd4a0 --- /dev/null +++ b/core/sync/build.gradle.kts @@ -0,0 +1,59 @@ +plugins { + id("com.android.library") + id("org.jetbrains.kotlin.android") + kotlin("kapt") + id(Hilt.plugin) +} + +android { + compileSdk = Android.compileSdk + namespace = "com.looker.sync" + defaultConfig { + minSdk = Android.minSdk + targetSdk = Android.compileSdk + + testInstrumentationRunner = Test.jUnitRunner + } + + buildTypes { + release { + isMinifyEnabled = true + proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt")) + } + } + compileOptions { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 + } + kotlinOptions { + jvmTarget = "1.8" + } + buildFeatures { + buildConfig = false + aidl = false + renderScript = false + shaders = false + resValues = false + } +} + +dependencies { + + implementation(project(Modules.coreCommon)) + implementation(project(Modules.coreData)) + implementation(project(Modules.coreDatastore)) + implementation(project(Modules.coreModel)) + + implementation(Core.core) + implementation(Lifecycle.livedata) + + implementation(Work.manager) + implementation(Hilt.android) + implementation(Hilt.work) + + kapt(Hilt.compiler) + + testImplementation(Test.jUnit) + androidTestImplementation(Test.androidJUnit) + androidTestImplementation(Test.espresso) +} \ No newline at end of file diff --git a/core/sync/consumer-rules.pro b/core/sync/consumer-rules.pro new file mode 100644 index 00000000..e69de29b diff --git a/core/sync/proguard-rules.pro b/core/sync/proguard-rules.pro new file mode 100644 index 00000000..481bb434 --- /dev/null +++ b/core/sync/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/core/sync/src/androidTest/java/com/looker/sync/ExampleInstrumentedTest.kt b/core/sync/src/androidTest/java/com/looker/sync/ExampleInstrumentedTest.kt new file mode 100644 index 00000000..8f663843 --- /dev/null +++ b/core/sync/src/androidTest/java/com/looker/sync/ExampleInstrumentedTest.kt @@ -0,0 +1,24 @@ +package com.looker.sync + +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.ext.junit.runners.AndroidJUnit4 + +import org.junit.Test +import org.junit.runner.RunWith + +import org.junit.Assert.* + +/** + * Instrumented test, which will execute on an Android device. + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +@RunWith(AndroidJUnit4::class) +class ExampleInstrumentedTest { + @Test + fun useAppContext() { + // Context of the app under test. + val appContext = InstrumentationRegistry.getInstrumentation().targetContext + assertEquals("com.looker.sync.test", appContext.packageName) + } +} \ No newline at end of file diff --git a/core/sync/src/main/AndroidManifest.xml b/core/sync/src/main/AndroidManifest.xml new file mode 100644 index 00000000..a5918e68 --- /dev/null +++ b/core/sync/src/main/AndroidManifest.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/core/sync/src/main/java/com/looker/sync/di/SyncModule.kt b/core/sync/src/main/java/com/looker/sync/di/SyncModule.kt new file mode 100644 index 00000000..66422b62 --- /dev/null +++ b/core/sync/src/main/java/com/looker/sync/di/SyncModule.kt @@ -0,0 +1,17 @@ +package com.looker.sync.di + +import com.looker.core.data.utils.SyncStatusMonitor +import com.looker.sync.status.WorkManagerSyncStatusMonitor +import dagger.Binds +import dagger.Module +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent + +@Module +@InstallIn(SingletonComponent::class) +interface SyncModule { + @Binds + fun bindsSyncStatusMonitor( + syncStatusMonitor: WorkManagerSyncStatusMonitor + ): SyncStatusMonitor +} \ No newline at end of file diff --git a/core/sync/src/main/java/com/looker/sync/status/WorkManagerSyncStatusMonitor.kt b/core/sync/src/main/java/com/looker/sync/status/WorkManagerSyncStatusMonitor.kt new file mode 100644 index 00000000..101d0296 --- /dev/null +++ b/core/sync/src/main/java/com/looker/sync/status/WorkManagerSyncStatusMonitor.kt @@ -0,0 +1,28 @@ +package com.looker.sync.status + +import android.content.Context +import androidx.lifecycle.Transformations +import androidx.lifecycle.asFlow +import androidx.work.WorkInfo +import androidx.work.WorkManager +import com.looker.core.data.utils.SyncStatusMonitor +import dagger.hilt.android.qualifiers.ApplicationContext +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.conflate +import javax.inject.Inject + +internal const val SyncWorkName = "sync_work" + +class WorkManagerSyncStatusMonitor @Inject constructor( + @ApplicationContext context: Context +) : SyncStatusMonitor { + override val isSyncing: Flow = + Transformations.map( + WorkManager.getInstance(context).getWorkInfosForUniqueWorkLiveData(SyncWorkName), + MutableList::anyRunning + ) + .asFlow() + .conflate() +} + +private val List.anyRunning get() = any { it.state == WorkInfo.State.RUNNING } \ No newline at end of file diff --git a/core/sync/src/main/java/com/looker/sync/worker/DelegatingWorker.kt b/core/sync/src/main/java/com/looker/sync/worker/DelegatingWorker.kt new file mode 100644 index 00000000..b243d007 --- /dev/null +++ b/core/sync/src/main/java/com/looker/sync/worker/DelegatingWorker.kt @@ -0,0 +1,61 @@ +package com.looker.sync.worker + +import android.content.Context +import androidx.hilt.work.HiltWorkerFactory +import androidx.work.CoroutineWorker +import androidx.work.Data +import androidx.work.ForegroundInfo +import androidx.work.WorkerParameters +import dagger.hilt.EntryPoint +import dagger.hilt.InstallIn +import dagger.hilt.android.EntryPointAccessors +import dagger.hilt.components.SingletonComponent +import kotlin.reflect.KClass + +@EntryPoint +@InstallIn(SingletonComponent::class) +interface HiltWorkerFactoryEntryPoint { + fun hiltWorkerFactory(): HiltWorkerFactory +} + +private const val WORKER_CLASS_NAME = "RouterWorkerDelegateClassName" + +/** + * Adds metadata to a WorkRequest to identify what [CoroutineWorker] the [DelegatingWorker] should + * delegate to + */ +internal fun KClass.delegatedData() = + Data.Builder() + .putString(WORKER_CLASS_NAME, qualifiedName) + .build() + +/** + * A worker that delegates sync to another [CoroutineWorker] constructed with a [HiltWorkerFactory]. + * + * This allows for creating and using [CoroutineWorker] instances with extended arguments + * without having to provide a custom WorkManager configuration that the app module needs to utilize. + * + * In other words, it allows for custom workers in a library module without having to own + * configuration of the WorkManager singleton. + */ +class DelegatingWorker( + appContext: Context, + workerParams: WorkerParameters, +) : CoroutineWorker(appContext, workerParams) { + + private val workerClassName = + workerParams.inputData.getString(WORKER_CLASS_NAME) ?: "" + + private val delegateWorker = + EntryPointAccessors.fromApplication(appContext) + .hiltWorkerFactory() + .createWorker(appContext, workerClassName, workerParams) + as? CoroutineWorker + ?: throw IllegalArgumentException("Unable to find appropriate worker") + + override suspend fun getForegroundInfo(): ForegroundInfo = + delegateWorker.getForegroundInfo() + + override suspend fun doWork(): Result = + delegateWorker.doWork() +} \ No newline at end of file diff --git a/core/sync/src/main/java/com/looker/sync/worker/SyncWorker.kt b/core/sync/src/main/java/com/looker/sync/worker/SyncWorker.kt new file mode 100644 index 00000000..04cadf84 --- /dev/null +++ b/core/sync/src/main/java/com/looker/sync/worker/SyncWorker.kt @@ -0,0 +1,27 @@ +package com.looker.sync.worker + +import android.content.Context +import androidx.hilt.work.HiltWorker +import androidx.work.CoroutineWorker +import androidx.work.ForegroundInfo +import androidx.work.WorkerParameters +import com.looker.core.datastore.UserPreferencesRepository +import dagger.assisted.Assisted +import dagger.assisted.AssistedInject + +@HiltWorker +class SyncWorker @AssistedInject constructor( + @Assisted private val appContext: Context, + @Assisted workerParams: WorkerParameters, + private val userPreferencesRepository: UserPreferencesRepository +): CoroutineWorker(appContext, workerParams) { + + override suspend fun getForegroundInfo(): ForegroundInfo = appContext.syncForegroundInfo() + + override suspend fun doWork(): Result { + TODO("Not yet implemented") + } + + + +} \ No newline at end of file diff --git a/core/sync/src/main/java/com/looker/sync/worker/SyncWorkerHelper.kt b/core/sync/src/main/java/com/looker/sync/worker/SyncWorkerHelper.kt new file mode 100644 index 00000000..3c1ef57b --- /dev/null +++ b/core/sync/src/main/java/com/looker/sync/worker/SyncWorkerHelper.kt @@ -0,0 +1,62 @@ +package com.looker.sync.worker + +import android.app.Notification +import android.app.NotificationChannel +import android.app.NotificationManager +import android.content.Context +import android.os.Build +import androidx.core.app.NotificationCompat +import androidx.work.Constraints +import androidx.work.ForegroundInfo +import androidx.work.NetworkType +import com.looker.core.datastore.UserPreferences +import com.looker.core.datastore.model.ProxyType +import com.looker.core.common.R as CommonR + +private const val SyncNotificationId = 0 +private const val SyncNotificationChannelID = "SyncNotificationChannel" + +// All sync work needs an internet connectionS +val SyncConstraints + get() = Constraints.Builder() + .setRequiredNetworkType(NetworkType.CONNECTED) + .build() + +/** + * Foreground information for sync on lower API levels when sync workers are being + * run with a foreground service + */ +fun Context.syncForegroundInfo() = ForegroundInfo( + SyncNotificationId, + syncWorkNotification(), +) + +/** + * Notification displayed on lower API levels when sync workers are being + * run with a foreground service + */ +private fun Context.syncWorkNotification(): Notification { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + val channel = NotificationChannel( + SyncNotificationChannelID, + getString(CommonR.string.sync_repositories), + NotificationManager.IMPORTANCE_DEFAULT, + ).apply { + description = getString(CommonR.string.sync_repositories) + } + // Register the channel with the system + val notificationManager: NotificationManager? = + getSystemService(Context.NOTIFICATION_SERVICE) as? NotificationManager + + notificationManager?.createNotificationChannel(channel) + } + + return NotificationCompat.Builder( + this, + SyncNotificationChannelID, + ) + .setSmallIcon(CommonR.drawable.ic_sync,) + .setContentTitle(getString(CommonR.string.syncing)) + .setPriority(NotificationCompat.PRIORITY_DEFAULT) + .build() +} \ No newline at end of file diff --git a/core/sync/src/test/java/com/looker/sync/ExampleUnitTest.kt b/core/sync/src/test/java/com/looker/sync/ExampleUnitTest.kt new file mode 100644 index 00000000..2bb9b3ca --- /dev/null +++ b/core/sync/src/test/java/com/looker/sync/ExampleUnitTest.kt @@ -0,0 +1,17 @@ +package com.looker.sync + +import org.junit.Test + +import org.junit.Assert.* + +/** + * Example local unit test, which will execute on the development machine (host). + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +class ExampleUnitTest { + @Test + fun addition_isCorrect() { + assertEquals(4, 2 + 2) + } +} \ No newline at end of file diff --git a/settings.gradle.kts b/settings.gradle.kts index 258103ce..14377c24 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -22,6 +22,7 @@ include( ":core:database", ":core:datastore", ":core:model", + ":core:sync", ":feature-settings", ":installer" -) \ No newline at end of file +)