- 相关文章:定制Android内核(Android-7.1.2)
- 文中提到的Launcher是指上海兰栖开发的远程桌面服务端app,Application ID是com.kos.launcher。它是个用户级app,正因为只有用户级权限,不少对内核的改动,就是为让它有能力做系统级app才能做的事。
- 建议流程。1)先抛开最后一个白名单,按文中步骤修改内核。2)修改完android内核后,安装Andorid平台launcher,并运行。3)找一台win10,运行系统自带的“远程桌面连接”,如何使用参考“Launcher远程桌面演示”。4)继续解决内核修改文中最后一个白名单。
系统需求
- Android的“Root授权”必须能授权给应用,即应用通过“su”可以提升到管理员权限。例子见底下的C代码fill_usb_tty,虽然是在一个通常权限app执行,但su后,就可读出只有管理员权限才能访问的cat /proc/tty/driver/usbserial。对如何读usbserial,会依次尝试两种方法,一是“su -c",如果不行就用第二种,“su 0”。
- 板上CPU至少能硬编码屏幕尺寸的H264。
- 板上应有Wifi和蓝牙模块(查询、修改IP必须)。
static int fill_usb_tty(int fix_count, SDL_ttyUSB** ppttyUSB) { #define CMD_COUNT 2 const char cmds[CMD_COUNT][128] = { // ROC-RK3588S-PC {"su -c \"cat /proc/tty/driver/usbserial\""}, // lubancat4 {"su 0 cat /proc/tty/driver/usbserial"}, }; ... for (int n = 0; n < CMD_COUNT && pos == 0; n ++) { const char* cmd = cmds[n]; FILE* fp = popen(cmd, "r"); 通过读fp就可得到/proc/tty/driver/usbserial文件内容 ...
这里不要尝试使用修改“/proc/tty/driver”目录权限,像“chmod 0777 /proc/tty/driver”,这种改法有可能造成其它问题,像如果插着两个usb转串口设备到板上,第二个插的会挤掉第一个的ttyUSB0。举个例子,此时设备是没有插usb转串口设备的,此时插了一个,ttyUSB0出现了,这时插第二个usb转串口设备,那没有出现ttyUSB1,还是只有ttyUSB0,但是ttyUSB0变成了第二个usb转串口设备。
以下说到具体操作时以Firefly ROC-RK3588S-PC为实例(Android12.0 SDK)。
<aosp>/device/rockchip/rk3588/roc_rk3588s_pc/roc_rk3588s_pc.mk ------ # {leagor}set persist.sys.root_access 1 or 3 PRODUCT_PROPERTY_OVERRIDES += persist.sys.root_access=3
在roc_rk3588s_pc.mk添加“PRODUCT_PROPERTY_OVERRIDES += persist.sys.root_access=3”,让“Root授权”默认值是“应用和ADB”。如果不想授权给ADB,也可以设为1,但必须是1或3,即必须授权给应用。
数值 | Root授权 |
0 | 已禁用。也是不存在persist.sys.root_access时的值 |
1 | 仅限于应用 |
2 | 仅限于ADB |
3 | 应用和ADB |
一、libkosapi.so
1.1 编译
- 从“Launcher+kdesktop”下载launcher-fullsrc-x.x.x-202xxxxx.rar,解压缩,取出目录:aosp-12。
- 复制aosp下的native/libs/kosapi到<aosp>/frameworks/native/libs/kosapi。
- 复制aosp下的native/include/kosapi到<aosp>/frameworks/native/include/kosapi。
- 编译libkosapi:mmm ./frameworks/native/libs/kosapi/。成功后<aosp>/out/target/product/roc_rk3588s_pc/system/lib(lib64)下会有libkosapi.so。
// 获取libkosapi.so版本。版本决定了libkospai提供哪些api、struct。不要修改 void kosGetVersion(char* ver, int /*max_bytes*/) { strcpy(ver, "1.0.3-20220801"); }
更多libkosapi.so细节参考“libkosapi api”。
1.2 让kosapi.so成为系统共享库
需要两个步骤。1)修改<aosp>/build/make/target/product/base_system.mk,指示把libkosapi.so放入设备的/system/lib。如果这里不增加,“make -j8”时不会编译libkosapi.so。2)libkosapi.so虽然已放在设备的/system/lib,但身份不是可用的共享库,运行Launcher还是会报找不到libkosapi.so。修改public.libraries.android.txt,即在调用android_init_namespaces时,public_libraries会有libkosapi.so。
<aosp>/system/core/rootdir/etc/public.libraries.android.txt ----------------- libandroid.so libc.so ... libkosapi.so
public.libraries.android.txt在设备上的位置是“/etc/public.libraries.txt”。
二、让com.kos.launcher拥有访问surfaceflinger权限
E/SurfaceFlinger: Permission Denial: can't access SurfaceFlinger pid=1750, uid=10055
com.kos.launcher没有“android.permission.ACCESS_SURFACE_FLINGER”权限,导致报上面这错误。
<aosp>/frameworks/native/services/surfaceflinger/SurfaceFlinger.cpp ------ status_t SurfaceFlinger::CheckTransactCodeCredentials(uint32_t code) { #pragma clang diagnostic push #pragma clang diagnostic error "-Wswitch-enum" switch (static_cast<ISurfaceComposerTag>(code)) { ... case ACQUIRE_FRAME_RATE_FLEXIBILITY_TOKEN: { ... if (!callingThreadHasUnscopedSurfaceFlingerAccess(usePermissionCache)) { IPCThreadState* ipc = IPCThreadState::self(); ALOGE("Permission Denial: can't access SurfaceFlinger pid=%d, uid=%d", ipc->getCallingPid(), ipc->getCallingUid()); // 注释掉下面这个return。 // return PERMISSION_DENIED; } return OK; } ... }
因为com.kos.launcher没有“android.permission.ACCESS_SURFACE_FLINGER”权限,callingThreadHasUnscopedSurfaceFlingerAccess会返回false,导致CheckTransactCodeCredentials返回失败PERMISSION_DENIED。修改让忽略这权限检查。android-7.2.1时,用户级app就已经没法获得android.permission.ACCESS_SURFACE_FLINGER权限,android-12也是如此。
三、让com.kos.launcher拥有读写/dev/uinput设备权限
要授权other能用/dev/uinput创建设备文件。
1750-1910/com.kos.launcher E/libkosapi-sendinput: Cannot open /dev/uinput: Permission denied. <aosp>/system/core/rootdir/ueventd.rc ------ /dev/uinput 0660 uhid uhid 改为 /dev/uinput 0666 uhid uhid
对鲁班猫4,修改<aosp>/device/rockchip/common/rootdir/ueventd.rockchip.rc
四、IP处理相关
4.1 修改ServiceManager.cpp,让app可以创建名叫“kosapid”的binder服务
<aosp>/frameworks/native/cmds/servicemanager/ServiceManager.cpp ------ Status ServiceManager::addService(const std::string& name, const sp<IBinder>& binder, bool allowIsolated, int32_t dumpPriority) { auto ctx = mAccess->getCallingContext(); // apps cannot add services if (multiuser_get_app_id(ctx.uid) >= AID_APP) { return Status::fromExceptionCode(Status::EX_SECURITY); } ... } 改为 Status ServiceManager::addService(const std::string& name, const sp<IBinder>& binder, bool allowIsolated, int32_t dumpPriority) { auto ctx = mAccess->getCallingContext(); // apps cannot add services if (multiuser_get_app_id(ctx.uid) >= AID_APP) { if (name != "kosapid") { return Status::fromExceptionCode(Status::EX_SECURITY); } } ... }
这么做的原因是,libkosapi.so要新建一个名称是“kosapid”的binder服务,以便接收系统ip发生变化的广播。这个地方没改,或改错了,直观上很难查。可运行launcher,然后检查logcat输出,搜下面这条输出,错误码“-1”可能会不同。
D/UnsolService: UnsolService::start fail(-1). Confirm that 'kosapid' is excluded in ServiceManager::addService
4.2 修改registerUnsolicitedEventListener,让不检查权限
<aosp>/system/netd/server/NetdNativeService.cpp ------ binder::Status NetdNativeService::registerUnsolicitedEventListener( const android::sp<android::net::INetdUnsolicitedEventListener>& listener) { // 修改:注释掉ENFORCE_NETWORK_STACK_PERMISSIONS // ENFORCE_NETWORK_STACK_PERMISSIONS(); gCtls->eventReporter.registerUnsolEventListener(listener); return binder::Status::ok(); }
ENFORCE_NETWORK_STACK_PERMISSIONS会检查app是否同时有android.permission.NETWORK_STACK、android.permission.MAINLINE_NETWORK_STACK权限,只要有一个不存在,就会抛出binder::Status::EX_SECURITY异常,导致不会执行后面的registerUnsolEventListener。但对用户级app,没法得到android.permission.NETWORK_STACK。修改让忽略这权限检查。
经过4.1+4.2修改后,只要拔、插网线,com.kos.launcher便会收到网络断开、连上事件。这里说下使用背景。launcher的远程桌面是个网络服务,一直在侦听(accept)3389端口。为保证连接可靠,它需要有种方法准确、快速判断出正在使用的ip是否无效了。很不幸,网络断开时,并不会准确、快速导致accept失败而退出。这就出现这么个现象,当网线快速拔出又立即插上时,launcher还在用着那个应该无效的ip,使后绪client连接失败。4.1+4.2的修改通过接收netd发出的网络事件,提供了一种准确、快速判断网络是否发生断开方法。
4.3 允许app可以打开、关闭wifi
<aosp>/packages/modules/Wifi/service/java/com/android/server/wifi/WifiServiceImpl.java ------ { public synchronized boolean setWifiEnabled(String packageName, boolean enable) { if (enforceChangePermission(packageName) != MODE_ALLOWED) { return false; } boolean isPrivileged = isPrivileged(Binder.getCallingPid(), Binder.getCallingUid()); if (!isPrivileged && !isDeviceOrProfileOwner(Binder.getCallingUid(), packageName) && !mWifiPermissionsUtil.isTargetSdkLessThan(packageName, Build.VERSION_CODES.Q, Binder.getCallingUid()) && !mWifiPermissionsUtil.isSystem(packageName, Binder.getCallingUid())) { mLog.info("setWifiEnabled not allowed for uid=%") .c(Binder.getCallingUid()).flush(); // 注释掉下面这个return // return false; } .... } }
注释掉上面代码中“return false”。这么做的原因是Android Q开始不允许应用程序开/关WIFI。但是,用远程桌面控制时,目的就是要抛开显示屏,这意味着要允许远程桌面这个app开/关wifi。
4.4 有Wifi时,确保WifiServiceImpl.getScanResults不返回空
<aosp>/packages/modules/Wifi/service/java/com/android/server/wifi/util/WifiPermissionsUtil.java ------ public void enforceCanAccessScanResults(String pkgName, @Nullable String featureId, int uid, @Nullable String message) throws SecurityException { checkPackage(uid, pkgName); ... // If neither caller or app has location access, there is no need to check // any other permissions. Deny access to scan results. if (!canCallingUidAccessLocation && !canAppPackageUseLocation) { ... // 注释掉下面这个throw // throw new SecurityException("UID " + uid + " has no location permission"); } // Check if Wifi Scan request is an operation allowed for this App. if (!isScanAllowedbyApps(pkgName, featureId, uid)) { ... // 注释掉下面这个throw // throw new SecurityException("UID " + uid + " has no wifi scan permission"); } ... }
让不够权限时,不抛出SecurityException异常。这么做的原因是,当设备没有有效ip时,launcher就要调用WifiServiceImpl.getScanResults,得到当前能扫描到的Wifi列表,让用户连接某个Wifi,从而得到一个有效ip。但由于launcher是一个用户级app,WifiServiceImpl.getScanResults会因为缺少权限而抛出异常,导致总得到空的Wifi列表。修改就是让跳过这些权限检查。
五、支持鼠标右键,(滚动)中键
如果这里不做修改,使用带有鼠标的设备做为远程桌面客户端时,像Win10,使用鼠标中键、右键将没有效果。而在控制Android时,中键用于垂直滚动、右键用于Back,还是较为实用的。
5.1 TouchInputMapper.h
<aosp>/frameworks/native/services/inputflinger/reader/mapper/TouchInputMapper.h ------ class TouchInputMapper : public InputMapper { private: // 增加成员函数:dispatchWheelScroll void dispatchWheelScroll(nsecs_t when, nsecs_t readTime, uint32_t policyFlags); };
5.2 TouchInputMapper.cpp
<aosp>/frameworks/native/services/inputflinger/reader/mapper/TouchInputMapper.cpp ------ void TouchInputMapper::cookAndDispatch(nsecs_t when, nsecs_t readTime) { ... if (!mCurrentMotionAborted) { dispatchButtonRelease(when, readTime, policyFlags); dispatchHoverExit(when, readTime, policyFlags); dispatchTouches(when, readTime, policyFlags); dispatchHoverEnterAndMove(when, readTime, policyFlags); dispatchButtonPress(when, readTime, policyFlags); // 为支持鼠标滚动,增加dispatchWheelScroll(...)让分发AMOTION_EVENT_ACTION_SCROLL事件 dispatchWheelScroll(when, readTime, policyFlags); } ... } void TouchInputMapper::dispatchWheelScroll(nsecs_t when, nsecs_t readTime, uint32_t policyFlags) { ... NotifyMotionArgs args(..., AMOTION_EVENT_ACTION_SCROLL, ...); getListener()->notifyMotion(&args); }
5.3 SingleTouchInputMapper.cpp
<aosp>/frameworks/native/services/inputflinger/reader/mapper/SingleTouchInputMapper.cpp --- void SingleTouchInputMapper::syncTouch(nsecs_t when, RawState* outState) { if (mTouchButtonAccumulator.isToolActive()) { outState->rawPointerData.pointerCount = 1; outState->rawPointerData.idToIndex[0] = 0; ... } 改为 void SingleTouchInputMapper::syncTouch(nsecs_t when, RawState* outState) { bool isCursorActive = (mCursorButtonAccumulator.getButtonState() & AMOTION_EVENT_BUTTON_BACK) || outState->rawVScroll != 0 || outState->rawHScroll != 0; if (mTouchButtonAccumulator.isToolActive() || isCursorActive) { outState->rawPointerData.pointerCount = 1; outState->rawPointerData.idToIndex[0] = 0; ... }
为什么这么修改参考“kOS(13):inputflinger—InputReader线程[1] ”中“让SingleTouchInputMapper支持鼠标右键、滚动”。<aosp>目录有修改后的SingleTouchInputMapper.cpp、TouchInputMapper.cpp、TouchInputMapper.h。
六、允许运行时加载外部存储中的libroseaplt.so、libroseaplt2.so
为什么要这么修改的原因参考“运行时加载动态链接库(Android、Windows)”。
6.1 修改linker.cpp,让libroseaplt.so、libroseaplt2.so进入灰名单
<aosp>/bionic/linker/linker.cpp ------ static bool is_exempt_lib(android_namespace_t* ns, const char* name, const soinfo* needed_by) { static const char* const kLibraryExemptList[] = { "libandroid_runtime.so", "libbinder.so", ... nullptr }; // 增加以下这个if if (name[0] == '/') { // and reduce the path to basename const char* name2 = basename(name); if (strcmp(name2, "libroseaplt.so") == 0 || strcmp(name2, "libroseaplt2.so") == 0) { return true; } } // If you're targeting N, you don't get the exempt-list. if (get_application_target_sdk_version() >= 24) { return false; } ... }
android-7.1.2时,还须要改<aosp>/kernel/mm/mmap.c,android-12好像不须要了。
七、增加USB转串口驱动
底盘、雷达、机械臂虽然用的是串口协议,但往往会用USB转串口。为此内核需增加USB转串口驱动。
7.1 修改kernel编译配置,增加常用USB转串口驱动
配置是哪个文件,那要看“make”linux内核时用的哪参数。
$msk ARCH=arm64 firefly_defconfig android-11.config pcie_wifi.config
以上是Firefly ROC-RK3588S-PC编译kernel用的make命令,它对应的配置文件是<aosp>/kernel-5.10/arch/arm64/configs/firefly_defconfig。编辑firefly_defconfig,以下修改增加了三种驱动。
<aosp>/kernel-5.10/arch/arm64/configs/firefly_defconfig ------ CONFIG_USB_SERIAL_CH341=y CONFIG_USB_SERIAL_CP210X=y CONFIG_USB_SERIAL_PL2303=y
对鲁班猫4,修改<aosp>/kernel-5.10/arch/arm64/configs/rockchip_defconfig
7.2 ueventd.rockchip.rc,让other组可以读写USB转串口驱动的设备节点/dev/ttyUSBn
把和usb串口驱动有关ttyACM*、ttyUSB*权限由660改为666。
<aosp>/device/rockchip/common/rootdir/ueventd.rockchip.rc /dev/ttyACM* 0666 radio radio /dev/ttyUSB0 0666 radio radio /dev/ttyUSB1 0666 radio radio /dev/ttyUSB2 0666 radio radio /dev/ttyUSB3 0666 radio radio /dev/ttyUSB4 0666 radio radio /dev/ttyUSB5 0666 radio radio /dev/ttyUSB6 0666 radio radio /dev/ttyUSB7 0666 radio radio /dev/ttyUSB8 0666 radio radio /dev/ttyUSB9 0666 radio radio
八、com.kos.launcher加入防杀白名单
Launcher是个普通app,工作特点是往往长时间工作在后台,极易被Android Freezer杀死。为避免误杀,必须把它加入防杀白名单。
<aosp>/frameworks/base/services/core/java/com/android/server/am/OomAdjuster.java ------ public class OomAdjuster { @GuardedBy({"mService", "mProcLock"}) private void updateAppFreezeStateLSP(ProcessRecord app) { if (!mCachedAppOptimizer.useFreezer()) { return; } if (app.mOptRecord.isFreezeExempt()) { // 如果要在该app禁用Freeze,一般会在这里return。像com.android.settings。 // 对com.kos.launcher,如果能在这里return,那自然是最好的。 // 曾试着把com.kos.lanucher加入lowmem_package_filter.xml,没生效。 return; } // 下面这个if是新加的。如果上面isFreezeExempt()无法做到return,那这里加个额外退出 if (app.processName.equals("com.kos.launcher")) { return; } ..... } }
更多Freezer内容参考“Android Freezer 简介”。
九、确保配置改变不会导致重启Activity
理论上说,只要在android:configChanges写上所有可能的配置改变,那无论配置怎么改变都不会导致该Activity重启。但在android-12,我不知道怎么写android:configChanges才能让包含CONFIG_ASSETS_PATHS、CONFIG_WINDOW_CONFIGURATION这两种改变。
<aosp>/frameworks/base/services/core/java/com/android/server/wm/ActivityRecord.java ------ private boolean shouldRelaunchLocked(int changes, Configuration changesConfig) { ... // 增加下面这个if块 if (packageName != null && packageName.equals("com.kos.launcher")) { return false; } return (changes&(~configChanged)) != 0; }
既然android:configChanges无法包含所有改变,但又要确保配置改变不会导致重启Activity,这里修改shouldRelaunchLocked。发现是“com.kos.launcher”这个app的activity时,强制返回false,即认为内部能消化掉此次改变,不必重启此Activity。关于这修改的更多内容参考“android:configChanges”中的“四、确保配置改变不会导致重启Activity”。
十、默认“文件和媒体”权限从“仅允许问媒体文件”改为“允许管理所有文件”
远程桌面一个主要功能是复制文件。在android-12,app能有完整文件权限的只有/sdcard中的外部存储目录,对com.kos.launcher,这个目录是/sdcard/Android/data/files。复制文件时,用户希望能一次就复制到想要的目的地,为此须要给com.kos.launcher更大文件权限。怎么提高权限,android-12提供了通过让用户进入“设置”修改“文件和媒体”权限的方法,即由默认的“仅允许问媒体文件”改为“允许管理所有文件”,更多可参考“Android 11 上的文件读写权限(MANAGE_EXTERNAL_STORAGE)”。
以上方法是可以提高com.kos.launcher权限了,但这里提供了另一种方法,直接让默认“文件和媒体”权限从“仅允许问媒体文件”改为“允许管理所有文件”。这么改后,所有用户级app一开始就拥有了“允许管理所有文件”的文件权限,这自然就包括com.kos.launcher。
<aosp>/frameworks/base/core/java/android/app/AppOpsManager.java ------ private static int[] sOpDefaultMode = new int[] { ... AppOpsManager.MODE_ALLOWED, // ALLOW_MEDIA_LOCATION AppOpsManager.MODE_DEFAULT, // QUERY_ALL_PACKAGES // OP_MANAGE_EXTERNAL_STORAGE,从MODE_DEFAULT改为MODE_ALLOWED AppOpsManager.MODE_ALLOWED, // MANAGE_EXTERNAL_STORAGE, ... };
修改sOpDefaultMode中索引OP_MANAGE_EXTERNAL_STORAGE(92)处的值,从MODE_DEFAULT改为MODE_ALLOWED。
十一、app进入后台后,关掉杀死serivce、停止摄像头、停止录音
在android-12,当app切到后台,(大概)一分钟后,app正运行的service会被杀死;如果正播放摄像头,摄像头会被停掉。关于这问题更多详细可参考“(android-12)app进入后台,关掉杀死serivce、停止摄像头、停止录音”。
11.1 关掉杀死serivce
<aosp>/frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java ------ @GuardedBy(anyOf = {"this", "mProcLock"}) int getAppStartModeLOSP(int uid, String packageName, int packageTargetSdk, int callingPid, boolean alwaysRestrict, boolean disabledOnly, boolean forcedStandby) { if (mInternal.isPendingTopUid(uid)) { return ActivityManager.APP_START_MODE_NORMAL; } // 增加下面这个if判断,希望关闭限制的apk直接返回APP_START_MODE_NORMAL。 if (packageName != null && packageName.equals("com.kos.launcher")) { Slog.d(TAG, "{leagor}checkAllowBackground: uid=" + uid + " pkg=" + packageName + " it is in whitelist"); return ActivityManager.APP_START_MODE_NORMAL; } ...... }
上面例代码中,com.kos.launcher是需要关闭限制的apk,原理很简单,增加个if判断,希望关闭限制的apk直接返回APP_START_MODE_NORMAL。
11.2 关掉停止摄像头
<aosp>/frameworks/av/services/camera/libcameraservice/CameraService.cpp ------ void CameraService::UidPolicy::onUidIdle(uid_t uid, bool /* disabled */) { // 增加enable_background,以及后面的if判断 bool enable_background = true; if (enable_background) { ALOGI("{leagor}[UidPolicy::onUidIdle]uid: %i, enable background, do nothing", (int)uid); return; } bool deleted = false; ...... }
要关掉停止摄像头,在UidPolicy::onUidIdle,什么都不做,直接返回。
11.3 关掉停止录音
<aosp>/frameworks/av/services/audiopolicy/service/AudioPolicyService.cpp ------ void AudioPolicyService::UidPolicy::onUidIdle(uid_t uid, __unused bool disabled) { // 增加enable_background,以及后面的if判断。 bool enable_background = true; if (enable_background) { // The output is a bit much, remark it ALOGI("{leagor}[AudioPolicyService::UidPolicy::onUidIdle]uid: %i, enable background, do nothing", (int)uid); return; } updateUid(&mCachedUids, uid, false, ActivityManager::PROCESS_STATE_UNKNOWN, true); }
要关掉停止录音,在UidPolicy::onUidIdle,什么都不做,直接返回。
十二、修正com.kos.launcher两个“bug”
“bug”加引号,是因为不知道这bug是出在哪部分,是com.kos.launcher,还是aosp源码?
12.1 修正com.kos.launcher私有*.so被置零
(android-12)app私有*.so被置零有关于这问题的详细描述。解决分三步。
第一步:修改InstalldNativeService.cpp,在创建com.kos.launcher的CE时,调用自写的correct_so_zero_if_need
<aosp>/frameworks/native/cmds/installd/InstalldNativeService.cpp ------ #include "rose/filesystem.hpp" #include "rose/minizip/minizip.hpp" #include "rose/SDL_log.h" static void correct_so_zero_if_need(const std::string& app_dir, const std::string& pkgname, const std::string& unzip_dir) { ...... } binder::Status InstalldNativeService::createAppData(const std::optional<std::string>& uuid, const std::string& packageName, int32_t userId, int32_t flags, int32_t appId, const std::string& seInfo, int32_t targetSdkVersion, int64_t* _aidl_return) { ...... if (flags & FLAG_STORAGE_CE) { ...... if (packageName == "com.kos.launcher") { correct_so_zero_if_need("/data/app", packageName, path); } } ...... }
第二步:增加和correct_so_zero_if_need相关的额外cpp/hpp
这放在<installd>下的两个目录:librose_src、rose。
第三步:修改Android.bp
<aosp>/frameworks/native/cmds/installd/Android.bp ------ cc_defaults { name: "installd_defaults", ...... srcs: [ .... 在需要编译的源文件,增加librose_src中的cpp "librose_src/minizip/miniunz.cpp", "librose_src/minizip/unzip.c", "librose_src/filesystem.cpp", "librose_src/SDL.c", "librose_src/SDL_string.c", "librose_src/string_utils.cpp", ], shared_libs: [ .... 在需要链接的so,增加解压需要的libz.so "libz", ],
12.2 修正com.kos.launcher开机启动失败
(android-12)app开机启动失败有关于这问题的详细描述。
<aosp>/frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java ------ private AndroidPackage addForInitLI(ParsedPackage parsedPackage, @ParseFlags int parseFlags, @ScanFlags int scanFlags, long currentTime, @Nullable UserHandle user) throws PackageManagerException { ...... synchronized (mLock) { ...... if (scanSystemPartition) { ...... } // 在“synchronized (mLock)”末尾增加下面这个if判断 if (installedPkgSetting != null && installedPkgSetting.name != null && installedPkgSetting.name.equals("com.kos.launcher")) { int userId = user.myUserId(); boolean stopped = installedPkgSetting.getStopped(userId); // 一旦stopped被错误“恢复”true时,notLaunched往往也成了的true。上次启动时运行过,notLaunched应该是false。 Log.d(TAG, "{leagor}[addForInitLI](1)name=" + installedPkgSetting.name + " stopped=" + stopped + " notLaunched=" + installedPkgSetting.getNotLaunched(userId)); if (stopped) { // apk是com.kos.launcher,并且PackageUserState.stopped是true mSettings.setPackageStoppedStateLPw(this, parsedPackage.getPackageName(), false, userId); // installedPkgSetting是引用,经过上面修改,此时的stopped和notLaunched都应该是false。 Log.d(TAG, "{leagor}[addForInitLI](2)name=" + installedPkgSetting.name + " stopped=" + installedPkgSetting.getStopped(userId) + " notLaunched=" + installedPkgSetting.getNotLaunched(userId)); } } } ...... }
PackageManagerService.scanDirLI扫描到com.kos.launcher时,如果发现PackageUserState.stopped是true,那强制改到false。
十三、app进入后台后,允许正常蓝牙扫描
app进入后台,只能扫到associatedDevices、SanitizedExposureNotification这两类peripheral,但在一些应用,这是不够的。
<aosp>/packages/apps/Bluetooth/src/com/android/bluetooth/Utils.java ------ public static boolean checkCallerHasFineLocation( Context context, AttributionSource attributionSource, UserHandle userHandle) { if (blockedByLocationOff(context, userHandle)) { Log.e(TAG, "Permission denial: Location is off."); return false; } // STOPSHIP(b/188391719): enable this security enforcement // attributionSource.enforceCallingUid(); if (PermissionChecker.checkPermissionForDataDeliveryFromDataSource( context, ACCESS_FINE_LOCATION, PID_UNKNOWN, new AttributionSource(context.getAttributionSource(), attributionSource), "Bluetooth location check") == PERMISSION_GRANTED) { return true; } else { // 修改:checkCallerHasFineLocation==false时,函数依旧返回true。 Log.e(TAG, "{leagor}checkPermissionForDataDeliveryFromDataSource's false to true"); return true; } /* Log.e(TAG, "Permission denial: Need ACCESS_FINE_LOCATION " + "permission to get scan results"); return false; */ }
修改方法:checkPermissionForDataDeliveryFromDataSource返回false时,checkCallerHasFineLocation依旧返回true。由于“if”两个分支都已有return,得注释掉后面Log.e、return false,免得编译出错。"扫描:BluetoothLeScanner.startScan"写了为什么这么改原因。
十四、修正com.kos.launcher在后台运行,点击桌面图标却重新启动
com.kos.launcher正运行着,并切到后台,然后点击桌面com.kos.launcher图标。出现行为不是com.kos.launcher被切到前台,而是重新启动了com.kos.launcher这个app。这里解决这个问题。
<aosp>/frameworks/base/services/core/java/com/android/server/wm/Task.java ------ class Task extends TaskFragment { boolean isSameIntentFilter(ActivityRecord r) { final Intent intent = new Intent(r.intent); // Make sure the component are the same if the input activity has the same real activity // as the one in the task because either one of them could be the alias activity. if (Objects.equals(realActivity, r.mActivityComponent) && this.intent != null) { intent.setComponent(this.intent.getComponent()); } if (intent.getComponent() != null && intent.getComponent().getPackageName() != null) { // 这个if块是我加的,com.kos.launcher时,强制认为一样。 String rPackageName = r.mActivityComponent.getPackageName(); if (intent.getComponent().getPackageName().equals(rPackageName) && rPackageName.equals("com.kos.launcher")) { Slog.d(TAG, "{leagor}isSameIntentFilter(1), it is " + rPackageName + " always return true"); Slog.d(TAG, "{leagor}isSameIntentFilter(2), intent: " + intent + " this.intent: " + this.intent); return true; } } return intent.filterEquals(this.intent); } }
目前解决办法是修改isSameIntentFilter,增加那第二个if。关于这问题详细说明见“(android-12)app在后台运行,点击桌面图标重新启动问题”。
十五、允许com.kos.launcher可以重启设备,以及关机
firefly额外提供了class SchedulePowerOnOffUtil处理重启和关机,修改只针对野火鲁班猫4的android-12。
15.1 允许发送Intent.ACTION_REBOOT、Intent.ACTION_SHUTDOWN
这须要改两个文件。
<aosp>/frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java ------ final int broadcastIntentLocked(ProcessRecord callerApp, String callerPackage, ...) { ... switch (UserHandle.getAppId(callingUid)) { case ROOT_UID: ... default: if (callerPackage != null && callerPackage.equals("com.kos.launcher")) { // 增加的if分支,原来只有下面的else部分 Slog.i(TAG, "{leagor}broadcastIntentLocked, is " + callerPackage + ", isCallerSystem always true"); isCallerSystem = true; } else { isCallerSystem = (callerApp != null) && callerApp.isPersistent(); } break; } ... } <aosp>/frameworks/base/services/core/java/com/android/server/am/BroadcastQueue.java ------ private void deliverToRegisteredReceiverLocked(BroadcastRecord r, ...) { if (filter.requiredPermission != null) { // 改为下面这个if判断 boolean isLeagorWhiteList = r.intent.getAction() != null && r.intent.getAction().equals(Intent.ACTION_REBOOT); if (filter.requiredPermission != null && !isLeagorWhiteList) { }
app是com.kos.launcher,让isCallerSystem是true,这样就能处理这个用户级app的Intent.ACTION_REBOOT。同时省掉com.kos.launcher在配置中需写上REBOOT权限。
15.2 增加关机,以及重启和关机都可以带延时
<aosp>/frameworks/base/services/core/java/com/android/server/Watchdog.java ------ public class Watchdog { final class RebootRequestReceiver extends BroadcastReceiver { @Override public void onReceive(Context c, Intent intent) { if (intent.getIntExtra("nowait", 0) != 0) { int interval = intent.getIntExtra("interval", 0); rebootSystem("Received ACTION_REBOOT broadcast", interval, true);
为支持延时重启,修改了rebootSystem函数,第二个参数interval指示要延时的秒数。
对nowait语义,表示是否要等到pms.reboot()/pms.reboot()执行完了,此函数才返回。见pms.reboot第三个参数wait。和同步有关,和延时没啥关系。pms指的是IPowerManager, 实现它的class在<aosp>/frameworks/base/core/java/android/os/PowerManager.java
return; } Slog.w(TAG, "Unsupported ACTION_REBOOT broadcast: " + intent); } } final class ShutdownRequestReceiver extends BroadcastReceiver { @Override public void onReceive(Context c, Intent intent) { if (intent.getIntExtra("nowait", 0) != 0) { boolean isLeagor = intent.getBooleanExtra("leagor", false); int interval = intent.getIntExtra("interval", 0); if (isLeagor) { rebootSystem("Received ACTION_SHUTDOWN broadcast", interval, false); } return; } Slog.w(TAG, "Unsupported ACTION_SHUTDOWN broadcast: " + intent); } }
ShutdownRequestReceiver是个新增类,用于处理ACTION_SHUTDOWN。Watchdog.java本没有ACTION_SHUTDOWN,是我后加的,其它地方也会发ACTION_SHUTDOWN,让Watchdog.java只处理带"leagor==true"的。
public void init(Context context, ActivityManagerService activity) { ... context.registerReceiver(new ShutdownRequestReceiver(), new IntentFilter(Intent.ACTION_SHUTDOWN), android.Manifest.permission.SHUTDOWN, null);
类似Intent.ACTION_REBOOT,在Watchdog初始化时注册能处理ACTION_SHUTDOWN,处理者就是新加的ShutdownRequestReceiver。
} void rebootSystem(String reason, int interval, boolean isReboot) { Slog.i(TAG, (isReboot? "Rebooting": "Shutdowning") + " system because: " + reason + ", interval: " + interval); if (interval > 0) { try { Thread.sleep(interval * 1000); } catch (InterruptedException e) { } } IPowerManager pms = (IPowerManager)ServiceManager.getService(Context.POWER_SERVICE); try { if (isReboot) { pms.reboot(false, reason, false); } else { pms.shutdown(false, reason, false); } } catch (RemoteException ex) { }
修改了rebootSystem实现,如果interval不是0,要先延时。同时让可以执行关机。
} }
在Watchdog.java,开了个服务,可以处理Intent.ACTION_REBOOT广播。按类似方法,也让能处理ACTION_SHUTDOWN。
十六、让拥有直接访问usb权限
要直接访问usb设备时,像奥比中光深度相机,对于低点targetSdk时,像27,会弹出个确定框。

对高点targetSdk,像32,则认为没有权限,提示类似以下错误。
E/java MessagesHandler: handleMessage: failed to open device, error: User has not given 10078/com.kos.launcher permission to access device /dev/bus/usb/001/004
修改让总拥有访问权限,从而不让出现上面确认框,也不会因没有权限而失败。
<aosp>/frameworks/base/services/usb/java/com/android/server/usb/UsbUserPermissionManager.java ------ boolean hasPermission(@NonNull UsbDevice device, @NonNull String packageName, int pid, int uid) { ...... synchronized (mLock) { if (uid == Process.SYSTEM_UID || mDisablePermissionDialogs) { return true; } if (packageName != null && packageName.equals("com.kos.launcher")) { // 这个if块是我加的,com.kos.launcher时,强制认为有权限。 return true; } DeviceFilter filter = new DeviceFilter(device); ...... }
解决办法是修改hasPermission,让packageName是com.kos.launcher时,强制认为有权限。
对鲁班猫4,这个修改还能解决启动时类似下面、没有获得usb设备访问权限bug。
2024-04-24 21:12:49.160 2355-2355/com.kos.launcher E/AndroidRuntime: FATAL EXCEPTION: SDLActivity Process: com.kos.launcher, PID: 2355 java.lang.RuntimeException: Unable to start activity ComponentInfo{com.kos.launcher/com.kos.launcher.app}: java.lang.SecurityException: User has not given 10081/com.kos.launcher permission to access device /dev/bus/usb/002/018 at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3707) at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3864)
这里报没有访问权限,但又没说哪个权限。
十七、强制不使用USB麦克风
市面上有这么种摄像头,usb描述符写着它是支持麦克风录音,但硬件上就没模块。这就产生一个问题,一旦插入这种摄像头,按现在roc-3588s-pc录音策略,USB录音源优先级高于3.5mm,录音源会由3.5mm麦克风自动切到这硬件其实是不存在的录音源,录下的将全是静音。
想到是两个解决方案。1)永远忽略USB录音源。1.25mm、3.5mm的依现在策略不变。当然,这就造成客户没法使用USB麦克风。2)让app可以强制指定一种,一旦指定,中间录音源不会自动切换。
这里改动是针对第一方案:永远忽略USB录音源。
--- a/frameworks/base/services/usb/java/com/android/server/usb/UsbAlsaDevice.java +++ b/frameworks/base/services/usb/java/com/android/server/usb/UsbAlsaDevice.java @@ -213,8 +213,9 @@ public final class UsbAlsaDevice { if (mHasInput) { int device = mIsInputHeadset ? AudioSystem.DEVICE_IN_USB_HEADSET : AudioSystem.DEVICE_IN_USB_DEVICE; - boolean connected = isInputJackConnected(); + //boolean connected = isInputJackConnected(); + boolean connected = false; Slog.i(TAG, "INPUT JACK connected: " + connected); int inputState = (enable && connected) ? 1 : 0; if (inputState != mInputState) { mInputState = inputState;
十八、修改/data文件系统类型为ext4
修改只针对野火鲁班猫4的android-12。
用df -t f2fs可查看类型是f2fs的文件系统。
rk3588s_lubancat_4_v1_hdmi:/ $ df -t f2fs Filesystem 1K-blocks Used Available Use% Mounted on /dev/block/dm-7 25505792 62292 25312428 1% /data
/data是f2fs时,一旦断电,然后重启,造成较重严重的丢数据。这表现在两个方面。
- 安装apk后,立即断电,重启,安装的apk包会损坏,这问题在“12.1 修正com.kos.launcher私有*.so被置零”已有涉及,但如果是f2fs,这概率会大增。
- apk会有配置数据,存储在一个叫<Intermal Memory>/Android/data/com.kos.launcher/files/preferences的文件。launcher这个app改了配置,这个文件也会跟着变,立即断电,重启,保存的配置丢了。
不过,如果是在界面上按“关机”、“重启”按钮,导致的重启,那两个问题基本都不会发生。
为解决这丢数据问题,参考“Rockchip RK3588 Android SDK关闭data分区的磁盘加密功能及修改data分区的文件系统”中的“userdata区文件系统换为EXT4”。核心是修改两个文件,fstab.in和recovery.fstab。修改时,注意recovery.fstab所在目录,有些主板不是在rk3588_s或rk3588s_s,而是写在其它目录,像鲁班猫4是在rk3588s_lubancat_4_v1_hdmi。
/data文件系统改为ext4后,断电、重启,数据丢问题就没了,只是让降低概率。
十九、默认赋予app权限,不弹出权限申请窗口
firefly给的android-12,已在这方面做了修改,并已实现。修改只针对野火鲁班猫4的android-12。
修改<aosp>/packages/modules/Permission/PermissionController/src/com/android/permissioncontroller/permission/ui/GrantPermissionsActivity.java中的onRequestInfoLoad函数。
private void onRequestInfoLoad(List<RequestInfo> requests) { ... } else if (requests.isEmpty()) { setResultAndFinish(); return; } // if (mCallingPackage.equals("com.kos.launcher")) { Log.i(LOG_TAG, "{leagor-install}onRequestInfoLoad, mCallingPackage: " + mCallingPackage + " my app"); String permissionGroupName; int grantResult = GRANTED_ALWAYS; // from GrantPermissionsViewHandler.GRANTED_ALWAYS permissionGroupName = "android.permission-group.CAMERA"; mViewModel.onPermissionGrantResult(permissionGroupName, null, grantResult); permissionGroupName = "android.permission-group.LOCATION"; List<String> affectedForegroundPermissions = new ArrayList<>(); affectedForegroundPermissions.add("android.permission.ACCESS_FINE_LOCATION"); affectedForegroundPermissions.add("android.permission.ACCESS_COARSE_LOCATION"); mViewModel.onPermissionGrantResult(permissionGroupName, affectedForegroundPermissions, grantResult); permissionGroupName = "android.permission-group.MICROPHONE"; mViewModel.onPermissionGrantResult(permissionGroupName, null, grantResult); permissionGroupName = "android.permission-group.NEARBY_DEVICES"; mViewModel.onPermissionGrantResult(permissionGroupName, null, grantResult); permissionGroupName = "android.permission-group.STORAGE"; mViewModel.onPermissionGrantResult(permissionGroupName, null, grantResult); setResultAndFinish(); return; }
修改方法就是在这里新增个if块。
if (mRequestInfos == null) { mTotalRequests = requests.size(); } mRequestInfos = requests; showNextRequest(); }
注意这个if块不能加在onCreate,像onCreate的return前执行,那会失败。为什么这么改,参考“(android-12)默认赋予app权限,不弹出权限申请窗口”。
二十、减少logcat输出
20.1 不断出输出以下日志
RILU: find_pci_device is 0 RILU: cannot find ttyname for AT Port RILC: USB can't find at device
按如下修改就是关闭了4G/5G模块的支持,就不会有那些打印。修改有两个文件。
<aosp>/device/rockchip/common/BoardConfig.mk ------ BOARD_HAS_RK_4G_MODEM ?= true 改为 BOARD_HAS_RK_4G_MODEM ?= false <aosp>/hardware/ril/rild/rild.rc ------ service ril-daemon /vendor/bin/hw/rild -l /vendor/lib64/libquectel-ril.so class main user root group radio cache inet misc audio log readproc wakelock capabilities BLOCK_SUSPEND NET_ADMIN NET_RAW 改为 # service ril-daemon /vendor/bin/hw/rild -l /vendor/lib64/libquectel-ril.so # class main # user root # group radio cache inet misc audio log readproc wakelock # capabilities BLOCK_SUSPEND NET_ADMIN NET_RAW 即注释rild掉全部内容。
20.2 没有接HDMI显示器时
没有接HDMI显示器时,会不断输出下面日志
E/hwc-drm-two: ValidateDisplay,line=1635 init_success_=0 skip. E/HWComposer: getDeviceCompositionChanges: presentOrValidate failed for display 0: BadDisplay (2) E/CompositionEngine: chooseCompositionStrategy failed for Internal display: -2147483648 (Unknown error -2147483648) E/hwc-drm-two: PresentDisplay,line=1314 init_success_=0 skip. E/HWComposer: presentAndGetReleaseFences: present failed for display 0: BadDisplay (2) E/CompositionEngine: chooseCompositionStrategy failed for Internal display: -2147483648 (Unknown error -2147483648)
方法是修改下面这几个文件
- <aosp>/hardware/rockchip/hwcomposer/drmhwc2/drmhwctwo.cpp
- <aosp>/frameworks/native/services/surfaceflinger/DisplayHardware/HWComposer.cpp
- <aosp>/frameworks/native/services/surfaceflinger/CompositionEngine/src/Display.cpp
20.3 E/RKDENOISE: rkdenoise_process: pDenoiseState NULL
把libanr.so放到目录下/vendor/rockchip/common/tinyalsa/lib/hw
--- a/vendor/rockchip/common/tinyalsa/tinyalsa.mk +++ b/vendor/rockchip/common/tinyalsa/tinyalsa.mk @@ -1,4 +1,5 @@ LOCAL_PATH := vendor/rockchip/common/tinyalsa PRODUCT_COPY_FILES += \ + $(LOCAL_PATH)/lib/hw/libanr.so:/vendor/lib/hw/libanr.so \
20.4 减少相机运行的输出日志
一般放在:<aosp>/hardware/interfaces/camera/device/3.4/default/ExternalCameraDeviceSession.cpp
V/ExtCamDevSsn@3.4: threadLoop(3100) halbuf_wxh(1280x720) frameNumber(232972) V/ExtCamDevSsn@3.4: threadLoop: ANDROID_SCALER_CROP_REGION not set V/ExtCamDevSsn@3.4: @dequeueV4l2FrameLocked(3938) cameraId:120 selectV4l2FD done.
ExtCamDevSsn@3.4中的3.4对应文件路径中的3.4。
二十一、猫4录音源问题
对录音,猫4有个板上咪头,即使猫4全外露时,像距离猫4半米,录音音量已经很小。加上极可能罩个外壳,实际已没法用咪头。想到是采用3.5mm的音频线中的麦克风。不改官方源码,关于咪头、麦克风录音源的几个结论。
- 没有外接3.5mm麦克风,会用板上咪头录音。
- 插上3.5mm麦克风,不重启android,还是会用板上咪头录音。
- 插着3.5mm麦克风,重启android,此时录音源会变成3.5mm麦克风。此时拔掉3.5mm麦克风,会变成用咪头录音。这现象和1)一样。此时再插入3.5mm麦克风,那还是会用板上咪头录音,这现象就和2)一样。
综上所述,要做到用麦克风录音,只一种方法:android启动时,要插着3.5mm麦克风,而且在运行过程中,3.5mm麦克风不能掉。
怕用的过程出意外,像麦克风拔出过,只是很快就插回了。此时希望立即回到麦克风录音,想到两种可能方法。
- 让麦克风录音优先级高于咪头。也就是说,中间插入麦克风时,会优先使用麦克风,代替掉咪头。
- 屏蔽掉咪头这个录音源。
目前使用第二种,方法是修改es8388_config.h。
<aosp>/hardware/rockchip/audio/tinyalsa_hal/codec_config/es8388_config.h ------ const struct config_control es8388_main_mic_capture_controls[] = { ... { .ctl_name = "Capture Mute", .int_val = {on}, <-- off改为on }, ... { .ctl_name = "Differential Mux", .str_val = "Line 1", <-- 2改为1 }, };
这两处要同时修改。
顺便说下,对ROC-RK3588S-PC,针应2)情况,中间插入3.5mm麦克风时,录音源会变成3.5mm麦克风。猫4、ROC-RK3588S-PC都使用美标3.5mm音频线。它们都有咪头接口,但ROC-RK3588S-PC不焊咪头,猫4是咪头直接焊上了
二十二、com.kos.launcher加入静默安装白名单
如果升级com.kos.launcher时,用户接受手动重启设备,这里可以不用改。
要是不修改,安装launcher时会有两次弹框确认,一是开始安装时“安装”,二是安装成功后“启动”。为什么要静默安装?——升级launcher时,要是不静默安装,安装成功后,老版本launcher已停止运行,新版本launcher却停留在第二次弹框确认界面。由于Luancher未运行,此时远程桌面无法连接,只有重启设备,有了静默安装就不必重启设备了。
以下修改只是不让弹出第二个确认框。
<aosp>/frameworks/base/packages/PackageInstaller/src/com/android/packageinstaller/InstallSuccess.java ------ public class InstallSuccess extends AlertActivity { protected void onCreate(@Nullable Bundle savedInstanceState) { ... mLaunchIntent = getPackageManager().getLaunchIntentForPackage(mAppPackageName); if (mAppPackageName != null && mAppPackageName.equals("com.kos.launcher")) { // see below: launchButton.setOnClickListener(view -> { try { startActivity(mLaunchIntent); } catch (ActivityNotFoundException | SecurityException e) { Log.e(LOG_TAG, "Could not start activity", e); } finish(); return; }
修改方法就是增加上面这个if块。
bindUi(); ... } }
当正安装的是“com.kos.launcher”app时,直接调用startActivity开始安装。至于startActivity是怎么来的,参考此文件的“launchButton.setOnClickListener(view -> {”部分。
参考
- kOS(13):inputflinger—InputReader线程 https://zhuanlan.zhihu.com/p/196635542