diff --git a/build.gradle b/build.gradle index 4c8edca..8427e50 100644 --- a/build.gradle +++ b/build.gradle @@ -1,12 +1,12 @@ buildscript { ext.versions = [ - android: '3.4.1', - kotlin: '1.3.72' + android: '7.0.2', + kotlin: '1.5.30' ] repositories { + mavenCentral() google() - jcenter() } dependencies { @@ -19,14 +19,13 @@ apply plugin: 'com.android.application' apply plugin: 'kotlin-android' android { - compileSdkVersion 29 - buildToolsVersion '29.0.3' + compileSdkVersion 31 defaultConfig { archivesBaseName = 'foxy-droid' applicationId 'nya.kitsunyan.foxydroid' minSdkVersion 21 - targetSdkVersion 29 + targetSdkVersion 31 versionCode 4 versionName '1.3' @@ -111,18 +110,21 @@ android { } repositories { + mavenCentral() google() - jcenter() + maven { url 'https://jitpack.io' } } dependencies { - implementation 'org.jetbrains.kotlin:kotlin-stdlib-jdk7:' + versions.kotlin - implementation 'androidx.fragment:fragment:1.2.5' + implementation 'org.jetbrains.kotlin:kotlin-stdlib-jdk8:' + versions.kotlin + implementation 'androidx.fragment:fragment-ktx:1.3.6' implementation 'androidx.viewpager2:viewpager2:1.0.0' implementation 'androidx.vectordrawable:vectordrawable:1.1.0' - implementation 'com.squareup.okhttp3:okhttp:4.7.2' - implementation 'io.reactivex.rxjava3:rxjava:3.0.4' + implementation 'com.squareup.okhttp3:okhttp:5.0.0-alpha.2' + implementation 'io.reactivex.rxjava3:rxjava:3.1.1' implementation 'io.reactivex.rxjava3:rxandroid:3.0.0' - implementation 'com.fasterxml.jackson.core:jackson-core:2.11.0' - implementation 'com.squareup.picasso:picasso:2.71828' + implementation 'io.reactivex.rxjava3:rxkotlin:3.0.1' + implementation 'com.fasterxml.jackson.core:jackson-core:2.12.5' + implementation 'com.squareup.picasso:picasso:2.8' + implementation 'com.github.topjohnwu.libsu:core:3.1.2' } diff --git a/gradle.properties b/gradle.properties index 5bac8ac..e5635fe 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1 +1,4 @@ android.useAndroidX=true +android.enableJetifier=true +org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 +org.gradle.parallel=true diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 558870d..892ac8c 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,6 @@ +#Wed Sep 15 23:07:46 CEST 2021 distributionBase=GRADLE_USER_HOME +distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-bin.zip distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-5.1.1-bin.zip -zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME diff --git a/src/main/AndroidManifest.xml b/src/main/AndroidManifest.xml index 11237ad..cdf1c4d 100644 --- a/src/main/AndroidManifest.xml +++ b/src/main/AndroidManifest.xml @@ -9,6 +9,9 @@ + + android:name=".MainApplication$BootReceiver" + android:exported="false"> @@ -31,7 +35,8 @@ + android:windowSoftInputMode="adjustResize" + android:exported="true"> diff --git a/src/main/kotlin/nya/kitsunyan/foxydroid/MainApplication.kt b/src/main/kotlin/nya/kitsunyan/foxydroid/MainApplication.kt index 4a2de84..ef71399 100644 --- a/src/main/kotlin/nya/kitsunyan/foxydroid/MainApplication.kt +++ b/src/main/kotlin/nya/kitsunyan/foxydroid/MainApplication.kt @@ -9,31 +9,25 @@ import android.content.ComponentName import android.content.Context import android.content.Intent import android.content.IntentFilter -import android.content.pm.PackageInfo import com.squareup.picasso.OkHttp3Downloader import com.squareup.picasso.Picasso import nya.kitsunyan.foxydroid.content.Cache import nya.kitsunyan.foxydroid.content.Preferences import nya.kitsunyan.foxydroid.content.ProductPreferences import nya.kitsunyan.foxydroid.database.Database -import nya.kitsunyan.foxydroid.entity.InstalledItem import nya.kitsunyan.foxydroid.index.RepositoryUpdater import nya.kitsunyan.foxydroid.network.Downloader import nya.kitsunyan.foxydroid.network.PicassoDownloader import nya.kitsunyan.foxydroid.service.Connection import nya.kitsunyan.foxydroid.service.SyncService import nya.kitsunyan.foxydroid.utility.Utils +import nya.kitsunyan.foxydroid.utility.Utils.toInstalledItem import nya.kitsunyan.foxydroid.utility.extension.android.* import java.net.InetSocketAddress import java.net.Proxy @Suppress("unused") class MainApplication: Application() { - private fun PackageInfo.toInstalledItem(): InstalledItem { - val signatureString = singleSignature?.let(Utils::calculateHash).orEmpty() - return InstalledItem(packageName, versionName.orEmpty(), versionCodeCompat, signatureString) - } - override fun attachBaseContext(base: Context) { super.attachBaseContext(Utils.configureLocale(base)) } diff --git a/src/main/kotlin/nya/kitsunyan/foxydroid/content/Preferences.kt b/src/main/kotlin/nya/kitsunyan/foxydroid/content/Preferences.kt index 470fe68..dc5b1ce 100644 --- a/src/main/kotlin/nya/kitsunyan/foxydroid/content/Preferences.kt +++ b/src/main/kotlin/nya/kitsunyan/foxydroid/content/Preferences.kt @@ -15,7 +15,7 @@ object Preferences { private val subject = PublishSubject.create>() - private val keys = sequenceOf(Key.AutoSync, Key.IncompatibleVersions, Key.ProxyHost, Key.ProxyPort, Key.ProxyType, + private val keys = sequenceOf(Key.AutoSync, Key.IncompatibleVersions, Key.RootInstallation, Key.ProxyHost, Key.ProxyPort, Key.ProxyType, Key.SortOrder, Key.Theme, Key.UpdateNotify, Key.UpdateUnstable).map { Pair(it.name, it) }.toMap() fun init(context: Context) { @@ -82,6 +82,7 @@ object Preferences { sealed class Key(val name: String, val default: Value) { object AutoSync: Key("auto_sync", Value.EnumerationValue(Preferences.AutoSync.Wifi)) object IncompatibleVersions: Key("incompatible_versions", Value.BooleanValue(false)) + object RootInstallation : Key("root_installation",Value.BooleanValue(false)) object ProxyHost: Key("proxy_host", Value.StringValue("localhost")) object ProxyPort: Key("proxy_port", Value.IntValue(9050)) object ProxyType: Key("proxy_type", Value.EnumerationValue(Preferences.ProxyType.Direct)) diff --git a/src/main/kotlin/nya/kitsunyan/foxydroid/database/Database.kt b/src/main/kotlin/nya/kitsunyan/foxydroid/database/Database.kt index 7b7a24d..ab61b31 100644 --- a/src/main/kotlin/nya/kitsunyan/foxydroid/database/Database.kt +++ b/src/main/kotlin/nya/kitsunyan/foxydroid/database/Database.kt @@ -17,12 +17,13 @@ import nya.kitsunyan.foxydroid.utility.extension.android.* import nya.kitsunyan.foxydroid.utility.extension.json.* import java.io.ByteArrayOutputStream +// TODO migrate to Room object Database { fun init(context: Context): Boolean { val helper = Helper(context) db = helper.writableDatabase if (helper.created) { - for (repository in Repository.defaultRepositories) { + for (repository in Repository.defaultRepositories.sortedBy { it.name }) { RepositoryAdapter.put(repository) } } diff --git a/src/main/kotlin/nya/kitsunyan/foxydroid/entity/Product.kt b/src/main/kotlin/nya/kitsunyan/foxydroid/entity/Product.kt index 133e9b5..4e8b0d7 100644 --- a/src/main/kotlin/nya/kitsunyan/foxydroid/entity/Product.kt +++ b/src/main/kotlin/nya/kitsunyan/foxydroid/entity/Product.kt @@ -129,7 +129,7 @@ data class Product(val repositoryId: Long, val packageName: String, val name: St companion object { fun findSuggested(products: List, installedItem: InstalledItem?, extract: (T) -> Product): T? { - return products.maxWith(compareBy({ extract(it).compatible && + return products.maxWithOrNull(compareBy({ extract(it).compatible && (installedItem == null || installedItem.signature in extract(it).signatures) }, { extract(it).versionCode })) } diff --git a/src/main/kotlin/nya/kitsunyan/foxydroid/entity/Repository.kt b/src/main/kotlin/nya/kitsunyan/foxydroid/entity/Repository.kt index 6debcf0..0a5feb1 100644 --- a/src/main/kotlin/nya/kitsunyan/foxydroid/entity/Repository.kt +++ b/src/main/kotlin/nya/kitsunyan/foxydroid/entity/Repository.kt @@ -107,12 +107,104 @@ data class Repository(val id: Long, val address: String, val mirrors: List getString(R.string.dark) } } + addSwitch(Preferences.Key.RootInstallation, getString(R.string.root_installation), + getString(R.string.root_installation_summary)) addSwitch(Preferences.Key.IncompatibleVersions, getString(R.string.incompatible_versions), getString(R.string.incompatible_versions_summary)) } @@ -110,6 +113,7 @@ class PreferencesFragment: ScreenFragment() { preferences[Preferences.Key.ProxyHost]?.setEnabled(enabled) preferences[Preferences.Key.ProxyPort]?.setEnabled(enabled) } + preferences[Preferences.Key.RootInstallation]?.setEnabled(Shell.getShell().isRoot) if (key == Preferences.Key.Theme) { requireActivity().recreate() } diff --git a/src/main/kotlin/nya/kitsunyan/foxydroid/screen/ProductFragment.kt b/src/main/kotlin/nya/kitsunyan/foxydroid/screen/ProductFragment.kt index deb28c9..f359437 100644 --- a/src/main/kotlin/nya/kitsunyan/foxydroid/screen/ProductFragment.kt +++ b/src/main/kotlin/nya/kitsunyan/foxydroid/screen/ProductFragment.kt @@ -34,6 +34,7 @@ import nya.kitsunyan.foxydroid.service.Connection import nya.kitsunyan.foxydroid.service.DownloadService import nya.kitsunyan.foxydroid.utility.RxUtils import nya.kitsunyan.foxydroid.utility.Utils +import nya.kitsunyan.foxydroid.utility.Utils.startPackageInstaller import nya.kitsunyan.foxydroid.utility.extension.android.* import nya.kitsunyan.foxydroid.widget.DividerItemDecoration @@ -55,7 +56,7 @@ class ProductFragment(): ScreenFragment(), ProductAdapter.Callbacks { private enum class Action(val id: Int, val adapterAction: ProductAdapter.Action, val iconResId: Int) { INSTALL(1, ProductAdapter.Action.INSTALL, R.drawable.ic_archive), - UPDATE(2, ProductAdapter.Action.UPDATE, R.drawable.ic_archive), + UPDATE(2, ProductAdapter.Action.UPDATE, R.drawable.ic_update), LAUNCH(3, ProductAdapter.Action.LAUNCH, R.drawable.ic_launch), DETAILS(4, ProductAdapter.Action.DETAILS, R.drawable.ic_tune), UNINSTALL(5, ProductAdapter.Action.UNINSTALL, R.drawable.ic_delete) @@ -332,23 +333,7 @@ class ProductFragment(): ScreenFragment(), ProductAdapter.Callbacks { ProductAdapter.Action.INSTALL, ProductAdapter.Action.UPDATE -> { val installedItem = installed?.installedItem - val productRepository = Product.findSuggested(products, installedItem) { it.first } - val compatibleReleases = productRepository?.first?.selectedReleases.orEmpty() - .filter { installedItem == null || installedItem.signature == it.signature } - val release = if (compatibleReleases.size >= 2) { - compatibleReleases - .filter { it.platforms.contains(Android.primaryPlatform) } - .minBy { it.platforms.size } - ?: compatibleReleases.minBy { it.platforms.size } - ?: compatibleReleases.firstOrNull() - } else { - compatibleReleases.firstOrNull() - } - val binder = downloadConnection.binder - if (productRepository != null && release != null && binder != null) { - binder.enqueue(packageName, productRepository.first.name, productRepository.second, release) - } - Unit + Utils.startInstallUpdateAction(packageName, installedItem, products, downloadConnection) } ProductAdapter.Action.LAUNCH -> { val launcherActivities = installed?.launcherActivities.orEmpty() diff --git a/src/main/kotlin/nya/kitsunyan/foxydroid/screen/ProductsFragment.kt b/src/main/kotlin/nya/kitsunyan/foxydroid/screen/ProductsFragment.kt index 7af8a19..1cca4ac 100644 --- a/src/main/kotlin/nya/kitsunyan/foxydroid/screen/ProductsFragment.kt +++ b/src/main/kotlin/nya/kitsunyan/foxydroid/screen/ProductsFragment.kt @@ -30,10 +30,10 @@ class ProductsFragment(): ScreenFragment(), CursorOwner.Callback { private const val STATE_LAYOUT_MANAGER = "layoutManager" } - enum class Source(val titleResId: Int, val sections: Boolean, val order: Boolean) { - AVAILABLE(R.string.available, true, true), - INSTALLED(R.string.installed, false, false), - UPDATES(R.string.updates, false, false) + enum class Source(val titleResId: Int, val sections: Boolean, val order: Boolean, val updates: Boolean) { + AVAILABLE(R.string.available, true, true, false), + INSTALLED(R.string.installed, false, false, false), + UPDATES(R.string.updates, false, false, true) } constructor(source: Source): this() { diff --git a/src/main/kotlin/nya/kitsunyan/foxydroid/screen/ScreenActivity.kt b/src/main/kotlin/nya/kitsunyan/foxydroid/screen/ScreenActivity.kt index 2466e1c..b75d00e 100644 --- a/src/main/kotlin/nya/kitsunyan/foxydroid/screen/ScreenActivity.kt +++ b/src/main/kotlin/nya/kitsunyan/foxydroid/screen/ScreenActivity.kt @@ -2,7 +2,6 @@ package nya.kitsunyan.foxydroid.screen import android.content.Context import android.content.Intent -import android.net.Uri import android.os.Bundle import android.os.Parcel import android.view.View @@ -13,12 +12,11 @@ import android.widget.Toolbar import androidx.fragment.app.Fragment import androidx.fragment.app.FragmentActivity import nya.kitsunyan.foxydroid.R -import nya.kitsunyan.foxydroid.content.Cache import nya.kitsunyan.foxydroid.content.Preferences import nya.kitsunyan.foxydroid.database.CursorOwner import nya.kitsunyan.foxydroid.utility.KParcelable import nya.kitsunyan.foxydroid.utility.Utils -import nya.kitsunyan.foxydroid.utility.extension.android.* +import nya.kitsunyan.foxydroid.utility.Utils.startPackageInstaller import nya.kitsunyan.foxydroid.utility.extension.resources.* import nya.kitsunyan.foxydroid.utility.extension.text.* @@ -204,11 +202,7 @@ abstract class ScreenActivity: FragmentActivity() { is SpecialIntent.Install -> { val packageName = specialIntent.packageName if (!packageName.isNullOrEmpty()) { - val fragment = currentFragment - if (fragment !is ProductFragment || fragment.packageName != packageName) { - pushFragment(ProductFragment(packageName)) - } - specialIntent.cacheFileName?.let(::startPackageInstaller) + specialIntent.cacheFileName?.let { startPackageInstaller(it) } } Unit } @@ -229,18 +223,6 @@ abstract class ScreenActivity: FragmentActivity() { } } - internal fun startPackageInstaller(cacheFileName: String) { - val (uri, flags) = if (Android.sdk(24)) { - Pair(Cache.getReleaseUri(this, cacheFileName), Intent.FLAG_GRANT_READ_URI_PERMISSION) - } else { - Pair(Uri.fromFile(Cache.getReleaseFile(this, cacheFileName)), 0) - } - // TODO Handle deprecation - @Suppress("DEPRECATION") - startActivity(Intent(Intent.ACTION_INSTALL_PACKAGE) - .setDataAndType(uri, "application/vnd.android.package-archive").setFlags(flags)) - } - internal fun navigateProduct(packageName: String) = pushFragment(ProductFragment(packageName)) internal fun navigateRepositories() = pushFragment(RepositoriesFragment()) internal fun navigatePreferences() = pushFragment(PreferencesFragment()) diff --git a/src/main/kotlin/nya/kitsunyan/foxydroid/screen/TabsFragment.kt b/src/main/kotlin/nya/kitsunyan/foxydroid/screen/TabsFragment.kt index 61a2b6a..f973d77 100644 --- a/src/main/kotlin/nya/kitsunyan/foxydroid/screen/TabsFragment.kt +++ b/src/main/kotlin/nya/kitsunyan/foxydroid/screen/TabsFragment.kt @@ -1,7 +1,9 @@ package nya.kitsunyan.foxydroid.screen import android.animation.ValueAnimator +import android.app.AlertDialog import android.content.Context +import android.content.DialogInterface import android.content.res.ColorStateList import android.graphics.Canvas import android.graphics.ColorFilter @@ -16,13 +18,9 @@ import android.view.View import android.view.ViewGroup import android.view.animation.AccelerateInterpolator import android.view.animation.DecelerateInterpolator -import android.widget.FrameLayout -import android.widget.ImageView -import android.widget.LinearLayout -import android.widget.SearchView -import android.widget.TextView -import android.widget.Toolbar +import android.widget.* import androidx.fragment.app.Fragment +import androidx.fragment.app.findFragment import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import androidx.viewpager2.adapter.FragmentStateAdapter @@ -33,6 +31,7 @@ import io.reactivex.rxjava3.disposables.Disposable import io.reactivex.rxjava3.schedulers.Schedulers import nya.kitsunyan.foxydroid.R import nya.kitsunyan.foxydroid.content.Preferences +import nya.kitsunyan.foxydroid.database.CursorOwner import nya.kitsunyan.foxydroid.database.Database import nya.kitsunyan.foxydroid.entity.ProductItem import nya.kitsunyan.foxydroid.service.Connection @@ -61,6 +60,7 @@ class TabsFragment: ScreenFragment() { val sectionChange = view.findViewById(R.id.section_change)!! val sectionName = view.findViewById(R.id.section_name)!! val sectionIcon = view.findViewById(R.id.section_icon)!! + val updateAll = view.findViewById