用戶控制: 用戶在長(zhǎng)時(shí)間運(yùn)行的應(yīng)用程序上獲得更多透明度和控制權(quán):
- 前臺(tái)服務(wù)仍然需要包含通知浊竟,并且應(yīng)用程序必須請(qǐng)求權(quán)限才能顯示通知墨礁。
- FGS 通知現(xiàn)在可以被用戶關(guān)閉而不影響 FGS
- 用戶可以在任務(wù)管理器中查看長(zhǎng)時(shí)間運(yùn)行的應(yīng)用列表
- 任務(wù)管理器還允許用戶停止應(yīng)用程序
通知權(quán)限
介紹
- android T 之前的通知:默認(rèn)允許通知推送,需要用戶跳轉(zhuǎn)至多級(jí)設(shè)置頁(yè)面去設(shè)置通知權(quán)限誉尖。
- android T新增前臺(tái)服務(wù)的通知權(quán)限(運(yùn)行時(shí)權(quán)限):
使用
- android 13及以上: 應(yīng)用自行控制權(quán)限對(duì)話框顯示時(shí)間
- android 12L 及以下: 通常應(yīng)用啟動(dòng)時(shí)彈框
豁免:與媒體會(huì)話有關(guān)的通知不受此行為變更的影響。
代碼
在ServiceRecord的構(gòu)造方法中會(huì)回調(diào)updateFgsHasNotificationPermission方法馍盟,去異步(避免死鎖)從NMS中去獲取此app是否有權(quán)限發(fā)送通知缘圈;ServiceRecord僅記錄,具體權(quán)限校驗(yàn)在NMS中野崇。
// ServiceRecord.java
// Whether FGS package has permissions to show notifications.
boolean mFgsHasNotificationPermission;
private void updateFgsHasNotificationPermission() {
// Do asynchronous communication with notification manager to avoid deadlocks.
final String localPackageName = packageName;
final int appUid = appInfo.uid;
ams.mHandler.post(new Runnable() {
public void run() {
NotificationManagerInternal nm = LocalServices.getService(
NotificationManagerInternal.class);
if (nm == null) {
return;
}
// Record whether the package has permission to notify the user
mFgsHasNotificationPermission = nm.areNotificationsEnabledForPackage(
localPackageName, appUid);
}
});
}
前臺(tái)服務(wù)任務(wù)管理器
介紹
前臺(tái)任務(wù)管理器
無(wú)論app的目標(biāo)sdk版本是多少称开,android 13上都允許用戶從抽屜式通知欄中停止前臺(tái)服務(wù)(整個(gè)應(yīng)用)。停止原因在ApplicationExitInfo表現(xiàn)為REASON_USER_REQUESTED。
豁免
以下應(yīng)用可以運(yùn)行前臺(tái)服務(wù)鳖轰,而完全不會(huì)顯示在任務(wù)管理器中:
- 系統(tǒng)級(jí)應(yīng)用
- 安全應(yīng)用清酥,即具有 ROLE_EMERGENCY 角色的應(yīng)用
- 處于演示模式的設(shè)備上的應(yīng)用
當(dāng)以下類型的應(yīng)用運(yùn)行前臺(tái)服務(wù)時(shí),它們會(huì)顯示在 FGS 任務(wù)管理器中蕴侣,但應(yīng)用名稱旁邊沒(méi)有可以供用戶按的停止按鈕:
- 設(shè)備所有者應(yīng)用
- 資料所有者應(yīng)用
- 常駐應(yīng)用
- 具有 ROLE_DIALER 角色的應(yīng)用
- 處于doze白名單應(yīng)用
代碼
// FgsManagerController.kt
fun updateUiControl() {
uiControl = when (activityManager.getBackgroundRestrictionExemptionReason(uid)) {
// 不顯示在任務(wù)管理器中
PowerExemptionManager.REASON_SYSTEM_UID,
PowerExemptionManager.REASON_DEVICE_DEMO_MODE -> UIControl.HIDE_ENTRY
// 顯示在任務(wù)管理器焰轻,但是沒(méi)有停止按鈕
PowerExemptionManager.REASON_ALLOWLISTED_PACKAGE,
PowerExemptionManager.REASON_DEVICE_OWNER,
PowerExemptionManager.REASON_DISALLOW_APPS_CONTROL,
PowerExemptionManager.REASON_DPO_PROTECTED_APP,
PowerExemptionManager.REASON_PROFILE_OWNER,
PowerExemptionManager.REASON_PROC_STATE_PERSISTENT,
PowerExemptionManager.REASON_PROC_STATE_PERSISTENT_UI,
PowerExemptionManager.REASON_ROLE_DIALER,
PowerExemptionManager.REASON_SYSTEM_MODULE -> UIControl.HIDE_BUTTON
// 正常情況
else -> UIControl.NORMAL
}
uiControlInitialized = true
}
// AppRestrictionController.java
/**
* @return The reason code of whether or not the given UID should be exempted from background
* restrictions here.
*
* <p>
* Note: Call it with caution as it'll try to acquire locks in other services.
* </p>
*/
@ReasonCode
int getBackgroundRestrictionExemptionReason(int uid) {
// uid < 10000
if (UserHandle.isCore(uid)) {
return REASON_SYSTEM_UID;
}
// Whitelist system apps
if (isOnSystemDeviceIdleAllowlist(uid)) {
return REASON_SYSTEM_ALLOW_LISTED;
}
// Whitelist user apps , doze白名單,可聯(lián)系功耗同學(xué)添加
if (isOnDeviceIdleAllowlist(uid)) {
return REASON_ALLOWLISTED_PACKAGE;
}
// 暫無(wú)研究
final ActivityManagerInternal am = mInjector.getActivityManagerInternal();
if (am.isAssociatedCompanionApp(UserHandle.getUserId(uid), uid)) {
return REASON_COMPANION_DEVICE_MANAGER;
}
// 處于演示模式的設(shè)備上的應(yīng)用
if (UserManager.isDeviceInDemoMode(mContext) {
return REASON_DEVICE_DEMO_MODE;
}
final int userId = UserHandle.getUserId(uid);
if (mInjector.getUserManagerInternal()
.hasUserRestriction(UserManager.DISALLOW_APPS_CONTROL, userId)) {
return REASON_DISALLOW_APPS_CONTROL;
}
// 設(shè)備所有者
if (am.isDeviceOwner(uid)) {
return REASON_DEVICE_OWNER;
}
// 資料所有者
if (am.isProfileOwner(uid)) {
return REASON_PROFILE_OWNER;
}
// persistent常駐應(yīng)用
final int uidProcState = am.getUidProcessState(uid);
if (uidProcState <= PROCESS_STATE_PERSISTENT) {
return REASON_PROC_STATE_PERSISTENT;
} else if (uidProcState <= PROCESS_STATE_PERSISTENT_UI) {
return REASON_PROC_STATE_PERSISTENT_UI;
}
final String[] packages = mInjector.getPackageManager().getPackagesForUid(uid);
if (packages != null) {
final AppOpsManager appOpsManager = mInjector.getAppOpsManager();
final PackageManagerInternal pm = mInjector.getPackageManagerInternal();
final AppStandbyInternal appStandbyInternal = mInjector.getAppStandbyInternal();
for (String pkg : packages) {
// VPN
if (appOpsManager.checkOpNoThrow(AppOpsManager.OP_ACTIVATE_VPN,
uid, pkg) == AppOpsManager.MODE_ALLOWED) {
return REASON_OP_ACTIVATE_VPN;
} else if (appOpsManager.checkOpNoThrow(AppOpsManager.OP_ACTIVATE_PLATFORM_VPN,
uid, pkg) == AppOpsManager.MODE_ALLOWED) {
return REASON_OP_ACTIVATE_PLATFORM_VPN;
} else if (isSystemModule(pkg)) {
return REASON_SYSTEM_MODULE;
} else if (isCarrierApp(pkg)) {
return REASON_CARRIER_PRIVILEGED_APP;
} else if (isExemptedFromSysConfig(pkg)) {
return REASON_SYSTEM_ALLOW_LISTED;
} else if (mConstantsObserver.mBgRestrictionExemptedPackages.contains(pkg)) {
return REASON_SYSTEM_ALLOW_LISTED;
} else if (pm.isPackageStateProtected(pkg, userId)) {
return REASON_DPO_PROTECTED_APP;
} else if (appStandbyInternal.isActiveDeviceAdmin(pkg, userId)) {
return REASON_ACTIVE_DEVICE_ADMIN;
}
}
}
// dialer角色
if (isRoleHeldByUid(RoleManager.ROLE_DIALER, uid)) {
return REASON_ROLE_DIALER;
}
if (isRoleHeldByUid(RoleManager.ROLE_EMERGENCY, uid)) {
return REASON_ROLE_EMERGENCY;
}
return REASON_DENIED;
}
測(cè)試
adb shell dumpsys deviceidle // 查看是否在doze白名單昆雀,可以不顯示停止按鈕
adb shell cmd activity stop-app PACKAGE_NAME
監(jiān)控長(zhǎng)期運(yùn)行的前臺(tái)服務(wù)
介紹
長(zhǎng)時(shí)間運(yùn)行服務(wù)通知
如果系統(tǒng)檢測(cè)到您的應(yīng)用長(zhǎng)時(shí)間運(yùn)行某項(xiàng)前臺(tái)服務(wù)(在 24 小時(shí)的時(shí)間段內(nèi)至少運(yùn)行 20 小時(shí))辱志,便會(huì)發(fā)送通知邀請(qǐng)用戶與前臺(tái)服務(wù) (FGS) 任務(wù)管理器互動(dòng)。該通知包含以下內(nèi)容:
APP is running in the background for a long time. Tap to review.
如果前臺(tái)服務(wù)的類型為 FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK 或 FOREGROUND_SERVICE_TYPE_LOCATION狞膘,系統(tǒng)將不會(huì)顯示此通知揩懒。
代碼
- 谷歌提交記錄
commit 64ac1a923065d38fa33789b315c716f4c93312fc
Author: Jing Ji <jji@google.com>
Date: Sat Dec 11 03:14:45 2021 -0800
Monitor long-running foreground services
If a certain package has foreground services running for a long time,
say the accumulated durations over last X hours are more than Y hours,
system will post a notification to remind the user.
Some type of apps are subjected to be exempted, i.e. if it's already
in the device idle allowlist. More exemption could be added in
the follow-up CLs.
Bug: 200326767
Bug: 203105544
Test: atest FrameworksMockingServicesTests:BackgroundRestrictionTest
Change-Id: I3a8f34c33e7a533240abc7cf4fa569a0956eec73
- 主要邏輯在新增的類:
// 監(jiān)控濫用(長(zhǎng)時(shí)間運(yùn)行)FGS 的跟蹤器
frameworks/base/services/core/java/com/android/server/am/AppFGSTracker.java
顯示長(zhǎng)時(shí)間運(yùn)行通知
static final long DEFAULT_BG_FGS_LONG_RUNNING_THRESHOLD = 20 * ONE_HOUR;
對(duì)應(yīng)的流程如下:
移除長(zhǎng)時(shí)間運(yùn)行通知
若FGS已經(jīng)停止運(yùn)行則取消該通知