Merge "Make AppOpsUidStateTracker callbacks more testable"

This commit is contained in:
Evan Severson
2022-09-28 00:09:12 +00:00
committed by Android (Google) Code Review
4 changed files with 171 additions and 135 deletions

View File

@@ -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

View File

@@ -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);
/**

View File

@@ -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<UidStateChangedCallback, Handler> mUidStateChangedCallbacks = new ArrayMap<>();
private ArrayMap<UidStateChangedCallback, Executor>
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)));

View File

@@ -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<Message> 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<Message> 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<Message> 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<Message> 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> message,
AtomicLong delay) {
ArgumentCaptor<Message> messageCaptor = ArgumentCaptor.forClass(Message.class);
ArgumentCaptor<Long> 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<Message> 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<QueueElement> {
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<QueueElement> 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;
}
}
}