diff --git a/source/app/src/main/ic_launcher-playstore.png b/source/app/src/main/ic_launcher-playstore.png index 011f256a06..1af026ff66 100644 Binary files a/source/app/src/main/ic_launcher-playstore.png and b/source/app/src/main/ic_launcher-playstore.png differ diff --git a/source/app/src/main/res/drawable/ic_launcher_foreground.xml b/source/app/src/main/res/drawable/ic_launcher_foreground.xml index 682eea297a..1c52454b4e 100644 --- a/source/app/src/main/res/drawable/ic_launcher_foreground.xml +++ b/source/app/src/main/res/drawable/ic_launcher_foreground.xml @@ -1,6 +1,6 @@ + + + + + \ No newline at end of file diff --git a/source/app/src/main/res/mipmap-hdpi/ic_launcher.webp b/source/app/src/main/res/mipmap-hdpi/ic_launcher.webp new file mode 100644 index 0000000000..84d6ab69f9 Binary files /dev/null and b/source/app/src/main/res/mipmap-hdpi/ic_launcher.webp differ diff --git a/source/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp b/source/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp new file mode 100644 index 0000000000..d8de8a5704 Binary files /dev/null and b/source/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp differ diff --git a/source/app/src/main/res/mipmap-mdpi/ic_launcher.webp b/source/app/src/main/res/mipmap-mdpi/ic_launcher.webp new file mode 100644 index 0000000000..0e2910eccc Binary files /dev/null and b/source/app/src/main/res/mipmap-mdpi/ic_launcher.webp differ diff --git a/source/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp b/source/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp new file mode 100644 index 0000000000..1e9c991531 Binary files /dev/null and b/source/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp differ diff --git a/source/app/src/main/res/mipmap-xhdpi/ic_launcher.webp b/source/app/src/main/res/mipmap-xhdpi/ic_launcher.webp new file mode 100644 index 0000000000..a50aad84ca Binary files /dev/null and b/source/app/src/main/res/mipmap-xhdpi/ic_launcher.webp differ diff --git a/source/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp b/source/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp new file mode 100644 index 0000000000..6ffdee30bd Binary files /dev/null and b/source/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp differ diff --git a/source/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp b/source/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp new file mode 100644 index 0000000000..0a57f79349 Binary files /dev/null and b/source/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp differ diff --git a/source/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp b/source/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp new file mode 100644 index 0000000000..bdb4e30efb Binary files /dev/null and b/source/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp differ diff --git a/source/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp b/source/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp new file mode 100644 index 0000000000..ce25e0e1cd Binary files /dev/null and b/source/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp differ diff --git a/source/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp b/source/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp new file mode 100644 index 0000000000..5ec0d60a75 Binary files /dev/null and b/source/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp differ diff --git a/source/core/data/src/main/kotlin/com/xayah/core/data/repository/CloudRepository.kt b/source/core/data/src/main/kotlin/com/xayah/core/data/repository/CloudRepository.kt index 2a2002fd77..473e54da83 100644 --- a/source/core/data/src/main/kotlin/com/xayah/core/data/repository/CloudRepository.kt +++ b/source/core/data/src/main/kotlin/com/xayah/core/data/repository/CloudRepository.kt @@ -42,6 +42,7 @@ class CloudRepository @Inject constructor( var isSuccess = true val out = mutableListOf() + PathUtil.setFilesDirSELinux(context) runCatching { client.upload(src = src, dst = dstDir, onUploading = onUploading) diff --git a/source/core/data/src/main/kotlin/com/xayah/core/data/repository/DirectoryRepository.kt b/source/core/data/src/main/kotlin/com/xayah/core/data/repository/DirectoryRepository.kt index 1ef27c4f4f..885a96718b 100644 --- a/source/core/data/src/main/kotlin/com/xayah/core/data/repository/DirectoryRepository.kt +++ b/source/core/data/src/main/kotlin/com/xayah/core/data/repository/DirectoryRepository.kt @@ -45,9 +45,8 @@ class DirectoryRepository @Inject constructor( val customDirList = mutableListOf() pathList.forEach { pathString -> if (pathString.isNotEmpty()) { - val path = Paths.get(pathString) - val parent = path.parent.pathString - val child = path.name + val parent = PathUtil.getParentPath(pathString) + val child = PathUtil.getFileName(pathString) // Custom storage val dir = DirectoryUpsertEntity( diff --git a/source/core/data/src/main/kotlin/com/xayah/core/data/repository/PackageRepository.kt b/source/core/data/src/main/kotlin/com/xayah/core/data/repository/PackageRepository.kt index cacb5a8bde..4f262a65f3 100644 --- a/source/core/data/src/main/kotlin/com/xayah/core/data/repository/PackageRepository.kt +++ b/source/core/data/src/main/kotlin/com/xayah/core/data/repository/PackageRepository.kt @@ -300,7 +300,7 @@ class PackageRepository @Inject constructor( this.storageStats.appBytes = stats.appBytes this.storageStats.cacheBytes = stats.cacheBytes this.storageStats.dataBytes = stats.dataBytes - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) this.storageStats.externalCacheBytes = stats.externalCacheBytes + this.storageStats.externalCacheBytes = stats.externalCacheBytes } } } diff --git a/source/core/hiddenapi/src/main/java/android/os/UserHandleHidden.java b/source/core/hiddenapi/src/main/java/android/os/UserHandleHidden.java index 187e7a0b59..adc50c4bb6 100644 --- a/source/core/hiddenapi/src/main/java/android/os/UserHandleHidden.java +++ b/source/core/hiddenapi/src/main/java/android/os/UserHandleHidden.java @@ -10,4 +10,8 @@ public class UserHandleHidden { public static UserHandle of(int userId) { throw new RuntimeException("Stub!"); } + + public int getIdentifier() { + throw new RuntimeException("Stub!"); + } } diff --git a/source/core/model/src/main/kotlin/com/xayah/core/model/util/ModelUtil.kt b/source/core/model/src/main/kotlin/com/xayah/core/model/util/ModelUtil.kt index cac168db97..8f9f2ab99f 100644 --- a/source/core/model/src/main/kotlin/com/xayah/core/model/util/ModelUtil.kt +++ b/source/core/model/src/main/kotlin/com/xayah/core/model/util/ModelUtil.kt @@ -1,5 +1,6 @@ package com.xayah.core.model.util +import android.os.Build import com.xayah.core.model.CompressionType import com.xayah.core.model.KillAppOption import com.xayah.core.model.LZ4_SUFFIX @@ -53,7 +54,7 @@ fun SelectionType.Companion.of(name: String?): SelectionType = runCatching { SelectionType.valueOf(name!!.uppercase()) }.getOrDefault(SelectionType.DEFAULT) fun ThemeType.Companion.of(name: String?): ThemeType = - runCatching { ThemeType.valueOf(name!!.uppercase()) }.getOrDefault(ThemeType.AUTO) + runCatching { ThemeType.valueOf(name!!.uppercase()) }.getOrDefault(if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) ThemeType.AUTO else ThemeType.LIGHT_THEME) fun SmbAuthMode.Companion.indexOf(index: Int): SmbAuthMode = when (index) { 1 -> SmbAuthMode.GUEST diff --git a/source/core/network/src/main/kotlin/com/xayah/core/network/client/FTPClientImpl.kt b/source/core/network/src/main/kotlin/com/xayah/core/network/client/FTPClientImpl.kt index 725d0e878a..1f3c63b154 100644 --- a/source/core/network/src/main/kotlin/com/xayah/core/network/client/FTPClientImpl.kt +++ b/source/core/network/src/main/kotlin/com/xayah/core/network/client/FTPClientImpl.kt @@ -11,6 +11,7 @@ import com.xayah.core.network.util.getExtraEntity import com.xayah.core.rootservice.parcelables.PathParcelable import com.xayah.core.util.GsonUtil import com.xayah.core.util.LogUtil +import com.xayah.core.util.PathUtil import com.xayah.core.util.toPathList import com.xayah.core.util.withMainContext import com.xayah.libpickyou.parcelables.DirChildrenParcelable @@ -25,10 +26,7 @@ import java.io.FileInputStream import java.io.IOException import java.io.InputStream import java.io.OutputStream -import java.nio.file.Paths import javax.security.auth.login.LoginException -import kotlin.io.path.Path -import kotlin.io.path.pathString class FTPClientImpl(private val entity: CloudEntity, private val extra: FTPExtra) : CloudClient { private var client: FTPClient? = null @@ -88,7 +86,7 @@ class FTPClientImpl(private val entity: CloudEntity, private val extra: FTPExtra } override fun upload(src: String, dst: String, onUploading: (read: Long, total: Long) -> Unit) = withClient { client -> - val name = Paths.get(src).fileName + val name = PathUtil.getFileName(src) val dstPath = "$dst/$name" log { "upload: $src to $dstPath" } val srcFile = File(src) @@ -102,7 +100,7 @@ class FTPClientImpl(private val entity: CloudEntity, private val extra: FTPExtra } override fun download(src: String, dst: String, onDownloading: (written: Long, total: Long) -> Unit) = withClient { client -> - val name = Paths.get(src).fileName + val name = PathUtil.getFileName(src) val dstPath = "$dst/$name" log { "download: $src to $dstPath" } val dstFile = File(dstPath) @@ -130,11 +128,10 @@ class FTPClientImpl(private val entity: CloudEntity, private val extra: FTPExtra private fun listFile(src: String): FTPFile { var srcFile: FTPFile? = null withClient { client -> - val srcPath = Path(src) srcFile = client.mlistFile(src) if (srcFile == null) { - srcFile = client.listFiles(runCatching { srcPath.parent.pathString }.getOrElse { "." }) - .firstOrNull { it.name == srcPath.fileName.pathString } + srcFile = client.listFiles(runCatching { PathUtil.getParentPath(src) }.getOrElse { "." }) + .firstOrNull { it.name == PathUtil.getFileName(src) } } } if (srcFile != null) { @@ -253,7 +250,7 @@ class FTPClientImpl(private val entity: CloudEntity, private val extra: FTPExtra connect() PickYouLauncher.apply { val prefix = "${context.getString(R.string.cloud)}:" - sTraverseBackend = { listFiles(it.pathString.replaceFirst(prefix, "")) } + sTraverseBackend = { listFiles(it.replaceFirst(prefix, "")) } sMkdirsBackend = { parent, child -> runCatching { mkdirRecursively(handleOriginalPath("$parent/$child")) }.isSuccess } diff --git a/source/core/network/src/main/kotlin/com/xayah/core/network/client/SFTPClientImpl.kt b/source/core/network/src/main/kotlin/com/xayah/core/network/client/SFTPClientImpl.kt index 474473b10e..c7cb9b2882 100644 --- a/source/core/network/src/main/kotlin/com/xayah/core/network/client/SFTPClientImpl.kt +++ b/source/core/network/src/main/kotlin/com/xayah/core/network/client/SFTPClientImpl.kt @@ -11,6 +11,7 @@ import com.xayah.core.network.util.getExtraEntity import com.xayah.core.rootservice.parcelables.PathParcelable import com.xayah.core.util.GsonUtil import com.xayah.core.util.LogUtil +import com.xayah.core.util.PathUtil import com.xayah.core.util.toPathList import com.xayah.core.util.withMainContext import com.xayah.libpickyou.parcelables.DirChildrenParcelable @@ -27,8 +28,6 @@ import net.schmizz.sshj.userauth.password.PasswordFinder import net.schmizz.sshj.userauth.password.Resource import java.io.File import java.io.FileOutputStream -import java.nio.file.Paths -import kotlin.io.path.pathString class SFTPClientImpl(private val entity: CloudEntity, private val extra: SFTPExtra) : CloudClient { @@ -109,7 +108,7 @@ class SFTPClientImpl(private val entity: CloudEntity, private val extra: SFTPExt } override fun upload(src: String, dst: String, onUploading: (read: Long, total: Long) -> Unit) { - val name = Paths.get(src).fileName + val name = PathUtil.getFileName(src) val dstPath = "$dst/$name" log { "upload: $src to $dstPath" } val dstFile = openFile(dstPath) @@ -126,7 +125,7 @@ class SFTPClientImpl(private val entity: CloudEntity, private val extra: SFTPExt } override fun download(src: String, dst: String, onDownloading: (written: Long, total: Long) -> Unit) { - val name = Paths.get(src).fileName + val name = PathUtil.getFileName(src) val dstPath = "${dst}/$name" log { "download: $src to $dstPath" } val dstFile = File(dstPath) @@ -221,7 +220,7 @@ class SFTPClientImpl(private val entity: CloudEntity, private val extra: SFTPExt connect() PickYouLauncher.apply { val prefix = "${context.getString(R.string.cloud)}:" - sTraverseBackend = { listFiles(it.pathString.replaceFirst(prefix, ".")) } + sTraverseBackend = { listFiles(it.replaceFirst(prefix, ".")) } sMkdirsBackend = { parent, child -> runCatching { mkdirRecursively(handleOriginalPath("$parent/$child")) }.isSuccess } diff --git a/source/core/network/src/main/kotlin/com/xayah/core/network/client/SMBClientImpl.kt b/source/core/network/src/main/kotlin/com/xayah/core/network/client/SMBClientImpl.kt index bc3f3468d4..c697e61e98 100644 --- a/source/core/network/src/main/kotlin/com/xayah/core/network/client/SMBClientImpl.kt +++ b/source/core/network/src/main/kotlin/com/xayah/core/network/client/SMBClientImpl.kt @@ -30,6 +30,7 @@ import com.xayah.core.network.util.getExtraEntity import com.xayah.core.rootservice.parcelables.PathParcelable import com.xayah.core.util.GsonUtil import com.xayah.core.util.LogUtil +import com.xayah.core.util.PathUtil import com.xayah.core.util.SymbolUtil import com.xayah.core.util.toPathList import com.xayah.core.util.withLog @@ -41,8 +42,6 @@ import com.xayah.libpickyou.ui.model.PickerType import java.io.File import java.io.FileInputStream import java.io.IOException -import java.nio.file.Paths -import kotlin.io.path.pathString class SMBClientImpl(private val entity: CloudEntity, private val extra: SMBExtra) : CloudClient { @@ -200,7 +199,7 @@ class SMBClientImpl(private val entity: CloudEntity, private val extra: SMBExtra } override fun upload(src: String, dst: String, onUploading: (read: Long, total: Long) -> Unit) = run { - val name = Paths.get(src).fileName + val name = PathUtil.getFileName(src) val dstPath = "$dst/$name" log { "upload: $src to $dstPath" } val dstFile = openFile(dstPath) @@ -216,7 +215,7 @@ class SMBClientImpl(private val entity: CloudEntity, private val extra: SMBExtra } override fun download(src: String, dst: String, onDownloading: (written: Long, total: Long) -> Unit) = run { - val name = Paths.get(src).fileName + val name = PathUtil.getFileName(src) val dstPath = "$dst/$name" log { "download: $src to $dstPath" } val dstOutputStream = File(dstPath).outputStream() @@ -343,7 +342,7 @@ class SMBClientImpl(private val entity: CloudEntity, private val extra: SMBExtra connect() PickYouLauncher.apply { val prefix = "${context.getString(R.string.cloud)}:" - sTraverseBackend = { listFiles(it.pathString.replaceFirst(prefix, "")) } + sTraverseBackend = { listFiles(it.replaceFirst(prefix, "")) } sMkdirsBackend = { parent, child -> val (_, target) = handleOriginalPath("$parent/$child") runCatching { mkdirRecursively(target) }.isSuccess diff --git a/source/core/network/src/main/kotlin/com/xayah/core/network/client/WebDAVClientImpl.kt b/source/core/network/src/main/kotlin/com/xayah/core/network/client/WebDAVClientImpl.kt index 56f797a6e0..8da9579f31 100644 --- a/source/core/network/src/main/kotlin/com/xayah/core/network/client/WebDAVClientImpl.kt +++ b/source/core/network/src/main/kotlin/com/xayah/core/network/client/WebDAVClientImpl.kt @@ -6,6 +6,7 @@ import com.xayah.core.model.database.CloudEntity import com.xayah.core.network.R import com.xayah.core.rootservice.parcelables.PathParcelable import com.xayah.core.util.LogUtil +import com.xayah.core.util.PathUtil import com.xayah.core.util.toPathList import com.xayah.core.util.withMainContext import com.xayah.libpickyou.parcelables.DirChildrenParcelable @@ -15,9 +16,7 @@ import com.xayah.libpickyou.ui.model.PickerType import com.xayah.libsardine.impl.OkHttpSardine import okhttp3.OkHttpClient import java.io.File -import java.nio.file.Paths import java.util.concurrent.TimeUnit -import kotlin.io.path.pathString class WebDAVClientImpl(private val entity: CloudEntity) : CloudClient { private var client: OkHttpSardine? = null @@ -72,7 +71,7 @@ class WebDAVClientImpl(private val entity: CloudEntity) : CloudClient { } override fun upload(src: String, dst: String, onUploading: (read: Long, total: Long) -> Unit) = withClient { client -> - val name = Paths.get(src).fileName + val name = PathUtil.getFileName(src) val dstPath = "${getPath(dst)}/$name" log { "upload: $src to $dstPath" } val srcFile = File(src) @@ -80,7 +79,7 @@ class WebDAVClientImpl(private val entity: CloudEntity) : CloudClient { } override fun download(src: String, dst: String, onDownloading: (written: Long, total: Long) -> Unit) = withClient { client -> - val name = Paths.get(src).fileName + val name = PathUtil.getFileName(src) val dstPath = "${dst}/$name" log { "download: ${getPath(src)} to $dstPath" } val dstOutputStream = File(dstPath).outputStream() @@ -191,7 +190,7 @@ class WebDAVClientImpl(private val entity: CloudEntity) : CloudClient { connect() PickYouLauncher.apply { val prefix = "${context.getString(R.string.cloud)}:" - sTraverseBackend = { listFiles(it.pathString.replaceFirst(prefix, "")) } + sTraverseBackend = { listFiles(it.replaceFirst(prefix, "")) } sMkdirsBackend = { parent, child -> runCatching { mkdirRecursively(handleOriginalPath("$parent/$child")) }.isSuccess } diff --git a/source/core/rootservice/src/main/aidl/com/xayah/core/rootservice/IRemoteRootService.aidl b/source/core/rootservice/src/main/aidl/com/xayah/core/rootservice/IRemoteRootService.aidl index 55291feb3d..0b7814d92e 100644 --- a/source/core/rootservice/src/main/aidl/com/xayah/core/rootservice/IRemoteRootService.aidl +++ b/source/core/rootservice/src/main/aidl/com/xayah/core/rootservice/IRemoteRootService.aidl @@ -1,6 +1,7 @@ package com.xayah.core.rootservice; import com.xayah.core.rootservice.parcelables.StatFsParcelable; +import com.xayah.core.rootservice.parcelables.StorageStatsParcelable; interface IRemoteRootService { StatFsParcelable readStatFs(String path); @@ -28,7 +29,7 @@ interface IRemoteRootService { boolean queryInstalled(String packageName, int userId); int getPackageUid(String packageName, int userId); UserHandle getUserHandle(int userId); - StorageStats queryStatsForPackage(in PackageInfo packageInfo, in UserHandle user); + StorageStatsParcelable queryStatsForPackage(in PackageInfo packageInfo, in UserHandle user); List getUsers(); ParcelFileDescriptor walkFileTree(String path); PackageInfo getPackageArchiveInfo(String path); diff --git a/source/core/rootservice/src/main/aidl/com/xayah/core/rootservice/parcelables/StorageStatsParcelable.aidl b/source/core/rootservice/src/main/aidl/com/xayah/core/rootservice/parcelables/StorageStatsParcelable.aidl new file mode 100644 index 0000000000..f259a7adcd --- /dev/null +++ b/source/core/rootservice/src/main/aidl/com/xayah/core/rootservice/parcelables/StorageStatsParcelable.aidl @@ -0,0 +1,2 @@ +package com.xayah.core.rootservice.parcelables; +parcelable StorageStatsParcelable; \ No newline at end of file diff --git a/source/core/rootservice/src/main/kotlin/com/xayah/core/rootservice/impl/RemoteRootServiceImpl.kt b/source/core/rootservice/src/main/kotlin/com/xayah/core/rootservice/impl/RemoteRootServiceImpl.kt index 0ad6a13c88..4bf6625c6d 100644 --- a/source/core/rootservice/src/main/kotlin/com/xayah/core/rootservice/impl/RemoteRootServiceImpl.kt +++ b/source/core/rootservice/src/main/kotlin/com/xayah/core/rootservice/impl/RemoteRootServiceImpl.kt @@ -1,8 +1,8 @@ package com.xayah.core.rootservice.impl +import android.annotation.TargetApi import android.app.ActivityManagerHidden import android.app.ActivityThread -import android.app.usage.StorageStats import android.app.usage.StorageStatsManager import android.content.Context import android.content.pm.PackageInfo @@ -25,11 +25,13 @@ import com.xayah.core.hiddenapi.castTo import com.xayah.core.rootservice.IRemoteRootService import com.xayah.core.rootservice.parcelables.PathParcelable import com.xayah.core.rootservice.parcelables.StatFsParcelable +import com.xayah.core.rootservice.parcelables.StorageStatsParcelable import com.xayah.core.rootservice.util.ExceptionUtil.tryOn import com.xayah.core.rootservice.util.ExceptionUtil.tryWithBoolean import com.xayah.core.rootservice.util.SsaidUtil import com.xayah.core.util.FileUtil import com.xayah.core.util.HashUtil +import com.xayah.core.util.PathUtil import com.xayah.core.util.command.BaseUtil.setAllPermissions import java.io.File import java.io.IOException @@ -51,12 +53,12 @@ internal class RemoteRootServiceImpl : IRemoteRootService.Stub() { private var systemContext: Context private var packageManager: PackageManager private var packageManagerHidden: PackageManagerHidden - private var storageStatsManager: StorageStatsManager private var userManager: UserManagerHidden private var activityManager: ActivityManagerHidden private fun getSystemContext(): Context = ActivityThread.systemMain().systemContext + @TargetApi(Build.VERSION_CODES.O) private fun getStorageStatsManager(): StorageStatsManager = systemContext.getSystemService(Context.STORAGE_STATS_SERVICE) as StorageStatsManager private fun getUserManager(): UserManagerHidden = UserManagerHidden.get(systemContext).castTo() @@ -86,7 +88,6 @@ internal class RemoteRootServiceImpl : IRemoteRootService.Stub() { systemContext = getSystemContext() packageManager = systemContext.packageManager packageManagerHidden = packageManager.castTo() - storageStatsManager = getStorageStatsManager() userManager = getUserManager() activityManager = getActivityManager() } @@ -180,30 +181,54 @@ internal class RemoteRootServiceImpl : IRemoteRootService.Stub() { override fun clearEmptyDirectoriesRecursively(path: String) = synchronized(lock) { tryOn { - Files.walkFileTree(Paths.get(path), object : SimpleFileVisitor() { - override fun preVisitDirectory(dir: Path?, attrs: BasicFileAttributes?): FileVisitResult { - if (dir != null && attrs != null) { - if (Files.isDirectory(dir) && Files.list(dir).count() == 0L) { - // Empty dir - Files.delete(dir) - } + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + clearEmptyDirectoriesRecursivelyApi26(path) + } else { + clearEmptyDirectoriesRecursivelyApi24(path) + } + } + } + + private fun clearEmptyDirectoriesRecursivelyApi24(path: String) { + val dir = File(path) + if (dir.isDirectory) { + dir.listFiles()?.also { items -> + if (items.isEmpty()) { + dir.delete() + } else { + items.forEach { + clearEmptyDirectoriesRecursivelyApi24(it.absolutePath) } - return FileVisitResult.CONTINUE } + } + } + } - override fun visitFile(file: Path?, attrs: BasicFileAttributes?): FileVisitResult { - return FileVisitResult.CONTINUE + @TargetApi(Build.VERSION_CODES.O) + private fun clearEmptyDirectoriesRecursivelyApi26(path: String) { + Files.walkFileTree(Paths.get(path), object : SimpleFileVisitor() { + override fun preVisitDirectory(dir: Path?, attrs: BasicFileAttributes?): FileVisitResult { + if (dir != null && attrs != null) { + if (Files.isDirectory(dir) && Files.list(dir).count() == 0L) { + // Empty dir + Files.delete(dir) + } } + return FileVisitResult.CONTINUE + } - override fun visitFileFailed(file: Path?, exc: IOException?): FileVisitResult { - return FileVisitResult.CONTINUE - } + override fun visitFile(file: Path?, attrs: BasicFileAttributes?): FileVisitResult { + return FileVisitResult.CONTINUE + } - override fun postVisitDirectory(dir: Path?, exc: IOException?): FileVisitResult { - return FileVisitResult.CONTINUE - } - }) - } + override fun visitFileFailed(file: Path?, exc: IOException?): FileVisitResult { + return FileVisitResult.CONTINUE + } + + override fun postVisitDirectory(dir: Path?, exc: IOException?): FileVisitResult { + return FileVisitResult.CONTINUE + } + }) } override fun setAllPermissions(src: String): Unit = synchronized(lock) { File(src).setAllPermissions() } @@ -278,17 +303,60 @@ internal class RemoteRootServiceImpl : IRemoteRootService.Stub() { UserHandleHidden.of(userId) } - override fun queryStatsForPackage(packageInfo: PackageInfo, user: UserHandle): StorageStats? = synchronized(lock) { - tryOn( - block = { - storageStatsManager.queryStatsForPackage(packageInfo.applicationInfo.storageUuid, packageInfo.packageName, user) - }, - onException = { - null - } + private fun queryStatsForPackageApi24(packageInfo: PackageInfo, user: UserHandle): StorageStatsParcelable { + val userHandle: UserHandleHidden = user.castTo() + // https://cs.android.com/android/platform/superproject/+/android-8.0.0_r51:frameworks/base/services/core/java/com/android/server/pm/Installer.java;l=225 + // https://cs.android.com/android/platform/superproject/+/android-8.0.0_r51:frameworks/native/cmds/installd/InstalldNativeService.cpp;l=1340 + // https://cs.android.com/android/platform/superproject/+/android-8.0.0_r51:frameworks/base/services/usage/java/com/android/server/usage/StorageStatsService.java;l=439 + // https://cs.android.com/android/platform/superproject/+/android-8.0.0_r51:frameworks/base/core/java/android/app/usage/StorageStats.java;l=31 + val userDir = "${PathUtil.getPackageUserDir(userHandle.identifier)}/${packageInfo.packageName}" + val userCacheDir = "$userDir/cache" + val userCodeCacheDir = "$userDir}/code_cache" + val dataDir = "${PathUtil.getPackageDataDir(userHandle.identifier)}/${packageInfo.packageName}" + val dataCacheDir = "$dataDir/cache" + val dataCodeCacheDir = "$dataDir}/code_cache" + val obbDir = "${PathUtil.getPackageObbDir(userHandle.identifier)}/${packageInfo.packageName}" + val obbCacheDir = "$obbDir/cache" + val obbCodeCacheDir = "$obbDir}/code_cache" + val mediaDir = "${PathUtil.getPackageMediaDir(userHandle.identifier)}/${packageInfo.packageName}" + val mediaCacheDir = "$mediaDir/cache" + val mediaCodeCacheDir = "$mediaDir}/code_cache" + + val cacheDirSize = FileUtil.calculateSize(userCacheDir) + FileUtil.calculateSize(userCodeCacheDir) + + FileUtil.calculateSize(dataCacheDir) + FileUtil.calculateSize(dataCodeCacheDir) + + FileUtil.calculateSize(obbCacheDir) + FileUtil.calculateSize(obbCodeCacheDir) + + FileUtil.calculateSize(mediaCacheDir) + FileUtil.calculateSize(mediaCodeCacheDir) + val dataDirSize = FileUtil.calculateSize(userDir) + FileUtil.calculateSize(dataDir) + FileUtil.calculateSize(obbDir) + FileUtil.calculateSize(mediaDir) - cacheDirSize + return StorageStatsParcelable( + FileUtil.calculateSize(PathUtil.getParentPath(packageInfo.applicationInfo.sourceDir)), + cacheDirSize, + dataDirSize, + 0, ) } + @TargetApi(Build.VERSION_CODES.O) + fun queryStatsForPackageApi26(packageInfo: PackageInfo, user: UserHandle): StorageStatsParcelable { + val stats = getStorageStatsManager().queryStatsForPackage(packageInfo.applicationInfo.storageUuid, packageInfo.packageName, user) + + return StorageStatsParcelable( + stats.appBytes, + stats.cacheBytes, + stats.dataBytes, + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { stats.externalCacheBytes } else { 0 }, + ) + } + + override fun queryStatsForPackage(packageInfo: PackageInfo, user: UserHandle): StorageStatsParcelable? = synchronized(lock) { + runCatching { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + queryStatsForPackageApi26(packageInfo, user) + } else { + queryStatsForPackageApi24(packageInfo, user) + } + }.getOrNull() + } + override fun getUsers(): List = synchronized(lock) { tryOn( block = { @@ -301,41 +369,73 @@ internal class RemoteRootServiceImpl : IRemoteRootService.Stub() { } override fun walkFileTree(path: String): ParcelFileDescriptor = synchronized(lock) { - writeToParcel { parcel -> + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + walkFileTreeApi26(path) + } else { + walkFileTreeApi24(path) + } + } + + private fun walkFileTreeApi24(path: String): ParcelFileDescriptor { + fun walkFileTreeRecursively(path: String): List { + val pathParcelableList = mutableListOf() + val file = File(path) + if (file.isFile) { + pathParcelableList.add(PathParcelable(file.absolutePath)) + } else if (file.isDirectory) { + for (item in file.listFiles()!!) { + pathParcelableList.addAll(walkFileTreeRecursively(item.absolutePath)) + } + } + return pathParcelableList + } + return writeToParcel { parcel -> parcel.writeTypedList( tryOn( - block = { - val pathParcelableList = mutableListOf() - Files.walkFileTree(Paths.get(path), object : SimpleFileVisitor() { - override fun preVisitDirectory(dir: Path?, attrs: BasicFileAttributes?): FileVisitResult { - return FileVisitResult.CONTINUE - } - - override fun visitFile(file: Path?, attrs: BasicFileAttributes?): FileVisitResult { - if (file != null && attrs != null) { - pathParcelableList.add(PathParcelable(file.pathString)) - } - return FileVisitResult.CONTINUE - } - - override fun visitFileFailed(file: Path?, exc: IOException?): FileVisitResult { - return FileVisitResult.CONTINUE - } - - override fun postVisitDirectory(dir: Path?, exc: IOException?): FileVisitResult { - return FileVisitResult.CONTINUE - } - }) - pathParcelableList - }, + block = { walkFileTreeRecursively(path) }, onException = { listOf() - } + }, ) ) } } + @TargetApi(Build.VERSION_CODES.O) + private fun walkFileTreeApi26(path: String): ParcelFileDescriptor = writeToParcel { parcel -> + parcel.writeTypedList( + tryOn( + block = { + val pathParcelableList = mutableListOf() + Files.walkFileTree(Paths.get(path), object : SimpleFileVisitor() { + override fun preVisitDirectory(dir: Path?, attrs: BasicFileAttributes?): FileVisitResult { + return FileVisitResult.CONTINUE + } + + override fun visitFile(file: Path?, attrs: BasicFileAttributes?): FileVisitResult { + if (file != null && attrs != null) { + pathParcelableList.add(PathParcelable(file.pathString)) + } + return FileVisitResult.CONTINUE + } + + override fun visitFileFailed(file: Path?, exc: IOException?): FileVisitResult { + return FileVisitResult.CONTINUE + } + + override fun postVisitDirectory(dir: Path?, exc: IOException?): FileVisitResult { + return FileVisitResult.CONTINUE + } + }) + pathParcelableList + }, + onException = { + listOf() + } + ) + ) + } + override fun getPackageArchiveInfo(path: String): PackageInfo? = synchronized(lock) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { systemContext.packageManager.getPackageArchiveInfo(path, PackageInfoFlags.of(PackageManager.GET_ACTIVITIES.toLong()))?.apply { diff --git a/source/core/rootservice/src/main/kotlin/com/xayah/core/rootservice/parcelables/PathParcelable.kt b/source/core/rootservice/src/main/kotlin/com/xayah/core/rootservice/parcelables/PathParcelable.kt index 269a407dd8..37e826f7ae 100644 --- a/source/core/rootservice/src/main/kotlin/com/xayah/core/rootservice/parcelables/PathParcelable.kt +++ b/source/core/rootservice/src/main/kotlin/com/xayah/core/rootservice/parcelables/PathParcelable.kt @@ -12,11 +12,10 @@ class PathParcelable() : Parcelable { var extension: String = "" constructor(pathString: String) : this() { - val path = Paths.get(pathString) this.pathList = pathString.split("/") this.pathString = pathString - this.nameWithoutExtension = path.fileName.pathString.split(".").first() - this.extension = path.fileName.pathString.replace("${nameWithoutExtension}.", "") + this.nameWithoutExtension = pathList.last().split(".").first() + this.extension = pathList.last().replace("${nameWithoutExtension}.", "") } constructor(parcel: Parcel) : this() { diff --git a/source/core/rootservice/src/main/kotlin/com/xayah/core/rootservice/parcelables/StorageStatsParcelable.kt b/source/core/rootservice/src/main/kotlin/com/xayah/core/rootservice/parcelables/StorageStatsParcelable.kt new file mode 100644 index 0000000000..f9f80df66b --- /dev/null +++ b/source/core/rootservice/src/main/kotlin/com/xayah/core/rootservice/parcelables/StorageStatsParcelable.kt @@ -0,0 +1,47 @@ +package com.xayah.core.rootservice.parcelables + +import android.os.Parcel +import android.os.Parcelable + +class StorageStatsParcelable() : Parcelable { + var appBytes: Long = 0L + var cacheBytes: Long = 0L + var dataBytes: Long = 0L + var externalCacheBytes: Long = 0L + + constructor(appBytes: Long, cacheBytes: Long, dateBytes: Long, externalCacheBytes: Long): this() { + this.appBytes = appBytes + this.cacheBytes = cacheBytes + this.dataBytes = dateBytes + this.externalCacheBytes = externalCacheBytes + } + + constructor(parcel: Parcel) : this() { + appBytes = parcel.readLong() + cacheBytes = parcel.readLong() + dataBytes = parcel.readLong() + externalCacheBytes = parcel.readLong() + } + + override fun writeToParcel(parcel: Parcel, flags: Int) { + parcel.writeLong(appBytes) + parcel.writeLong(cacheBytes) + parcel.writeLong(dataBytes) + parcel.writeLong(externalCacheBytes) + } + + override fun describeContents(): Int { + return 0 + } + + companion object CREATOR : Parcelable.Creator { + override fun createFromParcel(parcel: Parcel): StorageStatsParcelable { + return StorageStatsParcelable(parcel) + } + + override fun newArray(size: Int): Array { + return arrayOfNulls(size) + } + } + +} \ No newline at end of file diff --git a/source/core/rootservice/src/main/kotlin/com/xayah/core/rootservice/service/RemoteRootService.kt b/source/core/rootservice/src/main/kotlin/com/xayah/core/rootservice/service/RemoteRootService.kt index 0a1c381ccb..651b9c8343 100644 --- a/source/core/rootservice/src/main/kotlin/com/xayah/core/rootservice/service/RemoteRootService.kt +++ b/source/core/rootservice/src/main/kotlin/com/xayah/core/rootservice/service/RemoteRootService.kt @@ -1,6 +1,5 @@ package com.xayah.core.rootservice.service -import android.app.usage.StorageStats import android.content.ComponentName import android.content.Context import android.content.Intent @@ -18,6 +17,7 @@ import com.xayah.core.rootservice.IRemoteRootService import com.xayah.core.rootservice.impl.RemoteRootServiceImpl import com.xayah.core.rootservice.parcelables.PathParcelable import com.xayah.core.rootservice.parcelables.StatFsParcelable +import com.xayah.core.rootservice.parcelables.StorageStatsParcelable import com.xayah.core.rootservice.util.ExceptionUtil.tryOnScope import com.xayah.core.rootservice.util.withMainContext import com.xayah.core.util.GsonUtil @@ -262,7 +262,7 @@ class RemoteRootService(private val context: Context) { suspend fun getUserHandle(userId: Int): UserHandle? = runCatching { getService().getUserHandle(userId) }.onFailure(onFailure).getOrNull() - suspend fun queryStatsForPackage(packageInfo: PackageInfo, user: UserHandle): StorageStats? = + suspend fun queryStatsForPackage(packageInfo: PackageInfo, user: UserHandle): StorageStatsParcelable? = runCatching { getService().queryStatsForPackage(packageInfo, user) }.onFailure(onFailure).getOrNull() suspend fun getUsers(): List = runCatching { getService().users }.onFailure(onFailure).getOrElse { listOf() } diff --git a/source/core/systemapi/src/main/java/com/android/providers/settings/SettingsStateApi31.java b/source/core/systemapi/src/main/java/com/android/providers/settings/SettingsStateApi31.java index 1082d7a8db..c15ad98bc6 100644 --- a/source/core/systemapi/src/main/java/com/android/providers/settings/SettingsStateApi31.java +++ b/source/core/systemapi/src/main/java/com/android/providers/settings/SettingsStateApi31.java @@ -16,6 +16,7 @@ package com.android.providers.settings; +import android.annotation.TargetApi; import android.os.Build; import android.os.FileUtils; import android.os.Handler; @@ -66,6 +67,7 @@ * the same lock to grab the current state to write to disk. *

*/ +@TargetApi(Build.VERSION_CODES.S) public class SettingsStateApi31 implements SettingsState { private static final boolean DEBUG = false; private static final boolean DEBUG_PERSISTENCE = false; diff --git a/source/core/util/src/main/kotlin/com/xayah/core/util/DateUtil.kt b/source/core/util/src/main/kotlin/com/xayah/core/util/DateUtil.kt index e02ed17707..e72e28bd37 100644 --- a/source/core/util/src/main/kotlin/com/xayah/core/util/DateUtil.kt +++ b/source/core/util/src/main/kotlin/com/xayah/core/util/DateUtil.kt @@ -1,6 +1,8 @@ package com.xayah.core.util +import android.annotation.TargetApi import android.content.Context +import android.os.Build import android.text.format.DateUtils import java.text.SimpleDateFormat import java.time.Instant @@ -9,9 +11,9 @@ import java.time.ZoneId import java.time.temporal.ChronoUnit import java.util.Date import java.util.Locale +import java.util.concurrent.TimeUnit import kotlin.math.abs - object DateUtil { private const val SECOND_IN_MILLIS: Long = 1000 private const val MINUTE_IN_MILLIS = SECOND_IN_MILLIS * 60 @@ -62,19 +64,23 @@ object DateUtil { return String.format(format, count) } + fun getNumberOfDaysPassed(date1: Long, date2: Long): Long { + return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + getNumberOfDaysPassedApi26(date1, date2) + } else { + getNumberOfDaysPassedApi24(date1, date2) + } + } + + private fun getNumberOfDaysPassedApi24(date1: Long, date2: Long) = TimeUnit.MILLISECONDS.toDays(abs(date2 - date1)) /** * @see packages/apps/Messaging/src/com/android/messaging/util/Dates.java */ - fun getNumberOfDaysPassed(date1: Long, date2: Long): Long { + @TargetApi(Build.VERSION_CODES.O) + private fun getNumberOfDaysPassedApi26(date1: Long, date2: Long): Long { val dateTime1 = LocalDateTime.ofInstant(Instant.ofEpochMilli(date1), ZoneId.systemDefault()) val dateTime2 = LocalDateTime.ofInstant(Instant.ofEpochMilli(date2), ZoneId.systemDefault()) return abs(ChronoUnit.DAYS.between(dateTime2, dateTime1)) } - - fun getNumberOfHoursPassed(date1: Long, date2: Long): Long { - val dateTime1 = LocalDateTime.ofInstant(Instant.ofEpochMilli(date1), ZoneId.systemDefault()) - val dateTime2 = LocalDateTime.ofInstant(Instant.ofEpochMilli(date2), ZoneId.systemDefault()) - return abs(ChronoUnit.HOURS.between(dateTime2, dateTime1)) - } } diff --git a/source/core/util/src/main/kotlin/com/xayah/core/util/FileUtil.kt b/source/core/util/src/main/kotlin/com/xayah/core/util/FileUtil.kt index fc2abffd68..d45ef27b90 100644 --- a/source/core/util/src/main/kotlin/com/xayah/core/util/FileUtil.kt +++ b/source/core/util/src/main/kotlin/com/xayah/core/util/FileUtil.kt @@ -1,5 +1,7 @@ package com.xayah.core.util +import android.annotation.TargetApi +import android.os.Build import java.io.File import java.io.IOException import java.nio.file.FileVisitResult @@ -16,6 +18,31 @@ object FileUtil { }.getOrElse { listOf() } fun calculateSize(path: String): Long = run { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + calculateSizeApi26(path) + } else { + calculateSizeApi24(path) + } + } + + private fun calculateSizeApi24(path: String): Long { + val file = File(path) + if (!file.exists()) { + return 0 + } + var size: Long = 0 + if (file.isFile) { + size += file.length() + } else if (file.isDirectory) { + for (item in file.listFiles()!!) { + size += calculateSizeApi24(item.absolutePath) + } + } + return size + } + + @TargetApi(Build.VERSION_CODES.O) + private fun calculateSizeApi26(path: String): Long { val size = AtomicLong(0) runCatching { Files.walkFileTree(Paths.get(path), object : SimpleFileVisitor() { @@ -39,7 +66,7 @@ object FileUtil { } }) } - size.get() + return size.get() } fun deleteRecursively(path: String): Boolean = runCatching { File(path).deleteRecursively() }.getOrElse { false } diff --git a/source/core/util/src/main/kotlin/com/xayah/core/util/HashUtil.kt b/source/core/util/src/main/kotlin/com/xayah/core/util/HashUtil.kt index 0515bfbc44..84144529a6 100644 --- a/source/core/util/src/main/kotlin/com/xayah/core/util/HashUtil.kt +++ b/source/core/util/src/main/kotlin/com/xayah/core/util/HashUtil.kt @@ -1,9 +1,21 @@ package com.xayah.core.util +import android.annotation.TargetApi +import android.os.Build import org.apache.commons.codec.digest.DigestUtils +import java.io.FileInputStream import java.nio.file.Files import java.nio.file.Paths object HashUtil { - fun calculateMD5(src: String) = DigestUtils.md5Hex(Files.newInputStream(Paths.get(src))) + fun calculateMD5(src: String): String = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + calculateMD5Api26(src) + } else { + calculateMD5Api24(src) + } + + private fun calculateMD5Api24(src: String) = DigestUtils.md5Hex(FileInputStream(src)) + + @TargetApi(Build.VERSION_CODES.O) + private fun calculateMD5Api26(src: String) = DigestUtils.md5Hex(Files.newInputStream(Paths.get(src))) } diff --git a/source/core/util/src/main/kotlin/com/xayah/core/util/NotificationUtil.kt b/source/core/util/src/main/kotlin/com/xayah/core/util/NotificationUtil.kt index 497a9945c9..2259adebe9 100644 --- a/source/core/util/src/main/kotlin/com/xayah/core/util/NotificationUtil.kt +++ b/source/core/util/src/main/kotlin/com/xayah/core/util/NotificationUtil.kt @@ -1,13 +1,13 @@ package com.xayah.core.util import android.Manifest -import android.app.Notification import android.app.NotificationChannel import android.app.NotificationManager import android.app.PendingIntent import android.content.Context import android.content.Intent import android.content.pm.PackageManager +import android.net.Uri import android.os.Build import android.provider.Settings import android.provider.Settings.EXTRA_APP_PACKAGE @@ -35,7 +35,7 @@ object NotificationUtil { fun requestPermissions(context: Context) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { ActivityCompat.requestPermissions(context.getActivity(), arrayOf(Manifest.permission.POST_NOTIFICATIONS), 1) - } else { + } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { runCatching { val intent = Intent() intent.setAction(Settings.ACTION_APP_NOTIFICATION_SETTINGS) @@ -46,6 +46,15 @@ object NotificationUtil { }.onFailure { Toast.makeText(context, context.getString(R.string.grant_ntfy_perm_manually), Toast.LENGTH_SHORT).show() } + } else { + runCatching { + val intent = Intent().apply { + setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS) + addCategory(Intent.CATEGORY_DEFAULT) + setData(Uri.parse("package:${context.packageName}")) + } + context.startActivity(intent) + } } } @@ -53,13 +62,20 @@ object NotificationUtil { val pendingIntent: PendingIntent = context.packageManager.getLaunchIntentForPackage(context.packageName).let { intent -> PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_IMMUTABLE) } - val channel = NotificationChannel(ForegroundServiceChannelId, ForegroundServiceChannelName, NotificationManager.IMPORTANCE_LOW).apply { - description = ForegroundServiceChannelDesc - } - val notificationManager: NotificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager - notificationManager.createNotificationChannel(channel) - Notification.Builder(context, ForegroundServiceChannelId).setContentIntent(pendingIntent).build() + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + val channel = NotificationChannel( + ForegroundServiceChannelId, + ForegroundServiceChannelName, + NotificationManager.IMPORTANCE_LOW + ).apply { + description = ForegroundServiceChannelDesc + } + val notificationManager: NotificationManager = + context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager + notificationManager.createNotificationChannel(channel) + } + NotificationCompat.Builder(context, ForegroundServiceChannelId).setContentIntent(pendingIntent).build() } fun getProgressNotificationBuilder(context: Context) = diff --git a/source/core/util/src/main/kotlin/com/xayah/core/util/PathUtil.kt b/source/core/util/src/main/kotlin/com/xayah/core/util/PathUtil.kt index df173847ed..621324673a 100644 --- a/source/core/util/src/main/kotlin/com/xayah/core/util/PathUtil.kt +++ b/source/core/util/src/main/kotlin/com/xayah/core/util/PathUtil.kt @@ -7,9 +7,7 @@ import com.xayah.core.util.command.SELinux import dagger.hilt.android.qualifiers.ApplicationContext import kotlinx.coroutines.flow.first import kotlinx.coroutines.runBlocking -import java.nio.file.Paths import javax.inject.Inject -import kotlin.io.path.pathString const val LogRelativeDir = "log" const val IconRelativeDir = "icon" @@ -38,8 +36,22 @@ class PathUtil @Inject constructor( @ApplicationContext private val context: Context, ) { companion object { - fun getParentPath(path: String): String = Paths.get(path).parent.pathString - fun getFileName(path: String): String = Paths.get(path).fileName.pathString + /** + * Returns the parent path, or empty string if this path does not have a parent. + */ + fun getParentPath(path: String): String { + if (path.contains('/').not() || path == "/") return "" + val child = path.substring(path.lastIndexOf('/')) + return path.replace(child, "") + } + + /** + * Returns the name of the file or directory denoted by this path, or empty string if this path has zero elements. + */ + fun getFileName(path: String): String { + if (path.isEmpty()) return "" + return path.substring(path.lastIndexOf('/') + 1) + } // Paths for processing. @SuppressLint("SdCardPath") diff --git a/source/core/util/src/main/kotlin/com/xayah/core/util/command/BaseUtil.kt b/source/core/util/src/main/kotlin/com/xayah/core/util/command/BaseUtil.kt index 3c1e713edc..ef50a66d33 100644 --- a/source/core/util/src/main/kotlin/com/xayah/core/util/command/BaseUtil.kt +++ b/source/core/util/src/main/kotlin/com/xayah/core/util/command/BaseUtil.kt @@ -45,6 +45,7 @@ private class EnvInitializer : Shell.Initializer() { .add("set -o pipefail") // Ensure that the exit code of each command is correct. .add("alias tar=${QUOTE}busybox tar$QUOTE") .add("alias awk=${QUOTE}busybox awk$QUOTE") + .add("alias ps=${QUOTE}busybox ps$QUOTE") .exec() } } @@ -124,12 +125,12 @@ object BaseUtil { } suspend fun kill(context: Context, vararg keys: String) { - // ps -A | grep -w $key1 | grep -w $key2 | ... | awk 'NF>1{print $2}' | xargs kill -9 + // ps -A | grep -w $key1 | grep -w $key2 | ... | awk 'NF>1{print $1}' | xargs kill -9 val keysArg = keys.map { "| grep -w $it" }.toTypedArray() execute( "ps -A", *keysArg, - "| awk 'NF>1{print ${USD}2}'", + "| awk 'NF>1{print ${USD}1}'", "| xargs kill -9", shell = getNewShell(context), timeout = -1 diff --git a/source/feature/main/settings/src/main/kotlin/com/xayah/feature/main/settings/Item.kt b/source/feature/main/settings/src/main/kotlin/com/xayah/feature/main/settings/Item.kt index 5990ac57e9..b1cd007e25 100644 --- a/source/feature/main/settings/src/main/kotlin/com/xayah/feature/main/settings/Item.kt +++ b/source/feature/main/settings/src/main/kotlin/com/xayah/feature/main/settings/Item.kt @@ -1,5 +1,6 @@ package com.xayah.feature.main.settings +import android.os.Build import androidx.compose.animation.ExperimentalAnimationApi import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue @@ -22,23 +23,38 @@ fun DarkThemeSelectable() { val context = LocalContext.current val dialogState = LocalSlotScope.current!!.dialogSlot val items = remember { - listOf( - DialogRadioItem( - enum = ThemeType.AUTO, - title = StringResourceToken.fromStringId(R.string.theme_auto), - desc = StringResourceToken.fromStringId(R.string.theme_auto_desc), - ), - DialogRadioItem( - enum = ThemeType.LIGHT_THEME, - title = StringResourceToken.fromStringId(R.string.theme_light), - desc = StringResourceToken.fromStringId(R.string.theme_light_desc), - ), - DialogRadioItem( - enum = ThemeType.DARK_THEME, - title = StringResourceToken.fromStringId(R.string.theme_dark), - desc = StringResourceToken.fromStringId(R.string.theme_dark_desc), - ), - ) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { + listOf( + DialogRadioItem( + enum = ThemeType.AUTO, + title = StringResourceToken.fromStringId(R.string.theme_auto), + desc = StringResourceToken.fromStringId(R.string.theme_auto_desc), + ), + DialogRadioItem( + enum = ThemeType.LIGHT_THEME, + title = StringResourceToken.fromStringId(R.string.theme_light), + desc = StringResourceToken.fromStringId(R.string.theme_light_desc), + ), + DialogRadioItem( + enum = ThemeType.DARK_THEME, + title = StringResourceToken.fromStringId(R.string.theme_dark), + desc = StringResourceToken.fromStringId(R.string.theme_dark_desc), + ), + ) + } else { + listOf( + DialogRadioItem( + enum = ThemeType.LIGHT_THEME, + title = StringResourceToken.fromStringId(R.string.theme_light), + desc = StringResourceToken.fromStringId(R.string.theme_light_desc), + ), + DialogRadioItem( + enum = ThemeType.DARK_THEME, + title = StringResourceToken.fromStringId(R.string.theme_dark), + desc = StringResourceToken.fromStringId(R.string.theme_dark_desc), + ), + ) + } } val currentType by observeCurrentTheme() val currentIndex by remember(currentType) { mutableIntStateOf(items.indexOfFirst { it.enum == currentType }) } diff --git a/source/gradle/libs.versions.toml b/source/gradle/libs.versions.toml index 86977c51b6..bbe0890b74 100644 --- a/source/gradle/libs.versions.toml +++ b/source/gradle/libs.versions.toml @@ -1,7 +1,7 @@ [versions] # config compileSdk = "34" -minSdk = "26" +minSdk = "24" targetSdk = "34" # __(API)_(feature)___(version)_(abi) # TODO: Feature bit is no longer useful, drop when next api releases @@ -37,7 +37,7 @@ palette = "1.0.0" google-services = "4.3.15" # Pinned: https://github.com/firebase/firebase-android-sdk/issues/4693#issuecomment-1765778239 firebase-crashlytics-gradle = "2.9.9" # Workaround: https://github.com/xamarin/GooglePlayServicesComponents/issues/642#issuecomment-1221520982 firebase-bom = "32.7.0" -pickyou = "2.1.7" +pickyou = "2.1.8" datastore-preferences = "1.1.1" serialization-protobuf = "1.6.0" apache-commons = "3.10.0"