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
17package android.support.v7.util;
18
19import android.os.Handler;
20import android.os.Looper;
21import android.support.v4.content.ParallelExecutorCompat;
22import android.util.Log;
23
24import java.util.concurrent.Executor;
25import java.util.concurrent.atomic.AtomicBoolean;
26
27class MessageThreadUtil<T> implements ThreadUtil<T> {
28
29    @Override
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
86    public BackgroundCallback<T> getBackgroundProxy(final BackgroundCallback<T> callback) {
87        return new BackgroundCallback<T>() {
88            final MessageQueue mQueue = new MessageQueue();
89            final private Executor mExecutor = ParallelExecutorCompat.getParallelExecutor();
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