Android 13 - 对讲app后台休眠后无法录音

这个问题是客户要求预置的一个app,反馈的现象视频是录音进程并未启动,本地还无法复现。第二天电量掉到了4%才复现,由此可见,需要严格按照现象视频操作。

下面简单分析下日志

复制代码
app创建一个录音实例
16:58:05.901 AudioRecord               I  Set mSessionId:0 app name:lte.xxx.xx:media 

当前进程(真实 PID = 3204, UID = 10120)在请求创建录音时,声称自己是另一个进程(PID = 3140)
16:58:05.904 AudioFlinger              W  createRecord uid 10120 pid 3204 tried to pass itself off as pid 3140

HAL 层正常初始化日志,表明音频路由正在建立
16:58:05.916 audio_hw_primary          I  AUDIO_PORT_TYPE_MIX source handle:13 hw_module:0 stream:-1  source:-1
16:58:05.917 audio_hw_primary          I  AUDIO_PORT_TYPE_DEVICE sink device type:0x2 hw_module:0


16:58:06.420 APM_AudioPolicyManager    D  setStreamVolumeIndex: stream AUDIO_STREAM_ACCESSIBILITY attributes={ Content type: AUDIO_CONTENT_TYPE_UNKNOWN Usage: AUDIO_USAGE_ASSISTANCE_ACCESSIBILITY Source: AUDIO_SOURCE_DEFAULT Flags: 0x0 Tags:  }

开始录音
16:58:06.421 AudioRecord               I  start(65): sync event 0 trigger session 0


16:58:06.421 AudioRecord               I  start mClientAttributionSource.uid:10120 mClientAttributionSource.pid:3140  mSessionId:337 app name:xxx.xxx.xxx:media 

16:58:06.424 APM_AudioPolicyManager    D  setStreamVolumeIndex: stream AUDIO_STREAM_ACCESSIBILITY attributes={ Content type: AUDIO_CONTENT_TYPE_UNKNOWN Usage: AUDIO_USAGE_ASSISTANCE_ACCESSIBILITY Source: AUDIO_SOURCE_DEFAULT Flags: 0x0 Tags:  }


正在评估 RECORD_AUDIO 操作(op code 27)的权限。
16:58:06.425 AppOps                    D  RECORD_AUDIO-evalMode-op:27 ,mode: 4 ,capability: 8

16:58:06.429 AidlConversion            W  legacy2aidl_audio_channel_mask_t_AudioChannelLayout: no AudioChannelLayout found for legacy output audio_channel_mask_t value 0x10

16:58:06.429 audio_hw_primary          I  out_set_parameters app_scene:5:stream_mute=true
16:58:06.430 APM_AudioPolicyManager    D  setAppState(portId:65, state:2)
16:58:06.430 APM_AudioPolicyManager    D  setAppState(portId:65, state:0)
16:58:06.431 AidlConversion            W  aidl2legacy_AudioChannelLayout_audio_channel_mask_t: no legacy output audio_channel_mask_t found for AudioChannelLayout{layoutMask: 16}
16:58:06.434 AudioSceneRecognizer      D  AudioScene for uid:10120 pkg name:xxx.xx.xxxsceneId:1 changed:true

录音操作被阻止,未启动
16:58:06.435 AppOps                    E  Operation not started: uid=10120 pkg=xxx.xxx.xxx(null) op=RECORD_AUDIO

AudioFlinger 会继续创建音频流(底层资源分配),但不会传递真实音频数据(返回静音或错误)。
这是为了保持接口一致性,避免崩溃,但应用层会收不到有效录音数据。
16:58:06.435 APM_AudioPolicyManager    I  startInput portId 65
16:58:06.435 APM_AudioPolicyManager    I  startInput input:94, session:337)

其中,最关键的就是

Operation not started: uid=10120 pkg=xxx.xxx.xxx(null) op=RECORD_AUDIO

这里就已经被阻止了,具体原因可以从上面的日志看到:

RECORD_AUDIO-evalMode-op:27 ,mode: 4 ,capability: 8

mode: 4 = MODE_IGNORED(表示拒绝录音权限)

AppOps mode 含义:

0 = MODE_ALLOWED

1 = MODE_IGNORED(拒绝)

2 = MODE_ERRORED

3 = MODE_FOREGROUND(仅前台允许)

4 = MODE_IGNORED(明确拒绝)

源码定义如下:

frameworks/base/core/java/android/app/AppOpsManager.java

复制代码
    /**
     * Result from {@link #checkOp}, {@link #noteOp}, {@link #startOp}: the given caller is
     * allowed to perform the given operation.
     */
    public static final int MODE_ALLOWED = 0;

    /**
     * Result from {@link #checkOp}, {@link #noteOp}, {@link #startOp}: the given caller is
     * not allowed to perform the given operation, and this attempt should
     * <em>silently fail</em> (it should not cause the app to crash).
     *
     * UNISOC: CTA Feature:
     * It should show a dialog on screen to get user permission. For CTA permission, default value is
     * MODE_IGNORED. In permission settings, the DENY refer to MODE_IGNORED.
     */
    public static final int MODE_IGNORED = 1;

    /**
     * Result from {@link #checkOpNoThrow}, {@link #noteOpNoThrow}, {@link #startOpNoThrow}: the
     * given caller is not allowed to perform the given operation, and this attempt should
     * cause it to have a fatal error, typically a {@link SecurityException}.
     */
    public static final int MODE_ERRORED = 2;

    /**
     * Result from {@link #checkOp}, {@link #noteOp}, {@link #startOp}: the given caller should
     * use its default security check.  This mode is not normally used; it should only be used
     * with appop permissions, and callers must explicitly check for it and deal with it.
     */
    public static final int MODE_DEFAULT = 3;

    /**
     * Special mode that means "allow only when app is in foreground."  This is <b>not</b>
     * returned from {@link #unsafeCheckOp}, {@link #noteOp}, {@link #startOp}.  Rather,
     * {@link #unsafeCheckOp} will always return {@link #MODE_ALLOWED} (because it is always
     * possible for it to be ultimately allowed, depending on the app's background state),
     * and {@link #noteOp} and {@link #startOp} will return {@link #MODE_ALLOWED} when the app
     * being checked is currently in the foreground, otherwise {@link #MODE_IGNORED}.
     *
     * <p>The only place you will this normally see this value is through
     * {@link #unsafeCheckOpRaw}, which returns the actual raw mode of the op.  Note that because
     * you can't know the current state of the app being checked (and it can change at any
     * point), you can only treat the result here as an indication that it will vary between
     * {@link #MODE_ALLOWED} and {@link #MODE_IGNORED} depending on changes in the background
     * state of the app.  You thus must always use {@link #noteOp} or {@link #startOp} to do
     * the actual check for access to the op.</p>
     */
    public static final int MODE_FOREGROUND = 4;

    /**
     * UNISOC: CTA Feature:
     * Result from {@link #checkOpNoThrow}, {@link #noteOpNoThrow}, {@link #startOpNoThrow}: the given caller is
     * not allowed to perform the given operation. It was set by user deny and remember forever.
     * @hide
     */
    public static final int MODE_DENY_FOREVER = 5;

在AppOpsService中,RECORD_AUDIO操作需要PROCESS_CAPABILITY_FOREGROUND_MICROPHONE能力。如果没有这个能力,操作就会被忽略(MODE_IGNORED)。PROCESS_CAPABILITY_FOREGROUND_MICROPHONE是通过前台服务类型FOREGROUND_SERVICE_TYPE_MICROPHONE来设置的。

下面增加包名过滤,对app允许录音

frameworks/base/services/core/java/com/android/server/appop/AppOpsService.java

复制代码
        int evalMode(int op, int mode) {
            if (mode == MODE_FOREGROUND) {
                if (appWidgetVisible) {
                    return MODE_ALLOWED;
                } else if (mActivityManagerInternal != null
                        && mActivityManagerInternal.isPendingTopUid(uid)) {
                    return MODE_ALLOWED;
                } else if (mActivityManagerInternal != null
                        && mActivityManagerInternal.isTempAllowlistedForFgsWhileInUse(uid)) {
                    return MODE_ALLOWED;
                } else if (state <= UID_STATE_TOP) {
                    // process is in TOP.
                    return MODE_ALLOWED;
                } else if (state <= AppOpsManager.resolveFirstUnrestrictedUidState(op)) {
                    // process is in foreground, check its capability.
                    switch (op) {
                        case AppOpsManager.OP_FINE_LOCATION:
                        case AppOpsManager.OP_COARSE_LOCATION:
                        case AppOpsManager.OP_MONITOR_LOCATION:
                        case AppOpsManager.OP_MONITOR_HIGH_POWER_LOCATION:
                            if ((capability & PROCESS_CAPABILITY_FOREGROUND_LOCATION) != 0) {
                                return MODE_ALLOWED;
                            } else {
                                Slog.d(TAG, "evalMode-op:" + op + " ,mode: " + mode
                                        + " ,capability: " + capability);
                                uniEvent.putInt("evalMode-op", op)
                                        .putInt("mode", mode)
                                        .putInt("capability", capability);
                                UniView.report(uniEvent);
                                return MODE_IGNORED;
                            }
                        case OP_CAMERA:
                            if ((capability & PROCESS_CAPABILITY_FOREGROUND_CAMERA) != 0) {
                                return MODE_ALLOWED;
                            } else {
                                Slog.d(TAG, "CAMERAR-evalMode-op:" + op + " ,mode: " + mode
                                        + " ,capability: " + capability);
                                uniEvent.putInt("CAMERA-evalMode-op", op)
                                        .putInt("CAMERA-mode", mode)
                                        .putInt("CAMERA-capability", capability);
                                UniView.report(uniEvent);
                                return MODE_IGNORED;
                            }
                        case OP_RECORD_AUDIO:
                            if ((capability & PROCESS_CAPABILITY_FOREGROUND_MICROPHONE) != 0) {
                                return MODE_ALLOWED;
                            } else {
                                // Exempt intercom app from RECORD_AUDIO capability check
                                // Allow xxx.xx.xxx to record audio even without foreground microphone capability
                                // Note: xxx.xx.xxx typically has uid 10120, but we check by attempting to get package name
                                try {
                                    String[] packages = mContext.getPackageManager().getPackagesForUid(uid);
                                    if (packages != null) {
                                        for (String pkg : packages) {
                                            if ("xxx.xxx.xx".equals(pkg)) {
                                                Slog.i(TAG, "lichang Exempting intercom app " + pkg + " (uid:" + uid + ") from RECORD_AUDIO capability check");
                                                return MODE_ALLOWED;
                                            }
                                        }
                                    }
                                } catch (Exception e) {
                                    Slog.w(TAG, "lichang Failed to get packages for uid " + uid, e);
                                }
                                Slog.d(TAG, "RECORD_AUDIO-evalMode-op:" + op + " ,mode: " + mode
                                        + " ,capability: " + capability);
                                uniEvent.putInt("RECORD_AUDIO-evalMode-op", op)
                                        .putInt("RECORD_AUDIO-mode", mode)
                                        .putInt("RECORD_AUDIO-capability", capability);
                                UniView.report(uniEvent);
                                return MODE_IGNORED;
                            }
                        default:
                            return MODE_ALLOWED;
                    }
                } else {
                    Slog.d(TAG, "PNForegroundeval-evalMode-op:" + op + " ,mode: " + mode
                            + " ,capability: " + capability);
                    uniEvent.putInt("PNForegroundeval-evalMode-op", op)
                            .putInt("PNForegroundeval-mode", mode);
                    UniView.report(uniEvent);
                    // process is not in foreground.
                    return MODE_IGNORED;
                }
            }
            return mode;
        }

我查了下,日志提到的 capability 为 8,在源码中的定义是

frameworks/base/core/java/android/app/ActivityManager.java

复制代码
    /** @hide Process does not have any capability */
    @TestApi
    public static final int PROCESS_CAPABILITY_NONE = 0;

    /** @hide Process can access location while in foreground */
    @TestApi
    public static final int PROCESS_CAPABILITY_FOREGROUND_LOCATION = 1 << 0;

    /** @hide Process can access camera while in foreground */
    @TestApi
    public static final int PROCESS_CAPABILITY_FOREGROUND_CAMERA = 1 << 1;

    /** @hide Process can access microphone while in foreground */
    @TestApi
    public static final int PROCESS_CAPABILITY_FOREGROUND_MICROPHONE = 1 << 2;

    /** @hide Process can access network despite any power saving resrictions */
    @TestApi
    public static final int PROCESS_CAPABILITY_NETWORK = 1 << 3;

对应的条件为

capability & PROCESS_CAPABILITY_FOREGROUND_MICROPHONE

= 8 & 4

= 0b1000 & 0b0100

= 0
& 表示"按位与":只有当两个对应位都为 1 时,结果位才是 1;否则为 0。

也就是系统只授予了 网络豁免能力(NETWORK) ,但**没有授予前台麦克风能力。**其实这个问题应该app去修改,客户协调不了只能系统去改。

正常应该是把录音进程放到前台服务实现。

相关推荐
一叶知秋yyds16 小时前
Ubuntu 虚拟机安装 OpenClaw 完整流程
linux·运维·ubuntu·openclaw
楠奕18 小时前
CentOS7安装GoldenDB单机搭建及常见报错解决方案
linux·运维·服务器
恋猫de小郭19 小时前
Android 上为什么主题字体对 Flutter 不生效,对 Compose 生效?Flutter 中文字体问题修复
android·前端·flutter
三少爷的鞋19 小时前
不要让调用方承担你本该承担的复杂度 —— Android Data 层设计原则
android
李李李勃谦19 小时前
Flutter 框架跨平台鸿蒙开发 - 创意灵感收集
android·flutter·harmonyos
剑锋所指,所向披靡!19 小时前
Linux常用指令(2)
linux·运维·服务器
不愿透露姓名的大鹏19 小时前
Oracle归档日志爆满急救指南
linux·数据库·oracle·dba
W.W.H.20 小时前
嵌入式常见的面试题1
linux·网络·经验分享·网络协议·tcp/ip
木白CPP20 小时前
DMA-Buffer内核驱动API文档
linux