• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2018 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.internal.inputmethod;
18 
19 import android.annotation.AnyThread;
20 import android.annotation.DrawableRes;
21 import android.annotation.NonNull;
22 import android.annotation.Nullable;
23 import android.net.Uri;
24 import android.os.IBinder;
25 import android.os.RemoteException;
26 import android.util.Log;
27 import android.view.View;
28 import android.view.inputmethod.InputMethodSubtype;
29 
30 import com.android.internal.annotations.GuardedBy;
31 import com.android.internal.infra.AndroidFuture;
32 
33 import java.util.Objects;
34 
35 /**
36  * A utility class to take care of boilerplate code around IPCs.
37  */
38 public final class InputMethodPrivilegedOperations {
39     private static final String TAG = "InputMethodPrivilegedOperations";
40 
41     private static final class OpsHolder {
42         @Nullable
43         @GuardedBy("this")
44         private IInputMethodPrivilegedOperations mPrivOps;
45 
46         /**
47          * Sets {@link IInputMethodPrivilegedOperations}.
48          *
49          * <p>This method can be called only once.</p>
50          *
51          * @param privOps Binder interface to be set
52          */
53         @AnyThread
set(@onNull IInputMethodPrivilegedOperations privOps)54         public synchronized void set(@NonNull IInputMethodPrivilegedOperations privOps) {
55             if (mPrivOps != null) {
56                 throw new IllegalStateException(
57                         "IInputMethodPrivilegedOperations must be set at most once."
58                                 + " privOps=" + privOps);
59             }
60             mPrivOps = privOps;
61         }
62 
63         /**
64          * A simplified version of {@link android.os.Debug#getCaller()}.
65          *
66          * @return method name of the caller.
67          */
68         @AnyThread
getCallerMethodName()69         private static String getCallerMethodName() {
70             final StackTraceElement[] callStack = Thread.currentThread().getStackTrace();
71             if (callStack.length <= 4) {
72                 return "<bottom of call stack>";
73             }
74             return callStack[4].getMethodName();
75         }
76 
77         @AnyThread
78         @Nullable
getAndWarnIfNull()79         public synchronized IInputMethodPrivilegedOperations getAndWarnIfNull() {
80             if (mPrivOps == null) {
81                 Log.e(TAG, getCallerMethodName() + " is ignored."
82                         + " Call it within attachToken() and InputMethodService.onDestroy()");
83             }
84             return mPrivOps;
85         }
86     }
87     private final OpsHolder mOps = new OpsHolder();
88 
89     /**
90      * Sets {@link IInputMethodPrivilegedOperations}.
91      *
92      * <p>This method can be called only once.</p>
93      *
94      * @param privOps Binder interface to be set
95      */
96     @AnyThread
set(@onNull IInputMethodPrivilegedOperations privOps)97     public void set(@NonNull IInputMethodPrivilegedOperations privOps) {
98         Objects.requireNonNull(privOps, "privOps must not be null");
99         mOps.set(privOps);
100     }
101 
102     /**
103      * Calls {@link IInputMethodPrivilegedOperations#setImeWindowStatusAsync(int, int}.
104      *
105      * @param vis visibility flags
106      * @param backDisposition disposition flags
107      * @see android.inputmethodservice.InputMethodService#IME_ACTIVE
108      * @see android.inputmethodservice.InputMethodService#IME_VISIBLE
109      * @see android.inputmethodservice.InputMethodService#IME_INVISIBLE
110      * @see android.inputmethodservice.InputMethodService#BACK_DISPOSITION_DEFAULT
111      * @see android.inputmethodservice.InputMethodService#BACK_DISPOSITION_ADJUST_NOTHING
112      */
113     @AnyThread
setImeWindowStatusAsync(int vis, int backDisposition)114     public void setImeWindowStatusAsync(int vis, int backDisposition) {
115         final IInputMethodPrivilegedOperations ops = mOps.getAndWarnIfNull();
116         if (ops == null) {
117             return;
118         }
119         try {
120             ops.setImeWindowStatusAsync(vis, backDisposition);
121         } catch (RemoteException e) {
122             throw e.rethrowFromSystemServer();
123         }
124     }
125 
126     /**
127      * Calls {@link IInputMethodPrivilegedOperations#reportStartInputAsync(IBinder)}.
128      *
129      * @param startInputToken {@link IBinder} token to distinguish startInput session
130      */
131     @AnyThread
reportStartInputAsync(IBinder startInputToken)132     public void reportStartInputAsync(IBinder startInputToken) {
133         final IInputMethodPrivilegedOperations ops = mOps.getAndWarnIfNull();
134         if (ops == null) {
135             return;
136         }
137         try {
138             ops.reportStartInputAsync(startInputToken);
139         } catch (RemoteException e) {
140             throw e.rethrowFromSystemServer();
141         }
142     }
143 
144     /**
145      * Calls {@link IInputMethodPrivilegedOperations#createInputContentUriToken(Uri, String,
146      * AndroidFuture)}.
147      *
148      * @param contentUri Content URI to which a temporary read permission should be granted
149      * @param packageName Indicates what package needs to have a temporary read permission
150      * @return special Binder token that should be set to
151      *         {@link android.view.inputmethod.InputContentInfo#setUriToken(IInputContentUriToken)}
152      */
153     @AnyThread
createInputContentUriToken(Uri contentUri, String packageName)154     public IInputContentUriToken createInputContentUriToken(Uri contentUri, String packageName) {
155         final IInputMethodPrivilegedOperations ops = mOps.getAndWarnIfNull();
156         if (ops == null) {
157             return null;
158         }
159         try {
160             final AndroidFuture<IBinder> future = new AndroidFuture<>();
161             ops.createInputContentUriToken(contentUri, packageName, future);
162             return IInputContentUriToken.Stub.asInterface(CompletableFutureUtil.getResult(future));
163         } catch (RemoteException e) {
164             // For historical reasons, this error was silently ignored.
165             // Note that the caller already logs error so we do not need additional Log.e() here.
166             // TODO(team): Check if it is safe to rethrow error here.
167             return null;
168         }
169     }
170 
171     /**
172      * Calls {@link IInputMethodPrivilegedOperations#reportFullscreenModeAsync(boolean)}.
173      *
174      * @param fullscreen {@code true} if the IME enters full screen mode
175      */
176     @AnyThread
reportFullscreenModeAsync(boolean fullscreen)177     public void reportFullscreenModeAsync(boolean fullscreen) {
178         final IInputMethodPrivilegedOperations ops = mOps.getAndWarnIfNull();
179         if (ops == null) {
180             return;
181         }
182         try {
183             ops.reportFullscreenModeAsync(fullscreen);
184         } catch (RemoteException e) {
185             throw e.rethrowFromSystemServer();
186         }
187     }
188 
189     /**
190      * Calls {@link IInputMethodPrivilegedOperations#updateStatusIconAsync(String, int)}.
191      *
192      * @param packageName package name from which the status icon should be loaded
193      * @param iconResId resource ID of the icon to be loaded
194      */
195     @AnyThread
updateStatusIconAsync(String packageName, @DrawableRes int iconResId)196     public void updateStatusIconAsync(String packageName, @DrawableRes int iconResId) {
197         final IInputMethodPrivilegedOperations ops = mOps.getAndWarnIfNull();
198         if (ops == null) {
199             return;
200         }
201         try {
202             ops.updateStatusIconAsync(packageName, iconResId);
203         } catch (RemoteException e) {
204             throw e.rethrowFromSystemServer();
205         }
206     }
207 
208     /**
209      * Calls {@link IInputMethodPrivilegedOperations#setInputMethod(String, AndroidFuture)}.
210      *
211      * @param id IME ID of the IME to switch to
212      * @see android.view.inputmethod.InputMethodInfo#getId()
213      */
214     @AnyThread
setInputMethod(String id)215     public void setInputMethod(String id) {
216         final IInputMethodPrivilegedOperations ops = mOps.getAndWarnIfNull();
217         if (ops == null) {
218             return;
219         }
220         try {
221             final AndroidFuture<Void> future = new AndroidFuture<>();
222             ops.setInputMethod(id, future);
223             CompletableFutureUtil.getResult(future);
224         } catch (RemoteException e) {
225             throw e.rethrowFromSystemServer();
226         }
227     }
228 
229     /**
230      * Calls {@link IInputMethodPrivilegedOperations#setInputMethodAndSubtype(String,
231      * InputMethodSubtype, AndroidFuture)}
232      *
233      * @param id IME ID of the IME to switch to
234      * @param subtype {@link InputMethodSubtype} to switch to
235      * @see android.view.inputmethod.InputMethodInfo#getId()
236      */
237     @AnyThread
setInputMethodAndSubtype(String id, InputMethodSubtype subtype)238     public void setInputMethodAndSubtype(String id, InputMethodSubtype subtype) {
239         final IInputMethodPrivilegedOperations ops = mOps.getAndWarnIfNull();
240         if (ops == null) {
241             return;
242         }
243         try {
244             final AndroidFuture<Void> future = new AndroidFuture<>();
245             ops.setInputMethodAndSubtype(id, subtype, future);
246             CompletableFutureUtil.getResult(future);
247         } catch (RemoteException e) {
248             throw e.rethrowFromSystemServer();
249         }
250     }
251 
252     /**
253      * Calls {@link IInputMethodPrivilegedOperations#hideMySoftInput(int, IVoidResultCallback)}
254      *
255      * @param flags additional operating flags
256      * @param reason the reason to hide soft input
257      * @see android.view.inputmethod.InputMethodManager#HIDE_IMPLICIT_ONLY
258      * @see android.view.inputmethod.InputMethodManager#HIDE_NOT_ALWAYS
259      */
260     @AnyThread
hideMySoftInput(int flags, @SoftInputShowHideReason int reason)261     public void hideMySoftInput(int flags, @SoftInputShowHideReason int reason) {
262         final IInputMethodPrivilegedOperations ops = mOps.getAndWarnIfNull();
263         if (ops == null) {
264             return;
265         }
266         try {
267             final AndroidFuture<Void> future = new AndroidFuture<>();
268             ops.hideMySoftInput(flags, reason, future);
269             CompletableFutureUtil.getResult(future);
270         } catch (RemoteException e) {
271             throw e.rethrowFromSystemServer();
272         }
273     }
274 
275     /**
276      * Calls {@link IInputMethodPrivilegedOperations#showMySoftInput(int, AndroidFuture)}
277      *
278      * @param flags additional operating flags
279      * @see android.view.inputmethod.InputMethodManager#SHOW_IMPLICIT
280      * @see android.view.inputmethod.InputMethodManager#SHOW_FORCED
281      */
282     @AnyThread
showMySoftInput(int flags)283     public void showMySoftInput(int flags) {
284         final IInputMethodPrivilegedOperations ops = mOps.getAndWarnIfNull();
285         if (ops == null) {
286             return;
287         }
288         try {
289             final AndroidFuture<Void> future = new AndroidFuture<>();
290             ops.showMySoftInput(flags, future);
291             CompletableFutureUtil.getResult(future);
292         } catch (RemoteException e) {
293             throw e.rethrowFromSystemServer();
294         }
295     }
296 
297     /**
298      * Calls {@link IInputMethodPrivilegedOperations#switchToPreviousInputMethod(AndroidFuture)}
299      *
300      * @return {@code true} if handled
301      */
302     @AnyThread
switchToPreviousInputMethod()303     public boolean switchToPreviousInputMethod() {
304         final IInputMethodPrivilegedOperations ops = mOps.getAndWarnIfNull();
305         if (ops == null) {
306             return false;
307         }
308         try {
309             final AndroidFuture<Boolean> value = new AndroidFuture<>();
310             ops.switchToPreviousInputMethod(value);
311             return CompletableFutureUtil.getResult(value);
312         } catch (RemoteException e) {
313             throw e.rethrowFromSystemServer();
314         }
315     }
316 
317     /**
318      * Calls {@link IInputMethodPrivilegedOperations#switchToNextInputMethod(boolean,
319      * IBooleanResultCallback)}
320      *
321      * @param onlyCurrentIme {@code true} to switch to a {@link InputMethodSubtype} within the same
322      *                       IME
323      * @return {@code true} if handled
324      */
325     @AnyThread
switchToNextInputMethod(boolean onlyCurrentIme)326     public boolean switchToNextInputMethod(boolean onlyCurrentIme) {
327         final IInputMethodPrivilegedOperations ops = mOps.getAndWarnIfNull();
328         if (ops == null) {
329             return false;
330         }
331         try {
332             final AndroidFuture<Boolean> future = new AndroidFuture<>();
333             ops.switchToNextInputMethod(onlyCurrentIme, future);
334             return CompletableFutureUtil.getResult(future);
335         } catch (RemoteException e) {
336             throw e.rethrowFromSystemServer();
337         }
338     }
339 
340     /**
341      * Calls {@link IInputMethodPrivilegedOperations#shouldOfferSwitchingToNextInputMethod(
342      * AndroidFuture)}
343      *
344      * @return {@code true} if the IEM should offer a way to globally switch IME
345      */
346     @AnyThread
shouldOfferSwitchingToNextInputMethod()347     public boolean shouldOfferSwitchingToNextInputMethod() {
348         final IInputMethodPrivilegedOperations ops = mOps.getAndWarnIfNull();
349         if (ops == null) {
350             return false;
351         }
352         try {
353             final AndroidFuture<Boolean> future = new AndroidFuture<>();
354             ops.shouldOfferSwitchingToNextInputMethod(future);
355             return CompletableFutureUtil.getResult(future);
356         } catch (RemoteException e) {
357             throw e.rethrowFromSystemServer();
358         }
359     }
360 
361     /**
362      * Calls {@link IInputMethodPrivilegedOperations#notifyUserActionAsync()}
363      */
364     @AnyThread
notifyUserActionAsync()365     public void notifyUserActionAsync() {
366         final IInputMethodPrivilegedOperations ops = mOps.getAndWarnIfNull();
367         if (ops == null) {
368             return;
369         }
370         try {
371             ops.notifyUserActionAsync();
372         } catch (RemoteException e) {
373             throw e.rethrowFromSystemServer();
374         }
375     }
376 
377     /**
378      * Calls {@link IInputMethodPrivilegedOperations#applyImeVisibilityAsync(IBinder, boolean)}.
379      *
380      * @param showOrHideInputToken placeholder token that maps to window requesting
381      *        {@link android.view.inputmethod.InputMethodManager#showSoftInput(View, int)} or
382      *        {@link android.view.inputmethod.InputMethodManager#hideSoftInputFromWindow
383      *        (IBinder, int)}
384      * @param setVisible {@code true} to set IME visible, else hidden.
385      */
386     @AnyThread
applyImeVisibilityAsync(IBinder showOrHideInputToken, boolean setVisible)387     public void applyImeVisibilityAsync(IBinder showOrHideInputToken, boolean setVisible) {
388         final IInputMethodPrivilegedOperations ops = mOps.getAndWarnIfNull();
389         if (ops == null) {
390             return;
391         }
392         try {
393             ops.applyImeVisibilityAsync(showOrHideInputToken, setVisible);
394         } catch (RemoteException e) {
395             throw e.rethrowFromSystemServer();
396         }
397     }
398 
399     /**
400      * Calls {@link IInputMethodPrivilegedOperations#onStylusHandwritingReady(int, int)}
401      */
402     @AnyThread
onStylusHandwritingReady(int requestId, int pid)403     public void onStylusHandwritingReady(int requestId, int pid) {
404         final IInputMethodPrivilegedOperations ops = mOps.getAndWarnIfNull();
405         if (ops == null) {
406             return;
407         }
408         try {
409             ops.onStylusHandwritingReady(requestId, pid);
410         } catch (RemoteException e) {
411             throw e.rethrowFromSystemServer();
412         }
413     }
414 
415     /**
416      * IME notifies that the current handwriting session should be closed.
417      * @param requestId
418      */
419     @AnyThread
resetStylusHandwriting(int requestId)420     public void resetStylusHandwriting(int requestId) {
421         final IInputMethodPrivilegedOperations ops = mOps.getAndWarnIfNull();
422         if (ops == null) {
423             return;
424         }
425         try {
426             ops.resetStylusHandwriting(requestId);
427         } catch (RemoteException e) {
428             throw e.rethrowFromSystemServer();
429         }
430     }
431 }
432