1 /*
2  * Copyright (C) 2022 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 package com.android.server.pm;
17 
18 import static org.junit.Assert.fail;
19 
20 import android.util.Log;
21 
22 import com.android.internal.annotations.GuardedBy;
23 import com.android.server.pm.UserManagerInternal.UserVisibilityListener;
24 
25 import com.google.common.truth.Expect;
26 
27 import java.util.ArrayList;
28 import java.util.Arrays;
29 import java.util.Collections;
30 import java.util.List;
31 import java.util.concurrent.CountDownLatch;
32 import java.util.concurrent.TimeUnit;
33 /**
34  * {@link UserVisibilityListener} implementation that expects callback events to be asynchronously
35  * received.
36  */
37 public final class AsyncUserVisibilityListener implements UserVisibilityListener {
38 
39     private static final String TAG = AsyncUserVisibilityListener.class.getSimpleName();
40 
41     private static final long WAIT_TIMEOUT_MS = 2_000;
42     private static final long WAIT_NO_EVENTS_TIMEOUT_MS = 100;
43 
44     private static int sNextId;
45 
46     private final Object mLock = new Object();
47     private final Expect mExpect;
48     private final int mId = ++sNextId;
49     private final Thread mExpectedReceiverThread;
50     private final CountDownLatch mLatch;
51     private final List<UserVisibilityChangedEvent> mExpectedEvents;
52 
53     @GuardedBy("mLock")
54     private final List<UserVisibilityChangedEvent> mReceivedEvents = new ArrayList<>();
55 
56     @GuardedBy("mLock")
57     private final List<String> mErrors = new ArrayList<>();
58 
AsyncUserVisibilityListener(Expect expect, Thread expectedReceiverThread, List<UserVisibilityChangedEvent> expectedEvents)59     private AsyncUserVisibilityListener(Expect expect, Thread expectedReceiverThread,
60             List<UserVisibilityChangedEvent> expectedEvents) {
61         mExpect = expect;
62         mExpectedReceiverThread = expectedReceiverThread;
63         mExpectedEvents = expectedEvents;
64         mLatch = new CountDownLatch(expectedEvents.size());
65     }
66 
67     @Override
onUserVisibilityChanged(int userId, boolean visible)68     public void onUserVisibilityChanged(int userId, boolean visible) {
69         UserVisibilityChangedEvent event = new UserVisibilityChangedEvent(userId, visible);
70         Thread callingThread = Thread.currentThread();
71         Log.d(TAG, "Received event (" + event + ") on thread " + callingThread);
72 
73         if (callingThread != mExpectedReceiverThread) {
74             addError("event %s received in on thread %s but was expected on thread %s",
75                     event, callingThread, mExpectedReceiverThread);
76         }
77         synchronized (mLock) {
78             mReceivedEvents.add(event);
79             mLatch.countDown();
80         }
81     }
82 
83     /**
84      * Verifies the expected events were called.
85      */
verify()86     public void verify() throws InterruptedException {
87         waitForEventsAndCheckErrors();
88 
89         List<UserVisibilityChangedEvent> receivedEvents = getReceivedEvents();
90 
91         if (receivedEvents.isEmpty()) {
92             mExpect.withMessage("received events").that(receivedEvents).isEmpty();
93             return;
94         }
95 
96         // NOTE: check "inOrder" might be too harsh in some cases (for example, if the fg user
97         // has 2 profiles, the order of the events on the profiles wouldn't matter), but we
98         // still need some dependency (like "user A became invisible before user B became
99         // visible", so this is fine for now (but eventually we might need to add more
100         // sophisticated assertions)
101         mExpect.withMessage("received events").that(receivedEvents)
102                 .containsExactlyElementsIn(mExpectedEvents).inOrder();
103     }
104 
105     @Override
toString()106     public String toString() {
107         List<UserVisibilityChangedEvent> receivedEvents = getReceivedEvents();
108         return "[" + getClass().getSimpleName() + ": id=" + mId
109                 + ", creationThread=" + mExpectedReceiverThread
110                 + ", received=" + receivedEvents.size()
111                 + ", events=" + receivedEvents + "]";
112     }
113 
getReceivedEvents()114     private List<UserVisibilityChangedEvent> getReceivedEvents() {
115         synchronized (mLock) {
116             return Collections.unmodifiableList(mReceivedEvents);
117         }
118     }
119 
waitForEventsAndCheckErrors()120     private void waitForEventsAndCheckErrors() throws InterruptedException {
121         waitForEvents();
122         synchronized (mLock) {
123             if (!mErrors.isEmpty()) {
124                 fail(mErrors.size() + " errors on received events: " + mErrors);
125             }
126         }
127     }
128 
waitForEvents()129     private void waitForEvents() throws InterruptedException {
130         if (mExpectedEvents.isEmpty()) {
131             Log.v(TAG, "Sleeping " + WAIT_NO_EVENTS_TIMEOUT_MS + "ms to make sure no event is "
132                     + "received");
133             Thread.sleep(WAIT_NO_EVENTS_TIMEOUT_MS);
134             return;
135         }
136 
137         int expectedNumberEvents = mExpectedEvents.size();
138         Log.v(TAG, "Waiting up to " + WAIT_TIMEOUT_MS + "ms until " + expectedNumberEvents
139                 + " events are received");
140         if (!mLatch.await(WAIT_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
141             List<UserVisibilityChangedEvent> receivedEvents = getReceivedEvents();
142             addError("Timed out (%d ms) waiting for %d events; received %d so far (%s), "
143                     + "but expecting %d (%s)", WAIT_NO_EVENTS_TIMEOUT_MS, expectedNumberEvents,
144                     receivedEvents.size(), receivedEvents, expectedNumberEvents, mExpectedEvents);
145         }
146     }
147 
148     @SuppressWarnings("AnnotateFormatMethod")
addError(String format, Object...args)149     private void addError(String format, Object...args) {
150         synchronized (mLock) {
151             mErrors.add(String.format(format, args));
152         }
153     }
154 
155     /**
156      * Factory for {@link AsyncUserVisibilityListener} objects.
157      */
158     public static final class Factory {
159         private final Expect mExpect;
160         private final Thread mExpectedReceiverThread;
161 
Factory(Expect expect, Thread expectedReceiverThread)162         public Factory(Expect expect, Thread expectedReceiverThread) {
163             mExpect = expect;
164             mExpectedReceiverThread = expectedReceiverThread;
165         }
166 
167         /**
168          * Creates a {@link AsyncUserVisibilityListener} that is expecting the given events.
169          */
forEvents(UserVisibilityChangedEvent...expectedEvents)170         public AsyncUserVisibilityListener forEvents(UserVisibilityChangedEvent...expectedEvents) {
171             return new AsyncUserVisibilityListener(mExpect, mExpectedReceiverThread,
172                     Arrays.asList(expectedEvents));
173         }
174 
175         /**
176          * Creates a {@link AsyncUserVisibilityListener} that is expecting no events.
177          */
forNoEvents()178         public AsyncUserVisibilityListener forNoEvents() {
179             return new AsyncUserVisibilityListener(mExpect, mExpectedReceiverThread,
180                     Collections.emptyList());
181         }
182     }
183 }
184