Loading apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java +54 −7 Original line number Diff line number Diff line Loading @@ -27,6 +27,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.app.ActivityManager; import android.app.ActivityManagerInternal; import android.app.BackgroundStartPrivileges; import android.app.UserSwitchObserver; import android.app.job.JobInfo; import android.app.job.JobParameters; Loading Loading @@ -388,6 +389,12 @@ class JobConcurrencyManager { private final ArrayList<ContextAssignment> mRecycledPreferredUidOnly = new ArrayList<>(); private final ArrayList<ContextAssignment> mRecycledStoppable = new ArrayList<>(); private final AssignmentInfo mRecycledAssignmentInfo = new AssignmentInfo(); private final SparseIntArray mRecycledPrivilegedState = new SparseIntArray(); private static final int PRIVILEGED_STATE_UNDEFINED = 0; private static final int PRIVILEGED_STATE_NONE = 1; private static final int PRIVILEGED_STATE_BAL = 2; private static final int PRIVILEGED_STATE_TOP = 3; private final Pools.Pool<ContextAssignment> mContextAssignmentPool = new Pools.SimplePool<>(MAX_RETAINED_OBJECTS); Loading Loading @@ -792,7 +799,7 @@ class JobConcurrencyManager { cleanUpAfterAssignmentChangesLocked( mRecycledChanged, mRecycledIdle, mRecycledPreferredUidOnly, mRecycledStoppable, mRecycledAssignmentInfo); mRecycledAssignmentInfo, mRecycledPrivilegedState); noteConcurrency(); } Loading Loading @@ -915,7 +922,8 @@ class JobConcurrencyManager { continue; } final boolean hasImmediacyPrivilege = hasImmediacyPrivilegeLocked(nextPending); final boolean hasImmediacyPrivilege = hasImmediacyPrivilegeLocked(nextPending, mRecycledPrivilegedState); if (DEBUG && isSimilarJobRunningLocked(nextPending)) { Slog.w(TAG, "Already running similar job to: " + nextPending); } Loading Loading @@ -1183,7 +1191,8 @@ class JobConcurrencyManager { final ArraySet<ContextAssignment> idle, final List<ContextAssignment> preferredUidOnly, final List<ContextAssignment> stoppable, final AssignmentInfo assignmentInfo) { final AssignmentInfo assignmentInfo, final SparseIntArray privilegedState) { for (int s = stoppable.size() - 1; s >= 0; --s) { final ContextAssignment assignment = stoppable.get(s); assignment.clear(); Loading @@ -1205,20 +1214,58 @@ class JobConcurrencyManager { stoppable.clear(); preferredUidOnly.clear(); assignmentInfo.clear(); privilegedState.clear(); mWorkCountTracker.resetStagingCount(); mActivePkgStats.forEach(mPackageStatsStagingCountClearer); } @VisibleForTesting @GuardedBy("mLock") boolean hasImmediacyPrivilegeLocked(@NonNull JobStatus job) { boolean hasImmediacyPrivilegeLocked(@NonNull JobStatus job, @NonNull SparseIntArray cachedPrivilegedState) { if (!job.shouldTreatAsExpeditedJob() && !job.shouldTreatAsUserInitiatedJob()) { return false; } // EJs & user-initiated jobs for the TOP app should run immediately. // However, even for user-initiated jobs, if the app has not recently been in TOP or BAL // state, we don't give the immediacy privilege so that we can try and maintain // reasonably concurrency behavior. return job.lastEvaluatedBias == JobInfo.BIAS_TOP_APP // TODO(): include BAL state for user-initiated jobs && (job.shouldTreatAsExpeditedJob() || job.shouldTreatAsUserInitiatedJob()); if (job.lastEvaluatedBias == JobInfo.BIAS_TOP_APP) { return true; } final int uid = job.getSourceUid(); final int privilegedState = cachedPrivilegedState.get(uid, PRIVILEGED_STATE_UNDEFINED); switch (privilegedState) { case PRIVILEGED_STATE_TOP: return true; case PRIVILEGED_STATE_BAL: return job.shouldTreatAsUserInitiatedJob(); case PRIVILEGED_STATE_NONE: return false; case PRIVILEGED_STATE_UNDEFINED: default: final ActivityManagerInternal activityManagerInternal = LocalServices.getService(ActivityManagerInternal.class); final int procState = activityManagerInternal.getUidProcessState(uid); if (procState == ActivityManager.PROCESS_STATE_TOP) { cachedPrivilegedState.put(uid, PRIVILEGED_STATE_TOP); return true; } if (job.shouldTreatAsExpeditedJob()) { // EJs only get the TOP privilege. return false; } final BackgroundStartPrivileges bsp = activityManagerInternal.getBackgroundStartPrivileges(uid); final boolean balAllowed = bsp.allowsBackgroundActivityStarts(); if (DEBUG) { Slog.d(TAG, "Job " + job.toShortString() + " bal state: " + bsp); } cachedPrivilegedState.put(uid, balAllowed ? PRIVILEGED_STATE_BAL : PRIVILEGED_STATE_NONE); return balAllowed; } } @GuardedBy("mLock") Loading apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java +20 −0 Original line number Diff line number Diff line Loading @@ -31,6 +31,7 @@ import android.app.Activity; import android.app.ActivityManager; import android.app.ActivityManagerInternal; import android.app.AppGlobals; import android.app.BackgroundStartPrivileges; import android.app.IUidObserver; import android.app.compat.CompatChanges; import android.app.job.IJobScheduler; Loading Loading @@ -3841,6 +3842,25 @@ public class JobSchedulerService extends com.android.server.SystemService return callingResult; } } final int uid = sourceUid != -1 ? sourceUid : callingUid; final int procState = mActivityManagerInternal.getUidProcessState(uid); if (DEBUG) { Slog.d(TAG, "Uid " + uid + " proc state=" + ActivityManager.procStateToString(procState)); } if (procState != ActivityManager.PROCESS_STATE_TOP) { final BackgroundStartPrivileges bsp = mActivityManagerInternal.getBackgroundStartPrivileges(uid); if (DEBUG) { Slog.d(TAG, "Uid " + uid + ": " + bsp); } if (!bsp.allowsBackgroundActivityStarts()) { Slog.e(TAG, "Uid " + uid + " not in a state to schedule user-initiated jobs"); return JobScheduler.RESULT_FAILURE; } } } if (jobWorkItem != null) { jobWorkItem.enforceValidity(rejectNegativeNetworkEstimates); Loading core/java/android/app/ActivityManagerInternal.java +5 −0 Original line number Diff line number Diff line Loading @@ -464,6 +464,11 @@ public abstract class ActivityManagerInternal { public abstract boolean isActivityStartsLoggingEnabled(); /** Returns true if the background activity starts is enabled. */ public abstract boolean isBackgroundActivityStartsEnabled(); /** * Returns The current {@link BackgroundStartPrivileges} of the UID. */ @NonNull public abstract BackgroundStartPrivileges getBackgroundStartPrivileges(int uid); public abstract void reportCurKeyguardUsageEvent(boolean keyguardShowing); /** @see com.android.server.am.ActivityManagerService#monitor */ Loading services/core/java/com/android/server/am/ActivityManagerService.java +44 −0 Original line number Diff line number Diff line Loading @@ -487,6 +487,7 @@ import java.util.concurrent.Future; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; import java.util.function.BiFunction; import java.util.function.Consumer; import java.util.function.Supplier; public class ActivityManagerService extends IActivityManager.Stub Loading Loading @@ -6452,6 +6453,44 @@ public class ActivityManagerService extends IActivityManager.Stub return entry == null ? null : entry.second; } private static class GetBackgroundStartPrivilegesFunctor implements Consumer<ProcessRecord> { private BackgroundStartPrivileges mBackgroundStartPrivileges = BackgroundStartPrivileges.NONE; private int mUid; void prepare(int uid) { mUid = uid; mBackgroundStartPrivileges = BackgroundStartPrivileges.NONE; } @NonNull BackgroundStartPrivileges getResult() { return mBackgroundStartPrivileges; } public void accept(ProcessRecord pr) { if (pr.uid == mUid) { mBackgroundStartPrivileges = mBackgroundStartPrivileges.merge(pr.getBackgroundStartPrivileges()); } } } private final GetBackgroundStartPrivilegesFunctor mGetBackgroundStartPrivilegesFunctor = new GetBackgroundStartPrivilegesFunctor(); /** * Returns the current complete {@link BackgroundStartPrivileges} of the UID. */ @NonNull private BackgroundStartPrivileges getBackgroundStartPrivileges(int uid) { synchronized (mProcLock) { mGetBackgroundStartPrivilegesFunctor.prepare(uid); mProcessList.forEachLruProcessesLOSP(false, mGetBackgroundStartPrivilegesFunctor); return mGetBackgroundStartPrivilegesFunctor.getResult(); } } /** * @return allowlist tag for a uid from mPendingTempAllowlist, null if not currently on * the allowlist Loading Loading @@ -17851,6 +17890,11 @@ public class ActivityManagerService extends IActivityManager.Stub return mConstants.mFlagBackgroundActivityStartsEnabled; } @Override public BackgroundStartPrivileges getBackgroundStartPrivileges(int uid) { return ActivityManagerService.this.getBackgroundStartPrivileges(uid); } public void reportCurKeyguardUsageEvent(boolean keyguardShowing) { ActivityManagerService.this.reportGlobalUsageEvent(keyguardShowing ? UsageEvents.Event.KEYGUARD_SHOWN services/core/java/com/android/server/am/ProcessRecord.java +58 −0 Original line number Diff line number Diff line Loading @@ -20,6 +20,7 @@ import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM; import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME; import static com.android.server.am.ActivityManagerService.MY_PID; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.ActivityManager; import android.app.ApplicationExitInfo; Loading Loading @@ -340,6 +341,24 @@ class ProcessRecord implements WindowProcessListener { @GuardedBy("mService") private boolean mInFullBackup; /** * A set of tokens that currently contribute to this process being temporarily allowed * to start certain components (eg. activities or foreground services) even if it's not * in the foreground. */ @GuardedBy("mBackgroundStartPrivileges") private final ArrayMap<Binder, BackgroundStartPrivileges> mBackgroundStartPrivileges = new ArrayMap<>(); /** * The merged BackgroundStartPrivileges based on what's in {@link #mBackgroundStartPrivileges}. * This is lazily generated using {@link #getBackgroundStartPrivileges()}. */ @Nullable @GuardedBy("mBackgroundStartPrivileges") private BackgroundStartPrivileges mBackgroundStartPrivilegesMerged = BackgroundStartPrivileges.NONE; /** * Controller for driving the process state on the window manager side. */ Loading Loading @@ -1325,11 +1344,50 @@ class ProcessRecord implements WindowProcessListener { Objects.requireNonNull(entity); mWindowProcessController.addOrUpdateBackgroundStartPrivileges(entity, backgroundStartPrivileges); setBackgroundStartPrivileges(entity, backgroundStartPrivileges); } void removeBackgroundStartPrivileges(Binder entity) { Objects.requireNonNull(entity); mWindowProcessController.removeBackgroundStartPrivileges(entity); setBackgroundStartPrivileges(entity, null); } @NonNull BackgroundStartPrivileges getBackgroundStartPrivileges() { synchronized (mBackgroundStartPrivileges) { if (mBackgroundStartPrivilegesMerged == null) { // Lazily generate the merged version when it's actually needed. mBackgroundStartPrivilegesMerged = BackgroundStartPrivileges.NONE; for (int i = mBackgroundStartPrivileges.size() - 1; i >= 0; --i) { mBackgroundStartPrivilegesMerged = mBackgroundStartPrivilegesMerged.merge( mBackgroundStartPrivileges.valueAt(i)); } } return mBackgroundStartPrivilegesMerged; } } private void setBackgroundStartPrivileges(@NonNull Binder entity, @Nullable BackgroundStartPrivileges backgroundStartPrivileges) { synchronized (mBackgroundStartPrivileges) { final boolean changed; if (backgroundStartPrivileges == null) { changed = mBackgroundStartPrivileges.remove(entity) != null; } else { final BackgroundStartPrivileges oldBsp = mBackgroundStartPrivileges.put(entity, backgroundStartPrivileges); // BackgroundStartPrivileges tries to reuse the same object and avoid creating // additional objects. For now, we just compare the reference to see if something // has changed. // TODO: actually compare the individual values to see if there's a change changed = backgroundStartPrivileges != oldBsp; } if (changed) { mBackgroundStartPrivilegesMerged = null; } } } @Override Loading Loading
apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java +54 −7 Original line number Diff line number Diff line Loading @@ -27,6 +27,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.app.ActivityManager; import android.app.ActivityManagerInternal; import android.app.BackgroundStartPrivileges; import android.app.UserSwitchObserver; import android.app.job.JobInfo; import android.app.job.JobParameters; Loading Loading @@ -388,6 +389,12 @@ class JobConcurrencyManager { private final ArrayList<ContextAssignment> mRecycledPreferredUidOnly = new ArrayList<>(); private final ArrayList<ContextAssignment> mRecycledStoppable = new ArrayList<>(); private final AssignmentInfo mRecycledAssignmentInfo = new AssignmentInfo(); private final SparseIntArray mRecycledPrivilegedState = new SparseIntArray(); private static final int PRIVILEGED_STATE_UNDEFINED = 0; private static final int PRIVILEGED_STATE_NONE = 1; private static final int PRIVILEGED_STATE_BAL = 2; private static final int PRIVILEGED_STATE_TOP = 3; private final Pools.Pool<ContextAssignment> mContextAssignmentPool = new Pools.SimplePool<>(MAX_RETAINED_OBJECTS); Loading Loading @@ -792,7 +799,7 @@ class JobConcurrencyManager { cleanUpAfterAssignmentChangesLocked( mRecycledChanged, mRecycledIdle, mRecycledPreferredUidOnly, mRecycledStoppable, mRecycledAssignmentInfo); mRecycledAssignmentInfo, mRecycledPrivilegedState); noteConcurrency(); } Loading Loading @@ -915,7 +922,8 @@ class JobConcurrencyManager { continue; } final boolean hasImmediacyPrivilege = hasImmediacyPrivilegeLocked(nextPending); final boolean hasImmediacyPrivilege = hasImmediacyPrivilegeLocked(nextPending, mRecycledPrivilegedState); if (DEBUG && isSimilarJobRunningLocked(nextPending)) { Slog.w(TAG, "Already running similar job to: " + nextPending); } Loading Loading @@ -1183,7 +1191,8 @@ class JobConcurrencyManager { final ArraySet<ContextAssignment> idle, final List<ContextAssignment> preferredUidOnly, final List<ContextAssignment> stoppable, final AssignmentInfo assignmentInfo) { final AssignmentInfo assignmentInfo, final SparseIntArray privilegedState) { for (int s = stoppable.size() - 1; s >= 0; --s) { final ContextAssignment assignment = stoppable.get(s); assignment.clear(); Loading @@ -1205,20 +1214,58 @@ class JobConcurrencyManager { stoppable.clear(); preferredUidOnly.clear(); assignmentInfo.clear(); privilegedState.clear(); mWorkCountTracker.resetStagingCount(); mActivePkgStats.forEach(mPackageStatsStagingCountClearer); } @VisibleForTesting @GuardedBy("mLock") boolean hasImmediacyPrivilegeLocked(@NonNull JobStatus job) { boolean hasImmediacyPrivilegeLocked(@NonNull JobStatus job, @NonNull SparseIntArray cachedPrivilegedState) { if (!job.shouldTreatAsExpeditedJob() && !job.shouldTreatAsUserInitiatedJob()) { return false; } // EJs & user-initiated jobs for the TOP app should run immediately. // However, even for user-initiated jobs, if the app has not recently been in TOP or BAL // state, we don't give the immediacy privilege so that we can try and maintain // reasonably concurrency behavior. return job.lastEvaluatedBias == JobInfo.BIAS_TOP_APP // TODO(): include BAL state for user-initiated jobs && (job.shouldTreatAsExpeditedJob() || job.shouldTreatAsUserInitiatedJob()); if (job.lastEvaluatedBias == JobInfo.BIAS_TOP_APP) { return true; } final int uid = job.getSourceUid(); final int privilegedState = cachedPrivilegedState.get(uid, PRIVILEGED_STATE_UNDEFINED); switch (privilegedState) { case PRIVILEGED_STATE_TOP: return true; case PRIVILEGED_STATE_BAL: return job.shouldTreatAsUserInitiatedJob(); case PRIVILEGED_STATE_NONE: return false; case PRIVILEGED_STATE_UNDEFINED: default: final ActivityManagerInternal activityManagerInternal = LocalServices.getService(ActivityManagerInternal.class); final int procState = activityManagerInternal.getUidProcessState(uid); if (procState == ActivityManager.PROCESS_STATE_TOP) { cachedPrivilegedState.put(uid, PRIVILEGED_STATE_TOP); return true; } if (job.shouldTreatAsExpeditedJob()) { // EJs only get the TOP privilege. return false; } final BackgroundStartPrivileges bsp = activityManagerInternal.getBackgroundStartPrivileges(uid); final boolean balAllowed = bsp.allowsBackgroundActivityStarts(); if (DEBUG) { Slog.d(TAG, "Job " + job.toShortString() + " bal state: " + bsp); } cachedPrivilegedState.put(uid, balAllowed ? PRIVILEGED_STATE_BAL : PRIVILEGED_STATE_NONE); return balAllowed; } } @GuardedBy("mLock") Loading
apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java +20 −0 Original line number Diff line number Diff line Loading @@ -31,6 +31,7 @@ import android.app.Activity; import android.app.ActivityManager; import android.app.ActivityManagerInternal; import android.app.AppGlobals; import android.app.BackgroundStartPrivileges; import android.app.IUidObserver; import android.app.compat.CompatChanges; import android.app.job.IJobScheduler; Loading Loading @@ -3841,6 +3842,25 @@ public class JobSchedulerService extends com.android.server.SystemService return callingResult; } } final int uid = sourceUid != -1 ? sourceUid : callingUid; final int procState = mActivityManagerInternal.getUidProcessState(uid); if (DEBUG) { Slog.d(TAG, "Uid " + uid + " proc state=" + ActivityManager.procStateToString(procState)); } if (procState != ActivityManager.PROCESS_STATE_TOP) { final BackgroundStartPrivileges bsp = mActivityManagerInternal.getBackgroundStartPrivileges(uid); if (DEBUG) { Slog.d(TAG, "Uid " + uid + ": " + bsp); } if (!bsp.allowsBackgroundActivityStarts()) { Slog.e(TAG, "Uid " + uid + " not in a state to schedule user-initiated jobs"); return JobScheduler.RESULT_FAILURE; } } } if (jobWorkItem != null) { jobWorkItem.enforceValidity(rejectNegativeNetworkEstimates); Loading
core/java/android/app/ActivityManagerInternal.java +5 −0 Original line number Diff line number Diff line Loading @@ -464,6 +464,11 @@ public abstract class ActivityManagerInternal { public abstract boolean isActivityStartsLoggingEnabled(); /** Returns true if the background activity starts is enabled. */ public abstract boolean isBackgroundActivityStartsEnabled(); /** * Returns The current {@link BackgroundStartPrivileges} of the UID. */ @NonNull public abstract BackgroundStartPrivileges getBackgroundStartPrivileges(int uid); public abstract void reportCurKeyguardUsageEvent(boolean keyguardShowing); /** @see com.android.server.am.ActivityManagerService#monitor */ Loading
services/core/java/com/android/server/am/ActivityManagerService.java +44 −0 Original line number Diff line number Diff line Loading @@ -487,6 +487,7 @@ import java.util.concurrent.Future; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; import java.util.function.BiFunction; import java.util.function.Consumer; import java.util.function.Supplier; public class ActivityManagerService extends IActivityManager.Stub Loading Loading @@ -6452,6 +6453,44 @@ public class ActivityManagerService extends IActivityManager.Stub return entry == null ? null : entry.second; } private static class GetBackgroundStartPrivilegesFunctor implements Consumer<ProcessRecord> { private BackgroundStartPrivileges mBackgroundStartPrivileges = BackgroundStartPrivileges.NONE; private int mUid; void prepare(int uid) { mUid = uid; mBackgroundStartPrivileges = BackgroundStartPrivileges.NONE; } @NonNull BackgroundStartPrivileges getResult() { return mBackgroundStartPrivileges; } public void accept(ProcessRecord pr) { if (pr.uid == mUid) { mBackgroundStartPrivileges = mBackgroundStartPrivileges.merge(pr.getBackgroundStartPrivileges()); } } } private final GetBackgroundStartPrivilegesFunctor mGetBackgroundStartPrivilegesFunctor = new GetBackgroundStartPrivilegesFunctor(); /** * Returns the current complete {@link BackgroundStartPrivileges} of the UID. */ @NonNull private BackgroundStartPrivileges getBackgroundStartPrivileges(int uid) { synchronized (mProcLock) { mGetBackgroundStartPrivilegesFunctor.prepare(uid); mProcessList.forEachLruProcessesLOSP(false, mGetBackgroundStartPrivilegesFunctor); return mGetBackgroundStartPrivilegesFunctor.getResult(); } } /** * @return allowlist tag for a uid from mPendingTempAllowlist, null if not currently on * the allowlist Loading Loading @@ -17851,6 +17890,11 @@ public class ActivityManagerService extends IActivityManager.Stub return mConstants.mFlagBackgroundActivityStartsEnabled; } @Override public BackgroundStartPrivileges getBackgroundStartPrivileges(int uid) { return ActivityManagerService.this.getBackgroundStartPrivileges(uid); } public void reportCurKeyguardUsageEvent(boolean keyguardShowing) { ActivityManagerService.this.reportGlobalUsageEvent(keyguardShowing ? UsageEvents.Event.KEYGUARD_SHOWN
services/core/java/com/android/server/am/ProcessRecord.java +58 −0 Original line number Diff line number Diff line Loading @@ -20,6 +20,7 @@ import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM; import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME; import static com.android.server.am.ActivityManagerService.MY_PID; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.ActivityManager; import android.app.ApplicationExitInfo; Loading Loading @@ -340,6 +341,24 @@ class ProcessRecord implements WindowProcessListener { @GuardedBy("mService") private boolean mInFullBackup; /** * A set of tokens that currently contribute to this process being temporarily allowed * to start certain components (eg. activities or foreground services) even if it's not * in the foreground. */ @GuardedBy("mBackgroundStartPrivileges") private final ArrayMap<Binder, BackgroundStartPrivileges> mBackgroundStartPrivileges = new ArrayMap<>(); /** * The merged BackgroundStartPrivileges based on what's in {@link #mBackgroundStartPrivileges}. * This is lazily generated using {@link #getBackgroundStartPrivileges()}. */ @Nullable @GuardedBy("mBackgroundStartPrivileges") private BackgroundStartPrivileges mBackgroundStartPrivilegesMerged = BackgroundStartPrivileges.NONE; /** * Controller for driving the process state on the window manager side. */ Loading Loading @@ -1325,11 +1344,50 @@ class ProcessRecord implements WindowProcessListener { Objects.requireNonNull(entity); mWindowProcessController.addOrUpdateBackgroundStartPrivileges(entity, backgroundStartPrivileges); setBackgroundStartPrivileges(entity, backgroundStartPrivileges); } void removeBackgroundStartPrivileges(Binder entity) { Objects.requireNonNull(entity); mWindowProcessController.removeBackgroundStartPrivileges(entity); setBackgroundStartPrivileges(entity, null); } @NonNull BackgroundStartPrivileges getBackgroundStartPrivileges() { synchronized (mBackgroundStartPrivileges) { if (mBackgroundStartPrivilegesMerged == null) { // Lazily generate the merged version when it's actually needed. mBackgroundStartPrivilegesMerged = BackgroundStartPrivileges.NONE; for (int i = mBackgroundStartPrivileges.size() - 1; i >= 0; --i) { mBackgroundStartPrivilegesMerged = mBackgroundStartPrivilegesMerged.merge( mBackgroundStartPrivileges.valueAt(i)); } } return mBackgroundStartPrivilegesMerged; } } private void setBackgroundStartPrivileges(@NonNull Binder entity, @Nullable BackgroundStartPrivileges backgroundStartPrivileges) { synchronized (mBackgroundStartPrivileges) { final boolean changed; if (backgroundStartPrivileges == null) { changed = mBackgroundStartPrivileges.remove(entity) != null; } else { final BackgroundStartPrivileges oldBsp = mBackgroundStartPrivileges.put(entity, backgroundStartPrivileges); // BackgroundStartPrivileges tries to reuse the same object and avoid creating // additional objects. For now, we just compare the reference to see if something // has changed. // TODO: actually compare the individual values to see if there's a change changed = backgroundStartPrivileges != oldBsp; } if (changed) { mBackgroundStartPrivilegesMerged = null; } } } @Override Loading