From 482f199c66ab16a4d254f4bbaecefb6dec09efb3 Mon Sep 17 00:00:00 2001 From: Evan Severson Date: Mon, 26 Sep 2022 15:26:08 -0700 Subject: [PATCH] Make AppOpsUidStateTracker callbacks more testable The unit tests will become easier to write when validating callbacks and when having delayed behaviors. This is done by 1: using an executor instead of a handler so that we only need to check if the execute mock is invoked and 2: a custom executor interface that supports delayed execution. Executors are preferred so that this interface will be more consistent with public api guidelines and also because post/send methods in Handler are final so it's impossible to decouple a subclass/mocked Handler's behavior from the current Looper. Finally the test clock implementation takes the custom executor in the constructor so it can tell the executor when time changes so it can execute delayed runnables. Test: AppOpsUidStateTrackerTest Bug: 247111208 Change-Id: I930e7e56630408440f6ca03b4c2ab84b19b20eb9 --- .../android/server/appop/AppOpsService.java | 31 +++- .../server/appop/AppOpsUidStateTracker.java | 5 +- .../appop/AppOpsUidStateTrackerImpl.java | 97 ++++++---- .../appop/AppOpsUidStateTrackerTest.java | 173 +++++++++--------- 4 files changed, 171 insertions(+), 135 deletions(-) diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java index 4a7e63162747b..f16f2f19f5982 100644 --- a/services/core/java/com/android/server/appop/AppOpsService.java +++ b/services/core/java/com/android/server/appop/AppOpsService.java @@ -110,6 +110,7 @@ import android.os.Binder; import android.os.Build; import android.os.Bundle; import android.os.Handler; +import android.os.HandlerExecutor; import android.os.IBinder; import android.os.PackageTagsList; import android.os.Process; @@ -374,10 +375,17 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch public AppOpsUidStateTracker getUidStateTracker() { if (mUidStateTracker == null) { mUidStateTracker = new AppOpsUidStateTrackerImpl( - LocalServices.getService(ActivityManagerInternal.class), mHandler, + LocalServices.getService(ActivityManagerInternal.class), + mHandler, + r -> { + synchronized (AppOpsService.this) { + r.run(); + } + }, Clock.SYSTEM_CLOCK, mConstants); - mUidStateTracker.addUidStateChangedCallback(mHandler, this::onUidStateChanged); + mUidStateTracker.addUidStateChangedCallback(new HandlerExecutor(mHandler), + this::onUidStateChanged); } return mUidStateTracker; } @@ -5596,6 +5604,8 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch pw.println(" Only output the watcher sections."); pw.println(" --history"); pw.println(" Only output history."); + pw.println(" --uid-state-change-logs"); + pw.println(" Include logs about uid state changes."); } private void dumpStatesLocked(@NonNull PrintWriter pw, @Nullable String filterAttributionTag, @@ -5732,6 +5742,7 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch // TODO ntmyren: Remove the dumpHistory and dumpFilter boolean dumpHistory = false; boolean includeDiscreteOps = false; + boolean dumpUidStateChangeLogs = false; int nDiscreteOps = 10; @HistoricalOpsRequestFilter int dumpFilter = 0; boolean dumpAll = false; @@ -5814,6 +5825,8 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch } else if (arg.length() > 0 && arg.charAt(0) == '-') { pw.println("Unknown option: " + arg); return; + } else if ("--uid-state-change-logs".equals(arg)) { + dumpUidStateChangeLogs = true; } else { pw.println("Unknown command: " + arg); return; @@ -6265,6 +6278,12 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch pw.println(" AppOps policy not set."); } } + + if (dumpAll || dumpUidStateChangeLogs) { + pw.println(); + pw.println("Uid State Changes Event Log:"); + getUidStateTracker().dumpEvents(pw); + } } // Must not hold the appops lock @@ -6277,14 +6296,6 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch mHistoricalRegistry.dumpDiscreteData(pw, dumpUid, dumpPackage, dumpAttributionTag, dumpFilter, dumpOp, sdf, date, " ", nDiscreteOps); } - - if (dumpAll) { - pw.println(); - pw.println("Uid State Changes Event Log:"); - if (mUidStateTracker != null) { - mUidStateTracker.dumpEvents(pw); - } - } } @Override diff --git a/services/core/java/com/android/server/appop/AppOpsUidStateTracker.java b/services/core/java/com/android/server/appop/AppOpsUidStateTracker.java index 3da121bc73fa9..742bf4b6ebc7f 100644 --- a/services/core/java/com/android/server/appop/AppOpsUidStateTracker.java +++ b/services/core/java/com/android/server/appop/AppOpsUidStateTracker.java @@ -30,10 +30,11 @@ import static android.app.AppOpsManager.UID_STATE_FOREGROUND_SERVICE; import static android.app.AppOpsManager.UID_STATE_PERSISTENT; import static android.app.AppOpsManager.UID_STATE_TOP; -import android.os.Handler; +import android.annotation.CallbackExecutor; import android.util.SparseArray; import java.io.PrintWriter; +import java.util.concurrent.Executor; interface AppOpsUidStateTracker { @@ -95,7 +96,7 @@ interface AppOpsUidStateTracker { /** * Listen to changes in {@link android.app.AppOpsManager.UidState} */ - void addUidStateChangedCallback(Handler handler, + void addUidStateChangedCallback(@CallbackExecutor Executor executor, UidStateChangedCallback callback); /** diff --git a/services/core/java/com/android/server/appop/AppOpsUidStateTrackerImpl.java b/services/core/java/com/android/server/appop/AppOpsUidStateTrackerImpl.java index 52b67b530c19f..3c281d13c7690 100644 --- a/services/core/java/com/android/server/appop/AppOpsUidStateTrackerImpl.java +++ b/services/core/java/com/android/server/appop/AppOpsUidStateTrackerImpl.java @@ -44,17 +44,18 @@ import android.util.SparseIntArray; import android.util.SparseLongArray; import android.util.TimeUtils; +import com.android.internal.annotations.VisibleForTesting; import com.android.internal.os.Clock; import com.android.internal.util.function.pooled.PooledLambda; import java.io.PrintWriter; -import java.util.concurrent.CountDownLatch; +import java.util.concurrent.Executor; class AppOpsUidStateTrackerImpl implements AppOpsUidStateTracker { private static final String LOG_TAG = AppOpsUidStateTrackerImpl.class.getSimpleName(); - private final Handler mHandler; + private final DelayableExecutor mExecutor; private final Clock mClock; private ActivityManagerInternal mActivityManagerInternal; private AppOpsService.Constants mConstants; @@ -68,18 +69,46 @@ class AppOpsUidStateTrackerImpl implements AppOpsUidStateTracker { private SparseLongArray mPendingCommitTime = new SparseLongArray(); private SparseBooleanArray mPendingGone = new SparseBooleanArray(); - private ArrayMap mUidStateChangedCallbacks = new ArrayMap<>(); + private ArrayMap + mUidStateChangedCallbacks = new ArrayMap<>(); private final EventLog mEventLog; + @VisibleForTesting + interface DelayableExecutor extends Executor { + + void execute(Runnable runnable); + + void executeDelayed(Runnable runnable, long delay); + } + AppOpsUidStateTrackerImpl(ActivityManagerInternal activityManagerInternal, - Handler handler, Clock clock, AppOpsService.Constants constants) { + Handler handler, Executor lockingExecutor, Clock clock, + AppOpsService.Constants constants) { + + this(activityManagerInternal, new DelayableExecutor() { + @Override + public void execute(Runnable runnable) { + handler.post(() -> lockingExecutor.execute(runnable)); + } + + @Override + public void executeDelayed(Runnable runnable, long delay) { + handler.postDelayed(() -> lockingExecutor.execute(runnable), delay); + } + }, clock, constants, handler.getLooper().getThread()); + } + + @VisibleForTesting + AppOpsUidStateTrackerImpl(ActivityManagerInternal activityManagerInternal, + DelayableExecutor executor, Clock clock, AppOpsService.Constants constants, + Thread executorThread) { mActivityManagerInternal = activityManagerInternal; - mHandler = handler; + mExecutor = executor; mClock = clock; mConstants = constants; - mEventLog = new EventLog(handler); + mEventLog = new EventLog(executor, executorThread); } @Override @@ -157,11 +186,12 @@ class AppOpsUidStateTrackerImpl implements AppOpsUidStateTracker { } @Override - public void addUidStateChangedCallback(Handler handler, UidStateChangedCallback callback) { + public void addUidStateChangedCallback(Executor executor, UidStateChangedCallback callback) { if (mUidStateChangedCallbacks.containsKey(callback)) { throw new IllegalStateException("Callback is already registered."); } - mUidStateChangedCallbacks.put(callback, handler); + + mUidStateChangedCallbacks.put(callback, executor); } @Override @@ -232,7 +262,7 @@ class AppOpsUidStateTrackerImpl implements AppOpsUidStateTracker { final long commitTime = mClock.elapsedRealtime() + settleTime; mPendingCommitTime.put(uid, commitTime); - mHandler.sendMessageDelayed(PooledLambda.obtainMessage( + mExecutor.executeDelayed(PooledLambda.obtainRunnable( AppOpsUidStateTrackerImpl::updateUidPendingStateIfNeeded, this, uid), settleTime + 1); } @@ -323,10 +353,11 @@ class AppOpsUidStateTrackerImpl implements AppOpsUidStateTracker { for (int i = 0; i < mUidStateChangedCallbacks.size(); i++) { UidStateChangedCallback cb = mUidStateChangedCallbacks.keyAt(i); - Handler h = mUidStateChangedCallbacks.valueAt(i); + Executor executor = mUidStateChangedCallbacks.valueAt(i); - h.sendMessage(PooledLambda.obtainMessage(UidStateChangedCallback::onUidStateChanged, - cb, uid, pendingUidState, foregroundChange)); + executor.execute(PooledLambda.obtainRunnable( + UidStateChangedCallback::onUidStateChanged, cb, uid, pendingUidState, + foregroundChange)); } } @@ -366,7 +397,8 @@ class AppOpsUidStateTrackerImpl implements AppOpsUidStateTracker { // Memory usage: 24 * size bytes private static final int EVAL_FOREGROUND_MODE_MAX_SIZE = 200; - private final Handler mHandler; + private final DelayableExecutor mExecutor; + private final Thread mExecutorThread; private int[][] mUpdateUidProcStateLog = new int[UPDATE_UID_PROC_STATE_LOG_MAX_SIZE][3]; private long[] mUpdateUidProcStateLogTimestamps = @@ -384,15 +416,16 @@ class AppOpsUidStateTrackerImpl implements AppOpsUidStateTracker { private int mEvalForegroundModeLogSize = 0; private int mEvalForegroundModeLogHead = 0; - EventLog(Handler handler) { - mHandler = handler; + EventLog(DelayableExecutor executor, Thread executorThread) { + mExecutor = executor; + mExecutorThread = executorThread; } void logUpdateUidProcState(int uid, int procState, int capability) { if (UPDATE_UID_PROC_STATE_LOG_MAX_SIZE == 0) { return; } - mHandler.sendMessage(PooledLambda.obtainMessage(EventLog::logUpdateUidProcStateAsync, + mExecutor.execute(PooledLambda.obtainRunnable(EventLog::logUpdateUidProcStateAsync, this, System.currentTimeMillis(), uid, procState, capability)); } @@ -416,7 +449,7 @@ class AppOpsUidStateTrackerImpl implements AppOpsUidStateTracker { if (COMMIT_UID_STATE_LOG_MAX_SIZE == 0) { return; } - mHandler.sendMessage(PooledLambda.obtainMessage(EventLog::logCommitUidStateAsync, + mExecutor.execute(PooledLambda.obtainRunnable(EventLog::logCommitUidStateAsync, this, System.currentTimeMillis(), uid, uidState, capability, visible)); } @@ -442,7 +475,7 @@ class AppOpsUidStateTrackerImpl implements AppOpsUidStateTracker { if (EVAL_FOREGROUND_MODE_MAX_SIZE == 0) { return; } - mHandler.sendMessage(PooledLambda.obtainMessage(EventLog::logEvalForegroundModeAsync, + mExecutor.execute(PooledLambda.obtainRunnable(EventLog::logEvalForegroundModeAsync, this, System.currentTimeMillis(), uid, uidState, capability, code, result)); } @@ -466,22 +499,6 @@ class AppOpsUidStateTrackerImpl implements AppOpsUidStateTracker { } void dumpEvents(PrintWriter pw) { - if (Thread.currentThread() != mHandler.getLooper().getThread()) { - // All operations are done on the handler's thread - CountDownLatch latch = new CountDownLatch(1); - mHandler.post(() -> { - dumpEvents(pw); - latch.countDown(); - }); - - try { - latch.await(); - } catch (InterruptedException e) { - throw new RuntimeException(e); - } - return; - } - int updateIdx = 0; int commitIdx = 0; int evalIdx = 0; @@ -536,13 +553,13 @@ class AppOpsUidStateTrackerImpl implements AppOpsUidStateTracker { pw.print(" UPDATE_UID_PROC_STATE"); pw.print(" uid="); - pw.print(uid); + pw.print(String.format("%-8d", uid)); pw.print(" procState="); pw.print(String.format("%-30s", ActivityManager.procStateToString(procState))); pw.print(" capability="); - pw.print(ActivityManager.getCapabilitiesSummary(capability)); + pw.print(ActivityManager.getCapabilitiesSummary(capability) + " "); pw.println(); } @@ -559,13 +576,13 @@ class AppOpsUidStateTrackerImpl implements AppOpsUidStateTracker { pw.print(" COMMIT_UID_STATE "); pw.print(" uid="); - pw.print(uid); + pw.print(String.format("%-8d", uid)); pw.print(" uidState="); pw.print(String.format("%-30s", AppOpsManager.uidStateToString(uidState))); pw.print(" capability="); - pw.print(ActivityManager.getCapabilitiesSummary(capability)); + pw.print(ActivityManager.getCapabilitiesSummary(capability) + " "); pw.print(" visibleAppWidget="); pw.print(visibleAppWidget); @@ -586,13 +603,13 @@ class AppOpsUidStateTrackerImpl implements AppOpsUidStateTracker { pw.print(" EVAL_FOREGROUND_MODE "); pw.print(" uid="); - pw.print(uid); + pw.print(String.format("%-8d", uid)); pw.print(" uidState="); pw.print(String.format("%-30s", AppOpsManager.uidStateToString(uidState))); pw.print(" capability="); - pw.print(ActivityManager.getCapabilitiesSummary(capability)); + pw.print(ActivityManager.getCapabilitiesSummary(capability) + " "); pw.print(" code="); pw.print(String.format("%-20s", AppOpsManager.opToName(code))); diff --git a/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsUidStateTrackerTest.java b/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsUidStateTrackerTest.java index 86e1264756543..e1713b0beb77d 100644 --- a/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsUidStateTrackerTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsUidStateTrackerTest.java @@ -34,12 +34,9 @@ import static android.app.AppOpsManager.UID_STATE_TOP; import static com.android.server.appop.AppOpsUidStateTracker.processStateToUidState; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.atLeast; import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.never; @@ -49,25 +46,22 @@ import static org.mockito.Mockito.verify; import android.app.ActivityManager; import android.app.ActivityManagerInternal; import android.app.AppOpsManager; -import android.os.Handler; -import android.os.Message; import android.util.SparseArray; import com.android.dx.mockito.inline.extended.ExtendedMockito; import com.android.dx.mockito.inline.extended.StaticMockitoSession; import com.android.internal.os.Clock; import com.android.server.appop.AppOpsUidStateTracker.UidStateChangedCallback; +import com.android.server.appop.AppOpsUidStateTrackerImpl.DelayableExecutor; import org.junit.After; import org.junit.Before; import org.junit.Test; -import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.quality.Strictness; -import java.util.concurrent.atomic.AtomicLong; -import java.util.concurrent.atomic.AtomicReference; +import java.util.PriorityQueue; public class AppOpsUidStateTrackerTest { @@ -80,13 +74,12 @@ public class AppOpsUidStateTrackerTest { @Mock ActivityManagerInternal mAmi; - @Mock - Handler mHandler; - @Mock AppOpsService.Constants mConstants; - AppOpsUidStateTrackerTestClock mClock = new AppOpsUidStateTrackerTestClock(); + AppOpsUidStateTrackerTestExecutor mExecutor = new AppOpsUidStateTrackerTestExecutor(); + + AppOpsUidStateTrackerTestClock mClock = new AppOpsUidStateTrackerTestClock(mExecutor); AppOpsUidStateTracker mIntf; @@ -101,7 +94,8 @@ public class AppOpsUidStateTrackerTest { mConstants.TOP_STATE_SETTLE_TIME = 10 * 1000L; mConstants.FG_SERVICE_STATE_SETTLE_TIME = 5 * 1000L; mConstants.BG_STATE_SETTLE_TIME = 1 * 1000L; - mIntf = new AppOpsUidStateTrackerImpl(mAmi, mHandler, mClock, mConstants); + mIntf = new AppOpsUidStateTrackerImpl(mAmi, mExecutor, mClock, mConstants, + Thread.currentThread()); } @After @@ -263,18 +257,10 @@ public class AppOpsUidStateTrackerTest { // Still in foreground due to settle time assertForeground(UID); - AtomicReference messageAtomicReference = new AtomicReference<>(); - AtomicLong delayAtomicReference = new AtomicLong(); + mClock.advanceTime(mConstants.TOP_STATE_SETTLE_TIME - 1); + assertForeground(UID); - getPostDelayedMessageArguments(messageAtomicReference, delayAtomicReference); - Message message = messageAtomicReference.get(); - long delay = delayAtomicReference.get(); - - assertNotNull(message); - assertEquals(mConstants.TOP_STATE_SETTLE_TIME + 1, delay); - - mClock.advanceTime(mConstants.TOP_STATE_SETTLE_TIME + 1); - message.getCallback().run(); + mClock.advanceTime(1); assertBackground(UID); } @@ -291,18 +277,10 @@ public class AppOpsUidStateTrackerTest { // Still in foreground due to settle time assertForeground(UID); - AtomicReference messageAtomicReference = new AtomicReference<>(); - AtomicLong delayAtomicReference = new AtomicLong(); + mClock.advanceTime(mConstants.FG_SERVICE_STATE_SETTLE_TIME - 1); + assertForeground(UID); - getPostDelayedMessageArguments(messageAtomicReference, delayAtomicReference); - Message message = messageAtomicReference.get(); - long delay = delayAtomicReference.get(); - - assertNotNull(message); - assertEquals(mConstants.FG_SERVICE_STATE_SETTLE_TIME + 1, delay); - - mClock.advanceTime(mConstants.FG_SERVICE_STATE_SETTLE_TIME + 1); - message.getCallback().run(); + mClock.advanceTime(1); assertBackground(UID); } @@ -319,14 +297,8 @@ public class AppOpsUidStateTrackerTest { // Still in foreground due to settle time assertForeground(UID); - AtomicReference messageAtomicReference = new AtomicReference<>(); - - getPostDelayedMessageArguments(messageAtomicReference, null); - Message message = messageAtomicReference.get(); - // 1 ms short of settle time mClock.advanceTime(mConstants.FG_SERVICE_STATE_SETTLE_TIME - 1); - message.getCallback().run(); assertForeground(UID); } @@ -471,8 +443,6 @@ public class AppOpsUidStateTrackerTest { .topState() .update(); - getLatestPostMessageArgument().getCallback().run(); - verify(cb).onUidStateChanged(eq(UID), eq(UID_STATE_TOP), eq(true)); } @@ -484,8 +454,6 @@ public class AppOpsUidStateTrackerTest { .foregroundServiceState() .update(); - getLatestPostMessageArgument().getCallback().run(); - verify(cb).onUidStateChanged(eq(UID), eq(UID_STATE_FOREGROUND_SERVICE), eq(true)); } @@ -497,8 +465,6 @@ public class AppOpsUidStateTrackerTest { .foregroundState() .update(); - getLatestPostMessageArgument().getCallback().run(); - verify(cb).onUidStateChanged(eq(UID), eq(UID_STATE_FOREGROUND), eq(true)); } @@ -510,8 +476,6 @@ public class AppOpsUidStateTrackerTest { .backgroundState() .update(); - getLatestPostMessageArgument().getCallback().run(); - verify(cb).onUidStateChanged(eq(UID), eq(UID_STATE_BACKGROUND), eq(false)); } @@ -679,7 +643,6 @@ public class AppOpsUidStateTrackerTest { .nonExistentState() .update(); - verify(mHandler, never()).post(any()); verify(cb, never()).onUidStateChanged(anyInt(), anyInt(), anyBoolean()); } @@ -695,7 +658,6 @@ public class AppOpsUidStateTrackerTest { .nonExistentState() .update(); - getLatestPostMessageArgument().getCallback().run(); verify(cb, atLeastOnce()).onUidStateChanged(eq(UID), eq(UID_STATE_CACHED), eq(false)); } @@ -711,7 +673,6 @@ public class AppOpsUidStateTrackerTest { .nonExistentState() .update(); - getLatestPostMessageArgument().getCallback().run(); verify(cb, atLeastOnce()).onUidStateChanged(eq(UID), eq(UID_STATE_CACHED), eq(true)); } @@ -727,7 +688,6 @@ public class AppOpsUidStateTrackerTest { .nonExistentState() .update(); - getLatestPostMessageArgument().getCallback().run(); verify(cb, atLeastOnce()).onUidStateChanged(eq(UID), eq(UID_STATE_CACHED), eq(true)); } @@ -743,10 +703,32 @@ public class AppOpsUidStateTrackerTest { .nonExistentState() .update(); - getLatestPostMessageArgument().getCallback().run(); verify(cb, atLeastOnce()).onUidStateChanged(eq(UID), eq(UID_STATE_CACHED), eq(true)); } + @Test + public void testUidStateChangedBackgroundThenForegroundImmediately() { + procStateBuilder(UID) + .topState() + .update(); + + UidStateChangedCallback cb = addUidStateChangeCallback(); + + procStateBuilder(UID) + .backgroundState() + .update(); + + mClock.advanceTime(mConstants.TOP_STATE_SETTLE_TIME - 1); + + procStateBuilder(UID) + .topState() + .update(); + + mClock.advanceTime(1); + + verify(cb, never()).onUidStateChanged(anyInt(), anyInt(), anyBoolean()); + } + public void testUidStateChangedCallback(int initialState, int finalState) { int initialUidState = processStateToUidState(initialState); int finalUidState = processStateToUidState(finalState); @@ -767,13 +749,9 @@ public class AppOpsUidStateTrackerTest { .update(); if (finalUidStateIsBackgroundAndLessImportant) { - AtomicReference delayedMessage = new AtomicReference<>(); - getPostDelayedMessageArguments(delayedMessage, new AtomicLong()); mClock.advanceTime(mConstants.TOP_STATE_SETTLE_TIME + 1); - delayedMessage.get().getCallback().run(); } - getLatestPostMessageArgument().getCallback().run(); verify(cb, atLeastOnce()) .onUidStateChanged(eq(UID), eq(finalUidState), eq(foregroundChange)); } @@ -781,7 +759,7 @@ public class AppOpsUidStateTrackerTest { private UidStateChangedCallback addUidStateChangeCallback() { UidStateChangedCallback cb = Mockito.mock(UidStateChangedCallback.class); - mIntf.addUidStateChangedCallback(mHandler, cb); + mIntf.addUidStateChangedCallback(r -> r.run(), cb); return cb; } @@ -795,30 +773,6 @@ public class AppOpsUidStateTrackerTest { assertEquals(MODE_IGNORED, mIntf.evalMode(uid, OP_NO_CAPABILITIES, MODE_FOREGROUND)); } - private void getPostDelayedMessageArguments(AtomicReference message, - AtomicLong delay) { - - ArgumentCaptor messageCaptor = ArgumentCaptor.forClass(Message.class); - ArgumentCaptor delayCaptor = ArgumentCaptor.forClass(Long.class); - - verify(mHandler).sendMessageDelayed(messageCaptor.capture(), delayCaptor.capture()); - - if (message != null) { - message.set(messageCaptor.getValue()); - } - if (delay != null) { - delay.set(delayCaptor.getValue()); - } - } - - private Message getLatestPostMessageArgument() { - ArgumentCaptor messageCaptor = ArgumentCaptor.forClass(Message.class); - - verify(mHandler, atLeast(1)).sendMessage(messageCaptor.capture()); - - return messageCaptor.getValue(); - } - private UidProcStateUpdateBuilder procStateBuilder(int uid) { return new UidProcStateUpdateBuilder(mIntf, uid); } @@ -896,8 +850,14 @@ public class AppOpsUidStateTrackerTest { private static class AppOpsUidStateTrackerTestClock extends Clock { + private AppOpsUidStateTrackerTestExecutor mExecutor; long mElapsedRealTime = 0x5f3759df; + AppOpsUidStateTrackerTestClock(AppOpsUidStateTrackerTestExecutor executor) { + mExecutor = executor; + executor.setUptime(mElapsedRealTime); + } + @Override public long elapsedRealtime() { return mElapsedRealTime; @@ -905,6 +865,53 @@ public class AppOpsUidStateTrackerTest { void advanceTime(long time) { mElapsedRealTime += time; + mExecutor.setUptime(mElapsedRealTime); // assume uptime == elapsedtime + } + } + + private static class AppOpsUidStateTrackerTestExecutor implements DelayableExecutor { + + private static class QueueElement implements Comparable { + + private long mExecutionTime; + private Runnable mRunnable; + + private QueueElement(long executionTime, Runnable runnable) { + mExecutionTime = executionTime; + mRunnable = runnable; + } + + @Override + public int compareTo(QueueElement queueElement) { + return Long.compare(mExecutionTime, queueElement.mExecutionTime); + } + } + + private long mUptime = 0; + + private PriorityQueue mDelayedMessages = new PriorityQueue(); + + @Override + public void execute(Runnable runnable) { + runnable.run(); + } + + @Override + public void executeDelayed(Runnable runnable, long delay) { + if (delay <= 0) { + execute(runnable); + } + + mDelayedMessages.add(new QueueElement(mUptime + delay, runnable)); + } + + private void setUptime(long uptime) { + while (!mDelayedMessages.isEmpty() + && mDelayedMessages.peek().mExecutionTime <= uptime) { + mDelayedMessages.poll().mRunnable.run(); + } + + mUptime = uptime; } } }