/* * Copyright (C) 2018 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package android.inputmethodservice; import static java.lang.annotation.RetentionPolicy.SOURCE; import android.annotation.IntDef; import android.content.Context; import android.content.Intent; import android.os.IBinder; import android.os.Looper; import android.util.Log; import android.view.InputChannel; import android.view.KeyEvent; import com.android.internal.annotations.GuardedBy; import com.android.internal.inputmethod.IMultiClientInputMethod; import com.android.internal.inputmethod.IMultiClientInputMethodPrivilegedOperations; import com.android.internal.inputmethod.MultiClientInputMethodPrivilegedOperations; import java.lang.annotation.Retention; import java.lang.ref.WeakReference; final class MultiClientInputMethodServiceDelegateImpl { private static final String TAG = "MultiClientInputMethodServiceDelegateImpl"; private final Object mLock = new Object(); @Retention(SOURCE) @IntDef({InitializationPhase.INSTANTIATED, InitializationPhase.ON_BIND_CALLED, InitializationPhase.INITIALIZE_CALLED, InitializationPhase.ON_UNBIND_CALLED, InitializationPhase.ON_DESTROY_CALLED}) private @interface InitializationPhase { int INSTANTIATED = 1; int ON_BIND_CALLED = 2; int INITIALIZE_CALLED = 3; int ON_UNBIND_CALLED = 4; int ON_DESTROY_CALLED = 5; } @GuardedBy("mLock") @InitializationPhase private int mInitializationPhase; private final MultiClientInputMethodPrivilegedOperations mPrivOps = new MultiClientInputMethodPrivilegedOperations(); private final MultiClientInputMethodServiceDelegate.ServiceCallback mServiceCallback; private final Context mContext; MultiClientInputMethodServiceDelegateImpl(Context context, MultiClientInputMethodServiceDelegate.ServiceCallback serviceCallback) { mInitializationPhase = InitializationPhase.INSTANTIATED; mContext = context; mServiceCallback = serviceCallback; } void onDestroy() { synchronized (mLock) { switch (mInitializationPhase) { case InitializationPhase.INSTANTIATED: case InitializationPhase.ON_UNBIND_CALLED: mInitializationPhase = InitializationPhase.ON_DESTROY_CALLED; break; default: Log.e(TAG, "unexpected state=" + mInitializationPhase); break; } } } private static final class ServiceImpl extends IMultiClientInputMethod.Stub { private final WeakReference mImpl; ServiceImpl(MultiClientInputMethodServiceDelegateImpl service) { mImpl = new WeakReference<>(service); } @Override public void initialize(IMultiClientInputMethodPrivilegedOperations privOps) { final MultiClientInputMethodServiceDelegateImpl service = mImpl.get(); if (service == null) { return; } synchronized (service.mLock) { switch (service.mInitializationPhase) { case InitializationPhase.ON_BIND_CALLED: service.mPrivOps.set(privOps); service.mInitializationPhase = InitializationPhase.INITIALIZE_CALLED; service.mServiceCallback.initialized(); break; default: Log.e(TAG, "unexpected state=" + service.mInitializationPhase); break; } } } @Override public void addClient(int clientId, int uid, int pid, int selfReportedDisplayId) { final MultiClientInputMethodServiceDelegateImpl service = mImpl.get(); if (service == null) { return; } service.mServiceCallback.addClient(clientId, uid, pid, selfReportedDisplayId); } @Override public void removeClient(int clientId) { final MultiClientInputMethodServiceDelegateImpl service = mImpl.get(); if (service == null) { return; } service.mServiceCallback.removeClient(clientId); } } IBinder onBind(Intent intent) { synchronized (mLock) { switch (mInitializationPhase) { case InitializationPhase.INSTANTIATED: mInitializationPhase = InitializationPhase.ON_BIND_CALLED; return new ServiceImpl(this); default: Log.e(TAG, "unexpected state=" + mInitializationPhase); break; } } return null; } boolean onUnbind(Intent intent) { synchronized (mLock) { switch (mInitializationPhase) { case InitializationPhase.ON_BIND_CALLED: case InitializationPhase.INITIALIZE_CALLED: mInitializationPhase = InitializationPhase.ON_UNBIND_CALLED; mPrivOps.dispose(); break; default: Log.e(TAG, "unexpected state=" + mInitializationPhase); break; } } return false; } IBinder createInputMethodWindowToken(int displayId) { return mPrivOps.createInputMethodWindowToken(displayId); } void acceptClient(int clientId, MultiClientInputMethodServiceDelegate.ClientCallback clientCallback, KeyEvent.DispatcherState dispatcherState, Looper looper) { final InputChannel[] channels = InputChannel.openInputChannelPair("MSIMS-session"); final InputChannel writeChannel = channels[0]; final InputChannel readChannel = channels[1]; try { final MultiClientInputMethodClientCallbackAdaptor callbackAdaptor = new MultiClientInputMethodClientCallbackAdaptor(clientCallback, looper, dispatcherState, readChannel); mPrivOps.acceptClient(clientId, callbackAdaptor.createIInputMethodSession(), callbackAdaptor.createIMultiClientInputMethodSession(), writeChannel); } finally { writeChannel.dispose(); } } void reportImeWindowTarget(int clientId, int targetWindowHandle, IBinder imeWindowToken) { mPrivOps.reportImeWindowTarget(clientId, targetWindowHandle, imeWindowToken); } boolean isUidAllowedOnDisplay(int displayId, int uid) { return mPrivOps.isUidAllowedOnDisplay(displayId, uid); } void setActive(int clientId, boolean active) { mPrivOps.setActive(clientId, active); } }