/* * Copyright (C) 2014 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.systemui.recents.events; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.os.Bundle; import android.os.Handler; import android.os.Looper; import android.os.SystemClock; import android.os.UserHandle; import android.util.Log; import android.util.MutableBoolean; import com.android.systemui.recents.misc.ReferenceCountedTrigger; import java.io.PrintWriter; import java.lang.ref.WeakReference; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.List; /** * Represents a subscriber, which implements various event bus handler methods. */ class Subscriber { private WeakReference mSubscriber; long registrationTime; Subscriber(Object subscriber, long registrationTime) { mSubscriber = new WeakReference<>(subscriber); this.registrationTime = registrationTime; } public String toString(int priority) { Object sub = mSubscriber.get(); String id = Integer.toHexString(System.identityHashCode(sub)); return sub.getClass().getSimpleName() + " [0x" + id + ", P" + priority + "]"; } public Object getReference() { return mSubscriber.get(); } } /** * Represents an event handler with a priority. */ class EventHandler { int priority; Subscriber subscriber; EventHandlerMethod method; EventHandler(Subscriber subscriber, EventHandlerMethod method, int priority) { this.subscriber = subscriber; this.method = method; this.priority = priority; } @Override public String toString() { return subscriber.toString(priority) + " " + method.toString(); } } /** * Represents the low level method handling a particular event. */ class EventHandlerMethod { private Method mMethod; Class eventType; EventHandlerMethod(Method method, Class eventType) { mMethod = method; mMethod.setAccessible(true); this.eventType = eventType; } public void invoke(Object target, EventBus.Event event) throws InvocationTargetException, IllegalAccessException { mMethod.invoke(target, event); } @Override public String toString() { return mMethod.getName() + "(" + eventType.getSimpleName() + ")"; } } /** * A simple in-process event bus. It is simple because we can make assumptions about the state of * SystemUI and Recent's lifecycle. * *

* Currently, there is a single EventBus that handles {@link EventBus.Event}s for each subscriber * on the main application thread. Publishers can send() events to synchronously call subscribers * of that event, or post() events to be processed in the next run of the {@link Looper}. In * addition, the EventBus supports sending and handling {@link EventBus.InterprocessEvent}s * (within the same package) implemented using standard {@link BroadcastReceiver} mechanism. * Interprocess events must be posted using postInterprocess() to ensure that it is dispatched * correctly across processes. * *

* Subscribers must be registered with a particular EventBus before they will receive events, and * handler methods must match a specific signature. * *

* Event method signature:

* *

* Interprocess-Event method signature:

*

* *

* Each subscriber can be registered with a given priority (default 1), and events will be dispatch * in decreasing order of priority. For subscribers with the same priority, events will be * dispatched by latest registration time to earliest. * *

* Interprocess events must extend {@link EventBus.InterprocessEvent}, have a constructor which * takes a {@link Bundle} and implement toBundle(). This allows us to serialize events to be sent * across processes. * *

* Caveats:

* *

* Future optimizations: *

  • throw exception/log when a subscriber loses the reference *
  • trace cost per registration & invocation *
  • trace cross-process invocation *
  • register(subscriber, Class<?>...) -- pass in exact class types you want registered *
  • setSubscriberEventHandlerPriority(subscriber, Class, priority) *
  • allow subscribers to implement interface, ie. EventBus.Subscriber, which lets then test a * message before invocation (ie. check if task id == this task id) *
  • add postOnce() which automatically debounces *
  • add postDelayed() which delays / postDelayedOnce() which delays and bounces *
  • consolidate register() and registerInterprocess() *
  • sendForResult<ReturnType>(Event) to send and get a result, but who will send the * result? *

    */ public class EventBus extends BroadcastReceiver { private static final String TAG = "EventBus"; private static final boolean DEBUG_TRACE_ALL = false; /** * An event super class that allows us to track internal event state across subscriber * invocations. * * Events should not be edited by subscribers. */ public static class Event implements Cloneable { // Indicates that this event's dispatch should be traced and logged to logcat boolean trace; // Indicates that this event must be posted on the EventBus's looper thread before invocation boolean requiresPost; // Not currently exposed, allows a subscriber to cancel further dispatch of this event boolean cancelled; // Only accessible from derived events protected Event() {} /** * Called by the EventBus prior to dispatching this event to any subscriber of this event. */ void onPreDispatch() { // Do nothing } /** * Called by the EventBus after dispatching this event to every subscriber of this event. */ void onPostDispatch() { // Do nothing } @Override protected Object clone() throws CloneNotSupportedException { Event evt = (Event) super.clone(); // When cloning an event, reset the cancelled-dispatch state evt.cancelled = false; return evt; } } /** * An event that represents an animated state change, which allows subscribers to coordinate * callbacks which happen after the animation has taken place. * * Internally, it is guaranteed that increment() and decrement() will be called before and the * after the event is dispatched. */ public static class AnimatedEvent extends Event { private final ReferenceCountedTrigger mTrigger = new ReferenceCountedTrigger(); // Only accessible from derived events protected AnimatedEvent() {} /** * Returns the reference counted trigger that coordinates the animations for this event. */ public ReferenceCountedTrigger getAnimationTrigger() { return mTrigger; } /** * Adds a callback that is guaranteed to be called after the state has changed regardless of * whether an actual animation took place. */ public void addPostAnimationCallback(Runnable r) { mTrigger.addLastDecrementRunnable(r); } @Override void onPreDispatch() { mTrigger.increment(); } @Override void onPostDispatch() { mTrigger.decrement(); } @Override protected Object clone() throws CloneNotSupportedException { throw new CloneNotSupportedException(); } } /** * An event that can be reusable, only used for situations where we want to reduce memory * allocations when events are sent frequently (ie. on scroll). */ public static class ReusableEvent extends Event { private int mDispatchCount; protected ReusableEvent() {} @Override void onPostDispatch() { super.onPostDispatch(); mDispatchCount++; } @Override protected Object clone() throws CloneNotSupportedException { throw new CloneNotSupportedException(); } } /** * An inter-process event super class that allows us to track user state across subscriber * invocations. */ public static class InterprocessEvent extends Event { private static final String EXTRA_USER = "_user"; // The user which this event originated from public final int user; // Only accessible from derived events protected InterprocessEvent(int user) { this.user = user; } /** * Called from the event bus */ protected InterprocessEvent(Bundle b) { user = b.getInt(EXTRA_USER); } protected Bundle toBundle() { Bundle b = new Bundle(); b.putInt(EXTRA_USER, user); return b; } } /** * Proguard must also know, and keep, all methods matching this signature. * * -keepclassmembers class ** { * public void onBusEvent(**); * public void onInterprocessBusEvent(**); * } */ private static final String METHOD_PREFIX = "onBusEvent"; private static final String INTERPROCESS_METHOD_PREFIX = "onInterprocessBusEvent"; // Ensures that interprocess events can only be sent from a process holding this permission. */ private static final String PERMISSION_SELF = "com.android.systemui.permission.SELF"; // Used for passing event data across process boundaries private static final String EXTRA_INTERPROCESS_EVENT_BUNDLE = "interprocess_event_bundle"; // The default priority of all subscribers private static final int DEFAULT_SUBSCRIBER_PRIORITY = 1; // Orders the handlers by priority and registration time private static final Comparator EVENT_HANDLER_COMPARATOR = new Comparator() { @Override public int compare(EventHandler h1, EventHandler h2) { // Rank the handlers by priority descending, followed by registration time descending. // aka. the later registered if (h1.priority != h2.priority) { return h2.priority - h1.priority; } else { return Long.compare(h2.subscriber.registrationTime, h1.subscriber.registrationTime); } } }; // Used for initializing the default bus private static final Object sLock = new Object(); private static volatile EventBus sDefaultBus; // The handler to post all events private Handler mHandler; // Keep track of whether we have registered a broadcast receiver already, so that we can // unregister ourselves before re-registering again with a new IntentFilter. private boolean mHasRegisteredReceiver; /** * Map from event class -> event handler list. Keeps track of the actual mapping from event * to subscriber method. */ private HashMap, ArrayList> mEventTypeMap = new HashMap<>(); /** * Map from subscriber class -> event handler method lists. Used to determine upon registration * of a new subscriber whether we need to read all the subscriber's methods again using * reflection or whether we can just add the subscriber to the event type map. */ private HashMap, ArrayList> mSubscriberTypeMap = new HashMap<>(); /** * Map from interprocess event name -> interprocess event class. Used for mapping the event * name after receiving the broadcast, to the event type. After which a new instance is created * and posted in the local process. */ private HashMap> mInterprocessEventNameMap = new HashMap<>(); /** * Set of all currently registered subscribers */ private ArrayList mSubscribers = new ArrayList<>(); // For tracing private int mCallCount; private long mCallDurationMicros; /** * Private constructor to create an event bus for a given looper. */ private EventBus(Looper looper) { mHandler = new Handler(looper); } /** * @return the default event bus for the application's main thread. */ public static EventBus getDefault() { if (sDefaultBus == null) synchronized (sLock) { if (sDefaultBus == null) { if (DEBUG_TRACE_ALL) { logWithPid("New EventBus"); } sDefaultBus = new EventBus(Looper.getMainLooper()); } } return sDefaultBus; } /** * Registers a subscriber to receive events with the default priority. * * @param subscriber the subscriber to handle events. If this is the first instance of the * subscriber's class type that has been registered, the class's methods will * be scanned for appropriate event handler methods. */ public void register(Object subscriber) { registerSubscriber(subscriber, DEFAULT_SUBSCRIBER_PRIORITY, null); } /** * Registers a subscriber to receive events with the given priority. * * @param subscriber the subscriber to handle events. If this is the first instance of the * subscriber's class type that has been registered, the class's methods will * be scanned for appropriate event handler methods. * @param priority the priority that this subscriber will receive events relative to other * subscribers */ public void register(Object subscriber, int priority) { registerSubscriber(subscriber, priority, null); } /** * Explicitly registers a subscriber to receive interprocess events with the default priority. * * @param subscriber the subscriber to handle events. If this is the first instance of the * subscriber's class type that has been registered, the class's methods will * be scanned for appropriate event handler methods. */ public void registerInterprocessAsCurrentUser(Context context, Object subscriber) { registerInterprocessAsCurrentUser(context, subscriber, DEFAULT_SUBSCRIBER_PRIORITY); } /** * Registers a subscriber to receive interprocess events with the given priority. * * @param subscriber the subscriber to handle events. If this is the first instance of the * subscriber's class type that has been registered, the class's methods will * be scanned for appropriate event handler methods. * @param priority the priority that this subscriber will receive events relative to other * subscribers */ public void registerInterprocessAsCurrentUser(Context context, Object subscriber, int priority) { if (DEBUG_TRACE_ALL) { logWithPid("registerInterprocessAsCurrentUser(" + subscriber.getClass().getSimpleName() + ")"); } // Register the subscriber normally, and update the broadcast receiver filter if this is // a new subscriber type with interprocess events MutableBoolean hasInterprocessEventsChanged = new MutableBoolean(false); registerSubscriber(subscriber, priority, hasInterprocessEventsChanged); if (DEBUG_TRACE_ALL) { logWithPid("hasInterprocessEventsChanged: " + hasInterprocessEventsChanged.value); } if (hasInterprocessEventsChanged.value) { registerReceiverForInterprocessEvents(context); } } /** * Remove all EventHandlers pointing to the specified subscriber. This does not remove the * mapping of subscriber type to event handler method, in case new instances of this subscriber * are registered. */ public void unregister(Object subscriber) { if (DEBUG_TRACE_ALL) { logWithPid("unregister()"); } // Fail immediately if we are being called from the non-main thread long callingThreadId = Thread.currentThread().getId(); if (callingThreadId != mHandler.getLooper().getThread().getId()) { throw new RuntimeException("Can not unregister() a subscriber from a non-main thread."); } // Return early if this is not a registered subscriber if (!findRegisteredSubscriber(subscriber, true /* removeFoundSubscriber */)) { return; } Class subscriberType = subscriber.getClass(); ArrayList subscriberMethods = mSubscriberTypeMap.get(subscriberType); if (subscriberMethods != null) { // For each of the event handlers the subscriber handles, remove all references of that // handler for (EventHandlerMethod method : subscriberMethods) { ArrayList eventHandlers = mEventTypeMap.get(method.eventType); for (int i = eventHandlers.size() - 1; i >= 0; i--) { if (eventHandlers.get(i).subscriber.getReference() == subscriber) { eventHandlers.remove(i); } } } } } /** * Explicit unregistration for interprocess event subscribers. This actually behaves exactly * the same as unregister() since we also do not want to stop listening for specific * inter-process messages in case new instances of that subscriber is registered. */ public void unregisterInterprocess(Context context, Object subscriber) { if (DEBUG_TRACE_ALL) { logWithPid("unregisterInterprocess()"); } unregister(subscriber); } /** * Sends an event to the subscribers of the given event type immediately. This can only be * called from the same thread as the EventBus's looper thread (for the default EventBus, this * is the main application thread). */ public void send(Event event) { // Fail immediately if we are being called from the non-main thread long callingThreadId = Thread.currentThread().getId(); if (callingThreadId != mHandler.getLooper().getThread().getId()) { throw new RuntimeException("Can not send() a message from a non-main thread."); } if (DEBUG_TRACE_ALL) { logWithPid("send(" + event.getClass().getSimpleName() + ")"); } // Reset the event's cancelled state event.requiresPost = false; event.cancelled = false; queueEvent(event); } /** * Post a message to the subscribers of the given event type. The messages will be posted on * the EventBus's looper thread (for the default EventBus, this is the main application thread). */ public void post(Event event) { if (DEBUG_TRACE_ALL) { logWithPid("post(" + event.getClass().getSimpleName() + ")"); } // Reset the event's cancelled state event.requiresPost = true; event.cancelled = false; queueEvent(event); } /** * If this method is called from the main thread, it will be handled directly. If this method * is not called from the main thread, it will be posted onto the main thread. */ public void sendOntoMainThread(Event event) { long callingThreadId = Thread.currentThread().getId(); if (callingThreadId != mHandler.getLooper().getThread().getId()) { post(event); } else { send(event); } } /** Prevent post()ing an InterprocessEvent */ @Deprecated public void post(InterprocessEvent event) { throw new RuntimeException("Not supported, use postInterprocess"); } /** Prevent send()ing an InterprocessEvent */ @Deprecated public void send(InterprocessEvent event) { throw new RuntimeException("Not supported, use postInterprocess"); } /** * Posts an interprocess event. */ public void postInterprocess(Context context, final InterprocessEvent event) { if (DEBUG_TRACE_ALL) { logWithPid("postInterprocess(" + event.getClass().getSimpleName() + ")"); } String eventType = event.getClass().getName(); Bundle eventBundle = event.toBundle(); Intent intent = new Intent(eventType); intent.setPackage(context.getPackageName()); intent.putExtra(EXTRA_INTERPROCESS_EVENT_BUNDLE, eventBundle); intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT | Intent.FLAG_RECEIVER_FOREGROUND); context.sendBroadcastAsUser(intent, UserHandle.ALL); } /** * Receiver for interprocess events. */ @Override public void onReceive(Context context, Intent intent) { if (DEBUG_TRACE_ALL) { logWithPid("onReceive(" + intent.getAction() + ", user " + UserHandle.myUserId() + ")"); } Bundle eventBundle = intent.getBundleExtra(EXTRA_INTERPROCESS_EVENT_BUNDLE); Class eventType = mInterprocessEventNameMap.get(intent.getAction()); try { Constructor ctor = eventType.getConstructor(Bundle.class); send((Event) ctor.newInstance(eventBundle)); } catch (NoSuchMethodException| InvocationTargetException| InstantiationException| IllegalAccessException e) { Log.e(TAG, "Failed to create InterprocessEvent", e.getCause()); } } /** * @return a dump of the current state of the EventBus */ public void dump(String prefix, PrintWriter writer) { writer.println(dumpInternal(prefix)); } public String dumpInternal(String prefix) { String innerPrefix = prefix + " "; String innerInnerPrefix = innerPrefix + " "; StringBuilder output = new StringBuilder(); output.append(prefix); output.append("Registered class types:"); output.append("\n"); ArrayList> subsciberTypes = new ArrayList<>(mSubscriberTypeMap.keySet()); Collections.sort(subsciberTypes, new Comparator>() { @Override public int compare(Class o1, Class o2) { return o1.getSimpleName().compareTo(o2.getSimpleName()); } }); for (int i = 0; i < subsciberTypes.size(); i++) { Class clz = subsciberTypes.get(i); output.append(innerPrefix); output.append(clz.getSimpleName()); output.append("\n"); } output.append(prefix); output.append("Event map:"); output.append("\n"); ArrayList> classes = new ArrayList<>(mEventTypeMap.keySet()); Collections.sort(classes, new Comparator>() { @Override public int compare(Class o1, Class o2) { return o1.getSimpleName().compareTo(o2.getSimpleName()); } }); for (int i = 0; i < classes.size(); i++) { Class clz = classes.get(i); output.append(innerPrefix); output.append(clz.getSimpleName()); output.append(" -> "); output.append("\n"); ArrayList handlers = mEventTypeMap.get(clz); for (EventHandler handler : handlers) { Object subscriber = handler.subscriber.getReference(); if (subscriber != null) { String id = Integer.toHexString(System.identityHashCode(subscriber)); output.append(innerInnerPrefix); output.append(subscriber.getClass().getSimpleName()); output.append(" [0x" + id + ", #" + handler.priority + "]"); output.append("\n"); } } } return output.toString(); } /** * Registers a new subscriber. */ private void registerSubscriber(Object subscriber, int priority, MutableBoolean hasInterprocessEventsChangedOut) { // Fail immediately if we are being called from the non-main thread long callingThreadId = Thread.currentThread().getId(); if (callingThreadId != mHandler.getLooper().getThread().getId()) { throw new RuntimeException("Can not register() a subscriber from a non-main thread."); } // Return immediately if this exact subscriber is already registered if (findRegisteredSubscriber(subscriber, false /* removeFoundSubscriber */)) { return; } long t1 = 0; if (DEBUG_TRACE_ALL) { t1 = SystemClock.currentTimeMicro(); logWithPid("registerSubscriber(" + subscriber.getClass().getSimpleName() + ")"); } Subscriber sub = new Subscriber(subscriber, SystemClock.uptimeMillis()); Class subscriberType = subscriber.getClass(); ArrayList subscriberMethods = mSubscriberTypeMap.get(subscriberType); if (subscriberMethods != null) { if (DEBUG_TRACE_ALL) { logWithPid("Subscriber class type already registered"); } // If we've parsed this subscriber type before, just add to the set for all the known // events for (EventHandlerMethod method : subscriberMethods) { ArrayList eventTypeHandlers = mEventTypeMap.get(method.eventType); eventTypeHandlers.add(new EventHandler(sub, method, priority)); sortEventHandlersByPriority(eventTypeHandlers); } mSubscribers.add(sub); return; } else { if (DEBUG_TRACE_ALL) { logWithPid("Subscriber class type requires registration"); } // If we are parsing this type from scratch, ensure we add it to the subscriber type // map, and pull out he handler methods below subscriberMethods = new ArrayList<>(); mSubscriberTypeMap.put(subscriberType, subscriberMethods); mSubscribers.add(sub); } // Find all the valid event bus handler methods of the subscriber MutableBoolean isInterprocessEvent = new MutableBoolean(false); Method[] methods = subscriberType.getDeclaredMethods(); for (Method m : methods) { Class[] parameterTypes = m.getParameterTypes(); isInterprocessEvent.value = false; if (isValidEventBusHandlerMethod(m, parameterTypes, isInterprocessEvent)) { Class eventType = (Class) parameterTypes[0]; ArrayList eventTypeHandlers = mEventTypeMap.get(eventType); if (eventTypeHandlers == null) { eventTypeHandlers = new ArrayList<>(); mEventTypeMap.put(eventType, eventTypeHandlers); } if (isInterprocessEvent.value) { try { // Enforce that the event must have a Bundle constructor eventType.getConstructor(Bundle.class); mInterprocessEventNameMap.put(eventType.getName(), (Class) eventType); if (hasInterprocessEventsChangedOut != null) { hasInterprocessEventsChangedOut.value = true; } } catch (NoSuchMethodException e) { throw new RuntimeException("Expected InterprocessEvent to have a Bundle constructor"); } } EventHandlerMethod method = new EventHandlerMethod(m, eventType); EventHandler handler = new EventHandler(sub, method, priority); eventTypeHandlers.add(handler); subscriberMethods.add(method); sortEventHandlersByPriority(eventTypeHandlers); if (DEBUG_TRACE_ALL) { logWithPid(" * Method: " + m.getName() + " event: " + parameterTypes[0].getSimpleName() + " interprocess? " + isInterprocessEvent.value); } } } if (DEBUG_TRACE_ALL) { logWithPid("Registered " + subscriber.getClass().getSimpleName() + " in " + (SystemClock.currentTimeMicro() - t1) + " microseconds"); } } /** * Adds a new message. */ private void queueEvent(final Event event) { ArrayList eventHandlers = mEventTypeMap.get(event.getClass()); if (eventHandlers == null) { // This is just an optimization to return early if there are no handlers. However, we // should still ensure that we call pre/post dispatch callbacks so that AnimatedEvents // are still cleaned up correctly if a listener has not been registered to handle them event.onPreDispatch(); event.onPostDispatch(); return; } // Prepare this event boolean hasPostedEvent = false; event.onPreDispatch(); // We need to clone the list in case a subscriber unregisters itself during traversal // TODO: Investigate whether we can skip the object creation here eventHandlers = (ArrayList) eventHandlers.clone(); int eventHandlerCount = eventHandlers.size(); for (int i = 0; i < eventHandlerCount; i++) { final EventHandler eventHandler = eventHandlers.get(i); if (eventHandler.subscriber.getReference() != null) { if (event.requiresPost) { mHandler.post(new Runnable() { @Override public void run() { processEvent(eventHandler, event); } }); hasPostedEvent = true; } else { processEvent(eventHandler, event); } } } // Clean up after this event, deferring until all subscribers have been called if (hasPostedEvent) { mHandler.post(new Runnable() { @Override public void run() { event.onPostDispatch(); } }); } else { event.onPostDispatch(); } } /** * Processes and dispatches the given event to the given event handler, on the thread of whoever * calls this method. */ private void processEvent(final EventHandler eventHandler, final Event event) { // Skip if the event was already cancelled if (event.cancelled) { if (event.trace || DEBUG_TRACE_ALL) { logWithPid("Event dispatch cancelled"); } return; } try { if (event.trace || DEBUG_TRACE_ALL) { logWithPid(" -> " + eventHandler.toString()); } Object sub = eventHandler.subscriber.getReference(); if (sub != null) { long t1 = 0; if (DEBUG_TRACE_ALL) { t1 = SystemClock.currentTimeMicro(); } eventHandler.method.invoke(sub, event); if (DEBUG_TRACE_ALL) { long duration = (SystemClock.currentTimeMicro() - t1); mCallDurationMicros += duration; mCallCount++; logWithPid(eventHandler.method.toString() + " duration: " + duration + " microseconds, avg: " + (mCallDurationMicros / mCallCount)); } } else { Log.e(TAG, "Failed to deliver event to null subscriber"); } } catch (IllegalAccessException e) { Log.e(TAG, "Failed to invoke method", e.getCause()); } catch (InvocationTargetException e) { throw new RuntimeException(e.getCause()); } } /** * Re-registers the broadcast receiver for any new messages that we want to listen for. */ private void registerReceiverForInterprocessEvents(Context context) { if (DEBUG_TRACE_ALL) { logWithPid("registerReceiverForInterprocessEvents()"); } // Rebuild the receiver filter with the new interprocess events IntentFilter filter = new IntentFilter(); for (String eventName : mInterprocessEventNameMap.keySet()) { filter.addAction(eventName); if (DEBUG_TRACE_ALL) { logWithPid(" filter: " + eventName); } } // Re-register the receiver with the new filter if (mHasRegisteredReceiver) { context.unregisterReceiver(this); } context.registerReceiverAsUser(this, UserHandle.ALL, filter, PERMISSION_SELF, mHandler); mHasRegisteredReceiver = true; } /** * Returns whether this subscriber is currently registered. If {@param removeFoundSubscriber} * is true, then remove the subscriber before returning. */ private boolean findRegisteredSubscriber(Object subscriber, boolean removeFoundSubscriber) { for (int i = mSubscribers.size() - 1; i >= 0; i--) { Subscriber sub = mSubscribers.get(i); if (sub.getReference() == subscriber) { if (removeFoundSubscriber) { mSubscribers.remove(i); } return true; } } return false; } /** * @return whether {@param method} is a valid (normal or interprocess) event bus handler method */ private boolean isValidEventBusHandlerMethod(Method method, Class[] parameterTypes, MutableBoolean isInterprocessEventOut) { int modifiers = method.getModifiers(); if (Modifier.isPublic(modifiers) && Modifier.isFinal(modifiers) && method.getReturnType().equals(Void.TYPE) && parameterTypes.length == 1) { if (EventBus.InterprocessEvent.class.isAssignableFrom(parameterTypes[0]) && method.getName().startsWith(INTERPROCESS_METHOD_PREFIX)) { isInterprocessEventOut.value = true; return true; } else if (EventBus.Event.class.isAssignableFrom(parameterTypes[0]) && method.getName().startsWith(METHOD_PREFIX)) { isInterprocessEventOut.value = false; return true; } else { if (DEBUG_TRACE_ALL) { if (!EventBus.Event.class.isAssignableFrom(parameterTypes[0])) { logWithPid(" Expected method take an Event-based parameter: " + method.getName()); } else if (!method.getName().startsWith(INTERPROCESS_METHOD_PREFIX) && !method.getName().startsWith(METHOD_PREFIX)) { logWithPid(" Expected method start with method prefix: " + method.getName()); } } } } else { if (DEBUG_TRACE_ALL) { if (!Modifier.isPublic(modifiers)) { logWithPid(" Expected method to be public: " + method.getName()); } else if (!Modifier.isFinal(modifiers)) { logWithPid(" Expected method to be final: " + method.getName()); } else if (!method.getReturnType().equals(Void.TYPE)) { logWithPid(" Expected method to return null: " + method.getName()); } } } return false; } /** * Sorts the event handlers by priority and registration time. */ private void sortEventHandlersByPriority(List eventHandlers) { Collections.sort(eventHandlers, EVENT_HANDLER_COMPARATOR); } /** * Helper method to log the given {@param text} with the current process and user id. */ private static void logWithPid(String text) { Log.d(TAG, "[" + android.os.Process.myPid() + ", u" + UserHandle.myUserId() + "] " + text); } }