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 static org.hamcrest.CoreMatchers.is;
20 import static org.hamcrest.CoreMatchers.not;
21 import static org.hamcrest.CoreMatchers.notNullValue;
22 import static org.hamcrest.CoreMatchers.sameInstance;
23 import static org.hamcrest.MatcherAssert.assertThat;
24 
25 import android.os.Looper;
26 import android.support.test.filters.MediumTest;
27 import android.support.test.rule.ActivityTestRule;
28 import android.support.test.runner.AndroidJUnit4;
29 
30 import org.junit.Before;
31 import org.junit.Rule;
32 import org.junit.Test;
33 import org.junit.runner.RunWith;
34 
35 import java.util.HashMap;
36 import java.util.Map;
37 import java.util.concurrent.Semaphore;
38 import java.util.concurrent.TimeUnit;
39 
40 @RunWith(AndroidJUnit4.class)
41 @MediumTest
42 public class ThreadUtilTest {
43     @Rule
44     public ActivityTestRule<TestActivity> mActivityRule =
45             new ActivityTestRule<>(TestActivity.class);
46 
47     Map<String, LockedObject> results = new HashMap<>();
48 
49     ThreadUtil.MainThreadCallback<Integer> mMainThreadProxy;
50     ThreadUtil.BackgroundCallback<Integer> mBackgroundProxy;
51 
52     @Before
setup()53     public void setup() throws Throwable {
54         mActivityRule.runOnUiThread(new Runnable() {
55             @Override
56             public void run() {
57                 ThreadUtil<Integer> threadUtil = new MessageThreadUtil<>();
58 
59                 mMainThreadProxy = threadUtil.getMainThreadProxy(
60                         new ThreadUtil.MainThreadCallback<Integer>() {
61                             @Override
62                             public void updateItemCount(int generation, int itemCount) {
63                                 assertMainThread();
64                                 setResultData("updateItemCount", generation, itemCount);
65                             }
66 
67                             @Override
68                             public void addTile(int generation, TileList.Tile<Integer> data) {
69                                 assertMainThread();
70                                 setResultData("addTile", generation, data);
71                             }
72 
73                             @Override
74                             public void removeTile(int generation, int position) {
75                                 assertMainThread();
76                                 setResultData("removeTile", generation, position);
77                             }
78                         });
79 
80                 mBackgroundProxy = threadUtil.getBackgroundProxy(
81                         new ThreadUtil.BackgroundCallback<Integer>() {
82                             @Override
83                             public void refresh(int generation) {
84                                 assertBackgroundThread();
85                                 setResultData("refresh", generation);
86                             }
87 
88                             @Override
89                             public void updateRange(int rangeStart, int rangeEnd, int extRangeStart,
90                                     int extRangeEnd, int scrollHint) {
91                                 assertBackgroundThread();
92                                 setResultData("updateRange", rangeStart, rangeEnd,
93                                         extRangeStart, extRangeEnd, scrollHint);
94                             }
95 
96                             @Override
97                             public void loadTile(int position, int scrollHint) {
98                                 assertBackgroundThread();
99                                 setResultData("loadTile", position, scrollHint);
100                             }
101 
102                             @Override
103                             public void recycleTile(TileList.Tile<Integer> data) {
104                                 assertBackgroundThread();
105                                 setResultData("recycleTile", data);
106                             }
107                         });
108             }
109         });
110     }
111 
112     @Test
updateItemCount()113     public void updateItemCount() throws InterruptedException {
114         initWait("updateItemCount");
115         // In this test and below the calls to mMainThreadProxy are not really made from the UI
116         // thread. That's OK since the message queue inside mMainThreadProxy is synchronized.
117         mMainThreadProxy.updateItemCount(7, 9);
118         Object[] data = waitFor("updateItemCount");
119         assertThat(data, is(new Object[]{7, 9}));
120     }
121 
122     @Test
addTile()123     public void addTile() throws InterruptedException {
124         initWait("addTile");
125         TileList.Tile<Integer> tile = new TileList.Tile<Integer>(Integer.class, 10);
126         mMainThreadProxy.addTile(3, tile);
127         Object[] data = waitFor("addTile");
128         assertThat(data, is(new Object[]{3, tile}));
129     }
130 
131     @Test
removeTile()132     public void removeTile() throws InterruptedException {
133         initWait("removeTile");
134         mMainThreadProxy.removeTile(1, 2);
135         Object[] data = waitFor("removeTile");
136         assertThat(data, is(new Object[]{1, 2}));
137     }
138 
139     @Test
refresh()140     public void refresh() throws InterruptedException {
141         initWait("refresh");
142         // In this test and below the calls to mBackgroundProxy are not really made from the worker
143         // thread. That's OK since the message queue inside mBackgroundProxy is synchronized.
144         mBackgroundProxy.refresh(2);
145         Object[] data = waitFor("refresh");
146         assertThat(data, is(new Object[]{2}));
147     }
148 
149     @Test
rangeUpdate()150     public void rangeUpdate() throws InterruptedException {
151         initWait("updateRange");
152         mBackgroundProxy.updateRange(10, 20, 5, 25, 1);
153         Object[] data = waitFor("updateRange");
154         assertThat(data, is(new Object[] {10, 20, 5, 25, 1}));
155     }
156 
157     @Test
loadTile()158     public void loadTile() throws InterruptedException {
159         initWait("loadTile");
160         mBackgroundProxy.loadTile(2, 1);
161         Object[] data = waitFor("loadTile");
162         assertThat(data, is(new Object[]{2, 1}));
163     }
164 
165     @Test
recycleTile()166     public void recycleTile() throws InterruptedException {
167         initWait("recycleTile");
168         TileList.Tile<Integer> tile = new TileList.Tile<Integer>(Integer.class, 10);
169         mBackgroundProxy.recycleTile(tile);
170         Object[] data = waitFor("recycleTile");
171         assertThat(data, is(new Object[]{tile}));
172     }
173 
assertMainThread()174     private void assertMainThread() {
175         assertThat(Looper.myLooper(), notNullValue());
176         assertThat(Looper.myLooper(), sameInstance(Looper.getMainLooper()));
177     }
178 
assertBackgroundThread()179     private void assertBackgroundThread() {
180         assertThat(Looper.myLooper(), not(Looper.getMainLooper()));
181     }
182 
initWait(String key)183     private void initWait(String key) throws InterruptedException {
184         results.put(key, new LockedObject());
185     }
186 
waitFor(String key)187     private Object[] waitFor(String key) throws InterruptedException {
188         return results.get(key).waitFor();
189     }
190 
setResultData(String key, Object... args)191     private void setResultData(String key, Object... args) {
192         if (results.containsKey(key)) {
193             results.get(key).set(args);
194         }
195     }
196 
197     private class LockedObject {
198         private Semaphore mLock = new Semaphore(1);
199         private volatile Object[] mArgs;
200 
LockedObject()201         public LockedObject() {
202             mLock.drainPermits();
203         }
204 
set(Object... args)205         public void set(Object... args) {
206             mArgs = args;
207             mLock.release(1);
208         }
209 
waitFor()210         public Object[] waitFor() throws InterruptedException {
211             mLock.tryAcquire(1, 2, TimeUnit.SECONDS);
212             return mArgs;
213         }
214     }
215 }
216