Merge "New Executor implementaiton for SystemUI."

This commit is contained in:
TreeHugger Robot
2019-12-05 19:48:41 +00:00
committed by Android (Google) Code Review
12 changed files with 968 additions and 47 deletions

View File

@@ -37,6 +37,7 @@ import com.android.systemui.statusbar.phone.KeyguardLiftController;
import com.android.systemui.statusbar.phone.StatusBar;
import com.android.systemui.statusbar.phone.StatusBarComponent;
import com.android.systemui.statusbar.policy.HeadsUpManager;
import com.android.systemui.util.concurrency.ConcurrencyModule;
import com.android.systemui.util.sensors.AsyncSensorManager;
import com.android.systemui.util.time.SystemClock;
import com.android.systemui.util.time.SystemClockImpl;
@@ -53,6 +54,7 @@ import dagger.Provides;
* implementation.
*/
@Module(includes = {AssistModule.class,
ConcurrencyModule.class,
PeopleHubModule.class},
subcomponents = {StatusBarComponent.class})
public abstract class SystemUIModule {

View File

@@ -0,0 +1,30 @@
/*
* 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.dagger.qualifiers;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import javax.inject.Qualifier;
@Qualifier
@Documented
@Retention(RUNTIME)
public @interface Background {
}

View File

@@ -0,0 +1,30 @@
/*
* 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.dagger.qualifiers;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import javax.inject.Qualifier;
@Qualifier
@Documented
@Retention(RUNTIME)
public @interface Main {
}

View File

@@ -25,7 +25,6 @@ import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.os.Handler;
import android.provider.Settings;
import android.service.quicksettings.Tile;
import android.service.quicksettings.TileService;
@@ -34,8 +33,8 @@ import android.util.ArraySet;
import android.widget.Button;
import com.android.systemui.R;
import com.android.systemui.dagger.qualifiers.BgHandler;
import com.android.systemui.dagger.qualifiers.MainHandler;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.plugins.qs.QSTile;
import com.android.systemui.plugins.qs.QSTile.State;
import com.android.systemui.qs.QSTileHost;
@@ -47,6 +46,7 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.Executor;
import javax.inject.Inject;
@@ -55,8 +55,8 @@ public class TileQueryHelper {
private final ArrayList<TileInfo> mTiles = new ArrayList<>();
private final ArraySet<String> mSpecs = new ArraySet<>();
private final Handler mBgHandler;
private final Handler mMainHandler;
private final Executor mMainExecutor;
private final Executor mBgExecutor;
private final Context mContext;
private TileStateListener mListener;
@@ -64,10 +64,10 @@ public class TileQueryHelper {
@Inject
public TileQueryHelper(Context context,
@MainHandler Handler mainHandler, @BgHandler Handler bgHandler) {
@Main Executor mainExecutor, @Background Executor bgExecutor) {
mContext = context;
mMainHandler = mainHandler;
mBgHandler = bgHandler;
mMainExecutor = mainExecutor;
mBgExecutor = bgExecutor;
}
public void setListener(TileStateListener listener) {
@@ -126,7 +126,7 @@ public class TileQueryHelper {
tilesToAdd.add(tile);
}
mBgHandler.post(() -> {
mBgExecutor.execute(() -> {
for (QSTile tile : tilesToAdd) {
final QSTile.State state = tile.getState().copy();
// Ignore the current state and get the generic label instead.
@@ -139,7 +139,7 @@ public class TileQueryHelper {
}
private void addPackageTiles(final QSTileHost host) {
mBgHandler.post(() -> {
mBgExecutor.execute(() -> {
Collection<QSTile> params = host.getTiles();
PackageManager pm = mContext.getPackageManager();
List<ResolveInfo> services = pm.queryIntentServicesAsUser(
@@ -185,7 +185,7 @@ public class TileQueryHelper {
private void notifyTilesChanged(final boolean finished) {
final ArrayList<TileInfo> tilesToReturn = new ArrayList<>(mTiles);
mMainHandler.post(() -> {
mMainExecutor.execute(() -> {
if (mListener != null) {
mListener.onTilesChanged(tilesToReturn);
}

View File

@@ -17,14 +17,13 @@
package com.android.systemui.statusbar;
import android.annotation.NonNull;
import android.os.Handler;
import android.os.HandlerExecutor;
import android.provider.DeviceConfig;
import android.util.ArrayMap;
import com.android.systemui.dagger.qualifiers.BgHandler;
import com.android.systemui.dagger.qualifiers.Background;
import java.util.Map;
import java.util.concurrent.Executor;
import javax.inject.Inject;
import javax.inject.Singleton;
@@ -49,10 +48,10 @@ public class FeatureFlags {
private final Map<String, Boolean> mCachedDeviceConfigFlags = new ArrayMap<>();
@Inject
public FeatureFlags(@BgHandler Handler bgHandler) {
public FeatureFlags(@Background Executor executor) {
DeviceConfig.addOnPropertiesChangedListener(
"systemui",
new HandlerExecutor(bgHandler),
executor,
this::onPropertiesChanged);
}

View File

@@ -0,0 +1,88 @@
/*
* 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 android.content.Context;
import android.os.Handler;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.BgHandler;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dagger.qualifiers.MainHandler;
import java.util.concurrent.Executor;
import dagger.Module;
import dagger.Provides;
/**
* Dagger Module for classes found within the concurrent package.
*/
@Module
public abstract class ConcurrencyModule {
/**
* Provide a Background-Thread Executor by default.
*/
@Provides
public static Executor provideExecutor(@BgHandler Handler handler) {
return new ExecutorImpl(handler);
}
/**
* Provide a Background-Thread Executor.
*/
@Provides
@Background
public static Executor provideBackgroundExecutor(@BgHandler Handler handler) {
return new ExecutorImpl(handler);
}
/**
* Provide a Main-Thread Executor.
*/
@Provides
@Main
public static Executor provideMainExecutor(Context context) {
return context.getMainExecutor();
}
/**
* Provide a Background-Thread Executor by default.
*/
@Provides
public static DelayableExecutor provideDelayableExecutor(@BgHandler Handler handler) {
return new ExecutorImpl(handler);
}
/**
* Provide a Background-Thread Executor.
*/
@Provides
@Background
public static DelayableExecutor provideBackgroundDelayableExecutor(@BgHandler Handler handler) {
return new ExecutorImpl(handler);
}
/**
* Provide a Main-Thread Executor.
*/
@Provides
@Main
public static DelayableExecutor provideMainDelayableExecutor(@MainHandler Handler handler) {
return new ExecutorImpl(handler);
}
}

View File

@@ -0,0 +1,72 @@
/*
* 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 Runnables to be delayed and/or cancelled.
*/
public interface DelayableExecutor extends Executor {
/**
* Execute supplied Runnable on the Executors thread after a specified delay.
*
* See {@link android.os.Handler#postDelayed(Runnable, long)}.
*
* @return A Runnable that, when run, removes the supplied argument from the Executor queue.
*/
default Runnable executeDelayed(Runnable r, long delayMillis) {
return executeDelayed(r, delayMillis, TimeUnit.MILLISECONDS);
}
/**
* Execute supplied Runnable on the Executors thread after a specified delay.
*
* See {@link android.os.Handler#postDelayed(Runnable, long)}.
*
* @return A Runnable that, when run, removes the supplied argument from the Executor queue..
*/
Runnable executeDelayed(Runnable r, long delay, TimeUnit unit);
/**
* Execute supplied Runnable on the Executors thread at a specified uptime.
*
* See {@link android.os.Handler#postAtTime(Runnable, long)}.
*
* @return A Runnable that, when run, removes the supplied argument from the Executor queue.
*/
default Runnable executeAtTime(Runnable r, long uptime) {
return executeAtTime(r, uptime, TimeUnit.MILLISECONDS);
}
/**
* Execute supplied Runnable on the Executors thread at a specified uptime.
*
* See {@link android.os.Handler#postAtTime(Runnable, long)}.
*
* @return A Runnable that, when run, removes the supplied argument from the Executor queue.
*/
Runnable executeAtTime(Runnable r, long uptimeMillis, TimeUnit unit);
/**
* Remove all pending Runnables.
*
*/
void removeAll();
}

View File

@@ -0,0 +1,58 @@
/*
* 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 android.os.Handler;
import android.os.HandlerExecutor;
import android.os.Message;
import java.util.concurrent.TimeUnit;
/**
* Implementations of {@link DelayableExecutor} for SystemUI.
*/
public class ExecutorImpl extends HandlerExecutor implements DelayableExecutor {
private final Handler mHandler;
public ExecutorImpl(Handler handler) {
super(handler);
mHandler = handler;
}
@Override
public Runnable executeDelayed(Runnable r, long delay, TimeUnit unit) {
Object token = new Object();
Message m = mHandler.obtainMessage(0, token);
mHandler.sendMessageDelayed(m, unit.toMillis(delay));
return () -> mHandler.removeCallbacksAndMessages(token);
}
@Override
public Runnable executeAtTime(Runnable r, long uptimeMillis, TimeUnit unit) {
Object token = new Object();
Message m = mHandler.obtainMessage(0, token);
mHandler.sendMessageAtTime(m, unit.toMillis(uptimeMillis));
return () -> mHandler.removeCallbacksAndMessages(token);
}
@Override
public void removeAll() {
mHandler.removeCallbacksAndMessages(null);
}
}

View File

@@ -36,13 +36,9 @@ import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
import android.os.Handler;
import android.os.Looper;
import android.provider.Settings;
import android.service.quicksettings.Tile;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.testing.TestableLooper.RunWithLooper;
import android.text.TextUtils;
import android.util.ArraySet;
@@ -52,6 +48,8 @@ import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.plugins.qs.QSTile;
import com.android.systemui.qs.QSTileHost;
import com.android.systemui.util.concurrency.FakeExecutor;
import com.android.systemui.util.time.FakeSystemClock;
import org.junit.Before;
import org.junit.Test;
@@ -69,7 +67,6 @@ import java.util.Set;
@SmallTest
@RunWith(AndroidTestingRunner.class)
@RunWithLooper
public class TileQueryHelperTest extends SysuiTestCase {
private static final String CURRENT_TILES = "wifi,dnd,nfc";
private static final String ONLY_STOCK_TILES = "wifi,dnd";
@@ -98,14 +95,13 @@ public class TileQueryHelperTest extends SysuiTestCase {
private ArgumentCaptor<List<TileQueryHelper.TileInfo>> mCaptor;
private QSTile.State mState;
private TestableLooper mBGLooper;
private TileQueryHelper mTileQueryHelper;
private Handler mMainHandler;
private FakeExecutor mMainExecutor;
private FakeExecutor mBgExecutor;
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
mBGLooper = TestableLooper.get(this);
mContext.setMockPackageManager(mPackageManager);
mState = new QSTile.State();
@@ -123,9 +119,11 @@ public class TileQueryHelperTest extends SysuiTestCase {
}
).when(mQSTileHost).createTile(anyString());
mMainHandler = new Handler(Looper.getMainLooper());
mTileQueryHelper = new TileQueryHelper(mContext, mMainHandler,
new Handler(mBGLooper.getLooper()));
FakeSystemClock clock = new FakeSystemClock();
clock.setAutoIncrement(false);
mMainExecutor = new FakeExecutor(clock);
mBgExecutor = new FakeExecutor(clock);
mTileQueryHelper = new TileQueryHelper(mContext, mMainExecutor, mBgExecutor);
mTileQueryHelper.setListener(mListener);
}
@@ -138,8 +136,7 @@ public class TileQueryHelperTest extends SysuiTestCase {
public void testIsFinished_trueAfterQuerying() {
mTileQueryHelper.queryTiles(mQSTileHost);
mBGLooper.processAllMessages();
waitForIdleSync(mMainHandler);
FakeExecutor.exhaustExecutors(mMainExecutor, mBgExecutor);
assertTrue(mTileQueryHelper.isFinished());
}
@@ -148,8 +145,7 @@ public class TileQueryHelperTest extends SysuiTestCase {
public void testQueryTiles_callsListenerTwice() {
mTileQueryHelper.queryTiles(mQSTileHost);
mBGLooper.processAllMessages();
waitForIdleSync(mMainHandler);
FakeExecutor.exhaustExecutors(mMainExecutor, mBgExecutor);
verify(mListener, times(2)).onTilesChanged(any());
}
@@ -163,8 +159,7 @@ public class TileQueryHelperTest extends SysuiTestCase {
mTileQueryHelper.queryTiles(mQSTileHost);
mBGLooper.processAllMessages();
waitForIdleSync(mMainHandler);
FakeExecutor.exhaustExecutors(mMainExecutor, mBgExecutor);
assertTrue(mTileQueryHelper.isFinished());
}
@@ -178,8 +173,7 @@ public class TileQueryHelperTest extends SysuiTestCase {
mTileQueryHelper.queryTiles(mQSTileHost);
mBGLooper.processAllMessages();
waitForIdleSync(mMainHandler);
FakeExecutor.exhaustExecutors(mMainExecutor, mBgExecutor);
verify(mListener, atLeastOnce()).onTilesChanged(mCaptor.capture());
List<String> specs = new ArrayList<>();
@@ -199,8 +193,7 @@ public class TileQueryHelperTest extends SysuiTestCase {
mTileQueryHelper.queryTiles(mQSTileHost);
mBGLooper.processAllMessages();
waitForIdleSync(mMainHandler);
FakeExecutor.exhaustExecutors(mMainExecutor, mBgExecutor);
verify(mListener, atLeastOnce()).onTilesChanged(mCaptor.capture());
List<String> specs = new ArrayList<>();
@@ -220,8 +213,7 @@ public class TileQueryHelperTest extends SysuiTestCase {
mTileQueryHelper.queryTiles(mQSTileHost);
mBGLooper.processAllMessages();
waitForIdleSync(mMainHandler);
FakeExecutor.exhaustExecutors(mMainExecutor, mBgExecutor);
verify(mListener, atLeastOnce()).onTilesChanged(mCaptor.capture());
List<String> specs = new ArrayList<>();
@@ -251,8 +243,7 @@ public class TileQueryHelperTest extends SysuiTestCase {
"");
mTileQueryHelper.queryTiles(mQSTileHost);
mBGLooper.processAllMessages();
waitForIdleSync(mMainHandler);
FakeExecutor.exhaustExecutors(mMainExecutor, mBgExecutor);
verify(mListener, atLeastOnce()).onTilesChanged(mCaptor.capture());
List<TileQueryHelper.TileInfo> tileInfos = mCaptor.getValue();

View File

@@ -0,0 +1,227 @@
/*
* 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 com.android.systemui.util.time.FakeSystemClock;
import com.android.systemui.util.time.FakeSystemClock.ClockTickListener;
import java.util.Collections;
import java.util.PriorityQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
public class FakeExecutor implements DelayableExecutor {
private final FakeSystemClock mClock;
private PriorityQueue<QueuedRunnable> mQueuedRunnables = new PriorityQueue<>();
private boolean mIgnoreClockUpdates;
private ClockTickListener mClockTickListener = new ClockTickListener() {
@Override
public void onUptimeMillis(long uptimeMillis) {
if (!mIgnoreClockUpdates) {
runAllReady();
}
}
};
/**
* Initializes a fake executor.
*
* @param clock FakeSystemClock allowing control over delayed runnables. It is strongly
* recommended that this clock have its auto-increment setting set to false to
* prevent unexpected advancement of the time.
*/
public FakeExecutor(FakeSystemClock clock) {
mClock = clock;
mClock.addListener(mClockTickListener);
}
/**
* Runs a single runnable if it's scheduled to run according to the internal clock.
*
* If constructed to advance the clock automatically, this will advance the clock enough to
* run the next pending item.
*
* This method does not advance the clock past the item that was run.
*
* @return Returns true if an item was run.
*/
public boolean runNextReady() {
if (!mQueuedRunnables.isEmpty() && mQueuedRunnables.peek().mWhen <= mClock.uptimeMillis()) {
mQueuedRunnables.poll().mRunnable.run();
return true;
}
return false;
}
/**
* Runs all Runnables that are scheduled to run according to the internal clock.
*
* If constructed to advance the clock automatically, this will advance the clock enough to
* run all the pending items. This method does not advance the clock past items that were
* run. It is equivalent to calling {@link #runNextReady()} in a loop.
*
* @return Returns the number of items that ran.
*/
public int runAllReady() {
int num = 0;
while (runNextReady()) {
num++;
}
return num;
}
/**
* Advances the internal clock to the next item to run.
*
* The clock will only move forward. If the next item is set to run in the past or there is no
* next item, the clock does not change.
*
* Note that this will cause one or more items to actually run.
*
* @return The delta in uptimeMillis that the clock advanced, or 0 if the clock did not advance.
*/
public long advanceClockToNext() {
if (mQueuedRunnables.isEmpty()) {
return 0;
}
long startTime = mClock.uptimeMillis();
long nextTime = mQueuedRunnables.peek().mWhen;
if (nextTime <= startTime) {
return 0;
}
updateClock(nextTime);
return nextTime - startTime;
}
/**
* Advances the internal clock to the last item to run.
*
* The clock will only move forward. If the last item is set to run in the past or there is no
* next item, the clock does not change.
*
* @return The delta in uptimeMillis that the clock advanced, or 0 if the clock did not advance.
*/
public long advanceClockToLast() {
if (mQueuedRunnables.isEmpty()) {
return 0;
}
long startTime = mClock.uptimeMillis();
long nextTime = Collections.max(mQueuedRunnables).mWhen;
if (nextTime <= startTime) {
return 0;
}
updateClock(nextTime);
return nextTime - startTime;
}
/**
* Returns the number of un-executed runnables waiting to run.
*/
public int numPending() {
return mQueuedRunnables.size();
}
@Override
public Runnable executeDelayed(Runnable r, long delay, TimeUnit unit) {
if (delay < 0) {
delay = 0;
}
return executeAtTime(r, mClock.uptimeMillis() + unit.toMillis(delay));
}
@Override
public Runnable executeAtTime(Runnable r, long uptime, TimeUnit unit) {
long uptimeMillis = unit.toMillis(uptime);
QueuedRunnable container = new QueuedRunnable(r, uptimeMillis);
mQueuedRunnables.offer(container);
return () -> mQueuedRunnables.remove(container);
}
@Override
public void execute(Runnable command) {
executeDelayed(command, 0);
}
@Override
public void removeAll() {
mQueuedRunnables.clear();
}
/**
* Run all Executors in a loop until they all report they have no ready work to do.
*
* Useful if you have Executors the post work to other Executors, and you simply want to
* run them all until they stop posting work.
*/
public static void exhaustExecutors(FakeExecutor ...executors) {
boolean didAnything;
do {
didAnything = false;
for (FakeExecutor executor : executors) {
didAnything = didAnything || executor.runAllReady() != 0;
}
} while (didAnything);
}
private void updateClock(long nextTime) {
mIgnoreClockUpdates = true;
mClock.setUptimeMillis(nextTime);
mIgnoreClockUpdates = false;
}
private static class QueuedRunnable implements Comparable<QueuedRunnable> {
private static AtomicInteger sCounter = new AtomicInteger();
Runnable mRunnable;
long mWhen;
private int mCounter;
private QueuedRunnable(Runnable r, long when) {
mRunnable = r;
mWhen = when;
// PrioirityQueue orders items arbitrarily when equal. We want to ensure that
// otherwise-equal elements are ordered according to their insertion order. Because this
// class only is constructed right before insertion, we use a static counter to track
// insertion order of otherwise equal elements.
mCounter = sCounter.incrementAndGet();
}
@Override
public int compareTo(QueuedRunnable other) {
long diff = mWhen - other.mWhen;
if (diff == 0) {
return mCounter - other.mCounter;
}
return diff > 0 ? 1 : -1;
}
}
}

View File

@@ -0,0 +1,366 @@
/*
* 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 static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
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;
import java.util.ArrayList;
import java.util.List;
import kotlin.jvm.functions.Function4;
@SmallTest
@RunWith(AndroidTestingRunner.class)
public class FakeExecutorTest extends SysuiTestCase {
@Before
public void setUp() throws Exception {
}
/**
* Test FakeExecutor that receives non-delayed items to execute.
*/
@Test
public void testNoDelay() {
FakeSystemClock clock = new FakeSystemClock();
clock.setAutoIncrement(false);
FakeExecutor fakeExecutor = new FakeExecutor(clock);
RunnableImpl runnable = new RunnableImpl();
assertEquals(0, clock.uptimeMillis());
assertEquals(0, runnable.mRunCount);
// Execute two runnables. They should not run and should be left pending.
fakeExecutor.execute(runnable);
assertEquals(0, runnable.mRunCount);
assertEquals(0, clock.uptimeMillis());
assertEquals(1, fakeExecutor.numPending());
fakeExecutor.execute(runnable);
assertEquals(0, runnable.mRunCount);
assertEquals(0, clock.uptimeMillis());
assertEquals(2, fakeExecutor.numPending());
// Run one pending runnable.
assertTrue(fakeExecutor.runNextReady());
assertEquals(1, runnable.mRunCount);
assertEquals(0, clock.uptimeMillis());
assertEquals(1, fakeExecutor.numPending());
// Run a second pending runnable.
assertTrue(fakeExecutor.runNextReady());
assertEquals(2, runnable.mRunCount);
assertEquals(0, clock.uptimeMillis());
assertEquals(0, fakeExecutor.numPending());
// No more runnables to run.
assertFalse(fakeExecutor.runNextReady());
// Add two more runnables.
fakeExecutor.execute(runnable);
fakeExecutor.execute(runnable);
assertEquals(2, runnable.mRunCount);
assertEquals(0, clock.uptimeMillis());
assertEquals(2, fakeExecutor.numPending());
// Execute all pending runnables in batch.
assertEquals(2, fakeExecutor.runAllReady());
assertEquals(4, runnable.mRunCount);
assertEquals(0, clock.uptimeMillis());
assertEquals(0, fakeExecutor.runAllReady());
}
/**
* Test FakeExecutor that is told to delay execution on items.
*/
@Test
public void testDelayed() {
FakeSystemClock clock = new FakeSystemClock();
clock.setAutoIncrement(false);
FakeExecutor fakeExecutor = new FakeExecutor(clock);
RunnableImpl runnable = new RunnableImpl();
// Add three delayed runnables.
fakeExecutor.executeDelayed(runnable, 1);
fakeExecutor.executeDelayed(runnable, 50);
fakeExecutor.executeDelayed(runnable, 100);
assertEquals(0, runnable.mRunCount);
assertEquals(0, clock.uptimeMillis());
assertEquals(3, fakeExecutor.numPending());
// Delayed runnables should not advance the clock and therefore should not run.
assertFalse(fakeExecutor.runNextReady());
assertEquals(0, fakeExecutor.runAllReady());
assertEquals(3, fakeExecutor.numPending());
// Advance the clock to the next runnable. One runnable should execute.
assertEquals(1, fakeExecutor.advanceClockToNext());
assertEquals(1, fakeExecutor.runAllReady());
assertEquals(2, fakeExecutor.numPending());
assertEquals(1, runnable.mRunCount);
// Advance the clock to the last runnable.
assertEquals(99, fakeExecutor.advanceClockToLast());
assertEquals(2, fakeExecutor.runAllReady());
// Now all remaining runnables should execute.
assertEquals(0, fakeExecutor.numPending());
assertEquals(3, runnable.mRunCount);
}
/**
* Test FakeExecutor that is told to delay execution on items.
*/
@Test
public void testDelayed_AdvanceAndRun() {
FakeSystemClock clock = new FakeSystemClock();
clock.setAutoIncrement(false);
FakeExecutor fakeExecutor = new FakeExecutor(clock);
RunnableImpl runnable = new RunnableImpl();
// Add three delayed runnables.
fakeExecutor.executeDelayed(runnable, 1);
fakeExecutor.executeDelayed(runnable, 50);
fakeExecutor.executeDelayed(runnable, 100);
assertEquals(0, runnable.mRunCount);
assertEquals(0, clock.uptimeMillis());
assertEquals(3, fakeExecutor.numPending());
// Delayed runnables should not advance the clock and therefore should not run.
assertFalse(fakeExecutor.runNextReady());
assertEquals(0, fakeExecutor.runAllReady());
assertEquals(3, fakeExecutor.numPending());
// Advance the clock to the next runnable. Check that it is run.
assertEquals(1, fakeExecutor.advanceClockToNext());
assertEquals(1, fakeExecutor.runAllReady());
assertEquals(1, clock.uptimeMillis());
assertEquals(2, fakeExecutor.numPending());
assertEquals(1, runnable.mRunCount);
assertEquals(49, fakeExecutor.advanceClockToNext());
assertEquals(1, fakeExecutor.runAllReady());
assertEquals(50, clock.uptimeMillis());
assertEquals(1, fakeExecutor.numPending());
assertEquals(2, runnable.mRunCount);
assertEquals(50, fakeExecutor.advanceClockToNext());
assertEquals(1, fakeExecutor.runAllReady());
assertEquals(100, clock.uptimeMillis());
assertEquals(0, fakeExecutor.numPending());
assertEquals(3, runnable.mRunCount);
// Nothing left to do
assertEquals(0, fakeExecutor.advanceClockToNext());
assertEquals(0, fakeExecutor.runAllReady());
assertEquals(100, clock.uptimeMillis());
assertEquals(0, fakeExecutor.numPending());
assertEquals(3, runnable.mRunCount);
}
/**
* Test execution order.
*/
@Test
public void testExecutionOrder() {
FakeSystemClock clock = new FakeSystemClock();
clock.setAutoIncrement(false);
FakeExecutor fakeExecutor = new FakeExecutor(clock);
RunnableImpl runnableA = new RunnableImpl();
RunnableImpl runnableB = new RunnableImpl();
RunnableImpl runnableC = new RunnableImpl();
RunnableImpl runnableD = new RunnableImpl();
Function4<Integer, Integer, Integer, Integer, Void> checkRunCounts =
(Integer argA, Integer argB, Integer argC, Integer argD) -> {
assertEquals("RunnableA run count wrong", argA.intValue(), runnableA.mRunCount);
assertEquals("RunnableB run count wrong", argB.intValue(), runnableB.mRunCount);
assertEquals("RunnableC run count wrong", argC.intValue(), runnableC.mRunCount);
assertEquals("RunnableD run count wrong", argD.intValue(), runnableD.mRunCount);
return null;
};
assertEquals(0, clock.uptimeMillis());
checkRunCounts.invoke(0, 0, 0, 0);
fakeExecutor.execute(runnableA);
fakeExecutor.execute(runnableB);
fakeExecutor.execute(runnableC);
fakeExecutor.execute(runnableD);
fakeExecutor.runNextReady();
checkRunCounts.invoke(1, 0, 0, 0);
fakeExecutor.runNextReady();
checkRunCounts.invoke(1, 1, 0, 0);
fakeExecutor.runNextReady();
checkRunCounts.invoke(1, 1, 1, 0);
fakeExecutor.runNextReady();
checkRunCounts.invoke(1, 1, 1, 1);
fakeExecutor.executeDelayed(runnableA, 100);
fakeExecutor.execute(runnableB);
fakeExecutor.executeDelayed(runnableC, 50);
fakeExecutor.execute(runnableD);
fakeExecutor.advanceClockToNext();
fakeExecutor.runAllReady();
checkRunCounts.invoke(1, 2, 1, 2);
fakeExecutor.advanceClockToNext();
fakeExecutor.runAllReady();
checkRunCounts.invoke(1, 2, 2, 2);
fakeExecutor.advanceClockToNext();
fakeExecutor.runAllReady();
checkRunCounts.invoke(2, 2, 2, 2);
fakeExecutor.execute(runnableA);
fakeExecutor.executeAtTime(runnableB, 0); // this is in the past!
fakeExecutor.executeAtTime(runnableC, 1000);
fakeExecutor.executeAtTime(runnableD, 500);
fakeExecutor.advanceClockToNext();
fakeExecutor.runAllReady();
checkRunCounts.invoke(3, 3, 2, 2);
fakeExecutor.advanceClockToNext();
fakeExecutor.runAllReady();
checkRunCounts.invoke(3, 3, 2, 3);
fakeExecutor.advanceClockToNext();
fakeExecutor.runAllReady();
checkRunCounts.invoke(3, 3, 3, 3);
}
/**
* Test removing a single item.
*/
@Test
public void testRemoval_single() {
FakeSystemClock clock = new FakeSystemClock();
clock.setAutoIncrement(false);
FakeExecutor fakeExecutor = new FakeExecutor(clock);
RunnableImpl runnable = new RunnableImpl();
Runnable removeFunction;
// Nothing to remove.
assertEquals(0, runnable.mRunCount);
assertEquals(0, fakeExecutor.numPending());
// Two pending items that have not yet run.
// We will try to remove the second item.
fakeExecutor.executeDelayed(runnable, 100);
removeFunction = fakeExecutor.executeDelayed(runnable, 200);
assertEquals(2, fakeExecutor.numPending());
assertEquals(0, runnable.mRunCount);
// Remove the item.
removeFunction.run();
assertEquals(1, fakeExecutor.numPending());
assertEquals(0, runnable.mRunCount);
// One item to run.
fakeExecutor.advanceClockToLast();
fakeExecutor.runAllReady();
assertEquals(0, fakeExecutor.numPending());
assertEquals(1, runnable.mRunCount);
// Nothing to remove.
removeFunction.run();
fakeExecutor.runAllReady();
assertEquals(0, fakeExecutor.numPending());
assertEquals(1, runnable.mRunCount);
}
/**
* Test removing multiple items.
*/
@Test
public void testRemoval_multi() {
FakeSystemClock clock = new FakeSystemClock();
clock.setAutoIncrement(false);
FakeExecutor fakeExecutor = new FakeExecutor(clock);
List<Runnable> removeFunctions = new ArrayList<>();
RunnableImpl runnable = new RunnableImpl();
// Nothing to remove.
assertEquals(0, runnable.mRunCount);
assertEquals(0, fakeExecutor.numPending());
// Three pending items that have not yet run.
// We will try to remove the first and third items.
removeFunctions.add(fakeExecutor.executeDelayed(runnable, 100));
fakeExecutor.executeDelayed(runnable, 200);
removeFunctions.add(fakeExecutor.executeDelayed(runnable, 300));
assertEquals(3, fakeExecutor.numPending());
assertEquals(0, runnable.mRunCount);
// Remove the items.
removeFunctions.forEach(Runnable::run);
assertEquals(1, fakeExecutor.numPending());
assertEquals(0, runnable.mRunCount);
// One item to run.
fakeExecutor.advanceClockToLast();
fakeExecutor.runAllReady();
assertEquals(0, fakeExecutor.numPending());
assertEquals(1, runnable.mRunCount);
// Nothing to remove.
removeFunctions.forEach(Runnable::run);
assertEquals(0, fakeExecutor.numPending());
assertEquals(1, runnable.mRunCount);
}
/**
* Test removing everything
*/
@Test
public void testRemoval_all() {
FakeSystemClock clock = new FakeSystemClock();
clock.setAutoIncrement(false);
FakeExecutor fakeExecutor = new FakeExecutor(clock);
RunnableImpl runnable = new RunnableImpl();
// Nothing to remove.
assertEquals(0, runnable.mRunCount);
assertEquals(0, fakeExecutor.numPending());
// Two pending items that have not yet run.
fakeExecutor.executeDelayed(runnable, 100);
fakeExecutor.executeDelayed(runnable, 200);
assertEquals(2, fakeExecutor.numPending());
assertEquals(0, runnable.mRunCount);
// Remove the items.
fakeExecutor.removeAll();
// Nothing to run
fakeExecutor.advanceClockToLast();
assertEquals(0, fakeExecutor.runAllReady());
assertEquals(0, fakeExecutor.numPending());
assertEquals(0, runnable.mRunCount);
}
private static class RunnableImpl implements Runnable {
int mRunCount;
@Override
public void run() {
mRunCount++;
}
}
}

View File

@@ -16,6 +16,9 @@
package com.android.systemui.util.time;
import java.util.ArrayList;
import java.util.List;
public class FakeSystemClock implements SystemClock {
private boolean mAutoIncrement = true;
@@ -26,11 +29,13 @@ public class FakeSystemClock implements SystemClock {
private long mCurrentThreadTimeMicro;
private long mCurrentTimeMicro;
List<ClockTickListener> mListeners = new ArrayList<>();
@Override
public long uptimeMillis() {
long value = mUptimeMillis;
if (mAutoIncrement) {
mUptimeMillis++;
setUptimeMillis(mUptimeMillis + 1);
}
return value;
}
@@ -39,7 +44,7 @@ public class FakeSystemClock implements SystemClock {
public long elapsedRealtime() {
long value = mElapsedRealtime;
if (mAutoIncrement) {
mElapsedRealtime++;
setElapsedRealtime(mElapsedRealtime + 1);
}
return value;
}
@@ -48,7 +53,7 @@ public class FakeSystemClock implements SystemClock {
public long elapsedRealtimeNanos() {
long value = mElapsedRealtimeNanos;
if (mAutoIncrement) {
mElapsedRealtimeNanos++;
setElapsedRealtimeNanos(mElapsedRealtimeNanos + 1);
}
return value;
}
@@ -57,7 +62,7 @@ public class FakeSystemClock implements SystemClock {
public long currentThreadTimeMillis() {
long value = mCurrentThreadTimeMillis;
if (mAutoIncrement) {
mCurrentThreadTimeMillis++;
setCurrentThreadTimeMillis(mCurrentThreadTimeMillis + 1);
}
return value;
}
@@ -66,7 +71,7 @@ public class FakeSystemClock implements SystemClock {
public long currentThreadTimeMicro() {
long value = mCurrentThreadTimeMicro;
if (mAutoIncrement) {
mCurrentThreadTimeMicro++;
setCurrentThreadTimeMicro(mCurrentThreadTimeMicro + 1);
}
return value;
}
@@ -75,37 +80,90 @@ public class FakeSystemClock implements SystemClock {
public long currentTimeMicro() {
long value = mCurrentTimeMicro;
if (mAutoIncrement) {
mCurrentTimeMicro++;
setCurrentTimeMicro(mCurrentTimeMicro + 1);
}
return value;
}
public void setUptimeMillis(long uptimeMillis) {
mUptimeMillis = uptimeMillis;
for (ClockTickListener listener : mListeners) {
listener.onUptimeMillis(mUptimeMillis);
}
}
public void setElapsedRealtime(long elapsedRealtime) {
mElapsedRealtime = elapsedRealtime;
for (ClockTickListener listener : mListeners) {
listener.onElapsedRealtime(mElapsedRealtime);
}
}
public void setElapsedRealtimeNanos(long elapsedRealtimeNanos) {
mElapsedRealtimeNanos = elapsedRealtimeNanos;
for (ClockTickListener listener : mListeners) {
listener.onElapsedRealtimeNanos(mElapsedRealtimeNanos);
}
}
public void setCurrentThreadTimeMillis(long currentThreadTimeMillis) {
mCurrentThreadTimeMillis = currentThreadTimeMillis;
for (ClockTickListener listener : mListeners) {
listener.onCurrentThreadTimeMillis(mCurrentThreadTimeMillis);
}
}
public void setCurrentThreadTimeMicro(long currentThreadTimeMicro) {
mCurrentThreadTimeMicro = currentThreadTimeMicro;
for (ClockTickListener listener : mListeners) {
listener.onCurrentThreadTimeMicro(mCurrentThreadTimeMicro);
}
}
public void setCurrentTimeMicro(long currentTimeMicro) {
mCurrentTimeMicro = currentTimeMicro;
for (ClockTickListener listener : mListeners) {
listener.onCurrentTimeMicro(mCurrentTimeMicro);
}
}
/** If true, each call to get____ will be one higher than the previous call to that method. */
public void setAutoIncrement(boolean autoIncrement) {
mAutoIncrement = autoIncrement;
}
public void addListener(ClockTickListener listener) {
mListeners.add(listener);
}
public void removeListener(ClockTickListener listener) {
mListeners.remove(listener);
}
/** Alert all the listeners about the current time. */
public void synchronizeListeners() {
for (ClockTickListener listener : mListeners) {
listener.onUptimeMillis(mUptimeMillis);
listener.onElapsedRealtime(mElapsedRealtime);
listener.onElapsedRealtimeNanos(mElapsedRealtimeNanos);
listener.onCurrentThreadTimeMillis(mCurrentThreadTimeMillis);
listener.onCurrentThreadTimeMicro(mCurrentThreadTimeMicro);
listener.onCurrentTimeMicro(mCurrentTimeMicro);
}
}
public interface ClockTickListener {
default void onUptimeMillis(long uptimeMillis) {}
default void onElapsedRealtime(long elapsedRealtime) {}
default void onElapsedRealtimeNanos(long elapsedRealtimeNanos) {}
default void onCurrentThreadTimeMillis(long currentThreadTimeMillis) {}
default void onCurrentThreadTimeMicro(long currentThreadTimeMicro) {}
default void onCurrentTimeMicro(long currentTimeMicro) {}
}
}