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