Merge "Adding executor with repeat functionality" into rvc-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
3eb14956fd
@@ -136,6 +136,36 @@ public abstract class ConcurrencyModule {
|
||||
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.
|
||||
*
|
||||
|
||||
@@ -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