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去修改,客户协调不了只能系统去改。

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

相关推荐
程序 代码狂人2 小时前
CentOS7中有趣的yum源(彩蛋)-----第二期
linux·运维·服务器
Y‍waiX‍‍‮‪‎⁠‌‫‎‌‫‬2 小时前
CentOS7(Linux)从系统安装到环境搭建
linux·运维·服务器
若风的雨2 小时前
【 ARMv8多核处理器启动方案设计】
linux·arm开发
Minilinux20183 小时前
Android系列之 屏幕触控机制(一)
android·屏幕触控·andorid touch·viewroot
皓月盈江3 小时前
MoonBit国产编程语言创建新包和使用新包
linux·moonbit·国产编程语言·moonbit教程·moonbit创建包·moonbit使用包
冰语竹3 小时前
Android学习-随笔(安装后设置路径)
android·学习
小白郭莫搞科技3 小时前
鸿蒙跨端框架Flutter学习:ListView卡片样式详解
linux·服务器·windows
栈低来信3 小时前
Linux设备模型
linux
晚风吹长发3 小时前
初步了解Linux中的信号捕捉
linux·运维·服务器·c++·算法·进程·x信号