Winson | e6c9073 | 2015-09-24 16:06:29 -0700 | [diff] [blame] | 1 | /* |
| 2 | * Copyright (C) 2014 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.systemui.recents.events; |
| 18 | |
| 19 | import android.content.BroadcastReceiver; |
| 20 | import android.content.Context; |
| 21 | import android.content.Intent; |
| 22 | import android.content.IntentFilter; |
| 23 | import android.os.Bundle; |
| 24 | import android.os.Handler; |
| 25 | import android.os.Looper; |
| 26 | import android.os.SystemClock; |
| 27 | import android.os.UserHandle; |
| 28 | import android.util.Log; |
| 29 | import android.util.MutableBoolean; |
Winson Chung | aaeaac1 | 2015-12-16 16:49:36 -0500 | [diff] [blame] | 30 | import com.android.systemui.recents.misc.ReferenceCountedTrigger; |
| 31 | |
Winson | e6c9073 | 2015-09-24 16:06:29 -0700 | [diff] [blame] | 32 | import java.lang.ref.WeakReference; |
| 33 | import java.lang.reflect.Constructor; |
| 34 | import java.lang.reflect.InvocationTargetException; |
| 35 | import java.lang.reflect.Method; |
| 36 | import java.lang.reflect.Modifier; |
| 37 | import java.util.ArrayList; |
| 38 | import java.util.Collections; |
| 39 | import java.util.Comparator; |
| 40 | import java.util.HashMap; |
| 41 | import java.util.List; |
| 42 | |
| 43 | /** |
| 44 | * Represents a subscriber, which implements various event bus handler methods. |
| 45 | */ |
| 46 | class Subscriber { |
| 47 | private WeakReference<Object> mSubscriber; |
| 48 | |
| 49 | long registrationTime; |
| 50 | |
| 51 | Subscriber(Object subscriber, long registrationTime) { |
| 52 | mSubscriber = new WeakReference<>(subscriber); |
| 53 | this.registrationTime = registrationTime; |
| 54 | } |
| 55 | |
| 56 | public String toString(int priority) { |
| 57 | Object sub = mSubscriber.get(); |
| 58 | String id = Integer.toHexString(System.identityHashCode(sub)); |
| 59 | return sub.getClass().getSimpleName() + " [0x" + id + ", P" + priority + "]"; |
| 60 | } |
| 61 | |
| 62 | public Object getReference() { |
| 63 | return mSubscriber.get(); |
| 64 | } |
| 65 | } |
| 66 | |
| 67 | /** |
| 68 | * Represents an event handler with a priority. |
| 69 | */ |
| 70 | class EventHandler { |
| 71 | int priority; |
| 72 | Subscriber subscriber; |
| 73 | EventHandlerMethod method; |
| 74 | |
| 75 | EventHandler(Subscriber subscriber, EventHandlerMethod method, int priority) { |
| 76 | this.subscriber = subscriber; |
| 77 | this.method = method; |
| 78 | this.priority = priority; |
| 79 | } |
| 80 | |
| 81 | @Override |
| 82 | public String toString() { |
| 83 | return subscriber.toString(priority) + " " + method.toString(); |
| 84 | } |
| 85 | } |
| 86 | |
| 87 | /** |
| 88 | * Represents the low level method handling a particular event. |
| 89 | */ |
| 90 | class EventHandlerMethod { |
| 91 | private Method mMethod; |
| 92 | Class<? extends EventBus.Event> eventType; |
| 93 | |
| 94 | EventHandlerMethod(Method method, Class<? extends EventBus.Event> eventType) { |
| 95 | mMethod = method; |
| 96 | mMethod.setAccessible(true); |
| 97 | this.eventType = eventType; |
| 98 | } |
| 99 | |
| 100 | public void invoke(Object target, EventBus.Event event) |
| 101 | throws InvocationTargetException, IllegalAccessException { |
| 102 | mMethod.invoke(target, event); |
| 103 | } |
| 104 | |
| 105 | @Override |
| 106 | public String toString() { |
| 107 | return mMethod.getName() + "(" + eventType.getSimpleName() + ")"; |
| 108 | } |
| 109 | } |
| 110 | |
| 111 | /** |
| 112 | * A simple in-process event bus. It is simple because we can make assumptions about the state of |
| 113 | * SystemUI and Recent's lifecycle. |
| 114 | * |
| 115 | * <p> |
| 116 | * Currently, there is a single EventBus that handles {@link EventBus.Event}s for each subscriber |
| 117 | * on the main application thread. Publishers can send() events to synchronously call subscribers |
| 118 | * of that event, or post() events to be processed in the next run of the {@link Looper}. In |
| 119 | * addition, the EventBus supports sending and handling {@link EventBus.InterprocessEvent}s |
| 120 | * (within the same package) implemented using standard {@link BroadcastReceiver} mechanism. |
| 121 | * Interprocess events must be posted using postInterprocess() to ensure that it is dispatched |
| 122 | * correctly across processes. |
| 123 | * |
| 124 | * <p> |
| 125 | * Subscribers must be registered with a particular EventBus before they will receive events, and |
| 126 | * handler methods must match a specific signature. |
| 127 | * |
| 128 | * <p> |
| 129 | * Event method signature:<ul> |
| 130 | * <li>Methods must be public final |
| 131 | * <li>Methods must return void |
| 132 | * <li>Methods must be called "onBusEvent" |
| 133 | * <li>Methods must take one parameter, of class type deriving from {@link EventBus.Event} |
| 134 | * </ul> |
| 135 | * |
| 136 | * <p> |
| 137 | * Interprocess-Event method signature:<ul> |
| 138 | * <li>Methods must be public final |
| 139 | * <li>Methods must return void |
| 140 | * <li>Methods must be called "onInterprocessBusEvent" |
| 141 | * <li>Methods must take one parameter, of class type deriving from {@link EventBus.InterprocessEvent} |
| 142 | * </ul> |
| 143 | * </p> |
| 144 | * |
| 145 | * </p> |
| 146 | * Each subscriber can be registered with a given priority (default 1), and events will be dispatch |
| 147 | * in decreasing order of priority. For subscribers with the same priority, events will be |
| 148 | * dispatched by latest registration time to earliest. |
| 149 | * |
| 150 | * <p> |
| 151 | * Interprocess events must extend {@link EventBus.InterprocessEvent}, have a constructor which |
| 152 | * takes a {@link Bundle} and implement toBundle(). This allows us to serialize events to be sent |
| 153 | * across processes. |
| 154 | * |
| 155 | * <p> |
| 156 | * Caveats:<ul> |
| 157 | * <li>The EventBus keeps a {@link WeakReference} to the publisher to prevent memory leaks, so |
| 158 | * there must be another strong reference to the publisher for it to not get garbage-collected and |
| 159 | * continue receiving events. |
| 160 | * <li>Because the event handlers are called back using reflection, the EventBus is not intended |
| 161 | * for use in tight, performance criticial loops. For most user input/system callback events, this |
| 162 | * is generally of low enough frequency to use the EventBus. |
| 163 | * <li>Because the event handlers are called back using reflection, there will often be no |
| 164 | * references to them from actual code. The proguard configuration will be need to be updated to |
| 165 | * keep these extra methods: |
| 166 | * |
| 167 | * -keepclassmembers class ** { |
| 168 | * public void onBusEvent(**); |
| 169 | * public void onInterprocessBusEvent(**); |
| 170 | * } |
| 171 | * -keepclassmembers class ** extends **.EventBus$InterprocessEvent { |
| 172 | * public <init>(android.os.Bundle); |
| 173 | * } |
| 174 | * |
| 175 | * <li>Subscriber registration can be expensive depending on the subscriber's {@link Class}. This |
| 176 | * is only done once per class type, but if possible, it is best to pre-register an instance of |
| 177 | * that class beforehand or when idle. |
| 178 | * <li>Each event should be sent once. Events may hold internal information about the current |
| 179 | * dispatch, or may be queued to be dispatched on another thread (if posted from a non-main thread), |
| 180 | * so it may be unsafe to edit, change, or re-send the event again. |
| 181 | * <li>Events should follow a pattern of public-final POD (plain old data) objects, where they are |
| 182 | * initialized by the constructor and read by each subscriber of that event. Subscribers should |
| 183 | * never alter events as they are processed, and this enforces that pattern. |
| 184 | * </ul> |
| 185 | * |
| 186 | * <p> |
| 187 | * Future optimizations: |
| 188 | * <li>throw exception/log when a subscriber loses the reference |
| 189 | * <li>trace cost per registration & invocation |
| 190 | * <li>trace cross-process invocation |
| 191 | * <li>register(subscriber, Class<?>...) -- pass in exact class types you want registered |
| 192 | * <li>setSubscriberEventHandlerPriority(subscriber, Class<Event>, priority) |
| 193 | * <li>allow subscribers to implement interface, ie. EventBus.Subscriber, which lets then test a |
| 194 | * message before invocation (ie. check if task id == this task id) |
| 195 | * <li>add postOnce() which automatically debounces |
| 196 | * <li>add postDelayed() which delays / postDelayedOnce() which delays and bounces |
| 197 | * <li>consolidate register() and registerInterprocess() |
| 198 | * <li>sendForResult<ReturnType>(Event) to send and get a result, but who will send the |
| 199 | * result? |
| 200 | * </p> |
| 201 | */ |
| 202 | public class EventBus extends BroadcastReceiver { |
| 203 | |
Winson | 0d14d4d | 2015-10-26 17:05:04 -0700 | [diff] [blame] | 204 | private static final String TAG = "EventBus"; |
| 205 | private static final boolean DEBUG_TRACE_ALL = false; |
Winson | e6c9073 | 2015-09-24 16:06:29 -0700 | [diff] [blame] | 206 | |
| 207 | /** |
| 208 | * An event super class that allows us to track internal event state across subscriber |
| 209 | * invocations. |
| 210 | * |
| 211 | * Events should not be edited by subscribers. |
| 212 | */ |
Winson | e7f138c | 2015-10-22 16:15:21 -0700 | [diff] [blame] | 213 | public static class Event implements Cloneable { |
Winson | e6c9073 | 2015-09-24 16:06:29 -0700 | [diff] [blame] | 214 | // Indicates that this event's dispatch should be traced and logged to logcat |
| 215 | boolean trace; |
| 216 | // Indicates that this event must be posted on the EventBus's looper thread before invocation |
| 217 | boolean requiresPost; |
| 218 | // Not currently exposed, allows a subscriber to cancel further dispatch of this event |
| 219 | boolean cancelled; |
| 220 | |
| 221 | // Only accessible from derived events |
| 222 | protected Event() {} |
Winson | e7f138c | 2015-10-22 16:15:21 -0700 | [diff] [blame] | 223 | |
Winson Chung | aaeaac1 | 2015-12-16 16:49:36 -0500 | [diff] [blame] | 224 | /** |
| 225 | * Called by the EventBus prior to dispatching this event to any subscriber of this event. |
| 226 | */ |
| 227 | void onPreDispatch() { |
| 228 | // Do nothing |
| 229 | } |
| 230 | |
| 231 | /** |
| 232 | * Called by the EventBus after dispatching this event to every subscriber of this event. |
| 233 | */ |
| 234 | void onPostDispatch() { |
| 235 | // Do nothing |
| 236 | } |
| 237 | |
Winson | e7f138c | 2015-10-22 16:15:21 -0700 | [diff] [blame] | 238 | @Override |
| 239 | protected Object clone() throws CloneNotSupportedException { |
| 240 | Event evt = (Event) super.clone(); |
| 241 | // When cloning an event, reset the cancelled-dispatch state |
| 242 | evt.cancelled = false; |
| 243 | return evt; |
| 244 | } |
Winson | e6c9073 | 2015-09-24 16:06:29 -0700 | [diff] [blame] | 245 | } |
| 246 | |
| 247 | /** |
Winson Chung | aaeaac1 | 2015-12-16 16:49:36 -0500 | [diff] [blame] | 248 | * An event that represents an animated state change, which allows subscribers to coordinate |
| 249 | * callbacks which happen after the animation has taken place. |
| 250 | * |
| 251 | * Internally, it is guaranteed that increment() and decrement() will be called before and the |
| 252 | * after the event is dispatched. |
| 253 | */ |
| 254 | public static class AnimatedEvent extends Event { |
| 255 | |
| 256 | private final ReferenceCountedTrigger mTrigger = new ReferenceCountedTrigger(); |
| 257 | |
| 258 | // Only accessible from derived events |
| 259 | protected AnimatedEvent() {} |
| 260 | |
| 261 | /** |
| 262 | * Returns the reference counted trigger that coordinates the animations for this event. |
| 263 | */ |
| 264 | public ReferenceCountedTrigger getAnimationTrigger() { |
| 265 | return mTrigger; |
| 266 | } |
| 267 | |
| 268 | /** |
| 269 | * Adds a callback that is guaranteed to be called after the state has changed regardless of |
| 270 | * whether an actual animation took place. |
| 271 | */ |
| 272 | public void addPostAnimationCallback(Runnable r) { |
| 273 | mTrigger.addLastDecrementRunnable(r); |
| 274 | } |
| 275 | |
| 276 | @Override |
| 277 | void onPreDispatch() { |
| 278 | mTrigger.increment(); |
| 279 | } |
| 280 | |
| 281 | @Override |
| 282 | void onPostDispatch() { |
| 283 | mTrigger.decrement(); |
| 284 | } |
| 285 | |
| 286 | @Override |
| 287 | protected Object clone() throws CloneNotSupportedException { |
| 288 | throw new CloneNotSupportedException(); |
| 289 | } |
| 290 | } |
| 291 | |
| 292 | /** |
Winson | 5500390 | 2016-01-12 12:00:37 -0800 | [diff] [blame] | 293 | * An event that can be reusable, only used for situations where we want to reduce memory |
| 294 | * allocations when events are sent frequently (ie. on scroll). |
| 295 | */ |
| 296 | public static class ReusableEvent extends Event { |
| 297 | |
| 298 | private int mDispatchCount; |
| 299 | |
| 300 | protected ReusableEvent() {} |
| 301 | |
| 302 | @Override |
| 303 | void onPostDispatch() { |
| 304 | super.onPostDispatch(); |
| 305 | mDispatchCount++; |
| 306 | } |
| 307 | |
| 308 | @Override |
| 309 | protected Object clone() throws CloneNotSupportedException { |
| 310 | throw new CloneNotSupportedException(); |
| 311 | } |
| 312 | } |
| 313 | |
| 314 | /** |
Winson | e6c9073 | 2015-09-24 16:06:29 -0700 | [diff] [blame] | 315 | * An inter-process event super class that allows us to track user state across subscriber |
| 316 | * invocations. |
| 317 | */ |
| 318 | public static class InterprocessEvent extends Event { |
| 319 | private static final String EXTRA_USER = "_user"; |
| 320 | |
| 321 | // The user which this event originated from |
| 322 | public final int user; |
| 323 | |
| 324 | // Only accessible from derived events |
| 325 | protected InterprocessEvent(int user) { |
| 326 | this.user = user; |
| 327 | } |
| 328 | |
| 329 | /** |
| 330 | * Called from the event bus |
| 331 | */ |
| 332 | protected InterprocessEvent(Bundle b) { |
| 333 | user = b.getInt(EXTRA_USER); |
| 334 | } |
| 335 | |
| 336 | protected Bundle toBundle() { |
| 337 | Bundle b = new Bundle(); |
| 338 | b.putInt(EXTRA_USER, user); |
| 339 | return b; |
| 340 | } |
| 341 | } |
| 342 | |
| 343 | /** |
| 344 | * Proguard must also know, and keep, all methods matching this signature. |
| 345 | * |
| 346 | * -keepclassmembers class ** { |
| 347 | * public void onBusEvent(**); |
| 348 | * public void onInterprocessBusEvent(**); |
| 349 | * } |
| 350 | */ |
| 351 | private static final String METHOD_PREFIX = "onBusEvent"; |
| 352 | private static final String INTERPROCESS_METHOD_PREFIX = "onInterprocessBusEvent"; |
| 353 | |
| 354 | // Ensures that interprocess events can only be sent from a process holding this permission. */ |
| 355 | private static final String PERMISSION_SELF = "com.android.systemui.permission.SELF"; |
| 356 | |
| 357 | // Used for passing event data across process boundaries |
| 358 | private static final String EXTRA_INTERPROCESS_EVENT_BUNDLE = "interprocess_event_bundle"; |
| 359 | |
| 360 | // The default priority of all subscribers |
| 361 | private static final int DEFAULT_SUBSCRIBER_PRIORITY = 1; |
| 362 | |
Winson | e6c9073 | 2015-09-24 16:06:29 -0700 | [diff] [blame] | 363 | // Orders the handlers by priority and registration time |
| 364 | private static final Comparator<EventHandler> EVENT_HANDLER_COMPARATOR = new Comparator<EventHandler>() { |
| 365 | @Override |
| 366 | public int compare(EventHandler h1, EventHandler h2) { |
| 367 | // Rank the handlers by priority descending, followed by registration time descending. |
| 368 | // aka. the later registered |
| 369 | if (h1.priority != h2.priority) { |
| 370 | return h2.priority - h1.priority; |
| 371 | } else { |
| 372 | return Long.compare(h2.subscriber.registrationTime, h1.subscriber.registrationTime); |
| 373 | } |
| 374 | } |
| 375 | }; |
| 376 | |
| 377 | // Used for initializing the default bus |
| 378 | private static final Object sLock = new Object(); |
| 379 | private static EventBus sDefaultBus; |
| 380 | |
| 381 | // The handler to post all events |
| 382 | private Handler mHandler; |
| 383 | |
| 384 | // Keep track of whether we have registered a broadcast receiver already, so that we can |
| 385 | // unregister ourselves before re-registering again with a new IntentFilter. |
| 386 | private boolean mHasRegisteredReceiver; |
| 387 | |
| 388 | /** |
| 389 | * Map from event class -> event handler list. Keeps track of the actual mapping from event |
| 390 | * to subscriber method. |
| 391 | */ |
| 392 | private HashMap<Class<? extends Event>, ArrayList<EventHandler>> mEventTypeMap = new HashMap<>(); |
| 393 | |
| 394 | /** |
| 395 | * Map from subscriber class -> event handler method lists. Used to determine upon registration |
| 396 | * of a new subscriber whether we need to read all the subscriber's methods again using |
| 397 | * reflection or whether we can just add the subscriber to the event type map. |
| 398 | */ |
| 399 | private HashMap<Class<? extends Object>, ArrayList<EventHandlerMethod>> mSubscriberTypeMap = new HashMap<>(); |
| 400 | |
| 401 | /** |
| 402 | * Map from interprocess event name -> interprocess event class. Used for mapping the event |
| 403 | * name after receiving the broadcast, to the event type. After which a new instance is created |
| 404 | * and posted in the local process. |
| 405 | */ |
| 406 | private HashMap<String, Class<? extends InterprocessEvent>> mInterprocessEventNameMap = new HashMap<>(); |
| 407 | |
| 408 | /** |
| 409 | * Set of all currently registered subscribers |
| 410 | */ |
| 411 | private ArrayList<Subscriber> mSubscribers = new ArrayList<>(); |
| 412 | |
| 413 | // For tracing |
| 414 | private int mCallCount; |
| 415 | private long mCallDurationMicros; |
| 416 | |
| 417 | /** |
| 418 | * Private constructor to create an event bus for a given looper. |
| 419 | */ |
| 420 | private EventBus(Looper looper) { |
| 421 | mHandler = new Handler(looper); |
| 422 | } |
| 423 | |
| 424 | /** |
| 425 | * @return the default event bus for the application's main thread. |
| 426 | */ |
| 427 | public static EventBus getDefault() { |
| 428 | if (sDefaultBus == null) |
| 429 | synchronized (sLock) { |
| 430 | if (sDefaultBus == null) { |
| 431 | if (DEBUG_TRACE_ALL) { |
| 432 | logWithPid("New EventBus"); |
| 433 | } |
| 434 | sDefaultBus = new EventBus(Looper.getMainLooper()); |
| 435 | } |
| 436 | } |
| 437 | return sDefaultBus; |
| 438 | } |
| 439 | |
| 440 | /** |
| 441 | * Registers a subscriber to receive events with the default priority. |
| 442 | * |
| 443 | * @param subscriber the subscriber to handle events. If this is the first instance of the |
| 444 | * subscriber's class type that has been registered, the class's methods will |
| 445 | * be scanned for appropriate event handler methods. |
| 446 | */ |
| 447 | public void register(Object subscriber) { |
| 448 | registerSubscriber(subscriber, DEFAULT_SUBSCRIBER_PRIORITY, null); |
| 449 | } |
| 450 | |
| 451 | /** |
| 452 | * Registers a subscriber to receive events with the given priority. |
| 453 | * |
| 454 | * @param subscriber the subscriber to handle events. If this is the first instance of the |
| 455 | * subscriber's class type that has been registered, the class's methods will |
| 456 | * be scanned for appropriate event handler methods. |
| 457 | * @param priority the priority that this subscriber will receive events relative to other |
| 458 | * subscribers |
| 459 | */ |
| 460 | public void register(Object subscriber, int priority) { |
| 461 | registerSubscriber(subscriber, priority, null); |
| 462 | } |
| 463 | |
| 464 | /** |
| 465 | * Explicitly registers a subscriber to receive interprocess events with the default priority. |
| 466 | * |
| 467 | * @param subscriber the subscriber to handle events. If this is the first instance of the |
| 468 | * subscriber's class type that has been registered, the class's methods will |
| 469 | * be scanned for appropriate event handler methods. |
| 470 | */ |
| 471 | public void registerInterprocessAsCurrentUser(Context context, Object subscriber) { |
| 472 | registerInterprocessAsCurrentUser(context, subscriber, DEFAULT_SUBSCRIBER_PRIORITY); |
| 473 | } |
| 474 | |
| 475 | /** |
| 476 | * Registers a subscriber to receive interprocess events with the given priority. |
| 477 | * |
| 478 | * @param subscriber the subscriber to handle events. If this is the first instance of the |
| 479 | * subscriber's class type that has been registered, the class's methods will |
| 480 | * be scanned for appropriate event handler methods. |
| 481 | * @param priority the priority that this subscriber will receive events relative to other |
| 482 | * subscribers |
| 483 | */ |
| 484 | public void registerInterprocessAsCurrentUser(Context context, Object subscriber, int priority) { |
| 485 | if (DEBUG_TRACE_ALL) { |
| 486 | logWithPid("registerInterprocessAsCurrentUser(" + subscriber.getClass().getSimpleName() + ")"); |
| 487 | } |
| 488 | |
| 489 | // Register the subscriber normally, and update the broadcast receiver filter if this is |
| 490 | // a new subscriber type with interprocess events |
| 491 | MutableBoolean hasInterprocessEventsChanged = new MutableBoolean(false); |
| 492 | registerSubscriber(subscriber, priority, hasInterprocessEventsChanged); |
| 493 | if (DEBUG_TRACE_ALL) { |
| 494 | logWithPid("hasInterprocessEventsChanged: " + hasInterprocessEventsChanged.value); |
| 495 | } |
| 496 | if (hasInterprocessEventsChanged.value) { |
| 497 | registerReceiverForInterprocessEvents(context); |
| 498 | } |
| 499 | } |
| 500 | |
| 501 | /** |
| 502 | * Remove all EventHandlers pointing to the specified subscriber. This does not remove the |
| 503 | * mapping of subscriber type to event handler method, in case new instances of this subscriber |
| 504 | * are registered. |
| 505 | */ |
| 506 | public void unregister(Object subscriber) { |
| 507 | if (DEBUG_TRACE_ALL) { |
| 508 | logWithPid("unregister()"); |
| 509 | } |
| 510 | |
| 511 | // Fail immediately if we are being called from the non-main thread |
| 512 | long callingThreadId = Thread.currentThread().getId(); |
| 513 | if (callingThreadId != mHandler.getLooper().getThread().getId()) { |
| 514 | throw new RuntimeException("Can not unregister() a subscriber from a non-main thread."); |
| 515 | } |
| 516 | |
| 517 | // Return early if this is not a registered subscriber |
| 518 | if (!findRegisteredSubscriber(subscriber, true /* removeFoundSubscriber */)) { |
| 519 | return; |
| 520 | } |
| 521 | |
| 522 | Class<?> subscriberType = subscriber.getClass(); |
| 523 | ArrayList<EventHandlerMethod> subscriberMethods = mSubscriberTypeMap.get(subscriberType); |
| 524 | if (subscriberMethods != null) { |
| 525 | // For each of the event handlers the subscriber handles, remove all references of that |
| 526 | // handler |
| 527 | for (EventHandlerMethod method : subscriberMethods) { |
| 528 | ArrayList<EventHandler> eventHandlers = mEventTypeMap.get(method.eventType); |
| 529 | for (int i = eventHandlers.size() - 1; i >= 0; i--) { |
| 530 | if (eventHandlers.get(i).subscriber.getReference() == subscriber) { |
| 531 | eventHandlers.remove(i); |
| 532 | } |
| 533 | } |
| 534 | } |
| 535 | } |
| 536 | } |
| 537 | |
| 538 | /** |
| 539 | * Explicit unregistration for interprocess event subscribers. This actually behaves exactly |
| 540 | * the same as unregister() since we also do not want to stop listening for specific |
| 541 | * inter-process messages in case new instances of that subscriber is registered. |
| 542 | */ |
| 543 | public void unregisterInterprocess(Context context, Object subscriber) { |
| 544 | if (DEBUG_TRACE_ALL) { |
| 545 | logWithPid("unregisterInterprocess()"); |
| 546 | } |
| 547 | unregister(subscriber); |
| 548 | } |
| 549 | |
| 550 | /** |
| 551 | * Sends an event to the subscribers of the given event type immediately. This can only be |
| 552 | * called from the same thread as the EventBus's looper thread (for the default EventBus, this |
| 553 | * is the main application thread). |
| 554 | */ |
| 555 | public void send(Event event) { |
| 556 | // Fail immediately if we are being called from the non-main thread |
| 557 | long callingThreadId = Thread.currentThread().getId(); |
| 558 | if (callingThreadId != mHandler.getLooper().getThread().getId()) { |
| 559 | throw new RuntimeException("Can not send() a message from a non-main thread."); |
| 560 | } |
| 561 | |
| 562 | if (DEBUG_TRACE_ALL) { |
| 563 | logWithPid("send(" + event.getClass().getSimpleName() + ")"); |
| 564 | } |
| 565 | |
| 566 | // Reset the event's cancelled state |
| 567 | event.requiresPost = false; |
| 568 | event.cancelled = false; |
| 569 | queueEvent(event); |
| 570 | } |
| 571 | |
| 572 | /** |
| 573 | * Post a message to the subscribers of the given event type. The messages will be posted on |
| 574 | * the EventBus's looper thread (for the default EventBus, this is the main application thread). |
| 575 | */ |
| 576 | public void post(Event event) { |
| 577 | if (DEBUG_TRACE_ALL) { |
| 578 | logWithPid("post(" + event.getClass().getSimpleName() + ")"); |
| 579 | } |
| 580 | |
| 581 | // Reset the event's cancelled state |
| 582 | event.requiresPost = true; |
| 583 | event.cancelled = false; |
| 584 | queueEvent(event); |
| 585 | } |
| 586 | |
Jorim Jaggi | dd98d41 | 2015-11-18 15:57:38 -0800 | [diff] [blame] | 587 | /** |
| 588 | * If this method is called from the main thread, it will be handled directly. If this method |
| 589 | * is not called from the main thread, it will be posted onto the main thread. |
| 590 | */ |
| 591 | public void sendOntoMainThread(Event event) { |
| 592 | long callingThreadId = Thread.currentThread().getId(); |
| 593 | if (callingThreadId != mHandler.getLooper().getThread().getId()) { |
| 594 | post(event); |
| 595 | } else { |
| 596 | send(event); |
| 597 | } |
| 598 | } |
| 599 | |
Winson | e6c9073 | 2015-09-24 16:06:29 -0700 | [diff] [blame] | 600 | /** Prevent post()ing an InterprocessEvent */ |
| 601 | @Deprecated |
| 602 | public void post(InterprocessEvent event) { |
| 603 | throw new RuntimeException("Not supported, use postInterprocess"); |
| 604 | } |
| 605 | |
| 606 | /** Prevent send()ing an InterprocessEvent */ |
| 607 | @Deprecated |
| 608 | public void send(InterprocessEvent event) { |
| 609 | throw new RuntimeException("Not supported, use postInterprocess"); |
| 610 | } |
| 611 | |
| 612 | /** |
| 613 | * Posts an interprocess event. |
| 614 | */ |
| 615 | public void postInterprocess(Context context, final InterprocessEvent event) { |
| 616 | if (DEBUG_TRACE_ALL) { |
| 617 | logWithPid("postInterprocess(" + event.getClass().getSimpleName() + ")"); |
| 618 | } |
| 619 | String eventType = event.getClass().getName(); |
| 620 | Bundle eventBundle = event.toBundle(); |
| 621 | Intent intent = new Intent(eventType); |
| 622 | intent.setPackage(context.getPackageName()); |
| 623 | intent.putExtra(EXTRA_INTERPROCESS_EVENT_BUNDLE, eventBundle); |
| 624 | intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT | |
| 625 | Intent.FLAG_RECEIVER_FOREGROUND); |
| 626 | context.sendBroadcastAsUser(intent, UserHandle.ALL); |
| 627 | } |
| 628 | |
| 629 | /** |
| 630 | * Receiver for interprocess events. |
| 631 | */ |
| 632 | @Override |
| 633 | public void onReceive(Context context, Intent intent) { |
| 634 | if (DEBUG_TRACE_ALL) { |
| 635 | logWithPid("onReceive(" + intent.getAction() + ", user " + UserHandle.myUserId() + ")"); |
| 636 | } |
| 637 | |
| 638 | Bundle eventBundle = intent.getBundleExtra(EXTRA_INTERPROCESS_EVENT_BUNDLE); |
| 639 | Class<? extends InterprocessEvent> eventType = mInterprocessEventNameMap.get(intent.getAction()); |
| 640 | try { |
| 641 | Constructor<? extends InterprocessEvent> ctor = eventType.getConstructor(Bundle.class); |
| 642 | send((Event) ctor.newInstance(eventBundle)); |
| 643 | } catch (NoSuchMethodException| |
| 644 | InvocationTargetException| |
| 645 | InstantiationException| |
| 646 | IllegalAccessException e) { |
Winson | eca4ab6 | 2015-11-04 10:50:28 -0800 | [diff] [blame] | 647 | Log.e(TAG, "Failed to create InterprocessEvent", e.getCause()); |
Winson | e6c9073 | 2015-09-24 16:06:29 -0700 | [diff] [blame] | 648 | } |
| 649 | } |
| 650 | |
| 651 | /** |
| 652 | * @return a dump of the current state of the EventBus |
| 653 | */ |
| 654 | public String dump() { |
| 655 | StringBuilder output = new StringBuilder(); |
| 656 | output.append("Registered class types:"); |
| 657 | output.append("\n"); |
| 658 | for (Class<?> clz : mSubscriberTypeMap.keySet()) { |
| 659 | output.append("\t"); |
| 660 | output.append(clz.getSimpleName()); |
| 661 | output.append("\n"); |
| 662 | } |
| 663 | output.append("Event map:"); |
| 664 | output.append("\n"); |
| 665 | for (Class<?> clz : mEventTypeMap.keySet()) { |
| 666 | output.append("\t"); |
| 667 | output.append(clz.getSimpleName()); |
| 668 | output.append(" -> "); |
| 669 | output.append("\n"); |
| 670 | ArrayList<EventHandler> handlers = mEventTypeMap.get(clz); |
| 671 | for (EventHandler handler : handlers) { |
| 672 | Object subscriber = handler.subscriber.getReference(); |
| 673 | if (subscriber != null) { |
| 674 | String id = Integer.toHexString(System.identityHashCode(subscriber)); |
| 675 | output.append("\t\t"); |
| 676 | output.append(subscriber.getClass().getSimpleName()); |
| 677 | output.append(" [0x" + id + ", #" + handler.priority + "]"); |
| 678 | output.append("\n"); |
| 679 | } |
| 680 | } |
| 681 | } |
| 682 | return output.toString(); |
| 683 | } |
| 684 | |
| 685 | /** |
| 686 | * Registers a new subscriber. |
Winson | e6c9073 | 2015-09-24 16:06:29 -0700 | [diff] [blame] | 687 | */ |
| 688 | private void registerSubscriber(Object subscriber, int priority, |
| 689 | MutableBoolean hasInterprocessEventsChangedOut) { |
| 690 | // Fail immediately if we are being called from the non-main thread |
| 691 | long callingThreadId = Thread.currentThread().getId(); |
| 692 | if (callingThreadId != mHandler.getLooper().getThread().getId()) { |
| 693 | throw new RuntimeException("Can not register() a subscriber from a non-main thread."); |
| 694 | } |
| 695 | |
| 696 | // Return immediately if this exact subscriber is already registered |
| 697 | if (findRegisteredSubscriber(subscriber, false /* removeFoundSubscriber */)) { |
| 698 | return; |
| 699 | } |
| 700 | |
| 701 | long t1 = 0; |
| 702 | if (DEBUG_TRACE_ALL) { |
| 703 | t1 = SystemClock.currentTimeMicro(); |
| 704 | logWithPid("registerSubscriber(" + subscriber.getClass().getSimpleName() + ")"); |
| 705 | } |
| 706 | Subscriber sub = new Subscriber(subscriber, SystemClock.uptimeMillis()); |
| 707 | Class<?> subscriberType = subscriber.getClass(); |
| 708 | ArrayList<EventHandlerMethod> subscriberMethods = mSubscriberTypeMap.get(subscriberType); |
| 709 | if (subscriberMethods != null) { |
| 710 | if (DEBUG_TRACE_ALL) { |
| 711 | logWithPid("Subscriber class type already registered"); |
| 712 | } |
| 713 | |
| 714 | // If we've parsed this subscriber type before, just add to the set for all the known |
| 715 | // events |
| 716 | for (EventHandlerMethod method : subscriberMethods) { |
| 717 | ArrayList<EventHandler> eventTypeHandlers = mEventTypeMap.get(method.eventType); |
| 718 | eventTypeHandlers.add(new EventHandler(sub, method, priority)); |
| 719 | sortEventHandlersByPriority(eventTypeHandlers); |
| 720 | } |
| 721 | mSubscribers.add(sub); |
| 722 | return; |
| 723 | } else { |
| 724 | if (DEBUG_TRACE_ALL) { |
| 725 | logWithPid("Subscriber class type requires registration"); |
| 726 | } |
| 727 | |
| 728 | // If we are parsing this type from scratch, ensure we add it to the subscriber type |
| 729 | // map, and pull out he handler methods below |
| 730 | subscriberMethods = new ArrayList<>(); |
| 731 | mSubscriberTypeMap.put(subscriberType, subscriberMethods); |
| 732 | mSubscribers.add(sub); |
| 733 | } |
| 734 | |
| 735 | // Find all the valid event bus handler methods of the subscriber |
| 736 | MutableBoolean isInterprocessEvent = new MutableBoolean(false); |
Winson | 2536c7e | 2015-10-01 15:49:31 -0700 | [diff] [blame] | 737 | Method[] methods = subscriberType.getDeclaredMethods(); |
Winson | e6c9073 | 2015-09-24 16:06:29 -0700 | [diff] [blame] | 738 | for (Method m : methods) { |
| 739 | Class<?>[] parameterTypes = m.getParameterTypes(); |
| 740 | isInterprocessEvent.value = false; |
| 741 | if (isValidEventBusHandlerMethod(m, parameterTypes, isInterprocessEvent)) { |
| 742 | Class<? extends Event> eventType = (Class<? extends Event>) parameterTypes[0]; |
| 743 | ArrayList<EventHandler> eventTypeHandlers = mEventTypeMap.get(eventType); |
| 744 | if (eventTypeHandlers == null) { |
| 745 | eventTypeHandlers = new ArrayList<>(); |
| 746 | mEventTypeMap.put(eventType, eventTypeHandlers); |
| 747 | } |
| 748 | if (isInterprocessEvent.value) { |
| 749 | try { |
| 750 | // Enforce that the event must have a Bundle constructor |
| 751 | eventType.getConstructor(Bundle.class); |
| 752 | |
| 753 | mInterprocessEventNameMap.put(eventType.getName(), |
| 754 | (Class<? extends InterprocessEvent>) eventType); |
| 755 | if (hasInterprocessEventsChangedOut != null) { |
| 756 | hasInterprocessEventsChangedOut.value = true; |
| 757 | } |
| 758 | } catch (NoSuchMethodException e) { |
| 759 | throw new RuntimeException("Expected InterprocessEvent to have a Bundle constructor"); |
| 760 | } |
| 761 | } |
| 762 | EventHandlerMethod method = new EventHandlerMethod(m, eventType); |
| 763 | EventHandler handler = new EventHandler(sub, method, priority); |
| 764 | eventTypeHandlers.add(handler); |
| 765 | subscriberMethods.add(method); |
| 766 | sortEventHandlersByPriority(eventTypeHandlers); |
| 767 | |
| 768 | if (DEBUG_TRACE_ALL) { |
| 769 | logWithPid(" * Method: " + m.getName() + |
| 770 | " event: " + parameterTypes[0].getSimpleName() + |
| 771 | " interprocess? " + isInterprocessEvent.value); |
| 772 | } |
| 773 | } |
| 774 | } |
| 775 | if (DEBUG_TRACE_ALL) { |
| 776 | logWithPid("Registered " + subscriber.getClass().getSimpleName() + " in " + |
| 777 | (SystemClock.currentTimeMicro() - t1) + " microseconds"); |
| 778 | } |
| 779 | } |
| 780 | |
| 781 | /** |
| 782 | * Adds a new message. |
| 783 | */ |
| 784 | private void queueEvent(final Event event) { |
| 785 | ArrayList<EventHandler> eventHandlers = mEventTypeMap.get(event.getClass()); |
| 786 | if (eventHandlers == null) { |
| 787 | return; |
| 788 | } |
Winson Chung | aaeaac1 | 2015-12-16 16:49:36 -0500 | [diff] [blame] | 789 | |
| 790 | // Prepare this event |
| 791 | boolean hasPostedEvent = false; |
| 792 | event.onPreDispatch(); |
| 793 | |
Winson | e6c9073 | 2015-09-24 16:06:29 -0700 | [diff] [blame] | 794 | // We need to clone the list in case a subscriber unregisters itself during traversal |
Winson | 5500390 | 2016-01-12 12:00:37 -0800 | [diff] [blame] | 795 | // TODO: Investigate whether we can skip the object creation here |
Winson | e6c9073 | 2015-09-24 16:06:29 -0700 | [diff] [blame] | 796 | eventHandlers = (ArrayList<EventHandler>) eventHandlers.clone(); |
Winson | 5500390 | 2016-01-12 12:00:37 -0800 | [diff] [blame] | 797 | int eventHandlerCount = eventHandlers.size(); |
| 798 | for (int i = 0; i < eventHandlerCount; i++) { |
| 799 | final EventHandler eventHandler = eventHandlers.get(i); |
Winson | e6c9073 | 2015-09-24 16:06:29 -0700 | [diff] [blame] | 800 | if (eventHandler.subscriber.getReference() != null) { |
| 801 | if (event.requiresPost) { |
| 802 | mHandler.post(new Runnable() { |
| 803 | @Override |
| 804 | public void run() { |
| 805 | processEvent(eventHandler, event); |
| 806 | } |
| 807 | }); |
Winson Chung | aaeaac1 | 2015-12-16 16:49:36 -0500 | [diff] [blame] | 808 | hasPostedEvent = true; |
Winson | e6c9073 | 2015-09-24 16:06:29 -0700 | [diff] [blame] | 809 | } else { |
| 810 | processEvent(eventHandler, event); |
| 811 | } |
| 812 | } |
| 813 | } |
Winson Chung | aaeaac1 | 2015-12-16 16:49:36 -0500 | [diff] [blame] | 814 | |
| 815 | // Clean up after this event, deferring until all subscribers have been called |
| 816 | if (hasPostedEvent) { |
| 817 | mHandler.post(new Runnable() { |
| 818 | @Override |
| 819 | public void run() { |
| 820 | event.onPostDispatch(); |
| 821 | } |
| 822 | }); |
| 823 | } else { |
| 824 | event.onPostDispatch(); |
| 825 | } |
Winson | e6c9073 | 2015-09-24 16:06:29 -0700 | [diff] [blame] | 826 | } |
| 827 | |
| 828 | /** |
| 829 | * Processes and dispatches the given event to the given event handler, on the thread of whoever |
| 830 | * calls this method. |
| 831 | */ |
| 832 | private void processEvent(final EventHandler eventHandler, final Event event) { |
| 833 | // Skip if the event was already cancelled |
| 834 | if (event.cancelled) { |
| 835 | if (event.trace || DEBUG_TRACE_ALL) { |
| 836 | logWithPid("Event dispatch cancelled"); |
| 837 | } |
| 838 | return; |
| 839 | } |
| 840 | |
| 841 | try { |
| 842 | if (event.trace || DEBUG_TRACE_ALL) { |
| 843 | logWithPid(" -> " + eventHandler.toString()); |
| 844 | } |
| 845 | Object sub = eventHandler.subscriber.getReference(); |
| 846 | if (sub != null) { |
| 847 | long t1 = 0; |
| 848 | if (DEBUG_TRACE_ALL) { |
| 849 | t1 = SystemClock.currentTimeMicro(); |
| 850 | } |
| 851 | eventHandler.method.invoke(sub, event); |
| 852 | if (DEBUG_TRACE_ALL) { |
| 853 | long duration = (SystemClock.currentTimeMicro() - t1); |
| 854 | mCallDurationMicros += duration; |
| 855 | mCallCount++; |
| 856 | logWithPid(eventHandler.method.toString() + " duration: " + duration + |
| 857 | " microseconds, avg: " + (mCallDurationMicros / mCallCount)); |
| 858 | } |
| 859 | } else { |
| 860 | Log.e(TAG, "Failed to deliver event to null subscriber"); |
| 861 | } |
| 862 | } catch (IllegalAccessException e) { |
Winson | eca4ab6 | 2015-11-04 10:50:28 -0800 | [diff] [blame] | 863 | Log.e(TAG, "Failed to invoke method", e.getCause()); |
Winson | e6c9073 | 2015-09-24 16:06:29 -0700 | [diff] [blame] | 864 | } catch (InvocationTargetException e) { |
Winson | 5b7dd53 | 2015-12-01 16:02:12 -0800 | [diff] [blame] | 865 | throw new RuntimeException(e.getCause()); |
Winson | e6c9073 | 2015-09-24 16:06:29 -0700 | [diff] [blame] | 866 | } |
| 867 | } |
| 868 | |
| 869 | /** |
| 870 | * Re-registers the broadcast receiver for any new messages that we want to listen for. |
| 871 | */ |
| 872 | private void registerReceiverForInterprocessEvents(Context context) { |
| 873 | if (DEBUG_TRACE_ALL) { |
| 874 | logWithPid("registerReceiverForInterprocessEvents()"); |
| 875 | } |
| 876 | // Rebuild the receiver filter with the new interprocess events |
| 877 | IntentFilter filter = new IntentFilter(); |
| 878 | for (String eventName : mInterprocessEventNameMap.keySet()) { |
| 879 | filter.addAction(eventName); |
| 880 | if (DEBUG_TRACE_ALL) { |
| 881 | logWithPid(" filter: " + eventName); |
| 882 | } |
| 883 | } |
| 884 | // Re-register the receiver with the new filter |
| 885 | if (mHasRegisteredReceiver) { |
| 886 | context.unregisterReceiver(this); |
| 887 | } |
| 888 | context.registerReceiverAsUser(this, UserHandle.ALL, filter, PERMISSION_SELF, mHandler); |
| 889 | mHasRegisteredReceiver = true; |
| 890 | } |
| 891 | |
| 892 | /** |
| 893 | * Returns whether this subscriber is currently registered. If {@param removeFoundSubscriber} |
| 894 | * is true, then remove the subscriber before returning. |
| 895 | */ |
| 896 | private boolean findRegisteredSubscriber(Object subscriber, boolean removeFoundSubscriber) { |
| 897 | for (int i = mSubscribers.size() - 1; i >= 0; i--) { |
| 898 | Subscriber sub = mSubscribers.get(i); |
| 899 | if (sub.getReference() == subscriber) { |
| 900 | if (removeFoundSubscriber) { |
| 901 | mSubscribers.remove(i); |
| 902 | } |
| 903 | return true; |
| 904 | } |
| 905 | } |
| 906 | return false; |
| 907 | } |
| 908 | |
| 909 | /** |
| 910 | * @return whether {@param method} is a valid (normal or interprocess) event bus handler method |
| 911 | */ |
| 912 | private boolean isValidEventBusHandlerMethod(Method method, Class<?>[] parameterTypes, |
| 913 | MutableBoolean isInterprocessEventOut) { |
| 914 | int modifiers = method.getModifiers(); |
| 915 | if (Modifier.isPublic(modifiers) && |
| 916 | Modifier.isFinal(modifiers) && |
| 917 | method.getReturnType().equals(Void.TYPE) && |
| 918 | parameterTypes.length == 1) { |
| 919 | if (EventBus.InterprocessEvent.class.isAssignableFrom(parameterTypes[0]) && |
| 920 | method.getName().startsWith(INTERPROCESS_METHOD_PREFIX)) { |
| 921 | isInterprocessEventOut.value = true; |
| 922 | return true; |
| 923 | } else if (EventBus.Event.class.isAssignableFrom(parameterTypes[0]) && |
| 924 | method.getName().startsWith(METHOD_PREFIX)) { |
| 925 | isInterprocessEventOut.value = false; |
| 926 | return true; |
| 927 | } else { |
| 928 | if (DEBUG_TRACE_ALL) { |
| 929 | if (!EventBus.Event.class.isAssignableFrom(parameterTypes[0])) { |
| 930 | logWithPid(" Expected method take an Event-based parameter: " + method.getName()); |
| 931 | } else if (!method.getName().startsWith(INTERPROCESS_METHOD_PREFIX) && |
| 932 | !method.getName().startsWith(METHOD_PREFIX)) { |
| 933 | logWithPid(" Expected method start with method prefix: " + method.getName()); |
| 934 | } |
| 935 | } |
| 936 | } |
| 937 | } else { |
| 938 | if (DEBUG_TRACE_ALL) { |
| 939 | if (!Modifier.isPublic(modifiers)) { |
| 940 | logWithPid(" Expected method to be public: " + method.getName()); |
| 941 | } else if (!Modifier.isFinal(modifiers)) { |
| 942 | logWithPid(" Expected method to be final: " + method.getName()); |
| 943 | } else if (!method.getReturnType().equals(Void.TYPE)) { |
| 944 | logWithPid(" Expected method to return null: " + method.getName()); |
| 945 | } |
| 946 | } |
| 947 | } |
| 948 | return false; |
| 949 | } |
| 950 | |
| 951 | /** |
| 952 | * Sorts the event handlers by priority and registration time. |
| 953 | */ |
| 954 | private void sortEventHandlersByPriority(List<EventHandler> eventHandlers) { |
| 955 | Collections.sort(eventHandlers, EVENT_HANDLER_COMPARATOR); |
| 956 | } |
| 957 | |
| 958 | /** |
| 959 | * Helper method to log the given {@param text} with the current process and user id. |
| 960 | */ |
| 961 | private static void logWithPid(String text) { |
| 962 | Log.d(TAG, "[" + android.os.Process.myPid() + ", u" + UserHandle.myUserId() + "] " + text); |
| 963 | } |
| 964 | } |