Merge "Adding executor with repeat functionality" into rvc-dev am: 3eb14956fd am: 69aab16963
Change-Id: I6284262085ef25717808aaa5aedb9a1f315c0857
This commit is contained in:
@@ -136,6 +136,36 @@ public abstract class ConcurrencyModule {
|
|||||||
return new ExecutorImpl(looper);
|
return new ExecutorImpl(looper);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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.
|
* Provide an Executor specifically for running UI operations on a separate thread.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user