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