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 java.lang.annotation.ElementType.FIELD; 20 import static java.lang.annotation.ElementType.LOCAL_VARIABLE; 21 import static java.lang.annotation.ElementType.METHOD; 22 import static java.lang.annotation.ElementType.PARAMETER; 23 import static java.lang.annotation.RetentionPolicy.CLASS; 24 25 import org.jspecify.annotations.NonNull; 26 27 import java.lang.annotation.Retention; 28 import java.lang.annotation.Target; 29 import java.util.ArrayList; 30 import java.util.List; 31 import java.util.concurrent.CountDownLatch; 32 import java.util.concurrent.TimeUnit; 33 34 /** 35 * This is a ItemAnimator test double implementation that does not actually run any animations. 36 * Tests that utilize this class must manually end animations during (or at the end of) test 37 * execution. 38 * 39 * 1. Test MUST call endAnimation(ViewHolder) on UI thread to finish animation of a given ViewHolder 40 * Or Test calls endAnimations() on UI thread to end animations for all. 41 * 2. Test can call getAddAnimations() etc. to get ViewHolders that currently running animation. 42 * 3. Test can call {@link #expect(int, int)} and {@link #waitFor(int)} to wait given 43 * Events are fired. 44 */ 45 public class ItemAnimatorTestDouble extends SimpleItemAnimator { 46 47 static final long TIMEOUT_SECOND = 10; 48 49 ArrayList<RecyclerView.ViewHolder> mAdds = new ArrayList<>(); 50 ArrayList<RecyclerView.ViewHolder> mRemoves = new ArrayList<>(); 51 ArrayList<RecyclerView.ViewHolder> mMoves = new ArrayList<>(); 52 ArrayList<RecyclerView.ViewHolder> mChangesOld = new ArrayList<>(); 53 ArrayList<RecyclerView.ViewHolder> mChangesNew = new ArrayList<>(); 54 55 @Retention(CLASS) 56 @Target({PARAMETER, METHOD, LOCAL_VARIABLE, FIELD}) 57 public @interface CountDownLatchIndex { 58 } 59 60 @CountDownLatchIndex 61 public static final int ADD_START = 0; 62 63 @CountDownLatchIndex 64 public static final int ADD_FINISHED = 1; 65 66 @CountDownLatchIndex 67 public static final int REMOVE_START = 2; 68 69 @CountDownLatchIndex 70 public static final int REMOVE_FINISHED = 3; 71 72 @CountDownLatchIndex 73 public static final int MOVE_START = 4; 74 75 @CountDownLatchIndex 76 public static final int MOVE_FINISHED = 5; 77 78 @CountDownLatchIndex 79 public static final int CHANGE_OLD_START = 6; 80 81 @CountDownLatchIndex 82 public static final int CHANGE_OLD_FINISHED = 7; 83 84 @CountDownLatchIndex 85 public static final int CHANGE_NEW_START = 8; 86 87 @CountDownLatchIndex 88 public static final int CHANGE_NEW_FINISHED = 9; 89 90 static final int NUM_COUNT_DOWN_LATCH = 10; 91 92 CountDownLatch[] mCountDownLatches = new CountDownLatch[NUM_COUNT_DOWN_LATCH]; 93 94 getAddAnimations()95 public List<RecyclerView.ViewHolder> getAddAnimations() { 96 return mAdds; 97 } 98 getRemoveAnimations()99 public List<RecyclerView.ViewHolder> getRemoveAnimations() { 100 return mRemoves; 101 } 102 getMovesAnimations()103 public List<RecyclerView.ViewHolder> getMovesAnimations() { 104 return mMoves; 105 } 106 getChangesOldAnimations()107 public List<RecyclerView.ViewHolder> getChangesOldAnimations() { 108 return mChangesOld; 109 } 110 getChangesNewAnimations()111 public List<RecyclerView.ViewHolder> getChangesNewAnimations() { 112 return mChangesNew; 113 } 114 115 @Override animateRemove(RecyclerView.@onNull ViewHolder holder)116 public boolean animateRemove(RecyclerView.@NonNull ViewHolder holder) { 117 mRemoves.add(holder); 118 dispatchRemoveStarting(holder); 119 return false; 120 } 121 122 @Override animateAdd(RecyclerView.@onNull ViewHolder holder)123 public boolean animateAdd(RecyclerView.@NonNull ViewHolder holder) { 124 mAdds.add(holder); 125 dispatchAddStarting(holder); 126 return false; 127 } 128 129 @Override animateMove(RecyclerView.@onNull ViewHolder holder, int fromX, int fromY, int toX, int toY)130 public boolean animateMove(RecyclerView.@NonNull ViewHolder holder, int fromX, int fromY, 131 int toX, int toY) { 132 mMoves.add(holder); 133 dispatchMoveStarting(holder); 134 return false; 135 } 136 137 @Override animateChange(RecyclerView.@onNull ViewHolder oldHolder, RecyclerView.ViewHolder newHolder, int fromLeft, int fromTop, int toLeft, int toTop)138 public boolean animateChange(RecyclerView.@NonNull ViewHolder oldHolder, 139 RecyclerView.ViewHolder newHolder, int fromLeft, int fromTop, int toLeft, int toTop) { 140 mChangesOld.add(oldHolder); 141 mChangesNew.add(newHolder); 142 dispatchChangeStarting(oldHolder, true); 143 dispatchChangeStarting(newHolder, false); 144 return false; 145 } 146 expect(@ountDownLatchIndex int index, int count)147 public void expect(@CountDownLatchIndex int index, int count) { 148 mCountDownLatches[index] = new CountDownLatch(count); 149 } 150 waitFor(@ountDownLatchIndex int index)151 public boolean waitFor(@CountDownLatchIndex int index) 152 throws InterruptedException { 153 return mCountDownLatches[index].await(TIMEOUT_SECOND, TimeUnit.SECONDS); 154 } 155 156 @Override onChangeStarting(RecyclerView.ViewHolder item, boolean oldItem)157 public void onChangeStarting(RecyclerView.ViewHolder item, boolean oldItem) { 158 CountDownLatch latch = mCountDownLatches[oldItem ? CHANGE_OLD_START : CHANGE_NEW_START]; 159 if (latch != null) { 160 latch.countDown(); 161 } 162 } 163 164 @Override onMoveStarting(RecyclerView.ViewHolder item)165 public void onMoveStarting(RecyclerView.ViewHolder item) { 166 CountDownLatch latch = mCountDownLatches[MOVE_START]; 167 if (latch != null) { 168 latch.countDown(); 169 } 170 } 171 172 @Override onAddStarting(RecyclerView.ViewHolder item)173 public void onAddStarting(RecyclerView.ViewHolder item) { 174 CountDownLatch latch = mCountDownLatches[ADD_START]; 175 if (latch != null) { 176 latch.countDown(); 177 } 178 } 179 180 @Override onRemoveStarting(RecyclerView.ViewHolder item)181 public void onRemoveStarting(RecyclerView.ViewHolder item) { 182 CountDownLatch latch = mCountDownLatches[REMOVE_START]; 183 if (latch != null) { 184 latch.countDown(); 185 } 186 } 187 188 @Override onChangeFinished(RecyclerView.ViewHolder item, boolean oldItem)189 public void onChangeFinished(RecyclerView.ViewHolder item, boolean oldItem) { 190 CountDownLatch latch = mCountDownLatches[oldItem 191 ? CHANGE_OLD_FINISHED : CHANGE_NEW_FINISHED]; 192 if (latch != null) { 193 latch.countDown(); 194 } 195 } 196 197 @Override onMoveFinished(RecyclerView.ViewHolder item)198 public void onMoveFinished(RecyclerView.ViewHolder item) { 199 CountDownLatch latch = mCountDownLatches[MOVE_FINISHED]; 200 if (latch != null) { 201 latch.countDown(); 202 } 203 } 204 205 @Override onAddFinished(RecyclerView.ViewHolder item)206 public void onAddFinished(RecyclerView.ViewHolder item) { 207 CountDownLatch latch = mCountDownLatches[ADD_FINISHED]; 208 if (latch != null) { 209 latch.countDown(); 210 } 211 } 212 213 @Override onRemoveFinished(RecyclerView.ViewHolder item)214 public void onRemoveFinished(RecyclerView.ViewHolder item) { 215 CountDownLatch latch = mCountDownLatches[REMOVE_FINISHED]; 216 if (latch != null) { 217 latch.countDown(); 218 } 219 } 220 221 @Override runPendingAnimations()222 public void runPendingAnimations() { 223 } 224 225 @Override endAnimation(RecyclerView.@onNull ViewHolder item)226 public void endAnimation(RecyclerView.@NonNull ViewHolder item) { 227 if (mAdds.remove(item)) { 228 dispatchAddFinished(item); 229 } else if (mRemoves.remove(item)) { 230 dispatchRemoveFinished(item); 231 } else if (mMoves.remove(item)) { 232 dispatchMoveFinished(item); 233 } else if (mChangesOld.remove(item)) { 234 dispatchChangeFinished(item, true); 235 } else if (mChangesNew.remove(item)) { 236 dispatchChangeFinished(item, false); 237 } 238 } 239 240 @Override endAnimations()241 public void endAnimations() { 242 for (int i = mAdds.size() - 1; i >= 0; i--) { 243 endAnimation(mAdds.get(i)); 244 } 245 for (int i = mRemoves.size() - 1; i >= 0; i--) { 246 endAnimation(mRemoves.get(i)); 247 } 248 for (int i = mMoves.size() - 1; i >= 0; i--) { 249 endAnimation(mMoves.get(i)); 250 } 251 for (int i = mChangesOld.size() - 1; i >= 0; i--) { 252 endAnimation(mChangesOld.get(i)); 253 } 254 for (int i = mChangesNew.size() - 1; i >= 0; i--) { 255 endAnimation(mChangesNew.get(i)); 256 } 257 } 258 259 @Override isRunning()260 public boolean isRunning() { 261 return mAdds.size() != 0 262 || mRemoves.size() != 0 263 || mMoves.size() != 0 264 || mChangesOld.size() != 0 265 || mChangesNew.size() != 0; 266 } 267 } 268