• Home
  • History
  • Annotate
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2023 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.inputmethod;
17 
18 import static com.android.server.inputmethod.ClientController.ClientControllerCallback;
19 
20 import static com.google.common.truth.Truth.assertThat;
21 import static com.google.common.truth.Truth.assertWithMessage;
22 
23 import static org.junit.Assert.assertThrows;
24 import static org.mockito.Mockito.any;
25 import static org.mockito.Mockito.anyInt;
26 import static org.mockito.Mockito.anyLong;
27 import static org.mockito.Mockito.eq;
28 import static org.mockito.Mockito.verify;
29 import static org.mockito.Mockito.when;
30 
31 import android.content.pm.PackageManagerInternal;
32 import android.os.Handler;
33 import android.os.IBinder;
34 import android.os.Looper;
35 import android.platform.test.ravenwood.RavenwoodRule;
36 import android.view.Display;
37 
38 import com.android.internal.inputmethod.IInputMethodClient;
39 import com.android.internal.inputmethod.IRemoteInputConnection;
40 
41 import org.junit.Before;
42 import org.junit.Rule;
43 import org.junit.Test;
44 import org.mockito.Mock;
45 import org.mockito.MockitoAnnotations;
46 
47 import java.util.concurrent.CountDownLatch;
48 import java.util.concurrent.TimeUnit;
49 
50 // This test is designed to run on both device and host (Ravenwood) side.
51 public final class ClientControllerTest {
52     private static final int ANY_DISPLAY_ID = Display.DEFAULT_DISPLAY;
53     private static final int ANY_CALLER_UID = 1;
54     private static final int ANY_CALLER_PID = 2;
55     private static final String SOME_PACKAGE_NAME = "some.package";
56 
57     @Rule
58     public final RavenwoodRule mRavenwood = new RavenwoodRule.Builder()
59             .setProvideMainThread(true).build();
60 
61     @Mock
62     private PackageManagerInternal mMockPackageManagerInternal;
63 
64     @Mock(extraInterfaces = IBinder.class)
65     private IInputMethodClient mClient;
66 
67     @Mock
68     private IRemoteInputConnection mConnection;
69 
70     private Handler mHandler;
71 
72     private ClientController mController;
73 
74     @Before
setUp()75     public void setUp() {
76         MockitoAnnotations.initMocks(this);
77         when(mClient.asBinder()).thenReturn((IBinder) mClient);
78 
79         mHandler = new Handler(Looper.getMainLooper());
80         mController = new ClientController(mMockPackageManagerInternal);
81     }
82 
83     // TODO(b/322895594): No need to directly invoke create$ravenwood once b/322895594 is fixed.
createInvoker(IInputMethodClient client, Handler handler)84     private IInputMethodClientInvoker createInvoker(IInputMethodClient client, Handler handler) {
85         return RavenwoodRule.isOnRavenwood()
86                 ? IInputMethodClientInvoker.create$ravenwood(client, handler) :
87                 IInputMethodClientInvoker.create(client, handler);
88     }
89 
90     @Test
testAddClient_cannotAddTheSameClientTwice()91     public void testAddClient_cannotAddTheSameClientTwice() {
92         final var invoker = createInvoker(mClient, mHandler);
93         synchronized (ImfLock.class) {
94             mController.addClient(invoker, mConnection, ANY_DISPLAY_ID, ANY_CALLER_UID,
95                     ANY_CALLER_PID);
96 
97             SecurityException thrown = assertThrows(SecurityException.class,
98                     () -> {
99                         synchronized (ImfLock.class) {
100                             mController.addClient(invoker, mConnection, ANY_DISPLAY_ID,
101                                     ANY_CALLER_UID, ANY_CALLER_PID);
102                         }
103                     });
104             assertThat(thrown.getMessage()).isEqualTo(
105                     "uid=" + ANY_CALLER_UID + "/pid=" + ANY_CALLER_PID
106                             + "/displayId=0 is already registered");
107         }
108     }
109 
110     @Test
testAddClient()111     public void testAddClient() throws Exception {
112         final var invoker = createInvoker(mClient, mHandler);
113         synchronized (ImfLock.class) {
114             final var added = mController.addClient(invoker, mConnection, ANY_DISPLAY_ID,
115                     ANY_CALLER_UID,
116                     ANY_CALLER_PID);
117 
118             verify(invoker.asBinder()).linkToDeath(any(IBinder.DeathRecipient.class), eq(0));
119             assertThat(mController.getClient(invoker.asBinder())).isSameInstanceAs(added);
120         }
121     }
122 
123     @Test
testRemoveClient()124     public void testRemoveClient() {
125         final var invoker = createInvoker(mClient, mHandler);
126         final var callback = new TestClientControllerCallback();
127         ClientState added;
128         synchronized (ImfLock.class) {
129             mController.addClientControllerCallback(callback);
130             added = mController.addClient(invoker, mConnection, ANY_DISPLAY_ID, ANY_CALLER_UID,
131                     ANY_CALLER_PID);
132             assertThat(mController.getClient(invoker.asBinder())).isSameInstanceAs(added);
133             assertThat(mController.removeClient(mClient)).isTrue();
134         }
135 
136         // Test callback
137         final var removed = callback.waitForRemovedClient(5, TimeUnit.SECONDS);
138         assertThat(removed).isSameInstanceAs(added);
139     }
140 
141     @Test
testVerifyClientAndPackageMatch()142     public void testVerifyClientAndPackageMatch() {
143         final var invoker = createInvoker(mClient, mHandler);
144         when(mMockPackageManagerInternal.isSameApp(eq(SOME_PACKAGE_NAME),  /* flags= */
145                 anyLong(), eq(ANY_CALLER_UID), /* userId= */ anyInt())).thenReturn(true);
146 
147         synchronized (ImfLock.class) {
148             mController.addClient(invoker, mConnection, ANY_DISPLAY_ID, ANY_CALLER_UID,
149                     ANY_CALLER_PID);
150             assertThat(
151                     mController.verifyClientAndPackageMatch(mClient, SOME_PACKAGE_NAME)).isTrue();
152         }
153     }
154 
155     @Test
testVerifyClientAndPackageMatch_unknownClient()156     public void testVerifyClientAndPackageMatch_unknownClient() {
157         synchronized (ImfLock.class) {
158             assertThrows(IllegalArgumentException.class,
159                     () -> {
160                         synchronized (ImfLock.class) {
161                             mController.verifyClientAndPackageMatch(mClient, SOME_PACKAGE_NAME);
162                         }
163                     });
164         }
165     }
166 
167     private static class TestClientControllerCallback implements ClientControllerCallback {
168 
169         private final CountDownLatch mLatch = new CountDownLatch(1);
170 
171         private ClientState mRemoved;
172 
173         @Override
onClientRemoved(ClientState removed)174         public void onClientRemoved(ClientState removed) {
175             mRemoved = removed;
176             mLatch.countDown();
177         }
178 
waitForRemovedClient(long timeout, TimeUnit unit)179         ClientState waitForRemovedClient(long timeout, TimeUnit unit) {
180             try {
181                 assertWithMessage("ClientController callback wasn't called on user removed").that(
182                         mLatch.await(timeout, unit)).isTrue();
183             } catch (InterruptedException e) {
184                 Thread.currentThread().interrupt();
185                 throw new IllegalStateException("Unexpected thread interruption", e);
186             }
187             return mRemoved;
188         }
189     }
190 }
191