Skip to content

Commit eac1f03

Browse files
New update
1 parent 594c473 commit eac1f03

1 file changed

Lines changed: 81 additions & 144 deletions

File tree

Lines changed: 81 additions & 144 deletions
Original file line numberDiff line numberDiff line change
@@ -1,185 +1,122 @@
11
package io.github.cpatcher.handlers
22

33
import android.app.Application
4-
import android.content.Context
4+
import android.app.ActivityThread // Hidden API (Rikka Stub)
5+
import android.os.Process
6+
import android.os.UserHandle // Hidden API (Rikka Stub)
57
import io.github.cpatcher.arch.IHook
6-
import io.github.cpatcher.arch.hookAllAfter
7-
import io.github.cpatcher.logE
8+
import io.github.cpatcher.arch.hookAfter
89
import io.github.cpatcher.logI
10+
import io.github.cpatcher.logE
911
import io.github.cpatcher.logW
1012
import java.io.File
1113
import java.util.concurrent.Executors
12-
import java.util.concurrent.TimeUnit
14+
import java.util.concurrent.ThreadFactory
1315

1416
/**
15-
* CleanHandler - Automatic cache cleanup module for LSPosed-scoped applications
16-
*
17-
* Technical approach:
18-
* - Hooks into Application.onCreate() for earliest possible cleanup
19-
* - Performs asynchronous deletion to avoid blocking UI thread
20-
* - Implements comprehensive error containment per directory
21-
* - Tracks cleanup metrics for performance monitoring
17+
* CleanHandler (Hybrid Version)
18+
* * Kết hợp sức mạnh của:
19+
* 1. HookUtils DSL: Để can thiệp vào vòng đời Application.
20+
* 2. Hidden APIs: Để truy xuất thông tin hệ thống (Process, User) không qua Reflection.
2221
*/
2322
class CleanHandler : IHook() {
23+
2424
companion object {
25-
private const val CLEANUP_THREAD_NAME = "CpatcherCacheCleanup"
26-
private const val CLEANUP_TIMEOUT_MS = 5000L
25+
private const val THREAD_NAME = "Cpatcher-Cleaner"
26+
27+
// Tối ưu hóa Thread: Priority thấp để không ảnh hưởng khởi động App
2728
private val cleanupExecutor = Executors.newSingleThreadExecutor { r ->
28-
Thread(r, CLEANUP_THREAD_NAME).apply {
29+
Thread(r, THREAD_NAME).apply {
2930
priority = Thread.MIN_PRIORITY
3031
isDaemon = true
3132
}
3233
}
3334
}
34-
35+
3536
override fun onHook() {
36-
// MANDATORY: Package validation - only process if we're in a targeted app
37-
// Since this is a universal handler, we check if it's NOT our own module
38-
if (loadPackageParam.packageName == "io.github.cpatcher") {
39-
logI("${this::class.simpleName}: Skipping self-cleanup")
40-
return
41-
}
42-
43-
// Hook Application onCreate for earliest cleanup opportunity
44-
Application::class.java.hookAllAfter("onCreate") { param ->
45-
val application = param.thisObject as? Application
46-
if (application == null) {
47-
logW("${this::class.simpleName}: Unable to cast to Application")
48-
return@hookAllAfter
49-
}
50-
37+
// RULE 3: Isolation - Logic nằm gọn trong IHook
38+
if (loadPackageParam.packageName == "io.github.cpatcher") return
39+
40+
// RULE 2: HookUtils DSL Standard
41+
// Thay vì XposedHelpers.findAndHookMethod, ta dùng extension function
42+
Application::class.java.hookAfter("onCreate") { param ->
43+
val app = param.thisObject as? Application ?: return@hookAfter
44+
5145
runCatching {
52-
performCacheCleanup(application)
53-
}.onFailure { t ->
54-
logE("${this::class.simpleName}: Failed to initiate cache cleanup", t)
46+
// RULE 1: Hybrid Interaction Mandate
47+
// Gọi trực tiếp Hidden API, KHÔNG dùng Reflection.
48+
// ActivityThread.currentProcessName() và UserHandle.myUserId()
49+
// được cung cấp bởi thư viện dev.rikka.hidden.
50+
val currentProcess = ActivityThread.currentProcessName()
51+
val userId = UserHandle.myUserId()
52+
53+
logI("Initiating cache purge for $currentProcess (UID: ${Process.myUid()}, User: $userId)")
54+
55+
performHybridCleanup(app)
56+
}.onFailure {
57+
logE("Failed to init cleanup hook", it)
5558
}
5659
}
57-
58-
logI("${this::class.simpleName}: Successfully initialized for ${loadPackageParam.packageName}")
5960
}
60-
61-
private fun performCacheCleanup(context: Context) {
61+
62+
private fun performHybridCleanup(context: Application) {
6263
cleanupExecutor.execute {
6364
runCatching {
64-
val startTime = System.currentTimeMillis()
65-
var totalDeleted = 0L
66-
var filesDeleted = 0
67-
68-
// External cache cleanup - typically largest
69-
context.externalCacheDir?.let { dir ->
70-
if (dir.exists()) {
71-
val result = deleteDirectoryContents(dir)
72-
totalDeleted += result.first
73-
filesDeleted += result.second
74-
logI("${this::class.simpleName}: External cache - " +
75-
"${result.second} files, ${result.first / 1024}KB")
76-
}
77-
}
78-
79-
// Internal cache cleanup
80-
context.cacheDir?.let { dir ->
81-
if (dir.exists()) {
82-
val result = deleteDirectoryContents(dir)
83-
totalDeleted += result.first
84-
filesDeleted += result.second
85-
logI("${this::class.simpleName}: Internal cache - " +
86-
"${result.second} files, ${result.first / 1024}KB")
87-
}
88-
}
89-
90-
// Code cache cleanup - JIT compiled code
91-
context.codeCacheDir?.let { dir ->
65+
val start = System.currentTimeMillis()
66+
var freedBytes = 0L
67+
var fileCount = 0
68+
69+
// Danh sách các thư mục cache tiêu chuẩn
70+
val cacheDirs = listOfNotNull(
71+
context.externalCacheDir,
72+
context.cacheDir,
73+
context.codeCacheDir
74+
)
75+
76+
cacheDirs.forEach { dir ->
9277
if (dir.exists()) {
93-
val result = deleteDirectoryContents(dir)
94-
totalDeleted += result.first
95-
filesDeleted += result.second
96-
logI("${this::class.simpleName}: Code cache - " +
97-
"${result.second} files, ${result.first / 1024}KB")
78+
val (bytes, count) = deleteRecursively(dir)
79+
freedBytes += bytes
80+
fileCount += count
9881
}
9982
}
100-
101-
val elapsed = System.currentTimeMillis() - startTime
102-
103-
if (filesDeleted > 0) {
104-
logI("${this::class.simpleName}: Cleanup completed - " +
105-
"$filesDeleted files, ${totalDeleted / 1024}KB in ${elapsed}ms")
106-
} else {
107-
logI("${this::class.simpleName}: No cache files to clean")
83+
84+
if (fileCount > 0) {
85+
val time = System.currentTimeMillis() - start
86+
logI("Cleanup complete: Removed $fileCount files (${freedBytes / 1024} KB) in ${time}ms")
10887
}
109-
110-
}.onFailure { t ->
111-
logE("${this::class.simpleName}: Cache cleanup thread failed", t)
112-
}
113-
}
114-
}
115-
116-
/**
117-
* Recursively deletes directory contents while preserving the root directory
118-
* @return Pair of (total bytes deleted, file count)
119-
*/
120-
private fun deleteDirectoryContents(directory: File): Pair<Long, Int> {
121-
var totalSize = 0L
122-
var fileCount = 0
123-
124-
runCatching {
125-
directory.listFiles()?.forEach { file ->
126-
val result = deleteRecursively(file)
127-
totalSize += result.first
128-
fileCount += result.second
88+
}.onFailure {
89+
logE("Cleanup thread error", it)
12990
}
130-
}.onFailure { t ->
131-
logE("${this::class.simpleName}: Failed to list ${directory.absolutePath}", t)
13291
}
133-
134-
return Pair(totalSize, fileCount)
13592
}
136-
137-
/**
138-
* Recursively deletes a file or directory
139-
* @return Pair of (total bytes deleted, file count)
140-
*/
93+
94+
// Helper: Đệ quy xóa file an toàn, trả về (Bytes, Số lượng)
14195
private fun deleteRecursively(file: File): Pair<Long, Int> {
142-
if (!file.exists()) return Pair(0L, 0)
143-
144-
var totalSize = 0L
145-
var fileCount = 0
96+
var size = 0L
97+
var count = 0
14698

147-
runCatching {
148-
if (file.isDirectory) {
149-
file.listFiles()?.forEach { child ->
150-
val result = deleteRecursively(child)
151-
totalSize += result.first
152-
fileCount += result.second
153-
}
99+
if (!file.exists()) return 0L to 0
100+
101+
if (file.isDirectory) {
102+
file.listFiles()?.forEach { child ->
103+
val (childSize, childCount) = deleteRecursively(child)
104+
size += childSize
105+
count += childCount
154106
}
155-
156-
val fileSize = file.length()
107+
}
108+
109+
// Không xóa thư mục gốc (cache, code_cache) để tránh lỗi permission cục bộ
110+
// Chỉ xóa nội dung bên trong hoặc thư mục con
111+
val isRootCache = file.name == "cache" || file.name == "code_cache"
112+
if (!isRootCache) {
113+
val length = file.length()
157114
if (file.delete()) {
158-
totalSize += fileSize
159-
fileCount++
160-
}
161-
162-
}.onFailure { t ->
163-
// Silent failure for individual files - don't spam logs
164-
if (file.isDirectory) {
165-
logW("${this::class.simpleName}: Failed to delete directory ${file.name}")
115+
size += length
116+
count++
166117
}
167118
}
168119

169-
return Pair(totalSize, fileCount)
170-
}
171-
172-
/**
173-
* Cleanup executor on module unload (if supported by framework)
174-
*/
175-
fun cleanup() {
176-
runCatching {
177-
cleanupExecutor.shutdown()
178-
if (!cleanupExecutor.awaitTermination(CLEANUP_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
179-
cleanupExecutor.shutdownNow()
180-
}
181-
}.onFailure { t ->
182-
logE("${this::class.simpleName}: Failed to shutdown executor", t)
183-
}
120+
return size to count
184121
}
185-
}
122+
}

0 commit comments

Comments
 (0)