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