diff options
| author | TreeHugger Robot <treehugger-gerrit@google.com> | 2020-04-21 22:07:54 +0000 |
|---|---|---|
| committer | Android (Google) Code Review <android-gerrit@google.com> | 2020-04-21 22:07:54 +0000 |
| commit | 3eb14956fdbd06c017ca84eaaec8ee0334445fe5 (patch) | |
| tree | 93bfabfd213f3ab6ea0f56bd440142cbb0f8983b | |
| parent | 4a25277da6278c68ede5d0292e30632ed2e9ef03 (diff) | |
| parent | 05e51c1d5b1d6937d88f5a2f490cf0bff538f550 (diff) | |
Merge "Adding executor with repeat functionality" into rvc-dev
4 files changed, 330 insertions, 0 deletions
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 cc6d607a60cf..8acfbf2b6996 100644 --- a/packages/SystemUI/src/com/android/systemui/util/concurrency/ConcurrencyModule.java +++ b/packages/SystemUI/src/com/android/systemui/util/concurrency/ConcurrencyModule.java @@ -137,6 +137,36 @@ public abstract class ConcurrencyModule { } /** + * Provide a Background-Thread Executor by default. + */ + @Provides + @Singleton + public static RepeatableExecutor provideRepeatableExecutor(@Background DelayableExecutor exec) { + return new RepeatableExecutorImpl(exec); + } + + /** + * Provide a Background-Thread Executor. + */ + @Provides + @Singleton + @Background + public static RepeatableExecutor provideBackgroundRepeatableExecutor( + @Background DelayableExecutor exec) { + return new RepeatableExecutorImpl(exec); + } + + /** + * Provide a Main-Thread Executor. + */ + @Provides + @Singleton + @Main + public static RepeatableExecutor provideMainRepeatableExecutor(@Main DelayableExecutor exec) { + return new RepeatableExecutorImpl(exec); + } + + /** * Provide an Executor specifically for running UI operations on a separate thread. * * Keep submitted runnables short and to the point, just as with any other UI code. diff --git a/packages/SystemUI/src/com/android/systemui/util/concurrency/RepeatableExecutor.java b/packages/SystemUI/src/com/android/systemui/util/concurrency/RepeatableExecutor.java new file mode 100644 index 000000000000..aefdc992e831 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/util/concurrency/RepeatableExecutor.java @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2019 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; +import java.util.concurrent.TimeUnit; + +/** + * A sub-class of {@link Executor} that allows scheduling commands to execute periodically. + */ +public interface RepeatableExecutor extends Executor { + + /** + * Execute supplied Runnable on the Executors thread after initial delay, and subsequently with + * the given delay between the termination of one execution and the commencement of the next. + * + * Each invocation of the supplied Runnable will be scheduled after the previous invocation + * completes. For example, if you schedule the Runnable with a 60 second delay, and the Runnable + * itself takes 1 second, the effective delay will be 61 seconds between each invocation. + * + * See {@link java.util.concurrent.ScheduledExecutorService#scheduleRepeatedly(Runnable, + * long, long)} + * + * @return A Runnable that, when run, removes the supplied argument from the Executor queue. + */ + default Runnable executeRepeatedly(Runnable r, long initialDelayMillis, long delayMillis) { + return executeRepeatedly(r, initialDelayMillis, delayMillis, TimeUnit.MILLISECONDS); + } + + /** + * Execute supplied Runnable on the Executors thread after initial delay, and subsequently with + * the given delay between the termination of one execution and the commencement of the next.. + * + * See {@link java.util.concurrent.ScheduledExecutorService#scheduleRepeatedly(Runnable, + * long, long)} + * + * @return A Runnable that, when run, removes the supplied argument from the Executor queue. + */ + Runnable executeRepeatedly(Runnable r, long initialDelay, long delay, TimeUnit unit); +} diff --git a/packages/SystemUI/src/com/android/systemui/util/concurrency/RepeatableExecutorImpl.java b/packages/SystemUI/src/com/android/systemui/util/concurrency/RepeatableExecutorImpl.java new file mode 100644 index 000000000000..c03e10e5c981 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/util/concurrency/RepeatableExecutorImpl.java @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2019 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.TimeUnit; + +/** + * Implementation of {@link RepeatableExecutor} for SystemUI. + */ +class RepeatableExecutorImpl implements RepeatableExecutor { + + private final DelayableExecutor mExecutor; + + RepeatableExecutorImpl(DelayableExecutor executor) { + mExecutor = executor; + } + + @Override + public void execute(Runnable command) { + mExecutor.execute(command); + } + + @Override + public Runnable executeRepeatedly(Runnable r, long initDelay, long delay, TimeUnit unit) { + ExecutionToken token = new ExecutionToken(r, delay, unit); + token.start(initDelay, unit); + return token::cancel; + } + + private class ExecutionToken implements Runnable { + private final Runnable mCommand; + private final long mDelay; + private final TimeUnit mUnit; + private final Object mLock = new Object(); + private Runnable mCancel; + + ExecutionToken(Runnable r, long delay, TimeUnit unit) { + mCommand = r; + mDelay = delay; + mUnit = unit; + } + + @Override + public void run() { + mCommand.run(); + synchronized (mLock) { + if (mCancel != null) { + mCancel = mExecutor.executeDelayed(this, mDelay, mUnit); + } + } + } + + /** Starts execution that will repeat the command until {@link cancel}. */ + public void start(long startDelay, TimeUnit unit) { + synchronized (mLock) { + mCancel = mExecutor.executeDelayed(this, startDelay, unit); + } + } + + /** Cancel repeated execution of command. */ + public void cancel() { + synchronized (mLock) { + if (mCancel != null) { + mCancel.run(); + mCancel = null; + } + } + } + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/concurrency/RepeatableExecutorTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/concurrency/RepeatableExecutorTest.java new file mode 100644 index 000000000000..00f37ae6f6cb --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/util/concurrency/RepeatableExecutorTest.java @@ -0,0 +1,162 @@ +/* + * 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 static com.google.common.truth.Truth.assertThat; + +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; + +@SmallTest +@RunWith(AndroidTestingRunner.class) +public class RepeatableExecutorTest extends SysuiTestCase { + + private static final int DELAY = 100; + + private FakeSystemClock mFakeClock; + private FakeExecutor mFakeExecutor; + private RepeatableExecutor mExecutor; + private CountingTask mCountingTask; + + @Before + public void setUp() throws Exception { + mFakeClock = new FakeSystemClock(); + mFakeExecutor = new FakeExecutor(mFakeClock); + mCountingTask = new CountingTask(); + mExecutor = new RepeatableExecutorImpl(mFakeExecutor); + } + + /** + * Test FakeExecutor that receives non-delayed items to execute. + */ + @Test + public void testExecute() { + mExecutor.execute(mCountingTask); + mFakeExecutor.runAllReady(); + assertThat(mCountingTask.getCount()).isEqualTo(1); + } + + @Test + public void testRepeats() { + // GIVEN that a command is queued to repeat + mExecutor.executeRepeatedly(mCountingTask, DELAY, DELAY); + // WHEN The clock advances and the task is run + mFakeExecutor.advanceClockToNext(); + mFakeExecutor.runAllReady(); + // THEN another task is queued + assertThat(mCountingTask.getCount()).isEqualTo(1); + assertThat(mFakeExecutor.numPending()).isEqualTo(1); + } + + @Test + public void testNoExecutionBeforeStartDelay() { + // WHEN a command is queued with a start delay + mExecutor.executeRepeatedly(mCountingTask, 2 * DELAY, DELAY); + mFakeExecutor.runAllReady(); + // THEN then it doesn't run immediately + assertThat(mCountingTask.getCount()).isEqualTo(0); + assertThat(mFakeExecutor.numPending()).isEqualTo(1); + } + + @Test + public void testExecuteAfterStartDelay() { + // GIVEN that a command is queued to repeat with a longer start delay + mExecutor.executeRepeatedly(mCountingTask, 2 * DELAY, DELAY); + // WHEN the clock advances the start delay + mFakeClock.advanceTime(2 * DELAY); + mFakeExecutor.runAllReady(); + // THEN the command has run and another task is queued + assertThat(mCountingTask.getCount()).isEqualTo(1); + assertThat(mFakeExecutor.numPending()).isEqualTo(1); + } + + @Test + public void testExecuteWithZeroStartDelay() { + // WHEN a command is queued with no start delay + mExecutor.executeRepeatedly(mCountingTask, 0L, DELAY); + mFakeExecutor.runAllReady(); + // THEN the command has run and another task is queued + assertThat(mCountingTask.getCount()).isEqualTo(1); + assertThat(mFakeExecutor.numPending()).isEqualTo(1); + } + + @Test + public void testAdvanceTimeTwice() { + // GIVEN that a command is queued to repeat + mExecutor.executeRepeatedly(mCountingTask, DELAY, DELAY); + // WHEN the clock advances the time DELAY twice + mFakeClock.advanceTime(DELAY); + mFakeExecutor.runAllReady(); + mFakeClock.advanceTime(DELAY); + mFakeExecutor.runAllReady(); + // THEN the command has run twice and another task is queued + assertThat(mCountingTask.getCount()).isEqualTo(2); + assertThat(mFakeExecutor.numPending()).isEqualTo(1); + } + + @Test + public void testCancel() { + // GIVEN that a scheduled command has been cancelled + Runnable cancel = mExecutor.executeRepeatedly(mCountingTask, DELAY, DELAY); + cancel.run(); + // WHEN the clock advances the time DELAY + mFakeClock.advanceTime(DELAY); + mFakeExecutor.runAllReady(); + // THEN the comamnd has not run and no further tasks are queued + assertThat(mCountingTask.getCount()).isEqualTo(0); + assertThat(mFakeExecutor.numPending()).isEqualTo(0); + } + + @Test + public void testCancelAfterStart() { + // GIVEN that a command has reapeated a few times + Runnable cancel = mExecutor.executeRepeatedly(mCountingTask, DELAY, DELAY); + mFakeClock.advanceTime(DELAY); + mFakeExecutor.runAllReady(); + // WHEN cancelled and time advances + cancel.run(); + // THEN the command has only run the first time + assertThat(mCountingTask.getCount()).isEqualTo(1); + assertThat(mFakeExecutor.numPending()).isEqualTo(0); + } + + /** + * Runnable used for testing that counts the number of times run() is invoked. + */ + private static class CountingTask implements Runnable { + + private int mRunCount; + + @Override + public void run() { + mRunCount++; + } + + /** Gets the run count. */ + public int getCount() { + return mRunCount; + } + } +} |
