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