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"