diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java index 072d17f218291..403ad9b41f670 100644 --- a/services/core/java/com/android/server/appop/AppOpsService.java +++ b/services/core/java/com/android/server/appop/AppOpsService.java @@ -105,6 +105,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; } @@ -4809,6 +4817,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, @@ -4946,6 +4956,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; @@ -5028,6 +5039,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; @@ -5363,6 +5376,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 @@ -5375,14 +5394,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; } } }