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.ide.common.rendering.api.ILayoutLog;
20 import com.android.tools.layoutlib.annotations.NotNull;
21 
22 import android.os.SystemClock_Delegate;
23 import android.util.TimeUtils;
24 import android.view.Choreographer.FrameCallback;
25 
26 import java.util.ArrayList;
27 import java.util.List;
28 
29 /**
30  * Manages {@link android.view.Choreographer} callbacks. Keeps track of the currently active
31  * callbacks and allows to execute callbacks if their time is due.
32  */
33 public class ChoreographerCallbacks {
34     // Simple wrapper around ArrayList to be able to use protected removeRange method
35     private static class RangeList<T> extends ArrayList<T> {
removeFrontElements(int n)36         private void removeFrontElements(int n) {
37             removeRange(0, n);
38         }
39     }
40 
41     private static class Callback {
42         private final Object mAction;
43         private final Object mToken;
44         private final long mDueTime;
45 
Callback(@otNull Object action, Object token, long dueTime)46         private Callback(@NotNull Object action, Object token, long dueTime) {
47             mAction = action;
48             mToken = token;
49             mDueTime = dueTime;
50         }
51     }
52 
53     private final RangeList<Callback> mCallbacks = new RangeList<>();
54 
add(Object action, Object token, long delayMillis)55     public void add(Object action, Object token, long delayMillis) {
56         synchronized (mCallbacks) {
57             int idx = 0;
58             final long now = SystemClock_Delegate.uptimeMillis();
59             final long dueTime = now + delayMillis;
60             while (idx < mCallbacks.size()) {
61                 if (mCallbacks.get(idx).mDueTime > dueTime) {
62                     break;
63                 } else {
64                     ++idx;
65                 }
66             }
67             mCallbacks.add(idx, new Callback(action, token, dueTime));
68         }
69     }
70 
remove(Object action, Object token)71     public void remove(Object action, Object token) {
72         synchronized (mCallbacks) {
73             mCallbacks.removeIf(el -> (action == null || el.mAction == action)
74                     && (token == null || el.mToken == token));
75         }
76     }
77 
execute(long currentTimeMs, @NotNull ILayoutLog logger)78     public void execute(long currentTimeMs, @NotNull ILayoutLog logger) {
79         final long currentTimeNanos = currentTimeMs * TimeUtils.NANOS_PER_MS;
80         List<Callback> toExecute;
81         synchronized (mCallbacks) {
82             int idx = 0;
83             while (idx < mCallbacks.size()) {
84                 if (mCallbacks.get(idx).mDueTime > currentTimeMs) {
85                     break;
86                 } else {
87                     ++idx;
88                 }
89             }
90             toExecute = new ArrayList<>(mCallbacks.subList(0, idx));
91             mCallbacks.removeFrontElements(idx);
92         }
93 
94         // We run the callbacks outside of the synchronized block to avoid deadlocks caused by
95         // callbacks calling back into ChoreographerCallbacks.
96         toExecute.forEach(p -> executeSafely(p.mAction, currentTimeNanos, logger));
97     }
98 
clear()99     public void clear() {
100         synchronized (mCallbacks) {
101             mCallbacks.clear();
102         }
103     }
104 
executeSafely(@otNull Object action, long frameTimeNanos, @NotNull ILayoutLog logger)105     private static void executeSafely(@NotNull Object action, long frameTimeNanos,
106             @NotNull ILayoutLog logger) {
107         try {
108             if (action instanceof FrameCallback) {
109                 FrameCallback callback = (FrameCallback) action;
110                 callback.doFrame(frameTimeNanos);
111             } else if (action instanceof Runnable) {
112                 Runnable runnable = (Runnable) action;
113                 runnable.run();
114             } else {
115                 logger.error(ILayoutLog.TAG_BROKEN,
116                         "Unexpected action as Choreographer callback", (Object) null, null);
117             }
118         } catch (Throwable t) {
119             logger.error(ILayoutLog.TAG_BROKEN, "Failed executing Choreographer callback", t,
120                     null, null);
121         }
122     }
123 }
124