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 31 /** 32 * Initializes a fake executor. 33 * 34 * @param clock FakeSystemClock allowing control over delayed runnables. It is strongly 35 * recommended that this clock have its auto-increment setting set to false to 36 * prevent unexpected advancement of the time. 37 */ FakeExecutor(FakeSystemClock clock)38 public FakeExecutor(FakeSystemClock clock) { 39 mClock = clock; 40 mClock.addListener(() -> { 41 if (!mIgnoreClockUpdates) { 42 runAllReady(); 43 } 44 }); 45 } 46 47 /** 48 * Runs a single runnable if it's scheduled to run according to the internal clock. 49 * 50 * If constructed to advance the clock automatically, this will advance the clock enough to 51 * run the next pending item. 52 * 53 * This method does not advance the clock past the item that was run. 54 * 55 * @return Returns true if an item was run. 56 */ runNextReady()57 public boolean runNextReady() { 58 if (!mQueuedRunnables.isEmpty() && mQueuedRunnables.peek().mWhen <= mClock.uptimeMillis()) { 59 mQueuedRunnables.poll().mRunnable.run(); 60 return true; 61 } 62 63 return false; 64 } 65 66 /** 67 * Runs all Runnables that are scheduled to run according to the internal clock. 68 * 69 * If constructed to advance the clock automatically, this will advance the clock enough to 70 * run all the pending items. This method does not advance the clock past items that were 71 * run. It is equivalent to calling {@link #runNextReady()} in a loop. 72 * 73 * @return Returns the number of items that ran. 74 */ runAllReady()75 public int runAllReady() { 76 int num = 0; 77 while (runNextReady()) { 78 num++; 79 } 80 81 return num; 82 } 83 84 /** 85 * Advances the internal clock to the next item to run. 86 * 87 * The clock will only move forward. If the next item is set to run in the past or there is no 88 * next item, the clock does not change. 89 * 90 * Note that this will cause one or more items to actually run. 91 * 92 * @return The delta in uptimeMillis that the clock advanced, or 0 if the clock did not advance. 93 */ advanceClockToNext()94 public long advanceClockToNext() { 95 if (mQueuedRunnables.isEmpty()) { 96 return 0; 97 } 98 99 long startTime = mClock.uptimeMillis(); 100 long nextTime = mQueuedRunnables.peek().mWhen; 101 if (nextTime <= startTime) { 102 return 0; 103 } 104 updateClock(nextTime); 105 106 return nextTime - startTime; 107 } 108 109 110 /** 111 * Advances the internal clock to the last item to run. 112 * 113 * The clock will only move forward. If the last item is set to run in the past or there is no 114 * next item, the clock does not change. 115 * 116 * @return The delta in uptimeMillis that the clock advanced, or 0 if the clock did not advance. 117 */ advanceClockToLast()118 public long advanceClockToLast() { 119 if (mQueuedRunnables.isEmpty()) { 120 return 0; 121 } 122 123 long startTime = mClock.uptimeMillis(); 124 long nextTime = Collections.max(mQueuedRunnables).mWhen; 125 if (nextTime <= startTime) { 126 return 0; 127 } 128 129 updateClock(nextTime); 130 131 return nextTime - startTime; 132 } 133 134 /** 135 * Returns the number of un-executed runnables waiting to run. 136 */ numPending()137 public int numPending() { 138 return mQueuedRunnables.size(); 139 } 140 141 @Override executeDelayed(Runnable r, long delay, TimeUnit unit)142 public Runnable executeDelayed(Runnable r, long delay, TimeUnit unit) { 143 if (delay < 0) { 144 delay = 0; 145 } 146 return executeAtTime(r, mClock.uptimeMillis() + unit.toMillis(delay)); 147 } 148 149 @Override executeAtTime(Runnable r, long uptime, TimeUnit unit)150 public Runnable executeAtTime(Runnable r, long uptime, TimeUnit unit) { 151 long uptimeMillis = unit.toMillis(uptime); 152 153 QueuedRunnable container = new QueuedRunnable(r, uptimeMillis); 154 155 mQueuedRunnables.offer(container); 156 157 return () -> mQueuedRunnables.remove(container); 158 } 159 160 @Override execute(Runnable command)161 public void execute(Runnable command) { 162 executeDelayed(command, 0); 163 } 164 165 /** 166 * Run all Executors in a loop until they all report they have no ready work to do. 167 * 168 * Useful if you have Executors the post work to other Executors, and you simply want to 169 * run them all until they stop posting work. 170 */ exhaustExecutors(FakeExecutor ....executors)171 public static void exhaustExecutors(FakeExecutor ...executors) { 172 boolean didAnything; 173 do { 174 didAnything = false; 175 for (FakeExecutor executor : executors) { 176 didAnything = didAnything || executor.runAllReady() != 0; 177 } 178 } while (didAnything); 179 } 180 updateClock(long nextTime)181 private void updateClock(long nextTime) { 182 mIgnoreClockUpdates = true; 183 mClock.setUptimeMillis(nextTime); 184 mIgnoreClockUpdates = false; 185 } 186 187 private static class QueuedRunnable implements Comparable<QueuedRunnable> { 188 private static AtomicInteger sCounter = new AtomicInteger(); 189 190 Runnable mRunnable; 191 long mWhen; 192 private int mCounter; 193 QueuedRunnable(Runnable r, long when)194 private QueuedRunnable(Runnable r, long when) { 195 mRunnable = r; 196 mWhen = when; 197 198 // PrioirityQueue orders items arbitrarily when equal. We want to ensure that 199 // otherwise-equal elements are ordered according to their insertion order. Because this 200 // class only is constructed right before insertion, we use a static counter to track 201 // insertion order of otherwise equal elements. 202 mCounter = sCounter.incrementAndGet(); 203 } 204 205 @Override compareTo(QueuedRunnable other)206 public int compareTo(QueuedRunnable other) { 207 long diff = mWhen - other.mWhen; 208 209 if (diff == 0) { 210 return mCounter - other.mCounter; 211 } 212 213 return diff > 0 ? 1 : -1; 214 } 215 } 216 } 217