feat: 基础架构

This commit is contained in:
lisonge 2021-12-14 11:34:20 +08:00
parent b8f7d503ac
commit b288cdd120
45 changed files with 630 additions and 245 deletions

View File

@ -1,8 +1,16 @@
# AdCloser
# gkd
基于无障碍和自定义匹配规则的广告关闭app
搞快点,顾名思义,做快速点击,一款基于无障碍和自定义匹配规则的快速点击app
记录递归调用的次数, 根据统计次数考虑是否添加规则长度, 可减少匹配时间
## feature
- 跳过启动页面
- 关闭app内部广告
- 任意点击操作
- 支持导入订阅链接
- 支持浏览器直接跳转导入规则至app
类似 uiautomatorviewer 的路径选取规则创建器
# Fix Bug

View File

@ -1,6 +1,7 @@
plugins {
id("com.android.application")
id("kotlin-android")
id("kotlin-kapt")
}
val composeVersion = "1.0.5"
@ -9,7 +10,7 @@ android {
buildToolsVersion = "31.0.0"
defaultConfig {
applicationId = "li.songe.ad_closer"
applicationId = "li.songe.gkd"
minSdk = 26
targetSdk = 31
versionCode = 1
@ -40,9 +41,11 @@ android {
)
)
signingConfig = signingConfigs.getByName("release")
manifestPlaceholders["appName"] = "搞快点"
}
debug {
applicationIdSuffix = ".debug"
manifestPlaceholders["appName"] = "搞快点-dev"
}
}
compileOptions {
@ -66,29 +69,50 @@ android {
}
dependencies {
implementation("androidx.core:core-ktx:1.7.0")
// normal
implementation("androidx.appcompat:appcompat:1.4.0")
implementation("com.google.android.material:material:1.4.0")
// ktx
implementation("androidx.core:core-ktx:1.7.0")
implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.4.0")
// compose
implementation("androidx.compose.ui:ui:$composeVersion")
implementation("androidx.compose.material:material:$composeVersion")
implementation("androidx.compose.ui:ui-tooling-preview:$composeVersion")
implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.4.0")
debugImplementation("androidx.compose.ui:ui-tooling:$composeVersion")
androidTestImplementation("androidx.compose.ui:ui-test-junit4:$composeVersion")
implementation("androidx.activity:activity-compose:1.4.0")
implementation("androidx.navigation:navigation-compose:2.4.0-beta02")
// test
testImplementation("junit:junit:4.13.2")
androidTestImplementation("androidx.test.ext:junit:1.1.3")
androidTestImplementation("androidx.test.espresso:espresso-core:3.4.0")
androidTestImplementation("androidx.compose.ui:ui-test-junit4:$composeVersion")
debugImplementation("androidx.compose.ui:ui-tooling:$composeVersion")
// https://github.com/RikkaApps/Shizuku-API
val shizuku_version = "12.1.0"
implementation("dev.rikka.shizuku:api:$shizuku_version")
// Add this line if you want to support Shizuku
implementation("dev.rikka.shizuku:provider:$shizuku_version")
val shizukuVersion = "12.1.0"
implementation("dev.rikka.shizuku:api:$shizukuVersion")
implementation("dev.rikka.shizuku:provider:$shizukuVersion")
// 工具集合类
// https://github.com/Blankj/AndroidUtilCode/blob/master/lib/utilcode/README-CN.md
implementation("com.blankj:utilcodex:1.30.6")
// https://developer.android.com/jetpack/compose/navigation
implementation("androidx.navigation:navigation-compose:2.4.0-beta02")
// https://bugly.qq.com/docs/user-guide/instruction-manual-android/
implementation("com.tencent.bugly:crashreport:4.0.0")
// https://developer.android.google.cn/training/data-storage/room?hl=zh-cn
val roomVersion = "2.3.0"
implementation("androidx.room:room-runtime:$roomVersion")
kapt("androidx.room:room-compiler:$roomVersion")
implementation("androidx.room:room-ktx:$roomVersion")
implementation("com.squareup.retrofit2:retrofit:2.9.0")
implementation ("com.google.code.gson:gson:2.8.9")
}

View File

@ -18,4 +18,7 @@
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
#-renamesourcefileattribute SourceFile
-dontwarn com.tencent.bugly.**
-keep public class com.tencent.bugly.**{*;}

View File

@ -1,4 +1,4 @@
package li.songe.ad_closer
package li.songe.gkd
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.ext.junit.runners.AndroidJUnit4
@ -19,6 +19,6 @@ class ExampleInstrumentedTest {
fun useAppContext() {
// Context of the app under test.
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
assertEquals("li.songe.ad_closer", appContext.packageName)
assertEquals("li.songe.gkd", appContext.packageName)
}
}

View File

@ -1,27 +1,28 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="li.songe.ad_closer">
package="li.songe.gkd">
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<application
android:allowBackup="false"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:label="${appName}"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:name=".App"
android:name="li.songe.gkd.App"
android:theme="@style/Theme.AdCloser">
<activity
android:name=".MainActivity"
android:name="li.songe.gkd.MainActivity"
android:exported="true"
android:label="@string/app_name"
android:excludeFromRecents="true"
android:theme="@style/Theme.AdCloser.NoActionBar">
</activity>
<activity
android:name=".EntryActivity"
android:name="li.songe.gkd.EntryActivity"
android:exported="true"
android:label="@string/app_name"
android:excludeFromRecents="true"
android:theme="@style/Entry">
<intent-filter>
@ -30,9 +31,20 @@
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity android:name="li.songe.gkd.WebActivity"
android:exported="true">
<intent-filter>
<category android:name="android.intent.category.BROWSABLE" />
<data
android:host="dev.songe.li"
android:path="/detail"
android:port="80"
android:scheme="adcloser" />
</intent-filter>
</activity>
<service
android:name=".service.AdCloserService"
android:name="li.songe.gkd.service.GkdService"
android:exported="false"
android:label="@string/accessibility_service_label"
android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE">

View File

@ -1,5 +1,5 @@
// IUserService.aidl
package li.songe.ad_closer;
package li.songe.gkd;
interface IUserService {
void destroy() = 16777114; // Destroy method defined by Shizuku server

View File

@ -1,11 +0,0 @@
package li.songe.ad_closer
import android.app.Application
import com.blankj.utilcode.util.LogUtils
class App:Application() {
override fun onCreate() {
super.onCreate()
LogUtils.d("onCreate")
}
}

View File

@ -1,72 +0,0 @@
package li.songe.ad_closer
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Surface
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.tooling.preview.Preview
import com.blankj.utilcode.util.LogUtils
import li.songe.ad_closer.ui.theme.AdCloserTheme
import rikka.shizuku.Shizuku
import rikka.shizuku.Shizuku.OnRequestPermissionResultListener
import android.content.pm.PackageManager
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
AdCloserTheme {
// A surface container using the 'background' color from the theme
Surface(color = MaterialTheme.colors.background) {
Greeting("Android2")
}
}
LogUtils.d(this)
packageName
}
// Shizuku.addRequestPermissionResultListener { requestCode, grantResult ->
// LogUtils.d(requestCode, grantResult)
// }
// checkPermission(0)
// Shizuku.bindUserService()
}
private fun checkPermission(code: Int): Boolean {
if (Shizuku.isPreV11()) {
// Pre-v11 is unsupported
return false
}
return when {
Shizuku.checkSelfPermission() == PackageManager.PERMISSION_GRANTED -> {
// Granted
true
}
Shizuku.shouldShowRequestPermissionRationale() -> {
// Users choose "Deny and don't ask again"
false
}
else -> {
// Request the permission
Shizuku.requestPermission(code)
false
}
}
}
}
@Composable
fun Greeting(name: String) {
Text(text = "Hello $name 4399")
}
@Preview(showBackground = true)
@Composable
fun DefaultPreview() {
AdCloserTheme {
Greeting("React")
}
}

View File

@ -1,11 +0,0 @@
package li.songe.ad_closer.data
data class RuleGroup(
val id: Long,
val description: String,
val packageName: String,
val className: String,
val ruleList: List<String>
)
// 从网址导入时, 会显示 规则描述 目标应用 目标活动界面, 此界面可点击打开

View File

@ -0,0 +1,18 @@
package li.songe.gkd
import android.app.Application
import com.blankj.utilcode.util.LogUtils
import com.tencent.bugly.crashreport.CrashReport
class App:Application() {
companion object{
lateinit var context:Application
}
override fun onCreate() {
super.onCreate()
context = this
LogUtils.d("onCreate")
LogUtils.getConfig().isLog2FileSwitch = true
CrashReport.initCrashReport(applicationContext, "d0ce46b353", false);
}
}

View File

@ -1,4 +1,4 @@
package li.songe.ad_closer
package li.songe.gkd
import android.content.Intent
import android.os.Bundle

View File

@ -0,0 +1,44 @@
package li.songe.gkd
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Scaffold
import androidx.compose.material.Surface
import androidx.compose.ui.graphics.Color
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import androidx.navigation.compose.rememberNavController
import kotlinx.coroutines.launch
import li.songe.gkd.ui.BottomNavigationBar
import li.songe.gkd.ui.NavHostContainer
import li.songe.gkd.ui.theme.AdCloserTheme
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
AdCloserTheme {
Surface(color = MaterialTheme.colors.background) {
Surface(color = Color.White) {
val navController = rememberNavController()
Scaffold(
bottomBar = {
BottomNavigationBar(navController = navController)
}, content = { padding ->
NavHostContainer(navController = navController, padding = padding)
}
)
}
}
}
}
lifecycleScope.launch {
lifecycle.repeatOnLifecycle(Lifecycle.State.CREATED) {
}
}
}
}

View File

@ -0,0 +1,12 @@
package li.songe.gkd
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import com.blankj.utilcode.util.LogUtils
class WebActivity:AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
LogUtils.d(intent.data)
}
}

View File

@ -1,17 +1,13 @@
package li.songe.ad_closer.data
package li.songe.gkd.data
data class Rule(
val packageName: String,
val className: String,
val selector: String
val selector: String,
val description: String = "",
) {
companion object {
val defaultRuleList = listOf<Rule>(
// Rule(
// "com.zhihu.android",
// "com.zhihu.android.mix.activity.ContentMixProfileActivity",
// "View[text=查看详情] + View[text=×]"
// ),
Rule(
"com.zhihu.android",
"com.zhihu.android.mix.activity.ContentMixProfileActivity",
@ -77,6 +73,11 @@ data class Rule(
"com.baidu.tieba.tblauncher.MainTabActivity",
"ImageView[id=com.baidu.tieba:id/float_layer_feedback_picture]"
),
Rule(
"com.baidu.tieba",
"com.baidu.tieba.pb.pb.main.PbActivity",
"ImageView[id=com.baidu.tieba:id/float_layer_feedback_picture]"
),
Rule(
"com.baidu.tieba",
"com.baidu.tieba.tblauncher.MainTabActivity",
@ -125,7 +126,7 @@ data class Rule(
Rule(
"com.tencent.mm",
"com.tencent.mm.plugin.sns.ui.SnsTimeLineUI",
"LinearLayout[childCount=2] > LinearLayout[id=com.tencent.mm:id/fzb] > TextView[id=com.tencent.mm:id/fzg] + LinearLayout[id=com.tencent.mm:id/fj][childCount=0]"
"LinearLayout > LinearLayout[id=com.tencent.mm:id/fzb][childCount=2] > TextView[id=com.tencent.mm:id/fzg] + LinearLayout[id=com.tencent.mm:id/fj][childCount=0]"
),
Rule(
"com.baidu.tieba",

View File

@ -0,0 +1,30 @@
package li.songe.gkd.db
import androidx.room.Database
import androidx.room.Room
import androidx.room.RoomDatabase
import li.songe.gkd.App
@Database(entities = [Rule::class, RuleGroup::class], version = 1)
abstract class AppDatabase : RoomDatabase() {
abstract fun ruleDao(): RuleDao
abstract fun ruleGroupDao(): RuleGroupDao
companion object {
val db by lazy {
var basePath = (App.context.getExternalFilesDir(null)?.absolutePath ?: "")
var name = "database.db"
if (basePath.isNotEmpty()) {
if (!basePath.endsWith("/")) {
basePath += "/"
}
name = basePath + name
}
Room.databaseBuilder(
App.context,
AppDatabase::class.java,
name
).build()
}
}
}

View File

@ -0,0 +1,40 @@
package li.songe.gkd.db
import androidx.room.*
import kotlin.random.Random
@Entity(tableName = "rule")
data class Rule(
@PrimaryKey() @ColumnInfo(name = "id") val id: Long = Random.nextLong(
-9007199254740991L,
9007199254740991L
),
@ColumnInfo(name = "package_name") var packageName: String,
@ColumnInfo(name = "class_name") var className: String,
@ColumnInfo(name = "selector") var selector: String,
@ColumnInfo(name = "description") var description: String = "",
@ColumnInfo(name = "rule_group_id") var ruleGroupUid: Long,
@ColumnInfo(name = "ctime") var ctime: Long = System.currentTimeMillis(),
@ColumnInfo(name = "mtime") var mtime: Long = System.currentTimeMillis(),
@ColumnInfo(name = "disable") var disable: Boolean = false,
/**
* 规则序列号, 先匹配 seq 最小的规则, 如果序列号相等, 则执行顺序是未知的
*/
@ColumnInfo(name = "seq") var seq: Int = 0,
)
@Dao
interface RuleDao {
@Query("SELECT * FROM rule")
suspend fun query(): MutableList<Rule>
@Update
suspend fun update(vararg args: Rule)
@Delete
suspend fun delete(vararg args: Rule)
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insert(vararg args: Rule)
}

View File

@ -0,0 +1,48 @@
package li.songe.gkd.db
import androidx.room.*
import kotlin.random.Random
@Entity(tableName = "rule_group")
data class RuleGroup(
@PrimaryKey() @ColumnInfo(name = "id") val id: Long = Random.nextLong(
-9007199254740991L,
9007199254740991L
),
@ColumnInfo(name = "package_name") var packageName: String,
@ColumnInfo(name = "class_name") var className: String,
@ColumnInfo(name = "description") var description: String = "",
@ColumnInfo(name = "ctime") var ctime: Long = System.currentTimeMillis(),
@ColumnInfo(name = "mtime") var mtime: Long = System.currentTimeMillis(),
@ColumnInfo(name = "disable") var disable: Boolean = false,
)
data class RuleGroupWithRuleList(
@Embedded val ruleGroup: RuleGroup,
@Relation(
parentColumn = "id",
entityColumn = "rule_group_id"
)
val ruleList: MutableList<Rule>
)
@Dao
interface RuleGroupDao {
@Query("SELECT * FROM rule_group")
suspend fun query(): MutableList<RuleGroup>
@Update
suspend fun update(vararg args: RuleGroup)
@Delete
suspend fun delete(vararg args: RuleGroup)
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insert(vararg args: RuleGroup)
@Transaction
@Query("SELECT * FROM rule_group")
suspend fun queryRuleGroupWithRuleList(): MutableList<RuleGroupWithRuleList>
}

View File

@ -1,7 +1,8 @@
package li.songe.ad_closer.log
package li.songe.gkd.log
data class OperationRecord(
val timestamp: Long,
val packageName: String,
val classNme: String,
val ruleId: String)
val ruleId: String
)

View File

@ -1,28 +1,19 @@
package li.songe.ad_closer.service
package li.songe.gkd.service
import android.accessibilityservice.AccessibilityService
import android.view.accessibility.AccessibilityEvent
import android.view.accessibility.AccessibilityNodeInfo
import com.blankj.utilcode.util.LogUtils
import kotlinx.coroutines.*
import li.songe.ad_closer.data.Rule
import li.songe.ad_closer.util.MatchRule
import li.songe.ad_closer.util.findNodeInfo
import li.songe.gkd.data.Rule
import li.songe.gkd.util.MatchRule
import li.songe.gkd.util.findNodeInfo
/**
* demo: https://juejin.cn/post/6844903589127651335
*/
class AdCloserService : AccessibilityService() {
// private fun getActivityInfo(componentName: ComponentName): ActivityInfo? {
// return try {
// packageManager.getActivityInfo(componentName, 0)
// } catch (e: NameNotFoundException) {
// null
// }
// }
class GkdService : AccessibilityService() {
private val scope = CoroutineScope(Dispatchers.Default + Job())
// override fun onDestroy() {
// super.onDestroy()
@ -33,9 +24,11 @@ class AdCloserService : AccessibilityService() {
LogUtils.d("onCreate")
}
private var job: Job? = null
override fun onServiceConnected() {
super.onServiceConnected()
scope.launch {
LogUtils.d("onServiceConnected")
job = scope.launch {
while (true) {
if (!this.isActive) {
break
@ -50,21 +43,23 @@ class AdCloserService : AccessibilityService() {
if (window != null && ruleListMap.containsKey(currentActivityClassName)) {
run loop@{
ruleListMap[currentActivityClassName]!!.forEachIndexed { _, rule ->
var nodeInfo = findNodeInfo(window, rule.matchUnit, listOf(0))
var level = 0
while (nodeInfo != null && !nodeInfo.isClickable) {
nodeInfo = nodeInfo.parent
level += 1
}
val nodeInfo = findNodeInfo(window, rule.matchUnit, listOf(0))
if (nodeInfo != null) {
nodeInfo.apply {
LogUtils.dTag("click", level, rule.rawText)
performAction(AccessibilityNodeInfo.ACTION_CLICK)
LogUtils.dTag("findNode", nodeInfo)
var parentNodeInfo = nodeInfo
var level = 0
while (parentNodeInfo != null && !parentNodeInfo.isClickable) {
parentNodeInfo = parentNodeInfo.parent
level += 1
}
if (parentNodeInfo != null) {
parentNodeInfo.apply {
LogUtils.dTag("click", level, rule.rawText)
performAction(AccessibilityNodeInfo.ACTION_CLICK)
}
return@loop
}
} else {
LogUtils.dTag("click", "not isClickable", rule.rawText)
}
return@loop
}
}
@ -92,18 +87,11 @@ class AdCloserService : AccessibilityService() {
}
override fun onAccessibilityEvent(event: AccessibilityEvent?) {
if (event == null) {
if (event == null || event.className == null) {
return
}
when (event.eventType) {
AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED -> {
// LogUtils.d(rootInActiveWindow?.packageName, event.packageName, event.className)
// if (event.packageName == rootInActiveWindow?.packageName && event.className != null && event.className.startsWith(
// event.packageName
// )
// ) {
//
// }
val className = event.className.toString()
// val packageName = event.packageName.toString()
// 在桌面和应用之间来回切换, 大概率导致识别失败
@ -120,30 +108,6 @@ class AdCloserService : AccessibilityService() {
else -> {
}
}
// val componentName =
// ComponentName(
// event.packageName?.toString() ?: "",
// event.className?.toString() ?: ""
// )
// val activityInfo = getActivityInfo(componentName)
// if (activityInfo != null) {
// val newClassName = event.className.toString()
// if (currentActivityClassName != newClassName) {
// currentActivityClassName = newClassName
//// LogUtils.dTag("newClassName", newClassName, rootInActiveWindow?.packageName)
// }
// }
// when (event.eventType) {
// AccessibilityEvent.TYPE_WINDOWS_CHANGED -> {
// }
// AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED -> {
// }
// AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED -> {
//
// }
// else -> {
// }
// }
}
private var currentActivityClassName = ""
@ -160,16 +124,15 @@ class AdCloserService : AccessibilityService() {
}
override fun onInterrupt() {
scope.cancel()
// val invok = {a: Int, b: Int->a+b}
job?.cancel()
LogUtils.d("onInterrupt")
}
override fun onDestroy() {
super.onDestroy()
scope.cancel()
LogUtils.d("onDestroy")
}
}
//typealias Test = (a: Int, b: Int) -> Int
//typealias Test = (a: Int, ) -> Int
//fun invok(a: Int, b: Int = 0){
//
//}

View File

@ -1,6 +1,6 @@
package li.songe.ad_closer.service
package li.songe.gkd.service
import li.songe.ad_closer.IUserService
import li.songe.gkd.IUserService
class UserService: IUserService.Stub() {
override fun destroy() {

View File

@ -0,0 +1,34 @@
package li.songe.gkd.ui
import androidx.annotation.DrawableRes
import li.songe.gkd.R
data class BottomNavItem(
val label: String,
@DrawableRes
val icon: Int,
val route: String,
)
val BottomNavItems = listOf(
BottomNavItem(
label = "统计",
icon = R.drawable.ic_chart_bar,
route = "statistics"
),
BottomNavItem(
label = "本地",
icon = R.drawable.ic_database_set,
route = "native"
),
BottomNavItem(
label = "订阅",
icon = R.drawable.ic_link,
route = "subscription"
),
BottomNavItem(
label = "设置",
icon = R.drawable.ic_cog,
route = "settings"
),
)

View File

@ -0,0 +1,81 @@
package li.songe.gkd.ui
import androidx.compose.foundation.layout.*
import androidx.compose.material.BottomNavigation
import androidx.compose.material.BottomNavigationItem
import androidx.compose.material.Icon
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.dp
import androidx.navigation.NavController
import androidx.navigation.NavHostController
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.currentBackStackEntryAsState
import li.songe.gkd.ui.page.NativePage
import li.songe.gkd.ui.page.SettingsPage
import li.songe.gkd.ui.page.StatisticsPage
import li.songe.gkd.ui.page.SubscriptionPage
@Composable
fun NavHostContainer(
navController: NavHostController,
padding: PaddingValues
) {
NavHost(
navController = navController,
startDestination = "statistics",
modifier = Modifier.padding(paddingValues = padding),
builder = {
composable("native") {
NativePage()
}
composable("settings") {
SettingsPage()
}
composable("statistics") {
StatisticsPage()
}
composable("subscription") {
SubscriptionPage()
}
}
)
}
@Composable
fun BottomNavigationBar(navController: NavController) {
BottomNavigation(
backgroundColor = Color.White,
) {
val navBackStackEntry by navController.currentBackStackEntryAsState()
val currentRoute = navBackStackEntry?.destination?.route
LaunchedEffect(Unit){
navController.navigate(BottomNavItems[1].route)
}
BottomNavItems.forEach { navItem ->
BottomNavigationItem(
selected = currentRoute == navItem.route,
onClick = {
navController.navigate(navItem.route)
},
icon = {
Icon(
painter = painterResource(id = navItem.icon),
contentDescription = navItem.label,
modifier = Modifier.padding(2.dp)
)
},
label = {
Text(text = navItem.label)
}
)
}
}
}

View File

@ -0,0 +1,42 @@
package li.songe.gkd.ui.page
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material.Switch
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.dp
import li.songe.gkd.R
@Composable
fun NativePage() {
Column {
Row(
modifier = Modifier.height(40.dp)
) {
Image(
painter = painterResource(R.drawable.ic_app_2),
contentDescription = "",
modifier = Modifier
.fillMaxHeight()
.clip(CircleShape)
)
Column {
Text(text = "应用名称")
Text(text = "8/10")
}
val checkedState = remember { mutableStateOf(true) }
Switch(checked = checkedState.value,
onCheckedChange = {
checkedState.value = it
}
)
}
}
}

View File

@ -0,0 +1,62 @@
package li.songe.gkd.ui.page
import androidx.compose.animation.animateColorAsState
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.Switch
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
@Composable
fun SettingsPage() {
Column(
modifier = Modifier
.verticalScroll(
state = rememberScrollState()
)
.padding(20.dp)
) {
Row {
val checkedState = remember { mutableStateOf(true) }
val animatedColor = animateColorAsState(
Color(
0,
0,
0,
(0xFF * (if (checkedState.value) 1f else .3f)).toInt()
)
)
Text(
text = "服务已${(if (checkedState.value) "开启" else "关闭")}",
color = animatedColor.value
)
Switch(checked = checkedState.value,
onCheckedChange = {
checkedState.value = it
}
)
}
Row {
val checkedState = remember { mutableStateOf(true) }
Text(text = "在[最近任务]界面中隐藏本应用")
Switch(checked = checkedState.value,
onCheckedChange = { checkedState.value = it }
)
}
Row {
val checkedState = remember { mutableStateOf(true) }
Text(text = "通知栏显示")
Switch(checked = checkedState.value,
onCheckedChange = { checkedState.value = it }
)
}
}
}

View File

@ -0,0 +1,9 @@
package li.songe.gkd.ui.page
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
@Composable
fun StatisticsPage(){
Text(text = "Statistics")
}

View File

@ -0,0 +1,9 @@
package li.songe.gkd.ui.page
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
@Composable
fun SubscriptionPage(){
Text(text = "Subscription")
}

View File

@ -1,4 +1,4 @@
package li.songe.ad_closer.ui.theme
package li.songe.gkd.ui.theme
import androidx.compose.ui.graphics.Color

View File

@ -1,4 +1,4 @@
package li.songe.ad_closer.ui.theme
package li.songe.gkd.ui.theme
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.Shapes

View File

@ -1,4 +1,4 @@
package li.songe.ad_closer.ui.theme
package li.songe.gkd.ui.theme
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.material.MaterialTheme

View File

@ -1,4 +1,4 @@
package li.songe.ad_closer.ui.theme
package li.songe.gkd.ui.theme
import androidx.compose.material.Typography
import androidx.compose.ui.text.TextStyle

View File

@ -1,4 +1,4 @@
package li.songe.ad_closer.util
package li.songe.gkd.util
import java.lang.Error
import java.util.*
@ -20,7 +20,6 @@ data class AttributeSelector(val attr: Attribute, val operator: Operator, val va
Less -> "<"
More -> ">"
Start -> "^"
else-> TODO()
} + "="
}
}
@ -35,7 +34,6 @@ data class AttributeSelector(val attr: Attribute, val operator: Operator, val va
ChildCount -> "childCount"
Id -> "id"
Text -> "text"
else -> TODO()
}
}
}
@ -107,7 +105,7 @@ data class AttributeSelector(val attr: Attribute, val operator: Operator, val va
fun parseMulti(text: String): List<AttributeSelector> {
var startIndex = -1
var endIndex = -1
var endIndex: Int
val attrRawList = mutableListOf<String>()
text.forEachIndexed { index, c ->
when (c) {
@ -131,7 +129,6 @@ data class AttributeSelector(val attr: Attribute, val operator: Operator, val va
Attribute.ChildCount -> "childCount"
Attribute.Id -> "id"
Attribute.Text -> "text"
else-> TODO()
}
val operator = (when (selector.operator) {
Operator.End -> "$"
@ -140,7 +137,6 @@ data class AttributeSelector(val attr: Attribute, val operator: Operator, val va
Operator.Less -> "<"
Operator.More -> ">"
Operator.Start -> "^"
else-> TODO()
})
return "[$attr$operator=${selector.value}]"
}

View File

@ -1,8 +1,6 @@
package li.songe.ad_closer.util
package li.songe.gkd.util
import android.graphics.Rect
import android.view.accessibility.AccessibilityNodeInfo
import com.blankj.utilcode.util.LogUtils
/**
* @param pathIndexList 当前节点在节点树的路径, 最后一项代表节点是父节点第几个元素, 第一项是 0
@ -49,7 +47,6 @@ private fun match(
AttributeSelector.Operator.Less -> childCount < it.value.toInt()
AttributeSelector.Operator.More -> childCount > it.value.toInt()
AttributeSelector.Operator.Start -> false
else -> TODO()
}
AttributeSelector.Attribute.Id -> {
when (it.operator) {
@ -59,7 +56,6 @@ private fun match(
AttributeSelector.Operator.Less -> false
AttributeSelector.Operator.More -> false
AttributeSelector.Operator.Start -> false
else -> TODO()
}
}
AttributeSelector.Attribute.Text -> text != null && when (it.operator) {
@ -69,9 +65,7 @@ private fun match(
AttributeSelector.Operator.Less -> false
AttributeSelector.Operator.More -> false
AttributeSelector.Operator.Start -> text.startsWith(it.value)
else -> TODO()
}
else -> TODO()
}
}
if (!condition2) {
@ -147,17 +141,6 @@ fun findNodeInfo(
return nodeInfo1
}
//inline fun AccessibilityNodeInfo.forEach(action: (AccessibilityNodeInfo) -> Unit): Unit {
// var index = 0
// while (index < childCount) {
// val child: AccessibilityNodeInfo? = getChild(index)
// if (child != null) {
// action(child)
// }
// index += 1
// }
//}
inline fun AccessibilityNodeInfo.forEachIndexed(action: (index: Int, AccessibilityNodeInfo) -> Unit) {
var index = 0
while (index < childCount) {

View File

@ -1,4 +1,4 @@
package li.songe.ad_closer.util
package li.songe.gkd.util
data class MatchRule(val matchUnit: MatchUnit, val rawText: String) {
companion object {
@ -29,11 +29,6 @@ data class MatchRule(val matchUnit: MatchUnit, val rawText: String) {
return selector.rawText
}
}
// sealed class RuleUnit<T>(open val data: T)
// class MatchRuleUnit(override val data: MatchUnit) : RuleUnit<MatchUnit>(data)
// class RelationRuleUnit(override val data: RelationUnit) : RuleUnit<RelationUnit>(data)
}
/**

View File

@ -1,4 +1,4 @@
package li.songe.ad_closer.util
package li.songe.gkd.util
data class MatchUnit(
val className: String,
@ -19,9 +19,6 @@ data class MatchUnit(
}
}
}
// if (markIndex <= 0) {
// throw Error("markIndex: expect it>0, got $markIndex")
// }
val className = sb.toString()
return MatchUnit(

View File

@ -1,4 +1,4 @@
package li.songe.ad_closer.util
package li.songe.gkd.util
data class RelationUnit(val to: MatchUnit, val operator: Operator) {
sealed class Operator {
@ -14,6 +14,7 @@ data class RelationUnit(val to: MatchUnit, val operator: Operator) {
return offset.toString()
}
Parent -> ">"
else -> throw NotImplementedError()
}
}
}
@ -57,6 +58,7 @@ data class RelationUnit(val to: MatchUnit, val operator: Operator) {
throw Error("operator.offset: expect no-zero, got 0")
}
}
else -> throw NotImplementedError()
}
}
}

View File

@ -0,0 +1,6 @@
<vector android:autoMirrored="true" android:height="200dp"
android:viewportHeight="1024" android:viewportWidth="1024"
android:width="200dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#38C152" android:pathData="M0,3.14h1020.86v1020.86H0z"/>
<path android:fillColor="#FFFFFF" android:pathData="M325.87,621.42h-76.61l-14.84,50.07h-62.97l84.05,-257.52h32v0.21l0.17,-0.21h32l84.22,257.52h-62.97l-15.06,-50.07zM263.07,575.48h48.99l-24.04,-79.96h-1.08l-23.87,79.96zM482.06,583.22v88.27h-59.79L422.27,413.97h98.33c30.54,0 54.76,7.83 72.69,23.36 17.89,15.57 26.88,36.05 26.88,61.38 0,25.38 -8.99,45.77 -26.88,61.29 -17.93,15.49 -42.15,23.27 -72.69,23.27h-38.54v-0.04zM482.06,537.25h38.54c12.99,0 22.88,-3.57 29.68,-10.71 6.84,-7.1 10.24,-16.3 10.24,-27.49 0,-11.48 -3.36,-20.82 -10.15,-28.13 -6.75,-7.31 -16.69,-10.97 -29.76,-10.97h-38.54v77.3zM713.04,583.22v88.27h-59.79L653.25,413.97h98.33c30.54,0 54.76,7.83 72.65,23.36 17.98,15.57 26.88,36.05 26.88,61.38 0,25.38 -8.9,45.77 -26.88,61.29 -17.89,15.49 -42.11,23.27 -72.65,23.27L713.04,583.27v-0.04zM713.04,537.25h38.54c12.99,0 22.84,-3.57 29.72,-10.71 6.79,-7.1 10.24,-16.3 10.24,-27.49 0,-11.48 -3.4,-20.82 -10.15,-28.13 -6.8,-7.31 -16.73,-10.97 -29.81,-10.97L713.04,459.95v77.3z"/>
</vector>

View File

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="200dp"
android:height="200dp"
android:autoMirrored="true"
android:viewportWidth="1024"
android:viewportHeight="1024">
<path
android:fillColor="#FF000000"
android:pathData="M387.26,479.68l-192,0c-52.93,0 -96,-43.07 -96,-96l0,-192c0,-52.93 43.07,-96 96,-96l192,0c52.93,0 96,43.07 96,96l0,192C483.26,436.64 440.19,479.68 387.26,479.68zM195.26,159.68c-17.63,0 -32,14.37 -32,32l0,192c0,17.63 14.37,32 32,32l192,0c17.63,0 32,-14.37 32,-32l0,-192c0,-17.63 -14.37,-32 -32,-32L195.26,159.68z" />
<path
android:fillColor="#FF000000"
android:pathData="M387.26,927.68l-192,0c-52.93,0 -96,-43.07 -96,-96l0,-192c0,-52.93 43.07,-96 96,-96l192,0c52.93,0 96,43.07 96,96l0,192C483.26,884.64 440.19,927.68 387.26,927.68zM195.26,607.68c-17.63,0 -32,14.34 -32,32l0,192c0,17.66 14.37,32 32,32l192,0c17.63,0 32,-14.34 32,-32l0,-192c0,-17.66 -14.37,-32 -32,-32L195.26,607.68z" />
<path
android:fillColor="#FF000000"
android:pathData="M832.13,479.68l-192,0c-52.93,0 -96,-43.07 -96,-96l0,-192c0,-52.93 43.07,-96 96,-96l192,0c52.93,0 96,43.07 96,96l0,192C928.13,436.64 885.06,479.68 832.13,479.68zM640.13,159.68c-17.66,0 -32,14.37 -32,32l0,192c0,17.63 14.34,32 32,32l192,0c17.66,0 32,-14.37 32,-32l0,-192c0,-17.63 -14.34,-32 -32,-32L640.13,159.68z" />
<path
android:fillColor="#FF000000"
android:pathData="M832.13,927.68l-192,0c-52.93,0 -96,-43.07 -96,-96l0,-192c0,-52.93 43.07,-96 96,-96l192,0c52.93,0 96,43.07 96,96l0,192C928.13,884.64 885.06,927.68 832.13,927.68zM640.13,607.68c-17.66,0 -32,14.34 -32,32l0,192c0,17.66 14.34,32 32,32l192,0c17.66,0 32,-14.34 32,-32l0,-192c0,-17.66 -14.34,-32 -32,-32L640.13,607.68z" />
</vector>

View File

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="20dp"
android:height="20dp"
android:autoMirrored="true"
android:viewportWidth="1024"
android:viewportHeight="1024">
<path
android:fillColor="#262626"
android:pathData="M85.31,938.69L1024,938.69L1024,1024L0,1024L0,0h85.31v938.69zM256,341.31h85.31L341.31,768L256,768L256,341.31zM512,128h85.31v640L512,768L512,128zM768,341.31h85.31L853.31,768L768,768L768,341.31z" />
</vector>

View File

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="20dp"
android:height="20dp"
android:autoMirrored="true"
android:viewportWidth="1024"
android:viewportHeight="1024">
<path
android:fillColor="#262626"
android:pathData="M903.23,562.18c2.43,-21.82 3.65,-38.59 3.65,-50.18 0,-17.41 -1.6,-33.79 -3.65,-50.18l120.64,-102.66 -113.66,-204.54 -158.08,52.74a387.33,387.33 0,0 0,-88.96 -50.18L633.92,0L396.35,0l-35.33,157.18c-32.06,12.8 -61.57,30.21 -88.96,50.18l-155.84,-52.74L0.19,359.17 120.96,461.82c-2.11,16.38 -3.71,33.28 -3.71,50.18s1.6,33.79 3.71,50.18L12.54,657.09 128,866.82l144,-50.18c27.39,20.48 56.9,37.38 88.96,50.18L394.69,1024h239.23l29.25,-157.18c32.13,-12.8 61.57,-30.21 88.96,-50.18l145.22,50.18 125.12,-209.73 -119.23,-94.91zM798.98,474.62c2.11,15.87 2.62,26.62 2.62,37.38 0,10.75 -1.02,22.02 -2.56,37.38l-7.42,57.86 46.85,35.84 56.83,43.01 -36.86,61.95 -66.82,-26.11 -54.72,-21.5 -47.36,34.82a310.4,310.4 0,0 1,-65.86 37.38l-55.74,22.02 -8.45,57.86 -10.5,69.12L475.26,921.6l-9.98,-69.12 -8.45,-57.86 -55.81,-22.02a300.8,300.8 0,0 1,-64.7 -36.35l-47.94,-35.84 -55.74,22.02 -66.88,26.11 -36.86,-61.95 56.9,-43.01 46.85,-35.84 -7.36,-57.86A400.77,400.77 0,0 1,222.59 512c0,-10.24 1.09,-22.02 2.69,-37.38l7.36,-57.86 -46.85,-35.84 -56.9,-43.01 36.86,-61.95 66.88,26.11 54.72,21.5 47.36,-34.82a310.4,310.4 0,0 1,65.79 -37.38l55.81,-22.02 8.45,-57.86 10.5,-69.12L548.48,102.4l10.05,69.12 8.38,57.86 55.81,22.02a300.8,300.8 0,0 1,64.77 36.35l47.87,35.84 55.81,-22.02 66.82,-26.11 36.86,61.95 -56.32,43.52 -46.85,35.84 7.36,57.86zM512,320a192,192 0,1 0,0 384,192 192,0 1,0 0,-384zM512,608c-52.8,0 -96,-43.2 -96,-96S459.2,416 512,416s96,43.2 96,96S564.8,608 512,608z" />
</vector>

View File

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="20dp"
android:height="20dp"
android:autoMirrored="true"
android:viewportWidth="1024"
android:viewportHeight="1024">
<path
android:fillColor="#262626"
android:pathData="M0,311.94l469.63,222.27L469.63,1024L0,800.96L0,311.94zM85.44,743.62l298.75,141.89L384.19,591.68l-298.75,-141.44v293.38zM512.83,0l509.63,223.04L513.28,461.7 0,222.02 512.83,0zM512.7,96.58L215.68,225.09l297.47,138.88 294.78,-138.11 -295.3,-129.28zM1024,311.94v489.02L555.2,1022.72L555.2,534.21L1024,311.94zM640.64,591.62v292.8l297.92,-140.93v-293.12L640.64,591.55z" />
</vector>

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<vector android:autoMirrored="true" android:height="20dp"
android:viewportHeight="1024" android:viewportWidth="1024"
android:width="20dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#262626" android:pathData="M593.94,715.65a10.69,10.69 0,0 0,-14.98 0L424.22,870.4c-71.68,71.68 -192.58,79.23 -271.68,0 -79.23,-79.23 -71.62,-200 0,-271.62l154.75,-154.75a10.69,10.69 0,0 0,0 -15.04l-52.99,-52.99a10.69,10.69 0,0 0,-15.04 0L84.5,530.69a287.87,287.87 0,0 0,0 407.49,288 288,0 0,0 407.49,0l154.75,-154.75a10.69,10.69 0,0 0,0 -15.04l-52.74,-52.74zM938.33,84.48a288.26,288.26 0,0 1,0 407.62l-154.75,154.75a10.69,10.69 0,0 1,-15.04 0l-52.99,-52.99a10.69,10.69 0,0 1,0 -15.1l154.75,-154.69c71.68,-71.68 79.23,-192.45 0,-271.68 -79.1,-79.23 -200,-71.68 -271.68,0L443.93,307.2a10.69,10.69 0,0 1,-15.04 0l-52.86,-52.86a10.69,10.69 0,0 1,0 -15.04l154.88,-154.75a287.87,287.87 0,0 1,407.42 0zM642.01,325.38l52.67,52.74a10.69,10.69 0,0 1,0 15.04l-301.5,301.44a10.69,10.69 0,0 1,-15.04 0l-52.74,-52.67a10.69,10.69 0,0 1,0 -15.04l301.63,-301.5a10.69,10.69 0,0 1,15.04 0z"/>
</vector>

View File

@ -1,5 +1,5 @@
<resources>
<string name="app_name">AdCloser</string>
<string name="accessibility_service_label">accessibility_service_label</string>
<string name="accessibility_service_description">accessibility_service_description</string>
<string name="app_name">搞快点</string>
<string name="accessibility_service_label">搞快点</string>
<string name="accessibility_service_description">基于规则匹配的无障碍速点服务</string>
</resources>

View File

@ -1,6 +1,6 @@
package li.songe.ad_closer
package li.songe.gkd
import li.songe.ad_closer.util.MatchRule
import li.songe.gkd.util.MatchRule
import org.junit.Test
import org.junit.Assert.*

View File

@ -10,7 +10,7 @@ buildscript {
}
dependencies {
val kotlinVersion= "1.5.31"
classpath("com.android.tools.build:gradle:7.0.3")
classpath("com.android.tools.build:gradle:7.0.4")
classpath(kotlin("gradle-plugin", version = kotlinVersion))
// NOTE: Do not place your application dependencies here; they belong

View File

@ -5,5 +5,5 @@ dependencyResolutionManagement {
mavenCentral()
}
}
rootProject.name = "AdCloser"
rootProject.name = "gkd"
include(":app")