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