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