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