diff options
| author | 2015-10-01 21:00:26 +0000 | |
|---|---|---|
| committer | 2015-10-01 21:00:26 +0000 | |
| commit | 9cdf4565ddb3dbeaf1cdee6285fdea8a13de8e81 (patch) | |
| tree | ad84957684161447e568cbf17b3600e5c7144ae4 | |
| parent | 3089211ce6c279f92cb3ff8ce4137d322ce1ec97 (diff) | |
| parent | e6c9073d81822496c038e70361e25b3f1287c2bc (diff) | |
Merge "Incorporating event bus to proxy recents events."
9 files changed, 932 insertions, 58 deletions
diff --git a/packages/SystemUI/proguard.flags b/packages/SystemUI/proguard.flags index 47e24e8e82f3..368f9f79a20b 100644 --- a/packages/SystemUI/proguard.flags +++ b/packages/SystemUI/proguard.flags @@ -13,3 +13,11 @@ -keep class com.android.systemui.statusbar.phone.PhoneStatusBar -keep class com.android.systemui.statusbar.tv.TvStatusBar -keep class com.android.systemui.recents.* + +-keepclassmembers class ** { + public void onBusEvent(**); + public void onInterprocessBusEvent(**); +} +-keepclassmembers class ** extends **.EventBus$InterprocessEvent { + public <init>(android.os.Bundle); +}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/recents/Recents.java b/packages/SystemUI/src/com/android/systemui/recents/Recents.java index 23cc8f067d83..a78351a1ffbc 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/Recents.java +++ b/packages/SystemUI/src/com/android/systemui/recents/Recents.java @@ -74,6 +74,8 @@ import java.util.ArrayList; public class Recents extends SystemUI implements ActivityOptions.OnAnimationStartedListener, RecentsComponent { + public final static int EVENT_BUS_PRIORITY = 1; + final public static String EXTRA_TRIGGERED_FROM_ALT_TAB = "triggeredFromAltTab"; final public static String EXTRA_TRIGGERED_FROM_HOME_KEY = "triggeredFromHomeKey"; final public static String EXTRA_RECENTS_VISIBILITY = "recentsVisibility"; diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java index 9ce6b2c764b4..be99641d38b9 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java +++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java @@ -36,6 +36,7 @@ import com.android.systemui.R; import com.android.systemui.recents.misc.Console; import com.android.systemui.recents.misc.ReferenceCountedTrigger; import com.android.systemui.recents.misc.SystemServicesProxy; +import com.android.systemui.recents.model.RecentsPackageMonitor; import com.android.systemui.recents.model.RecentsTaskLoadPlan; import com.android.systemui.recents.model.RecentsTaskLoader; import com.android.systemui.recents.model.Task; @@ -53,7 +54,10 @@ import java.util.ArrayList; public class RecentsActivity extends Activity implements RecentsView.RecentsViewCallbacks, RecentsAppWidgetHost.RecentsAppWidgetHostCallbacks { + public final static int EVENT_BUS_PRIORITY = Recents.EVENT_BUS_PRIORITY + 1; + RecentsConfiguration mConfig; + RecentsPackageMonitor mPackageMonitor; long mLastTabKeyEventTime; // Top level views @@ -324,6 +328,7 @@ public class RecentsActivity extends Activity implements RecentsView.RecentsView SystemServicesProxy ssp = RecentsTaskLoader.getInstance().getSystemServicesProxy(); mConfig = RecentsConfiguration.initialize(this, ssp); mConfig.update(this, ssp, ssp.getWindowRect()); + mPackageMonitor = new RecentsPackageMonitor(); // Initialize the widget host (the host id is static and does not change) mAppWidgetHost = new RecentsAppWidgetHost(this, Constants.Values.App.AppWidgetHostId); @@ -371,7 +376,7 @@ public class RecentsActivity extends Activity implements RecentsView.RecentsView registerReceiver(mServiceBroadcastReceiver, filter); // Register any broadcast receivers for the task loader - loader.registerReceivers(this, mRecentsView); + mPackageMonitor.register(this); // Update the recent tasks updateRecentsTasks(); @@ -415,7 +420,7 @@ public class RecentsActivity extends Activity implements RecentsView.RecentsView unregisterReceiver(mServiceBroadcastReceiver); // Unregister any broadcast receivers for the task loader - loader.unregisterReceivers(); + mPackageMonitor.unregister(); // Workaround for b/22542869, if the RecentsActivity is started again, but without going // through SystemUI, we need to reset the config launch flags to ensure that we do not diff --git a/packages/SystemUI/src/com/android/systemui/recents/events/EventBus.java b/packages/SystemUI/src/com/android/systemui/recents/events/EventBus.java new file mode 100644 index 000000000000..4addfa57e900 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/recents/events/EventBus.java @@ -0,0 +1,844 @@ +/* + * 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 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<Object> 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<? extends EventBus.Event> eventType; + + EventHandlerMethod(Method method, Class<? extends EventBus.Event> 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. + * + * <p> + * 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. + * + * <p> + * Subscribers must be registered with a particular EventBus before they will receive events, and + * handler methods must match a specific signature. + * + * <p> + * Event method signature:<ul> + * <li>Methods must be public final + * <li>Methods must return void + * <li>Methods must be called "onBusEvent" + * <li>Methods must take one parameter, of class type deriving from {@link EventBus.Event} + * </ul> + * + * <p> + * Interprocess-Event method signature:<ul> + * <li>Methods must be public final + * <li>Methods must return void + * <li>Methods must be called "onInterprocessBusEvent" + * <li>Methods must take one parameter, of class type deriving from {@link EventBus.InterprocessEvent} + * </ul> + * </p> + * + * </p> + * 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. + * + * <p> + * 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. + * + * <p> + * Caveats:<ul> + * <li>The EventBus keeps a {@link WeakReference} to the publisher to prevent memory leaks, so + * there must be another strong reference to the publisher for it to not get garbage-collected and + * continue receiving events. + * <li>Because the event handlers are called back using reflection, the EventBus is not intended + * for use in tight, performance criticial loops. For most user input/system callback events, this + * is generally of low enough frequency to use the EventBus. + * <li>Because the event handlers are called back using reflection, there will often be no + * references to them from actual code. The proguard configuration will be need to be updated to + * keep these extra methods: + * + * -keepclassmembers class ** { + * public void onBusEvent(**); + * public void onInterprocessBusEvent(**); + * } + * -keepclassmembers class ** extends **.EventBus$InterprocessEvent { + * public <init>(android.os.Bundle); + * } + * + * <li>Subscriber registration can be expensive depending on the subscriber's {@link Class}. This + * is only done once per class type, but if possible, it is best to pre-register an instance of + * that class beforehand or when idle. + * <li>Each event should be sent once. Events may hold internal information about the current + * dispatch, or may be queued to be dispatched on another thread (if posted from a non-main thread), + * so it may be unsafe to edit, change, or re-send the event again. + * <li>Events should follow a pattern of public-final POD (plain old data) objects, where they are + * initialized by the constructor and read by each subscriber of that event. Subscribers should + * never alter events as they are processed, and this enforces that pattern. + * </ul> + * + * <p> + * Future optimizations: + * <li>throw exception/log when a subscriber loses the reference + * <li>trace cost per registration & invocation + * <li>trace cross-process invocation + * <li>register(subscriber, Class<?>...) -- pass in exact class types you want registered + * <li>setSubscriberEventHandlerPriority(subscriber, Class<Event>, priority) + * <li>allow subscribers to implement interface, ie. EventBus.Subscriber, which lets then test a + * message before invocation (ie. check if task id == this task id) + * <li>add postOnce() which automatically debounces + * <li>add postDelayed() which delays / postDelayedOnce() which delays and bounces + * <li>consolidate register() and registerInterprocess() + * <li>sendForResult<ReturnType>(Event) to send and get a result, but who will send the + * result? + * </p> + */ +public class EventBus extends BroadcastReceiver { + + public static final String TAG = "EventBus"; + + /** + * 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 { + // 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() {} + } + + /** + * 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; + + // Used for debugging everything + private static final boolean DEBUG_TRACE_ALL = false; + + // Orders the handlers by priority and registration time + private static final Comparator<EventHandler> EVENT_HANDLER_COMPARATOR = new Comparator<EventHandler>() { + @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 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<Class<? extends Event>, ArrayList<EventHandler>> 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<Class<? extends Object>, ArrayList<EventHandlerMethod>> 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<String, Class<? extends InterprocessEvent>> mInterprocessEventNameMap = new HashMap<>(); + + /** + * Set of all currently registered subscribers + */ + private ArrayList<Subscriber> 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<EventHandlerMethod> 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<EventHandler> 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); + } + + /** 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<? extends InterprocessEvent> eventType = mInterprocessEventNameMap.get(intent.getAction()); + try { + Constructor<? extends InterprocessEvent> ctor = eventType.getConstructor(Bundle.class); + send((Event) ctor.newInstance(eventBundle)); + } catch (NoSuchMethodException| + InvocationTargetException| + InstantiationException| + IllegalAccessException e) { + Log.e(TAG, "Failed to create InterprocessEvent", e); + } + } + + /** + * @return a dump of the current state of the EventBus + */ + public String dump() { + StringBuilder output = new StringBuilder(); + output.append("Registered class types:"); + output.append("\n"); + for (Class<?> clz : mSubscriberTypeMap.keySet()) { + output.append("\t"); + output.append(clz.getSimpleName()); + output.append("\n"); + } + output.append("Event map:"); + output.append("\n"); + for (Class<?> clz : mEventTypeMap.keySet()) { + output.append("\t"); + output.append(clz.getSimpleName()); + output.append(" -> "); + output.append("\n"); + ArrayList<EventHandler> 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("\t\t"); + output.append(subscriber.getClass().getSimpleName()); + output.append(" [0x" + id + ", #" + handler.priority + "]"); + output.append("\n"); + } + } + } + return output.toString(); + } + + /** + * Registers a new subscriber. + * + * @return return whether or not this + */ + 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<EventHandlerMethod> 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<EventHandler> 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.getMethods(); + for (Method m : methods) { + Class<?>[] parameterTypes = m.getParameterTypes(); + isInterprocessEvent.value = false; + if (isValidEventBusHandlerMethod(m, parameterTypes, isInterprocessEvent)) { + Class<? extends Event> eventType = (Class<? extends Event>) parameterTypes[0]; + ArrayList<EventHandler> 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<? extends InterprocessEvent>) 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<EventHandler> eventHandlers = mEventTypeMap.get(event.getClass()); + if (eventHandlers == null) { + return; + } + // We need to clone the list in case a subscriber unregisters itself during traversal + eventHandlers = (ArrayList<EventHandler>) eventHandlers.clone(); + for (final EventHandler eventHandler : eventHandlers) { + if (eventHandler.subscriber.getReference() != null) { + if (event.requiresPost) { + mHandler.post(new Runnable() { + @Override + public void run() { + processEvent(eventHandler, event); + } + }); + } else { + processEvent(eventHandler, event); + } + } + } + } + + /** + * 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); + } 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<EventHandler> 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); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/recents/events/activity/PackagesChangedEvent.java b/packages/SystemUI/src/com/android/systemui/recents/events/activity/PackagesChangedEvent.java new file mode 100644 index 000000000000..3b68574c2830 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/recents/events/activity/PackagesChangedEvent.java @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2015 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.activity; + +import com.android.systemui.recents.events.EventBus; +import com.android.systemui.recents.model.RecentsPackageMonitor; +import com.android.systemui.recents.views.TaskStackView; + +/** + * This event is sent by {@link RecentsPackageMonitor} when a package on the the system changes. + * {@link TaskStackView}s listen for this event, and remove the tasks associated with the removed + * packages. + */ +public class PackagesChangedEvent extends EventBus.Event { + + public final RecentsPackageMonitor monitor; + public final String packageName; + public final int userId; + + public PackagesChangedEvent(RecentsPackageMonitor monitor, String packageName, int userId) { + this.monitor = monitor; + this.packageName = packageName; + this.userId = userId; + } +} diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsPackageMonitor.java b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsPackageMonitor.java index e48e5f053f5f..8f9a2935c3a6 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsPackageMonitor.java +++ b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsPackageMonitor.java @@ -21,6 +21,8 @@ import android.content.Context; import android.os.Looper; import android.os.UserHandle; import com.android.internal.content.PackageMonitor; +import com.android.systemui.recents.events.EventBus; +import com.android.systemui.recents.events.activity.PackagesChangedEvent; import com.android.systemui.recents.misc.SystemServicesProxy; import java.util.HashSet; @@ -31,18 +33,9 @@ import java.util.List; * Recents list. */ public class RecentsPackageMonitor extends PackageMonitor { - public interface PackageCallbacks { - public void onPackagesChanged(RecentsPackageMonitor monitor, String packageName, - int userId); - } - - PackageCallbacks mCb; - SystemServicesProxy mSystemServicesProxy; /** Registers the broadcast receivers with the specified callbacks. */ - public void register(Context context, PackageCallbacks cb) { - mSystemServicesProxy = new SystemServicesProxy(context); - mCb = cb; + public void register(Context context) { try { // We register for events from all users, but will cross-reference them with // packages for the current user and any profiles they have @@ -60,17 +53,13 @@ public class RecentsPackageMonitor extends PackageMonitor { } catch (IllegalStateException e) { e.printStackTrace(); } - mSystemServicesProxy = null; - mCb = null; } @Override public void onPackageRemoved(String packageName, int uid) { - if (mCb == null) return; - // Notify callbacks that a package has changed final int eventUserId = getChangingUserId(); - mCb.onPackagesChanged(this, packageName, eventUserId); + EventBus.getDefault().send(new PackagesChangedEvent(this, packageName, eventUserId)); } @Override @@ -81,11 +70,9 @@ public class RecentsPackageMonitor extends PackageMonitor { @Override public void onPackageModified(String packageName) { - if (mCb == null) return; - // Notify callbacks that a package has changed final int eventUserId = getChangingUserId(); - mCb.onPackagesChanged(this, packageName, eventUserId); + EventBus.getDefault().send(new PackagesChangedEvent(this, packageName, eventUserId)); } /** @@ -108,7 +95,8 @@ public class RecentsPackageMonitor extends PackageMonitor { // If we know that the component still exists in the package, then skip continue; } - if (mSystemServicesProxy.getActivityInfo(cn, userId) != null) { + SystemServicesProxy ssp = RecentsTaskLoader.getInstance().getSystemServicesProxy(); + if (ssp.getActivityInfo(cn, userId) != null) { existingComponents.add(cn); } else { removedComponents.add(cn); diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java index 760382ed469e..39bef81386b4 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java +++ b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java @@ -262,8 +262,6 @@ public class RecentsTaskLoader { TaskResourceLoadQueue mLoadQueue; TaskResourceLoader mLoader; - RecentsPackageMonitor mPackageMonitor; - int mMaxThumbnailCacheSize; int mMaxIconCacheSize; int mNumVisibleTasksLoaded; @@ -293,7 +291,6 @@ public class RecentsTaskLoader { // Initialize the proxy, cache and loaders mSystemServicesProxy = new SystemServicesProxy(context); - mPackageMonitor = new RecentsPackageMonitor(); mLoadQueue = new TaskResourceLoadQueue(); mApplicationIconCache = new DrawableLruCache(iconCacheSize); mThumbnailCache = new BitmapLruCache(thumbnailCacheSize); @@ -519,17 +516,6 @@ public class RecentsTaskLoader { mLoadQueue.clearTasks(); } - /** Registers any broadcast receivers. */ - public void registerReceivers(Context context, RecentsPackageMonitor.PackageCallbacks cb) { - // Register the broadcast receiver to handle messages related to packages being added/removed - mPackageMonitor.register(context, cb); - } - - /** Unregisters any broadcast receivers. */ - public void unregisterReceivers() { - mPackageMonitor.unregister(); - } - /** * Handles signals from the system, trimming memory when requested to prevent us from running * out of memory. diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java index 73c9be94eeae..68faccc93c14 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java @@ -59,8 +59,7 @@ import java.util.List; * This view is the the top level layout that contains TaskStacks (which are laid out according * to their SpaceNode bounds. */ -public class RecentsView extends FrameLayout implements TaskStackView.TaskStackViewCallbacks, - RecentsPackageMonitor.PackageCallbacks { +public class RecentsView extends FrameLayout implements TaskStackView.TaskStackViewCallbacks { private static final String TAG = "RecentsView"; @@ -732,14 +731,4 @@ public class RecentsView extends FrameLayout implements TaskStackView.TaskStackV mCb.onTaskResize(t); } } - - /**** RecentsPackageMonitor.PackageCallbacks Implementation ****/ - - @Override - public void onPackagesChanged(RecentsPackageMonitor monitor, String packageName, int userId) { - // Propagate this event down to each task stack view - if (mTaskStackView != null) { - mTaskStackView.onPackagesChanged(monitor, packageName, userId); - } - } } diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java index b5f29a01c2b0..76b091cfc28b 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java @@ -33,11 +33,13 @@ import com.android.internal.logging.MetricsLogger; import com.android.systemui.R; import com.android.systemui.recents.Constants; import com.android.systemui.recents.RecentsActivityLaunchState; +import com.android.systemui.recents.RecentsActivity; import com.android.systemui.recents.RecentsConfiguration; +import com.android.systemui.recents.events.EventBus; +import com.android.systemui.recents.events.activity.PackagesChangedEvent; import com.android.systemui.recents.misc.DozeTrigger; import com.android.systemui.recents.misc.SystemServicesProxy; import com.android.systemui.recents.misc.Utilities; -import com.android.systemui.recents.model.RecentsPackageMonitor; import com.android.systemui.recents.model.RecentsTaskLoader; import com.android.systemui.recents.model.Task; import com.android.systemui.recents.model.TaskStack; @@ -54,7 +56,7 @@ import java.util.List; /* The visual representation of a task stack view */ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCallbacks, TaskView.TaskViewCallbacks, TaskStackViewScroller.TaskStackViewScrollerCallbacks, - ViewPool.ViewPoolConsumer<TaskView, Task>, RecentsPackageMonitor.PackageCallbacks { + ViewPool.ViewPoolConsumer<TaskView, Task> { /** The TaskView callbacks */ interface TaskStackViewCallbacks { @@ -77,7 +79,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal TaskStackViewTouchHandler mTouchHandler; TaskStackViewCallbacks mCb; ViewPool<TaskView, Task> mViewPool; - ArrayList<TaskViewTransform> mCurrentTaskTransforms = new ArrayList<TaskViewTransform>(); + ArrayList<TaskViewTransform> mCurrentTaskTransforms = new ArrayList<>(); DozeTrigger mUIDozeTrigger; DismissView mDismissAllButton; boolean mDismissAllButtonAnimating; @@ -97,9 +99,9 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal Matrix mTmpMatrix = new Matrix(); Rect mTmpRect = new Rect(); TaskViewTransform mTmpTransform = new TaskViewTransform(); - HashMap<Task, TaskView> mTmpTaskViewMap = new HashMap<Task, TaskView>(); - ArrayList<TaskView> mTaskViews = new ArrayList<TaskView>(); - List<TaskView> mImmutableTaskViews = new ArrayList<TaskView>(); + HashMap<Task, TaskView> mTmpTaskViewMap = new HashMap<>(); + ArrayList<TaskView> mTaskViews = new ArrayList<>(); + List<TaskView> mImmutableTaskViews = new ArrayList<>(); LayoutInflater mInflater; boolean mLayersDisabled; @@ -147,6 +149,18 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal mCb = cb; } + @Override + protected void onAttachedToWindow() { + EventBus.getDefault().register(this, RecentsActivity.EVENT_BUS_PRIORITY + 1); + super.onAttachedToWindow(); + } + + @Override + protected void onDetachedFromWindow() { + super.onDetachedFromWindow(); + EventBus.getDefault().unregister(this); + } + /** Sets the task stack */ void setStack(TaskStack stack) { // Set the new stack @@ -1430,13 +1444,12 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal postInvalidateOnAnimation(); } - /**** RecentsPackageMonitor.PackageCallbacks Implementation ****/ + /**** EventBus Events ****/ - @Override - public void onPackagesChanged(RecentsPackageMonitor monitor, String packageName, int userId) { + public final void onBusEvent(PackagesChangedEvent event) { // Compute which components need to be removed - HashSet<ComponentName> removedComponents = monitor.computeComponentsRemoved( - mStack.getTaskKeys(), packageName, userId); + HashSet<ComponentName> removedComponents = event.monitor.computeComponentsRemoved( + mStack.getTaskKeys(), event.packageName, event.userId); // For other tasks, just remove them directly if they no longer exist ArrayList<Task> tasks = mStack.getTasks(); |