Merge "Adding executor with repeat functionality" into rvc-dev

This commit is contained in:
TreeHugger Robot
2020-04-21 22:07:54 +00:00
committed by Android (Google) Code Review
4 changed files with 330 additions and 0 deletions

View File

@@ -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.
*

View File

@@ -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);
}

View File

@@ -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;
}
}
}
}
}

View File

@@ -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;
}
}
}