From b77dc05b565c055d3183806647a403350f008dd4 Mon Sep 17 00:00:00 2001 From: LooKeR Date: Sat, 5 Aug 2023 17:40:11 +0530 Subject: [PATCH] Improvements: Installer (#394) --- .../com/looker/droidify/MainApplication.kt | 4 +-- .../com/looker/droidify/ScreenActivity.kt | 4 +-- .../droidify/service/DownloadService.kt | 4 +-- .../ui/app_detail/AppDetailFragment.kt | 28 +++++++------------ .../ui/app_detail/AppDetailViewModel.kt | 6 ++-- build.gradle.kts | 8 +++--- buildSrc/src/main/kotlin/Libs.kt | 11 ++++---- .../plugins/AndroidLibraryConventionPlugin.kt | 2 +- .../kotlin/plugins/HiltConventionPlugin.kt | 4 +-- .../{Installer.kt => InstallManager.kt} | 23 ++++++++++----- .../com/looker/installer/InstallModule.kt | 2 +- .../{BaseInstaller.kt => Installer.kt} | 6 ++-- .../installer/installers/LegacyInstaller.kt | 6 ++-- .../installer/installers/RootInstaller.kt | 6 ++-- .../installer/installers/SessionInstaller.kt | 6 ++-- .../installer/installers/ShizukuInstaller.kt | 6 ++-- .../looker/installer/model/InstallState.kt | 10 +------ .../installer/model/InstallerQueueState.kt | 2 +- 18 files changed, 66 insertions(+), 72 deletions(-) rename installer/src/main/java/com/looker/installer/{Installer.kt => InstallManager.kt} (88%) rename installer/src/main/java/com/looker/installer/installers/{BaseInstaller.kt => Installer.kt} (57%) diff --git a/app/src/main/kotlin/com/looker/droidify/MainApplication.kt b/app/src/main/kotlin/com/looker/droidify/MainApplication.kt index 22de0751d..b0a693f49 100644 --- a/app/src/main/kotlin/com/looker/droidify/MainApplication.kt +++ b/app/src/main/kotlin/com/looker/droidify/MainApplication.kt @@ -37,7 +37,7 @@ import com.looker.droidify.sync.toJobNetworkType import com.looker.droidify.utility.Utils.toInstalledItem import com.looker.droidify.utility.extension.android.getInstalledPackagesCompat import com.looker.droidify.work.CleanUpWorker -import com.looker.installer.Installer +import com.looker.installer.InstallManager import com.topjohnwu.superuser.Shell import dagger.hilt.android.HiltAndroidApp import kotlinx.coroutines.CoroutineScope @@ -65,7 +65,7 @@ class MainApplication : Application(), ImageLoaderFactory { private val userPreferenceFlow get() = userPreferencesRepository.userPreferencesFlow @Inject - lateinit var installer: Installer + lateinit var installer: InstallManager override fun onCreate() { super.onCreate() diff --git a/app/src/main/kotlin/com/looker/droidify/ScreenActivity.kt b/app/src/main/kotlin/com/looker/droidify/ScreenActivity.kt index 3db1b0b2f..fa5885361 100644 --- a/app/src/main/kotlin/com/looker/droidify/ScreenActivity.kt +++ b/app/src/main/kotlin/com/looker/droidify/ScreenActivity.kt @@ -34,7 +34,7 @@ import com.looker.droidify.ui.repository.RepositoriesFragment import com.looker.droidify.ui.repository.RepositoryFragment import com.looker.droidify.ui.settings.SettingsFragment import com.looker.droidify.ui.tabs_fragment.TabsFragment -import com.looker.installer.Installer +import com.looker.installer.InstallManager import com.looker.installer.model.installFrom import dagger.hilt.EntryPoint import dagger.hilt.InstallIn @@ -60,7 +60,7 @@ abstract class ScreenActivity : AppCompatActivity() { registerForActivityResult(ActivityResultContracts.RequestPermission()) { } @Inject - lateinit var installer: Installer + lateinit var installer: InstallManager private class FragmentStackItem( val className: String, val arguments: Bundle?, diff --git a/app/src/main/kotlin/com/looker/droidify/service/DownloadService.kt b/app/src/main/kotlin/com/looker/droidify/service/DownloadService.kt index 37f3be538..d6fe15914 100644 --- a/app/src/main/kotlin/com/looker/droidify/service/DownloadService.kt +++ b/app/src/main/kotlin/com/looker/droidify/service/DownloadService.kt @@ -19,7 +19,7 @@ import com.looker.core.model.Repository import com.looker.droidify.BuildConfig import com.looker.droidify.MainActivity import com.looker.droidify.utility.extension.android.getPackageArchiveInfoCompat -import com.looker.installer.Installer +import com.looker.installer.InstallManager import com.looker.installer.model.installFrom import com.looker.network.Downloader import com.looker.network.NetworkResponse @@ -53,7 +53,7 @@ class DownloadService : ConnectionService() { } @Inject - lateinit var installer: Installer + lateinit var installer: InstallManager sealed class State(val packageName: String) { object Idle : State("") diff --git a/app/src/main/kotlin/com/looker/droidify/ui/app_detail/AppDetailFragment.kt b/app/src/main/kotlin/com/looker/droidify/ui/app_detail/AppDetailFragment.kt index 3134887b3..abc3f5774 100644 --- a/app/src/main/kotlin/com/looker/droidify/ui/app_detail/AppDetailFragment.kt +++ b/app/src/main/kotlin/com/looker/droidify/ui/app_detail/AppDetailFragment.kt @@ -19,6 +19,7 @@ import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.SimpleItemAnimator import com.google.android.material.dialog.MaterialAlertDialogBuilder +import com.looker.core.common.extension.firstItemVisible import com.looker.core.common.extension.systemBarsPadding import com.looker.core.datastore.UserPreferencesRepository import com.looker.core.model.* @@ -34,7 +35,7 @@ import com.looker.droidify.utility.Utils import com.looker.droidify.utility.Utils.startUpdate import com.looker.droidify.utility.extension.android.getApplicationInfoCompat import com.looker.droidify.utility.extension.screenActivity -import com.looker.installer.Installer +import com.looker.installer.InstallManager import com.looker.installer.model.* import dagger.hilt.android.AndroidEntryPoint import kotlinx.coroutines.flow.* @@ -77,7 +78,7 @@ class AppDetailFragment() : ScreenFragment(), AppDetailAdapter.Callbacks { get() = requireArguments().getString(EXTRA_PACKAGE_NAME)!! @Inject - lateinit var installer: Installer + lateinit var installer: InstallManager @Inject lateinit var userPreferencesRepository: UserPreferencesRepository @@ -131,7 +132,6 @@ class AppDetailFragment() : ScreenFragment(), AppDetailAdapter.Callbacks { val adapter = AppDetailAdapter(this@AppDetailFragment) this.adapter = adapter (itemAnimator as SimpleItemAnimator).supportsChangeAnimations = false - addOnScrollListener(scrollListener) savedInstanceState?.getParcelable(STATE_ADAPTER) ?.let(adapter::restoreState) layoutManagerState = savedInstanceState?.getParcelable(STATE_LAYOUT_MANAGER) @@ -226,6 +226,13 @@ class AppDetailFragment() : ScreenFragment(), AppDetailAdapter.Callbacks { launch { viewModel.installerState.collect { updateInstallState(it) } } + launch { + recyclerView?.firstItemVisible?.collect { isFirstItemVisible -> + updateToolbarButtons() + toolbar.title = if (!isFirstItemVisible) products[0].first.name + else getString(stringRes.application) + } + } } } @@ -366,21 +373,6 @@ class AppDetailFragment() : ScreenFragment(), AppDetailAdapter.Callbacks { } } - private val scrollListener = object : RecyclerView.OnScrollListener() { - private var lastPosition = -1 - - override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) { - val position = - (recyclerView.layoutManager as LinearLayoutManager).findFirstVisibleItemPosition() - val lastPosition = lastPosition - this.lastPosition = position - if ((lastPosition == 0) != (position == 0)) { - updateToolbarTitle() - updateToolbarButtons() - } - } - } - override fun onActionClick(action: AppDetailAdapter.Action) { when (action) { AppDetailAdapter.Action.INSTALL, diff --git a/app/src/main/kotlin/com/looker/droidify/ui/app_detail/AppDetailViewModel.kt b/app/src/main/kotlin/com/looker/droidify/ui/app_detail/AppDetailViewModel.kt index 91d86b3be..10565c1f9 100644 --- a/app/src/main/kotlin/com/looker/droidify/ui/app_detail/AppDetailViewModel.kt +++ b/app/src/main/kotlin/com/looker/droidify/ui/app_detail/AppDetailViewModel.kt @@ -2,7 +2,7 @@ package com.looker.droidify.ui.app_detail import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope -import com.looker.installer.Installer +import com.looker.installer.InstallManager import com.looker.installer.model.InstallerQueueState import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.SharingStarted @@ -11,8 +11,8 @@ import javax.inject.Inject @HiltViewModel class AppDetailViewModel @Inject constructor( - installer: Installer -) : ViewModel(){ + installer: InstallManager +) : ViewModel() { val installerState = installer.getStatus().stateIn( scope = viewModelScope, diff --git a/build.gradle.kts b/build.gradle.kts index c612219ab..4934602d7 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,10 +1,10 @@ plugins { id("com.android.application") version "8.0.2" apply false id("com.android.library") version "8.0.2" apply false - id("org.jetbrains.kotlin.android") version Version.kotlin apply false - kotlin("plugin.serialization") version Version.kotlin apply false - id("com.google.devtools.ksp") version Version.ksp apply false - id("com.google.dagger.hilt.android") version Hilt.version apply false + id("org.jetbrains.kotlin.android") version Kotlin.version apply false + kotlin("plugin.serialization") version Kotlin.version apply false + id(Ksp.plugin) version Ksp.version apply false + id(Hilt.plugin) version Hilt.version apply false } tasks.register("clean", Delete::class) { diff --git a/buildSrc/src/main/kotlin/Libs.kt b/buildSrc/src/main/kotlin/Libs.kt index 69e594f6f..92c0c224b 100644 --- a/buildSrc/src/main/kotlin/Libs.kt +++ b/buildSrc/src/main/kotlin/Libs.kt @@ -61,11 +61,11 @@ private object FDroid { } object Hilt { - const val version = "2.46.1" + const val version = "2.47" const val android = "com.google.dagger:hilt-android:$version" const val compiler = "com.google.dagger:hilt-compiler:$version" - const val plugin = "dagger.hilt.android.plugin" + const val plugin = "com.google.dagger.hilt.android" private const val androidXHilt = "1.0.0" const val work = "androidx.hilt:hilt-work:$androidXHilt" const val androidX = "androidx.hilt:hilt-compiler:$androidXHilt" @@ -76,6 +76,7 @@ object Jackson { } object Kotlin { + const val version = "1.9.0" const val serialization = "org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.1" const val datetime = "org.jetbrains.kotlinx:kotlinx-datetime:0.4.0" } @@ -126,9 +127,9 @@ object Test { const val espresso = "androidx.test.espresso:espresso-core:3.5.1" } -object Version { - const val kotlin = "1.8.22" - const val ksp = "1.8.22-1.0.11" +object Ksp { + const val version = "${Kotlin.version}-1.0.12" + const val plugin = "com.google.devtools.ksp" } object Work { diff --git a/buildSrc/src/main/kotlin/plugins/AndroidLibraryConventionPlugin.kt b/buildSrc/src/main/kotlin/plugins/AndroidLibraryConventionPlugin.kt index 30827f81c..1c3e99962 100644 --- a/buildSrc/src/main/kotlin/plugins/AndroidLibraryConventionPlugin.kt +++ b/buildSrc/src/main/kotlin/plugins/AndroidLibraryConventionPlugin.kt @@ -12,7 +12,7 @@ class AndroidLibraryConventionPlugin : Plugin { } dependencies { - add("implementation", kotlin("stdlib")) + add("implementation", platform("org.jetbrains.kotlin:kotlin-bom:${Kotlin.version}")) } } } diff --git a/buildSrc/src/main/kotlin/plugins/HiltConventionPlugin.kt b/buildSrc/src/main/kotlin/plugins/HiltConventionPlugin.kt index 496b30a8b..65f847e3b 100644 --- a/buildSrc/src/main/kotlin/plugins/HiltConventionPlugin.kt +++ b/buildSrc/src/main/kotlin/plugins/HiltConventionPlugin.kt @@ -6,7 +6,7 @@ class HiltConventionPlugin : Plugin { override fun apply(target: Project) { with(target) { with(pluginManager) { - apply("dagger.hilt.android.plugin") + apply(Hilt.plugin) // KAPT must go last to avoid build warnings. // See: https://stackoverflow.com/questions/70550883/warning-the-following-options-were-not-recognized-by-any-processor-dagger-f apply("org.jetbrains.kotlin.kapt") @@ -24,7 +24,7 @@ class HiltWorkerConventionPlugin : Plugin { override fun apply(target: Project) { with(target) { with(pluginManager) { - apply("dagger.hilt.android.plugin") + apply(Hilt.plugin) apply("org.jetbrains.kotlin.kapt") } diff --git a/installer/src/main/java/com/looker/installer/Installer.kt b/installer/src/main/java/com/looker/installer/InstallManager.kt similarity index 88% rename from installer/src/main/java/com/looker/installer/Installer.kt rename to installer/src/main/java/com/looker/installer/InstallManager.kt index e98db20ed..e4a7918e2 100644 --- a/installer/src/main/java/com/looker/installer/Installer.kt +++ b/installer/src/main/java/com/looker/installer/InstallManager.kt @@ -18,7 +18,7 @@ import kotlinx.coroutines.flow.* import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock -class Installer( +class InstallManager( private val context: Context, userPreferencesRepository: UserPreferencesRepository ) { @@ -29,12 +29,12 @@ class Installer( private val installState = MutableStateFlow(InstallItemState.EMPTY) private val installQueue = MutableStateFlow(emptySet()) - private var _baseInstaller: BaseInstaller? = null + private var _installer: Installer? = null set(value) { field?.cleanup() field = value } - private val baseInstaller: BaseInstaller get() = _baseInstaller!! + private val installer: Installer get() = _installer!! private val lock = Mutex() private val installerPreference = userPreferencesRepository @@ -48,7 +48,7 @@ class Installer( } fun close() { - _baseInstaller = null + _installer = null uninstallItems.close() installItems.close() } @@ -88,7 +88,7 @@ class Installer( } installState.emit(item statesTo InstallState.Installing) val success = withTimeoutOrNull(20_000) { - baseInstaller.performInstall(item) + installer.install(item) } ?: InstallState.Failed installState.emit(item statesTo success) lock.withLock { currentQueue.remove(item.packageName.name) } @@ -101,13 +101,13 @@ class Installer( private fun CoroutineScope.uninstaller() = launch { uninstallItems.consumeEach { - baseInstaller.performUninstall(it) + installer.uninstall(it) } } private suspend fun setInstaller(installerType: InstallerType) { lock.withLock { - _baseInstaller = when (installerType) { + _installer = when (installerType) { InstallerType.LEGACY -> LegacyInstaller(context) InstallerType.SESSION -> SessionInstaller(context) InstallerType.SHIZUKU -> ShizukuInstaller(context) @@ -116,3 +116,12 @@ class Installer( } } } + +data class InstallerQueueState( + val currentItem: InstallItemState, + val queued: Set +) { + companion object { + val EMPTY = InstallerQueueState(InstallItemState.EMPTY, emptySet()) + } +} diff --git a/installer/src/main/java/com/looker/installer/InstallModule.kt b/installer/src/main/java/com/looker/installer/InstallModule.kt index be710faff..38aa45d2b 100644 --- a/installer/src/main/java/com/looker/installer/InstallModule.kt +++ b/installer/src/main/java/com/looker/installer/InstallModule.kt @@ -18,5 +18,5 @@ object InstallModule { fun providesInstaller( @ApplicationContext context: Context, userPreferencesRepository: UserPreferencesRepository - ): Installer = Installer(context, userPreferencesRepository) + ): InstallManager = InstallManager(context, userPreferencesRepository) } \ No newline at end of file diff --git a/installer/src/main/java/com/looker/installer/installers/BaseInstaller.kt b/installer/src/main/java/com/looker/installer/installers/Installer.kt similarity index 57% rename from installer/src/main/java/com/looker/installer/installers/BaseInstaller.kt rename to installer/src/main/java/com/looker/installer/installers/Installer.kt index 301dee1dd..30b3a7f77 100644 --- a/installer/src/main/java/com/looker/installer/installers/BaseInstaller.kt +++ b/installer/src/main/java/com/looker/installer/installers/Installer.kt @@ -4,11 +4,11 @@ import com.looker.core.model.newer.PackageName import com.looker.installer.model.InstallItem import com.looker.installer.model.InstallState -interface BaseInstaller { +interface Installer { - suspend fun performInstall(installItem: InstallItem): InstallState + suspend fun install(installItem: InstallItem): InstallState - suspend fun performUninstall(packageName: PackageName) + suspend fun uninstall(packageName: PackageName) fun cleanup() diff --git a/installer/src/main/java/com/looker/installer/installers/LegacyInstaller.kt b/installer/src/main/java/com/looker/installer/installers/LegacyInstaller.kt index edfdf3f45..f437c7645 100644 --- a/installer/src/main/java/com/looker/installer/installers/LegacyInstaller.kt +++ b/installer/src/main/java/com/looker/installer/installers/LegacyInstaller.kt @@ -13,13 +13,13 @@ import kotlinx.coroutines.suspendCancellableCoroutine import kotlin.coroutines.resume @Suppress("DEPRECATION") -internal class LegacyInstaller(private val context: Context) : BaseInstaller { +internal class LegacyInstaller(private val context: Context) : Installer { companion object { private const val APK_MIME = "application/vnd.android.package-archive" } - override suspend fun performInstall( + override suspend fun install( installItem: InstallItem ): InstallState = suspendCancellableCoroutine { cont -> val (uri, flags) = if (SdkCheck.isNougat) { @@ -46,7 +46,7 @@ internal class LegacyInstaller(private val context: Context) : BaseInstaller { } } - override suspend fun performUninstall(packageName: PackageName) = + override suspend fun uninstall(packageName: PackageName) = context.uninstallPackage(packageName) override fun cleanup() {} diff --git a/installer/src/main/java/com/looker/installer/installers/RootInstaller.kt b/installer/src/main/java/com/looker/installer/installers/RootInstaller.kt index 83bc3058a..e5c959d90 100644 --- a/installer/src/main/java/com/looker/installer/installers/RootInstaller.kt +++ b/installer/src/main/java/com/looker/installer/installers/RootInstaller.kt @@ -11,7 +11,7 @@ import kotlinx.coroutines.suspendCancellableCoroutine import java.io.File import kotlin.coroutines.resume -internal class RootInstaller(private val context: Context) : BaseInstaller { +internal class RootInstaller(private val context: Context) : Installer { companion object { private const val ROOT_INSTALL_PACKAGE = "cat %s | pm install --user %s -t -r -S %s" @@ -54,7 +54,7 @@ internal class RootInstaller(private val context: Context) : BaseInstaller { ) } - override suspend fun performInstall( + override suspend fun install( installItem: InstallItem ): InstallState = suspendCancellableCoroutine { cont -> val releaseFile = Cache.getReleaseFile(context, installItem.installFileName) @@ -66,7 +66,7 @@ internal class RootInstaller(private val context: Context) : BaseInstaller { } } - override suspend fun performUninstall(packageName: PackageName) = + override suspend fun uninstall(packageName: PackageName) = context.uninstallPackage(packageName) override fun cleanup() {} diff --git a/installer/src/main/java/com/looker/installer/installers/SessionInstaller.kt b/installer/src/main/java/com/looker/installer/installers/SessionInstaller.kt index fd67aba36..949f3c298 100644 --- a/installer/src/main/java/com/looker/installer/installers/SessionInstaller.kt +++ b/installer/src/main/java/com/looker/installer/installers/SessionInstaller.kt @@ -17,7 +17,7 @@ import com.looker.installer.model.InstallState import kotlinx.coroutines.suspendCancellableCoroutine import kotlin.coroutines.resume -internal class SessionInstaller(private val context: Context) : BaseInstaller { +internal class SessionInstaller(private val context: Context) : Installer { private val sessionInstaller = context.packageManager.packageInstaller private val intent = Intent(context, SessionInstallerService::class.java) @@ -33,7 +33,7 @@ internal class SessionInstaller(private val context: Context) : BaseInstaller { } } - override suspend fun performInstall( + override suspend fun install( installItem: InstallItem ): InstallState = suspendCancellableCoroutine { cont -> val cacheFile = Cache.getReleaseFile(context, installItem.installFileName) @@ -77,7 +77,7 @@ internal class SessionInstaller(private val context: Context) : BaseInstaller { } @SuppressLint("MissingPermission") - override suspend fun performUninstall(packageName: PackageName) = + override suspend fun uninstall(packageName: PackageName) = suspendCancellableCoroutine { cont -> intent.putExtra( SessionInstallerService.KEY_ACTION, diff --git a/installer/src/main/java/com/looker/installer/installers/ShizukuInstaller.kt b/installer/src/main/java/com/looker/installer/installers/ShizukuInstaller.kt index ae76055de..3c3de12f4 100644 --- a/installer/src/main/java/com/looker/installer/installers/ShizukuInstaller.kt +++ b/installer/src/main/java/com/looker/installer/installers/ShizukuInstaller.kt @@ -13,13 +13,13 @@ import java.io.InputStream import kotlin.coroutines.resume @Suppress("DEPRECATION") -internal class ShizukuInstaller(private val context: Context) : BaseInstaller { +internal class ShizukuInstaller(private val context: Context) : Installer { companion object { private val SESSION_ID_REGEX = Regex("(?<=\\[).+?(?=])") } - override suspend fun performInstall( + override suspend fun install( installItem: InstallItem ): InstallState = suspendCancellableCoroutine { cont -> var sessionId: String? = null @@ -67,7 +67,7 @@ internal class ShizukuInstaller(private val context: Context) : BaseInstaller { } } - override suspend fun performUninstall(packageName: PackageName) = + override suspend fun uninstall(packageName: PackageName) = context.uninstallPackage(packageName) override fun cleanup() {} diff --git a/installer/src/main/java/com/looker/installer/model/InstallState.kt b/installer/src/main/java/com/looker/installer/model/InstallState.kt index ed380393c..af0928272 100644 --- a/installer/src/main/java/com/looker/installer/model/InstallState.kt +++ b/installer/src/main/java/com/looker/installer/model/InstallState.kt @@ -1,11 +1,3 @@ package com.looker.installer.model -sealed interface InstallState { - - object Failed : InstallState - - object Installing : InstallState - - object Installed : InstallState - -} \ No newline at end of file +enum class InstallState { Failed, Installing, Installed } \ No newline at end of file diff --git a/installer/src/main/java/com/looker/installer/model/InstallerQueueState.kt b/installer/src/main/java/com/looker/installer/model/InstallerQueueState.kt index cbeeffcae..9200dc4c6 100644 --- a/installer/src/main/java/com/looker/installer/model/InstallerQueueState.kt +++ b/installer/src/main/java/com/looker/installer/model/InstallerQueueState.kt @@ -13,4 +13,4 @@ infix fun String.isQueuedIn(state: InstallerQueueState): Boolean = this in state infix fun String.isInstalling(state: InstallerQueueState): Boolean = this == state.currentItem.installedItem.packageName.name - && state.currentItem.state is InstallState.Installing \ No newline at end of file + && state.currentItem.state == InstallState.Installing \ No newline at end of file