固件修改 / DIY 方式
禁用 / 恢复系统app
无需 root
adb shell /system/bin/pm hide com.phicomm.speaker.productiontest
adb shell /system/bin/pm unhide com.phicomm.speaker.productiontest
无需 root。但已 root 设备可以在设备上安装小黑盒等程序实现图形界面上管理。
卸载系统app
无需 root
adb shell /system/bin/pm uninstall --user 0 com.phicomm.speaker.productiontest
想要恢复只能把设备恢复出厂设置(或者重新刷机初始化 user 分区)
彻底卸载系统 app (需要 root)
adb shell + su
busybox mount -o remount,rw /system
mkdir /system/app-disabled
mv /system/app/EchoService.apk /system/app-disabled/
reboot
安装 apk
无需 root
adb shell settings put secure install_non_market_apps 1
adb push EchoService.apk /mnt/internal_sd/
adb shell /system/bin/pm install -r /mnt/internal_sd/EchoService.apk
adb shell rm /mnt/internal_sd/EchoService.apk
adb reboot
自带斐讯系统 app
/system/app
/system/priv-app
系统 app 使用的 native library 位于 /system/lib
com.phicomm.speaker.launcher // 顶部转盘按钮音量调节功能。
com.phicomm.speaker.airskill
com.phicomm.speaker.player // EchoService, 控制氛围灯, dlna 播放等。
com.phicomm.speaker.exceptionreporter // 垃圾
com.phicomm.speaker.ijetty // /system/app/IJetty/IJetty.apk
com.phicomm.speaker.netctl // /system/app/NetControl/NetControl.apk
com.phicomm.speaker.otaservice // 垃圾
com.phicomm.speaker.systemtool // /system/app/SystemTool/SystemTool.apk
com.phicomm.speaker.device // /system/app/Unisound/Unisound.apk。核心App。开机音效;顶部中央按键处理:按三下顶部按钮开启蓝牙;长按配网模式;单击唤醒等;语音助手功能(基于云知声 VoiceUI SDK 开发 com.unisound.vui)
com.phicomm.speaker.productiontest // 垃圾
com.phicomm.speaker.bugreport // 垃圾
EchoService (com.phicomm.speaker.player)
每次说:“小讯小讯,打开蓝牙”后系统会进行一系列处理,首先是会语音识别命令,然后会关闭当前的第三方流服务,同时开启第三方流服务的等待监听功能(如果连接了wifi的话),dlna,airplay,bt开始进行监听,同时开启了一个延迟90秒的关闭命令,如果没有设备链接的话,就关闭蓝牙等第三方服务,进入休眠。等待下一次的唤醒。
对应的app是EchoService.app
斐讯使用的dlna是基于libplatinum的,airplay是不支持视频投放的airtunes版(基于libshareport)
固件开发 / Mod
控制氛围灯
EchoService 里的代码:
package com.phicomm.speaker.player.light;
public class LedLight {
private static int mColor = 0;
public static void setColor(long paramLong, int paramInt) {
if (mColor != paramInt) {
set_color(paramLong, paramInt);
mColor = paramInt;
}
}
public static native void set_color(long paramLong, int paramInt);
static {
System.loadLibrary("ledLight-jni");
}
}
LedLight.setColor(32767L, 0xFFFFFF & Color.HSBtoColor(PlayerVisualizer.this.mHue, 1.0F, f1));
LedLight.setColor(32767L, 0);
native method "set_color":
第1个参数斐讯 app 代码里传入的是固定值32767L (0x7fff),含义不明。
第2个参数为6个byte的 int 类型 RGB 颜色,0 (黑色?) 为关闭氛围灯。(Android 的 Color 类型是8个byte的 int,高位2 byte 是透明通道,所以传入参数时用 0xFFFFFF & 处理了下)
调用的是 /system/lib/libledLight-jni.so 库,理论上上第三方 app 也可以调用 (Android < 7.0 不限制用户 app 访问非 Android NDK 标准库的系统私有动态库),具体步骤是:
- 在自己的 app 里实现个 com.phicomm.speaker.player.light.LedLight 类,内容可以完全复制斐讯App里的LedLight类。包名、类名和方法名必须完全相同,因为 Android and /or Java JNI 匹配 native method 是完整匹配名字的。斐讯 LedLight 类里的 native void set_color 严格匹配 libledLight-jni.so 里的
JNIEXPORT void JNICALL Java_com_phicomm_speaker_player_light_LedLight_set_color(JNIEnv* env, jobject this)
- R1 固件必须关闭 selinux(需要 root 权限),否则 JNI 调用 native set_color 时会报错无法访问。可以:
- 在 adb shell su 里执行一次
setenforce 0
。 - App 也可以执行 "su -c setenforce 0",但需要请求 root 权限。
- 刷入预先用 Magisk 关闭 selinux 的 boot rooted 镜像。
- 在 adb shell su 里执行一次
进一步,通过逆向 R1 固件里的 /system/lib/libledLight-jin.so 动态库,可以发现 native 的 setColor 实际上最终是调用的 /system/lib/liblight.so 里的 light_set 函数:
int __fastcall light_set(int a1, int a2, int a3)
{
int v3; // r8
int v4; // r4
int v5; // r5
int v6; // r0
int v7; // r0
char v9[128]; // [sp+Ch] [bp-9Ch]
v3 = a3;
v4 = a1;
v5 = a2;
memset(v9, 0, 0x80u);
v6 = _strlen_chk(v9, 128);
sprintf(&v9[v6], "%llx %06x", v4, v5, v3);
v7 = _strlen_chk(v9, 128);
return sub_700("/sys/class/leds/multi_leds0/led_color", v9, v7);
}
a2 和 a3 是 Java native setColor 传入的两个 int 类型参数。所以实际上控制氛围灯颜色是往 "/sys/class/leds/multi_leds0/led_color" 这个设备文件写入 sprintf(str, "%llx %06x", param1, param2) 格式化后的字符串。R1 是 armv7 32位处理器, 所以 1l 是 4字节, x 是小写 hex 字母格式符。所以最后写入的是11个字节的字符串,举例:
- setColor(32767, 0) : 实际写入设备文件字符串 "7fff 000000"
- setColor(32767, 0xffffff) (白色) : 实际写入设备文件字符串 "7fff ffffff"
"/sys/class/leds/multi_leds0/led_color" 这个文件的权限是 666,所以理论上任何 apk 应该都可以直接写这个文件来设置氛围灯,无需 root 权限。但实际上由于 Android 5.1+ 开启了 selinux,第三方 app 无法访问 LED 灯的设备文件(无论是通过 JNI 还是直接在 Java 里写入),除非先获取 root 权限然后关闭 selinux (su -c setenforce 0
)。
测试:(用 shell 似乎无需 root 权限也不需要关闭 selinux 就能访问 led 灯)
adb shell "echo -n '7fff ffffff' > /sys/class/leds/multi_leds0/led_color"
PS. liblight.so 里还定义了 light_show 和 light_close 函数,light_close 好像是往设备文件写入 "1 200 1 1 1 0 0 200 0 15 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15" 字符串,意义不明。也没看到其他地方有调用。似乎系统里还有 /sys/class/leds/multi_leds1/led_color 这个 LED 设备?
控制按键
R1音箱顶部有一圈原型音量调节触摸旋钮和中央一个按键。
在 adb shell su 里使用 getevent -lt 调试实时按键。
顶部中央按键
官方固件里按3下开启蓝牙;长按5秒开启配网模式;按住插入电源开机清空用户数据。
[ 15321.260021] /dev/input/event0: EV_KEY 00c3 DOWN
[ 15321.260021] /dev/input/event0: EV_SYN SYN_REPORT 00000000
[ 15321.390027] /dev/input/event0: EV_KEY 00c3 UP
[ 15321.390027] /dev/input/event0: EV_SYN SYN_REPORT 00000000
EV_KEY 后的按键键值是 Linux /usr/include/linux/input.h 里定义的输入按键编码。00c3 (195) input.h 里未定义,是自定义按键。在 Android app Activity onKeyDown / onKeyUp 里监听到的按键代码是 275.
顶部音量调节触摸旋钮
官方固件里顺时针触摸增大音量。(功能代码位于 com.phicomm.speaker.launcher)
这一圈旋钮似乎是个触摸按键,触碰其中某个位置时按键事件:
[ 15797.543638] /dev/input/event1: EV_KEY BTN_TOUCH DOWN
[ 15797.543638] /dev/input/event1: EV_ABS ABS_X 00000026
[ 15797.543638] /dev/input/event1: EV_SYN SYN_REPORT 00000000
[ 15797.940017] /dev/input/event1: EV_KEY BTN_TOUCH UP
[ 15797.940017] /dev/input/event1: EV_SYN SYN_REPORT 00000000
官方固件里似乎将圆纽的逆时针 / 顺时针旋转直接映射为了 VOLUME_DOWN 和 VOLUME_UP 键。