1 /*
2  * Copyright 2018 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 androidx.recyclerview.widget;
18 
19 import android.os.AsyncTask;
20 import android.os.Handler;
21 import android.os.Looper;
22 import android.util.Log;
23 
24 import java.util.concurrent.Executor;
25 import java.util.concurrent.atomic.AtomicBoolean;
26 
27 class MessageThreadUtil<T> implements ThreadUtil<T> {
28 
29     @Override
getMainThreadProxy(final MainThreadCallback<T> callback)30     public MainThreadCallback<T> getMainThreadProxy(final MainThreadCallback<T> callback) {
31         return new MainThreadCallback<T>() {
32             final MessageQueue mQueue = new MessageQueue();
33             final private Handler mMainThreadHandler = new Handler(Looper.getMainLooper());
34 
35             static final int UPDATE_ITEM_COUNT = 1;
36             static final int ADD_TILE = 2;
37             static final int REMOVE_TILE = 3;
38 
39             @Override
40             public void updateItemCount(int generation, int itemCount) {
41                 sendMessage(SyncQueueItem.obtainMessage(UPDATE_ITEM_COUNT, generation, itemCount));
42             }
43 
44             @Override
45             public void addTile(int generation, TileList.Tile<T> tile) {
46                 sendMessage(SyncQueueItem.obtainMessage(ADD_TILE, generation, tile));
47             }
48 
49             @Override
50             public void removeTile(int generation, int position) {
51                 sendMessage(SyncQueueItem.obtainMessage(REMOVE_TILE, generation, position));
52             }
53 
54             private void sendMessage(SyncQueueItem msg) {
55                 mQueue.sendMessage(msg);
56                 mMainThreadHandler.post(mMainThreadRunnable);
57             }
58 
59             private Runnable mMainThreadRunnable = new Runnable() {
60                 @Override
61                 public void run() {
62                     SyncQueueItem msg = mQueue.next();
63                     while (msg != null) {
64                         switch (msg.what) {
65                             case UPDATE_ITEM_COUNT:
66                                 callback.updateItemCount(msg.arg1, msg.arg2);
67                                 break;
68                             case ADD_TILE:
69                                 //noinspection unchecked
70                                 callback.addTile(msg.arg1, (TileList.Tile<T>) msg.data);
71                                 break;
72                             case REMOVE_TILE:
73                                 callback.removeTile(msg.arg1, msg.arg2);
74                                 break;
75                             default:
76                                 Log.e("ThreadUtil", "Unsupported message, what=" + msg.what);
77                         }
78                         msg = mQueue.next();
79                     }
80                 }
81             };
82         };
83     }
84 
85     @Override
getBackgroundProxy(final BackgroundCallback<T> callback)86     public BackgroundCallback<T> getBackgroundProxy(final BackgroundCallback<T> callback) {
87         return new BackgroundCallback<T>() {
88             final MessageQueue mQueue = new MessageQueue();
89             private final Executor mExecutor = AsyncTask.THREAD_POOL_EXECUTOR;
90             AtomicBoolean mBackgroundRunning = new AtomicBoolean(false);
91 
92             static final int REFRESH = 1;
93             static final int UPDATE_RANGE = 2;
94             static final int LOAD_TILE = 3;
95             static final int RECYCLE_TILE = 4;
96 
97             @Override
98             public void refresh(int generation) {
99                 sendMessageAtFrontOfQueue(SyncQueueItem.obtainMessage(REFRESH, generation, null));
100             }
101 
102             @Override
103             public void updateRange(int rangeStart, int rangeEnd,
104                                     int extRangeStart, int extRangeEnd, int scrollHint) {
105                 sendMessageAtFrontOfQueue(SyncQueueItem.obtainMessage(UPDATE_RANGE,
106                         rangeStart, rangeEnd, extRangeStart, extRangeEnd, scrollHint, null));
107             }
108 
109             @Override
110             public void loadTile(int position, int scrollHint) {
111                 sendMessage(SyncQueueItem.obtainMessage(LOAD_TILE, position, scrollHint));
112             }
113 
114             @Override
115             public void recycleTile(TileList.Tile<T> tile) {
116                 sendMessage(SyncQueueItem.obtainMessage(RECYCLE_TILE, 0, tile));
117             }
118 
119             private void sendMessage(SyncQueueItem msg) {
120                 mQueue.sendMessage(msg);
121                 maybeExecuteBackgroundRunnable();
122             }
123 
124             private void sendMessageAtFrontOfQueue(SyncQueueItem msg) {
125                 mQueue.sendMessageAtFrontOfQueue(msg);
126                 maybeExecuteBackgroundRunnable();
127             }
128 
129             private void maybeExecuteBackgroundRunnable() {
130                 if (mBackgroundRunning.compareAndSet(false, true)) {
131                     mExecutor.execute(mBackgroundRunnable);
132                 }
133             }
134 
135             private Runnable mBackgroundRunnable = new Runnable() {
136                 @Override
137                 public void run() {
138                     while (true) {
139                         SyncQueueItem msg = mQueue.next();
140                         if (msg == null) {
141                             break;
142                         }
143                         switch (msg.what) {
144                             case REFRESH:
145                                 mQueue.removeMessages(REFRESH);
146                                 callback.refresh(msg.arg1);
147                                 break;
148                             case UPDATE_RANGE:
149                                 mQueue.removeMessages(UPDATE_RANGE);
150                                 mQueue.removeMessages(LOAD_TILE);
151                                 callback.updateRange(
152                                         msg.arg1, msg.arg2, msg.arg3, msg.arg4, msg.arg5);
153                                 break;
154                             case LOAD_TILE:
155                                 callback.loadTile(msg.arg1, msg.arg2);
156                                 break;
157                             case RECYCLE_TILE:
158                                 //noinspection unchecked
159                                 callback.recycleTile((TileList.Tile<T>) msg.data);
160                                 break;
161                             default:
162                                 Log.e("ThreadUtil", "Unsupported message, what=" + msg.what);
163                         }
164                     }
165                     mBackgroundRunning.set(false);
166                 }
167             };
168         };
169     }
170 
171     /**
172      * Replica of android.os.Message. Unfortunately, cannot use it without a Handler and don't want
173      * to create a thread just for this component.
174      */
175     static class SyncQueueItem {
176 
177         private static SyncQueueItem sPool;
178         private static final Object sPoolLock = new Object();
179         private SyncQueueItem next;
180         public int what;
181         public int arg1;
182         public int arg2;
183         public int arg3;
184         public int arg4;
185         public int arg5;
186         public Object data;
187 
188         void recycle() {
189             next = null;
190             what = arg1 = arg2 = arg3 = arg4 = arg5 = 0;
191             data = null;
192             synchronized (sPoolLock) {
193                 if (sPool != null) {
194                     next = sPool;
195                 }
196                 sPool = this;
197             }
198         }
199 
200         static SyncQueueItem obtainMessage(int what, int arg1, int arg2, int arg3, int arg4,
201                                            int arg5, Object data) {
202             synchronized (sPoolLock) {
203                 final SyncQueueItem item;
204                 if (sPool == null) {
205                     item = new SyncQueueItem();
206                 } else {
207                     item = sPool;
208                     sPool = sPool.next;
209                     item.next = null;
210                 }
211                 item.what = what;
212                 item.arg1 = arg1;
213                 item.arg2 = arg2;
214                 item.arg3 = arg3;
215                 item.arg4 = arg4;
216                 item.arg5 = arg5;
217                 item.data = data;
218                 return item;
219             }
220         }
221 
222         static SyncQueueItem obtainMessage(int what, int arg1, int arg2) {
223             return obtainMessage(what, arg1, arg2, 0, 0, 0, null);
224         }
225 
226         static SyncQueueItem obtainMessage(int what, int arg1, Object data) {
227             return obtainMessage(what, arg1, 0, 0, 0, 0, data);
228         }
229     }
230 
231     static class MessageQueue {
232 
233         private SyncQueueItem mRoot;
234 
235         synchronized SyncQueueItem next() {
236             if (mRoot == null) {
237                 return null;
238             }
239             final SyncQueueItem next = mRoot;
240             mRoot = mRoot.next;
241             return next;
242         }
243 
244         synchronized void sendMessageAtFrontOfQueue(SyncQueueItem item) {
245             item.next = mRoot;
246             mRoot = item;
247         }
248 
249         synchronized void sendMessage(SyncQueueItem item) {
250             if (mRoot == null) {
251                 mRoot = item;
252                 return;
253             }
254             SyncQueueItem last = mRoot;
255             while (last.next != null) {
256                 last = last.next;
257             }
258             last.next = item;
259         }
260 
261         synchronized void removeMessages(int what) {
262             while (mRoot != null && mRoot.what == what) {
263                 SyncQueueItem item = mRoot;
264                 mRoot = mRoot.next;
265                 item.recycle();
266             }
267             if (mRoot != null) {
268                 SyncQueueItem prev = mRoot;
269                 SyncQueueItem item = prev.next;
270                 while (item != null) {
271                     SyncQueueItem next = item.next;
272                     if (item.what == what) {
273                         prev.next = next;
274                         item.recycle();
275                     } else {
276                         prev = item;
277                     }
278                     item = next;
279                 }
280             }
281         }
282     }
283 }
284