吉利 / 领克 Flyme Auto(OneOS)车机的小工具,只用普通应用权限做两件事:
- 进入工程模式 —— 一键拉起
com.desaysv.engmode; - 把任意应用写进桌面「我的应用」 —— 通用实现,不写死包名。
外加只读的设备信息(VIN / 序列号 / 版本)和系统设置入口。
适用于用户自己的车机,用于安装自己开发/侧载的应用。本项目与吉利、领克、 德赛西威、魅族官方无关。
| 功能 | 说明 |
|---|---|
| ① 进入工程模式 | 多策略:显式组件 → getLaunchIntentForPackage → MAIN/LAUNCHER 解析,全过程打印结果 |
| ② 扫描工程模式页面 | 遍历 40+ 个已知 Activity,用 getActivityInfo() 读出 exported,能直接进的给按钮 |
| ③ 桌面应用列表管理 | 列出所有已安装应用,选中即写入桌面「我的应用」;可批量写入;可按 _id 单条删除。助手启动时会自动把自己写进去(已有记录则跳过) |
| ④ 设备信息 | Build + 车机系统属性(VIN、序列号、版本、ADB 开关…),可一键复制 |
| ⑤ 打开系统设置 | 快捷入口 |
| ⑥ 安装 APK | 扫描本机/U 盘 APK 列表,或从文件选择器挑;交给系统安装器完成(用户确认) |
| ⑦ 应用管理 | 卸载、应用详情、启用/禁用尝试、从桌面记录移除、复制 adb 命令 |
| ⑧ 添加桌面组件 | 给 6 个卡片槽位各选一个应用,桌面「添加卡片」里生成「领克助手 · 卡片N」,卡片只显示该应用图标、点击直接打开 |
写入后需要重启车机生效,应用会弹窗提示。车机可以用硬件按键强制重启 (不同车型组合不同,常见为长按电源键或音量键组合)。
对着本车机的 framework-res.apk 实测的权限级别:
| 权限 | protectionLevel | 结果 |
|---|---|---|
REQUEST_DELETE_PACKAGES |
0x00 normal |
✅ 可用 |
REQUEST_INSTALL_PACKAGES |
0x42 signature|appop |
|
INSTALL_PACKAGES(静默装) |
0x12 signature|privileged |
❌ 拿不到 |
DELETE_PACKAGES(静默卸) |
0x12 signature|privileged |
❌ 拿不到 |
CHANGE_COMPONENT_ENABLED_STATE(禁用) |
0x12 signature|privileged |
❌ 拿不到 |
所以:
- 安装:把 APK 复制到应用私有目录,用自带的最小
ContentProvider以content://交给系统安装器(Android 7+ 禁止file://跨进程,而 FileProvider 在 androidx 里,本项目不依赖 androidx)。没有「安装未知应用」开关时会先跳到系统设置。 静默安装做不到。 - 卸载:
ACTION_DELETE+package:,由系统弹出确认界面,不需要特权。 - 禁用:普通应用做不到。代码里保留了调用路径(万一以系统签名构建就能用),
被拒时会弹出说明并把可直接执行的 adb 命令复制到剪贴板:
adb shell pm disable-user --user 0 <包名>。 - 近似「隐藏」:功能③的「从桌面记录移除」可以把这个应用从桌面「我的应用」里拿掉 (只删车机商店记录,不卸载应用)。
工程模式宿主 com.desaysv.engmode 的 MainActivity 声明为
android:exported="true" 且带 MAIN/LAUNCHER,所以任何普通应用都能拉起:
startActivity(new Intent().setComponent(new ComponentName(
"com.desaysv.engmode", "com.desaysv.engmode.MainActivity"))
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));内部页面(版本信息、屏幕测试、煲机…)大多 exported=false,普通应用无法直达,
只能从工程模式菜单进入 —— 功能②会现场告诉你哪些可以。
车机桌面(com.android.launcher3)的「我的应用」是白名单驱动的:
显示 = 已安装 ∩ (assets 白名单 ∪ 商店 provider 记录 ∪ shortcut)
assets 白名单在系统分区只读,所以只能走可写的商店 provider。对着
OneOS-AppStore.apk 逆向后确认:
permission com.geely.appstore.APPSTORE_PROVIDER protectionLevel = normal ← 声明即授予
provider authorities="com.geely.appstore.AppstoreProvider" exported=true
table t_app_info(_id, package_name, apk_name, apk_icon, apk_size,
apk_version_name, apk_version_code, apk_type,
apk_type_name, display_screen)
insert() ContentValues 直接进 SQLiteDatabase.insert —— 不校验包名、除 _id 外全部可选
delete() /appInfo 走 delete(table, null, null) —— 无 _id 会清空整表
因此本工具不写死包名:权限是 normal 级,对所有包名一视同仁。实现细节:
apk_icon故意不写(TEXT 列),桌面回落到已安装应用的图标;- 分类名含「游戏」时桌面归为
itemType=3,其余itemType=2; - 删除只走
content://…/appInfo/<_id>,绝不触发清空整表; - 顺带只读查一下桌面的
com.android.launcher3.interactive/shortcutEntity做交叉校验。
这台车机的 adbd 会过滤命令里的 install 字样(吉利/领克防篡改),而且不支持流式安装,
所以 adb install 会失败(error: closed)。用仓库里的脚本,它内部已经拆分了关键字:
./install-apk.sh # 用 ./lynk-assistant.apk,默认 -r -t
./install-apk.sh -b # 先构建再安装
./install-apk.sh -g lynk-assistant.apk
./install-apk.sh -u <包名> # 卸载(同样拆分 un+install)
./install-apk.sh -s <序列号> ... # 多台设备时指定
./install-apk.sh --list # 列出设备
./install-apk.sh -h # 全部选项等价的手动两步(脚本就是这么做的):
adb push lynk-assistant.apk /data/local/tmp/
adb shell 'a=in; b=stall; pm ${a}${b} -r -t /data/local/tmp/lynk-assistant.apk'也可以把 APK 拷到 U 盘或 Download 目录,用应用里的 ⑥ 安装 APK 装(同样走系统安装器)。
装好后桌面「我的应用」默认看不到它(白名单驱动),但助手每次启动都会自动 把自己写进去(已有记录则跳过,结果打印在主界面日志里),重启车机后即可在桌面看到 「领克助手」。也可以在应用内用功能③手动增删其它应用。
不需要 Gradle、不需要联网,只用 Android SDK build-tools + 一个 JDK:
ANDROID_HOME=/path/to/Android/sdk ./build.sh可选环境变量:BT_VERSION(默认 36.0.0)、PLATFORM(默认 android-37.0)、
LYNK_KEYSTORE(默认 ~/.android/debug.keystore,不存在会自动用 keytool 生成)。
重新生成图标(需要 Pillow):
python3 make_icons.py目录结构:
AndroidManifest.xml
build.sh
install-apk.sh 车机安装/卸载脚本(拆分 install 关键字绕过 adbd 过滤)
make_icons.py
res/values/strings.xml
res/mipmap-*/ic_launcher.png
java/com/geely/lynkassistant/
MainActivity.java 入口 + 工程模式 + 页面扫描
AppListActivity.java 桌面应用列表管理(含重启提示)
AppStoreBridge.java 商店 provider 读写封装
DeviceInfoActivity.java 设备信息(只读)
ApkInstallActivity.java APK 扫描 / 选择 / 安装
ApkFileProvider.java 最小 ContentProvider(把 APK 交给系统安装器)
AppManagerActivity.java 卸载 / 禁用尝试 / 应用详情 / 桌面记录移除
WidgetCardActivity.java 添加桌面组件(6 个槽位选应用)
CardStore.java 槽位 → 目标应用
BaseCardWidget.java 桌面卡片基类(画图标 + 点击拉起)
CardWidget1..6.java 6 个卡片 provider,各占桌面「添加卡片」一项
Ui.java 深色 UI 组件(分栏 / 返回键 / 卡片)
给 6 个槽位各选一个应用,桌面长按 → 添加卡片 里就会出现
「领克助手 · 卡片 1..6」,摆上桌面后卡片只显示该应用的图标,点一下直接打开。
图标是运行时从 PackageManager 取的(getApplicationIcon),转成 Bitmap 后
用 RemoteViews.setImageViewBitmap 交给桌面渲染(控制在 256px 以内,避免
RemoteViews 的 1MB binder 限制)。
为什么它能出现在「添加卡片」列表:桌面(com.android.launcher3)的小组件过滤
BaseWidgetVm.isNeedWidget() 有一条兜底分支:
(displayType == 0) && packageName.startsWith("com.geely")
// 即:主屏组件只要包名以 com.geely 开头即通过实测对照(同一份代码,只改包名):
| 包名 | 结果 |
|---|---|
com.geely.lynkcard(普通类名) |
✅ 出现在「添加卡片」 |
io.github.machetehot.lynkassistant |
❌ 不出现 |
所以本应用的 applicationId 定为 com.geely.lynkassistant(Java 包名同名)。
这不是官方安装渠道,属于借用厂商命名空间取巧,固件更新/主题覆盖后可能失效。
- 写入 / 删除后需要重启车机才在桌面生效,普通应用没权限重启桌面;
- 应用商店同步/刷新可能清理
t_app_info记录,届时重新写入即可; - 安装不是静默的:必须由系统安装器弹窗确认,且首次要在系统设置里允许本应用 「安装未知应用」;静默安装需要系统签名;
- 禁用应用做不到:
CHANGE_COMPONENT_ENABLED_STATE是 signature|privileged, 只能给出 adb 命令自己执行; - 卸载系统应用会被系统拒绝(普通应用只能删用户应用的数据/卸载用户应用);
- 扫描 U 盘依赖该目录对本应用可读,部分车机
/mnt/media_rw/*仅 root 可读; - 工程模式由车机厂商提供,本工具只负责拉起;不同车型的工程模式包名可能不同;
- 不同车机固件的系统属性、桌面实现可能不同,功能③依赖 Geely/OneOS 的商店 provider。
仅供在你自己拥有或获授权的车机上使用,用于安装自己开发或合法获取的应用。 请自行确认车机保修条款与当地法规。作者不对任何误用或由此造成的损失负责。
MIT