拆开三星「模式和日常程序」:它凭什么不用 root

作者:

在

写 RoutineX 的时候,我一直在对照三星。同样一件事,三星的系统设置里点几下就能定时执行别人 App 的快捷方式,小米上我得写个应用、要 root、还得处理锁屏。

差别到底在哪,我决定把三星那两个包装下来拆开看。

分析的版本是 S25 Ultra 上的 com.samsung.android.app.routines 5.0.03.22 和 com.samsung.android.app.routineplus 1.1.28。下面是一路看下来的结论。

一、它是特权应用,这一点不意外

package:/system/priv-app/Routines/Routines.apk
sharedUserId = android.uid.systemui

priv-app 是系统镜像里的特权目录。更有意思的是 sharedUserId:它和 SystemUI 共用一个进程身份,也就是平台签名加上高权限 uid。

manifest 里申请的东西也印证了这一点:

权限 能干什么
WRITE_SECURE_SETTINGS 直接改亮度、屏幕超时、旋转这些系统设置
REAL_GET_TASKS 看到真实的最近任务,判断当前前台是哪个应用
START_ACTIVITIES_FROM_BACKGROUND 后台直接拉起界面,不受 Android 10 之后的后台启动限制
BLUETOOTH_SCAN / CHANGE_WIFI_STATE 无线相关的条件和动作

到这里还算常规。真正让我改主意的是下一步。

二、快捷方式能执行,靠的不是权限

我原本的猜测很简单:系统 uid 大概能绕过那个 signature 级权限检查。比亚迪快捷方式指向的 Activity 挂了个 com.byd.aeri.jump.permission,我们普通应用调 startActivity 会被直接拒掉,root 却能过。三星大概也是同理。

然后我查了这个权限的持有者:

Permission [com.byd.aeri.jump.permission]
    sourcePackage=com.byd.aeri.caranywhere
    prot=signature

底下一条 granted 记录都没有。整个系统里没有任何一个包持有它。

可桌面上长按比亚迪图标、点「每日签到」,是能用的。这说明快捷方式的启动根本不走普通 startActivity 那条权限检查路径。

Android 给快捷方式单独留了一条通道。应用注册快捷方式时,把自己的一个 Intent 交给系统保管;启动器通过 LauncherApps.startShortcut() 让系统去启动它。真正执行的时候,系统是以发布者自己的身份发起的,所以不会撞上发布者自己声明的签名权限。

这个设计说得通:快捷方式是应用主动暴露给启动器的入口,用户长按点它,本来就该能进去。

三星做的事就是调这条通道。它是特权应用,天然有启动器级别的访问权。我们不是桌面,拿不到这个权限,只能退回去用 root 发 Intent。

顺着这个结论,我马上想验证一件事:普通应用能不能也走这条通道。如果能,root 就只需要用来做一次性的枚举,日常执行可以完全不用。

写了个探针应用,把 getShortcuts() 和 startShortcut() 各试一遍。源码在 https://github.com/z7ui/RoutineX/tree/main/probe,想在自己机器上复现的可以直接编。

hasShortcutHostPermission=false
getShortcuts  失败: SecurityException: Caller can't access shortcut information
startShortcut 失败: SecurityException: Caller can't access shortcut information

两条路都被同一个异常挡住。这套接口只对启动器开放,第三方应用得先拿到桌面角色或者 ACCESS_SHORTCUTS 这种特权权限才行。root 省不掉,读取和执行都得要。

所以结论还是那个:三星能这么做,靠的是它站在系统里。我们站在外面,只能用 root 借钥匙。

三、怎么把功能目录挖出来

一开始我想逐个反编译界面,太慢。后来找到一条捷径。

先拿类表:

apkanalyzer dex packages Routines.apk | awk '$1=="C"{print $NF}'

四万六千个类,其中三星自己的有三千六百多个。翻的时候注意到一个包:

com/samsung/android/app/routines/preloadproviders/<能力域>/<子类>/actions|conditions/<实现类>

每个子目录对应一个能力域,目录名就是功能名。把这个包的类名全抠出来,按目录分组,完整清单就出来了,一共 241 个类。比看界面快得多,而且能看到界面上没暴露的东西。

中文文案也顺手导了一份,aapt2 dump resources 出来 2393 条 zh-CN 字符串,用来核对每个能力的实际叫法。

四、它到底能做什么

条件这边,覆盖得比我预想的宽:

类别 具体条件
时间 特定时间、时间段、日出日落(含「日出到日落」这种区间)、睡眠时间表
地点 到达或离开某个位置
网络 Wi-Fi 连上断开(可指定名字)、蓝牙连某设备、移动数据、热点、飞行模式、NFC
设备 充电中、电量低于某个百分比、有线耳机插入、折叠屏开合、省电模式
通知 收到指定应用的通知
应用 某个应用被打开
健康 健身中、今日步数、活动卡路里
通话短信 来电、收到短信、快速拒接
系统 免打扰、声音模式、深色模式、屏幕方向、DeX、车载 Android Auto
日历 公共假日、工作日
闹钟 叫醒闹钟响了

动作这边有三十多个分组、两百多个具体动作。挑几个值得说的:

  • 流程类有条件分支、延迟执行、二次确认弹窗、自定义弹窗
  • 显示类细到字体大小、屏幕分辨率与流畅度、缩放、外屏亮度
  • 声音类里有分应用音量
  • 功能类有打开网址、复制到剪贴板、TTS 朗读文本
  • 通知类能朗读通知内容
  • 还有获取剪贴板、显示图片、倒数日、调整系统时间

五、「日常程序+」才是真正有想法的部分

这个包没在系统镜像里,是 Galaxy Store 推下来的普通 App,18MB。但它拿到了一堆三星自家签名权限,做出来的是本体没有的东西。

能力 说明
触控宏 录制一串点击和滑动,触发时按顺序回放。可设重复次数、间隔、开始前延迟
按钮操作 侧键双击长按、音量键、导航键触发例程,会覆盖系统原本的按键行为
指纹触发 指定某根手指解锁时运行例程
隔空操作 S-Pen 悬空手势:画圈、上下左右、摇晃
方向键 用手腕动作模拟方向键
请求 REST API 例程可以直接发 HTTP 请求
二维码分享 例程导出成 JSON 压成二维码,别人扫码导入
重复规则扩展 每月某日、日期尾数(每月 3 号、13 号、23 号)
Just in Routine 用户自己做的卡片,显示在即时简报里

例程上限二十个。这个限制挺有意思,像是故意留的。

六、如果要在小米上做,哪几个值得抄

看完这一圈,我心里的排序是这样的。

条件分支排第一。这是整套系统里最有价值的抽象。现在 RoutineX 只能说「到点做一件事」,加上分支就能表达「连着家里的 Wi-Fi 就静音,否则正常」。存储结构要从一条任务改成任务树,越早改越好。

触控宏排第二。小米上没有同类东西,做成独立应用也有人用。有 root 可以用 input 注入,没 root 就用无障碍手势。

收到指定通知就触发排第三。一个 NotificationListenerService 就够了,不需要 root。典型用法是收到某个人的消息时自动做点事。

延迟执行加二次确认成本最低。延迟就是多排一个闹钟,二次确认就是一个弹窗,但能挡掉很多误触发。

再往后是分应用音量、日出日落触发、每月某日这类。都不是难点,属于有空就补。

至于指纹触发和 S-Pen 隔空,抄不了。Android 不给第三方应用区分具体哪根手指,小米也没有对应的硬件事件。应用配对分屏理论上能做,但各家 ROM 实现差异太大,覆盖率很难保证。

七、最后

拆完之后最大的感受是,三星这套东西的宽度是靠生态堆出来的。它有两百多个动作,是因为它能改系统设置、能读健康数据、能连智能家居、能调自家 AI。这些能力大多是系统本身提供的,它只是把入口接到了例程里。

第三方应用做同样的事,只能在有限的地方用力。所以 RoutineX 不打算对标它的全部,先把时间触发和快捷方式执行做扎实,再往上加两三个高价值的能力,就够了。

分析用的脚本和中间产物我留在本地了,包括类清单、中文资源表和能力目录。要折腾别的系统应用,这套方法能重复用。

评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注