定制Android内核(Android-12)

  • 相关文章:定制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时,一旦断电,然后重启,造成较重严重的丢数据。这表现在两个方面。

  1. 安装apk后,立即断电,重启,安装的apk包会损坏,这问题在“12.1 修正com.kos.launcher私有*.so被置零”已有涉及,但如果是f2fs,这概率会大增。
  2. 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的音频线中的麦克风。不改官方源码,关于咪头、麦克风录音源的几个结论。

  1. 没有外接3.5mm麦克风,会用板上咪头录音。
  2. 插上3.5mm麦克风,不重启android,还是会用板上咪头录音。
  3. 插着3.5mm麦克风,重启android,此时录音源会变成3.5mm麦克风。此时拔掉3.5mm麦克风,会变成用咪头录音。这现象和1)一样。此时再插入3.5mm麦克风,那还是会用板上咪头录音,这现象就和2)一样。

综上所述,要做到用麦克风录音,只一种方法:android启动时,要插着3.5mm麦克风,而且在运行过程中,3.5mm麦克风不能掉。

怕用的过程出意外,像麦克风拔出过,只是很快就插回了。此时希望立即回到麦克风录音,想到两种可能方法。

  1. 让麦克风录音优先级高于咪头。也就是说,中间插入麦克风时,会优先使用麦克风,代替掉咪头。
  2. 屏蔽掉咪头这个录音源。

目前使用第二种,方法是修改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 -> {”部分。

参考

  1. kOS(13):inputflinger—InputReader线程 https://zhuanlan.zhihu.com/p/196635542

全部评论: 0

    写评论: