1 /*
2  * Copyright (C) 2016 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 package com.android.tradefed.util.sl4a;
17 
18 import com.android.tradefed.log.LogUtil.CLog;
19 
20 import org.json.JSONException;
21 import org.json.JSONObject;
22 
23 import java.io.IOException;
24 import java.util.HashMap;
25 import java.util.LinkedList;
26 import java.util.List;
27 import java.util.Map;
28 import java.util.function.Predicate;
29 
30 /**
31  * Event dispatcher polls for event and queue them by name to be queried.
32  * TODO: add support for event handlers.
33  */
34 public class Sl4aEventDispatcher extends Thread {
35 
36     private Sl4aClient mClient;
37     private long mTimeout;
38 
39     public static final String SHUTDOWN_EVENT = "EventDispatcherShutdown";
40 
41     // The queues of events
42     private Map<String, LinkedList<EventSl4aObject>> mEventQueue = new HashMap<>();
43 
44     private boolean mCanceled = false;
45 
Sl4aEventDispatcher(Sl4aClient client, long timeout)46     public Sl4aEventDispatcher(Sl4aClient client, long timeout) {
47         this.setName(getClass().getCanonicalName());
48         this.setDaemon(true);
49         mClient = client;
50         mTimeout = timeout;
51     }
52 
53     /**
54      * Continuously poll events and cache them.
55      */
pollEvents()56     private void pollEvents() {
57         while (!mCanceled) {
58             if (!internalPolling()) {
59                 break;
60             }
61         }
62     }
63 
64     /**
65      * Internal polling of events, should not be called.
66      * Exposed for testing.
67      */
internalPolling()68     protected boolean internalPolling() {
69         try {
70             Object response = mClient.rpcCall("eventWait", mTimeout);
71             if (response == null) {
72                 return true;
73             }
74             EventSl4aObject event = new EventSl4aObject(new JSONObject(response.toString()));
75             if (SHUTDOWN_EVENT.equals(event.getName())) {
76                 mCanceled = true;
77                 return false;
78             }
79             synchronized(mEventQueue) {
80                 if (mEventQueue.containsKey(event.getName())) {
81                     mEventQueue.get(event.getName()).add(event);
82                 } else {
83                     LinkedList<EventSl4aObject> queue = new LinkedList<>();
84                     queue.add(event);
85                     mEventQueue.put(event.getName(), queue);
86                 }
87             }
88         } catch (IOException e) {
89             CLog.w("Error '%s' when polling the events.", e);
90         } catch (JSONException e) {
91             CLog.e(e);
92         }
93         return true;
94     }
95 
96     @Override
run()97     public void run() {
98         pollEvents();
99     }
100 
101     /**
102      * Stop the thread execution and clean up all the events.
103      */
cancel()104     public void cancel() {
105         mCanceled = true;
106         this.interrupt();
107         clearAllEvents();
108     }
109 
110     /**
111      * Poll for one event by name
112      *
113      * @param name the name of the event.
114      * @param timeout the timeout in millisecond for the pop event to return.
115      * @return the {@link EventSl4aObject} or null if no event found.
116      */
popEvent(String name, long timeout)117     public EventSl4aObject popEvent(String name, long timeout) {
118         long deadline = System.currentTimeMillis() + timeout;
119         while (System.currentTimeMillis() < deadline){
120             synchronized (mEventQueue) {
121                 if (mEventQueue.get(name) != null) {
122                     // find a remove the first element of the list, null if empty.
123                     EventSl4aObject res = mEventQueue.get(name).poll();
124                     if (res != null) {
125                         return res;
126                     }
127                 }
128             }
129         }
130         CLog.e("Timeout after waiting %sms for event '%s'", timeout, name);
131         return null;
132     }
133 
134     /**
135      * Poll for a particular event that match the name and predicate.
136      *
137      * @param name the name of the event.
138      * @param predicate the predicate the event needs to pass.
139      * @param timeout timeout the timeout in millisecond for the pop event to return.
140      * @return the {@link EventSl4aObject} or null if no event found.
141      */
waitForEvent(String name, Predicate<EventSl4aObject> predicate, long timeout)142     public EventSl4aObject waitForEvent(String name, Predicate<EventSl4aObject> predicate,
143             long timeout) {
144         long deadline = System.currentTimeMillis() + timeout;
145         while (System.currentTimeMillis() < deadline){
146             synchronized (mEventQueue) {
147                 if (mEventQueue.get(name) != null) {
148                     // find a remove the first element of the list, null if empty.
149                     EventSl4aObject res = mEventQueue.get(name).poll();
150                     if (res != null && predicate.test(res)) {
151                         return res;
152                     }
153                 }
154             }
155         }
156         CLog.e("Timeout after waiting %sms for event '%s'", timeout, name);
157         return null;
158     }
159 
160     /**
161      * Return all the events of one type, or empty list if no event.
162      */
popAllEvents(String name)163     public List<EventSl4aObject> popAllEvents(String name) {
164         synchronized (mEventQueue) {
165             if (mEventQueue.get(name) != null) {
166                 List<EventSl4aObject> results = new LinkedList<EventSl4aObject>();
167                 results.addAll(mEventQueue.get(name));
168                 mEventQueue.get(name).clear();
169                 return results;
170             }
171             return new LinkedList<EventSl4aObject>();
172         }
173     }
174 
175     /**
176      * Clear all the events for one event name.
177      */
clearEvents(String name)178     public void clearEvents(String name) {
179         synchronized (mEventQueue) {
180             if (mEventQueue.get(name) != null) {
181                 mEventQueue.get(name).clear();
182             }
183         }
184     }
185 
186     /**
187      * clear all the events
188      */
clearAllEvents()189     public void clearAllEvents() {
190         synchronized (mEventQueue) {
191             for (String key : mEventQueue.keySet()) {
192                 mEventQueue.get(key).clear();
193             }
194             mEventQueue.clear();
195         }
196     }
197 
198     /** Object returned by the event poller. */
199     public static class EventSl4aObject {
200         private String mName = null;
201         private String mData = null;
202         private long mTime = 0L;
203 
EventSl4aObject(JSONObject response)204         public EventSl4aObject(JSONObject response) throws JSONException {
205             mName = response.getString("name");
206             mData = response.get("data").toString();
207             mTime = response.getLong("time");
208         }
209 
getName()210         public String getName() {
211             return mName;
212         }
213 
getData()214         public String getData() {
215             return mData;
216         }
217 
getTime()218         public long getTime() {
219             return mTime;
220         }
221 
222         @Override
toString()223         public String toString() {
224             return "EventSl4aObject [mName=" + mName + ", mData=" + mData + ", mTime=" + mTime
225                     + "]";
226         }
227     }
228 }
229