这个问题是客户要求预置的一个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去修改,客户协调不了只能系统去改。
正常应该是把录音进程放到前台服务实现。