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