From 8da64cacf3383e1462045a790a97e24b975f4e4f Mon Sep 17 00:00:00 2001 From: Sudheer Shanka Date: Thu, 26 May 2022 10:28:39 -0700 Subject: [PATCH] Allow exemption of roles/permissions from broadcast response events. Bug: 206518114 Test: atest tests/tests/app.usage/src/android/app/usage/cts/BroadcastResponseStatsTest.java Change-Id: Iaae9775491caeae95ff04f4fd0fcb83b064a0f56 --- .../server/usage/AppStandbyInternal.java | 14 ++ .../server/usage/AppStandbyController.java | 73 +++++++++++ .../usage/BroadcastResponseStatsTracker.java | 124 +++++++++++++++++- .../server/usage/UsageStatsService.java | 1 + 4 files changed, 211 insertions(+), 1 deletion(-) diff --git a/apex/jobscheduler/framework/java/com/android/server/usage/AppStandbyInternal.java b/apex/jobscheduler/framework/java/com/android/server/usage/AppStandbyInternal.java index 88acf65073334..c43c832992cf9 100644 --- a/apex/jobscheduler/framework/java/com/android/server/usage/AppStandbyInternal.java +++ b/apex/jobscheduler/framework/java/com/android/server/usage/AppStandbyInternal.java @@ -280,6 +280,20 @@ public interface AppStandbyInternal { */ boolean shouldNoteResponseEventForAllBroadcastSessions(); + /** + * Returns the list of roles whose holders are exempted from the requirement of starting + * a response event after receiving a broadcast. + */ + @NonNull + List getBroadcastResponseExemptedRoles(); + + /** + * Returns the list of permissions whose holders are exempted from the requirement of starting + * a response event after receiving a broadcast. + */ + @NonNull + List getBroadcastResponseExemptedPermissions(); + /** * Return the last known value corresponding to the {@code key} from * {@link android.provider.DeviceConfig#NAMESPACE_APP_STANDBY} in AppStandbyController. diff --git a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java index c1f31e971ec5b..9e3e3553c1256 100644 --- a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java +++ b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java @@ -100,6 +100,7 @@ import android.os.UserHandle; import android.provider.DeviceConfig; import android.provider.Settings.Global; import android.telephony.TelephonyManager; +import android.text.TextUtils; import android.util.ArrayMap; import android.util.ArraySet; import android.util.IndentingPrintWriter; @@ -420,6 +421,26 @@ public class AppStandbyController volatile boolean mNoteResponseEventForAllBroadcastSessions = ConstantsObserver.DEFAULT_NOTE_RESPONSE_EVENT_FOR_ALL_BROADCAST_SESSIONS; + /** + * List of roles whose holders are exempted from the requirement of starting + * a response event after receiving a broadcast. + * + * The list of roles will be separated by '|' in the string. + */ + volatile String mBroadcastResponseExemptedRoles = + ConstantsObserver.DEFAULT_BROADCAST_RESPONSE_EXEMPTED_ROLES; + volatile List mBroadcastResponseExemptedRolesList = Collections.EMPTY_LIST; + + /** + * List of permissions whose holders are exempted from the requirement of starting + * a response event after receiving a broadcast. + * + * The list of permissions will be separated by '|' in the string. + */ + volatile String mBroadcastResponseExemptedPermissions = + ConstantsObserver.DEFAULT_BROADCAST_RESPONSE_EXEMPTED_PERMISSIONS; + volatile List mBroadcastResponseExemptedPermissionsList = Collections.EMPTY_LIST; + /** * Map of last known values of keys in {@link DeviceConfig#NAMESPACE_APP_STANDBY}. * @@ -1959,6 +1980,18 @@ public class AppStandbyController return mNoteResponseEventForAllBroadcastSessions; } + @Override + @NonNull + public List getBroadcastResponseExemptedRoles() { + return mBroadcastResponseExemptedRolesList; + } + + @Override + @NonNull + public List getBroadcastResponseExemptedPermissions() { + return mBroadcastResponseExemptedPermissionsList; + } + @Override @Nullable public String getAppStandbyConstant(@NonNull String key) { @@ -2311,6 +2344,14 @@ public class AppStandbyController pw.print(mNoteResponseEventForAllBroadcastSessions); pw.println(); + pw.print(" mBroadcastResponseExemptedRoles"); + pw.print(mBroadcastResponseExemptedRoles); + pw.println(); + + pw.print(" mBroadcastResponseExemptedPermissions"); + pw.print(mBroadcastResponseExemptedPermissions); + pw.println(); + pw.println(); pw.print("mAppIdleEnabled="); pw.print(mAppIdleEnabled); pw.print(" mAllowRestrictedBucket="); @@ -2795,6 +2836,10 @@ public class AppStandbyController "broadcast_sessions_with_response_duration_ms"; private static final String KEY_NOTE_RESPONSE_EVENT_FOR_ALL_BROADCAST_SESSIONS = "note_response_event_for_all_broadcast_sessions"; + private static final String KEY_BROADCAST_RESPONSE_EXEMPTED_ROLES = + "brodacast_response_exempted_roles"; + private static final String KEY_BROADCAST_RESPONSE_EXEMPTED_PERMISSIONS = + "brodacast_response_exempted_permissions"; public static final long DEFAULT_CHECK_IDLE_INTERVAL_MS = COMPRESS_TIME ? ONE_MINUTE : 4 * ONE_HOUR; @@ -2837,6 +2882,11 @@ public class AppStandbyController 2 * ONE_MINUTE; public static final boolean DEFAULT_NOTE_RESPONSE_EVENT_FOR_ALL_BROADCAST_SESSIONS = true; + private static final String DEFAULT_BROADCAST_RESPONSE_EXEMPTED_ROLES = ""; + private static final String DEFAULT_BROADCAST_RESPONSE_EXEMPTED_PERMISSIONS = ""; + + private final TextUtils.SimpleStringSplitter mStringPipeSplitter = + new TextUtils.SimpleStringSplitter('|'); ConstantsObserver(Handler handler) { super(handler); @@ -2989,6 +3039,20 @@ public class AppStandbyController KEY_NOTE_RESPONSE_EVENT_FOR_ALL_BROADCAST_SESSIONS, DEFAULT_NOTE_RESPONSE_EVENT_FOR_ALL_BROADCAST_SESSIONS); break; + case KEY_BROADCAST_RESPONSE_EXEMPTED_ROLES: + mBroadcastResponseExemptedRoles = properties.getString( + KEY_BROADCAST_RESPONSE_EXEMPTED_ROLES, + DEFAULT_BROADCAST_RESPONSE_EXEMPTED_ROLES); + mBroadcastResponseExemptedRolesList = splitPipeSeparatedString( + mBroadcastResponseExemptedRoles); + break; + case KEY_BROADCAST_RESPONSE_EXEMPTED_PERMISSIONS: + mBroadcastResponseExemptedPermissions = properties.getString( + KEY_BROADCAST_RESPONSE_EXEMPTED_PERMISSIONS, + DEFAULT_BROADCAST_RESPONSE_EXEMPTED_PERMISSIONS); + mBroadcastResponseExemptedPermissionsList = splitPipeSeparatedString( + mBroadcastResponseExemptedPermissions); + break; default: if (!timeThresholdsUpdated && (name.startsWith(KEY_PREFIX_SCREEN_TIME_THRESHOLD) @@ -3003,6 +3067,15 @@ public class AppStandbyController } } + private List splitPipeSeparatedString(String string) { + final List values = new ArrayList<>(); + mStringPipeSplitter.setString(string); + while (mStringPipeSplitter.hasNext()) { + values.add(mStringPipeSplitter.next()); + } + return values; + } + private void updateTimeThresholds() { // Query the values as an atomic set. final DeviceConfig.Properties screenThresholdProperties = diff --git a/services/usage/java/com/android/server/usage/BroadcastResponseStatsTracker.java b/services/usage/java/com/android/server/usage/BroadcastResponseStatsTracker.java index 7e3990d959152..7d5be8ea9a79f 100644 --- a/services/usage/java/com/android/server/usage/BroadcastResponseStatsTracker.java +++ b/services/usage/java/com/android/server/usage/BroadcastResponseStatsTracker.java @@ -23,15 +23,23 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.UserIdInt; import android.app.ActivityManager.ProcessState; +import android.app.role.OnRoleHoldersChangedListener; +import android.app.role.RoleManager; import android.app.usage.BroadcastResponseStats; +import android.content.Context; +import android.content.pm.PackageManager; import android.os.SystemClock; import android.os.UserHandle; +import android.permission.PermissionManager; +import android.util.ArrayMap; import android.util.ArraySet; import android.util.LongArrayQueue; import android.util.Slog; import android.util.SparseArray; import com.android.internal.annotations.GuardedBy; +import com.android.internal.os.BackgroundThread; +import com.android.internal.util.CollectionUtils; import com.android.internal.util.IndentingPrintWriter; import java.lang.annotation.Retention; @@ -71,14 +79,33 @@ class BroadcastResponseStatsTracker { private SparseArray> mUserResponseStats = new SparseArray<>(); + /** + * Cache of package names holding exempted roles. + * + * Contains the mapping of userId -> {roleName -> } data. + */ + // TODO: Use SparseArrayMap to simplify the logic. + @GuardedBy("mLock") + private SparseArray>> mExemptedRoleHoldersCache = + new SparseArray<>(); + private final OnRoleHoldersChangedListener mRoleHoldersChangedListener = + this::onRoleHoldersChanged; + private AppStandbyInternal mAppStandby; private BroadcastResponseStatsLogger mLogger; + private RoleManager mRoleManager; BroadcastResponseStatsTracker(@NonNull AppStandbyInternal appStandby) { mAppStandby = appStandby; mLogger = new BroadcastResponseStatsLogger(); } + void onSystemServicesReady(Context context) { + mRoleManager = context.getSystemService(RoleManager.class); + mRoleManager.addOnRoleHoldersChangedListenerAsUser(BackgroundThread.getExecutor(), + mRoleHoldersChangedListener, UserHandle.ALL); + } + // TODO (206518114): Move all callbacks handling to a handler thread. void reportBroadcastDispatchEvent(int sourceUid, @NonNull String targetPackage, UserHandle targetUser, long idForResponseEvent, @@ -86,10 +113,19 @@ class BroadcastResponseStatsTracker { mLogger.logBroadcastDispatchEvent(sourceUid, targetPackage, targetUser, idForResponseEvent, timestampMs, targetUidProcState); if (targetUidProcState <= mAppStandby.getBroadcastResponseFgThresholdState()) { - // No need to track the broadcast response state while the target app is + // No need to track the broadcast response stats while the target app is // in the foreground. return; } + if (doesPackageHoldExemptedRole(targetPackage, targetUser)) { + // Package holds an exempted role, so no need to track the broadcast response stats. + return; + } + if (doesPackageHoldExemptedPermission(targetPackage, targetUser)) { + // Package holds an exempted permission, so no need to track the broadcast response + // stats + return; + } synchronized (mLock) { final ArraySet broadcastEvents = getOrCreateBroadcastEventsLocked(targetPackage, targetUser); @@ -253,6 +289,62 @@ class BroadcastResponseStatsTracker { } } + boolean doesPackageHoldExemptedRole(@NonNull String packageName, @NonNull UserHandle user) { + final List exemptedRoles = mAppStandby.getBroadcastResponseExemptedRoles(); + synchronized (mLock) { + for (int i = exemptedRoles.size() - 1; i >= 0; --i) { + final String roleName = exemptedRoles.get(i); + final List roleHolders = getRoleHoldersLocked(roleName, user); + if (CollectionUtils.contains(roleHolders, packageName)) { + return true; + } + } + } + return false; + } + + boolean doesPackageHoldExemptedPermission(@NonNull String packageName, + @NonNull UserHandle user) { + final List exemptedPermissions = mAppStandby + .getBroadcastResponseExemptedPermissions(); + for (int i = exemptedPermissions.size() - 1; i >= 0; --i) { + final String permissionName = exemptedPermissions.get(i); + if (PermissionManager.checkPackageNamePermission(permissionName, packageName, + user.getIdentifier()) == PackageManager.PERMISSION_GRANTED) { + return true; + } + } + return false; + } + + @GuardedBy("mLock") + @Nullable + private List getRoleHoldersLocked(@NonNull String roleName, @NonNull UserHandle user) { + ArrayMap> roleHoldersForUser = mExemptedRoleHoldersCache.get( + user.getIdentifier()); + if (roleHoldersForUser == null) { + roleHoldersForUser = new ArrayMap<>(); + mExemptedRoleHoldersCache.put(user.getIdentifier(), roleHoldersForUser); + } + List roleHolders = roleHoldersForUser.get(roleName); + if (roleHolders == null && mRoleManager != null) { + roleHolders = mRoleManager.getRoleHoldersAsUser(roleName, user); + roleHoldersForUser.put(roleName, roleHolders); + } + return roleHolders; + } + + private void onRoleHoldersChanged(@NonNull String roleName, @NonNull UserHandle user) { + synchronized (mLock) { + final ArrayMap> roleHoldersForUser = + mExemptedRoleHoldersCache.get(user.getIdentifier()); + if (roleHoldersForUser == null) { + return; + } + roleHoldersForUser.remove(roleName); + } + } + void onUserRemoved(@UserIdInt int userId) { synchronized (mLock) { mUserBroadcastEvents.remove(userId); @@ -260,6 +352,7 @@ class BroadcastResponseStatsTracker { for (int i = mUserResponseStats.size() - 1; i >= 0; --i) { mUserResponseStats.valueAt(i).remove(userId); } + mExemptedRoleHoldersCache.remove(userId); } } @@ -373,6 +466,8 @@ class BroadcastResponseStatsTracker { ipw.println(); dumpResponseStatsLocked(ipw); ipw.println(); + dumpRoleHoldersLocked(ipw); + ipw.println(); mLogger.dumpLogs(ipw); } @@ -417,5 +512,32 @@ class BroadcastResponseStatsTracker { } ipw.decreaseIndent(); } + + @GuardedBy("mLock") + private void dumpRoleHoldersLocked(@NonNull IndentingPrintWriter ipw) { + ipw.println("Role holders:"); + ipw.increaseIndent(); + for (int userIdx = 0; userIdx < mExemptedRoleHoldersCache.size(); ++userIdx) { + final int userId = mExemptedRoleHoldersCache.keyAt(userIdx); + final ArrayMap> roleHoldersForUser = + mExemptedRoleHoldersCache.valueAt(userIdx); + ipw.println("User " + userId + ":"); + ipw.increaseIndent(); + for (int roleIdx = 0; roleIdx < roleHoldersForUser.size(); ++roleIdx) { + final String roleName = roleHoldersForUser.keyAt(roleIdx); + final List holders = roleHoldersForUser.valueAt(roleIdx); + ipw.print(roleName + ": "); + for (int holderIdx = 0; holderIdx < holders.size(); ++holderIdx) { + if (holderIdx > 0) { + ipw.print(", "); + } + ipw.print(holders.get(holderIdx)); + } + ipw.println(); + } + ipw.decreaseIndent(); + } + ipw.decreaseIndent(); + } } diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java index 078177b3a89f2..ef13cd964f6c8 100644 --- a/services/usage/java/com/android/server/usage/UsageStatsService.java +++ b/services/usage/java/com/android/server/usage/UsageStatsService.java @@ -357,6 +357,7 @@ public class UsageStatsService extends SystemService implements getDpmInternal(); // initialize mShortcutServiceInternal getShortcutServiceInternal(); + mResponseStatsTracker.onSystemServicesReady(getContext()); if (ENABLE_KERNEL_UPDATES && KERNEL_COUNTER_FILE.exists()) { try {