1 /*
2  * Copyright (C) 2024 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 com.android.server.inputmethod;
18 
19 import static com.google.common.truth.Truth.assertThat;
20 
21 import static org.mockito.Mockito.spy;
22 import static org.mockito.Mockito.times;
23 import static org.mockito.Mockito.verify;
24 
25 import android.content.pm.UserInfo;
26 import android.os.ConditionVariable;
27 import android.os.Handler;
28 import android.os.Looper;
29 import android.platform.test.ravenwood.RavenwoodRule;
30 
31 import com.android.server.pm.UserManagerInternal;
32 
33 import org.junit.Before;
34 import org.junit.Rule;
35 import org.junit.Test;
36 import org.mockito.ArgumentCaptor;
37 import org.mockito.Mock;
38 import org.mockito.MockitoAnnotations;
39 
40 import java.util.ArrayList;
41 import java.util.List;
42 import java.util.function.IntFunction;
43 
44 // This test is designed to run on both device and host (Ravenwood) side.
45 public final class UserDataRepositoryTest {
46 
47     private static final int ANY_USER_ID = 1;
48 
49     @Rule
50     public final RavenwoodRule mRavenwood = new RavenwoodRule.Builder()
51             .setProvideMainThread(true).build();
52 
53     @Mock
54     private UserManagerInternal mMockUserManagerInternal;
55 
56     @Mock
57     private InputMethodManagerService mMockInputMethodManagerService;
58 
59     private Handler mHandler;
60 
61     private IntFunction<InputMethodBindingController> mBindingControllerFactory;
62 
63     @Before
setUp()64     public void setUp() {
65         MockitoAnnotations.initMocks(this);
66         mHandler = new Handler(Looper.getMainLooper());
67         mBindingControllerFactory = new IntFunction<InputMethodBindingController>() {
68 
69             @Override
70             public InputMethodBindingController apply(int userId) {
71                 return new InputMethodBindingController(userId, mMockInputMethodManagerService);
72             }
73         };
74     }
75 
76     @Test
testUserDataRepository_addsNewUserInfoOnUserCreatedEvent()77     public void testUserDataRepository_addsNewUserInfoOnUserCreatedEvent() {
78         // Create UserDataRepository and capture the user lifecycle listener
79         final var captor = ArgumentCaptor.forClass(UserManagerInternal.UserLifecycleListener.class);
80         final var bindingControllerFactorySpy = spy(mBindingControllerFactory);
81         final var repository = new UserDataRepository(mHandler, mMockUserManagerInternal,
82                 bindingControllerFactorySpy);
83 
84         verify(mMockUserManagerInternal, times(1)).addUserLifecycleListener(captor.capture());
85         final var listener = captor.getValue();
86 
87         // Assert that UserDataRepository is empty and then call onUserCreated
88         assertThat(collectUserData(repository)).isEmpty();
89         final var userInfo = new UserInfo();
90         userInfo.id = ANY_USER_ID;
91         listener.onUserCreated(userInfo, /* unused token */ new Object());
92         waitForIdle();
93 
94         // Assert UserDataRepository contains the expected UserData
95         final var allUserData = collectUserData(repository);
96         assertThat(allUserData).hasSize(1);
97         assertThat(allUserData.get(0).mUserId).isEqualTo(ANY_USER_ID);
98 
99         // Assert UserDataRepository called the InputMethodBindingController creator function.
100         verify(bindingControllerFactorySpy).apply(ANY_USER_ID);
101         assertThat(allUserData.get(0).mBindingController.mUserId).isEqualTo(ANY_USER_ID);
102     }
103 
104     @Test
testUserDataRepository_removesUserInfoOnUserRemovedEvent()105     public void testUserDataRepository_removesUserInfoOnUserRemovedEvent() {
106         // Create UserDataRepository and capture the user lifecycle listener
107         final var captor = ArgumentCaptor.forClass(UserManagerInternal.UserLifecycleListener.class);
108         final var repository = new UserDataRepository(mHandler, mMockUserManagerInternal,
109                 userId -> new InputMethodBindingController(userId, mMockInputMethodManagerService));
110 
111         verify(mMockUserManagerInternal, times(1)).addUserLifecycleListener(captor.capture());
112         final var listener = captor.getValue();
113 
114         // Add one UserData ...
115         final var userInfo = new UserInfo();
116         userInfo.id = ANY_USER_ID;
117         listener.onUserCreated(userInfo, /* unused token */ new Object());
118         waitForIdle();
119         // ... and then call onUserRemoved
120         assertThat(collectUserData(repository)).hasSize(1);
121         listener.onUserRemoved(userInfo);
122         waitForIdle();
123 
124         // Assert UserDataRepository is now empty
125         assertThat(collectUserData(repository)).isEmpty();
126     }
127 
128     @Test
testGetOrCreate()129     public void testGetOrCreate() {
130         final var repository = new UserDataRepository(mHandler, mMockUserManagerInternal,
131                 mBindingControllerFactory);
132 
133         synchronized (ImfLock.class) {
134             final var userData = repository.getOrCreate(ANY_USER_ID);
135             assertThat(userData.mUserId).isEqualTo(ANY_USER_ID);
136         }
137 
138         final var allUserData = collectUserData(repository);
139         assertThat(allUserData).hasSize(1);
140         assertThat(allUserData.get(0).mUserId).isEqualTo(ANY_USER_ID);
141 
142         // Assert UserDataRepository called the InputMethodBindingController creator function.
143         assertThat(allUserData.get(0).mBindingController.mUserId).isEqualTo(ANY_USER_ID);
144     }
145 
collectUserData(UserDataRepository repository)146     private List<UserDataRepository.UserData> collectUserData(UserDataRepository repository) {
147         final var collected = new ArrayList<UserDataRepository.UserData>();
148         synchronized (ImfLock.class) {
149             repository.forAllUserData(userData -> collected.add(userData));
150         }
151         return collected;
152     }
153 
waitForIdle()154     private void waitForIdle() {
155         final var done = new ConditionVariable();
156         mHandler.post(done::open);
157         done.block();
158     }
159 }
160