1 /* 2 * Copyright (C) 2019 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.systemui.util.concurrency; 18 19 import com.android.systemui.util.time.FakeSystemClock; 20 21 import java.util.Collections; 22 import java.util.PriorityQueue; 23 import java.util.concurrent.TimeUnit; 24 import java.util.concurrent.atomic.AtomicInteger; 25 26 public class FakeExecutor implements DelayableExecutor { 27 private final FakeSystemClock mClock; 28 private PriorityQueue<QueuedRunnable> mQueuedRunnables = new PriorityQueue<>(); 29 private boolean mIgnoreClockUpdates; 30 private boolean mExecuting; 31 32 /** 33 * Initializes a fake executor. 34 * 35 * @param clock FakeSystemClock allowing control over delayed runnables. It is strongly 36 * recommended that this clock have its auto-increment setting set to false to 37 * prevent unexpected advancement of the time. 38 */ FakeExecutor(FakeSystemClock clock)39 public FakeExecutor(FakeSystemClock clock) { 40 mClock = clock; 41 mClock.addListener(() -> { 42 if (!mIgnoreClockUpdates) { 43 runAllReady(); 44 } 45 }); 46 } 47 48 /** 49 * Runs a single runnable if it's scheduled to run according to the internal clock. 50 * 51 * If constructed to advance the clock automatically, this will advance the clock enough to 52 * run the next pending item. 53 * 54 * This method does not advance the clock past the item that was run. 55 * 56 * @return Returns true if an item was run. 57 */ runNextReady()58 public boolean runNextReady() { 59 if (!mQueuedRunnables.isEmpty() && mQueuedRunnables.peek().mWhen <= mClock.uptimeMillis()) { 60 mExecuting = true; 61 mQueuedRunnables.poll().mRunnable.run(); 62 mExecuting = false; 63 return true; 64 } 65 66 return false; 67 } 68 69 /** 70 * Runs all Runnables that are scheduled to run according to the internal clock. 71 * 72 * If constructed to advance the clock automatically, this will advance the clock enough to 73 * run all the pending items. This method does not advance the clock past items that were 74 * run. It is equivalent to calling {@link #runNextReady()} in a loop. 75 * 76 * @return Returns the number of items that ran. 77 */ runAllReady()78 public int runAllReady() { 79 int num = 0; 80 while (runNextReady()) { 81 num++; 82 } 83 84 return num; 85 } 86 87 /** 88 * Advances the internal clock to the next item to run. 89 * 90 * The clock will only move forward. If the next item is set to run in the past or there is no 91 * next item, the clock does not change. 92 * 93 * Note that this will cause one or more items to actually run. 94 * 95 * @return The delta in uptimeMillis that the clock advanced, or 0 if the clock did not advance. 96 */ advanceClockToNext()97 public long advanceClockToNext() { 98 if (mQueuedRunnables.isEmpty()) { 99 return 0; 100 } 101 102 long startTime = mClock.uptimeMillis(); 103 long nextTime = mQueuedRunnables.peek().mWhen; 104 if (nextTime <= startTime) { 105 return 0; 106 } 107 updateClock(nextTime); 108 109 return nextTime - startTime; 110 } 111 112 113 /** 114 * Advances the internal clock to the last item to run. 115 * 116 * The clock will only move forward. If the last item is set to run in the past or there is no 117 * next item, the clock does not change. 118 * 119 * @return The delta in uptimeMillis that the clock advanced, or 0 if the clock did not advance. 120 */ advanceClockToLast()121 public long advanceClockToLast() { 122 if (mQueuedRunnables.isEmpty()) { 123 return 0; 124 } 125 126 long startTime = mClock.uptimeMillis(); 127 long nextTime = Collections.max(mQueuedRunnables).mWhen; 128 if (nextTime <= startTime) { 129 return 0; 130 } 131 132 updateClock(nextTime); 133 134 return nextTime - startTime; 135 } 136 137 /** 138 * Returns the number of un-executed runnables waiting to run. 139 */ numPending()140 public int numPending() { 141 return mQueuedRunnables.size(); 142 } 143 144 @Override executeDelayed(Runnable r, long delay, TimeUnit unit)145 public Runnable executeDelayed(Runnable r, long delay, TimeUnit unit) { 146 if (delay < 0) { 147 delay = 0; 148 } 149 return executeAtTime(r, mClock.uptimeMillis() + unit.toMillis(delay)); 150 } 151 152 @Override executeAtTime(Runnable r, long uptime, TimeUnit unit)153 public Runnable executeAtTime(Runnable r, long uptime, TimeUnit unit) { 154 long uptimeMillis = unit.toMillis(uptime); 155 156 QueuedRunnable container = new QueuedRunnable(r, uptimeMillis); 157 158 mQueuedRunnables.offer(container); 159 160 return () -> mQueuedRunnables.remove(container); 161 } 162 163 @Override execute(Runnable command)164 public void execute(Runnable command) { 165 executeDelayed(command, 0); 166 } 167 isExecuting()168 public boolean isExecuting() { 169 return mExecuting; 170 } 171 172 /** 173 * Run all Executors in a loop until they all report they have no ready work to do. 174 * 175 * Useful if you have Executors the post work to other Executors, and you simply want to 176 * run them all until they stop posting work. 177 */ exhaustExecutors(FakeExecutor ....executors)178 public static void exhaustExecutors(FakeExecutor ...executors) { 179 boolean didAnything; 180 do { 181 didAnything = false; 182 for (FakeExecutor executor : executors) { 183 didAnything = didAnything || executor.runAllReady() != 0; 184 } 185 } while (didAnything); 186 } 187 updateClock(long nextTime)188 private void updateClock(long nextTime) { 189 mIgnoreClockUpdates = true; 190 mClock.setUptimeMillis(nextTime); 191 mIgnoreClockUpdates = false; 192 } 193 194 private static class QueuedRunnable implements Comparable<QueuedRunnable> { 195 private static AtomicInteger sCounter = new AtomicInteger(); 196 197 Runnable mRunnable; 198 long mWhen; 199 private int mCounter; 200 QueuedRunnable(Runnable r, long when)201 private QueuedRunnable(Runnable r, long when) { 202 mRunnable = r; 203 mWhen = when; 204 205 // PrioirityQueue orders items arbitrarily when equal. We want to ensure that 206 // otherwise-equal elements are ordered according to their insertion order. Because this 207 // class only is constructed right before insertion, we use a static counter to track 208 // insertion order of otherwise equal elements. 209 mCounter = sCounter.incrementAndGet(); 210 } 211 212 @Override compareTo(QueuedRunnable other)213 public int compareTo(QueuedRunnable other) { 214 long diff = mWhen - other.mWhen; 215 216 if (diff == 0) { 217 return mCounter - other.mCounter; 218 } 219 220 return diff > 0 ? 1 : -1; 221 } 222 } 223 } 224