android T 前台Service

获取android 13

用户控制: 用户在长时间运行的应用程序上获得更多透明度和控制权:

  • 前台服务仍然需要包含通知,并且应用程序必须请求权限才能显示通知。
  • FGS 通知现在可以被用户关闭而不影响 FGS
  • 用户可以在任务管理器中查看长时间运行的应用列表
    • 任务管理器还允许用户停止应用程序

通知权限

在这里插入图片描述

介绍

权限定义

  1. android T 之前的通知:默认允许通知推送,需要用户跳转至多级设置页面去设置通知权限。
  2. android T新增前台服务的通知权限(运行时权限):

使用

权限使用

  1. android 13及以上: 应用自行控制权限对话框显示时间
  2. android 12L 及以下: 通常应用启动时弹框

豁免:与媒体会话有关的通知不受此行为变更的影响。

代码

在ServiceRecord的构造方法中会回调updateFgsHasNotificationPermission方法,去异步(避免死锁)从NMS中去获取此app是否有权限发送通知;ServiceRecord仅记录,具体权限校验在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);
        }
    });
}

前台服务任务管理器

介绍

前台任务管理器
无论app的目标sdk版本是多少,android 13上都允许用户从抽屉式通知栏中停止前台服务(整个应用)。停止原因在ApplicationExitInfo表现为REASON_USER_REQUESTED。

在这里插入图片描述在这里插入图片描述

豁免

以下应用可以运行前台服务,而完全不会显示在任务管理器中:

  • 系统级应用
  • 安全应用,即具有 ROLE_EMERGENCY 角色的应用
  • 处于演示模式的设备上的应用

当以下类型的应用运行前台服务时,它们会显示在 FGS 任务管理器中,但应用名称旁边没有可以供用户按的停止按钮:

扫描二维码关注公众号,回复: 15059238 查看本文章
  • 设备所有者应用
  • 资料所有者应用
  • 常驻应用
  • 具有 ROLE_DIALER 角色的应用
    • 处于doze白名单应用

代码

// FgsManagerController.kt
fun updateUiControl() {
    
    
    uiControl = when (activityManager.getBackgroundRestrictionExemptionReason(uid)) {
    
    
        // 不显示在任务管理器中
        PowerExemptionManager.REASON_SYSTEM_UID,
        PowerExemptionManager.REASON_DEVICE_DEMO_MODE -> UIControl.HIDE_ENTRY
        // 显示在任务管理器,但是没有停止按钮
        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白名单,可联系功耗同学添加
    if (isOnDeviceIdleAllowlist(uid)) {
    
    
        return REASON_ALLOWLISTED_PACKAGE;
    }
    // 暂无研究
    final ActivityManagerInternal am = mInjector.getActivityManagerInternal();
    if (am.isAssociatedCompanionApp(UserHandle.getUserId(uid), uid)) {
    
    
        return REASON_COMPANION_DEVICE_MANAGER;
    }
    // 处于演示模式的设备上的应用
    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;
    }
    // 设备所有者
    if (am.isDeviceOwner(uid)) {
    
    
        return REASON_DEVICE_OWNER;
    }
    // 资料所有者
    if (am.isProfileOwner(uid)) {
    
    
        return REASON_PROFILE_OWNER;
    }
    // persistent常驻应用
    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;
}

测试

adb shell dumpsys deviceidle // 查看是否在doze白名单,可以不显示停止按钮
adb shell cmd activity stop-app PACKAGE_NAME

监控长期运行的前台服务

介绍

长时间运行服务通知
如果系统检测到您的应用长时间运行某项前台服务(在 24 小时的时间段内至少运行 20 小时),便会发送通知邀请用户与前台服务 (FGS) 任务管理器互动。该通知包含以下内容:

APP is running in the background for a long time. Tap to review.

如果前台服务的类型为 FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK 或 FOREGROUND_SERVICE_TYPE_LOCATION,系统将不会显示此通知。

代码

  1. 谷歌提交记录
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
  1. 主要逻辑在新增的类:
//  监控滥用(长时间运行)FGS 的跟踪器
frameworks/base/services/core/java/com/android/server/am/AppFGSTracker.java 

显示长时间运行通知

static final long DEFAULT_BG_FGS_LONG_RUNNING_THRESHOLD = 20 * ONE_HOUR;

对应的流程如下:
在这里插入图片描述

移除长时间运行通知

若FGS已经停止运行则取消该通知
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/xiaoyantan/article/details/124436285