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