Merge "New MessageRouter interface to replace Handler." into sc-v2-dev am: 6154a87850
Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/14980848 Change-Id: I70e44c393266951519dfbbb200656e57602d9e37
This commit is contained in:
@@ -0,0 +1,198 @@
|
||||
/*
|
||||
* Copyright (C) 2021 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.util.concurrency;
|
||||
|
||||
/**
|
||||
* Allows triggering methods based on a passed in id or message, generally on another thread.
|
||||
*
|
||||
* Messages sent on to this router must be processed in order. That is to say, if three
|
||||
* messages are sent with no delay, they must be processed in the order they were sent. Moreover,
|
||||
* if messages are sent with various delays, they must be processed in order of their delay.
|
||||
*
|
||||
* Messages can be passed by either a simple integer or an instance of a class. Unique integers are
|
||||
* considered unique messages. Unique message classes (not instances) are considered unique
|
||||
* messages. You can use message classes to pass extra data for processing to subscribers.
|
||||
*
|
||||
* <pre>
|
||||
* // Three messages with three unique integer messages.
|
||||
* // They can be subscribed to independently.
|
||||
* router.sendMessage(0);
|
||||
* router.sendMessage(1);
|
||||
* router.sendMessage(2);
|
||||
*
|
||||
* // Three messages with two unique message classes.
|
||||
* // The first and third messages will be delivered to the same subscribers.
|
||||
* router.sendMessage(new Foo(0));
|
||||
* router.sendMessage(new Bar(1));
|
||||
* router.sendMessage(new Foo(2));
|
||||
* </pre>
|
||||
*
|
||||
* The number of unique ids and message types used should be relatively constrained. Construct
|
||||
* a custom message-class and put unique, per-message data inside of it.
|
||||
*/
|
||||
public interface MessageRouter {
|
||||
/**
|
||||
* Alerts any listeners subscribed to the passed in id.
|
||||
*
|
||||
* The number of unique ids used should be relatively constrained - used to identify the type
|
||||
* of message being sent. If unique information needs to be passed with each call, use
|
||||
* {@link #sendMessage(Object)}.
|
||||
*
|
||||
* @param id An identifier for the message
|
||||
*/
|
||||
default void sendMessage(int id) {
|
||||
sendMessageDelayed(id, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Alerts any listeners subscribed to the passed in message.
|
||||
*
|
||||
* The number of message types used should be relatively constrained. If no unique information
|
||||
* needs to be passed in, you can simply use {@link #sendMessage(int)}} which takes an integer
|
||||
* instead of a unique class type.
|
||||
*
|
||||
* The class of the passed in object will be used to router the message.
|
||||
*
|
||||
* @param data A message containing extra data for processing.
|
||||
*/
|
||||
default void sendMessage(Object data) {
|
||||
sendMessageDelayed(data, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Alerts any listeners subscribed to the passed in id in the future.
|
||||
*
|
||||
* The number of unique ids used should be relatively constrained - used to identify the type
|
||||
* of message being sent. If unique information needs to be passed with each call, use
|
||||
* {@link #sendMessageDelayed(Object, long)}.
|
||||
*
|
||||
* @param id An identifier for the message
|
||||
* @param delayMs Number of milliseconds to wait before alerting.
|
||||
*/
|
||||
void sendMessageDelayed(int id, long delayMs);
|
||||
|
||||
|
||||
/**
|
||||
* Alerts any listeners subscribed to the passed in message in the future.
|
||||
*
|
||||
* The number of message types used should be relatively constrained. If no unique information
|
||||
* needs to be passed in, you can simply use {@link #sendMessageDelayed(int, long)} which takes
|
||||
* an integer instead of a unique class type.
|
||||
*
|
||||
* @param data A message containing extra data for processing.
|
||||
* @param delayMs Number of milliseconds to wait before alerting.
|
||||
*/
|
||||
void sendMessageDelayed(Object data, long delayMs);
|
||||
|
||||
/**
|
||||
* Cancel all unprocessed messages for a given id.
|
||||
*
|
||||
* If a message has multiple listeners and one of those listeners has been alerted, the other
|
||||
* listeners that follow it may also be alerted. This is only guaranteed to cancel messages
|
||||
* that are still queued.
|
||||
*
|
||||
* @param id The message id to cancel.
|
||||
*/
|
||||
void cancelMessages(int id);
|
||||
|
||||
/**
|
||||
* Cancel all unprocessed messages for a given message type.
|
||||
*
|
||||
* If a message has multiple listeners and one of those listeners has been alerted, the other
|
||||
* listeners that follow it may also be alerted. This is only guaranteed to cancel messages
|
||||
* that are still queued.
|
||||
*
|
||||
* @param messageType The class of the message to cancel
|
||||
*/
|
||||
<T> void cancelMessages(Class<T> messageType);
|
||||
|
||||
/**
|
||||
* Add a listener for a message that does not handle any extra data.
|
||||
*
|
||||
* See also {@link #subscribeTo(Class, DataMessageListener)}.
|
||||
*
|
||||
* @param id The message id to listener for.
|
||||
* @param listener
|
||||
*/
|
||||
void subscribeTo(int id, SimpleMessageListener listener);
|
||||
|
||||
/**
|
||||
* Add a listener for a message of a specific type.
|
||||
*
|
||||
* See also {@link #subscribeTo(Class, DataMessageListener)}.
|
||||
*
|
||||
* @param messageType The class of message to listen for.
|
||||
* @param listener
|
||||
*/
|
||||
<T> void subscribeTo(Class<T> messageType, DataMessageListener<T> listener);
|
||||
|
||||
/**
|
||||
* Remove a listener for a specific message.
|
||||
*
|
||||
* See also {@link #unsubscribeFrom(Class, DataMessageListener)}
|
||||
*
|
||||
* @param id The message id to stop listening for.
|
||||
* @param listener The listener to remove.
|
||||
*/
|
||||
void unsubscribeFrom(int id, SimpleMessageListener listener);
|
||||
|
||||
/**
|
||||
* Remove a listener for a specific message.
|
||||
*
|
||||
* See also {@link #unsubscribeFrom(int, SimpleMessageListener)}.
|
||||
*
|
||||
* @param messageType The class of message to stop listening for.
|
||||
* @param listener The listener to remove.
|
||||
*/
|
||||
<T> void unsubscribeFrom(Class<T> messageType, DataMessageListener<T> listener);
|
||||
|
||||
/**
|
||||
* Remove a listener for all messages that it is subscribed to.
|
||||
*
|
||||
* See also {@link #unsubscribeFrom(DataMessageListener)}.
|
||||
*
|
||||
* @param listener The listener to remove.
|
||||
*/
|
||||
void unsubscribeFrom(SimpleMessageListener listener);
|
||||
|
||||
/**
|
||||
* Remove a listener for all messages that it is subscribed to.
|
||||
*
|
||||
* See also {@link #unsubscribeFrom(SimpleMessageListener)}.
|
||||
*
|
||||
* @param listener The listener to remove.
|
||||
*/
|
||||
<T> void unsubscribeFrom(DataMessageListener<T> listener);
|
||||
|
||||
/**
|
||||
* A Listener interface for when no extra data is expected or desired.
|
||||
*/
|
||||
interface SimpleMessageListener {
|
||||
/** */
|
||||
void onMessage(int id);
|
||||
}
|
||||
|
||||
/**
|
||||
* A Listener interface for when extra data is expected or desired.
|
||||
*
|
||||
* @param <T>
|
||||
*/
|
||||
interface DataMessageListener<T> {
|
||||
/** */
|
||||
void onMessage(T data);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,186 @@
|
||||
/*
|
||||
* Copyright (C) 2021 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.util.concurrency;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Implementation of {@link MessageRouter}.
|
||||
*/
|
||||
public class MessageRouterImpl implements MessageRouter {
|
||||
|
||||
private final DelayableExecutor mDelayableExecutor;
|
||||
|
||||
private final Map<Integer, List<Runnable>> mIdMessageCancelers = new HashMap<>();
|
||||
private final Map<Class<Object>, List<Runnable>> mDataMessageCancelers = new HashMap<>();
|
||||
private final Map<Integer, List<SimpleMessageListener>> mSimpleMessageListenerMap =
|
||||
new HashMap<>();
|
||||
private final Map<Class<?>, List<DataMessageListener<Object>>> mDataMessageListenerMap =
|
||||
new HashMap<>();
|
||||
|
||||
public MessageRouterImpl(DelayableExecutor delayableExecutor) {
|
||||
mDelayableExecutor = delayableExecutor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendMessageDelayed(int id, long delayMs) {
|
||||
addCanceler(id, mDelayableExecutor.executeDelayed(() -> onMessage(id), delayMs));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendMessageDelayed(Object data, long delayMs) {
|
||||
addCanceler((Class<Object>) data.getClass(), mDelayableExecutor.executeDelayed(
|
||||
() -> onMessage(data), delayMs));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cancelMessages(int id) {
|
||||
synchronized (mIdMessageCancelers) {
|
||||
if (mIdMessageCancelers.containsKey(id)) {
|
||||
for (Runnable canceler : mIdMessageCancelers.get(id)) {
|
||||
canceler.run();
|
||||
}
|
||||
// Remove, don't clear, otherwise this could look like a memory leak as
|
||||
// more and more unique message ids are passed in.
|
||||
mIdMessageCancelers.remove(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> void cancelMessages(Class<T> messageType) {
|
||||
synchronized (mDataMessageCancelers) {
|
||||
if (mDataMessageCancelers.containsKey(messageType)) {
|
||||
for (Runnable canceler : mDataMessageCancelers.get(messageType)) {
|
||||
canceler.run();
|
||||
}
|
||||
// Remove, don't clear, otherwise this could look like a memory leak as
|
||||
// more and more unique message types are passed in.
|
||||
mDataMessageCancelers.remove(messageType);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void subscribeTo(int id, SimpleMessageListener listener) {
|
||||
synchronized (mSimpleMessageListenerMap) {
|
||||
mSimpleMessageListenerMap.putIfAbsent(id, new ArrayList<>());
|
||||
mSimpleMessageListenerMap.get(id).add(listener);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> void subscribeTo(Class<T> messageType, DataMessageListener<T> listener) {
|
||||
synchronized (mDataMessageListenerMap) {
|
||||
mDataMessageListenerMap.putIfAbsent(messageType, new ArrayList<>());
|
||||
mDataMessageListenerMap.get(messageType).add((DataMessageListener<Object>) listener);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unsubscribeFrom(int id, SimpleMessageListener listener) {
|
||||
synchronized (mSimpleMessageListenerMap) {
|
||||
if (mSimpleMessageListenerMap.containsKey(id)) {
|
||||
mSimpleMessageListenerMap.get(id).remove(listener);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> void unsubscribeFrom(Class<T> messageType, DataMessageListener<T> listener) {
|
||||
synchronized (mDataMessageListenerMap) {
|
||||
if (mDataMessageListenerMap.containsKey(messageType)) {
|
||||
mDataMessageListenerMap.get(messageType).remove(listener);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unsubscribeFrom(SimpleMessageListener listener) {
|
||||
synchronized (mSimpleMessageListenerMap) {
|
||||
for (Integer id : mSimpleMessageListenerMap.keySet()) {
|
||||
mSimpleMessageListenerMap.get(id).remove(listener);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> void unsubscribeFrom(DataMessageListener<T> listener) {
|
||||
synchronized (mDataMessageListenerMap) {
|
||||
for (Class<?> messageType : mDataMessageListenerMap.keySet()) {
|
||||
mDataMessageListenerMap.get(messageType).remove(listener);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void addCanceler(int id, Runnable canceler) {
|
||||
synchronized (mIdMessageCancelers) {
|
||||
mIdMessageCancelers.putIfAbsent(id, new ArrayList<>());
|
||||
mIdMessageCancelers.get(id).add(canceler);
|
||||
}
|
||||
}
|
||||
|
||||
private void addCanceler(Class<Object> data, Runnable canceler) {
|
||||
synchronized (mDataMessageCancelers) {
|
||||
mDataMessageCancelers.putIfAbsent(data, new ArrayList<>());
|
||||
mDataMessageCancelers.get(data).add(canceler);
|
||||
}
|
||||
}
|
||||
|
||||
private void onMessage(int id) {
|
||||
synchronized (mSimpleMessageListenerMap) {
|
||||
if (mSimpleMessageListenerMap.containsKey(id)) {
|
||||
for (SimpleMessageListener listener : mSimpleMessageListenerMap.get(id)) {
|
||||
listener.onMessage(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
synchronized (mIdMessageCancelers) {
|
||||
if (mIdMessageCancelers.containsKey(id) && !mIdMessageCancelers.get(id).isEmpty()) {
|
||||
mIdMessageCancelers.get(id).remove(0);
|
||||
if (mIdMessageCancelers.get(id).isEmpty()) {
|
||||
mIdMessageCancelers.remove(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void onMessage(Object data) {
|
||||
synchronized (mDataMessageListenerMap) {
|
||||
if (mDataMessageListenerMap.containsKey(data.getClass())) {
|
||||
for (DataMessageListener<Object> listener : mDataMessageListenerMap.get(
|
||||
data.getClass())) {
|
||||
listener.onMessage(data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
synchronized (mDataMessageCancelers) {
|
||||
if (mDataMessageCancelers.containsKey(data.getClass())
|
||||
&& !mDataMessageCancelers.get(data.getClass()).isEmpty()) {
|
||||
mDataMessageCancelers.get(data.getClass()).remove(0);
|
||||
if (mDataMessageCancelers.get(data.getClass()).isEmpty()) {
|
||||
mDataMessageCancelers.remove(data.getClass());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -170,4 +170,20 @@ public abstract class SysUIConcurrencyModule {
|
||||
public static Executor provideUiBackgroundExecutor() {
|
||||
return Executors.newSingleThreadExecutor();
|
||||
}
|
||||
|
||||
/** */
|
||||
@Provides
|
||||
@Main
|
||||
public static MessageRouter providesMainMessageRouter(
|
||||
@Main DelayableExecutor executor) {
|
||||
return new MessageRouterImpl(executor);
|
||||
}
|
||||
|
||||
/** */
|
||||
@Provides
|
||||
@Background
|
||||
public static MessageRouter providesBackgroundMessageRouter(
|
||||
@Background DelayableExecutor executor) {
|
||||
return new MessageRouterImpl(executor);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,7 +36,6 @@ import android.graphics.drawable.Drawable;
|
||||
import android.os.Build;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.os.Message;
|
||||
import android.os.Process;
|
||||
import android.os.SystemProperties;
|
||||
import android.provider.Settings;
|
||||
@@ -60,6 +59,8 @@ import com.android.systemui.qs.QSHost;
|
||||
import com.android.systemui.qs.logging.QSLogger;
|
||||
import com.android.systemui.qs.tileimpl.QSIconViewImpl;
|
||||
import com.android.systemui.qs.tileimpl.QSTileImpl;
|
||||
import com.android.systemui.util.concurrency.DelayableExecutor;
|
||||
import com.android.systemui.util.concurrency.MessageRouter;
|
||||
|
||||
import java.io.FileDescriptor;
|
||||
import java.io.PrintWriter;
|
||||
@@ -110,18 +111,18 @@ public class GarbageMonitor implements Dumpable {
|
||||
private static final int DO_GARBAGE_INSPECTION = 1000;
|
||||
private static final int DO_HEAP_TRACK = 3000;
|
||||
|
||||
private static final int GARBAGE_ALLOWANCE = 5;
|
||||
static final int GARBAGE_ALLOWANCE = 5;
|
||||
|
||||
private static final String TAG = "GarbageMonitor";
|
||||
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
|
||||
|
||||
private final Handler mHandler;
|
||||
private final MessageRouter mMessageRouter;
|
||||
private final TrackedGarbage mTrackedGarbage;
|
||||
private final LeakReporter mLeakReporter;
|
||||
private final Context mContext;
|
||||
private final ActivityManager mAm;
|
||||
private final DelayableExecutor mDelayableExecutor;
|
||||
private MemoryTile mQSTile;
|
||||
private DumpTruck mDumpTruck;
|
||||
private final DumpTruck mDumpTruck;
|
||||
|
||||
private final LongSparseArray<ProcessMemInfo> mData = new LongSparseArray<>();
|
||||
private final ArrayList<Long> mPids = new ArrayList<>();
|
||||
@@ -133,13 +134,16 @@ public class GarbageMonitor implements Dumpable {
|
||||
@Inject
|
||||
public GarbageMonitor(
|
||||
Context context,
|
||||
@Background Looper bgLooper,
|
||||
@Background DelayableExecutor delayableExecutor,
|
||||
@Background MessageRouter messageRouter,
|
||||
LeakDetector leakDetector,
|
||||
LeakReporter leakReporter) {
|
||||
mContext = context.getApplicationContext();
|
||||
mAm = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
|
||||
|
||||
mHandler = new BackgroundHeapCheckHandler(bgLooper);
|
||||
mDelayableExecutor = delayableExecutor;
|
||||
mMessageRouter = messageRouter;
|
||||
mMessageRouter.subscribeTo(DO_GARBAGE_INSPECTION, this::doGarbageInspection);
|
||||
mMessageRouter.subscribeTo(DO_HEAP_TRACK, this::doHeapTrack);
|
||||
|
||||
mTrackedGarbage = leakDetector.getTrackedGarbage();
|
||||
mLeakReporter = leakReporter;
|
||||
@@ -158,13 +162,13 @@ public class GarbageMonitor implements Dumpable {
|
||||
return;
|
||||
}
|
||||
|
||||
mHandler.sendEmptyMessage(DO_GARBAGE_INSPECTION);
|
||||
mMessageRouter.sendMessage(DO_GARBAGE_INSPECTION);
|
||||
}
|
||||
|
||||
public void startHeapTracking() {
|
||||
startTrackingProcess(
|
||||
android.os.Process.myPid(), mContext.getPackageName(), System.currentTimeMillis());
|
||||
mHandler.sendEmptyMessage(DO_HEAP_TRACK);
|
||||
mMessageRouter.sendMessage(DO_HEAP_TRACK);
|
||||
}
|
||||
|
||||
private boolean gcAndCheckGarbage() {
|
||||
@@ -586,33 +590,18 @@ public class GarbageMonitor implements Dumpable {
|
||||
}
|
||||
}
|
||||
|
||||
private class BackgroundHeapCheckHandler extends Handler {
|
||||
BackgroundHeapCheckHandler(Looper onLooper) {
|
||||
super(onLooper);
|
||||
if (Looper.getMainLooper().equals(onLooper)) {
|
||||
throw new RuntimeException(
|
||||
"BackgroundHeapCheckHandler may not run on the ui thread");
|
||||
}
|
||||
private void doGarbageInspection(int id) {
|
||||
if (gcAndCheckGarbage()) {
|
||||
mDelayableExecutor.executeDelayed(this::reinspectGarbageAfterGc, 100);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleMessage(Message m) {
|
||||
switch (m.what) {
|
||||
case DO_GARBAGE_INSPECTION:
|
||||
if (gcAndCheckGarbage()) {
|
||||
postDelayed(GarbageMonitor.this::reinspectGarbageAfterGc, 100);
|
||||
}
|
||||
mMessageRouter.cancelMessages(DO_GARBAGE_INSPECTION);
|
||||
mMessageRouter.sendMessageDelayed(DO_GARBAGE_INSPECTION, GARBAGE_INSPECTION_INTERVAL);
|
||||
}
|
||||
|
||||
removeMessages(DO_GARBAGE_INSPECTION);
|
||||
sendEmptyMessageDelayed(DO_GARBAGE_INSPECTION, GARBAGE_INSPECTION_INTERVAL);
|
||||
break;
|
||||
|
||||
case DO_HEAP_TRACK:
|
||||
update();
|
||||
removeMessages(DO_HEAP_TRACK);
|
||||
sendEmptyMessageDelayed(DO_HEAP_TRACK, HEAP_TRACK_INTERVAL);
|
||||
break;
|
||||
}
|
||||
}
|
||||
private void doHeapTrack(int id) {
|
||||
update();
|
||||
mMessageRouter.cancelMessages(DO_HEAP_TRACK);
|
||||
mMessageRouter.sendMessageDelayed(DO_HEAP_TRACK, HEAP_TRACK_INTERVAL);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,371 @@
|
||||
/*
|
||||
* Copyright (C) 2021 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.util.concurrency;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyInt;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.Mockito.inOrder;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.reset;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
import android.testing.AndroidTestingRunner;
|
||||
|
||||
import androidx.test.filters.SmallTest;
|
||||
|
||||
import com.android.systemui.SysuiTestCase;
|
||||
import com.android.systemui.util.time.FakeSystemClock;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.InOrder;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
|
||||
@SmallTest
|
||||
@RunWith(AndroidTestingRunner.class)
|
||||
public class MessageRouterImplTest extends SysuiTestCase {
|
||||
private static final int MESSAGE_A = 0;
|
||||
private static final int MESSAGE_B = 1;
|
||||
private static final int MESSAGE_C = 2;
|
||||
|
||||
private static final String METADATA_A = "A";
|
||||
private static final String METADATA_B = "B";
|
||||
private static final String METADATA_C = "C";
|
||||
private static final Foobar METADATA_FOO = new Foobar();
|
||||
|
||||
FakeExecutor mFakeExecutor = new FakeExecutor(new FakeSystemClock());
|
||||
@Mock
|
||||
MessageRouter.SimpleMessageListener mNoMdListener;
|
||||
@Mock
|
||||
MessageRouter.DataMessageListener<String> mStringListener;
|
||||
@Mock
|
||||
MessageRouter.DataMessageListener<Foobar> mFoobarListener;
|
||||
private MessageRouterImpl mMR;
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
mMR = new MessageRouterImpl(mFakeExecutor);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSingleMessage_NoMetaData() {
|
||||
mMR.subscribeTo(MESSAGE_A, mNoMdListener);
|
||||
|
||||
mMR.sendMessage(MESSAGE_A);
|
||||
verify(mNoMdListener, never()).onMessage(anyInt());
|
||||
|
||||
mFakeExecutor.runAllReady();
|
||||
verify(mNoMdListener).onMessage(MESSAGE_A);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSingleMessage_WithMetaData() {
|
||||
mMR.subscribeTo(String.class, mStringListener);
|
||||
|
||||
mMR.sendMessage(METADATA_A);
|
||||
verify(mStringListener, never()).onMessage(anyString());
|
||||
|
||||
mFakeExecutor.runAllReady();
|
||||
verify(mStringListener).onMessage(METADATA_A);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMessages_WithMixedMetaData() {
|
||||
mMR.subscribeTo(String.class, mStringListener);
|
||||
mMR.subscribeTo(Foobar.class, mFoobarListener);
|
||||
|
||||
mMR.sendMessage(METADATA_A);
|
||||
verify(mStringListener, never()).onMessage(anyString());
|
||||
verify(mFoobarListener, never()).onMessage(any(Foobar.class));
|
||||
|
||||
mFakeExecutor.runAllReady();
|
||||
verify(mStringListener).onMessage(METADATA_A);
|
||||
verify(mFoobarListener, never()).onMessage(any(Foobar.class));
|
||||
|
||||
reset(mStringListener);
|
||||
reset(mFoobarListener);
|
||||
|
||||
mMR.sendMessage(METADATA_FOO);
|
||||
verify(mStringListener, never()).onMessage(anyString());
|
||||
verify(mFoobarListener, never()).onMessage(any(Foobar.class));
|
||||
|
||||
mFakeExecutor.runAllReady();
|
||||
verify(mStringListener, never()).onMessage(anyString());
|
||||
verify(mFoobarListener).onMessage(METADATA_FOO);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMessages_WithAndWithoutMetaData() {
|
||||
mMR.subscribeTo(MESSAGE_A, mNoMdListener);
|
||||
mMR.subscribeTo(String.class, mStringListener);
|
||||
|
||||
mMR.sendMessage(MESSAGE_A);
|
||||
verify(mNoMdListener, never()).onMessage(anyInt());
|
||||
verify(mStringListener, never()).onMessage(anyString());
|
||||
|
||||
mFakeExecutor.runAllReady();
|
||||
verify(mNoMdListener).onMessage(MESSAGE_A);
|
||||
verify(mStringListener, never()).onMessage(anyString());
|
||||
|
||||
reset(mNoMdListener);
|
||||
reset(mStringListener);
|
||||
|
||||
mMR.sendMessage(METADATA_A);
|
||||
verify(mNoMdListener, never()).onMessage(anyInt());
|
||||
verify(mStringListener, never()).onMessage(anyString());
|
||||
|
||||
mFakeExecutor.runAllReady();
|
||||
verify(mNoMdListener, never()).onMessage(anyInt());
|
||||
verify(mStringListener).onMessage(METADATA_A);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRepeatedMessage() {
|
||||
mMR.subscribeTo(String.class, mStringListener);
|
||||
|
||||
mMR.sendMessage(METADATA_A);
|
||||
mMR.sendMessage(METADATA_A);
|
||||
mMR.sendMessage(METADATA_B);
|
||||
verify(mStringListener, never()).onMessage(anyString());
|
||||
|
||||
InOrder ordered = inOrder(mStringListener);
|
||||
mFakeExecutor.runNextReady();
|
||||
ordered.verify(mStringListener).onMessage(METADATA_A);
|
||||
mFakeExecutor.runNextReady();
|
||||
ordered.verify(mStringListener).onMessage(METADATA_A);
|
||||
mFakeExecutor.runNextReady();
|
||||
ordered.verify(mStringListener).onMessage(METADATA_B);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCancelMessage() {
|
||||
mMR.subscribeTo(MESSAGE_A, mNoMdListener);
|
||||
mMR.subscribeTo(MESSAGE_B, mNoMdListener);
|
||||
mMR.subscribeTo(MESSAGE_C, mNoMdListener);
|
||||
|
||||
mMR.sendMessage(MESSAGE_A);
|
||||
mMR.sendMessage(MESSAGE_B);
|
||||
mMR.sendMessage(MESSAGE_B);
|
||||
mMR.sendMessage(MESSAGE_B);
|
||||
mMR.sendMessage(MESSAGE_B);
|
||||
mMR.sendMessage(MESSAGE_C);
|
||||
verify(mNoMdListener, never()).onMessage(anyInt());
|
||||
|
||||
mMR.cancelMessages(MESSAGE_B);
|
||||
|
||||
mFakeExecutor.runAllReady();
|
||||
|
||||
InOrder ordered = inOrder(mNoMdListener);
|
||||
ordered.verify(mNoMdListener).onMessage(MESSAGE_A);
|
||||
ordered.verify(mNoMdListener).onMessage(MESSAGE_C);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSendMessage_NoSubscriber() {
|
||||
mMR.sendMessage(MESSAGE_A);
|
||||
verify(mNoMdListener, never()).onMessage(anyInt());
|
||||
|
||||
mFakeExecutor.runAllReady();
|
||||
verify(mNoMdListener, never()).onMessage(anyInt());
|
||||
|
||||
mMR.subscribeTo(MESSAGE_A, mNoMdListener);
|
||||
|
||||
mFakeExecutor.runAllReady();
|
||||
verify(mNoMdListener, never()).onMessage(anyInt());
|
||||
|
||||
mMR.sendMessage(MESSAGE_A);
|
||||
verify(mNoMdListener, never()).onMessage(anyInt());
|
||||
|
||||
mFakeExecutor.runAllReady();
|
||||
verify(mNoMdListener).onMessage(MESSAGE_A);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUnsubscribe_SpecificMessage_NoMetadata() {
|
||||
mMR.subscribeTo(MESSAGE_A, mNoMdListener);
|
||||
mMR.subscribeTo(MESSAGE_B, mNoMdListener);
|
||||
mMR.sendMessage(MESSAGE_A);
|
||||
mMR.sendMessage(MESSAGE_B);
|
||||
|
||||
mFakeExecutor.runAllReady();
|
||||
InOrder ordered = inOrder(mNoMdListener);
|
||||
ordered.verify(mNoMdListener).onMessage(MESSAGE_A);
|
||||
ordered.verify(mNoMdListener).onMessage(MESSAGE_B);
|
||||
|
||||
reset(mNoMdListener);
|
||||
mMR.unsubscribeFrom(MESSAGE_A, mNoMdListener);
|
||||
mMR.sendMessage(MESSAGE_A);
|
||||
mMR.sendMessage(MESSAGE_B);
|
||||
|
||||
mFakeExecutor.runAllReady();
|
||||
verify(mNoMdListener, never()).onMessage(MESSAGE_A);
|
||||
verify(mNoMdListener).onMessage(MESSAGE_B);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUnsubscribe_SpecificMessage_WithMetadata() {
|
||||
mMR.subscribeTo(String.class, mStringListener);
|
||||
mMR.subscribeTo(Foobar.class, mFoobarListener);
|
||||
mMR.sendMessage(METADATA_A);
|
||||
mMR.sendMessage(METADATA_FOO);
|
||||
|
||||
mFakeExecutor.runNextReady();
|
||||
verify(mStringListener).onMessage(METADATA_A);
|
||||
mFakeExecutor.runNextReady();
|
||||
verify(mFoobarListener).onMessage(METADATA_FOO);
|
||||
|
||||
reset(mStringListener);
|
||||
reset(mFoobarListener);
|
||||
mMR.unsubscribeFrom(String.class, mStringListener);
|
||||
mMR.sendMessage(METADATA_A);
|
||||
mMR.sendMessage(METADATA_FOO);
|
||||
|
||||
mFakeExecutor.runAllReady();
|
||||
verify(mStringListener, never()).onMessage(METADATA_A);
|
||||
verify(mFoobarListener).onMessage(METADATA_FOO);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUnsubscribe_AllMessages_NoMetadata() {
|
||||
mMR.subscribeTo(MESSAGE_A, mNoMdListener);
|
||||
mMR.subscribeTo(MESSAGE_B, mNoMdListener);
|
||||
mMR.subscribeTo(MESSAGE_C, mNoMdListener);
|
||||
|
||||
mMR.sendMessage(MESSAGE_A);
|
||||
mMR.sendMessage(MESSAGE_B);
|
||||
mMR.sendMessage(MESSAGE_C);
|
||||
|
||||
mFakeExecutor.runAllReady();
|
||||
verify(mNoMdListener).onMessage(MESSAGE_A);
|
||||
verify(mNoMdListener).onMessage(MESSAGE_B);
|
||||
verify(mNoMdListener).onMessage(MESSAGE_C);
|
||||
|
||||
reset(mNoMdListener);
|
||||
|
||||
mMR.unsubscribeFrom(mNoMdListener);
|
||||
mMR.sendMessage(MESSAGE_A);
|
||||
mMR.sendMessage(MESSAGE_B);
|
||||
mMR.sendMessage(MESSAGE_C);
|
||||
|
||||
mFakeExecutor.runAllReady();
|
||||
verify(mNoMdListener, never()).onMessage(MESSAGE_A);
|
||||
verify(mNoMdListener, never()).onMessage(MESSAGE_B);
|
||||
verify(mNoMdListener, never()).onMessage(MESSAGE_C);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUnsubscribe_AllMessages_WithMetadata() {
|
||||
mMR.subscribeTo(String.class, mStringListener);
|
||||
|
||||
mMR.sendMessage(METADATA_A);
|
||||
mMR.sendMessage(METADATA_B);
|
||||
mMR.sendMessage(METADATA_C);
|
||||
|
||||
mFakeExecutor.runAllReady();
|
||||
verify(mStringListener).onMessage(METADATA_A);
|
||||
verify(mStringListener).onMessage(METADATA_B);
|
||||
verify(mStringListener).onMessage(METADATA_C);
|
||||
|
||||
reset(mStringListener);
|
||||
|
||||
mMR.unsubscribeFrom(mStringListener);
|
||||
mMR.sendMessage(METADATA_A);
|
||||
mMR.sendMessage(METADATA_B);
|
||||
mMR.sendMessage(METADATA_C);
|
||||
|
||||
mFakeExecutor.runAllReady();
|
||||
verify(mStringListener, never()).onMessage(METADATA_A);
|
||||
verify(mStringListener, never()).onMessage(METADATA_B);
|
||||
verify(mStringListener, never()).onMessage(METADATA_C);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSingleDelayedMessage_NoMetaData() {
|
||||
mMR.subscribeTo(MESSAGE_A, mNoMdListener);
|
||||
|
||||
mMR.sendMessageDelayed(MESSAGE_A, 100);
|
||||
verify(mNoMdListener, never()).onMessage(anyInt());
|
||||
|
||||
mFakeExecutor.runAllReady();
|
||||
verify(mNoMdListener, never()).onMessage(anyInt());
|
||||
|
||||
mFakeExecutor.advanceClockToNext();
|
||||
mFakeExecutor.runAllReady();
|
||||
verify(mNoMdListener).onMessage(MESSAGE_A);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSingleDelayedMessage_WithMetaData() {
|
||||
mMR.subscribeTo(String.class, mStringListener);
|
||||
|
||||
mMR.sendMessageDelayed(METADATA_C, 1000);
|
||||
verify(mStringListener, never()).onMessage(anyString());
|
||||
|
||||
mFakeExecutor.runAllReady();
|
||||
verify(mStringListener, never()).onMessage(anyString());
|
||||
|
||||
mFakeExecutor.advanceClockToNext();
|
||||
mFakeExecutor.runAllReady();
|
||||
verify(mStringListener).onMessage(METADATA_C);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMultipleDelayedMessages() {
|
||||
mMR.subscribeTo(String.class, mStringListener);
|
||||
|
||||
mMR.sendMessageDelayed(METADATA_A, 100);
|
||||
mMR.sendMessageDelayed(METADATA_B, 1000);
|
||||
mMR.sendMessageDelayed(METADATA_C, 500);
|
||||
verify(mStringListener, never()).onMessage(anyString());
|
||||
|
||||
mFakeExecutor.runAllReady();
|
||||
verify(mStringListener, never()).onMessage(anyString());
|
||||
|
||||
mFakeExecutor.advanceClockToLast();
|
||||
mFakeExecutor.runAllReady();
|
||||
|
||||
InOrder ordered = inOrder(mStringListener);
|
||||
ordered.verify(mStringListener).onMessage(METADATA_A);
|
||||
ordered.verify(mStringListener).onMessage(METADATA_C);
|
||||
ordered.verify(mStringListener).onMessage(METADATA_B);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCancelDelayedMessages() {
|
||||
mMR.subscribeTo(String.class, mStringListener);
|
||||
|
||||
mMR.sendMessageDelayed(METADATA_A, 100);
|
||||
mMR.sendMessageDelayed(METADATA_B, 1000);
|
||||
mMR.sendMessageDelayed(METADATA_C, 500);
|
||||
verify(mStringListener, never()).onMessage(anyString());
|
||||
|
||||
mFakeExecutor.runAllReady();
|
||||
verify(mStringListener, never()).onMessage(anyString());
|
||||
|
||||
mMR.cancelMessages(String.class);
|
||||
mFakeExecutor.advanceClockToLast();
|
||||
mFakeExecutor.runAllReady();
|
||||
|
||||
verify(mStringListener, never()).onMessage(anyString());
|
||||
}
|
||||
|
||||
private static class Foobar {}
|
||||
}
|
||||
@@ -17,124 +17,94 @@
|
||||
package com.android.systemui.util.leak;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.anyInt;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Looper;
|
||||
import android.testing.AndroidTestingRunner;
|
||||
import android.testing.TestableLooper;
|
||||
import android.testing.TestableLooper.RunWithLooper;
|
||||
|
||||
import androidx.test.filters.SmallTest;
|
||||
|
||||
import com.android.systemui.SysuiTestCase;
|
||||
import com.android.systemui.util.concurrency.FakeExecutor;
|
||||
import com.android.systemui.util.concurrency.MessageRouterImpl;
|
||||
import com.android.systemui.util.time.FakeSystemClock;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
|
||||
@SmallTest
|
||||
@RunWith(AndroidTestingRunner.class)
|
||||
@RunWithLooper
|
||||
public class GarbageMonitorTest extends SysuiTestCase {
|
||||
|
||||
private LeakReporter mLeakReporter;
|
||||
private TrackedGarbage mTrackedGarbage;
|
||||
private TestableGarbageMonitor mGarbageMonitor;
|
||||
@Mock private LeakReporter mLeakReporter;
|
||||
@Mock private TrackedGarbage mTrackedGarbage;
|
||||
private GarbageMonitor mGarbageMonitor;
|
||||
private final FakeExecutor mFakeExecutor = new FakeExecutor(new FakeSystemClock());
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
mTrackedGarbage = mock(TrackedGarbage.class);
|
||||
mLeakReporter = mock(LeakReporter.class);
|
||||
MockitoAnnotations.initMocks(this);
|
||||
mGarbageMonitor =
|
||||
new TestableGarbageMonitor(
|
||||
new GarbageMonitor(
|
||||
mContext,
|
||||
TestableLooper.get(this).getLooper(),
|
||||
mFakeExecutor,
|
||||
new MessageRouterImpl(mFakeExecutor),
|
||||
new LeakDetector(null, mTrackedGarbage, null),
|
||||
mLeakReporter);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCallbacks_getScheduled() {
|
||||
mGarbageMonitor.startLeakMonitor();
|
||||
mGarbageMonitor.runCallbacksOnce();
|
||||
mGarbageMonitor.runCallbacksOnce();
|
||||
mGarbageMonitor.runCallbacksOnce();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNoGarbage_doesntDump() {
|
||||
when(mTrackedGarbage.countOldGarbage()).thenReturn(0);
|
||||
|
||||
mGarbageMonitor.startLeakMonitor();
|
||||
mGarbageMonitor.runCallbacksOnce();
|
||||
mGarbageMonitor.runCallbacksOnce();
|
||||
mGarbageMonitor.runCallbacksOnce();
|
||||
|
||||
verify(mLeakReporter, never()).dumpLeak(anyInt());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testALittleGarbage_doesntDump() {
|
||||
when(mTrackedGarbage.countOldGarbage()).thenReturn(4);
|
||||
when(mTrackedGarbage.countOldGarbage()).thenReturn(GarbageMonitor.GARBAGE_ALLOWANCE);
|
||||
|
||||
mGarbageMonitor.startLeakMonitor();
|
||||
mGarbageMonitor.runCallbacksOnce();
|
||||
mGarbageMonitor.runCallbacksOnce();
|
||||
mGarbageMonitor.runCallbacksOnce();
|
||||
mGarbageMonitor.reinspectGarbageAfterGc();
|
||||
|
||||
verify(mLeakReporter, never()).dumpLeak(anyInt());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTransientGarbage_doesntDump() {
|
||||
when(mTrackedGarbage.countOldGarbage()).thenReturn(100);
|
||||
when(mTrackedGarbage.countOldGarbage()).thenReturn(GarbageMonitor.GARBAGE_ALLOWANCE + 1);
|
||||
|
||||
// Start the leak monitor. Nothing gets reported immediately.
|
||||
mGarbageMonitor.startLeakMonitor();
|
||||
mGarbageMonitor.runInspectCallback();
|
||||
mFakeExecutor.runAllReady();
|
||||
verify(mLeakReporter, never()).dumpLeak(anyInt());
|
||||
|
||||
// Garbage gets reset to 0 before the leak reporte actually gets called.
|
||||
when(mTrackedGarbage.countOldGarbage()).thenReturn(0);
|
||||
mFakeExecutor.advanceClockToLast();
|
||||
mFakeExecutor.runAllReady();
|
||||
|
||||
mGarbageMonitor.runReinspectCallback();
|
||||
|
||||
// Therefore nothing gets dumped.
|
||||
verify(mLeakReporter, never()).dumpLeak(anyInt());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLotsOfPersistentGarbage_dumps() {
|
||||
when(mTrackedGarbage.countOldGarbage()).thenReturn(100);
|
||||
when(mTrackedGarbage.countOldGarbage()).thenReturn(GarbageMonitor.GARBAGE_ALLOWANCE + 1);
|
||||
|
||||
mGarbageMonitor.startLeakMonitor();
|
||||
mGarbageMonitor.runCallbacksOnce();
|
||||
mGarbageMonitor.reinspectGarbageAfterGc();
|
||||
|
||||
verify(mLeakReporter).dumpLeak(anyInt());
|
||||
verify(mLeakReporter).dumpLeak(GarbageMonitor.GARBAGE_ALLOWANCE + 1);
|
||||
}
|
||||
|
||||
private static class TestableGarbageMonitor extends GarbageMonitor {
|
||||
public TestableGarbageMonitor(
|
||||
Context context,
|
||||
Looper looper,
|
||||
LeakDetector leakDetector,
|
||||
LeakReporter leakReporter) {
|
||||
super(context, looper, leakDetector, leakReporter);
|
||||
}
|
||||
@Test
|
||||
public void testLotsOfPersistentGarbage_dumpsAfterAtime() {
|
||||
when(mTrackedGarbage.countOldGarbage()).thenReturn(GarbageMonitor.GARBAGE_ALLOWANCE + 1);
|
||||
|
||||
void runInspectCallback() {
|
||||
startLeakMonitor();
|
||||
}
|
||||
// Start the leak monitor. Nothing gets reported immediately.
|
||||
mGarbageMonitor.startLeakMonitor();
|
||||
mFakeExecutor.runAllReady();
|
||||
verify(mLeakReporter, never()).dumpLeak(anyInt());
|
||||
|
||||
void runReinspectCallback() {
|
||||
reinspectGarbageAfterGc();
|
||||
}
|
||||
mFakeExecutor.advanceClockToLast();
|
||||
mFakeExecutor.runAllReady();
|
||||
|
||||
void runCallbacksOnce() {
|
||||
// Note that TestableLooper doesn't currently support delayed messages so we need to run
|
||||
// callbacks explicitly.
|
||||
runInspectCallback();
|
||||
runReinspectCallback();
|
||||
}
|
||||
verify(mLeakReporter).dumpLeak(GarbageMonitor.GARBAGE_ALLOWANCE + 1);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user