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