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    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
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