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