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 androidx.annotation.NonNull;
22 import androidx.annotation.Nullable;
23 import android.view.View;
24 
25 /**
26  * An immutable object that stores event happened in the {@link MockIme}.
27  */
28 public final class ImeEvent {
29 
30     private enum ReturnType {
31         Null,
32         KnownUnsupportedType,
33         Boolean,
34     }
35 
getReturnTypeFromObject(@ullable Object object)36     private static ReturnType getReturnTypeFromObject(@Nullable Object object) {
37         if (object == null) {
38             return ReturnType.Null;
39         }
40         if (object instanceof AbstractInputMethodService.AbstractInputMethodImpl) {
41             return ReturnType.KnownUnsupportedType;
42         }
43         if (object instanceof View) {
44             return ReturnType.KnownUnsupportedType;
45         }
46         if (object instanceof Boolean) {
47             return ReturnType.Boolean;
48         }
49         throw new UnsupportedOperationException("Unsupported return type=" + object);
50     }
51 
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)52     ImeEvent(@NonNull String eventName, int nestLevel, @NonNull String threadName, int threadId,
53             boolean isMainThread, long enterTimestamp, long exitTimestamp, long enterWallTime,
54             long exitWallTime, @NonNull ImeState enterState, @Nullable ImeState exitState,
55             @NonNull Bundle arguments, @Nullable Object returnValue) {
56         this(eventName, nestLevel, threadName, threadId, isMainThread, enterTimestamp,
57                 exitTimestamp, enterWallTime, exitWallTime, enterState, exitState, arguments,
58                 returnValue, getReturnTypeFromObject(returnValue));
59     }
60 
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)61     private ImeEvent(@NonNull String eventName, int nestLevel, @NonNull String threadName,
62             int threadId, boolean isMainThread, long enterTimestamp, long exitTimestamp,
63             long enterWallTime, long exitWallTime, @NonNull ImeState enterState,
64             @Nullable ImeState exitState, @NonNull Bundle arguments, @Nullable Object returnValue,
65             @NonNull ReturnType returnType) {
66         mEventName = eventName;
67         mNestLevel = nestLevel;
68         mThreadName = threadName;
69         mThreadId = threadId;
70         mIsMainThread = isMainThread;
71         mEnterTimestamp = enterTimestamp;
72         mExitTimestamp = exitTimestamp;
73         mEnterWallTime = enterWallTime;
74         mExitWallTime = exitWallTime;
75         mEnterState = enterState;
76         mExitState = exitState;
77         mArguments = arguments;
78         mReturnValue = returnValue;
79         mReturnType = returnType;
80     }
81 
82     @NonNull
toBundle()83     Bundle toBundle() {
84         final Bundle bundle = new Bundle();
85         bundle.putString("mEventName", mEventName);
86         bundle.putInt("mNestLevel", mNestLevel);
87         bundle.putString("mThreadName", mThreadName);
88         bundle.putInt("mThreadId", mThreadId);
89         bundle.putBoolean("mIsMainThread", mIsMainThread);
90         bundle.putLong("mEnterTimestamp", mEnterTimestamp);
91         bundle.putLong("mExitTimestamp", mExitTimestamp);
92         bundle.putLong("mEnterWallTime", mEnterWallTime);
93         bundle.putLong("mExitWallTime", mExitWallTime);
94         bundle.putBundle("mEnterState", mEnterState.toBundle());
95         bundle.putBundle("mExitState", mExitState != null ? mExitState.toBundle() : null);
96         bundle.putBundle("mArguments", mArguments);
97         bundle.putString("mReturnType", mReturnType.name());
98         switch (mReturnType) {
99             case Null:
100             case KnownUnsupportedType:
101                 break;
102             case Boolean:
103                 bundle.putBoolean("mReturnValue", getReturnBooleanValue());
104                 break;
105             default:
106                 throw new UnsupportedOperationException("Unsupported type=" + mReturnType);
107         }
108         return bundle;
109     }
110 
111     @NonNull
fromBundle(@onNull Bundle bundle)112     static ImeEvent fromBundle(@NonNull Bundle bundle) {
113         final String eventName = bundle.getString("mEventName");
114         final int nestLevel = bundle.getInt("mNestLevel");
115         final String threadName = bundle.getString("mThreadName");
116         final int threadId = bundle.getInt("mThreadId");
117         final boolean isMainThread = bundle.getBoolean("mIsMainThread");
118         final long enterTimestamp = bundle.getLong("mEnterTimestamp");
119         final long exitTimestamp = bundle.getLong("mExitTimestamp");
120         final long enterWallTime = bundle.getLong("mEnterWallTime");
121         final long exitWallTime = bundle.getLong("mExitWallTime");
122         final ImeState enterState = ImeState.fromBundle(bundle.getBundle("mEnterState"));
123         final ImeState exitState = ImeState.fromBundle(bundle.getBundle("mExitState"));
124         final Bundle arguments = bundle.getBundle("mArguments");
125         final Object result;
126         final ReturnType returnType = ReturnType.valueOf(bundle.getString("mReturnType"));
127         switch (returnType) {
128             case Null:
129             case KnownUnsupportedType:
130                 result = null;
131                 break;
132             case Boolean:
133                 result = bundle.getBoolean("mReturnValue");
134                 break;
135             default:
136                 throw new UnsupportedOperationException("Unsupported type=" + returnType);
137         }
138         return new ImeEvent(eventName, nestLevel, threadName,
139                 threadId, isMainThread, enterTimestamp, exitTimestamp, enterWallTime, exitWallTime,
140                 enterState, exitState, arguments, result, returnType);
141     }
142 
143     /**
144      * Returns a string that represents the type of this event.
145      *
146      * <p>Examples: &quot;onCreate&quot;, &quot;onStartInput&quot;, ...</p>
147      *
148      * <p>TODO: Use enum type or something like that instead of raw String type.</p>
149      * @return A string that represents the type of this event.
150      */
151     @NonNull
getEventName()152     public String getEventName() {
153         return mEventName;
154     }
155 
156     /**
157      * Returns the nest level of this event.
158      *
159      * <p>For instance, when &quot;showSoftInput&quot; internally calls
160      * &quot;onStartInputView&quot;, the event for &quot;onStartInputView&quot; has 1 level higher
161      * nest level than &quot;showSoftInput&quot;.</p>
162      */
getNestLevel()163     public int getNestLevel() {
164         return mNestLevel;
165     }
166 
167     /**
168      * @return Name of the thread, where the event was consumed.
169      */
170     @NonNull
getThreadName()171     public String getThreadName() {
172         return mThreadName;
173     }
174 
175     /**
176      * @return Thread ID (TID) of the thread, where the event was consumed.
177      */
getThreadId()178     public int getThreadId() {
179         return mThreadId;
180     }
181 
182     /**
183      * @return {@code true} if the event was being consumed in the main thread.
184      */
isMainThread()185     public boolean isMainThread() {
186         return mIsMainThread;
187     }
188 
189     /**
190      * @return Monotonic time measured by {@link android.os.SystemClock#elapsedRealtimeNanos()} when
191      *         the corresponding event handler was called back.
192      */
getEnterTimestamp()193     public long getEnterTimestamp() {
194         return mEnterTimestamp;
195     }
196 
197     /**
198      * @return Monotonic time measured by {@link android.os.SystemClock#elapsedRealtimeNanos()} when
199      *         the corresponding event handler finished.
200      */
getExitTimestamp()201     public long getExitTimestamp() {
202         return mExitTimestamp;
203     }
204 
205     /**
206      * @return Wall-clock time measured by {@link System#currentTimeMillis()} when the corresponding
207      *         event handler was called back.
208      */
getEnterWallTime()209     public long getEnterWallTime() {
210         return mEnterWallTime;
211     }
212 
213     /**
214      * @return Wall-clock time measured by {@link System#currentTimeMillis()} when the corresponding
215      *         event handler finished.
216      */
getExitWallTime()217     public long getExitWallTime() {
218         return mExitWallTime;
219     }
220 
221     /**
222      * @return IME state snapshot taken when the corresponding event handler was called back.
223      */
224     @NonNull
getEnterState()225     public ImeState getEnterState() {
226         return mEnterState;
227     }
228 
229     /**
230      * @return IME state snapshot taken when the corresponding event handler finished.
231      */
232     @Nullable
getExitState()233     public ImeState getExitState() {
234         return mExitState;
235     }
236 
237     /**
238      * @return {@link Bundle} that stores parameters passed to the corresponding event handler.
239      */
240     @NonNull
getArguments()241     public Bundle getArguments() {
242         return mArguments;
243     }
244 
245     /**
246      * @return result value of this event.
247      * @throws NullPointerException if the return value is {@code null}
248      * @throws ClassCastException if the return value is non-{@code null} object that is different
249      *                            from {@link Boolean}
250      */
getReturnBooleanValue()251     public boolean getReturnBooleanValue() {
252         if (mReturnType == ReturnType.Null) {
253             throw new NullPointerException();
254         }
255         if (mReturnType != ReturnType.Boolean) {
256             throw new ClassCastException();
257         }
258         return (Boolean) mReturnValue;
259     }
260 
261     /**
262      * @return {@code true} if the event is issued when the event starts, not when the event
263      * finishes.
264      */
isEnterEvent()265     public boolean isEnterEvent() {
266         return mExitState == null;
267     }
268 
269     @NonNull
270     private final String mEventName;
271     private final int mNestLevel;
272     @NonNull
273     private final String mThreadName;
274     private final int mThreadId;
275     private final boolean mIsMainThread;
276     private final long mEnterTimestamp;
277     private final long mExitTimestamp;
278     private final long mEnterWallTime;
279     private final long mExitWallTime;
280     @NonNull
281     private final ImeState mEnterState;
282     @Nullable
283     private final ImeState mExitState;
284     @NonNull
285     private final Bundle mArguments;
286     @Nullable
287     private final Object mReturnValue;
288     @NonNull
289     private final ReturnType mReturnType;
290 }
291