diff --git a/.gitignore b/.gitignore index e79222d0..e2ada38b 100644 --- a/.gitignore +++ b/.gitignore @@ -4,7 +4,7 @@ *.ap_ *.aab *.json - +!/app/src/main/res/raw/*.json # Files for the ART/Dalvik VM *.dex diff --git a/README.md b/README.md index b7286f80..566e585e 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,5 @@ +![GitHub](https://img.shields.io/github/license/GuoXiCheng/SKIP) ![GitHub all releases](https://img.shields.io/github/downloads/GuoXiCheng/SKIP/total) ![GitHub Repo stars](https://img.shields.io/github/stars/GuoXiCheng/SKIP) + ## SKIP 介绍 SKIP 是一款免费开源的安卓应用,旨在利用 Android 无障碍服务帮助用户快速点击 APP 开屏广告的跳过按钮,让你的使用体验更加流畅。 diff --git a/apk/SKIP-v1.3.1.apk b/apk/SKIP-v1.3.1.apk new file mode 100644 index 00000000..08e584a1 Binary files /dev/null and b/apk/SKIP-v1.3.1.apk differ diff --git a/app/build.gradle b/app/build.gradle index f021ddeb..783e873d 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -11,7 +11,7 @@ android { minSdk 24 targetSdk 32 versionCode 1 - versionName "1.3" + versionName "1.3.1" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" vectorDrawables { @@ -50,7 +50,7 @@ android { } dependencies { - + implementation 'com.google.code.gson:gson:2.8.9' implementation 'androidx.core:core-ktx:1.8.0' implementation 'androidx.appcompat:appcompat:1.4.2' implementation 'com.google.android.material:material:1.4.0' @@ -66,4 +66,5 @@ dependencies { androidTestImplementation "androidx.compose.ui:ui-test-junit4:$compose_version" debugImplementation "androidx.compose.ui:ui-tooling:$compose_version" debugImplementation "androidx.compose.ui:ui-test-manifest:$compose_version" + } \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 25e0121a..8b3ee431 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -2,6 +2,7 @@ + + + + + () + +class WhitelistActivity : ComponentActivity() { + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + mutableList.clear() + + val packages = packageManager.getInstalledPackages(PackageManager.GET_META_DATA) + packages.forEach { + val appName = it.applicationInfo.loadLabel(packageManager).toString() + val packageName = it.packageName + + if (!MyUtils.isExcludeApplication(appName, packageName, packageManager)) { + mutableList.add(AppData(it.applicationInfo.loadIcon(packageManager), appName, false)) + } + + + } + + setContent { + Whitelist(mutableList) + } + + } +} +data class AppData( + val icon: Drawable, + val name: String, + var enabled: Boolean +) +@Composable +fun Whitelist (appList: List) { + Surface( + modifier = Modifier.fillMaxSize() + ) { + LazyColumn { + + items(appList.size) { index -> + val item = appList[index] + Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.padding(30.dp, 10.dp)) { + + Image( + bitmap = item.icon.toBitmap().asImageBitmap(), + contentDescription = "${item.name} Icon", // 添加图标的描述,通常用于辅助功能 + modifier = Modifier.size(48.dp) // 可选:设置图标的大小 + ) + + Text(item.name, modifier = Modifier.weight(1f).padding(10.dp, 0.dp), fontSize = 20.sp, fontWeight = FontWeight.Bold) + + Switch(checked = item.enabled, onCheckedChange = { + item.enabled = it + }) + + } + } + } + } +} + + + + diff --git a/app/src/main/java/com/android/skip/dataclass/PackageInfo.kt b/app/src/main/java/com/android/skip/dataclass/PackageInfo.kt new file mode 100644 index 00000000..032b0ae6 --- /dev/null +++ b/app/src/main/java/com/android/skip/dataclass/PackageInfo.kt @@ -0,0 +1,7 @@ +package com.android.skip.dataclass + +data class PackageInfo( + val package_name: String, val skip_text: String?, + val skip_id: String?, val start_page_node: Int?, + val skip_point: String? +) diff --git a/app/src/main/java/com/android/skip/handler/AbstractHandler.kt b/app/src/main/java/com/android/skip/handler/AbstractHandler.kt new file mode 100644 index 00000000..cc09e14b --- /dev/null +++ b/app/src/main/java/com/android/skip/handler/AbstractHandler.kt @@ -0,0 +1,20 @@ +package com.android.skip.handler + +import android.graphics.Rect +import android.view.accessibility.AccessibilityNodeInfo + +abstract class AbstractHandler: NodeHandler { + private var nextHandler: NodeHandler? = null + + override fun handle(node: AccessibilityNodeInfo): List { + if (nextHandler != null) { + return nextHandler!!.handle(node) + } + return listOf() + } + + override fun setNextHandler(handler: NodeHandler): NodeHandler { + nextHandler = handler + return handler + } +} \ No newline at end of file diff --git a/app/src/main/java/com/android/skip/handler/IdNodeHandler.kt b/app/src/main/java/com/android/skip/handler/IdNodeHandler.kt new file mode 100644 index 00000000..9c2fde75 --- /dev/null +++ b/app/src/main/java/com/android/skip/handler/IdNodeHandler.kt @@ -0,0 +1,21 @@ +package com.android.skip.handler + +import android.graphics.Rect +import android.view.accessibility.AccessibilityNodeInfo +import com.android.skip.manager.SkipConfigManager + +class IdNodeHandler: AbstractHandler() { + override fun handle(node: AccessibilityNodeInfo): List { + val listOfRect = node.findAccessibilityNodeInfosByViewId( + SkipConfigManager.getSkipId(node.packageName.toString()) + ).map { + val rect = Rect() + it.getBoundsInScreen(rect) + rect + } + + return listOfRect.ifEmpty { + super.handle(node) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/android/skip/handler/NodeHandler.kt b/app/src/main/java/com/android/skip/handler/NodeHandler.kt new file mode 100644 index 00000000..582416cf --- /dev/null +++ b/app/src/main/java/com/android/skip/handler/NodeHandler.kt @@ -0,0 +1,9 @@ +package com.android.skip.handler + +import android.graphics.Rect +import android.view.accessibility.AccessibilityNodeInfo + +interface NodeHandler { + fun handle(node: AccessibilityNodeInfo): List + fun setNextHandler (handler: NodeHandler): NodeHandler +} \ No newline at end of file diff --git a/app/src/main/java/com/android/skip/handler/PointHandler.kt b/app/src/main/java/com/android/skip/handler/PointHandler.kt new file mode 100644 index 00000000..157ea6b8 --- /dev/null +++ b/app/src/main/java/com/android/skip/handler/PointHandler.kt @@ -0,0 +1,16 @@ +package com.android.skip.handler + +import android.graphics.Rect +import android.view.accessibility.AccessibilityNodeInfo +import com.android.skip.manager.SkipConfigManager + +class PointHandler: AbstractHandler() { + override fun handle(node: AccessibilityNodeInfo): List { + val skipPoint = SkipConfigManager.getSkipPoint(node.packageName.toString()) + return if (skipPoint != null) { + listOf(skipPoint) + } else { + super.handle(node) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/android/skip/handler/TextNodeHandler.kt b/app/src/main/java/com/android/skip/handler/TextNodeHandler.kt new file mode 100644 index 00000000..f48cf672 --- /dev/null +++ b/app/src/main/java/com/android/skip/handler/TextNodeHandler.kt @@ -0,0 +1,20 @@ +package com.android.skip.handler + +import android.graphics.Rect +import android.view.accessibility.AccessibilityNodeInfo +import com.android.skip.manager.SkipConfigManager + +class TextNodeHandler : AbstractHandler() { + override fun handle(node: AccessibilityNodeInfo): List { + val listOfRect = node.findAccessibilityNodeInfosByText( + SkipConfigManager.getSkipText(node.packageName.toString()) + ).map { + val rect = Rect() + it.getBoundsInScreen(rect) + rect + } + return listOfRect.ifEmpty { + super.handle(node) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/android/skip/AnalyticsManager.kt b/app/src/main/java/com/android/skip/manager/AnalyticsManager.kt similarity index 62% rename from app/src/main/java/com/android/skip/AnalyticsManager.kt rename to app/src/main/java/com/android/skip/manager/AnalyticsManager.kt index ba9d2027..dadb5a9f 100644 --- a/app/src/main/java/com/android/skip/AnalyticsManager.kt +++ b/app/src/main/java/com/android/skip/manager/AnalyticsManager.kt @@ -1,4 +1,4 @@ -package com.android.skip +package com.android.skip.manager object AnalyticsManager { @@ -9,35 +9,35 @@ object AnalyticsManager { fun isPerformScan(currentPackageName: String): Boolean { if (packageName != currentPackageName) { - this.cleanScanCount() - this.cleanShowToastCount() - this.setPackageName(currentPackageName) + cleanScanCount() + cleanShowToastCount() + setPackageName(currentPackageName) return true } else if (scanCount <= maxScanCount) return true return false } fun isShowToast(): Boolean { - return this.showToastCount == 0 + return showToastCount == 0 } private fun setPackageName(currentPackageName: String) { - this.packageName = currentPackageName + packageName = currentPackageName } fun setShowToastCount() { - this.showToastCount = 1 + showToastCount = 1 } private fun cleanShowToastCount() { - this.showToastCount = 0 + showToastCount = 0 } fun increaseScanCount() { - this.scanCount += 1 + scanCount += 1 } private fun cleanScanCount() { - this.scanCount = 0 + scanCount = 0 } } \ No newline at end of file diff --git a/app/src/main/java/com/android/skip/manager/RectManager.kt b/app/src/main/java/com/android/skip/manager/RectManager.kt new file mode 100644 index 00000000..0cdf8ced --- /dev/null +++ b/app/src/main/java/com/android/skip/manager/RectManager.kt @@ -0,0 +1,23 @@ +package com.android.skip.manager + +import android.content.Context +import android.graphics.Rect + +object RectManager { + private var maxRectX = 1080 + private var maxRectY = 2268 + + fun setMaxRect(context: Context) { + val metrics = context.resources.displayMetrics + maxRectX = metrics.widthPixels + maxRectY = metrics.heightPixels + } + + fun getPointRect(percentX: Float, percentY: Float): Rect { + val rect = Rect() + val actualX = (percentX * maxRectX).toInt() + val actualY = (percentY * maxRectY).toInt() + rect.set(actualX - 10, actualY - 10, actualX + 10, actualY + 10) + return rect + } +} diff --git a/app/src/main/java/com/android/skip/manager/SkipConfigManager.kt b/app/src/main/java/com/android/skip/manager/SkipConfigManager.kt new file mode 100644 index 00000000..73c395e9 --- /dev/null +++ b/app/src/main/java/com/android/skip/manager/SkipConfigManager.kt @@ -0,0 +1,42 @@ +package com.android.skip.manager +import android.graphics.Rect +import com.android.skip.dataclass.PackageInfo +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken + +object SkipConfigManager { + private lateinit var appInfoMap: Map + fun setConfig(config: String) { + val gson = Gson() + val appInfoList: List = gson.fromJson(config, object : TypeToken>() {}.type) + appInfoMap = appInfoList.associateBy { it.package_name } + } + + fun getSkipText(packageName: String): String { + return appInfoMap[packageName]?.skip_text ?: "跳过" + } + + fun getSkipId(packageName: String): String { + return appInfoMap[packageName]?.skip_id ?: "no skip id" + } + + fun getStartPageNodeCount(packageName: String): Int { + return appInfoMap[packageName]?.start_page_node ?: 10 + } + + fun getSkipPoint(packageName: String): Rect? { + val skipPoint = appInfoMap[packageName]?.skip_point + val pointParts = skipPoint?.split(",")?.map { it.toFloatOrNull() } + if (pointParts?.size == 2 && isBetweenZeroAndOne(pointParts[0]) && isBetweenZeroAndOne(pointParts[1])) { + val (x, y) = pointParts + if (x is Float && y is Float) { + return RectManager.getPointRect(x, y) + } + } + return null + } + + private fun isBetweenZeroAndOne(value: Float?): Boolean { + return value != null && value in 0.0f..1.0f + } +} \ No newline at end of file diff --git a/app/src/main/java/com/android/skip/node/NodeCallBack.kt b/app/src/main/java/com/android/skip/node/NodeCallBack.kt new file mode 100644 index 00000000..d54a20f4 --- /dev/null +++ b/app/src/main/java/com/android/skip/node/NodeCallBack.kt @@ -0,0 +1,17 @@ +package com.android.skip.node + +import android.view.accessibility.AccessibilityNodeInfo + +interface NodeCallBack { + fun onCallback(accessibilityNodeInfo: AccessibilityNodeInfo) +} + +fun recursionNodes(node: AccessibilityNodeInfo?, callback: NodeCallBack) { + if (node == null) return + callback.onCallback(node) + for (i in 0 until node.childCount) { + val childNode = node.getChild(i) + recursionNodes(childNode, callback) + childNode.recycle() + } +} diff --git a/app/src/main/java/com/android/skip/node/NodeCount.kt b/app/src/main/java/com/android/skip/node/NodeCount.kt new file mode 100644 index 00000000..40bcb87b --- /dev/null +++ b/app/src/main/java/com/android/skip/node/NodeCount.kt @@ -0,0 +1,18 @@ +package com.android.skip.node + +import android.view.accessibility.AccessibilityNodeInfo + +class NodeCount: NodeCallBack { + private var nodeCount = 0 + override fun onCallback(accessibilityNodeInfo: AccessibilityNodeInfo) { + nodeCount ++ + } + + fun cleanCount() { + nodeCount = 0 + } + + fun getCount(): Int { + return nodeCount + } +} \ No newline at end of file diff --git a/app/src/main/java/com/android/skip/node/NodeRect.kt b/app/src/main/java/com/android/skip/node/NodeRect.kt new file mode 100644 index 00000000..0becb43c --- /dev/null +++ b/app/src/main/java/com/android/skip/node/NodeRect.kt @@ -0,0 +1,23 @@ +package com.android.skip.node + +import android.graphics.Rect +import android.util.Log +import android.view.accessibility.AccessibilityNodeInfo + +class NodeRect: NodeCallBack { + + private val rect = Rect() + private val list = mutableListOf() + override fun onCallback(accessibilityNodeInfo: AccessibilityNodeInfo) { + accessibilityNodeInfo.getBoundsInScreen(rect) + if (rect.exactCenterX() > 800 && rect.exactCenterY() < 200) { + list.clear() + list.add(accessibilityNodeInfo) + } + + } + + fun getList(): MutableList { + return list + } +} \ No newline at end of file diff --git a/app/src/main/java/com/android/skip/service/MyAccessibilityService.kt b/app/src/main/java/com/android/skip/service/MyAccessibilityService.kt index 0dad1e64..ba64a992 100644 --- a/app/src/main/java/com/android/skip/service/MyAccessibilityService.kt +++ b/app/src/main/java/com/android/skip/service/MyAccessibilityService.kt @@ -1,33 +1,40 @@ package com.android.skip.service import android.accessibilityservice.AccessibilityService -import android.accessibilityservice.GestureDescription -import android.graphics.Path -import android.graphics.Rect +import android.util.Log import android.view.accessibility.AccessibilityEvent import android.view.accessibility.AccessibilityNodeInfo -import android.widget.Toast -import com.android.skip.AnalyticsManager +import com.android.skip.MyUtils.click +import com.android.skip.handler.IdNodeHandler +import com.android.skip.handler.PointHandler +import com.android.skip.handler.TextNodeHandler +import com.android.skip.manager.AnalyticsManager +import com.android.skip.manager.SkipConfigManager +import com.android.skip.node.NodeCount +import com.android.skip.node.recursionNodes class MyAccessibilityService : AccessibilityService() { - private val path = Path() - private val rect = Rect() - override fun onAccessibilityEvent(p0: AccessibilityEvent?) { - try { if (!AnalyticsManager.isPerformScan(getCurrentRootNode().packageName.toString())) return - val skipNodes = handleRootNodeByPackageName() - if (skipNodes.isNotEmpty()) { - skipNodes[0].getBoundsInScreen(rect) - click(this, rect.exactCenterX(), rect.exactCenterY()) + val textNodeHandler = TextNodeHandler() + val idNodeHandler = IdNodeHandler() + val pointHandler = PointHandler() + + textNodeHandler.setNextHandler(idNodeHandler).setNextHandler(pointHandler) + + val listOfRect = textNodeHandler.handle(getCurrentRootNode()) + for (rect in listOfRect) { + if (isStartUpPage()) { + click(this, rect) + } } AnalyticsManager.increaseScanCount() } catch (e: Exception) { - println(e) + Log.d("SKIPS", e.message.toString()) } } @@ -37,40 +44,14 @@ class MyAccessibilityService : AccessibilityService() { else throw IllegalStateException("No valid root node available"); } - fun handleRootNodeByPackageName (): MutableList { - return when (getCurrentRootNode().packageName.toString()) { - "com.qiyi.video.lite", "com.qiyi.video" -> getCurrentRootNode().findAccessibilityNodeInfosByText("关闭") - "com.MobileTicket" -> getCurrentRootNode().findAccessibilityNodeInfosByViewId("com.MobileTicket:id/tv_skip") - else -> getCurrentRootNode().findAccessibilityNodeInfosByText("跳过") - } + private fun isStartUpPage(): Boolean { + val countCallBack = NodeCount() + countCallBack.cleanCount() + val currentNode = getCurrentRootNode() + recursionNodes(currentNode, countCallBack) + return countCallBack.getCount() < SkipConfigManager.getStartPageNodeCount(currentNode.packageName.toString()) } override fun onInterrupt() {} - private fun click(accessibilityService: AccessibilityService, x: Float, y: Float) { - path.reset() - path.moveTo(x, y) - path.lineTo(x, y) - - val builder = GestureDescription.Builder() - builder.addStroke(GestureDescription.StrokeDescription(path, 0, 1)) - val gesture = builder.build() - - accessibilityService.dispatchGesture( - gesture, - object : AccessibilityService.GestureResultCallback() { - override fun onCompleted(gestureDescription: GestureDescription) { - super.onCompleted(gestureDescription) - - if (AnalyticsManager.isShowToast()) { - Toast.makeText(accessibilityService, "已为您跳过广告", Toast.LENGTH_SHORT).show() - AnalyticsManager.setShowToastCount() - } - - } - }, - null - ) - } - } \ No newline at end of file diff --git a/app/src/main/res/raw/skip_config_v1.json b/app/src/main/res/raw/skip_config_v1.json new file mode 100644 index 00000000..22ec9ed8 --- /dev/null +++ b/app/src/main/res/raw/skip_config_v1.json @@ -0,0 +1,34 @@ +[ + { + "package_name": "com.qiyi.video.lite", + "skip_text": "关闭" + }, + { + "package_name":"com.qiyi.video", + "skip_text": "关闭" + }, + { + "package_name": "com.MobileTicket", + "skip_id": "com.MobileTicket:id/tv_skip" + }, + { + "package_name": "com.tencent.mm", + "start_page_node": 0 + }, + { + "package_name": "com.github.android", + "start_page_node": 0 + }, + { + "package_name": "com.qq.qcloud", + "skip_id": "com.qq.qcloud:id/gdt_ad_text" + }, + { + "package_name": "com.coolapk.market", + "skip_point": "0.9,0.07" + }, + { + "package_name": "com.example.pptv", + "start_page_node": 25 + } +] \ No newline at end of file diff --git a/settings.gradle b/settings.gradle index eea82b29..25dbcec2 100644 --- a/settings.gradle +++ b/settings.gradle @@ -8,8 +8,10 @@ pluginManagement { dependencyResolutionManagement { repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) repositories { - google() - mavenCentral() +// google() +// mavenCentral() + maven { url 'https://maven.aliyun.com/repository/google' } + maven { url 'https://maven.aliyun.com/repository/jcenter' } } } rootProject.name = "OneClick"