while-in-use权限限制
为了帮助保护用户隐私,Android 11(API 级别 30)对前台服务何时可以访问设备的位置、摄像头或麦克风进行了限制。 当您的应用程序在后台运行时启动前台服务时,前台服务有以下限制:
- 除非用户已向您的应用授予 ACCESS_BACKGROUND_LOCATION 权限,否则前台服务无法访问位置。
- 前台服务无法访问麦克风或摄像头。
确定您的应用程序中哪些服务受到影响
测试您的应用程序时,启动其前台服务。 如果启动的服务限制了对位置、麦克风和摄像头的访问,Logcat 中会显示以下消息:
Foreground service started from background can not have location/camera/microphone access: service SERVICE_NAME
限制原理
FGS 有两个限制:
在 R 中,mAllowWhileInUsePermissionInFgs 是允许在前台服务中使用 while-in-use 权限。 从后台启动的 FGS 中的使用中权限可能会受到限制。
在S中,mAllowStartForeground是允许FGS是否startForeground。 从后台启动的服务可能不会成为 FGS。具体见后台启动FGS限制
启动或绑定或调用startForeground时会调用setFgsRestrictionLocked方法去校验上面的两个FGS限制。
private void setFgsRestrictionLocked(String callingPackage,
int callingPid, int callingUid, Intent intent, ServiceRecord r, int userId,
boolean allowBackgroundActivityStarts) {
.......
if (!r.mAllowWhileInUsePermissionInFgs
|| (r.mAllowStartForeground == REASON_DENIED)) {
// while in use权限校验
final @ReasonCode int allowWhileInUse = shouldAllowFgsWhileInUsePermissionLocked(
callingPackage, callingPid, callingUid, r, allowBackgroundActivityStarts);
if (!r.mAllowWhileInUsePermissionInFgs) {
r.mAllowWhileInUsePermissionInFgs = (allowWhileInUse != REASON_DENIED);
}
// 是否允许后台启动FGS校验
.......
}
如果前台服务不是从 TOP 进程启动的,则不允许它在使用中访问位置/相机/麦克风。
if (!r.mAllowWhileInUsePermissionInFgs) {
Slog.w(TAG,
"Foreground service started from background can not have "
+ "location/camera/microphone access: service "
+ r.shortInstanceName);
}
限制豁免
在某些情况下,即使应用程序在后台运行时启动了前台服务,它仍然可以在应用程序在前台运行时(“使用中”)访问位置、摄像头和麦克风信息。 在这些相同的情况下,如果该服务声明了前台服务类型的位置并由具有 ACCESS_BACKGROUND_LOCATION 权限的应用程序启动,则该服务可以一直访问位置信息,即使该应用程序在后台运行时也是如此。
是否应允许 FGS 中的使用中权限。 一个典型的 BG 启动的 FGS 不允许有 while-in-use 权限
该服务由前台应用启动
跟 后台启动FGS限制-应用程序在前台 一样
- REASON_PROC_STATE_PERSISTENT
- REASON_PROC_STATE_PERSISTENT_UI
- REASON_PROC_STATE_TOP
private @ReasonCode int shouldAllowFgsWhileInUsePermissionLocked(String callingPackage,int callingPid, int callingUid, @Nullable ServiceRecord targetService,
boolean allowBackgroundActivityStarts) {
int ret = REASON_DENIED;
final int uidState = mAm.getUidStateLocked(callingUid);
if (ret == REASON_DENIED) {
// Is the calling UID at PROCESS_STATE_TOP or above?
if (uidState <= PROCESS_STATE_TOP) {
ret = getReasonCodeFromProcState(uidState);
}
}
该服务由可见应用启动
- REASON_UID_VISIBLE
if (ret == REASON_DENIED) {
// Does the calling UID have any visible activity?
final boolean isCallingUidVisible = mAm.mAtmInternal.isUidForeground(callingUid);
if (isCallingUidVisible) {
ret = REASON_UID_VISIBLE;
}
}
该服务通过与通知交互来启动
该服务作为从不同的可见应用程序发送的 PendingIntent 启动
- REASON_START_ACTIVITY_FLAG
if (ret == REASON_DENIED) {
// Is the allow activity background start flag on?
if (allowBackgroundActivityStarts) {
ret = REASON_START_ACTIVITY_FLAG;
}
}
该服务由系统组件启动(root/system/nfc/shell)
- REASON_SYSTEM_UID
if (ret == REASON_DENIED) {
boolean isCallerSystem = false;
final int callingAppId = UserHandle.getAppId(callingUid);
switch (callingAppId) {
case ROOT_UID:
case SYSTEM_UID:
case NFC_UID:
case SHELL_UID:
isCallerSystem = true;
break;
default:
isCallerSystem = false;
break;
}
if (isCallerSystem) {
ret = REASON_SYSTEM_UID;
}
}
该服务由具有 START_ACTIVITIES_FROM_BACKGROUND 特权权限的应用程序启动
- REASON_INSTR_BACKGROUND_ACTIVITY_PERMISSION
- REASON_BACKGROUND_ACTIVITY_PERMISSION
if (ret == REASON_DENIED) {
if (targetService != null && targetService.app != null) {
ActiveInstrumentation instr = targetService.app.getActiveInstrumentation();
if (instr != null && instr.mHasBackgroundActivityStartsPermission) {
ret = REASON_INSTR_BACKGROUND_ACTIVITY_PERMISSION;
}
}
}
if (ret == REASON_DENIED) {
if (mAm.checkPermission(START_ACTIVITIES_FROM_BACKGROUND, callingPid, callingUid)
== PERMISSION_GRANTED) {
ret = REASON_BACKGROUND_ACTIVITY_PERMISSION;
}
}
该服务由系统Captions Service/Attention Service应用启动
- REASON_ALLOWLISTED_PACKAGE
if (ret == REASON_DENIED) {
if (verifyPackage(callingPackage, callingUid)) {
final boolean isAllowedPackage =
mAllowListWhileInUsePermissionInFgs.contains(callingPackage);
if (isAllowedPackage) {
ret = REASON_ALLOWLISTED_PACKAGE;
}
} else {
EventLog.writeEvent(0x534e4554, "215003903", callingUid,
"callingPackage:" + callingPackage + " does not belong to callingUid:"
+ callingUid);
}
}
private void setAllowListWhileInUsePermissionInFgs() {
final String attentionServicePackageName =
mAm.mContext.getPackageManager().getAttentionServicePackageName();
if (!TextUtils.isEmpty(attentionServicePackageName)) {
mAllowListWhileInUsePermissionInFgs.add(attentionServicePackageName);
}
final String systemCaptionsServicePackageName =
mAm.mContext.getPackageManager().getSystemCaptionsServicePackageName();
if (!TextUtils.isEmpty(systemCaptionsServicePackageName)) {
mAllowListWhileInUsePermissionInFgs.add(systemCaptionsServicePackageName);
}
}
该服务由应用程序启动,该应用程序是在设备所有者模式下运行的设备策略控制器。
- REASON_DEVICE_OWNER
if (ret == REASON_DENIED) {
// Is the calling UID a device owner app?
final boolean isDeviceOwner = mAm.mInternal.isDeviceOwner(callingUid);
if (isDeviceOwner) {
ret = REASON_DEVICE_OWNER;
}
}
该服务由MediaSeesion相关的应用启动
if (ret == REASON_DENIED) {
if (mAm.mInternal.isTempAllowlistedForFgsWhileInUse(callingUid)) {
return REASON_TEMP_ALLOWED_WHILE_IN_USE;
}
}
if (canAllowWhileInUse) {
mActivityManagerLocal.tempAllowWhileInUsePermissionInFgs(targetUid,
MediaSessionDeviceConfig
.getMediaSessionCallbackFgsWhileInUseTempAllowDurationMs());
}
关于areBackgroundActivityStartsAllowed系列
该服务由某个 Activity 刚在不久前启动/结束的应用启动
该服务由运行后台启动activity特权的Active Instrumentation的应用启动
该服务由在前台任务的返回栈中拥有 Activity的应用启动
该服务由某个服务被另一个可见应用绑定的应用启动
…
- REASON_ACTIVITY_STARTER
if (ret == REASON_DENIED) {
final Integer allowedType = mAm.mProcessList.searchEachLruProcessesLOSP(false, pr -> {
if (pr.uid == callingUid) {
if (pr.getWindowProcessController().areBackgroundFgsStartsAllowed()) {
return REASON_ACTIVITY_STARTER;
}
}
return null;
});
if (allowedType != null) {
ret = allowedType;
}
}