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