1 /* 2 * Copyright (C) 2017 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.cts.mockime; 18 19 import android.inputmethodservice.AbstractInputMethodService; 20 import android.os.Bundle; 21 import android.os.Handler; 22 import android.os.Parcelable; 23 import android.view.View; 24 import android.view.inputmethod.TextSnapshot; 25 26 import androidx.annotation.NonNull; 27 import androidx.annotation.Nullable; 28 29 import java.util.ArrayList; 30 import java.util.List; 31 32 /** 33 * An immutable object that stores event happened in the {@link MockIme}. 34 */ 35 public final class ImeEvent { 36 37 private enum ReturnType { 38 Null, 39 Unavailable, 40 KnownUnsupportedType, 41 Boolean, 42 Integer, 43 Long, 44 String, 45 CharSequence, 46 Exception, 47 Parcelable, 48 List 49 } 50 51 /** 52 * A special placeholder object that represents that return value information is not available. 53 */ 54 static final Object RETURN_VALUE_UNAVAILABLE = new Object(); 55 getReturnTypeFromObject(@ullable Object object)56 private static ReturnType getReturnTypeFromObject(@Nullable Object object) { 57 if (object == null) { 58 return ReturnType.Null; 59 } 60 if (object == RETURN_VALUE_UNAVAILABLE) { 61 return ReturnType.Unavailable; 62 } 63 if (object instanceof AbstractInputMethodService.AbstractInputMethodImpl) { 64 return ReturnType.KnownUnsupportedType; 65 } 66 if (object instanceof View) { 67 return ReturnType.KnownUnsupportedType; 68 } 69 if (object instanceof Handler) { 70 return ReturnType.KnownUnsupportedType; 71 } 72 if (object instanceof TextSnapshot) { 73 return ReturnType.KnownUnsupportedType; 74 } 75 if (object instanceof Boolean) { 76 return ReturnType.Boolean; 77 } 78 if (object instanceof Integer) { 79 return ReturnType.Integer; 80 } 81 if (object instanceof Long) { 82 return ReturnType.Long; 83 } 84 if (object instanceof String) { 85 return ReturnType.String; 86 } 87 if (object instanceof CharSequence) { 88 return ReturnType.CharSequence; 89 } 90 if (object instanceof Exception) { 91 return ReturnType.Exception; 92 } 93 if (object instanceof Parcelable) { 94 return ReturnType.Parcelable; 95 } 96 if (object instanceof List) { 97 return ReturnType.List; 98 } 99 throw new UnsupportedOperationException("Unsupported return type=" + object); 100 } 101 ImeEvent(@onNull String eventName, int nestLevel, @NonNull String threadName, int threadId, boolean isMainThread, long enterTimestamp, long exitTimestamp, long enterWallTime, long exitWallTime, @NonNull ImeState enterState, @Nullable ImeState exitState, @NonNull Bundle arguments, @Nullable Object returnValue)102 ImeEvent(@NonNull String eventName, int nestLevel, @NonNull String threadName, int threadId, 103 boolean isMainThread, long enterTimestamp, long exitTimestamp, long enterWallTime, 104 long exitWallTime, @NonNull ImeState enterState, @Nullable ImeState exitState, 105 @NonNull Bundle arguments, @Nullable Object returnValue) { 106 this(eventName, nestLevel, threadName, threadId, isMainThread, enterTimestamp, 107 exitTimestamp, enterWallTime, exitWallTime, enterState, exitState, arguments, 108 returnValue, getReturnTypeFromObject(returnValue)); 109 } 110 ImeEvent(@onNull String eventName, int nestLevel, @NonNull String threadName, int threadId, boolean isMainThread, long enterTimestamp, long exitTimestamp, long enterWallTime, long exitWallTime, @NonNull ImeState enterState, @Nullable ImeState exitState, @NonNull Bundle arguments, @Nullable Object returnValue, @NonNull ReturnType returnType)111 private ImeEvent(@NonNull String eventName, int nestLevel, @NonNull String threadName, 112 int threadId, boolean isMainThread, long enterTimestamp, long exitTimestamp, 113 long enterWallTime, long exitWallTime, @NonNull ImeState enterState, 114 @Nullable ImeState exitState, @NonNull Bundle arguments, @Nullable Object returnValue, 115 @NonNull ReturnType returnType) { 116 mEventName = eventName; 117 mNestLevel = nestLevel; 118 mThreadName = threadName; 119 mThreadId = threadId; 120 mIsMainThread = isMainThread; 121 mEnterTimestamp = enterTimestamp; 122 mExitTimestamp = exitTimestamp; 123 mEnterWallTime = enterWallTime; 124 mExitWallTime = exitWallTime; 125 mEnterState = enterState; 126 mExitState = exitState; 127 mArguments = arguments; 128 mReturnValue = returnValue; 129 mReturnType = returnType; 130 } 131 132 @NonNull toBundle()133 Bundle toBundle() { 134 final Bundle bundle = new Bundle(); 135 bundle.putString("mEventName", mEventName); 136 bundle.putInt("mNestLevel", mNestLevel); 137 bundle.putString("mThreadName", mThreadName); 138 bundle.putInt("mThreadId", mThreadId); 139 bundle.putBoolean("mIsMainThread", mIsMainThread); 140 bundle.putLong("mEnterTimestamp", mEnterTimestamp); 141 bundle.putLong("mExitTimestamp", mExitTimestamp); 142 bundle.putLong("mEnterWallTime", mEnterWallTime); 143 bundle.putLong("mExitWallTime", mExitWallTime); 144 bundle.putBundle("mEnterState", mEnterState.toBundle()); 145 bundle.putBundle("mExitState", mExitState != null ? mExitState.toBundle() : null); 146 bundle.putBundle("mArguments", mArguments); 147 bundle.putString("mReturnType", mReturnType.name()); 148 switch (mReturnType) { 149 case Null: 150 case Unavailable: 151 case KnownUnsupportedType: 152 break; 153 case Boolean: 154 bundle.putBoolean("mReturnValue", getReturnBooleanValue()); 155 break; 156 case Integer: 157 bundle.putInt("mReturnValue", getReturnIntegerValue()); 158 break; 159 case Long: 160 bundle.putLong("mReturnValue", getReturnLongValue()); 161 break; 162 case String: 163 bundle.putString("mReturnValue", getReturnStringValue()); 164 break; 165 case CharSequence: 166 bundle.putCharSequence("mReturnValue", getReturnCharSequenceValue()); 167 break; 168 case Exception: 169 bundle.putSerializable("mReturnValue", getReturnExceptionValue()); 170 break; 171 case Parcelable: 172 bundle.putParcelable("mReturnValue", getReturnParcelableValue()); 173 break; 174 case List: 175 bundle.putParcelableArrayList("mReturnValue", getReturnParcelableArrayListValue()); 176 break; 177 default: 178 throw new UnsupportedOperationException("Unsupported type=" + mReturnType); 179 } 180 return bundle; 181 } 182 183 @NonNull fromBundle(@onNull Bundle bundle)184 static ImeEvent fromBundle(@NonNull Bundle bundle) { 185 final String eventName = bundle.getString("mEventName"); 186 final int nestLevel = bundle.getInt("mNestLevel"); 187 final String threadName = bundle.getString("mThreadName"); 188 final int threadId = bundle.getInt("mThreadId"); 189 final boolean isMainThread = bundle.getBoolean("mIsMainThread"); 190 final long enterTimestamp = bundle.getLong("mEnterTimestamp"); 191 final long exitTimestamp = bundle.getLong("mExitTimestamp"); 192 final long enterWallTime = bundle.getLong("mEnterWallTime"); 193 final long exitWallTime = bundle.getLong("mExitWallTime"); 194 final ImeState enterState = ImeState.fromBundle(bundle.getBundle("mEnterState")); 195 final ImeState exitState = ImeState.fromBundle(bundle.getBundle("mExitState")); 196 final Bundle arguments = bundle.getBundle("mArguments"); 197 final Object result; 198 final ReturnType returnType = ReturnType.valueOf(bundle.getString("mReturnType")); 199 switch (returnType) { 200 case Null: 201 case Unavailable: 202 case KnownUnsupportedType: 203 result = null; 204 break; 205 case Boolean: 206 result = bundle.getBoolean("mReturnValue"); 207 break; 208 case Integer: 209 result = bundle.getInt("mReturnValue"); 210 break; 211 case Long: 212 result = bundle.getLong("mReturnValue"); 213 break; 214 case String: 215 result = bundle.getString("mReturnValue"); 216 break; 217 case CharSequence: 218 result = bundle.getCharSequence("mReturnValue"); 219 break; 220 case Exception: 221 result = bundle.getSerializable("mReturnValue"); 222 break; 223 case Parcelable: 224 result = bundle.getParcelable("mReturnValue"); 225 break; 226 case List: 227 result = bundle.getParcelableArrayList("mReturnValue"); 228 break; 229 default: 230 throw new UnsupportedOperationException("Unsupported type=" + returnType); 231 } 232 return new ImeEvent(eventName, nestLevel, threadName, 233 threadId, isMainThread, enterTimestamp, exitTimestamp, enterWallTime, exitWallTime, 234 enterState, exitState, arguments, result, returnType); 235 } 236 237 /** 238 * Returns a string that represents the type of this event. 239 * 240 * <p>Examples: "onCreate", "onStartInput", ...</p> 241 * 242 * <p>TODO: Use enum type or something like that instead of raw String type.</p> 243 * @return A string that represents the type of this event. 244 */ 245 @NonNull getEventName()246 public String getEventName() { 247 return mEventName; 248 } 249 250 /** 251 * Returns the nest level of this event. 252 * 253 * <p>For instance, when "showSoftInput" internally calls 254 * "onStartInputView", the event for "onStartInputView" has 1 level higher 255 * nest level than "showSoftInput".</p> 256 */ getNestLevel()257 public int getNestLevel() { 258 return mNestLevel; 259 } 260 261 /** 262 * @return Name of the thread, where the event was consumed. 263 */ 264 @NonNull getThreadName()265 public String getThreadName() { 266 return mThreadName; 267 } 268 269 /** 270 * @return Thread ID (TID) of the thread, where the event was consumed. 271 */ getThreadId()272 public int getThreadId() { 273 return mThreadId; 274 } 275 276 /** 277 * @return {@code true} if the event was being consumed in the main thread. 278 */ isMainThread()279 public boolean isMainThread() { 280 return mIsMainThread; 281 } 282 283 /** 284 * @return Monotonic time measured by {@link android.os.SystemClock#elapsedRealtimeNanos()} when 285 * the corresponding event handler was called back. 286 */ getEnterTimestamp()287 public long getEnterTimestamp() { 288 return mEnterTimestamp; 289 } 290 291 /** 292 * @return Monotonic time measured by {@link android.os.SystemClock#elapsedRealtimeNanos()} when 293 * the corresponding event handler finished. 294 */ getExitTimestamp()295 public long getExitTimestamp() { 296 return mExitTimestamp; 297 } 298 299 /** 300 * @return Wall-clock time measured by {@link System#currentTimeMillis()} when the corresponding 301 * event handler was called back. 302 */ getEnterWallTime()303 public long getEnterWallTime() { 304 return mEnterWallTime; 305 } 306 307 /** 308 * @return Wall-clock time measured by {@link System#currentTimeMillis()} when the corresponding 309 * event handler finished. 310 */ getExitWallTime()311 public long getExitWallTime() { 312 return mExitWallTime; 313 } 314 315 /** 316 * @return IME state snapshot taken when the corresponding event handler was called back. 317 */ 318 @NonNull getEnterState()319 public ImeState getEnterState() { 320 return mEnterState; 321 } 322 323 /** 324 * @return IME state snapshot taken when the corresponding event handler finished. 325 */ 326 @Nullable getExitState()327 public ImeState getExitState() { 328 return mExitState; 329 } 330 331 /** 332 * @return {@link Bundle} that stores parameters passed to the corresponding event handler. 333 */ 334 @NonNull getArguments()335 public Bundle getArguments() { 336 return mArguments; 337 } 338 339 /** 340 * @return result value of this event. 341 * @throws NullPointerException if the return value is {@code null} 342 * @throws ClassCastException if the return value is non-{@code null} object that is different 343 * from {@link Boolean} 344 */ getReturnBooleanValue()345 public boolean getReturnBooleanValue() { 346 if (mReturnType == ReturnType.Null) { 347 throw new NullPointerException(); 348 } 349 if (mReturnType != ReturnType.Boolean) { 350 throw new ClassCastException(); 351 } 352 return (Boolean) mReturnValue; 353 } 354 355 /** 356 * @return result value of this event. 357 * @throws NullPointerException if the return value is {@code null} 358 * @throws ClassCastException if the return value is non-{@code null} object that is different 359 * from {@link Integer} 360 */ getReturnIntegerValue()361 public int getReturnIntegerValue() { 362 if (mReturnType == ReturnType.Null) { 363 throw new NullPointerException(); 364 } 365 if (mReturnType != ReturnType.Integer) { 366 throw new ClassCastException(); 367 } 368 return (Integer) mReturnValue; 369 } 370 371 /** 372 * @return result value of this event. 373 * @throws NullPointerException if the return value is {@code null} 374 * @throws ClassCastException if the return value is non-{@code null} object that is different 375 * from {@link Long} 376 */ getReturnLongValue()377 public long getReturnLongValue() { 378 if (mReturnType == ReturnType.Null) { 379 throw new NullPointerException(); 380 } 381 if (mReturnType != ReturnType.Long) { 382 throw new ClassCastException(); 383 } 384 return (Long) mReturnValue; 385 } 386 387 /** 388 * @return result value of this event. 389 * @throws NullPointerException if the return value is {@code null} 390 * @throws ClassCastException if the return value is non-{@code null} object that does not 391 * implement {@link CharSequence} 392 */ getReturnCharSequenceValue()393 public CharSequence getReturnCharSequenceValue() { 394 if (mReturnType == ReturnType.Null) { 395 throw new NullPointerException(); 396 } 397 if (mReturnType == ReturnType.CharSequence || mReturnType == ReturnType.String 398 || mReturnType == ReturnType.Parcelable) { 399 return (CharSequence) mReturnValue; 400 } 401 throw new ClassCastException(); 402 } 403 404 /** 405 * @return result value of this event. 406 * @throws NullPointerException if the return value is {@code null} 407 * @throws ClassCastException if the return value is non-{@code null} object that is different 408 * from {@link String} 409 */ getReturnStringValue()410 public String getReturnStringValue() { 411 if (mReturnType == ReturnType.Null) { 412 throw new NullPointerException(); 413 } 414 if (mReturnType != ReturnType.String) { 415 throw new ClassCastException(); 416 } 417 return (String) mReturnValue; 418 } 419 420 /** 421 * Retrieves a result that is known to be {@link Exception} or its subclasses. 422 * 423 * @param <T> {@link Exception} or its subclass. 424 * @return {@link Exception} object returned as a result of the command. 425 * @throws NullPointerException if the return value is {@code null} 426 * @throws ClassCastException if the return value is non-{@code null} object that is different 427 * from {@link Exception} 428 */ getReturnExceptionValue()429 public <T extends Exception> T getReturnExceptionValue() { 430 if (mReturnType == ReturnType.Null) { 431 throw new NullPointerException(); 432 } 433 if (mReturnType != ReturnType.Exception) { 434 throw new ClassCastException(); 435 } 436 return (T) mReturnValue; 437 } 438 439 /** 440 * @return result value of this event. 441 * @throws NullPointerException if the return value is {@code null} 442 * @throws ClassCastException if the return value is non-{@code null} object that is different 443 * from {@link Parcelable} 444 */ getReturnParcelableValue()445 public <T extends Parcelable> T getReturnParcelableValue() { 446 if (mReturnType == ReturnType.Null) { 447 throw new NullPointerException(); 448 } 449 if (mReturnType != ReturnType.Parcelable) { 450 throw new ClassCastException(); 451 } 452 return (T) mReturnValue; 453 } 454 455 /** 456 * @return result value of this event. 457 * @throws NullPointerException if the return value is {@code null} 458 * @throws ClassCastException if the return value is non-{@code null} object that is different 459 * from {@link ArrayList<? extends Parcelable>} 460 */ getReturnParcelableArrayListValue()461 public <T extends Parcelable> ArrayList<T> getReturnParcelableArrayListValue() { 462 if (mReturnType == ReturnType.Null) { 463 throw new NullPointerException(); 464 } 465 if (mReturnType != ReturnType.List) { 466 throw new ClassCastException(); 467 } 468 return (ArrayList<T>) mReturnValue; 469 } 470 471 /** 472 * @return {@code true} when the result value is an {@link Exception}. 473 */ isExceptionReturnValue()474 public boolean isExceptionReturnValue() { 475 return mReturnType == ReturnType.Exception; 476 } 477 478 /** 479 * @return {@code true} when the result value is {@code null}. 480 */ isNullReturnValue()481 public boolean isNullReturnValue() { 482 return mReturnType == ReturnType.Null; 483 } 484 485 /** 486 * @return {@code true} if the event is issued when the event starts, not when the event 487 * finishes. 488 */ isEnterEvent()489 public boolean isEnterEvent() { 490 return mExitState == null; 491 } 492 493 @NonNull 494 private final String mEventName; 495 private final int mNestLevel; 496 @NonNull 497 private final String mThreadName; 498 private final int mThreadId; 499 private final boolean mIsMainThread; 500 private final long mEnterTimestamp; 501 private final long mExitTimestamp; 502 private final long mEnterWallTime; 503 private final long mExitWallTime; 504 @NonNull 505 private final ImeState mEnterState; 506 @Nullable 507 private final ImeState mExitState; 508 @NonNull 509 private final Bundle mArguments; 510 @Nullable 511 private final Object mReturnValue; 512 @NonNull 513 private final ReturnType mReturnType; 514 } 515