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: &quot;onCreate&quot;, &quot;onStartInput&quot;, ...</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 &quot;showSoftInput&quot; internally calls
257      * &quot;onStartInputView&quot;, the event for &quot;onStartInputView&quot; has 1 level higher
258      * nest level than &quot;showSoftInput&quot;.</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