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 17 package com.android.server.inputmethod; 18 19 import android.annotation.NonNull; 20 import android.annotation.Nullable; 21 import android.content.pm.PackageManagerInternal; 22 import android.os.IBinder; 23 import android.os.RemoteException; 24 import android.util.ArrayMap; 25 26 import com.android.internal.annotations.GuardedBy; 27 import com.android.internal.annotations.VisibleForTesting; 28 import com.android.internal.inputmethod.IInputMethodClient; 29 import com.android.internal.inputmethod.IRemoteInputConnection; 30 31 import java.util.ArrayList; 32 import java.util.List; 33 import java.util.function.Consumer; 34 35 /** 36 * Store and manage {@link InputMethodManagerService} clients. 37 */ 38 final class ClientController { 39 40 @GuardedBy("ImfLock.class") 41 private final ArrayMap<IBinder, ClientState> mClients = new ArrayMap<>(); 42 43 @GuardedBy("ImfLock.class") 44 private final List<ClientControllerCallback> mCallbacks = new ArrayList<>(); 45 46 private final PackageManagerInternal mPackageManagerInternal; 47 48 interface ClientControllerCallback { 49 onClientRemoved(ClientState client)50 void onClientRemoved(ClientState client); 51 } 52 ClientController(PackageManagerInternal packageManagerInternal)53 ClientController(PackageManagerInternal packageManagerInternal) { 54 mPackageManagerInternal = packageManagerInternal; 55 } 56 57 @GuardedBy("ImfLock.class") addClient(IInputMethodClientInvoker clientInvoker, IRemoteInputConnection inputConnection, int selfReportedDisplayId, int callerUid, int callerPid)58 ClientState addClient(IInputMethodClientInvoker clientInvoker, 59 IRemoteInputConnection inputConnection, int selfReportedDisplayId, int callerUid, 60 int callerPid) { 61 final IBinder.DeathRecipient deathRecipient = () -> { 62 // Exceptionally holding ImfLock here since this is a internal lambda expression. 63 synchronized (ImfLock.class) { 64 removeClientAsBinder(clientInvoker.asBinder()); 65 } 66 }; 67 68 // TODO(b/319457906): Optimize this linear search. 69 final int numClients = mClients.size(); 70 for (int i = 0; i < numClients; ++i) { 71 final ClientState state = mClients.valueAt(i); 72 if (state.mUid == callerUid && state.mPid == callerPid 73 && state.mSelfReportedDisplayId == selfReportedDisplayId) { 74 throw new SecurityException("uid=" + callerUid + "/pid=" + callerPid 75 + "/displayId=" + selfReportedDisplayId + " is already registered"); 76 } 77 } 78 try { 79 clientInvoker.asBinder().linkToDeath(deathRecipient, 0 /* flags */); 80 } catch (RemoteException e) { 81 throw new IllegalStateException(e); 82 } 83 // We cannot fully avoid race conditions where the client UID already lost the access to 84 // the given self-reported display ID, even if the client is not maliciously reporting 85 // a fake display ID. Unconditionally returning SecurityException just because the 86 // client doesn't pass display ID verification can cause many test failures hence not an 87 // option right now. At the same time 88 // context.getSystemService(InputMethodManager.class) 89 // is expected to return a valid non-null instance at any time if we do not choose to 90 // have the client crash. Thus we do not verify the display ID at all here. Instead we 91 // later check the display ID every time the client needs to interact with the specified 92 // display. 93 final ClientState cs = new ClientState(clientInvoker, inputConnection, 94 callerUid, callerPid, selfReportedDisplayId, deathRecipient); 95 mClients.put(clientInvoker.asBinder(), cs); 96 return cs; 97 } 98 99 @VisibleForTesting 100 @GuardedBy("ImfLock.class") removeClient(IInputMethodClient client)101 boolean removeClient(IInputMethodClient client) { 102 return removeClientAsBinder(client.asBinder()); 103 } 104 105 @GuardedBy("ImfLock.class") removeClientAsBinder(IBinder binder)106 private boolean removeClientAsBinder(IBinder binder) { 107 final ClientState cs = mClients.remove(binder); 108 if (cs == null) { 109 return false; 110 } 111 binder.unlinkToDeath(cs.mClientDeathRecipient, 0 /* flags */); 112 for (int i = 0; i < mCallbacks.size(); i++) { 113 mCallbacks.get(i).onClientRemoved(cs); 114 } 115 return true; 116 } 117 118 @GuardedBy("ImfLock.class") addClientControllerCallback(ClientControllerCallback callback)119 void addClientControllerCallback(ClientControllerCallback callback) { 120 mCallbacks.add(callback); 121 } 122 123 @GuardedBy("ImfLock.class") 124 @Nullable getClient(IBinder binder)125 ClientState getClient(IBinder binder) { 126 return mClients.get(binder); 127 } 128 129 @GuardedBy("ImfLock.class") forAllClients(Consumer<ClientState> consumer)130 void forAllClients(Consumer<ClientState> consumer) { 131 for (int i = 0; i < mClients.size(); i++) { 132 consumer.accept(mClients.valueAt(i)); 133 } 134 } 135 136 @GuardedBy("ImfLock.class") verifyClientAndPackageMatch( @onNull IInputMethodClient client, @NonNull String packageName)137 boolean verifyClientAndPackageMatch( 138 @NonNull IInputMethodClient client, @NonNull String packageName) { 139 final ClientState cs = mClients.get(client.asBinder()); 140 if (cs == null) { 141 throw new IllegalArgumentException("unknown client " + client.asBinder()); 142 } 143 return InputMethodUtils.checkIfPackageBelongsToUid( 144 mPackageManagerInternal, cs.mUid, packageName); 145 } 146 } 147