1 /*
2  * Copyright (C) 2022 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 android.view.inputmethod;
18 
19 import android.annotation.AnyThread;
20 import android.annotation.NonNull;
21 import android.annotation.Nullable;
22 import android.os.Binder;
23 import android.os.Handler;
24 import android.os.HandlerThread;
25 import android.os.RemoteException;
26 import android.util.Log;
27 
28 import com.android.internal.annotations.GuardedBy;
29 import com.android.internal.inputmethod.IAccessibilityInputMethodSession;
30 import com.android.internal.inputmethod.IRemoteAccessibilityInputConnection;
31 
32 final class IAccessibilityInputMethodSessionInvoker {
33     private static final String TAG = "IAccessibilityInputMethodSessionInvoker";
34 
35     /**
36      * The actual instance of the method to make calls on it.
37      */
38     @NonNull
39     private final IAccessibilityInputMethodSession mSession;
40 
41     /**
42      * An optional {@link Handler} to dispatch {@link IAccessibilityInputMethodSession} method
43      * invocations to a background thread to emulate async (one-way) {@link Binder} call.
44      *
45      * {@code null} if {@code Binder.isProxy(mSession)} is {@code true}.
46      */
47     @Nullable
48     private final Handler mCustomHandler;
49 
50     private static final Object sAsyncBinderEmulationHandlerLock = new Object();
51 
52     @GuardedBy("sAsyncBinderEmulationHandlerLock")
53     @Nullable
54     private static Handler sAsyncBinderEmulationHandler;
55 
IAccessibilityInputMethodSessionInvoker( @onNull IAccessibilityInputMethodSession session, @Nullable Handler customHandler)56     private IAccessibilityInputMethodSessionInvoker(
57             @NonNull IAccessibilityInputMethodSession session,
58             @Nullable Handler customHandler) {
59         mSession = session;
60         mCustomHandler = customHandler;
61     }
62 
63     /**
64      * Create a {@link IAccessibilityInputMethodSessionInvoker} instance if applicable.
65      *
66      * @param session {@link IAccessibilityInputMethodSession} object to be wrapped.
67      * @return an instance of {@link IAccessibilityInputMethodSessionInvoker} if
68      *         {@code inputMethodSession} is not {@code null}. {@code null} otherwise.
69      */
70     @Nullable
createOrNull( @onNull IAccessibilityInputMethodSession session)71     public static IAccessibilityInputMethodSessionInvoker createOrNull(
72             @NonNull IAccessibilityInputMethodSession session) {
73         final Handler customHandler;
74         if (session != null && !Binder.isProxy(session)) {
75             synchronized (sAsyncBinderEmulationHandlerLock) {
76                 if (sAsyncBinderEmulationHandler == null) {
77                     final HandlerThread thread = new HandlerThread("IMM.IAIMS");
78                     thread.start();
79                     // Use an async handler instead of Handler#getThreadHandler().
80                     sAsyncBinderEmulationHandler = Handler.createAsync(thread.getLooper());
81                 }
82                 customHandler = sAsyncBinderEmulationHandler;
83             }
84         } else {
85             customHandler = null;
86         }
87 
88         return session == null
89                 ? null : new IAccessibilityInputMethodSessionInvoker(session, customHandler);
90     }
91 
92     @AnyThread
finishInput()93     void finishInput() {
94         if (mCustomHandler == null) {
95             finishInputInternal();
96         } else {
97             mCustomHandler.post(this::finishInputInternal);
98         }
99     }
100 
101     @AnyThread
finishInputInternal()102     private void finishInputInternal() {
103         try {
104             mSession.finishInput();
105         } catch (RemoteException e) {
106             Log.w(TAG, "A11yIME died", e);
107         }
108     }
109 
110     @AnyThread
updateSelection(int oldSelStart, int oldSelEnd, int selStart, int selEnd, int candidatesStart, int candidatesEnd)111     void updateSelection(int oldSelStart, int oldSelEnd, int selStart, int selEnd,
112             int candidatesStart, int candidatesEnd) {
113         if (mCustomHandler == null) {
114             updateSelectionInternal(
115                     oldSelStart, oldSelEnd, selStart, selEnd, candidatesStart, candidatesEnd);
116         } else {
117             mCustomHandler.post(() -> updateSelectionInternal(
118                     oldSelStart, oldSelEnd, selStart, selEnd, candidatesStart, candidatesEnd));
119         }
120     }
121 
122     @AnyThread
updateSelectionInternal(int oldSelStart, int oldSelEnd, int selStart, int selEnd, int candidatesStart, int candidatesEnd)123     private void updateSelectionInternal(int oldSelStart, int oldSelEnd, int selStart, int selEnd,
124             int candidatesStart, int candidatesEnd) {
125         try {
126             mSession.updateSelection(
127                     oldSelStart, oldSelEnd, selStart, selEnd, candidatesStart, candidatesEnd);
128         } catch (RemoteException e) {
129             Log.w(TAG, "A11yIME died", e);
130         }
131     }
132 
133     @AnyThread
invalidateInput(EditorInfo editorInfo, IRemoteAccessibilityInputConnection connection, int sessionId)134     void invalidateInput(EditorInfo editorInfo, IRemoteAccessibilityInputConnection connection,
135             int sessionId) {
136         if (mCustomHandler == null) {
137             invalidateInputInternal(editorInfo, connection, sessionId);
138         } else {
139             mCustomHandler.post(() -> invalidateInputInternal(editorInfo, connection, sessionId));
140         }
141     }
142 
143     @AnyThread
invalidateInputInternal(EditorInfo editorInfo, IRemoteAccessibilityInputConnection connection, int sessionId)144     private void invalidateInputInternal(EditorInfo editorInfo,
145             IRemoteAccessibilityInputConnection connection, int sessionId) {
146         try {
147             mSession.invalidateInput(editorInfo, connection, sessionId);
148         } catch (RemoteException e) {
149             Log.w(TAG, "A11yIME died", e);
150         }
151     }
152 }
153