Android 實(shí)現(xiàn)釘釘自動打卡

前言

  本文章旨在提供一種新的思路,在無需 Root 的情況下,實(shí)現(xiàn)自動化釘釘定時(shí)打卡,更多是為了自己方便而定制開發(fā),所以很多功能的實(shí)現(xiàn)局限性較大
   MIUI 用戶可以直接使用該 APP 實(shí)現(xiàn)自動考勤

1.需求分析

  公司考勤調(diào)整過后,需要使用釘釘一天打四次卡:08:30 前上班卡;12:00 后下班卡;13:30 前上班卡;18:00 后下班卡。防止中午午休時(shí)缺卡,現(xiàn)有如下需求:

到點(diǎn)自動啟動釘釘進(jìn)行打卡并息屏

  需求具體化,梳理上班、下班考勤流程( 釘釘默認(rèn) 上班打卡 啟用 極速打卡,而 下班打卡 則是 手動打卡

上下班打卡流程示意圖

涉及功能點(diǎn)

  • 亮屏
  • 息屏
  • 屏幕解鎖
  • 啟動釘釘
  • 模擬用戶操作
  • 保活

2.效果展示

到點(diǎn)喚醒屏幕并解鎖,啟動釘釘打卡后息屏,全程無手動操作

  圖中展示的下班打卡效果,上班打卡效果則更為簡單:喚醒屏幕并解鎖,啟動釘釘(觸發(fā)極速打卡)后息屏。
  點(diǎn)擊跳轉(zhuǎn)下載安裝包
安裝包下載二維碼

3.功能實(shí)現(xiàn)

3.1 亮屏

  當(dāng)觸發(fā)打卡功能時(shí),首先需要判斷屏幕是否處于亮屏狀態(tài),非亮屏狀態(tài)則喚醒屏幕
  通過 PowerManager 實(shí)現(xiàn),需要聲明權(quán)限:

    <!-- 允許應(yīng)用程序使用電源管理器的屏幕鎖功能 -->
    <uses-permission android:name="android.permission.WAKE_LOCK" />

  示例代碼:

    private fun wakeUpScreen(context: Context, tag: String) {
        val powerManager = context.getSystemService(Context.POWER_SERVICE) as PowerManager
        //是否需要亮屏喚醒屏幕
        if (!powerManager.isInteractive) {
            val pm = context.getSystemService(Context.POWER_SERVICE) as PowerManager
            val wl = pm.newWakeLock(
                PowerManager.ACQUIRE_CAUSES_WAKEUP or PowerManager.SCREEN_DIM_WAKE_LOCK,
                tag
            )
            wl.acquire(60 * 1000L /*1 minutes*/)
            wl.release()
        }
    }

3.2 息屏

  通過 DevicePolicyManager 實(shí)現(xiàn)
   1.自定義設(shè)備管理器啟用狀態(tài)監(jiān)聽器(點(diǎn)擊跳轉(zhuǎn)查看源碼)

class LockScreenDeviceAdminReceiver : DeviceAdminReceiver() {
    override fun onEnabled(context: Context, intent: Intent) {
        super.onEnabled(context, intent)
        //TODO 設(shè)備管理器已啟用
    }
 
    override fun onDisabled(context: Context, intent: Intent) {
        super.onDisabled(context, intent)
        //TODO 設(shè)備管理器已禁用
    }
}

  2.配置清單注冊監(jiān)聽器(點(diǎn)擊跳轉(zhuǎn)查看源碼)

        <!--設(shè)備管理器-->
        <receiver
            android:name=".service.LockScreenDeviceAdminReceiver"
            android:description="@string/app_name"
            android:label="@string/app_name"
            android:permission="android.permission.BIND_DEVICE_ADMIN">
            <meta-data
                android:name="android.app.device_admin"
                android:resource="@xml/lock_screen" />
            <intent-filter>
                <action android:name="android.app.action.DEVICE_ADMIN_ENABLED" />
            </intent-filter>
        </receiver>

  其中 lock_screen 資源文件為 申請?jiān)O(shè)備管理器的具體權(quán)限

<?xml version="1.0" encoding="UTF-8"?>
<!--該文件路徑 res/xml/lock_screen.xml -->
<device-admin xmlns:android="http://schemas.android.com/apk/res/android" >
    <uses-policies>
        <!-- 鎖定屏幕 -->
        <force-lock />
    </uses-policies>
</device-admin>

  3.判斷是否啟用設(shè)備管理器 / 啟用設(shè)備管理器

    /**
     * 判斷是否啟用設(shè)備管理器
     */
    fun isAdminActive(context: Context): Boolean {
        val devicePolicyManager =
            context.getSystemService(Context.DEVICE_POLICY_SERVICE) as DevicePolicyManager
        return devicePolicyManager.isAdminActive(ComponentName(context, LockScreenDeviceAdminReceiver::class.java))
    }
 
    /**
     * 申請啟用設(shè)備管理器
     */
    fun enableAdmin(context: Context){
        val intent = Intent(DevicePolicyManager.ACTION_ADD_DEVICE_ADMIN)
        intent.putExtra(
            DevicePolicyManager.EXTRA_DEVICE_ADMIN,
            ComponentName(context, LockScreenDeviceAdminReceiver::class.java)
        )
        intent.putExtra(DevicePolicyManager.EXTRA_ADD_EXPLANATION, "鎖屏")
        context.startActivity(intent)
    }

  4.使用設(shè)備管理器進(jìn)行鎖屏

        /**
         * 鎖屏
         */
        fun lockScreen(context: Context) {
            val devicePolicyManager =
                context.getSystemService(Context.DEVICE_POLICY_SERVICE) as DevicePolicyManager
            if (devicePolicyManager.isAdminActive(
                    ComponentName(
                        context,
                        LockScreenDeviceAdminReceiver::class.java
                    )
                )
            ) {
                devicePolicyManager.lockNow()
            }
        }

3.3 屏幕解鎖(通過模擬用戶操作實(shí)現(xiàn))

  使用 AccessibilityService 模擬用戶操作,實(shí)現(xiàn)解鎖功能
  基于筆者的手機(jī)系統(tǒng) MIUI 12 20.7.9 示例,解鎖流程為:喚醒屏幕 → 進(jìn)入負(fù)一屏 → 點(diǎn)擊“萬能遙控”按鈕 → 喚起解鎖密碼輸入頁面 → 輸入解鎖密碼 → 完成解鎖
  1.自定義 AccessibilityService (點(diǎn)擊跳轉(zhuǎn)查看源碼)

/**
 * 自定義 AccessibilityService 重寫 onAccessibilityEvent 方法
 * 根據(jù) AccessibilityEvent 的 eventType 監(jiān)聽系統(tǒng)變化動作:如通知變動、頁面變動、點(diǎn)擊、長按等
 * 再通過 AccessibilityService 來分析當(dāng)前頁面節(jié)點(diǎn),模擬用戶操作:滑動、點(diǎn)擊/長按指定按鈕
 */
class SelfAccessibilityService : AccessibilityService() {
 
    override fun onInterrupt() {
        TODO("Not yet implemented")
    }
 
    override fun onAccessibilityEvent(event: AccessibilityEvent?) {
        //只會監(jiān)聽在 accessibility-service 的 android:packageNames 中聲明的 packageName
        val packageName = event!!.packageName.toString()
        val eventType = event.eventType
        if (eventType == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) {
            //前臺頁面出現(xiàn)變動:回到桌面、啟動新的頁面等
            if ("com.android.systemui" == packageName) {
                //該頁面屬于系統(tǒng)頁面
            } else if ("com.alibaba.android.rimet" == packageName) {
                //該頁面屬于釘釘
                //當(dāng)前頁面節(jié)點(diǎn)信息
                val nodeInfo = this.rootInActiveWindow
                //目標(biāo) view id ,通過 uiautomatorviewer 工具分析布局可以知道第三方 APP 指定頁面的 view 的 id、text 及 description
                val aimsId = "com.alibaba.android.rimet:id/session_title"
                //查詢到結(jié)果節(jié)點(diǎn)集合
                val list = nodeInfo
                    .findAccessibilityNodeInfosByViewId(aimsId)
                for (n in list) {
                    val text = n.text
                    val description = n.contentDescription
                    //TODO 與目標(biāo) view 的 text、description 進(jìn)行匹配,匹配成功得到指定 view 節(jié)點(diǎn)
                    //TODO 匹配成功后,調(diào)用 AccessibilityNodeInfo 的 performAction(int action) 方法執(zhí)行相關(guān)操作:如點(diǎn)擊、長按
                }
            }
        }
    }
}

  2.配置清單注冊無障礙服務(wù)(點(diǎn)擊跳轉(zhuǎn)查看源碼)???????

        <!--無障礙服務(wù)-->
        <service
            android:name=".service.SelfAccessibilityService"
            android:label="@string/accessibility_service"
            android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE">
            <intent-filter>
                <action android:name="android.accessibilityservice.AccessibilityService" />
            </intent-filter>
            <meta-data
                android:name="android.accessibilityservice"
                android:resource="@xml/accessible_service_config" />
        </service>

  其中 accessible_service_config 資源文件為該 無障礙服務(wù)的具體配置

<?xml version="1.0" encoding="utf-8"?>
<!--該文件路徑 res/xml/accessible_service_config.xml -->
<accessibility-service xmlns:android="http://schemas.android.com/apk/res/android"
    android:accessibilityEventTypes="typeAllMask"
    android:accessibilityFeedbackType="feedbackGeneric"
    android:accessibilityFlags="flagReportViewIds|flagRetrieveInteractiveWindows"
    android:canPerformGestures="true"
    android:canRetrieveWindowContent="true"
    android:description="@string/accessibility_service_description"
    android:notificationTimeout="100"
    android:packageNames="com.android.systemui,com.alibaba.android.rimet" />

  3.跳轉(zhuǎn)無障礙服務(wù)頁面,讓用戶手動啟用無障礙服務(wù)

    /**
     * 跳轉(zhuǎn)系統(tǒng)無障礙服務(wù)設(shè)置頁面
     */
    fun openAccessibilityService(context: Context) {
        context.startActivity(Intent(android.provider.Settings.ACTION_ACCESSIBILITY_SETTINGS))
    }

  更多已下載的服務(wù) 中找到當(dāng)前 APP 提供的無障礙服務(wù)并啟用

啟用無障礙服務(wù)

  息屏、亮屏及解鎖功能測試與效果展示(解鎖流程的滑動點(diǎn)擊及密碼輸入動作皆借助 AccessibilityService 實(shí)現(xiàn))
息屏、亮屏與解鎖

3.4 啟動釘釘

        /**
         * 通過指定包名打開APP
         * 釘釘包名 com.alibaba.android.rimet
         * LaunchHomeActivity
         */
        fun openRimet(context: Context): Boolean {
            val packageManager = context.packageManager
            val pi: PackageInfo?
            val packageName = "com.alibaba.android.rimet"
            pi = try {
                packageManager.getPackageInfo(packageName, 0)
            } catch (e: PackageManager.NameNotFoundException) {
                //TODO 未安裝釘釘
                return false
            }
            val resolveIntent = Intent(Intent.ACTION_MAIN, null)
            resolveIntent.addCategory(Intent.CATEGORY_LAUNCHER)
            resolveIntent.setPackage(pi.packageName)
            val apps =
                packageManager.queryIntentActivities(resolveIntent, 0)
            val resolveInfo = apps.iterator().next()
            if (resolveInfo != null) {
                val className = resolveInfo.activityInfo.name
                val intent = Intent(Intent.ACTION_VIEW)
                intent.addCategory(Intent.CATEGORY_LAUNCHER)
                intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
                val cn = ComponentName(packageName, className)
                intent.component = cn
                context.startActivity(intent)
                return true
            } else {
                //TODO resolveInfo = null
                return false
            }
        }

3.5 保活

  在系統(tǒng)資源管理監(jiān)控越來越嚴(yán)格的現(xiàn)狀下,同時(shí)為了避免雜草叢生的無良 APP 影響用戶使用體驗(yàn),提出?;钸@個(gè)需求,其實(shí)是 不合理 、 不推薦不倡導(dǎo) 的。但確實(shí)有可以?;畹姆椒?,只是需要用戶手動授予更高的權(quán)限。
  此處基于筆者的手機(jī)系統(tǒng) Android 10 MIUI 12 20.7.9 示例實(shí)現(xiàn)?;睿?br>     1.授予 APP 自啟動權(quán)限
    2.啟用無障礙服務(wù)
  當(dāng) APP 具有自啟動權(quán)限后,其已啟用的無障礙服務(wù)就不會隨著 APP 被殺死而關(guān)閉(退出APP 或 最近任務(wù)頁面滑除APP),從而實(shí)現(xiàn)?;?。

殺死APP后,前臺服務(wù)依然被無障礙服務(wù)喚醒

4.注意事項(xiàng)

  • 目前該 APP 僅適配了 MIUI 系統(tǒng),僅支持 Android 7.0 或以上的 MIUI 系統(tǒng)
  • 目前支持的釘釘版本為 5.1.16
  • MIUI 系統(tǒng)下需要授予 APP 后臺彈出界面 權(quán)限(應(yīng)用詳情頁面手動授予)
  • 實(shí)現(xiàn)保活效果,需要授予 APP 自啟動 權(quán)限(應(yīng)用詳情頁面手動授予)
  • 默認(rèn)手機(jī)有設(shè)置 鎖屏密碼,且是純數(shù)字密碼
  • 手機(jī) 負(fù)一屏 模式需設(shè)置為 標(biāo)準(zhǔn)模式
  • 可能會因?yàn)槭謾C(jī)設(shè)置了 防誤觸,導(dǎo)致亮屏后無法解鎖(當(dāng)手機(jī)在口袋里的時(shí)候)
  • 釘釘內(nèi)需將 消息 頁面的 智能工作助理 置頂

5.開始使用

  1. 啟動 APP,進(jìn)入 設(shè)置 頁面
  2. 點(diǎn)擊第一項(xiàng)(設(shè)置“ 后臺彈出界面 ”權(quán)限)下的 跳轉(zhuǎn)設(shè)置 進(jìn)入應(yīng)用詳情頁面,分別授予 自啟動后臺彈出界面 權(quán)限后回到 APP
  3. 點(diǎn)擊第二項(xiàng)(獲得釘釘啟動權(quán)限)下的 啟動釘釘以獲取權(quán)限 ,允許啟動釘釘后回到 APP
  4. 啟用第三項(xiàng)(相關(guān)服務(wù)設(shè)置)下的 設(shè)備管理應(yīng)用無障礙服務(wù) 后回到 APP
  5. 點(diǎn)擊 設(shè)置/更新鎖屏密碼 按鈕,輸入當(dāng)前手機(jī)的 鎖屏密碼 并確定(僅保存本地)
  6. 添加考勤時(shí)間
  7. 回到 APP 首頁,點(diǎn)擊 測試 按鈕,來到功能測試頁面,逐一測試功能是否實(shí)現(xiàn)
  8. 測試無誤后,回到 APP 首頁,點(diǎn)擊 啟用 按鈕,啟用自動考勤服務(wù)

6.最后

  該 APP 為閑余時(shí)間做出來利于自己考勤用的,并未做過多的適配,目前僅適用于部分 MIUI 系統(tǒng)機(jī)型。如需適配別的手機(jī)系統(tǒng),可自行下載源碼調(diào)整。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
禁止轉(zhuǎn)載,如需轉(zhuǎn)載請通過簡信或評論聯(lián)系作者。

友情鏈接更多精彩內(nèi)容