diff --git a/packages/SystemUI/src/com/android/systemui/util/Assert.java b/packages/SystemUI/src/com/android/systemui/util/Assert.java index 3f05657ed09ec..e08936cbf75e7 100644 --- a/packages/SystemUI/src/com/android/systemui/util/Assert.java +++ b/packages/SystemUI/src/com/android/systemui/util/Assert.java @@ -25,16 +25,21 @@ import androidx.annotation.VisibleForTesting; */ public class Assert { private static final Looper sMainLooper = Looper.getMainLooper(); - private static Looper sTestLooper = null; + private static Thread sTestThread = null; @VisibleForTesting public static void setTestableLooper(Looper testLooper) { - sTestLooper = testLooper; + setTestThread(testLooper == null ? null : testLooper.getThread()); + } + + @VisibleForTesting + public static void setTestThread(Thread thread) { + sTestThread = thread; } public static void isMainThread() { if (!sMainLooper.isCurrentThread() - && (sTestLooper == null || !sTestLooper.isCurrentThread())) { + && (sTestThread == null || sTestThread != Thread.currentThread())) { throw new IllegalStateException("should be called from the main thread." + " sMainLooper.threadName=" + sMainLooper.getThread().getName() + " Thread.currentThread()=" + Thread.currentThread().getName()); @@ -43,7 +48,7 @@ public class Assert { public static void isNotMainThread() { if (sMainLooper.isCurrentThread() - && (sTestLooper == null || sTestLooper.isCurrentThread())) { + && (sTestThread == null || sTestThread == Thread.currentThread())) { throw new IllegalStateException("should not be called from the main thread."); } } diff --git a/packages/SystemUI/src/com/android/systemui/util/concurrency/ConcurrencyModule.java b/packages/SystemUI/src/com/android/systemui/util/concurrency/ConcurrencyModule.java index 7729965b56c4e..bf22a9897d16e 100644 --- a/packages/SystemUI/src/com/android/systemui/util/concurrency/ConcurrencyModule.java +++ b/packages/SystemUI/src/com/android/systemui/util/concurrency/ConcurrencyModule.java @@ -32,6 +32,7 @@ import java.util.concurrent.Executors; import javax.inject.Singleton; +import dagger.Binds; import dagger.Module; import dagger.Provides; @@ -199,4 +200,10 @@ public abstract class ConcurrencyModule { public static Executor provideUiBackgroundExecutor() { return Executors.newSingleThreadExecutor(); } + + /** + * Binds {@link ThreadFactoryImpl} to {@link ThreadFactory}. + */ + @Binds + public abstract ThreadFactory bindExecutorFactory(ThreadFactoryImpl impl); } diff --git a/packages/SystemUI/src/com/android/systemui/util/concurrency/ThreadFactory.java b/packages/SystemUI/src/com/android/systemui/util/concurrency/ThreadFactory.java new file mode 100644 index 0000000000000..0352fb51bc214 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/util/concurrency/ThreadFactory.java @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2020 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.concurrent.Executor; + +/** + * Factory for building Executors running on a unique named thread. + * + * Use this when our generally available @Main, @Background, @UiBackground, @LongRunning, or + * similar global qualifiers don't quite cut it. Note that the methods here create entirely new + * threads; there are no singletons here. Use responsibly. + */ +public interface ThreadFactory { + /** + * Return an {@link java.util.concurrent.Executor} running on a named thread. + * + * The thread is implicitly started and may be left running indefinitely, depending on the + * implementation. Assume this is the case and use responsibly. + **/ + Executor buildExecutorOnNewThread(String threadName); + + /** + * Return an {@link DelayableExecutor} running on a named thread. + * + * The thread is implicitly started and may be left running indefinitely, depending on the + * implementation. Assume this is the case and use responsibly. + **/ + DelayableExecutor buildDelayableExecutorOnNewThread(String threadName); +} diff --git a/packages/SystemUI/src/com/android/systemui/util/concurrency/ThreadFactoryImpl.java b/packages/SystemUI/src/com/android/systemui/util/concurrency/ThreadFactoryImpl.java new file mode 100644 index 0000000000000..ca8d836076343 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/util/concurrency/ThreadFactoryImpl.java @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2020 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 android.os.HandlerThread; + +import java.util.concurrent.Executor; + +import javax.inject.Inject; + +class ThreadFactoryImpl implements ThreadFactory { + @Inject + ThreadFactoryImpl() {} + + public Executor buildExecutorOnNewThread(String threadName) { + return buildDelayableExecutorOnNewThread(threadName); + } + + public DelayableExecutor buildDelayableExecutorOnNewThread(String threadName) { + HandlerThread handlerThread = new HandlerThread(threadName); + handlerThread.start(); + return new ExecutorImpl(handlerThread.getLooper()); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/util/sensors/AsyncSensorManager.java b/packages/SystemUI/src/com/android/systemui/util/sensors/AsyncSensorManager.java index 450336a6b73bb..ed4df175b1a63 100644 --- a/packages/SystemUI/src/com/android/systemui/util/sensors/AsyncSensorManager.java +++ b/packages/SystemUI/src/com/android/systemui/util/sensors/AsyncSensorManager.java @@ -25,18 +25,18 @@ import android.hardware.SensorEventListener; import android.hardware.SensorManager; import android.hardware.TriggerEventListener; import android.os.Handler; -import android.os.HandlerThread; import android.os.MemoryFile; import android.util.Log; -import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.Preconditions; import com.android.systemui.plugins.PluginListener; import com.android.systemui.plugins.SensorManagerPlugin; import com.android.systemui.shared.plugins.PluginManager; +import com.android.systemui.util.concurrency.ThreadFactory; import java.util.ArrayList; import java.util.List; +import java.util.concurrent.Executor; import javax.inject.Inject; import javax.inject.Singleton; @@ -56,25 +56,14 @@ public class AsyncSensorManager extends SensorManager private final SensorManager mInner; private final List mSensorCache; - private final Handler mHandler; + private final Executor mExecutor; private final List mPlugins; @Inject - public AsyncSensorManager(SensorManager sensorManager, PluginManager pluginManager) { - this(sensorManager, pluginManager, null); - } - - @VisibleForTesting - public AsyncSensorManager( - SensorManager sensorManager, PluginManager pluginManager, Handler handler) { + public AsyncSensorManager(SensorManager sensorManager, ThreadFactory threadFactory, + PluginManager pluginManager) { mInner = sensorManager; - if (handler == null) { - HandlerThread handlerThread = new HandlerThread("async_sensor"); - handlerThread.start(); - mHandler = new Handler(handlerThread.getLooper()); - } else { - mHandler = handler; - } + mExecutor = threadFactory.buildExecutorOnNewThread("async_sensor"); mSensorCache = mInner.getSensorList(Sensor.TYPE_ALL); mPlugins = new ArrayList<>(); if (pluginManager != null) { @@ -97,7 +86,7 @@ public class AsyncSensorManager extends SensorManager protected boolean registerListenerImpl(SensorEventListener listener, Sensor sensor, int delayUs, Handler handler, int maxReportLatencyUs, int reservedFlags) { - mHandler.post(() -> { + mExecutor.execute(() -> { if (!mInner.registerListener(listener, sensor, delayUs, maxReportLatencyUs, handler)) { Log.e(TAG, "Registering " + listener + " for " + sensor + " failed."); } @@ -129,12 +118,12 @@ public class AsyncSensorManager extends SensorManager @Override protected void registerDynamicSensorCallbackImpl(DynamicSensorCallback callback, Handler handler) { - mHandler.post(() -> mInner.registerDynamicSensorCallback(callback, handler)); + mExecutor.execute(() -> mInner.registerDynamicSensorCallback(callback, handler)); } @Override protected void unregisterDynamicSensorCallbackImpl(DynamicSensorCallback callback) { - mHandler.post(() -> mInner.unregisterDynamicSensorCallback(callback)); + mExecutor.execute(() -> mInner.unregisterDynamicSensorCallback(callback)); } @Override @@ -145,7 +134,7 @@ public class AsyncSensorManager extends SensorManager if (sensor == null) { throw new IllegalArgumentException("sensor cannot be null"); } - mHandler.post(() -> { + mExecutor.execute(() -> { if (!mInner.requestTriggerSensor(listener, sensor)) { Log.e(TAG, "Requesting " + listener + " for " + sensor + " failed."); } @@ -158,7 +147,7 @@ public class AsyncSensorManager extends SensorManager boolean disable) { Preconditions.checkArgument(disable); - mHandler.post(() -> { + mExecutor.execute(() -> { if (!mInner.cancelTriggerSensor(listener, sensor)) { Log.e(TAG, "Canceling " + listener + " for " + sensor + " failed."); } @@ -178,7 +167,7 @@ public class AsyncSensorManager extends SensorManager Log.w(TAG, "No plugins registered"); return false; } - mHandler.post(() -> { + mExecutor.execute(() -> { for (int i = 0; i < mPlugins.size(); i++) { mPlugins.get(i).registerListener(sensor, listener); } @@ -194,7 +183,7 @@ public class AsyncSensorManager extends SensorManager */ public void unregisterPluginListener(SensorManagerPlugin.Sensor sensor, SensorManagerPlugin.SensorEventListener listener) { - mHandler.post(() -> { + mExecutor.execute(() -> { for (int i = 0; i < mPlugins.size(); i++) { mPlugins.get(i).unregisterListener(sensor, listener); } @@ -214,14 +203,14 @@ public class AsyncSensorManager extends SensorManager @Override protected boolean setOperationParameterImpl(SensorAdditionalInfo parameter) { - mHandler.post(() -> mInner.setOperationParameter(parameter)); + mExecutor.execute(() -> mInner.setOperationParameter(parameter)); return true; } @Override protected void unregisterListenerImpl(SensorEventListener listener, Sensor sensor) { - mHandler.post(() -> { + mExecutor.execute(() -> { if (sensor == null) { mInner.unregisterListener(listener); } else { diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java index 655f933d28fe4..d259d3685ea6d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java @@ -30,9 +30,7 @@ import static org.mockito.Mockito.when; import android.app.AlarmManager; import android.hardware.Sensor; import android.hardware.display.AmbientDisplayConfiguration; -import android.os.Handler; import android.testing.AndroidTestingRunner; -import android.testing.TestableLooper; import android.testing.TestableLooper.RunWithLooper; import android.view.Display; @@ -43,6 +41,7 @@ import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.dock.DockManager; import com.android.systemui.statusbar.phone.DozeParameters; import com.android.systemui.util.concurrency.FakeExecutor; +import com.android.systemui.util.concurrency.FakeThreadFactory; import com.android.systemui.util.sensors.AsyncSensorManager; import com.android.systemui.util.sensors.FakeProximitySensor; import com.android.systemui.util.sensors.FakeSensorManager; @@ -92,7 +91,7 @@ public class DozeTriggersTest extends SysuiTestCase { mTapSensor = mSensors.getFakeTapSensor().getSensor(); WakeLock wakeLock = new WakeLockFake(); AsyncSensorManager asyncSensorManager = - new AsyncSensorManager(mSensors, null, new Handler()); + new AsyncSensorManager(mSensors, new FakeThreadFactory(mExecutor), null); FakeThresholdSensor thresholdSensor = new FakeThresholdSensor(); thresholdSensor.setLoaded(true); @@ -186,6 +185,6 @@ public class DozeTriggersTest extends SysuiTestCase { } private void waitForSensorManager() { - TestableLooper.get(this).processAllMessages(); + mExecutor.runAllReady(); } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/concurrency/FakeThreadFactory.java b/packages/SystemUI/tests/src/com/android/systemui/util/concurrency/FakeThreadFactory.java new file mode 100644 index 0000000000000..8c92482220145 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/util/concurrency/FakeThreadFactory.java @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2020 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.concurrent.Executor; + +/** + * Implementation of {@link ThreadFactory} that returns {@link FakeExecutor} where it can. + */ +public class FakeThreadFactory implements ThreadFactory { + private final FakeExecutor mFakeExecutor; + + public FakeThreadFactory(FakeExecutor fakeExecutor) { + mFakeExecutor = fakeExecutor; + } + + @Override + public Executor buildExecutorOnNewThread(String threadName) { + return mFakeExecutor; + } + + @Override + public DelayableExecutor buildDelayableExecutorOnNewThread(String threadName) { + return mFakeExecutor; + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/sensors/AsyncSensorManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/sensors/AsyncSensorManagerTest.java index 9149599f2c7c4..0d8dd2c0f1409 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/util/sensors/AsyncSensorManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/util/sensors/AsyncSensorManagerTest.java @@ -23,15 +23,16 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; import android.hardware.SensorEventListener; -import android.os.Handler; import android.testing.AndroidTestingRunner; -import android.testing.TestableLooper; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; import com.android.systemui.plugins.SensorManagerPlugin; import com.android.systemui.shared.plugins.PluginManager; +import com.android.systemui.util.concurrency.FakeExecutor; +import com.android.systemui.util.concurrency.FakeThreadFactory; +import com.android.systemui.util.time.FakeSystemClock; import org.junit.Before; import org.junit.Test; @@ -39,20 +40,20 @@ import org.junit.runner.RunWith; @SmallTest @RunWith(AndroidTestingRunner.class) -@TestableLooper.RunWithLooper public class AsyncSensorManagerTest extends SysuiTestCase { private AsyncSensorManager mAsyncSensorManager; private SensorEventListener mListener; private FakeSensorManager.FakeProximitySensor mSensor; private PluginManager mPluginManager; + private FakeExecutor mFakeExecutor = new FakeExecutor(new FakeSystemClock()); @Before public void setUp() throws Exception { mPluginManager = mock(PluginManager.class); FakeSensorManager fakeSensorManager = new FakeSensorManager(mContext); mAsyncSensorManager = new AsyncSensorManager( - fakeSensorManager, mPluginManager, new Handler()); + fakeSensorManager, new FakeThreadFactory(mFakeExecutor), mPluginManager); mSensor = fakeSensorManager.getFakeProximitySensor(); mListener = mock(SensorEventListener.class); } @@ -99,6 +100,6 @@ public class AsyncSensorManagerTest extends SysuiTestCase { } public void waitUntilRequestsCompleted() { - TestableLooper.get(this).processAllMessages(); + mFakeExecutor.runAllReady(); } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/sensors/ThresholdSensorImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/sensors/ThresholdSensorImplTest.java index 8ba7d62ba843c..d3a35a735f6d6 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/util/sensors/ThresholdSensorImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/util/sensors/ThresholdSensorImplTest.java @@ -20,12 +20,14 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; -import android.os.Handler; import android.test.suitebuilder.annotation.SmallTest; import android.testing.AndroidTestingRunner; -import android.testing.TestableLooper; import com.android.systemui.SysuiTestCase; +import com.android.systemui.util.Assert; +import com.android.systemui.util.concurrency.FakeExecutor; +import com.android.systemui.util.concurrency.FakeThreadFactory; +import com.android.systemui.util.time.FakeSystemClock; import org.junit.Before; import org.junit.Test; @@ -33,21 +35,20 @@ import org.junit.runner.RunWith; @SmallTest @RunWith(AndroidTestingRunner.class) -@TestableLooper.RunWithLooper public class ThresholdSensorImplTest extends SysuiTestCase { private ThresholdSensorImpl mThresholdSensor; private FakeSensorManager mSensorManager; private AsyncSensorManager mAsyncSensorManager; private FakeSensorManager.FakeProximitySensor mFakeProximitySensor; + private FakeExecutor mFakeExecutor = new FakeExecutor(new FakeSystemClock()); @Before public void setUp() throws Exception { - allowTestableLooperAsMainThread(); mSensorManager = new FakeSensorManager(getContext()); mAsyncSensorManager = new AsyncSensorManager( - mSensorManager, null, new Handler()); + mSensorManager, new FakeThreadFactory(mFakeExecutor), null); mFakeProximitySensor = mSensorManager.getFakeProximitySensor(); ThresholdSensorImpl.Builder thresholdSensorBuilder = new ThresholdSensorImpl.Builder( @@ -60,6 +61,7 @@ public class ThresholdSensorImplTest extends SysuiTestCase { @Test public void testSingleListener() { + Assert.setTestThread(Thread.currentThread()); TestableListener listener = new TestableListener(); assertFalse(mThresholdSensor.isRegistered()); @@ -81,6 +83,7 @@ public class ThresholdSensorImplTest extends SysuiTestCase { @Test public void testMultiListener() { + Assert.setTestThread(Thread.currentThread()); TestableListener listenerA = new TestableListener(); TestableListener listenerB = new TestableListener(); @@ -114,6 +117,7 @@ public class ThresholdSensorImplTest extends SysuiTestCase { @Test public void testDuplicateListener() { + Assert.setTestThread(Thread.currentThread()); TestableListener listenerA = new TestableListener(); assertFalse(mThresholdSensor.isRegistered()); @@ -138,6 +142,7 @@ public class ThresholdSensorImplTest extends SysuiTestCase { } @Test public void testUnregister() { + Assert.setTestThread(Thread.currentThread()); TestableListener listener = new TestableListener(); assertFalse(mThresholdSensor.isRegistered()); @@ -157,6 +162,7 @@ public class ThresholdSensorImplTest extends SysuiTestCase { @Test public void testPauseAndResume() { + Assert.setTestThread(Thread.currentThread()); TestableListener listener = new TestableListener(); assertFalse(mThresholdSensor.isRegistered()); @@ -199,6 +205,7 @@ public class ThresholdSensorImplTest extends SysuiTestCase { @Test public void testAlertListeners() { + Assert.setTestThread(Thread.currentThread()); TestableListener listenerA = new TestableListener(); TestableListener listenerB = new TestableListener(); @@ -230,6 +237,7 @@ public class ThresholdSensorImplTest extends SysuiTestCase { @Test public void testHysteresis() { + Assert.setTestThread(Thread.currentThread()); float lowValue = 10f; float highValue = 100f; FakeSensorManager.FakeGenericSensor sensor = mSensorManager.getFakeLightSensor(); @@ -278,6 +286,7 @@ public class ThresholdSensorImplTest extends SysuiTestCase { @Test public void testAlertAfterPause() { + Assert.setTestThread(Thread.currentThread()); TestableListener listener = new TestableListener(); mThresholdSensor.register(listener); @@ -307,7 +316,7 @@ public class ThresholdSensorImplTest extends SysuiTestCase { } private void waitForSensorManager() { - TestableLooper.get(this).processAllMessages(); + mFakeExecutor.runAllReady(); } }