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