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 android.inputmethodservice.cts;
18 
19 import static android.inputmethodservice.cts.common.DeviceEventConstants.ACTION_DEVICE_EVENT;
20 import static android.inputmethodservice.cts.common.DeviceEventConstants.EXTRA_EVENT_SENDER;
21 import static android.inputmethodservice.cts.common.DeviceEventConstants.EXTRA_EVENT_TIME;
22 import static android.inputmethodservice.cts.common.DeviceEventConstants.EXTRA_EVENT_TYPE;
23 import static android.inputmethodservice.cts.common.DeviceEventConstants.RECEIVER_CLASS;
24 import static android.inputmethodservice.cts.common.DeviceEventConstants.RECEIVER_PACKAGE;
25 
26 import android.content.ContentValues;
27 import android.content.Intent;
28 import android.database.Cursor;
29 import android.inputmethodservice.cts.common.DeviceEventConstants;
30 import android.inputmethodservice.cts.common.DeviceEventConstants.DeviceEventType;
31 import android.inputmethodservice.cts.common.EventProviderConstants.EventTableConstants;
32 import android.inputmethodservice.cts.common.test.TestInfo;
33 import android.inputmethodservice.cts.db.Entity;
34 import android.inputmethodservice.cts.db.Field;
35 import android.inputmethodservice.cts.db.Table;
36 import android.os.SystemClock;
37 import android.util.Log;
38 
39 import androidx.annotation.NonNull;
40 
41 import java.util.function.Predicate;
42 import java.util.stream.Stream;
43 
44 /**
45  * Device event object.
46  * <p>Device event is one of IME event and Test event, and is used to test behaviors of Input Method
47  * Framework.</p>
48  */
49 public final class DeviceEvent {
50 
51     private static final boolean DEBUG_STREAM = false;
52 
53     public static final Table<DeviceEvent> TABLE = new DeviceEventTable(EventTableConstants.NAME);
54 
55     /**
56      * Create an intent to send a device event.
57      * @param sender an event sender.
58      * @param type an event type defined at {@link DeviceEventType}.
59      * @return an intent that has event {@code sender}, {@code type}, time from
60      *         {@link SystemClock#uptimeMillis()}, and target component of event receiver.
61      */
newDeviceEventIntent(@onNull String sender, @NonNull DeviceEventType type)62     public static Intent newDeviceEventIntent(@NonNull String sender,
63             @NonNull DeviceEventType type) {
64         return new Intent()
65                 .setAction(ACTION_DEVICE_EVENT)
66                 .setClassName(RECEIVER_PACKAGE, RECEIVER_CLASS)
67                 .putExtra(EXTRA_EVENT_SENDER, sender)
68                 .putExtra(EXTRA_EVENT_TYPE, type.name())
69                 .putExtra(EXTRA_EVENT_TIME, SystemClock.uptimeMillis());
70     }
71 
72     /**
73      * Create an {@link DeviceEvent} object from an intent.
74      * @param intent a device event intent defined at {@link DeviceEventConstants}.
75      * @return {@link DeviceEvent} object that has event sender, type, and time form an
76      *         {@code intent}.
77      */
newEvent(Intent intent)78     public static DeviceEvent newEvent(Intent intent) {
79         final String sender = intent.getStringExtra(EXTRA_EVENT_SENDER);
80         if (sender == null) {
81             throw new IllegalArgumentException(
82                     "Intent must have " + EXTRA_EVENT_SENDER + ": " + intent);
83         }
84 
85         final String typeString = intent.getStringExtra(EXTRA_EVENT_TYPE);
86         if (typeString == null) {
87             throw new IllegalArgumentException(
88                     "Intent must have " + EXTRA_EVENT_TYPE + ": " + intent);
89         }
90         final DeviceEventType type = DeviceEventType.valueOf(typeString);
91 
92         if (!intent.hasExtra(EXTRA_EVENT_TIME)) {
93             throw new IllegalArgumentException(
94                     "Intent must have " + EXTRA_EVENT_TIME + ": " + intent);
95         }
96 
97         return new DeviceEvent(sender, type, intent.getLongExtra(EXTRA_EVENT_TIME, 0L));
98     }
99 
100     /**
101      * Build {@link ContentValues} object from {@link DeviceEvent} object.
102      * @param event a {@link DeviceEvent} object to be converted.
103      * @return a converted {@link ContentValues} object.
104      */
buildContentValues(DeviceEvent event)105     public static ContentValues buildContentValues(DeviceEvent event) {
106         return TABLE.buildContentValues(event);
107     }
108 
109     /**
110      * Build {@link Stream<DeviceEvent>} object from {@link Cursor} comes from Content Provider.
111      * @param cursor a {@link Cursor} object to be converted.
112      * @return a converted {@link Stream<DeviceEvent>} object.
113      */
buildStream(Cursor cursor)114     public static Stream<DeviceEvent> buildStream(Cursor cursor) {
115         return TABLE.buildStream(cursor);
116     }
117 
118     /**
119      * Build {@link Predicate<DeviceEvent>} whether a device event comes from {@code sender}
120      *
121      * @param sender event sender.
122      * @return {@link Predicate<DeviceEvent>} object.
123      */
isFrom(String sender)124     public static Predicate<DeviceEvent> isFrom(String sender) {
125         return e -> e.sender.equals(sender);
126     }
127 
128     /**
129      * Build {@link Predicate<DeviceEvent>} whether a device event has an event {@code type}.
130      *
131      * @param type a event type defined in {@link DeviceEventType}.
132      * @return {@link Predicate<DeviceEvent>} object.
133      */
isType(DeviceEventType type)134     public static Predicate<DeviceEvent> isType(DeviceEventType type) {
135         return e -> e.type == type;
136     }
137 
138     /**
139      * Build {@link Predicate<DeviceEvent>} whether a device event is newer than or equals to
140      * {@code time}.
141      *
142      * @param time a time to compare against.
143      * @return {@link Predicate<DeviceEvent>} object.
144      */
isNewerThan(long time)145     public static Predicate<DeviceEvent> isNewerThan(long time) {
146         return e -> e.time >= time;
147     }
148 
149     /**
150      * Event source, either Input Method class name or {@link TestInfo#getTestName()}.
151      */
152     @NonNull
153     public final String sender;
154 
155     /**
156      * Event type, either IME event or Test event.
157      */
158     @NonNull
159     public final DeviceEventType type;
160 
161     /**
162      * Event time, value is from {@link SystemClock#uptimeMillis()}.
163      */
164     public final long time;
165 
DeviceEvent(String sender, DeviceEventType type, long time)166     private DeviceEvent(String sender, DeviceEventType type, long time) {
167         this.sender = sender;
168         this.type = type;
169         this.time = time;
170     }
171 
172     @Override
toString()173     public String toString() {
174         return "Event{ time:" + time + " type:" + type + " sender:" + sender + " }";
175     }
176 
177     /**
178      * Abstraction of device event table in database.
179      */
180     private static final class DeviceEventTable extends Table<DeviceEvent> {
181 
182         private static final String LOG_TAG = DeviceEventTable.class.getSimpleName();
183 
184         private final Field mSender;
185         private final Field mType;
186         private final Field mTime;
187 
DeviceEventTable(String name)188         private DeviceEventTable(String name) {
189             super(name, new Entity.Builder<DeviceEvent>()
190                     .addField(EventTableConstants.SENDER, Cursor.FIELD_TYPE_STRING)
191                     .addField(EventTableConstants.TYPE, Cursor.FIELD_TYPE_STRING)
192                     .addField(EventTableConstants.TIME, Cursor.FIELD_TYPE_INTEGER)
193                     .build());
194             mSender = getField(EventTableConstants.SENDER);
195             mType = getField(EventTableConstants.TYPE);
196             mTime = getField(EventTableConstants.TIME);
197         }
198 
199         @Override
buildContentValues(DeviceEvent event)200         public ContentValues buildContentValues(DeviceEvent event) {
201             final ContentValues values = new ContentValues();
202             mSender.putString(values, event.sender);
203             mType.putString(values, event.type.name());
204             mTime.putLong(values, event.time);
205             return values;
206         }
207 
208         @Override
buildStream(Cursor cursor)209         public Stream<DeviceEvent> buildStream(Cursor cursor) {
210             if (DEBUG_STREAM) {
211                 Log.d(LOG_TAG, "buildStream:");
212             }
213             final Stream.Builder<DeviceEvent> builder = Stream.builder();
214             for (cursor.moveToFirst(); !cursor.isAfterLast(); cursor.moveToNext()) {
215                 final DeviceEvent event = new DeviceEvent(
216                         mSender.getString(cursor),
217                         DeviceEventType.valueOf(mType.getString(cursor)),
218                         mTime.getLong(cursor));
219                 builder.accept(event);
220                 if (DEBUG_STREAM) {
221                     Log.d(LOG_TAG, " event=" + event);
222                 }
223             }
224             return builder.build();
225         }
226     }
227 }
228