斐讯 R1 音箱/原厂固件

斐讯 R1 音箱/原厂固件

固件修改 / 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 镜像。

进一步,通过逆向 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 键。


Last update: 2020-12-10 01:25:19 UTC