1 /* 2 * Copyright (C) 2020 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.car.testapi; 18 19 import android.annotation.NonNull; 20 import android.annotation.Nullable; 21 import android.annotation.UserIdInt; 22 import android.car.user.CarUserManager; 23 import android.car.user.CarUserManager.UserLifecycleEvent; 24 import android.car.user.CarUserManager.UserLifecycleListener; 25 import android.util.Log; 26 27 import com.android.car.internal.common.CommonConstants.UserLifecycleEventType; 28 import com.android.internal.annotations.GuardedBy; 29 30 import java.util.ArrayList; 31 import java.util.Collections; 32 import java.util.List; 33 import java.util.concurrent.CountDownLatch; 34 import java.util.concurrent.TimeUnit; 35 import java.util.stream.Collectors; 36 37 /** 38 * {@link UserLifecycleListener} that blocks until the proper events are received. 39 * 40 * <p>It can be used in 2 "modes": 41 * 42 * <ul> 43 * <li>{@link #forAnyEvent()}: it blocks (through the {@link #waitForAnyEvent()} call) until any 44 * any event is received. It doesn't allow any customization (other than 45 * {@link Builder#setTimeout(long)}). 46 * <li>{@link #forSpecificEvents()}: it blocks (through the {@link #waitForEvents()} call) until 47 * all events specified by the {@link Builder} are received. 48 * <li>{@link #forNoExpectedEvent()} ()}: it blocks (through the {@link #waitForEvents()} call) 49 * only for the specified timeout, but does not anticipate that the expected events are received. 50 * </ul> 51 */ 52 public final class BlockingUserLifecycleListener implements UserLifecycleListener { 53 54 private static final String TAG = BlockingUserLifecycleListener.class.getSimpleName(); 55 56 private static final long DEFAULT_TIMEOUT_MS = 2_000; 57 58 private static int sNextId; 59 60 private final Object mLock = new Object(); 61 62 private final CountDownLatch mLatch = new CountDownLatch(1); 63 64 @GuardedBy("mLock") 65 private final List<UserLifecycleEvent> mAllReceivedEvents = new ArrayList<>(); 66 @GuardedBy("mLock") 67 private final List<UserLifecycleEvent> mExpectedEventsReceived = new ArrayList<>(); 68 69 @UserLifecycleEventType 70 private final List<Integer> mExpectedEventTypes; 71 72 @UserLifecycleEventType 73 private final List<Integer> mExpectedEventTypesLeft; 74 75 @UserIdInt 76 @Nullable 77 private final Integer mForUserId; 78 79 @UserIdInt 80 @Nullable 81 private final Integer mForPreviousUserId; 82 83 private final long mTimeoutMs; 84 85 // This indicates if the listener does not expect any events. 86 private final boolean mForNoEvents; 87 88 private final int mId = ++sNextId; 89 BlockingUserLifecycleListener(Builder builder)90 private BlockingUserLifecycleListener(Builder builder) { 91 mExpectedEventTypes = Collections 92 .unmodifiableList(new ArrayList<>(builder.mExpectedEventTypes)); 93 mExpectedEventTypesLeft = builder.mExpectedEventTypes; 94 mTimeoutMs = builder.mTimeoutMs; 95 mForNoEvents = builder.mForNoEvents; 96 mForUserId = builder.mForUserId; 97 mForPreviousUserId = builder.mForPreviousUserId; 98 Log.d(TAG, "constructor: " + this); 99 } 100 101 /** 102 * Creates a builder for tests that need to wait for an arbitrary event. 103 */ forAnyEvent()104 public static Builder forAnyEvent() { 105 return new Builder(/* forAnyEvent= */ true, /* forNoEvents= */ false); 106 } 107 108 /** 109 * Creates a builder for tests that need to wait for specific events. 110 */ forSpecificEvents()111 public static Builder forSpecificEvents() { 112 return new Builder(/* forAnyEvent= */ false, /* forNoEvents= */ false); 113 } 114 115 /** 116 * Creates a builder for tests that don't expect any specific event to occur. 117 */ forNoExpectedEvent()118 public static Builder forNoExpectedEvent() { 119 return new Builder(/* forAnyEvent= */ false, /* forNoEvents= */ true); 120 } 121 122 /** 123 * Builder for a customized {@link BlockingUserLifecycleListener} instance. 124 */ 125 public static final class Builder { 126 private long mTimeoutMs = DEFAULT_TIMEOUT_MS; 127 private final boolean mForAnyEvent; 128 private final boolean mForNoEvents; 129 Builder(boolean forAnyEvent, boolean forNoEvents)130 private Builder(boolean forAnyEvent, boolean forNoEvents) { 131 mForAnyEvent = forAnyEvent; 132 mForNoEvents = forNoEvents; 133 } 134 135 @UserLifecycleEventType 136 private final List<Integer> mExpectedEventTypes = new ArrayList<>(); 137 138 @UserIdInt 139 @Nullable 140 private Integer mForUserId; 141 142 @UserIdInt 143 @Nullable 144 private Integer mForPreviousUserId; 145 146 /** 147 * Sets the timeout. 148 */ setTimeout(long timeoutMs)149 public Builder setTimeout(long timeoutMs) { 150 mTimeoutMs = timeoutMs; 151 return this; 152 } 153 154 /** 155 * Sets the expected type - once the given event is received, the listener will unblock. 156 * 157 * @throws IllegalStateException if builder is {@link #forAnyEvent}. 158 * @throws IllegalArgumentException if the expected type was already added. 159 */ addExpectedEvent(@serLifecycleEventType int eventType)160 public Builder addExpectedEvent(@UserLifecycleEventType int eventType) { 161 assertNotForAnyEvent(); 162 mExpectedEventTypes.add(eventType); 163 return this; 164 } 165 166 /** 167 * Filters received events just for the given user. 168 */ forUser(@serIdInt int userId)169 public Builder forUser(@UserIdInt int userId) { 170 assertNotForAnyEvent(); 171 mForUserId = userId; 172 return this; 173 } 174 175 /** 176 * Filters received events just for the given previous user. 177 */ forPreviousUser(@serIdInt int userId)178 public Builder forPreviousUser(@UserIdInt int userId) { 179 assertNotForAnyEvent(); 180 mForPreviousUserId = userId; 181 return this; 182 } 183 184 /** 185 * Builds a new instance. 186 */ 187 @NonNull build()188 public BlockingUserLifecycleListener build() { 189 return new BlockingUserLifecycleListener(Builder.this); 190 } 191 assertNotForAnyEvent()192 private void assertNotForAnyEvent() { 193 checkState(!mForAnyEvent, "not allowed forAnyEvent()"); 194 } 195 } 196 197 @Override onEvent(UserLifecycleEvent event)198 public void onEvent(UserLifecycleEvent event) { 199 synchronized (mLock) { 200 Log.d(TAG, "onEvent(): expecting=" + mExpectedEventTypesLeft + ", received=" + event); 201 202 mAllReceivedEvents.add(event); 203 204 if (expectingSpecificUser() && event.getUserHandle().getIdentifier() != mForUserId) { 205 Log.w(TAG, "ignoring event for different user (expecting " + mForUserId + ")"); 206 return; 207 } 208 209 if (expectingSpecificPreviousUser() 210 && event.getPreviousUserId() != mForPreviousUserId) { 211 Log.w(TAG, "ignoring event for different previous user (expecting " 212 + mForPreviousUserId + ")"); 213 return; 214 } 215 216 Integer actualType = event.getEventType(); 217 boolean removed = mExpectedEventTypesLeft.remove(actualType); 218 if (removed) { 219 Log.v(TAG, "event removed; still expecting for " 220 + toString(mExpectedEventTypesLeft)); 221 mExpectedEventsReceived.add(event); 222 } else { 223 Log.v(TAG, "event not removed"); 224 } 225 226 if (mExpectedEventTypesLeft.isEmpty() && mLatch.getCount() == 1) { 227 Log.d(TAG, "all expected events received, counting down " + mLatch); 228 mLatch.countDown(); 229 } 230 } 231 } 232 233 /** 234 * Blocks until any event is received, and returns it. 235 * 236 * @throws IllegalStateException if listener was built using {@link #forSpecificEvents()}. 237 * @throws IllegalStateException if it times out before any event is received. 238 * @throws InterruptedException if interrupted before any event is received. 239 */ 240 @Nullable waitForAnyEvent()241 public UserLifecycleEvent waitForAnyEvent() throws InterruptedException { 242 checkState(isForAnyEvent(), 243 "cannot call waitForEvent() when built with expected events"); 244 waitForExpectedEvents(); 245 246 UserLifecycleEvent event; 247 synchronized (mLock) { 248 event = mAllReceivedEvents.isEmpty() ? null : mAllReceivedEvents.get(0); 249 Log.v(TAG, "waitForAnyEvent(): returning " + event); 250 } 251 return event; 252 } 253 254 /** 255 * Blocks until the events specified in the {@link Builder} are received, and returns them. 256 * 257 * @throws IllegalStateException if listener was built without any call to 258 * {@link Builder#addExpectedEvent(int)} or using {@link #forAnyEvent()}. 259 * Also throws if it times out before all specified events are received. 260 * @throws InterruptedException if interrupted before all specified events are received. 261 */ 262 @NonNull waitForEvents()263 public List<UserLifecycleEvent> waitForEvents() throws InterruptedException { 264 checkState(!isForAnyEvent(), 265 "cannot call waitForEvents() when built without specific expected events"); 266 waitForExpectedEvents(); 267 List<UserLifecycleEvent> events; 268 synchronized (mLock) { 269 events = mExpectedEventsReceived; 270 } 271 Log.v(TAG, "waitForEvents(): returning " + events); 272 return events; 273 } 274 275 /** 276 * Gets a list with all received events until now. 277 */ 278 @NonNull getAllReceivedEvents()279 public List<UserLifecycleEvent> getAllReceivedEvents() { 280 checkState(!isForAnyEvent(), 281 "cannot call getAllReceivedEvents() when built without specific expected events"); 282 synchronized (mLock) { 283 return Collections.unmodifiableList(new ArrayList<>(mAllReceivedEvents)); 284 } 285 } 286 287 @Override toString()288 public String toString() { 289 return "[" + getClass().getSimpleName() + ": " + stateToString() + "]"; 290 } 291 292 @NonNull stateToString()293 private String stateToString() { 294 synchronized (mLock) { 295 return "id=" + mId + ",timeout=" + mTimeoutMs + "ms" 296 + ",expectedEventTypes=" + toString(mExpectedEventTypes) 297 + ",expectedEventTypesLeft=" + toString(mExpectedEventTypesLeft) 298 + (expectingSpecificUser() ? ",forUser=" + mForUserId : "") 299 + (expectingSpecificPreviousUser() ? ",forPrevUser=" + mForPreviousUserId : "") 300 + ",received=" + mAllReceivedEvents 301 + ",waiting=" + mExpectedEventTypesLeft; 302 } 303 } 304 waitForExpectedEvents()305 private void waitForExpectedEvents() throws InterruptedException { 306 boolean mLatchResult = mLatch.await(mTimeoutMs, TimeUnit.MILLISECONDS); 307 308 if (mForNoEvents) { 309 // Do not throw an exception if no events are expected. 310 synchronized (mLock) { 311 Log.v(TAG, "No specified events are expected but waitForExpectedEvents() received: " 312 + mExpectedEventsReceived); 313 } 314 return; 315 } 316 317 if (!mLatchResult) { 318 String errorMessage = "did not receive all expected events (" + stateToString() + ")"; 319 Log.e(TAG, errorMessage); 320 throw new IllegalStateException(errorMessage); 321 } 322 } 323 324 @NonNull toString(@onNull List<Integer> eventTypes)325 private static String toString(@NonNull List<Integer> eventTypes) { 326 return eventTypes.stream() 327 .map((i) -> CarUserManager.lifecycleEventTypeToString(i)) 328 .collect(Collectors.toList()) 329 .toString(); 330 } 331 isForAnyEvent()332 private boolean isForAnyEvent() { 333 return mExpectedEventTypes.isEmpty(); 334 } 335 expectingSpecificUser()336 private boolean expectingSpecificUser() { 337 return mForUserId != null; 338 } 339 expectingSpecificPreviousUser()340 private boolean expectingSpecificPreviousUser() { 341 return mForPreviousUserId != null; 342 } 343 checkState(boolean expression, String errorMessage)344 private static void checkState(boolean expression, String errorMessage) { 345 if (!expression) { 346 throw new IllegalStateException(errorMessage); 347 } 348 } 349 } 350