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