├── .gitignore ├── .idea ├── .gitignore ├── compiler.xml ├── deploymentTargetDropDown.xml ├── gradle.xml ├── kotlinc.xml ├── migrations.xml ├── misc.xml └── vcs.xml ├── README.md ├── app ├── .gitignore ├── build.gradle.kts ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── cn │ │ └── martinkay │ │ └── checkin │ │ └── ExampleInstrumentedTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── cn │ │ │ └── martinkay │ │ │ └── checkin │ │ │ ├── MainActivity.kt │ │ │ ├── MainActivityV2.java │ │ │ ├── SharePrefHelper.kt │ │ │ ├── SignApplication.kt │ │ │ ├── TimeUtil.kt │ │ │ ├── adapter │ │ │ └── AutoConfigAdapter.java │ │ │ ├── broad │ │ │ └── AlarmReceiver.kt │ │ │ ├── calendarview │ │ │ └── ProgressMonthView.kt │ │ │ ├── constant │ │ │ └── Constant.java │ │ │ ├── entity │ │ │ └── AutoConfig.java │ │ │ ├── handler │ │ │ ├── BaseHandler.java │ │ │ ├── WeixinHandler.java │ │ │ └── pageprocessor │ │ │ │ ├── BasePageProcessor.java │ │ │ │ └── weixin │ │ │ │ ├── CompleteProcessor.java │ │ │ │ ├── LoginProcessor.java │ │ │ │ ├── MessagePageProcessor.java │ │ │ │ ├── SiginInProcessor.java │ │ │ │ └── WorkPageProcessor.java │ │ │ ├── model │ │ │ ├── ActiveAppinfo.java │ │ │ ├── CalendarScheme.java │ │ │ └── WeixinInfo.java │ │ │ ├── os │ │ │ └── ActivityExt.kt │ │ │ ├── service │ │ │ ├── AssistService.java │ │ │ ├── AutoCheckinService.java │ │ │ ├── BackgroundAccess.java │ │ │ ├── MyAccessibilityService.java │ │ │ └── WifiLockService.java │ │ │ ├── util │ │ │ ├── AccessibilityHelper.java │ │ │ ├── AndroidRootUtils.java │ │ │ ├── DateUtil.java │ │ │ └── ShellUtils.java │ │ │ └── utils │ │ │ ├── AlarManagerUtil.java │ │ │ ├── AutoSignPermissionUtils.kt │ │ │ ├── DialogUtils.kt │ │ │ ├── HLog.kt │ │ │ ├── HPackages.kt │ │ │ ├── HShizuku.kt │ │ │ ├── HTarget.kt │ │ │ ├── IsServiceRunningUtil.java │ │ │ └── JumpPermissionManagement.java │ └── res │ │ ├── animator │ │ ├── dialog_in.xml │ │ └── dialog_out.xml │ │ ├── drawable │ │ ├── bg_ripple.xml │ │ ├── circle_drawable.xml │ │ ├── circle_drawable_normal.xml │ │ ├── circle_drawable_pressed.xml │ │ ├── ic_add.xml │ │ ├── ic_launcher_background.xml │ │ └── ic_launcher_foreground.xml │ │ ├── layout │ │ ├── activity_main.xml │ │ ├── activity_main_v2.xml │ │ ├── add_rule_layout.xml │ │ ├── layout_dialog_progress.xml │ │ ├── randompkg_dialog.xml │ │ ├── rule_item.xml │ │ └── setting_dialog.xml │ │ ├── menu │ │ └── menu_main.xml │ │ ├── mipmap-anydpi-v26 │ │ ├── ic_launcher.xml │ │ └── ic_launcher_round.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-xxxhdpi │ │ ├── ic_calendar.png │ │ ├── ic_clear.png │ │ ├── ic_colorful_logo.png │ │ ├── ic_custom.png │ │ ├── ic_expand_list.png │ │ ├── ic_flyme_logo.png │ │ ├── ic_full.png │ │ ├── ic_func.png │ │ ├── ic_horizontal.png │ │ ├── ic_increase.png │ │ ├── ic_index_logo.png │ │ ├── ic_launcher.png │ │ ├── ic_more.png │ │ ├── ic_multi.png │ │ ├── ic_progress.png │ │ ├── ic_range.png │ │ ├── ic_reduce.png │ │ ├── ic_simple_logo.png │ │ ├── ic_single.png │ │ ├── ic_solar_system.png │ │ ├── ic_sun.png │ │ ├── ic_tab_logo.png │ │ ├── ic_vertical.png │ │ └── logo.png │ │ ├── values-night │ │ └── themes.xml │ │ ├── values │ │ ├── arrays.xml │ │ ├── attrs.xml │ │ ├── colors.xml │ │ ├── demens.xml │ │ ├── strings.xml │ │ ├── styles.xml │ │ └── themes.xml │ │ └── xml │ │ ├── acc_service.xml │ │ ├── accessibility_service_config.xml │ │ ├── backup_rules.xml │ │ ├── data_extraction_rules.xml │ │ └── file_paths_public.xml │ └── test │ └── java │ └── cn │ └── martinkay │ └── checkin │ └── ExampleUnitTest.java ├── build.gradle.kts ├── gradle.properties ├── gradle ├── libs.versions.toml └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── img ├── preview.jpg ├── preview2.jpg ├── preview3.jpg ├── preview_email.jpg ├── preview_listen.jpg ├── preview_setting.jpg └── qrcode.png ├── randompkg └── src │ └── main │ └── java │ └── cn │ └── martinkay │ └── randompkg │ ├── hide │ └── HideAPK.kt │ ├── signing │ ├── ApkSignerV2.java │ ├── ByteArrayStream.java │ ├── CryptoUtils.java │ ├── JarMap.java │ ├── SignApk.java │ └── ZipUtils.java │ └── utils │ ├── AXML.kt │ ├── Constant.kt │ ├── Keygen.kt │ └── ShellUtils.java ├── settings.gradle.kts └── smart └── detect ├── .gitignore ├── build.gradle.kts ├── consumer-rules.pro ├── proguard-rules.pro └── src ├── androidTest └── java │ └── cn │ └── martinkay │ └── detect │ └── ExampleInstrumentedTest.kt ├── main └── AndroidManifest.xml └── test └── java └── cn └── martinkay └── detect └── ExampleUnitTest.kt /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/caches 5 | /.idea/libraries 6 | /.idea/modules.xml 7 | /.idea/workspace.xml 8 | /.idea/navEditor.xml 9 | /.idea/assetWizardSettings.xml 10 | .DS_Store 11 | /build 12 | /captures 13 | .externalNativeBuild 14 | .cxx 15 | local.properties 16 | -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/deploymentTargetDropDown.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 22 | 23 | -------------------------------------------------------------------------------- /.idea/kotlinc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | -------------------------------------------------------------------------------- /.idea/migrations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 10 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 13 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 企业微信自动打卡 2 | 3 | ## 项目介绍 4 | 将手机放在公司,每天自动打卡,不用每天都带着手机去公司了。 5 | 6 | ## 群聊 7 | ![二维码](img/qrcode.png) 8 | 群号: 937814754 9 | 10 | ## 预览 11 | ![预览](img/preview.jpg) 12 | ![预览2](img/preview2.jpg) 13 | ![预览3](img/preview3.jpg) 14 | 15 | ## 全新功能预览 16 | ![设置预览](img/preview_setting.jpg) 17 | 18 | ![邮箱预览](img/preview_email.jpg) 19 | 20 | ![监听预览](img/preview_listen.jpg) 21 | 22 | ## 全新功能说明 23 | - [x] 支持通知保活,一些有杀后台的系统也可以保证后台存活 24 | - [x] 支持兼容不同版本的企微,只需要设置好id即可 25 | - [x] 支持邮箱通知,如:查询电量、签到状态的邮件通知 26 | - [x] 支持消息监听QQ\TIM\支付宝\微信 远程控制 如 电量、签到 等基础指令 27 | - [x] 支持超级指令 设置时间段是否开启、设置邮箱、重载配置、关闭程序 28 | 29 | ## ROOT优化 30 | - [x] 优化无障碍服务,root情况下,不需要每次都开启无障碍服务 31 | - [x] 优化无障碍服务,shizuku情况下,不需要每次都开启无障碍服务 32 | - [x] 优化屏幕状态,自动完成开屏和息屏(需要ROOT或者Shizuku(免root)) 33 | - [x] 时间抖动功能,不在固定的时间,而是会有秒或者分的抖动,增加随机性 34 | - [x] 增加日历功能,可以选择哪一天打卡 35 | 36 | ## 防检测优化 37 | - [x] 增加生成随机包名功能,在下载原版程序后,在程序内生成全新的包名,防止针对性检测插件的原版包名 38 | 39 | ## 继续优化 40 | - [ ] 增加控制端,支持远程关闭自动签到,适用于突发请假的情况。 41 | 42 | 43 | ## 部分技术 44 | 45 | ### ROOT权限或Shizuku下开启无障碍服务 46 | ```shell 47 | # 获取无障碍服务列表 拿到cn.martinkay.autocheckinplugin/cn.martinkay.checkin.service.MyAccessibilityService 48 | adb shell settings get secure enabled_accessibility_services 49 | ``` 50 | 51 | ```shell 52 | adb shell settings put secure enabled_accessibility_services cn.martinkay.autocheckinplugin/cn.martinkay.checkin.service.MyAccessibilityService 53 | ``` 54 | 55 | ```shell 56 | adb shell settings put secure accessibility_enabled 1 57 | ``` 58 | 59 | 60 | ## 构建 61 | Shizuku使用Android中的隐藏api。为了避免反射,使用一个特殊的SDK JAR来直接访问这些api。要成功构建,您需要从[这里](https://github.com/Reginer/aosp-android-jar)获取Android 14 (API 34) JAR,并按照说明安装它。 62 | 63 | 64 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /app/build.gradle.kts: -------------------------------------------------------------------------------- 1 | @Suppress("DSL_SCOPE_VIOLATION") // TODO: Remove once KTIJ-19369 is fixed 2 | plugins { 3 | alias(libs.plugins.com.android.application) 4 | alias(libs.plugins.org.jetbrains.kotlin.android) 5 | } 6 | 7 | android { 8 | namespace = "cn.martinkay.autocheckinpluginplus" 9 | compileSdk = 34 10 | 11 | defaultConfig { 12 | applicationId = "cn.martinkay.autocheckinpluginplus" 13 | minSdk = 24 14 | targetSdk = 34 15 | versionCode = 23 16 | versionName = "2.3" 17 | 18 | testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" 19 | } 20 | 21 | buildTypes { 22 | release { 23 | isMinifyEnabled = false 24 | proguardFiles( 25 | getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro" 26 | ) 27 | } 28 | } 29 | 30 | buildFeatures { 31 | buildConfig = true 32 | dataBinding = true 33 | viewBinding = true 34 | } 35 | 36 | compileOptions { 37 | sourceCompatibility = JavaVersion.VERSION_1_8 38 | targetCompatibility = JavaVersion.VERSION_1_8 39 | } 40 | kotlinOptions { 41 | jvmTarget = "1.8" 42 | } 43 | 44 | } 45 | 46 | dependencies { 47 | 48 | implementation(platform("org.jetbrains.kotlin:kotlin-bom:1.8.0")) 49 | implementation(project(":randompkg")) 50 | implementation(libs.core.ktx) 51 | implementation(libs.kotlinx.coroutines.android) 52 | implementation(libs.lifecycle.runtime.ktx) 53 | implementation(libs.appcompat) 54 | implementation(libs.material) 55 | implementation(libs.apache.commons) 56 | implementation(libs.constraintlayout) 57 | implementation(libs.gson) 58 | testImplementation(libs.junit) 59 | androidTestImplementation(libs.androidx.test.ext.junit) 60 | androidTestImplementation(libs.espresso.core) 61 | implementation(libs.libsu.core) 62 | implementation(libs.libsu.io) 63 | implementation(libs.calendarview) 64 | implementation(libs.shizuku.api) 65 | implementation(libs.shizuku.provider) 66 | implementation(libs.hiddenapibypass) 67 | implementation(libs.fastjson) 68 | } -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile -------------------------------------------------------------------------------- /app/src/androidTest/java/cn/martinkay/checkin/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package cn.martinkay.checkin; 2 | 3 | import android.content.Context; 4 | 5 | import androidx.test.platform.app.InstrumentationRegistry; 6 | import androidx.test.ext.junit.runners.AndroidJUnit4; 7 | 8 | import org.junit.Test; 9 | import org.junit.runner.RunWith; 10 | 11 | import static org.junit.Assert.*; 12 | 13 | /** 14 | * Instrumented test, which will execute on an Android device. 15 | * 16 | * @see Testing documentation 17 | */ 18 | @RunWith(AndroidJUnit4.class) 19 | public class ExampleInstrumentedTest { 20 | @Test 21 | public void useAppContext() { 22 | // Context of the app under test. 23 | Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); 24 | assertEquals("cn.martinkay.autocheckinplugin", appContext.getPackageName()); 25 | } 26 | } -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 42 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 55 | 56 | 57 | 61 | 62 | 63 | 64 | 65 | 66 | 73 | 74 | 75 | 76 | 79 | 80 | 81 | 86 | 87 | 88 | 95 | 96 | 102 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | -------------------------------------------------------------------------------- /app/src/main/java/cn/martinkay/checkin/SharePrefHelper.kt: -------------------------------------------------------------------------------- 1 | package cn.martinkay.checkin 2 | 3 | import android.content.Context 4 | import android.content.SharedPreferences 5 | 6 | const val IS_ENABLE_AUTO_SIGN = "is_enable_auto_sign" 7 | 8 | const val IS_ENABLE_TIME_JITTER = "is_enable_time_jitter" 9 | 10 | const val TIME_JITTER_VALUE = "time_jitter_value" 11 | 12 | const val IS_OPEN_MORNING_START_WORK_SIGN_TASK = "is_open_morning_start_work_sign_task" 13 | const val IS_OPEN_MORNING_OFF_WORK_SIGN_TASK = "is_open_morning_off_work_sign_task" 14 | 15 | const val IS_OPEN_AFTERNOON_START_WORK_SIGN_TASK = "is_open_afternoon_start_work_sign_task" 16 | const val IS_OPEN_AFTERNOON_OFF_WORK_SIGN_TASK = "is_open_afternoon_off_work_sign_task" 17 | 18 | const val IS_OPEN_SATURDAY_SIGN_TASK = "is_open_saturday_sign_task" 19 | const val IS_OPEN_SUNDAY_SIGN_TASK = "is_open_sunday_sign_task" 20 | 21 | const val SIGN_TASK_MORNING_START_WORK_START_TIME = "sign_task_morning_start_work_start_time" 22 | 23 | const val SIGN_TASK_MORNING_OFF_WORK_START_TIME = "sign_task_morning_off_work_start_time" 24 | 25 | const val SIGN_TASK_AFTERNOON_START_WORK_START_TIME = "sign_task_start_work_start_time" 26 | 27 | const val SIGN_TASK_AFTERNOON_OFF_WORK_START_TIME = "sign_task_stop_work_start_time" 28 | 29 | const val SIGN_OPEN_INTENT_START_TIME = "sign_open_intent_start_time" 30 | 31 | const val SIGN_CALENDAR_SCHEME_CACHE = "sign_calendar_scheme_cache" 32 | 33 | const val VERSION_UPDATE_FLAG = "version_update_flag" 34 | 35 | const val ENABLE_SMART_RECOGNITION_JUMP = "enable_smart_recognition_jump" 36 | 37 | const val ENABLE_START_QUICK_SIGN = "enable_start_quick_sign" 38 | 39 | object SharePrefHelper { 40 | 41 | private var mShare: SharedPreferences? = null 42 | 43 | init { 44 | getSharePref() 45 | if (!getBoolean(VERSION_UPDATE_FLAG, false)) { 46 | // 抖动变为long类型,版本更新清除旧存储值,否则会崩溃 47 | remove(TIME_JITTER_VALUE) 48 | putBoolean(VERSION_UPDATE_FLAG, true) 49 | } 50 | } 51 | 52 | private fun initSharePref(ctx: Context): SharedPreferences { 53 | return ctx.getSharedPreferences(ctx.packageName, Context.MODE_PRIVATE) 54 | } 55 | 56 | fun getSharePref(): SharedPreferences { 57 | if (null == mShare) { 58 | mShare = initSharePref(SignApplication.getInstance()) 59 | } 60 | return mShare!! 61 | } 62 | 63 | fun putString(key: String, value: String?) { 64 | val editor = getSharePref().edit() 65 | editor.putString(key, value) 66 | editor.apply() 67 | } 68 | 69 | fun getString(key: String, default: String?): String { 70 | return getSharePref().getString(key, default).toString() 71 | } 72 | 73 | fun putLong(key: String, value: Long) { 74 | val editor = getSharePref().edit() 75 | editor.putLong(key, value) 76 | editor.apply() 77 | } 78 | 79 | fun getLong(key: String, default: Long): Long { 80 | return getSharePref().getLong(key, default) 81 | } 82 | 83 | fun putBoolean(key: String, value: Boolean = false) { 84 | val editor = getSharePref().edit() 85 | editor.putBoolean(key, value) 86 | editor.apply() 87 | } 88 | 89 | fun getBoolean(key: String, default: Boolean): Boolean { 90 | return getSharePref().getBoolean(key, default) 91 | } 92 | 93 | fun remove(key: String) { 94 | getSharePref().edit().remove(key).apply() 95 | } 96 | } 97 | 98 | // 获取 早上上班的开始时间范围 99 | fun getMorningStartWorkStartTimeStr(): String { 100 | return SharePrefHelper.getString(SIGN_TASK_MORNING_START_WORK_START_TIME, "8:50") 101 | } 102 | 103 | // 获取 早上下班的开始时间范围 104 | fun getMorningOffWorkStartTimeStr(): String { 105 | return SharePrefHelper.getString(SIGN_TASK_MORNING_OFF_WORK_START_TIME, "12:00") 106 | } 107 | 108 | /** 109 | * 获取 下午下班的开始时间范围 110 | */ 111 | fun getAfternoonStartWorkStartTimeStr(): String { 112 | return SharePrefHelper.getString(SIGN_TASK_AFTERNOON_START_WORK_START_TIME, "12:50") 113 | } 114 | 115 | /** 116 | * 获取 下午下班的开始时间范围 117 | */ 118 | fun getAfternoonOffWorkStartTimeStr(): String { 119 | return SharePrefHelper.getString(SIGN_TASK_AFTERNOON_OFF_WORK_START_TIME, "18:00") 120 | } -------------------------------------------------------------------------------- /app/src/main/java/cn/martinkay/checkin/SignApplication.kt: -------------------------------------------------------------------------------- 1 | package cn.martinkay.checkin 2 | 3 | import android.app.Application 4 | 5 | class SignApplication : Application() { 6 | private var mFlag = false 7 | 8 | companion object { 9 | private lateinit var mApp: SignApplication 10 | 11 | fun getInstance(): SignApplication { 12 | return mApp 13 | } 14 | } 15 | 16 | override fun onCreate() { 17 | super.onCreate() 18 | mApp = this 19 | } 20 | 21 | fun getFlag(): Boolean { 22 | return mFlag 23 | } 24 | 25 | fun setFlag(mFlag: Boolean) { 26 | this.mFlag = mFlag 27 | } 28 | 29 | 30 | 31 | } -------------------------------------------------------------------------------- /app/src/main/java/cn/martinkay/checkin/TimeUtil.kt: -------------------------------------------------------------------------------- 1 | package cn.martinkay.checkin 2 | 3 | fun formatTime(str: String): String { 4 | var startHour = str.split(":")[0].toInt() 5 | val startMinute = str.split(":")[1].toInt() 6 | val str = formatNum(startHour) + ":" + formatNum(startMinute) 7 | return str 8 | } 9 | 10 | fun formatNum(num: Int): String { 11 | if (num < 10) { 12 | return "0$num" 13 | } 14 | return num.toString() 15 | } -------------------------------------------------------------------------------- /app/src/main/java/cn/martinkay/checkin/adapter/AutoConfigAdapter.java: -------------------------------------------------------------------------------- 1 | package cn.martinkay.checkin.adapter; 2 | 3 | import android.content.Context; 4 | import android.view.LayoutInflater; 5 | import android.view.View; 6 | import android.view.ViewGroup; 7 | import android.widget.BaseAdapter; 8 | import android.widget.TextView; 9 | 10 | import java.util.List; 11 | 12 | import cn.martinkay.autocheckinplugin.R; 13 | import cn.martinkay.checkin.entity.AutoConfig; 14 | 15 | public class AutoConfigAdapter extends BaseAdapter { 16 | 17 | private Context context; 18 | private List autoConfigList; 19 | 20 | public AutoConfigAdapter(Context context, List autoConfigList) { 21 | this.context = context; 22 | this.autoConfigList = autoConfigList; 23 | } 24 | 25 | @Override 26 | public int getCount() { 27 | return autoConfigList.size(); 28 | } 29 | 30 | @Override 31 | public Object getItem(int position) { 32 | return autoConfigList.get(position); 33 | } 34 | 35 | @Override 36 | public long getItemId(int position) { 37 | return (long) this.autoConfigList.get(position).hashCode(); 38 | } 39 | 40 | @Override 41 | public View getView(int position, View convertView, ViewGroup parent) { 42 | AutoConfig autoConfig = autoConfigList.get(position); 43 | View view = LayoutInflater.from(context).inflate(R.layout.rule_item, null); 44 | TextView name = view.findViewById(R.id.auto_config_name); 45 | TextView time = view.findViewById(R.id.auto_config_time); 46 | TextView week = view.findViewById(R.id.auto_config_week); 47 | TextView count = view.findViewById(R.id.auto_config_count); 48 | TextView next = view.findViewById(R.id.auto_config_next); 49 | 50 | name.setText(autoConfig.getName().toString()); 51 | time.setText(autoConfig.getActiveTime().toString()); 52 | week.setText(autoConfig.getActiveWeek().toString()); 53 | count.setText(autoConfig.getActiveCount().toString()); 54 | next.setText(autoConfig.getNextActiveCount().toString()); 55 | return view; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /app/src/main/java/cn/martinkay/checkin/calendarview/ProgressMonthView.kt: -------------------------------------------------------------------------------- 1 | package cn.martinkay.checkin.calendarview 2 | 3 | import android.content.Context 4 | import android.graphics.Canvas 5 | import android.graphics.Paint 6 | import android.graphics.RectF 7 | import android.view.View 8 | import cn.martinkay.checkin.model.CalendarScheme 9 | import com.haibin.calendarview.Calendar 10 | import com.haibin.calendarview.MonthView 11 | 12 | /** 13 | * 精美进度风格 14 | * Created by huanghaibin on 2018/2/8. 15 | */ 16 | class ProgressMonthView(context: Context) : MonthView(context) { 17 | 18 | private var radius = 0 19 | 20 | private val progressPaint = Paint() 21 | private val noneProgressPaint = Paint() 22 | private val forbiddenPaint = Paint() 23 | private val futurePaint = Paint() 24 | 25 | init { 26 | progressPaint.isAntiAlias = true 27 | progressPaint.style = Paint.Style.STROKE 28 | progressPaint.strokeWidth = dipToPx(context, 2.2f).toFloat() 29 | progressPaint.color = -0x440ab600 30 | 31 | noneProgressPaint.isAntiAlias = true 32 | noneProgressPaint.style = Paint.Style.STROKE 33 | noneProgressPaint.strokeWidth = dipToPx(context, 2.2f).toFloat() 34 | noneProgressPaint.color = -0x6f303031 35 | 36 | forbiddenPaint.isAntiAlias = true 37 | forbiddenPaint.style = Paint.Style.FILL 38 | forbiddenPaint.color = (0xffff4444).toInt() 39 | 40 | android.R.color.holo_green_light 41 | futurePaint.isAntiAlias = true 42 | futurePaint.style = Paint.Style.FILL 43 | futurePaint.color = (0xff99cc00).toInt() 44 | } 45 | 46 | override fun onLongClick(v: View?): Boolean { 47 | val result = super.onLongClick(v) 48 | // 强制刷新一次,不然外部拦截不会走到parent内部的 invalidate 49 | if (result) { 50 | invalidate() 51 | } 52 | return result 53 | } 54 | 55 | override fun onPreviewHook() { 56 | radius = mItemWidth.coerceAtMost(mItemHeight) / 11 * 4 57 | } 58 | 59 | override fun onDrawSelected( 60 | canvas: Canvas, calendar: Calendar, x: Int, y: Int, hasScheme: Boolean 61 | ): Boolean { 62 | //val cx = x + mItemWidth / 2 63 | //val cy = y + mItemHeight / 2 64 | //canvas.drawCircle(cx.toFloat(), cy.toFloat(), radius.toFloat(), mSelectedPaint) 65 | return true 66 | } 67 | 68 | override fun onDrawScheme(canvas: Canvas, calendar: Calendar, x: Int, y: Int) { 69 | val scheme = calendar.scheme 70 | 71 | val cx = x + mItemWidth / 2 72 | val cy = y + mItemHeight / 2 73 | when (scheme) { 74 | CalendarScheme.AUTO_SIGN_DAY_ALLOW -> { 75 | canvas.drawCircle(cx.toFloat(), cy.toFloat(), radius.toFloat(), futurePaint) 76 | } 77 | 78 | CalendarScheme.AUTO_SIGN_DAY_FORBIDDEN -> { 79 | canvas.drawCircle(cx.toFloat(), cy.toFloat(), radius.toFloat(), forbiddenPaint) 80 | } 81 | 82 | else -> { 83 | val angle = getAngle(calendar.scheme) 84 | if (angle != 0) { 85 | val progressRectF = RectF( 86 | (cx - radius).toFloat(), 87 | (cy - radius).toFloat(), 88 | (cx + radius).toFloat(), 89 | (cy + radius).toFloat() 90 | ) 91 | canvas.drawArc(progressRectF, -90f, angle.toFloat(), false, progressPaint) 92 | val noneRectF = RectF( 93 | (cx - radius).toFloat(), 94 | (cy - radius).toFloat(), 95 | (cx + radius).toFloat(), 96 | (cy + radius).toFloat() 97 | ) 98 | canvas.drawArc( 99 | noneRectF, 100 | (angle - 90).toFloat(), 101 | (360 - angle).toFloat(), 102 | false, 103 | noneProgressPaint 104 | ) 105 | } 106 | } 107 | } 108 | } 109 | 110 | override fun onDrawText( 111 | canvas: Canvas, calendar: Calendar, x: Int, y: Int, hasScheme: Boolean, isSelected: Boolean 112 | ) { 113 | val baselineY = mTextBaseLine + y 114 | val cx = x + mItemWidth / 2 115 | canvas.drawText( 116 | calendar.day.toString(), cx.toFloat(), baselineY, mCurMonthTextPaint 117 | ) 118 | } 119 | 120 | companion object { 121 | /** 122 | * 获取角度 123 | * 124 | * @param progress 进度 125 | * @return 获取角度 126 | */ 127 | private fun getAngle(scheme: String): Int { 128 | return when (scheme) { 129 | CalendarScheme.AUTO_SIGN_COUNT_1 -> 90 130 | CalendarScheme.AUTO_SIGN_COUNT_2 -> 180 131 | CalendarScheme.AUTO_SIGN_COUNT_3 -> 270 132 | CalendarScheme.AUTO_SIGN_COUNT_4 -> 360 133 | else -> 0 134 | } 135 | } 136 | 137 | /** 138 | * dp转px 139 | * 140 | * @param context context 141 | * @param dpValue dp 142 | * @return px 143 | */ 144 | private fun dipToPx(context: Context, dpValue: Float): Int { 145 | val scale = context.resources.displayMetrics.density 146 | return (dpValue * scale + 0.5f).toInt() 147 | } 148 | } 149 | } -------------------------------------------------------------------------------- /app/src/main/java/cn/martinkay/checkin/constant/Constant.java: -------------------------------------------------------------------------------- 1 | package cn.martinkay.checkin.constant; 2 | 3 | import cn.martinkay.checkin.model.ActiveAppinfo; 4 | import cn.martinkay.checkin.model.WeixinInfo; 5 | 6 | public class Constant { 7 | 8 | public static ActiveAppinfo activeAppinfo = null; 9 | 10 | public static String active_app = "weixin"; 11 | public static String package_name_weixin = "com.tencent.wework"; 12 | public static String className_weixin = "com.tencent.wework.launch.LaunchSplashActivity"; 13 | 14 | public static boolean isRandomPkg = false; 15 | 16 | public static String pkg = "cn.martinkay.autocheckinplugin"; 17 | 18 | public static boolean isRoot = false; 19 | 20 | public static boolean isShizuku = false; 21 | 22 | public static ActiveAppinfo getActiveApp() { 23 | if (activeAppinfo == null) { 24 | String str = active_app; 25 | if (str.equals(WeixinInfo.name)) { 26 | activeAppinfo = new WeixinInfo(); 27 | } 28 | } 29 | return activeAppinfo; 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /app/src/main/java/cn/martinkay/checkin/entity/AutoConfig.java: -------------------------------------------------------------------------------- 1 | package cn.martinkay.checkin.entity; 2 | 3 | public class AutoConfig { 4 | // 规则名 5 | private String name; 6 | // 触发时间 7 | private String activeTime; 8 | // 触发星期 9 | private String activeWeek; 10 | // 触发次数 11 | private Integer activeCount; 12 | // 触发次数大于1时,下次触发间隔时间。 13 | private Integer nextActiveCount; 14 | 15 | public String getName() { 16 | return name; 17 | } 18 | 19 | public void setName(String name) { 20 | this.name = name; 21 | } 22 | 23 | public String getActiveTime() { 24 | return activeTime; 25 | } 26 | 27 | public void setActiveTime(String activeTime) { 28 | this.activeTime = activeTime; 29 | } 30 | 31 | public String getActiveWeek() { 32 | return activeWeek; 33 | } 34 | 35 | public void setActiveWeek(String activeWeek) { 36 | this.activeWeek = activeWeek; 37 | } 38 | 39 | public Integer getActiveCount() { 40 | return activeCount; 41 | } 42 | 43 | public void setActiveCount(Integer activeCount) { 44 | this.activeCount = activeCount; 45 | } 46 | 47 | public Integer getNextActiveCount() { 48 | return nextActiveCount; 49 | } 50 | 51 | public void setNextActiveCount(Integer nextActiveCount) { 52 | this.nextActiveCount = nextActiveCount; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /app/src/main/java/cn/martinkay/checkin/handler/BaseHandler.java: -------------------------------------------------------------------------------- 1 | package cn.martinkay.checkin.handler; 2 | 3 | import android.view.accessibility.AccessibilityEvent; 4 | 5 | import cn.martinkay.checkin.service.MyAccessibilityService; 6 | 7 | public interface BaseHandler { 8 | boolean canHandler(String packageName); 9 | 10 | void doHandle(AccessibilityEvent event, MyAccessibilityService myAccessibilityService) throws Exception; 11 | 12 | } 13 | -------------------------------------------------------------------------------- /app/src/main/java/cn/martinkay/checkin/handler/WeixinHandler.java: -------------------------------------------------------------------------------- 1 | package cn.martinkay.checkin.handler; 2 | 3 | import android.view.accessibility.AccessibilityEvent; 4 | import android.view.accessibility.AccessibilityNodeInfo; 5 | import cn.martinkay.checkin.handler.pageprocessor.weixin.CompleteProcessor; 6 | import cn.martinkay.checkin.handler.pageprocessor.weixin.MessagePageProcessor; 7 | import cn.martinkay.checkin.handler.pageprocessor.weixin.SiginInProcessor; 8 | import cn.martinkay.checkin.handler.pageprocessor.weixin.WorkPageProcessor; 9 | import cn.martinkay.checkin.service.MyAccessibilityService; 10 | import cn.martinkay.checkin.util.AccessibilityHelper; 11 | import cn.martinkay.checkin.utils.AutoSignPermissionUtils; 12 | 13 | public class WeixinHandler implements BaseHandler { 14 | private static final String TAG = "WeixinHandler"; 15 | public static final String packageName = "com.tencent.wework"; 16 | private WorkPageProcessor workPageProcessor = new WorkPageProcessor(); 17 | private MessagePageProcessor messagePageProcessor = new MessagePageProcessor(); 18 | private SiginInProcessor siginInProcessor = new SiginInProcessor(); 19 | 20 | private CompleteProcessor completeProcessor = new CompleteProcessor(); 21 | 22 | @Override 23 | public void doHandle(AccessibilityEvent event, MyAccessibilityService myAccessibilityService) 24 | throws Exception { 25 | if (!AutoSignPermissionUtils.INSTANCE.isMobileAutoSignLaunch()) { 26 | return; 27 | } 28 | AccessibilityNodeInfo nodeById = 29 | AccessibilityHelper.getNodeById(myAccessibilityService, "com.tencent.wework:id/hrb", 30 | 0); 31 | if (nodeById != null) { 32 | AccessibilityHelper.clickButtonByNode(myAccessibilityService, nodeById); 33 | } else if (this.messagePageProcessor.canParse(event, myAccessibilityService)) { 34 | // 首页 35 | this.messagePageProcessor.processPage(event, myAccessibilityService); 36 | } else if (this.workPageProcessor.canParse(event, myAccessibilityService)) { 37 | // 控制台 38 | this.workPageProcessor.processPage(event, myAccessibilityService); 39 | } else if (this.completeProcessor.canParse(event, myAccessibilityService)) { 40 | // 打卡完成页 41 | this.completeProcessor.processPage(event, myAccessibilityService); 42 | } else if (this.siginInProcessor.canParse(event, myAccessibilityService)) { 43 | // 打卡页 44 | this.siginInProcessor.processPage(event, myAccessibilityService); 45 | } 46 | } 47 | 48 | @Override public boolean canHandler(String packageName2) { 49 | return packageName.equals(packageName2); 50 | } 51 | } -------------------------------------------------------------------------------- /app/src/main/java/cn/martinkay/checkin/handler/pageprocessor/BasePageProcessor.java: -------------------------------------------------------------------------------- 1 | package cn.martinkay.checkin.handler.pageprocessor; 2 | 3 | import android.os.Build; 4 | import android.view.accessibility.AccessibilityEvent; 5 | import android.view.accessibility.AccessibilityNodeInfo; 6 | import java.util.ArrayList; 7 | import java.util.Iterator; 8 | import java.util.LinkedList; 9 | import java.util.List; 10 | import java.util.Random; 11 | 12 | import cn.martinkay.checkin.service.MyAccessibilityService; 13 | 14 | /* loaded from: classes.dex */ 15 | public abstract class BasePageProcessor { 16 | Random random = new Random(); 17 | 18 | public abstract boolean canParse(AccessibilityEvent event, MyAccessibilityService myAccessibilityService); 19 | 20 | public abstract void processPage(AccessibilityEvent event, MyAccessibilityService myAccessibilityService); 21 | 22 | public void printAllNodes(AccessibilityEvent event, MyAccessibilityService myAccessibilityService) { 23 | ArrayList nodesFromWindows = myAccessibilityService.getNodesFromWindows(); 24 | if (nodesFromWindows != null) { 25 | Iterator it = nodesFromWindows.iterator(); 26 | while (it.hasNext()) { 27 | executeOperation(it.next()); 28 | } 29 | } 30 | } 31 | 32 | private boolean executeOperation(AccessibilityNodeInfo info) { 33 | if (info == null) { 34 | return false; 35 | } 36 | if (info.getChildCount() == 0) { 37 | System.out.println(info.toString()); 38 | } else { 39 | for (int i = 0; i < info.getChildCount(); i++) { 40 | if (info.getChild(i) != null) { 41 | executeOperation(info.getChild(i)); 42 | } 43 | } 44 | } 45 | return false; 46 | } 47 | 48 | public AccessibilityNodeInfo findNodesByText(MyAccessibilityService myAccessibilityService, String text) { 49 | ArrayList nodesFromWindows = myAccessibilityService.getNodesFromWindows(); 50 | LinkedList linkedList = new LinkedList(); 51 | if (nodesFromWindows != null) { 52 | Iterator it = nodesFromWindows.iterator(); 53 | while (it.hasNext()) { 54 | listAllNodes(it.next(), linkedList); 55 | } 56 | } 57 | for (AccessibilityNodeInfo accessibilityNodeInfo : linkedList) { 58 | if (accessibilityNodeInfo.getText() != null) { 59 | System.out.println(accessibilityNodeInfo.getText()); 60 | if (text.equals(accessibilityNodeInfo.getText().toString())) { 61 | return accessibilityNodeInfo; 62 | } 63 | } 64 | if (Build.VERSION.SDK_INT >= 26 && accessibilityNodeInfo.getHintText() != null && text.equals(accessibilityNodeInfo.getHintText().toString())) { 65 | return accessibilityNodeInfo; 66 | } 67 | } 68 | return null; 69 | } 70 | 71 | private void listAllNodes(AccessibilityNodeInfo info, List allNodes) { 72 | if (info == null) { 73 | return; 74 | } 75 | if (info.getChildCount() == 0) { 76 | allNodes.add(info); 77 | return; 78 | } 79 | for (int i = 0; i < info.getChildCount(); i++) { 80 | if (info.getChild(i) != null) { 81 | listAllNodes(info.getChild(i), allNodes); 82 | } 83 | } 84 | } 85 | } -------------------------------------------------------------------------------- /app/src/main/java/cn/martinkay/checkin/handler/pageprocessor/weixin/CompleteProcessor.java: -------------------------------------------------------------------------------- 1 | package cn.martinkay.checkin.handler.pageprocessor.weixin; 2 | 3 | import android.util.Log; 4 | import android.view.accessibility.AccessibilityEvent; 5 | import cn.martinkay.checkin.handler.pageprocessor.BasePageProcessor; 6 | import cn.martinkay.checkin.service.MyAccessibilityService; 7 | import cn.martinkay.checkin.util.AccessibilityHelper; 8 | import cn.martinkay.checkin.utils.AutoSignPermissionUtils; 9 | 10 | public class CompleteProcessor extends BasePageProcessor { 11 | @Override public boolean canParse(AccessibilityEvent event, 12 | MyAccessibilityService myAccessibilityService) { 13 | if (AccessibilityHelper.getNodeById(myAccessibilityService, "com.tencent.wework:id/bov", 0) 14 | != null && AccessibilityHelper.getNodeById(myAccessibilityService, 15 | "com.tencent.wework:id/bov", 0).toString().contains("后打卡")) { 16 | return true; 17 | } 18 | if (AccessibilityHelper.getNodeByText(myAccessibilityService, "后打卡", 0) != null) { 19 | return true; 20 | } 21 | if (AccessibilityHelper.getNodeByText(myAccessibilityService, "今日打卡已完成,好好休息", 0) 22 | != null) { 23 | return true; 24 | } 25 | if (AccessibilityHelper.getNodeByText(myAccessibilityService, "上班·正常", 0) != null) { 26 | return true; 27 | } 28 | if (AccessibilityHelper.getNodeByText(myAccessibilityService, "下班·正常", 0) != null) { 29 | return true; 30 | } 31 | if (AccessibilityHelper.getNodeByText(myAccessibilityService, "下班自动打卡·正常", 0) 32 | != null) { 33 | return true; 34 | } 35 | if (AccessibilityHelper.getNodeByText(myAccessibilityService, "上班自动打卡·正常", 0) 36 | != null) { 37 | return true; 38 | } 39 | if (AccessibilityHelper.getNodeByText(myAccessibilityService, "上班自动打卡·正常", 0) 40 | != null) { 41 | return true; 42 | } 43 | return false; 44 | } 45 | 46 | @Override public void processPage(AccessibilityEvent event, 47 | MyAccessibilityService myAccessibilityService) { 48 | if (!AutoSignPermissionUtils.INSTANCE.isMobileAutoSignLaunch()) { 49 | return; 50 | } 51 | Log.w("CompleteProcessor", "打卡成功 关闭"); 52 | AutoSignPermissionUtils.INSTANCE.increaseTodayAutoSignCount(); 53 | myAccessibilityService.clickHomeKey(); 54 | myAccessibilityService.closeApp("com.tencent.wework"); 55 | myAccessibilityService.autoLock(); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /app/src/main/java/cn/martinkay/checkin/handler/pageprocessor/weixin/LoginProcessor.java: -------------------------------------------------------------------------------- 1 | package cn.martinkay.checkin.handler.pageprocessor.weixin; 2 | 3 | import android.view.accessibility.AccessibilityEvent; 4 | import android.view.accessibility.AccessibilityNodeInfo; 5 | 6 | import cn.martinkay.checkin.handler.pageprocessor.BasePageProcessor; 7 | import cn.martinkay.checkin.service.MyAccessibilityService; 8 | import cn.martinkay.checkin.util.AccessibilityHelper; 9 | 10 | public class LoginProcessor extends BasePageProcessor { 11 | @Override 12 | public boolean canParse(AccessibilityEvent event, MyAccessibilityService myAccessibilityService) { 13 | AccessibilityNodeInfo topTextView = AccessibilityHelper.getNodeById(myAccessibilityService, "com.tencent.wework:id/lkw", 0); 14 | 15 | return false; 16 | } 17 | 18 | @Override 19 | public void processPage(AccessibilityEvent event, MyAccessibilityService myAccessibilityService) { 20 | 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /app/src/main/java/cn/martinkay/checkin/handler/pageprocessor/weixin/MessagePageProcessor.java: -------------------------------------------------------------------------------- 1 | package cn.martinkay.checkin.handler.pageprocessor.weixin; 2 | 3 | import static cn.martinkay.checkin.SharePrefHelperKt.ENABLE_START_QUICK_SIGN; 4 | 5 | import android.util.Log; 6 | import android.view.accessibility.AccessibilityEvent; 7 | import android.view.accessibility.AccessibilityNodeInfo; 8 | 9 | import java.text.SimpleDateFormat; 10 | import java.util.Date; 11 | 12 | import cn.martinkay.checkin.SharePrefHelper; 13 | import cn.martinkay.checkin.handler.pageprocessor.BasePageProcessor; 14 | import cn.martinkay.checkin.service.MyAccessibilityService; 15 | import cn.martinkay.checkin.util.AccessibilityHelper; 16 | import cn.martinkay.checkin.utils.AutoSignPermissionUtils; 17 | 18 | public class MessagePageProcessor extends BasePageProcessor { 19 | @Override 20 | public void processPage(AccessibilityEvent event, MyAccessibilityService myAccessibilityService) { 21 | try { 22 | boolean enableStartQuickSign = SharePrefHelper.INSTANCE.getBoolean(ENABLE_START_QUICK_SIGN, false); 23 | if (enableStartQuickSign) { 24 | // 如果开启了兼容启动快捷打卡,则查找 打卡这个应用的消息 25 | /** 26 | * 需要根据时间的上下5分钟的范围来确定是否是最近的一次打卡消息,否则可能会误判 27 | */ 28 | // 获取当前时间的上下5分钟的范围 29 | long fiveMinutes = 5 * 60 * 1000; 30 | // 获取消息列表 31 | AccessibilityNodeInfo offWork = AccessibilityHelper.getNodeByText(myAccessibilityService, "下班自动打卡", 0); 32 | // xx:xx下班自动打卡 取xx:xx 33 | if (offWork != null) { 34 | String offWorkText = offWork.getText().toString(); 35 | String time = offWorkText.substring(0, offWorkText.indexOf("下班自动打卡")); 36 | // time是xx:xx,转换为时间戳 37 | // 定义时间格式 38 | SimpleDateFormat sdf = new SimpleDateFormat("HH:mm"); 39 | // 将时间字符串解析为Date对象 40 | Date date = sdf.parse(time); 41 | 42 | // 获取当前时间 43 | Date currentTime = new Date(); 44 | 45 | // 将当前时间转换为时分格式 46 | String currentTimeString = sdf.format(currentTime); 47 | Date currentDate = sdf.parse(currentTimeString); 48 | 49 | // 计算时间差值 50 | long diff = Math.abs(date.getTime() - currentDate.getTime()); 51 | 52 | // 如果时间差值在5分钟内,则认为是最近的一次打卡消息 53 | if (diff <= fiveMinutes) { 54 | Log.w("CompleteProcessor", "打卡成功 关闭"); 55 | AutoSignPermissionUtils.INSTANCE.increaseTodayAutoSignCount(); 56 | myAccessibilityService.clickHomeKey(); 57 | myAccessibilityService.closeApp("com.tencent.wework"); 58 | myAccessibilityService.autoLock(); 59 | return; 60 | } 61 | } else { 62 | AccessibilityNodeInfo work = AccessibilityHelper.getNodeByText(myAccessibilityService, "上班自动打卡", 0); 63 | if (work != null) { 64 | 65 | String workText = work.getText().toString(); 66 | String time = workText.substring(0, workText.indexOf("上班自动打卡")); 67 | // time是xx:xx,转换为时间戳 68 | // 定义时间格式 69 | SimpleDateFormat sdf = new SimpleDateFormat("HH:mm"); 70 | // 将时间字符串解析为Date对象 71 | Date date = sdf.parse(time); 72 | 73 | // 获取当前时间 74 | Date currentTime = new Date(); 75 | 76 | // 将当前时间转换为时分格式 77 | String currentTimeString = sdf.format(currentTime); 78 | Date currentDate = sdf.parse(currentTimeString); 79 | 80 | // 计算时间差值 81 | long diff = Math.abs(date.getTime() - currentDate.getTime()); 82 | 83 | // 如果时间差值在5分钟内,则认为是最近的一次打卡消息 84 | if (diff <= fiveMinutes) { 85 | Log.w("CompleteProcessor", "打卡成功 关闭"); 86 | AutoSignPermissionUtils.INSTANCE.increaseTodayAutoSignCount(); 87 | myAccessibilityService.clickHomeKey(); 88 | myAccessibilityService.closeApp("com.tencent.wework"); 89 | myAccessibilityService.autoLock(); 90 | return; 91 | } 92 | } 93 | } 94 | AccessibilityHelper.clickButtonByNode(myAccessibilityService, findNodesByText(myAccessibilityService, "工作台")); 95 | } else { 96 | AccessibilityHelper.clickButtonByNode(myAccessibilityService, findNodesByText(myAccessibilityService, "工作台")); 97 | } 98 | } catch (Exception unused) { 99 | Log.e("Weixin", "首页处理失败"); 100 | } 101 | } 102 | 103 | @Override 104 | public boolean canParse(AccessibilityEvent event, MyAccessibilityService myAccessibilityService) { 105 | // 顶部的文字 106 | AccessibilityNodeInfo topTextView = AccessibilityHelper.getNodeById(myAccessibilityService, "com.tencent.wework:id/lkw", 0); 107 | if (topTextView != null) { 108 | return topTextView.getText().toString().contains("消息"); 109 | } else { 110 | return false; 111 | } 112 | } 113 | } -------------------------------------------------------------------------------- /app/src/main/java/cn/martinkay/checkin/handler/pageprocessor/weixin/SiginInProcessor.java: -------------------------------------------------------------------------------- 1 | package cn.martinkay.checkin.handler.pageprocessor.weixin; 2 | 3 | import android.util.Log; 4 | import android.view.accessibility.AccessibilityEvent; 5 | import android.view.accessibility.AccessibilityNodeInfo; 6 | import cn.martinkay.checkin.handler.pageprocessor.BasePageProcessor; 7 | import cn.martinkay.checkin.service.MyAccessibilityService; 8 | import cn.martinkay.checkin.util.AccessibilityHelper; 9 | import cn.martinkay.checkin.utils.AutoSignPermissionUtils; 10 | 11 | public class SiginInProcessor extends BasePageProcessor { 12 | private final String TAG = "SigninPageProcessor"; 13 | 14 | @Override public void processPage(AccessibilityEvent event, 15 | MyAccessibilityService myAccessibilityService) { 16 | try { 17 | Thread.sleep(2500L); 18 | if (findNodesByText(myAccessibilityService, "你已在打卡范围内") != null) { 19 | // 意味着这个时间段已经打卡过了 20 | AccessibilityNodeInfo workSign = 21 | AccessibilityHelper.getNodeByText(myAccessibilityService, "上班打卡", 0); 22 | AccessibilityNodeInfo offWorkSign = 23 | AccessibilityHelper.getNodeByText(myAccessibilityService, "下班打卡", 0); 24 | 25 | if (workSign != null) { 26 | AccessibilityHelper.clickButtonByNode(myAccessibilityService, workSign); 27 | Thread.sleep(3000L); 28 | Log.i("SigninPageProcessor", "打卡成功-已完成,返回页面"); 29 | runAccessibilityService(myAccessibilityService); 30 | } 31 | 32 | if (offWorkSign != null) { 33 | AccessibilityHelper.clickButtonByNode(myAccessibilityService, offWorkSign); 34 | Thread.sleep(3000L); 35 | Log.i("SigninPageProcessor", "打卡成功-已完成,返回页面"); 36 | runAccessibilityService(myAccessibilityService); 37 | } 38 | } else { 39 | AccessibilityHelper.moveToUp(myAccessibilityService); 40 | } 41 | // 第一次打卡完成 42 | if (findNodesByText(myAccessibilityService, "今日打卡已完成") != null) { 43 | Log.i("SigninPageProcessor", "打卡成功-已完成,返回页面"); 44 | runAccessibilityService(myAccessibilityService); 45 | } 46 | } catch (Exception unused) { 47 | Log.i("SigninPageProcessor", "工作台打卡失败"); 48 | } 49 | } 50 | 51 | private void runAccessibilityService(MyAccessibilityService myAccessibilityService) { 52 | if (!AutoSignPermissionUtils.INSTANCE.isMobileAutoSignLaunch()) { 53 | return; 54 | } 55 | AutoSignPermissionUtils.INSTANCE.increaseTodayAutoSignCount(); 56 | myAccessibilityService.clickHomeKey(); 57 | myAccessibilityService.closeApp("com.tencent.wework"); 58 | myAccessibilityService.autoLock(); 59 | } 60 | 61 | @Override public boolean canParse(AccessibilityEvent event, 62 | MyAccessibilityService myAccessibilityService) { 63 | return AccessibilityHelper.getNodeByText(myAccessibilityService, "打卡", 0) != null; 64 | } 65 | } -------------------------------------------------------------------------------- /app/src/main/java/cn/martinkay/checkin/handler/pageprocessor/weixin/WorkPageProcessor.java: -------------------------------------------------------------------------------- 1 | package cn.martinkay.checkin.handler.pageprocessor.weixin; 2 | 3 | 4 | import android.graphics.Path; 5 | import android.util.Log; 6 | import android.view.accessibility.AccessibilityEvent; 7 | import android.view.accessibility.AccessibilityNodeInfo; 8 | 9 | import cn.martinkay.checkin.handler.pageprocessor.BasePageProcessor; 10 | import cn.martinkay.checkin.service.MyAccessibilityService; 11 | import cn.martinkay.checkin.util.AccessibilityHelper; 12 | 13 | /** 14 | * 在控制台页面中,需要向下滑动直到找到打卡按钮,然后点击 15 | */ 16 | public class WorkPageProcessor extends BasePageProcessor { 17 | @Override 18 | public void processPage(AccessibilityEvent event, MyAccessibilityService myAccessibilityService) { 19 | try { 20 | Thread.sleep(1000L); 21 | AccessibilityNodeInfo findNodesByText = findNodesByText(myAccessibilityService, "打卡"); 22 | if (findNodesByText == null) { 23 | Path path = new Path(); 24 | float f = MyAccessibilityService.width / 2; 25 | path.moveTo(f, MyAccessibilityService.height * 0.8f); 26 | path.lineTo(f, MyAccessibilityService.height * 0.4f); 27 | AccessibilityHelper.dispatchGestrue(path, myAccessibilityService); 28 | return; 29 | } 30 | AccessibilityHelper.clickButtonByNode(myAccessibilityService, findNodesByText); 31 | } catch (Exception unused) { 32 | Log.i("Weixin", "工作台打卡失败"); 33 | } 34 | } 35 | 36 | @Override 37 | public boolean canParse(AccessibilityEvent event, MyAccessibilityService myAccessibilityService) { 38 | // 顶部的文字 39 | AccessibilityNodeInfo topTextView = AccessibilityHelper.getNodeById(myAccessibilityService, "com.tencent.wework:id/lkw", 0); 40 | if (topTextView != null) { 41 | return topTextView.getText().toString().contains("工作台"); 42 | } else { 43 | return false; 44 | } 45 | } 46 | } -------------------------------------------------------------------------------- /app/src/main/java/cn/martinkay/checkin/model/ActiveAppinfo.java: -------------------------------------------------------------------------------- 1 | package cn.martinkay.checkin.model; 2 | 3 | public class ActiveAppinfo { 4 | private String activityName; 5 | private String appName; 6 | private String moduleName; 7 | private String packageName; 8 | 9 | public String getModuleName() { 10 | return this.moduleName; 11 | } 12 | 13 | public void setModuleName(String moduleName) { 14 | this.moduleName = moduleName; 15 | } 16 | 17 | public String getPackageName() { 18 | return this.packageName; 19 | } 20 | 21 | public void setPackageName(String packageName) { 22 | this.packageName = packageName; 23 | } 24 | 25 | public String getActivityName() { 26 | return this.activityName; 27 | } 28 | 29 | public void setActivityName(String activityName) { 30 | this.activityName = activityName; 31 | } 32 | 33 | public String getAppName() { 34 | return this.appName; 35 | } 36 | 37 | public void setAppName(String appName) { 38 | this.appName = appName; 39 | } 40 | } -------------------------------------------------------------------------------- /app/src/main/java/cn/martinkay/checkin/model/CalendarScheme.java: -------------------------------------------------------------------------------- 1 | package cn.martinkay.checkin.model; 2 | 3 | import android.text.TextUtils; 4 | import com.alibaba.fastjson.annotation.JSONField; 5 | import com.haibin.calendarview.Calendar; 6 | 7 | public class CalendarScheme { 8 | 9 | public static final String AUTO_SIGN_COUNT_1 = "auto_sign_count_1"; 10 | public static final String AUTO_SIGN_COUNT_2 = "auto_sign_count_2"; 11 | public static final String AUTO_SIGN_COUNT_3 = "auto_sign_count_3"; 12 | public static final String AUTO_SIGN_COUNT_4 = "auto_sign_count_4"; 13 | public static final String AUTO_SIGN_DAY_ALLOW = "auto_sign_day_allow"; 14 | public static final String AUTO_SIGN_DAY_FORBIDDEN = "auto_sign_day_forbidden"; 15 | 16 | @JSONField(name = "date") public String date; 17 | @JSONField(name = "scheme") public String scheme; 18 | 19 | public void nextScheme() { 20 | if (TextUtils.isEmpty(scheme)) { 21 | scheme = AUTO_SIGN_COUNT_1; 22 | return; 23 | } 24 | switch (scheme) { 25 | case AUTO_SIGN_COUNT_1: 26 | scheme = AUTO_SIGN_COUNT_2; 27 | break; 28 | case AUTO_SIGN_COUNT_2: 29 | scheme = AUTO_SIGN_COUNT_3; 30 | break; 31 | case AUTO_SIGN_COUNT_3: 32 | case AUTO_SIGN_COUNT_4: 33 | scheme = AUTO_SIGN_COUNT_4; 34 | break; 35 | default: 36 | scheme = AUTO_SIGN_COUNT_1; 37 | } 38 | } 39 | 40 | /** 41 | * 是否是未来的打卡任务,包括今天 42 | */ 43 | public boolean isFutureTask() { 44 | java.util.Calendar calendarInstance = java.util.Calendar.getInstance(); 45 | Calendar currentCalendar = new Calendar(); 46 | currentCalendar.setYear(calendarInstance.get(java.util.Calendar.YEAR)); 47 | currentCalendar.setMonth(calendarInstance.get(java.util.Calendar.MONTH) + 1); 48 | currentCalendar.setDay(calendarInstance.get(java.util.Calendar.DAY_OF_MONTH)); 49 | return date.compareTo(currentCalendar.toString()) >= 0; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /app/src/main/java/cn/martinkay/checkin/model/WeixinInfo.java: -------------------------------------------------------------------------------- 1 | package cn.martinkay.checkin.model; 2 | 3 | 4 | import cn.martinkay.checkin.constant.Constant; 5 | 6 | public class WeixinInfo extends ActiveAppinfo { 7 | public static final String name = "weixin"; 8 | public static final String version = "v1.0"; 9 | 10 | public WeixinInfo() { 11 | setAppName(name); 12 | setModuleName(name); 13 | setPackageName(Constant.package_name_weixin); 14 | setActivityName(Constant.className_weixin); 15 | } 16 | } -------------------------------------------------------------------------------- /app/src/main/java/cn/martinkay/checkin/os/ActivityExt.kt: -------------------------------------------------------------------------------- 1 | package cn.martinkay.checkin.os 2 | 3 | import android.view.LayoutInflater 4 | import android.view.ViewGroup 5 | import androidx.appcompat.app.AppCompatActivity 6 | import androidx.viewbinding.ViewBinding 7 | 8 | inline fun AppCompatActivity.viewBinding( 9 | crossinline bindingInflater: (LayoutInflater) -> T 10 | ) = lazy(LazyThreadSafetyMode.NONE) { 11 | bindingInflater.invoke(layoutInflater) 12 | } 13 | 14 | inline fun AppCompatActivity.viewBindingRes( 15 | crossinline bindingView: (view: android.view.View) -> T 16 | ) = lazy(LazyThreadSafetyMode.NONE) { 17 | val rootView = this.findViewById(android.R.id.content).getChildAt(0) 18 | ?: throw IllegalArgumentException("viewBindingRes 需要用 Activity(@LayoutRes int contentLayoutId) 构造方法初始化") 19 | bindingView.invoke(rootView) 20 | } -------------------------------------------------------------------------------- /app/src/main/java/cn/martinkay/checkin/service/AssistService.java: -------------------------------------------------------------------------------- 1 | package cn.martinkay.checkin.service; 2 | 3 | import android.app.Service; 4 | import android.content.Intent; 5 | import android.os.Binder; 6 | import android.os.IBinder; 7 | 8 | public class AssistService extends Service { 9 | public AssistService() { 10 | } 11 | 12 | public class LocalBinder extends Binder { 13 | public AssistService getService() { 14 | return AssistService.this; 15 | } 16 | } 17 | 18 | @Override 19 | public IBinder onBind(Intent intent) { 20 | return new LocalBinder(); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /app/src/main/java/cn/martinkay/checkin/service/AutoCheckinService.java: -------------------------------------------------------------------------------- 1 | package cn.martinkay.checkin.service; 2 | 3 | import android.accessibilityservice.AccessibilityService; 4 | import android.content.BroadcastReceiver; 5 | import android.content.Context; 6 | import android.content.Intent; 7 | import android.content.IntentFilter; 8 | import android.util.Log; 9 | import android.view.accessibility.AccessibilityEvent; 10 | 11 | public class AutoCheckinService extends AccessibilityService { 12 | 13 | public static final String TAG = "AutoCheckinService"; 14 | public static final String PACKAGE_WECHAT_WORK = "com.tencent.wework"; 15 | 16 | @Override 17 | public void onCreate() { 18 | super.onCreate(); 19 | IntentFilter intentFilter = new IntentFilter(Intent.ACTION_TIME_TICK); 20 | registerReceiver(timeReceiver, intentFilter); 21 | Log.e(TAG, " onCreate()"); 22 | 23 | } 24 | 25 | private BroadcastReceiver timeReceiver = new BroadcastReceiver() { 26 | @Override 27 | public void onReceive(Context context, Intent intent) { 28 | if (intent != null) { 29 | checkToDoSignTask(); 30 | } 31 | } 32 | }; 33 | 34 | private void checkToDoSignTask() { 35 | 36 | } 37 | 38 | @Override 39 | public void onAccessibilityEvent(AccessibilityEvent event) { 40 | 41 | } 42 | 43 | @Override 44 | public void onInterrupt() { 45 | 46 | } 47 | 48 | 49 | } 50 | -------------------------------------------------------------------------------- /app/src/main/java/cn/martinkay/checkin/service/BackgroundAccess.java: -------------------------------------------------------------------------------- 1 | package cn.martinkay.checkin.service; 2 | 3 | 4 | import android.app.AppOpsManager; 5 | import android.content.Context; 6 | import android.os.Process; 7 | import android.util.Log; 8 | 9 | /* loaded from: classes.dex */ 10 | public class BackgroundAccess { 11 | public static boolean canBackgroundStart(Context context) { 12 | try { 13 | return ((AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE)).checkOp("android:system_alert_window", Process.myUid(), context.getPackageName()) == AppOpsManager.MODE_ALLOWED; 14 | } catch (Exception e) { 15 | Log.e("mes", "not support", e); 16 | return false; 17 | } 18 | } 19 | } -------------------------------------------------------------------------------- /app/src/main/java/cn/martinkay/checkin/service/WifiLockService.java: -------------------------------------------------------------------------------- 1 | package cn.martinkay.checkin.service; 2 | 3 | import android.app.Service; 4 | import android.content.Context; 5 | import android.content.Intent; 6 | import android.net.wifi.WifiManager; 7 | import android.os.IBinder; 8 | 9 | import androidx.annotation.Nullable; 10 | 11 | public class WifiLockService extends Service { 12 | 13 | private WifiManager.WifiLock wifiLock; 14 | 15 | @Override 16 | public int onStartCommand(Intent intent, int flags, int startId) { 17 | WifiManager wifiManager = (WifiManager) getApplicationContext().getSystemService(Context.WIFI_SERVICE); 18 | wifiLock = wifiManager.createWifiLock(WifiManager.WIFI_MODE_FULL_HIGH_PERF, "MyWifiLockTag"); 19 | wifiLock.acquire(); 20 | return START_STICKY; 21 | } 22 | 23 | @Override 24 | public void onDestroy() { 25 | super.onDestroy(); 26 | if (wifiLock != null && wifiLock.isHeld()) { 27 | wifiLock.release(); 28 | wifiLock = null; 29 | } 30 | } 31 | 32 | @Nullable 33 | @Override 34 | public IBinder onBind(Intent intent) { 35 | return null; 36 | } 37 | } -------------------------------------------------------------------------------- /app/src/main/java/cn/martinkay/checkin/util/AccessibilityHelper.java: -------------------------------------------------------------------------------- 1 | package cn.martinkay.checkin.util; 2 | 3 | import android.accessibilityservice.AccessibilityService; 4 | import android.accessibilityservice.GestureDescription; 5 | import android.graphics.Path; 6 | import android.graphics.Rect; 7 | import android.util.Log; 8 | import android.view.accessibility.AccessibilityNodeInfo; 9 | 10 | import java.util.List; 11 | import java.util.Random; 12 | 13 | import cn.martinkay.checkin.service.MyAccessibilityService; 14 | 15 | public class AccessibilityHelper { 16 | private static String TAG = "AccessibilityHelper"; 17 | private static Random random = new Random(); 18 | 19 | public static boolean clickButtonById(MyAccessibilityService myAccessibilityService, String id, int index) { 20 | AccessibilityNodeInfo nodeById = getNodeById(myAccessibilityService, id, 0); 21 | if (nodeById != null) { 22 | while (!nodeById.isClickable()) { 23 | nodeById = nodeById.getParent(); 24 | } 25 | nodeById.performAction(16); 26 | return true; 27 | } 28 | return false; 29 | } 30 | 31 | public static boolean clickButtonByText(MyAccessibilityService myAccessibilityService, String text, int index) { 32 | AccessibilityNodeInfo nodeByText = getNodeByText(myAccessibilityService, text, 0); 33 | if (nodeByText != null) { 34 | while (!nodeByText.isClickable()) { 35 | nodeByText = nodeByText.getParent(); 36 | } 37 | nodeByText.performAction(16); 38 | return true; 39 | } 40 | return false; 41 | } 42 | 43 | public static boolean clickButtonByNode(MyAccessibilityService myAccessibilityService, AccessibilityNodeInfo node) { 44 | if (node != null) { 45 | while (!node.isClickable()) { 46 | node = node.getParent(); 47 | } 48 | node.performAction(16); 49 | return true; 50 | } 51 | return false; 52 | } 53 | 54 | public static boolean touchViewByText(MyAccessibilityService myAccessibilityService, String text, int index) { 55 | AccessibilityNodeInfo nodeByText = getNodeByText(myAccessibilityService, text, index); 56 | if (nodeByText != null) { 57 | Rect rect = new Rect(); 58 | nodeByText.getBoundsInScreen(rect); 59 | Path path = new Path(); 60 | float nextInt = rect.left + random.nextInt(10); 61 | float nextInt2 = rect.top + random.nextInt(10); 62 | path.moveTo(nextInt, nextInt2); 63 | path.lineTo(nextInt, nextInt2); 64 | dispatchGestrue(path, myAccessibilityService); 65 | return true; 66 | } 67 | return false; 68 | } 69 | 70 | public static boolean touchViewByNode(MyAccessibilityService myAccessibilityService, AccessibilityNodeInfo nodeInfo) { 71 | if (nodeInfo != null) { 72 | Rect rect = new Rect(); 73 | nodeInfo.getBoundsInScreen(rect); 74 | Path path = new Path(); 75 | float nextInt = rect.left + random.nextInt(10); 76 | float nextInt2 = rect.top + random.nextInt(10); 77 | path.moveTo(nextInt, nextInt2); 78 | path.lineTo(nextInt, nextInt2); 79 | dispatchGestrue(path, myAccessibilityService); 80 | return true; 81 | } 82 | return false; 83 | } 84 | 85 | public static AccessibilityNodeInfo getNodeById(MyAccessibilityService myAccessibilityService, String id, int index) { 86 | List findAccessibilityNodeInfosByViewId; 87 | AccessibilityNodeInfo rootInActiveWindow = myAccessibilityService.getRootInActiveWindow(); 88 | if (rootInActiveWindow == null || (findAccessibilityNodeInfosByViewId = rootInActiveWindow.findAccessibilityNodeInfosByViewId(id)) == null || findAccessibilityNodeInfosByViewId.size() <= 0) { 89 | return null; 90 | } 91 | return index > findAccessibilityNodeInfosByViewId.size() ? findAccessibilityNodeInfosByViewId.get(findAccessibilityNodeInfosByViewId.size()) : findAccessibilityNodeInfosByViewId.get(index); 92 | } 93 | 94 | public static AccessibilityNodeInfo getNodeByText(MyAccessibilityService myAccessibilityService, String text, int index) { 95 | List findAccessibilityNodeInfosByText; 96 | AccessibilityNodeInfo rootInActiveWindow = myAccessibilityService.getRootInActiveWindow(); 97 | if (rootInActiveWindow == null || (findAccessibilityNodeInfosByText = rootInActiveWindow.findAccessibilityNodeInfosByText(text)) == null || findAccessibilityNodeInfosByText.size() <= 0) { 98 | return null; 99 | } 100 | return index > findAccessibilityNodeInfosByText.size() ? findAccessibilityNodeInfosByText.get(findAccessibilityNodeInfosByText.size()) : findAccessibilityNodeInfosByText.get(index); 101 | } 102 | 103 | public static AccessibilityNodeInfo findSpecialView(AccessibilityNodeInfo nodeInfo, String text) { 104 | List views = nodeInfo.findAccessibilityNodeInfosByText(text); 105 | if (views == null || views.isEmpty()) { 106 | views = nodeInfo.findAccessibilityNodeInfosByViewId(text); 107 | } 108 | if (views != null && !views.isEmpty()) { 109 | return views.get(0); 110 | } 111 | return null; 112 | } 113 | 114 | public static void dispatchGestrue(Path path, MyAccessibilityService myAccessibilityService) { 115 | Log.d(TAG, "滑动:" + myAccessibilityService.dispatchGesture(new GestureDescription.Builder().addStroke(new GestureDescription.StrokeDescription(path, 20L, 300L)).build(), new AccessibilityService.GestureResultCallback() { // from class: com.ycy.accessibilityservicetest.Util.AccessibilityHelper.1 116 | @Override // android.accessibilityservice.AccessibilityService.GestureResultCallback 117 | public void onCompleted(GestureDescription gestureDescription) { 118 | super.onCompleted(gestureDescription); 119 | } 120 | 121 | @Override // android.accessibilityservice.AccessibilityService.GestureResultCallback 122 | public void onCancelled(GestureDescription gestureDescription) { 123 | super.onCancelled(gestureDescription); 124 | } 125 | }, null)); 126 | } 127 | 128 | public static void dispatchGestrueByRect(Rect rect, MyAccessibilityService myAccessibilityService) { 129 | Path path = new Path(); 130 | float nextInt = rect.left + random.nextInt(10); 131 | float nextInt2 = rect.top + random.nextInt(10); 132 | path.moveTo(nextInt, nextInt2); 133 | path.lineTo(nextInt, nextInt2); 134 | dispatchGestrue(path, myAccessibilityService); 135 | } 136 | 137 | public static void goBack(MyAccessibilityService myAccessibilityService) { 138 | myAccessibilityService.performGlobalAction(1); 139 | Log.i(TAG, "执行全局返回动作"); 140 | } 141 | 142 | public static void moveToUp(MyAccessibilityService myAccessibilityService) { 143 | Path path = new Path(); 144 | path.moveTo((MyAccessibilityService.width / 2) + random.nextInt((int) (MyAccessibilityService.width * 0.1d)), (MyAccessibilityService.height * 0.8f) + random.nextInt((int) (MyAccessibilityService.height * 0.1d))); 145 | path.lineTo((MyAccessibilityService.width / 2) + random.nextInt((int) (MyAccessibilityService.width * 0.1d)), (MyAccessibilityService.height * 0.2f) + random.nextInt((int) (MyAccessibilityService.height * 0.1d))); 146 | dispatchGestrue(path, myAccessibilityService); 147 | } 148 | } -------------------------------------------------------------------------------- /app/src/main/java/cn/martinkay/checkin/util/AndroidRootUtils.java: -------------------------------------------------------------------------------- 1 | package cn.martinkay.checkin.util; 2 | 3 | import android.util.Log; 4 | 5 | import java.io.DataInputStream; 6 | import java.io.DataOutputStream; 7 | import java.io.IOException; 8 | 9 | //AndroidRootUtils Root根权限工具类 10 | public class AndroidRootUtils { 11 | /** 12 | * 13 | * 判断机器Android是否已经root,即是否获取root权限 14 | */ 15 | public static boolean checkDeviceRoot() { 16 | boolean resualt = false; 17 | int ret = execRootCmdSilent("echo test"); // 通过执行测试命令来检测 18 | if (ret != -1) { 19 | Log.i("checkDeviceRoot", "this Device have root!"); 20 | resualt = true; 21 | } else { 22 | resualt = false; 23 | Log.i("checkDeviceRoot", "this Device not root!"); 24 | } 25 | return resualt; 26 | } 27 | 28 | /** 29 | * 执行命令并且输出结果 30 | */ 31 | public static String execRootCmd(String cmd) { 32 | String result = ""; 33 | DataOutputStream dos = null; 34 | DataInputStream dis = null; 35 | try { 36 | Process p = Runtime.getRuntime().exec("su");// 经过Root处理的android系统即有su命令 37 | dos = new DataOutputStream(p.getOutputStream()); 38 | dis = new DataInputStream(p.getInputStream()); 39 | dos.writeBytes(cmd + "\n"); 40 | dos.flush(); 41 | dos.writeBytes("exit\n"); 42 | dos.flush(); 43 | String line = null; 44 | while ((line = dis.readLine()) != null) { 45 | result += line; 46 | } 47 | p.waitFor(); 48 | } catch (Exception e) { 49 | e.printStackTrace(); 50 | } finally { 51 | if (dos != null) { 52 | try { 53 | dos.close(); 54 | } catch (IOException e) { 55 | e.printStackTrace(); 56 | } 57 | } 58 | if (dis != null) { 59 | try { 60 | dis.close(); 61 | } catch (IOException e) { 62 | e.printStackTrace(); 63 | } 64 | } 65 | } 66 | return result; 67 | } 68 | 69 | /** 70 | * 执行命令但不关注结果输出 71 | */ 72 | public static int execRootCmdSilent(String cmd) { 73 | int result = -1; 74 | DataOutputStream dos = null; 75 | try { 76 | Process p = Runtime.getRuntime().exec("su"); 77 | dos = new DataOutputStream(p.getOutputStream()); 78 | dos.writeBytes(cmd + "\n"); 79 | dos.flush(); 80 | dos.writeBytes("exit\n"); 81 | dos.flush(); 82 | p.waitFor(); 83 | result = p.exitValue(); 84 | } catch (Exception e) { 85 | e.printStackTrace(); 86 | } finally { 87 | if (dos != null) { 88 | try { 89 | dos.close(); 90 | } catch (IOException e) { 91 | e.printStackTrace(); 92 | } 93 | } 94 | } 95 | return result; 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /app/src/main/java/cn/martinkay/checkin/util/DateUtil.java: -------------------------------------------------------------------------------- 1 | package cn.martinkay.checkin.util; 2 | 3 | 4 | import java.text.DateFormat; 5 | import java.text.SimpleDateFormat; 6 | import java.util.Date; 7 | 8 | /* loaded from: classes.dex */ 9 | public class DateUtil { 10 | static DateFormat dateFormatter = new SimpleDateFormat("yyyy-MM-dd"); 11 | static DateFormat datetimeFormatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); 12 | 13 | public static String getDateTimeString(Date date) { 14 | return datetimeFormatter.format(date); 15 | } 16 | 17 | public static String getDateString(Date date) { 18 | return dateFormatter.format(date); 19 | } 20 | 21 | public static String getCurrentDateTimeString() { 22 | return datetimeFormatter.format(new Date()); 23 | } 24 | 25 | public static String getCurrentDateString() { 26 | return dateFormatter.format(new Date()); 27 | } 28 | 29 | public static int isTimeout(String deadLine) { 30 | try { 31 | return new Date().compareTo(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse(deadLine)); 32 | } catch (Exception unused) { 33 | return 1; 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /app/src/main/java/cn/martinkay/checkin/util/ShellUtils.java: -------------------------------------------------------------------------------- 1 | package cn.martinkay.checkin.util; 2 | 3 | import android.util.Log; 4 | 5 | import java.io.BufferedReader; 6 | import java.io.DataOutputStream; 7 | import java.io.IOException; 8 | import java.io.InputStreamReader; 9 | import java.util.List; 10 | 11 | public class ShellUtils { 12 | 13 | public static final String COMMAND_SU = "su"; 14 | public static final String COMMAND_SH = "sh"; 15 | public static final String COMMAND_EXIT = "exit\n"; 16 | public static final String COMMAND_LINE_END = "\n"; 17 | public static String TAG = "blackspider_xposed"; 18 | 19 | private ShellUtils() { 20 | throw new AssertionError(); 21 | } 22 | 23 | /** 24 | * check whether has root permission 25 | * 26 | * @return 27 | */ 28 | public static boolean checkRootPermission() { 29 | return execCommand("echo root", true, false).result == 0; 30 | } 31 | 32 | /** 33 | * execute shell command, default return result msg 34 | * 35 | * @param command command 36 | * @param isRoot whether need to run with root 37 | * @return 38 | * @see ShellUtils#execCommand(String[], boolean, boolean) 39 | */ 40 | public static CommandResult execCommand(String command, boolean isRoot) { 41 | return execCommand(new String[] {command}, isRoot, true); 42 | } 43 | 44 | /** 45 | * execute shell commands, default return result msg 46 | * 47 | * @param commands command list 48 | * @param isRoot whether need to run with root 49 | * @return 50 | * @see ShellUtils#execCommand(String[], boolean, boolean) 51 | */ 52 | public static CommandResult execCommand(List commands, boolean isRoot) { 53 | return execCommand(commands == null ? null : commands.toArray(new String[] {}), isRoot, true); 54 | } 55 | 56 | /** 57 | * execute shell commands, default return result msg 58 | * 59 | * @param commands command array 60 | * @param isRoot whether need to run with root 61 | * @return 62 | * @see ShellUtils#execCommand(String[], boolean, boolean) 63 | */ 64 | public static CommandResult execCommand(String[] commands, boolean isRoot) { 65 | return execCommand(commands, isRoot, true); 66 | } 67 | 68 | /** 69 | * execute shell command 70 | * 71 | * @param command command 72 | * @param isRoot whether need to run with root 73 | * @param isNeedResultMsg whether need result msg 74 | * @return 75 | * @see ShellUtils#execCommand(String[], boolean, boolean) 76 | */ 77 | public static CommandResult execCommand(String command, boolean isRoot, boolean isNeedResultMsg) { 78 | return execCommand(new String[] {command}, isRoot, isNeedResultMsg); 79 | } 80 | 81 | /** 82 | * execute shell commands 83 | * 84 | * @param commands command list 85 | * @param isRoot whether need to run with root 86 | * @param isNeedResultMsg whether need result msg 87 | * @return 88 | * @see ShellUtils#execCommand(String[], boolean, boolean) 89 | */ 90 | public static CommandResult execCommand(List commands, boolean isRoot, boolean isNeedResultMsg) { 91 | return execCommand(commands == null ? null : commands.toArray(new String[] {}), isRoot, isNeedResultMsg); 92 | } 93 | 94 | /** 95 | * execute shell commands 96 | * 97 | * @param commands command array 98 | * @param isRoot whether need to run with root 99 | * @param isNeedResultMsg whether need result msg 100 | * @return
    101 | *
  • if isNeedResultMsg is false, {@link CommandResult#successMsg} is null and 102 | * {@link CommandResult#errorMsg} is null.
  • 103 | *
  • if {@link CommandResult#result} is -1, there maybe some excepiton.
  • 104 | *
105 | */ 106 | public static CommandResult execCommand(String[] commands, boolean isRoot, boolean isNeedResultMsg) { 107 | int result = -1; 108 | if (commands == null || commands.length == 0) { 109 | return new CommandResult(result, null, null); 110 | } 111 | 112 | Process process = null; 113 | BufferedReader successResult = null; 114 | BufferedReader errorResult = null; 115 | StringBuilder successMsg = null; 116 | StringBuilder errorMsg = null; 117 | 118 | DataOutputStream os = null; 119 | try { 120 | process = Runtime.getRuntime().exec(isRoot ? COMMAND_SU : COMMAND_SH); 121 | os = new DataOutputStream(process.getOutputStream()); 122 | for (String command : commands) { 123 | if (command == null) { 124 | continue; 125 | } 126 | Log.d(TAG,"执行命令 : " + command); 127 | // donnot use os.writeBytes(commmand), avoid chinese charset error 128 | os.write(command.getBytes()); 129 | os.writeBytes(COMMAND_LINE_END); 130 | os.flush(); 131 | } 132 | os.writeBytes(COMMAND_EXIT); 133 | os.flush(); 134 | 135 | result = process.waitFor(); 136 | // get command result 137 | if (isNeedResultMsg) { 138 | successMsg = new StringBuilder(); 139 | errorMsg = new StringBuilder(); 140 | successResult = new BufferedReader(new InputStreamReader(process.getInputStream())); 141 | errorResult = new BufferedReader(new InputStreamReader(process.getErrorStream())); 142 | String s; 143 | while ((s = successResult.readLine()) != null) { 144 | successMsg.append(s); 145 | } 146 | while ((s = errorResult.readLine()) != null) { 147 | errorMsg.append(s); 148 | } 149 | } 150 | } catch (IOException e) { 151 | e.printStackTrace(); 152 | } catch (Exception e) { 153 | e.printStackTrace(); 154 | } finally { 155 | try { 156 | if (os != null) { 157 | os.close(); 158 | } 159 | if (successResult != null) { 160 | successResult.close(); 161 | } 162 | if (errorResult != null) { 163 | errorResult.close(); 164 | } 165 | } catch (IOException e) { 166 | e.printStackTrace(); 167 | } 168 | 169 | if (process != null) { 170 | process.destroy(); 171 | } 172 | } 173 | return new CommandResult(result, successMsg == null ? null : successMsg.toString(), errorMsg == null ? null 174 | : errorMsg.toString()); 175 | } 176 | 177 | /** 178 | * result of command 179 | *
    180 | *
  • {@link CommandResult#result} means result of command, 0 means normal, else means error, same to excute in 181 | * linux shell
  • 182 | *
  • {@link CommandResult#successMsg} means success message of command result
  • 183 | *
  • {@link CommandResult#errorMsg} means error message of command result
  • 184 | *
185 | * 186 | * @author Trinea 2013-5-16 187 | */ 188 | public static class CommandResult { 189 | 190 | /** result of command **/ 191 | public int result; 192 | /** success message of command result **/ 193 | public String successMsg; 194 | /** error message of command result **/ 195 | public String errorMsg; 196 | 197 | public CommandResult(int result) { 198 | this.result = result; 199 | } 200 | 201 | public CommandResult(int result, String successMsg, String errorMsg) { 202 | this.result = result; 203 | this.successMsg = successMsg; 204 | this.errorMsg = errorMsg; 205 | } 206 | } 207 | } -------------------------------------------------------------------------------- /app/src/main/java/cn/martinkay/checkin/utils/AutoSignPermissionUtils.kt: -------------------------------------------------------------------------------- 1 | package cn.martinkay.checkin.utils 2 | 3 | import android.util.Log 4 | import cn.martinkay.checkin.ENABLE_SMART_RECOGNITION_JUMP 5 | import cn.martinkay.checkin.IS_OPEN_AFTERNOON_OFF_WORK_SIGN_TASK 6 | import cn.martinkay.checkin.IS_OPEN_AFTERNOON_START_WORK_SIGN_TASK 7 | import cn.martinkay.checkin.IS_OPEN_MORNING_OFF_WORK_SIGN_TASK 8 | import cn.martinkay.checkin.IS_OPEN_MORNING_START_WORK_SIGN_TASK 9 | import cn.martinkay.checkin.SIGN_CALENDAR_SCHEME_CACHE 10 | import cn.martinkay.checkin.SIGN_OPEN_INTENT_START_TIME 11 | import cn.martinkay.checkin.SharePrefHelper 12 | import cn.martinkay.checkin.model.CalendarScheme 13 | import com.alibaba.fastjson.JSON 14 | import kotlinx.coroutines.GlobalScope 15 | import kotlinx.coroutines.flow.MutableStateFlow 16 | import kotlinx.coroutines.launch 17 | import java.util.Calendar 18 | 19 | object AutoSignPermissionUtils { 20 | 21 | val notifyCalendarSchemeEvent: MutableStateFlow = MutableStateFlow(null) 22 | 23 | private val workingDays = listOf( 24 | Calendar.MONDAY, Calendar.TUESDAY, Calendar.WEDNESDAY, Calendar.THURSDAY, Calendar.FRIDAY 25 | ) 26 | 27 | fun isTodayAutoSignAllowed(): Boolean { 28 | val calendarSchemeMap = kotlin.runCatching { 29 | JSON.parseArray( 30 | SharePrefHelper.getString(SIGN_CALENDAR_SCHEME_CACHE, ""), 31 | CalendarScheme::class.java 32 | ).associateBy { it.date } 33 | }.onFailure { it.printStackTrace() }.getOrNull() 34 | val calendar = Calendar.getInstance() 35 | val workDay = workingDays.contains(calendar[Calendar.DAY_OF_WEEK]) 36 | if (calendarSchemeMap == null) { 37 | return workDay 38 | } 39 | val date = com.haibin.calendarview.Calendar().apply { 40 | year = calendar.get(Calendar.YEAR) 41 | month = calendar.get(Calendar.MONTH) + 1 42 | day = calendar.get(Calendar.DAY_OF_MONTH) 43 | }.toString() 44 | val value = calendarSchemeMap[date] ?: return workDay 45 | return value.scheme != CalendarScheme.AUTO_SIGN_DAY_FORBIDDEN 46 | } 47 | 48 | fun increaseTodayAutoSignCount() { 49 | val calendarSchemeMap = kotlin.runCatching { 50 | JSON.parseArray( 51 | SharePrefHelper.getString(SIGN_CALENDAR_SCHEME_CACHE, ""), 52 | CalendarScheme::class.java 53 | ).associateBy { it.date }.toMutableMap() 54 | }.onFailure { it.printStackTrace() }.getOrElse { mutableMapOf() } 55 | val calendar = Calendar.getInstance() 56 | val date = com.haibin.calendarview.Calendar().apply { 57 | year = calendar.get(Calendar.YEAR) 58 | month = calendar.get(Calendar.MONTH) + 1 59 | day = calendar.get(Calendar.DAY_OF_MONTH) 60 | }.toString() 61 | val value = calendarSchemeMap[date] ?: CalendarScheme().apply { 62 | this.date = date 63 | } 64 | value.nextScheme() 65 | calendarSchemeMap[date] = value 66 | SharePrefHelper.putString( 67 | SIGN_CALENDAR_SCHEME_CACHE, 68 | JSON.toJSONString(calendarSchemeMap.map { it.value }.toList()) 69 | ) 70 | GlobalScope.launch { notifyCalendarSchemeEvent.emit(Unit) } 71 | } 72 | 73 | fun isMobileAutoSignLaunch(): Boolean { 74 | // 判断是否开启智能识别跳转 如果关闭则直接返回true 75 | val enableSmartRecognitionJump = SharePrefHelper.getBoolean( 76 | ENABLE_SMART_RECOGNITION_JUMP, false 77 | ) 78 | if (!enableSmartRecognitionJump) { 79 | return true 80 | } 81 | val startTime = SharePrefHelper.getLong(SIGN_OPEN_INTENT_START_TIME, 0) 82 | if (System.currentTimeMillis() - startTime > 5000) { 83 | Log.i("CompleteProcessor", "不是由程序打开的,忽略") 84 | return false 85 | } 86 | return true 87 | } 88 | 89 | fun isEnableCurrentTimePeriod(requestCode: Int): Boolean { 90 | when (requestCode) { 91 | 0 -> { 92 | // 早上上班打卡 93 | val isMorningStartOpen = SharePrefHelper.getBoolean( 94 | IS_OPEN_MORNING_START_WORK_SIGN_TASK, false 95 | ) 96 | Log.i("ContentValues", "早上上班打卡状态:$isMorningStartOpen") 97 | return isMorningStartOpen 98 | } 99 | 100 | 1 -> { 101 | // 早上下班打卡 102 | val isMorningOffOpen = SharePrefHelper.getBoolean(IS_OPEN_MORNING_OFF_WORK_SIGN_TASK, false) 103 | Log.i("ContentValues", "早上下班打卡状态:$isMorningOffOpen") 104 | return isMorningOffOpen 105 | } 106 | 107 | 2 -> { 108 | // 下午上班打卡 109 | val isAfternoonStartOpen = SharePrefHelper.getBoolean( 110 | IS_OPEN_AFTERNOON_START_WORK_SIGN_TASK, false 111 | ) 112 | Log.i("ContentValues", "下午上班打卡状态:$isAfternoonStartOpen") 113 | return isAfternoonStartOpen 114 | } 115 | 116 | 3 -> { 117 | // 下午下班打卡 118 | val isAfternoonOffOpen = SharePrefHelper.getBoolean( 119 | IS_OPEN_AFTERNOON_OFF_WORK_SIGN_TASK, false 120 | ) 121 | Log.i("ContentValues", "下午下班打卡状态:$isAfternoonOffOpen") 122 | return isAfternoonOffOpen 123 | } 124 | 125 | else -> { 126 | return false 127 | } 128 | } 129 | } 130 | 131 | } -------------------------------------------------------------------------------- /app/src/main/java/cn/martinkay/checkin/utils/DialogUtils.kt: -------------------------------------------------------------------------------- 1 | package cn.martinkay.autocheckin.utils 2 | 3 | import android.content.Context 4 | import android.content.DialogInterface.OnClickListener 5 | import android.view.LayoutInflater 6 | import android.view.View 7 | import cn.martinkay.autocheckinplugin.R 8 | import cn.martinkay.autocheckinplugin.databinding.LayoutDialogProgressBinding 9 | import com.google.android.material.dialog.MaterialAlertDialogBuilder 10 | import org.jetbrains.annotations.Contract 11 | 12 | object DialogUtils { 13 | 14 | @JvmStatic 15 | @JvmOverloads 16 | fun newProgressDialog( 17 | context: Context, 18 | title: String, 19 | message: String? = null, 20 | cancelable: Boolean = false, 21 | onCancelClick: OnClickListener? = null 22 | ): MaterialAlertDialogBuilder { 23 | val binding = LayoutDialogProgressBinding.inflate(LayoutInflater.from(context)) 24 | val builder = newMaterialDialogBuilder(context) 25 | builder.setTitle(title) 26 | builder.setView(binding.root) 27 | builder.setCancelable(cancelable) 28 | 29 | if (message != null) { 30 | binding.message.text = message 31 | binding.message.visibility = View.VISIBLE 32 | } 33 | 34 | if (onCancelClick != null) { 35 | builder.setPositiveButton(android.R.string.cancel) { dialog, which -> 36 | dialog.dismiss() 37 | onCancelClick.onClick(dialog, which) 38 | } 39 | } 40 | 41 | return builder 42 | } 43 | 44 | /** 45 | * Create a new alert dialog with two buttons: Yes and No. This method 46 | * simply calls [.newYesNoDialog] with default values for title and message. 47 | * 48 | * @param context The context for the dialog. 49 | * @param positiveClickListener A listener that will be invoked on the Yes button 50 | * click. 51 | * @param negativeClickListener A listener that will be invoked on the No button 52 | * click. 53 | * @return The newly created dialog. 54 | */ 55 | @JvmStatic 56 | @JvmOverloads 57 | fun newYesNoDialog( 58 | context: Context, 59 | positiveClickListener: OnClickListener? = null, 60 | negativeClickListener: OnClickListener? = null 61 | ): MaterialAlertDialogBuilder { 62 | return newYesNoDialog( 63 | context, 64 | context.getString(R.string.msg_yesno_def_title), 65 | context.getString(R.string.msg_yesno_def_message), 66 | positiveClickListener, 67 | negativeClickListener 68 | ) 69 | } 70 | 71 | /** 72 | * Create a new alert dialog with two buttons: Yes and No. 73 | * 74 | * @param context The context for the dialog. 75 | * @param title The title of the dialog. 76 | * @param message The message of the dialog. 77 | * @param positiveClickListener A listener that will be invoked on the Yes button 78 | * click. 79 | * @param negativeClickListener A listener that will be invoked on the No button 80 | * click. 81 | * @return The newly created dialog instance. 82 | */ 83 | @JvmStatic 84 | @JvmOverloads 85 | fun newYesNoDialog( 86 | context: Context, 87 | title: String, 88 | message: String? = null, 89 | positiveClickListener: OnClickListener? = null, 90 | negativeClickListener: OnClickListener? = null 91 | ): MaterialAlertDialogBuilder { 92 | val builder = newMaterialDialogBuilder(context) 93 | builder.setTitle(title) 94 | builder.setMessage(message) 95 | builder.setPositiveButton(R.string.yes, positiveClickListener) 96 | builder.setNegativeButton(R.string.no, negativeClickListener) 97 | return builder 98 | } 99 | 100 | /** 101 | * Creates a new MaterialAlertDialogBuilder with the app's default style. 102 | * 103 | * @param context The context for the dialog builder. 104 | * @return The new MaterialAlertDialogBuilder instance. 105 | */ 106 | @JvmStatic 107 | @Contract("_ -> new") 108 | fun newMaterialDialogBuilder(context: Context): MaterialAlertDialogBuilder { 109 | return MaterialAlertDialogBuilder(context, R.style.AppTheme_MaterialAlertDialog) 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /app/src/main/java/cn/martinkay/checkin/utils/HLog.kt: -------------------------------------------------------------------------------- 1 | package cn.martinkay.checkin.utils 2 | 3 | import android.util.Log 4 | 5 | object HLog { 6 | private const val TAG = "Hail" 7 | fun i(tag: String, string: String) = Log.i(tag, string) 8 | fun e(t: Throwable) = Log.e(TAG, t.stackTraceToString()) 9 | } -------------------------------------------------------------------------------- /app/src/main/java/cn/martinkay/checkin/utils/HPackages.kt: -------------------------------------------------------------------------------- 1 | package cn.martinkay.checkin.utils 2 | 3 | object HPackages { 4 | val myUserId get() = android.os.Process.myUserHandle().hashCode() 5 | } -------------------------------------------------------------------------------- /app/src/main/java/cn/martinkay/checkin/utils/HShizuku.kt: -------------------------------------------------------------------------------- 1 | package cn.martinkay.checkin.utils 2 | 3 | import android.content.Context 4 | import android.content.pm.PackageManager 5 | import android.os.Build 6 | import android.os.IBinder 7 | import android.os.ParcelFileDescriptor 8 | import android.os.SystemClock 9 | import android.view.InputEvent 10 | import android.view.KeyEvent 11 | import androidx.annotation.RequiresApi 12 | import androidx.core.content.ContextCompat 13 | import cn.martinkay.checkin.utils.HPackages.myUserId 14 | import moe.shizuku.server.IShizukuService 15 | import org.lsposed.hiddenapibypass.HiddenApiBypass 16 | import rikka.shizuku.Shizuku 17 | import rikka.shizuku.ShizukuBinderWrapper 18 | import rikka.shizuku.SystemServiceHelper 19 | 20 | object HShizuku { 21 | private val isRoot get() = Shizuku.getUid() == 0 22 | private val userId get() = if (isRoot) myUserId else 0 23 | 24 | @RequiresApi(Build.VERSION_CODES.P) 25 | private fun asInterface(className: String, serviceName: String): Any = ShizukuBinderWrapper( 26 | SystemServiceHelper.getSystemService(serviceName) 27 | ).let { 28 | Class.forName("$className\$Stub").run { 29 | if (HTarget.P) HiddenApiBypass.invoke(this, null, "asInterface", it) 30 | else getMethod("asInterface", IBinder::class.java).invoke(null, it) 31 | } 32 | } 33 | 34 | fun isEnable(ctx: Context): Boolean { 35 | //本函数用于检查shizuku状态,shizukuIsRun代表shizuk是否运行,shizukuIsAccept代表shizuku是否授权 36 | var shizukuIsRun = true 37 | var shizukuIsAccept = false 38 | try { 39 | if (Shizuku.checkSelfPermission() != PackageManager.PERMISSION_GRANTED) Shizuku.requestPermission( 40 | 0 41 | ) else shizukuIsAccept = true 42 | } catch (e: Exception) { 43 | if (ContextCompat.checkSelfPermission( 44 | ctx, "moe.shizuku.manager.permission.API_V23" 45 | ) == PackageManager.PERMISSION_GRANTED 46 | ) shizukuIsAccept = true 47 | if (e.javaClass == IllegalStateException::class.java) { 48 | shizukuIsRun = false 49 | } 50 | } 51 | return shizukuIsRun && shizukuIsAccept 52 | } 53 | 54 | val lockScreen 55 | @RequiresApi(Build.VERSION_CODES.P) get() = runCatching { 56 | val input = asInterface("android.hardware.input.IInputManager", "input") 57 | val inject = input::class.java.getMethod( 58 | "injectInputEvent", InputEvent::class.java, Int::class.java 59 | ) 60 | val now = SystemClock.uptimeMillis() 61 | inject.invoke( 62 | input, KeyEvent(now, now, KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_POWER, 0), 0 63 | ) 64 | inject.invoke( 65 | input, KeyEvent(now, now, KeyEvent.ACTION_UP, KeyEvent.KEYCODE_POWER, 0), 0 66 | ) 67 | true 68 | }.getOrElse { 69 | HLog.e(it) 70 | false 71 | } 72 | 73 | @RequiresApi(Build.VERSION_CODES.P) 74 | fun forceStopApp(packageName: String) = runCatching { 75 | asInterface("android.app.IActivityManager", "activity").let { 76 | if (HTarget.P) HiddenApiBypass.invoke( 77 | it::class.java, it, "forceStopPackage", packageName, userId 78 | ) else it::class.java.getMethod( 79 | "forceStopPackage", String::class.java, Int::class.java 80 | ).invoke( 81 | it, packageName, userId 82 | ) 83 | } 84 | true 85 | }.getOrElse { 86 | HLog.e(it) 87 | false 88 | } 89 | 90 | @RequiresApi(Build.VERSION_CODES.P) 91 | fun clickHome() = runCatching { 92 | val input = asInterface("android.hardware.input.IInputManager", "input") 93 | val inject = input::class.java.getMethod( 94 | "injectInputEvent", InputEvent::class.java, Int::class.java 95 | ) 96 | val now = SystemClock.uptimeMillis() 97 | inject.invoke( 98 | input, KeyEvent(now, now, KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_HOME, 0), 0 99 | ) 100 | inject.invoke( 101 | input, KeyEvent(now, now, KeyEvent.ACTION_UP, KeyEvent.KEYCODE_HOME, 0), 0 102 | ) 103 | true 104 | }.getOrElse { 105 | HLog.e(it) 106 | false 107 | } 108 | 109 | 110 | 111 | fun execute(command: String, root: Boolean = isRoot): Pair = runCatching { 112 | IShizukuService.Stub.asInterface(Shizuku.getBinder()) 113 | .newProcess(arrayOf(if (root) "su" else "sh"), null, null).run { 114 | ParcelFileDescriptor.AutoCloseOutputStream(outputStream).use { 115 | it.write(command.toByteArray()) 116 | } 117 | waitFor() to inputStream.text.ifBlank { errorStream.text }.also { destroy() } 118 | } 119 | }.getOrElse { 0 to it.stackTraceToString() } 120 | 121 | private val ParcelFileDescriptor.text 122 | get() = ParcelFileDescriptor.AutoCloseInputStream(this) 123 | .use { it.bufferedReader().readText() } 124 | } -------------------------------------------------------------------------------- /app/src/main/java/cn/martinkay/checkin/utils/HTarget.kt: -------------------------------------------------------------------------------- 1 | package cn.martinkay.checkin.utils 2 | 3 | import android.os.Build 4 | 5 | object HTarget { 6 | private fun target(api: Int): Boolean = Build.VERSION.SDK_INT >= api 7 | 8 | val N = target(Build.VERSION_CODES.N) 9 | val O = target(Build.VERSION_CODES.O) 10 | val P = target(Build.VERSION_CODES.P) 11 | val Q = target(Build.VERSION_CODES.Q) 12 | val T = target(Build.VERSION_CODES.TIRAMISU) 13 | } -------------------------------------------------------------------------------- /app/src/main/java/cn/martinkay/checkin/utils/IsServiceRunningUtil.java: -------------------------------------------------------------------------------- 1 | package cn.martinkay.checkin.utils; 2 | 3 | import android.content.Context; 4 | import android.provider.Settings; 5 | import android.text.TextUtils; 6 | import android.util.Log; 7 | 8 | public class IsServiceRunningUtil { 9 | public static boolean isAccessibilitySettingsOn(Context context, String accessibilityServiceName) { 10 | int i; 11 | String str = context.getPackageName() + "/" + accessibilityServiceName; 12 | try { 13 | i = Settings.Secure.getInt(context.getContentResolver(), "accessibility_enabled", 0); 14 | } catch (Exception e) { 15 | Log.e("ContentValues", "get accessibility enable failed, the err:" + e.getMessage()); 16 | i = 0; 17 | } 18 | if (i == 1) { 19 | TextUtils.SimpleStringSplitter simpleStringSplitter = new TextUtils.SimpleStringSplitter(':'); 20 | String string = Settings.Secure.getString(context.getContentResolver(), "enabled_accessibility_services"); 21 | if (string != null) { 22 | simpleStringSplitter.setString(string); 23 | while (simpleStringSplitter.hasNext()) { 24 | if (simpleStringSplitter.next().equalsIgnoreCase(str)) { 25 | Log.v("ContentValues", "We've found the correct setting - accessibility is switched on!"); 26 | return true; 27 | } 28 | } 29 | } 30 | } else { 31 | Log.d("ContentValues", "Accessibility service disable"); 32 | } 33 | return false; 34 | } 35 | } -------------------------------------------------------------------------------- /app/src/main/java/cn/martinkay/checkin/utils/JumpPermissionManagement.java: -------------------------------------------------------------------------------- 1 | package cn.martinkay.checkin.utils; 2 | 3 | import android.app.Activity; 4 | import android.content.ComponentName; 5 | import android.content.Intent; 6 | import android.net.Uri; 7 | import android.os.Build; 8 | import android.util.Log; 9 | import cn.martinkay.autocheckinplugin.BuildConfig; 10 | 11 | import java.util.HashMap; 12 | import java.util.Map; 13 | 14 | public class JumpPermissionManagement { 15 | 16 | private static final String MANUFACTURER_HUAWEI = "Huawei"; 17 | private static final String MANUFACTURER_XIAOMI = "Xiaomi"; 18 | private static final String MANUFACTURER_LG = "LG"; 19 | private static final String MANUFACTURER_LETV = "Letv"; 20 | private static final String MANUFACTURER_OPPO = "OPPO"; 21 | private static final String MANUFACTURER_SONY = "Sony"; 22 | private static final String MANUFACTURER_MEIZU = "Meizu"; 23 | private static final String MANUFACTURER_SMARTISAN = "Smartisan"; 24 | 25 | 26 | private static final Map ROM_COMPONENTS = new HashMap<>(); 27 | 28 | static { 29 | ROM_COMPONENTS.put(MANUFACTURER_HUAWEI, new ComponentName("com.huawei.systemmanager", "com.huawei.permissionmanager.ui.MainActivity")); 30 | ROM_COMPONENTS.put(MANUFACTURER_LG, new ComponentName("com.android.settings", "com.android.settings.Settings$AccessLockSummaryActivity")); 31 | ROM_COMPONENTS.put(MANUFACTURER_LETV, new ComponentName("com.letv.android.letvsafe", "com.letv.android.letvsafe.PermissionAndApps")); 32 | ROM_COMPONENTS.put(MANUFACTURER_OPPO, new ComponentName("com.color.safecenter", "com.color.safecenter.permission.PermissionManagerActivity")); 33 | ROM_COMPONENTS.put(MANUFACTURER_SONY, new ComponentName("com.sonymobile.cta", "com.sonymobile.cta.SomcCTAMainActivity")); 34 | ROM_COMPONENTS.put(MANUFACTURER_SMARTISAN, new ComponentName("com.smartisanos.security", "com.smartisanos.security.MainActivity")); 35 | } 36 | 37 | public static void GoToSetting(Activity activity) { 38 | String manufacturer = Build.MANUFACTURER; 39 | ComponentName componentName = ROM_COMPONENTS.get(manufacturer); 40 | 41 | if (componentName != null) { 42 | Intent intent = new Intent(); 43 | intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 44 | intent.putExtra("packageName", BuildConfig.APPLICATION_ID); 45 | intent.setComponent(componentName); 46 | activity.startActivity(intent); 47 | } else if (MANUFACTURER_XIAOMI.equalsIgnoreCase(manufacturer)) { 48 | Xiaomi(activity); 49 | } else if (MANUFACTURER_MEIZU.equalsIgnoreCase(manufacturer)) { 50 | Meizu(activity); 51 | } else { 52 | ApplicationInfo(activity); 53 | Log.e("goToSetting", "目前暂不支持此系统"); 54 | } 55 | } 56 | 57 | public static void ApplicationInfo(Activity activity) { 58 | Intent intent = new Intent(); 59 | intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 60 | if (Build.VERSION.SDK_INT >= 9) { 61 | intent.setAction("android.settings.APPLICATION_DETAILS_SETTINGS"); 62 | intent.setData(Uri.fromParts("package", activity.getPackageName(), null)); 63 | } else if (Build.VERSION.SDK_INT <= 8) { 64 | intent.setAction("android.intent.action.VIEW"); 65 | intent.setClassName("com.android.settings", "com.android.settings.InstalledAppDetails"); 66 | intent.putExtra("com.android.settings.ApplicationPkgName", activity.getPackageName()); 67 | } 68 | activity.startActivity(intent); 69 | } 70 | 71 | public static void Xiaomi(Activity activity) { 72 | Intent intent = new Intent(); 73 | intent.setAction("miui.intent.action.APP_PERM_EDITOR"); 74 | intent.addCategory("android.intent.category.DEFAULT"); 75 | intent.putExtra("extra_pkgname", activity.getPackageName()); 76 | activity.startActivity(intent); 77 | } 78 | 79 | public static void Meizu(Activity activity) { 80 | Intent intent = new Intent("com.meizu.safe.security.SHOW_APPSEC"); 81 | intent.addCategory("android.intent.category.DEFAULT"); 82 | intent.putExtra("packageName", BuildConfig.APPLICATION_ID); 83 | activity.startActivity(intent); 84 | } 85 | 86 | public static void SystemConfig(Activity activity) { 87 | activity.startActivity(new Intent("android.settings.SETTINGS")); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /app/src/main/res/animator/dialog_in.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 9 | 13 | 14 | -------------------------------------------------------------------------------- /app/src/main/res/animator/dialog_out.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 9 | 13 | 14 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/bg_ripple.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/circle_drawable.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/circle_drawable_normal.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/circle_drawable_pressed.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_add.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 15 | 20 | 25 | 30 | 35 | 40 | 45 | 50 | 55 | 60 | 65 | 70 | 75 | 80 | 85 | 90 | 95 | 100 | 105 | 110 | 115 | 120 | 125 | 130 | 135 | 140 | 145 | 150 | 155 | 160 | 165 | 170 | 171 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | 15 | 18 | 21 | 22 | 23 | 24 | 30 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main_v2.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 13 | 14 | 21 | 22 | -------------------------------------------------------------------------------- /app/src/main/res/layout/add_rule_layout.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 13 | 14 | 18 | 19 | 23 | 24 |