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