- 相关文章:定制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