1 /*
2  * Copyright (C) 2020 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.layoutlib.bridge.util;
18 
19 import com.android.tools.layoutlib.annotations.NotNull;
20 import com.android.tools.layoutlib.annotations.Nullable;
21 
22 import android.os.Handler;
23 import android.util.Pair;
24 
25 import java.util.LinkedList;
26 import java.util.WeakHashMap;
27 
28 /**
29  * A queue that stores {@link Runnable}s associated with the corresponding {@link Handler}s.
30  * {@link Runnable}s get automatically released when the corresponding {@link Handler} gets
31  * collected by the garbage collector. All {@link Runnable}s are queued in a single virtual queue
32  * with respect to their corresponding uptime (the time when they should be executed).
33  */
34 public class HandlerMessageQueue {
35     private final WeakHashMap<Handler, LinkedList<Pair<Long, Runnable>>> runnablesMap =
36             new WeakHashMap<>();
37 
38     /**
39      * Adds a {@link Runnable} associated with the {@link Handler} to be executed at
40      * particular time
41      * @param h handler associated with the {@link Runnable}
42      * @param uptimeMillis time in milliseconds the {@link Runnable} to be executed
43      * @param r {@link Runnable} to be added
44      */
add(@otNull Handler h, long uptimeMillis, @NotNull Runnable r)45     public void add(@NotNull Handler h, long uptimeMillis, @NotNull Runnable r) {
46         synchronized (runnablesMap) {
47             LinkedList<Pair<Long, Runnable>> runnables = runnablesMap.computeIfAbsent(h,
48                     k -> new LinkedList<>());
49 
50             int idx = 0;
51             while (idx < runnables.size()) {
52                 if (runnables.get(idx).first <= uptimeMillis) {
53                     idx++;
54                 } else {
55                     break;
56                 }
57             }
58             runnables.add(idx, Pair.create(uptimeMillis, r));
59         }
60     }
61 
62     private static class HandlerWrapper {
63         public Handler handler;
64     }
65 
66     /**
67      * Removes from the queue and returns the {@link Runnable} with the smallest uptime if it
68      * is less than the one passed as a parameter or null if such runnable does not exist.
69      * @param uptimeMillis
70      * @return the {@link Runnable} from the queue
71      */
72     @Nullable
extractFirst(long uptimeMillis)73     public Runnable extractFirst(long uptimeMillis) {
74         synchronized (runnablesMap) {
75             final HandlerWrapper w = new HandlerWrapper();
76             runnablesMap.forEach((h, l) -> {
77                 if (!l.isEmpty()) {
78                     long currentUptime = l.getFirst().first;
79                     if (currentUptime <= uptimeMillis) {
80                         if (w.handler == null || currentUptime <
81                                 runnablesMap.get(w.handler).getFirst().first) {
82                             w.handler = h;
83                         }
84                     }
85                 }
86             });
87             if (w.handler != null) {
88                 return runnablesMap.get(w.handler).pollFirst().second;
89             }
90             return null;
91         }
92     }
93 
94     /**
95      * @return true is queue has no runnables left
96      */
isNotEmpty()97     public boolean isNotEmpty() {
98         synchronized (runnablesMap) {
99             return runnablesMap.values().stream().anyMatch(l -> !l.isEmpty());
100         }
101     }
102 
103     /**
104      * @return number of runnables in the queue
105      */
size()106     public int size() {
107         synchronized (runnablesMap) {
108             return runnablesMap.values().stream().mapToInt(LinkedList::size).sum();
109         }
110     }
111 
112     /**
113      * Completely clears the entire queue
114      */
clear()115     public void clear() {
116         synchronized (runnablesMap) {
117             runnablesMap.clear();
118         }
119     }
120 }
121