Loading services/core/java/com/android/server/appop/AppOpsService.java +21 −10 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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; } Loading Loading @@ -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, Loading Loading @@ -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; Loading Loading @@ -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; Loading Loading @@ -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 Loading @@ -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 Loading services/core/java/com/android/server/appop/AppOpsUidStateTracker.java +3 −2 Original line number Diff line number Diff line Loading @@ -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 { Loading Loading @@ -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); /** Loading services/core/java/com/android/server/appop/AppOpsUidStateTrackerImpl.java +57 −40 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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, 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, Handler handler, Clock clock, AppOpsService.Constants constants) { 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 Loading Loading @@ -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 Loading Loading @@ -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); } Loading Loading @@ -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)); } } Loading Loading @@ -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 = Loading @@ -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)); } Loading @@ -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)); } Loading @@ -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)); } Loading @@ -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; Loading Loading @@ -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(); } Loading @@ -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); Loading @@ -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))); Loading services/tests/mockingservicestests/src/com/android/server/appop/AppOpsUidStateTrackerTest.java +90 −83 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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 { Loading @@ -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; Loading @@ -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 Loading Loading @@ -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(); 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); assertForeground(UID); mClock.advanceTime(mConstants.TOP_STATE_SETTLE_TIME + 1); message.getCallback().run(); mClock.advanceTime(1); assertBackground(UID); } Loading @@ -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(); 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); assertForeground(UID); mClock.advanceTime(mConstants.FG_SERVICE_STATE_SETTLE_TIME + 1); message.getCallback().run(); mClock.advanceTime(1); assertBackground(UID); } Loading @@ -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); } Loading Loading @@ -471,8 +443,6 @@ public class AppOpsUidStateTrackerTest { .topState() .update(); getLatestPostMessageArgument().getCallback().run(); verify(cb).onUidStateChanged(eq(UID), eq(UID_STATE_TOP), eq(true)); } Loading @@ -484,8 +454,6 @@ public class AppOpsUidStateTrackerTest { .foregroundServiceState() .update(); getLatestPostMessageArgument().getCallback().run(); verify(cb).onUidStateChanged(eq(UID), eq(UID_STATE_FOREGROUND_SERVICE), eq(true)); } Loading @@ -497,8 +465,6 @@ public class AppOpsUidStateTrackerTest { .foregroundState() .update(); getLatestPostMessageArgument().getCallback().run(); verify(cb).onUidStateChanged(eq(UID), eq(UID_STATE_FOREGROUND), eq(true)); } Loading @@ -510,8 +476,6 @@ public class AppOpsUidStateTrackerTest { .backgroundState() .update(); getLatestPostMessageArgument().getCallback().run(); verify(cb).onUidStateChanged(eq(UID), eq(UID_STATE_BACKGROUND), eq(false)); } Loading Loading @@ -679,7 +643,6 @@ public class AppOpsUidStateTrackerTest { .nonExistentState() .update(); verify(mHandler, never()).post(any()); verify(cb, never()).onUidStateChanged(anyInt(), anyInt(), anyBoolean()); } Loading @@ -695,7 +658,6 @@ public class AppOpsUidStateTrackerTest { .nonExistentState() .update(); getLatestPostMessageArgument().getCallback().run(); verify(cb, atLeastOnce()).onUidStateChanged(eq(UID), eq(UID_STATE_CACHED), eq(false)); } Loading @@ -711,7 +673,6 @@ public class AppOpsUidStateTrackerTest { .nonExistentState() .update(); getLatestPostMessageArgument().getCallback().run(); verify(cb, atLeastOnce()).onUidStateChanged(eq(UID), eq(UID_STATE_CACHED), eq(true)); } Loading @@ -727,7 +688,6 @@ public class AppOpsUidStateTrackerTest { .nonExistentState() .update(); getLatestPostMessageArgument().getCallback().run(); verify(cb, atLeastOnce()).onUidStateChanged(eq(UID), eq(UID_STATE_CACHED), eq(true)); } Loading @@ -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); Loading @@ -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)); } Loading @@ -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; } Loading @@ -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); } Loading Loading @@ -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; Loading @@ -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; } } } Loading
services/core/java/com/android/server/appop/AppOpsService.java +21 −10 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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; } Loading Loading @@ -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, Loading Loading @@ -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; Loading Loading @@ -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; Loading Loading @@ -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 Loading @@ -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 Loading
services/core/java/com/android/server/appop/AppOpsUidStateTracker.java +3 −2 Original line number Diff line number Diff line Loading @@ -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 { Loading Loading @@ -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); /** Loading
services/core/java/com/android/server/appop/AppOpsUidStateTrackerImpl.java +57 −40 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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, 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, Handler handler, Clock clock, AppOpsService.Constants constants) { 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 Loading Loading @@ -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 Loading Loading @@ -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); } Loading Loading @@ -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)); } } Loading Loading @@ -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 = Loading @@ -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)); } Loading @@ -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)); } Loading @@ -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)); } Loading @@ -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; Loading Loading @@ -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(); } Loading @@ -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); Loading @@ -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))); Loading
services/tests/mockingservicestests/src/com/android/server/appop/AppOpsUidStateTrackerTest.java +90 −83 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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 { Loading @@ -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; Loading @@ -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 Loading Loading @@ -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(); 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); assertForeground(UID); mClock.advanceTime(mConstants.TOP_STATE_SETTLE_TIME + 1); message.getCallback().run(); mClock.advanceTime(1); assertBackground(UID); } Loading @@ -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(); 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); assertForeground(UID); mClock.advanceTime(mConstants.FG_SERVICE_STATE_SETTLE_TIME + 1); message.getCallback().run(); mClock.advanceTime(1); assertBackground(UID); } Loading @@ -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); } Loading Loading @@ -471,8 +443,6 @@ public class AppOpsUidStateTrackerTest { .topState() .update(); getLatestPostMessageArgument().getCallback().run(); verify(cb).onUidStateChanged(eq(UID), eq(UID_STATE_TOP), eq(true)); } Loading @@ -484,8 +454,6 @@ public class AppOpsUidStateTrackerTest { .foregroundServiceState() .update(); getLatestPostMessageArgument().getCallback().run(); verify(cb).onUidStateChanged(eq(UID), eq(UID_STATE_FOREGROUND_SERVICE), eq(true)); } Loading @@ -497,8 +465,6 @@ public class AppOpsUidStateTrackerTest { .foregroundState() .update(); getLatestPostMessageArgument().getCallback().run(); verify(cb).onUidStateChanged(eq(UID), eq(UID_STATE_FOREGROUND), eq(true)); } Loading @@ -510,8 +476,6 @@ public class AppOpsUidStateTrackerTest { .backgroundState() .update(); getLatestPostMessageArgument().getCallback().run(); verify(cb).onUidStateChanged(eq(UID), eq(UID_STATE_BACKGROUND), eq(false)); } Loading Loading @@ -679,7 +643,6 @@ public class AppOpsUidStateTrackerTest { .nonExistentState() .update(); verify(mHandler, never()).post(any()); verify(cb, never()).onUidStateChanged(anyInt(), anyInt(), anyBoolean()); } Loading @@ -695,7 +658,6 @@ public class AppOpsUidStateTrackerTest { .nonExistentState() .update(); getLatestPostMessageArgument().getCallback().run(); verify(cb, atLeastOnce()).onUidStateChanged(eq(UID), eq(UID_STATE_CACHED), eq(false)); } Loading @@ -711,7 +673,6 @@ public class AppOpsUidStateTrackerTest { .nonExistentState() .update(); getLatestPostMessageArgument().getCallback().run(); verify(cb, atLeastOnce()).onUidStateChanged(eq(UID), eq(UID_STATE_CACHED), eq(true)); } Loading @@ -727,7 +688,6 @@ public class AppOpsUidStateTrackerTest { .nonExistentState() .update(); getLatestPostMessageArgument().getCallback().run(); verify(cb, atLeastOnce()).onUidStateChanged(eq(UID), eq(UID_STATE_CACHED), eq(true)); } Loading @@ -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); Loading @@ -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)); } Loading @@ -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; } Loading @@ -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); } Loading Loading @@ -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; Loading @@ -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; } } }