Merge "Revert "[People Service] Add throttling to DataManager add / change listener"" into tm-qpr-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
9c81deab7b
@@ -113,7 +113,6 @@ public class DataManager {
|
|||||||
private static final long USAGE_STATS_QUERY_INTERVAL_SEC = 120L;
|
private static final long USAGE_STATS_QUERY_INTERVAL_SEC = 120L;
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
static final int MAX_CACHED_RECENT_SHORTCUTS = 30;
|
static final int MAX_CACHED_RECENT_SHORTCUTS = 30;
|
||||||
private static final int DEBOUNCE_LENGTH_MS = 500;
|
|
||||||
|
|
||||||
private final Context mContext;
|
private final Context mContext;
|
||||||
private final Injector mInjector;
|
private final Injector mInjector;
|
||||||
@@ -130,7 +129,6 @@ public class DataManager {
|
|||||||
private final List<PeopleService.ConversationsListener> mConversationsListeners =
|
private final List<PeopleService.ConversationsListener> mConversationsListeners =
|
||||||
new ArrayList<>(1);
|
new ArrayList<>(1);
|
||||||
private final Handler mHandler;
|
private final Handler mHandler;
|
||||||
private final PerPackageThrottler mShortcutsThrottler;
|
|
||||||
|
|
||||||
private ContentObserver mCallLogContentObserver;
|
private ContentObserver mCallLogContentObserver;
|
||||||
private ContentObserver mMmsSmsContentObserver;
|
private ContentObserver mMmsSmsContentObserver;
|
||||||
@@ -142,17 +140,14 @@ public class DataManager {
|
|||||||
private ConversationStatusExpirationBroadcastReceiver mStatusExpReceiver;
|
private ConversationStatusExpirationBroadcastReceiver mStatusExpReceiver;
|
||||||
|
|
||||||
public DataManager(Context context) {
|
public DataManager(Context context) {
|
||||||
this(context, new Injector(), BackgroundThread.get().getLooper(),
|
this(context, new Injector(), BackgroundThread.get().getLooper());
|
||||||
new PerPackageThrottlerImpl(BackgroundThread.getHandler(), DEBOUNCE_LENGTH_MS));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
DataManager(Context context, Injector injector, Looper looper,
|
DataManager(Context context, Injector injector, Looper looper) {
|
||||||
PerPackageThrottler shortcutsThrottler) {
|
|
||||||
mContext = context;
|
mContext = context;
|
||||||
mInjector = injector;
|
mInjector = injector;
|
||||||
mScheduledExecutor = mInjector.createScheduledExecutor();
|
mScheduledExecutor = mInjector.createScheduledExecutor();
|
||||||
mHandler = new Handler(looper);
|
mHandler = new Handler(looper);
|
||||||
mShortcutsThrottler = shortcutsThrottler;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Initialization. Called when the system services are up running. */
|
/** Initialization. Called when the system services are up running. */
|
||||||
@@ -1109,34 +1104,25 @@ public class DataManager {
|
|||||||
@Override
|
@Override
|
||||||
public void onShortcutsAddedOrUpdated(@NonNull String packageName,
|
public void onShortcutsAddedOrUpdated(@NonNull String packageName,
|
||||||
@NonNull List<ShortcutInfo> shortcuts, @NonNull UserHandle user) {
|
@NonNull List<ShortcutInfo> shortcuts, @NonNull UserHandle user) {
|
||||||
mShortcutsThrottler.scheduleDebounced(
|
mInjector.getBackgroundExecutor().execute(() -> {
|
||||||
new Pair<>(packageName, user.getIdentifier()),
|
|
||||||
() -> {
|
|
||||||
PackageData packageData = getPackage(packageName, user.getIdentifier());
|
PackageData packageData = getPackage(packageName, user.getIdentifier());
|
||||||
List<ShortcutInfo> queriedShortcuts = getShortcuts(packageName,
|
for (ShortcutInfo shortcut : shortcuts) {
|
||||||
user.getIdentifier(), null);
|
|
||||||
boolean hasCachedShortcut = false;
|
|
||||||
for (ShortcutInfo shortcut : queriedShortcuts) {
|
|
||||||
if (ShortcutHelper.isConversationShortcut(
|
if (ShortcutHelper.isConversationShortcut(
|
||||||
shortcut, mShortcutServiceInternal, user.getIdentifier())) {
|
shortcut, mShortcutServiceInternal, user.getIdentifier())) {
|
||||||
if (shortcut.isCached()) {
|
if (shortcut.isCached()) {
|
||||||
ConversationInfo info = packageData != null
|
ConversationInfo conversationInfo = packageData != null
|
||||||
? packageData.getConversationInfo(shortcut.getId())
|
? packageData.getConversationInfo(shortcut.getId()) : null;
|
||||||
: null;
|
if (conversationInfo == null
|
||||||
if (info == null
|
|| !conversationInfo.isShortcutCachedForNotification()) {
|
||||||
|| !info.isShortcutCachedForNotification()) {
|
// This is a newly cached shortcut. Clean up the existing cached
|
||||||
hasCachedShortcut = true;
|
// shortcuts to ensure the cache size is under the limit.
|
||||||
|
cleanupCachedShortcuts(user.getIdentifier(),
|
||||||
|
MAX_CACHED_RECENT_SHORTCUTS - 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
addOrUpdateConversationInfo(shortcut);
|
addOrUpdateConversationInfo(shortcut);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Added at least one new conversation. Uncache older existing cached
|
|
||||||
// shortcuts to ensure the cache size is under the limit.
|
|
||||||
if (hasCachedShortcut) {
|
|
||||||
cleanupCachedShortcuts(user.getIdentifier(),
|
|
||||||
MAX_CACHED_RECENT_SHORTCUTS);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,28 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2023 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.server.people.data;
|
|
||||||
|
|
||||||
import android.util.Pair;
|
|
||||||
|
|
||||||
/** The interface for throttling expensive runnables per package. */
|
|
||||||
interface PerPackageThrottler {
|
|
||||||
/**
|
|
||||||
* Schedule a runnable to run in the future, and debounce runnables for same {@code pkgUserId}
|
|
||||||
* that occur until that future has run.
|
|
||||||
*/
|
|
||||||
void scheduleDebounced(Pair<String, Integer> pkgUserId, Runnable runnable);
|
|
||||||
}
|
|
||||||
@@ -1,52 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2023 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.server.people.data;
|
|
||||||
|
|
||||||
import android.os.Handler;
|
|
||||||
import android.util.Pair;
|
|
||||||
|
|
||||||
import java.util.HashSet;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A class that implements a per-package throttler that prevents a runnable from executing more than
|
|
||||||
* once every {@code debounceTime}.
|
|
||||||
*/
|
|
||||||
public class PerPackageThrottlerImpl implements PerPackageThrottler {
|
|
||||||
private final Handler mBackgroundHandler;
|
|
||||||
private final int mDebounceTime;
|
|
||||||
private final HashSet<Pair<String, Integer>> mPkgScheduledTasks = new HashSet<>();
|
|
||||||
|
|
||||||
PerPackageThrottlerImpl(Handler backgroundHandler, int debounceTime) {
|
|
||||||
mBackgroundHandler = backgroundHandler;
|
|
||||||
mDebounceTime = debounceTime;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public synchronized void scheduleDebounced(
|
|
||||||
Pair<String, Integer> pkgUserId, Runnable runnable) {
|
|
||||||
if (mPkgScheduledTasks.contains(pkgUserId)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
mPkgScheduledTasks.add(pkgUserId);
|
|
||||||
mBackgroundHandler.postDelayed(() -> {
|
|
||||||
synchronized (this) {
|
|
||||||
mPkgScheduledTasks.remove(pkgUserId);
|
|
||||||
runnable.run();
|
|
||||||
}
|
|
||||||
}, mDebounceTime);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -86,7 +86,6 @@ import android.service.notification.StatusBarNotification;
|
|||||||
import android.telecom.TelecomManager;
|
import android.telecom.TelecomManager;
|
||||||
import android.telephony.TelephonyManager;
|
import android.telephony.TelephonyManager;
|
||||||
import android.text.format.DateUtils;
|
import android.text.format.DateUtils;
|
||||||
import android.util.Pair;
|
|
||||||
import android.util.Range;
|
import android.util.Range;
|
||||||
|
|
||||||
import com.android.internal.app.ChooserActivity;
|
import com.android.internal.app.ChooserActivity;
|
||||||
@@ -187,7 +186,6 @@ public final class DataManagerTest {
|
|||||||
private ShortcutInfo mShortcutInfo;
|
private ShortcutInfo mShortcutInfo;
|
||||||
private TestInjector mInjector;
|
private TestInjector mInjector;
|
||||||
private TestLooper mLooper;
|
private TestLooper mLooper;
|
||||||
private TestPerPackageThrottler mShortcutThrottler;
|
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void setUp() throws PackageManager.NameNotFoundException {
|
public void setUp() throws PackageManager.NameNotFoundException {
|
||||||
@@ -277,9 +275,7 @@ public final class DataManagerTest {
|
|||||||
|
|
||||||
mInjector = new TestInjector();
|
mInjector = new TestInjector();
|
||||||
mLooper = new TestLooper();
|
mLooper = new TestLooper();
|
||||||
mShortcutThrottler = new TestPerPackageThrottler();
|
mDataManager = new DataManager(mContext, mInjector, mLooper.getLooper());
|
||||||
mDataManager = new DataManager(mContext, mInjector, mLooper.getLooper(),
|
|
||||||
mShortcutThrottler);
|
|
||||||
mDataManager.initialize();
|
mDataManager.initialize();
|
||||||
|
|
||||||
when(mShortcutServiceInternal.isSharingShortcut(anyInt(), anyString(), anyString(),
|
when(mShortcutServiceInternal.isSharingShortcut(anyInt(), anyString(), anyString(),
|
||||||
@@ -287,7 +283,10 @@ public final class DataManagerTest {
|
|||||||
|
|
||||||
mShortcutInfo = buildShortcutInfo(TEST_PKG_NAME, USER_ID_PRIMARY, TEST_SHORTCUT_ID,
|
mShortcutInfo = buildShortcutInfo(TEST_PKG_NAME, USER_ID_PRIMARY, TEST_SHORTCUT_ID,
|
||||||
buildPerson());
|
buildPerson());
|
||||||
mockGetShortcuts(Collections.singletonList(mShortcutInfo));
|
when(mShortcutServiceInternal.getShortcuts(
|
||||||
|
anyInt(), anyString(), anyLong(), anyString(), anyList(), any(), any(),
|
||||||
|
anyInt(), anyInt(), anyInt(), anyInt()))
|
||||||
|
.thenReturn(Collections.singletonList(mShortcutInfo));
|
||||||
verify(mShortcutServiceInternal).addShortcutChangeCallback(
|
verify(mShortcutServiceInternal).addShortcutChangeCallback(
|
||||||
mShortcutChangeCallbackCaptor.capture());
|
mShortcutChangeCallbackCaptor.capture());
|
||||||
mShortcutChangeCallback = mShortcutChangeCallbackCaptor.getValue();
|
mShortcutChangeCallback = mShortcutChangeCallbackCaptor.getValue();
|
||||||
@@ -973,7 +972,6 @@ public final class DataManagerTest {
|
|||||||
buildPerson());
|
buildPerson());
|
||||||
ShortcutInfo shortcut3 = buildShortcutInfo(TEST_PKG_NAME, USER_ID_PRIMARY, "sc3",
|
ShortcutInfo shortcut3 = buildShortcutInfo(TEST_PKG_NAME, USER_ID_PRIMARY, "sc3",
|
||||||
buildPerson());
|
buildPerson());
|
||||||
mockGetShortcuts(List.of(shortcut1, shortcut2, shortcut3));
|
|
||||||
mShortcutChangeCallback.onShortcutsAddedOrUpdated(TEST_PKG_NAME,
|
mShortcutChangeCallback.onShortcutsAddedOrUpdated(TEST_PKG_NAME,
|
||||||
Arrays.asList(shortcut1, shortcut2, shortcut3), UserHandle.of(USER_ID_PRIMARY));
|
Arrays.asList(shortcut1, shortcut2, shortcut3), UserHandle.of(USER_ID_PRIMARY));
|
||||||
mShortcutChangeCallback.onShortcutsRemoved(TEST_PKG_NAME,
|
mShortcutChangeCallback.onShortcutsRemoved(TEST_PKG_NAME,
|
||||||
@@ -1225,6 +1223,7 @@ public final class DataManagerTest {
|
|||||||
eq(ShortcutInfo.FLAG_CACHED_NOTIFICATIONS));
|
eq(ShortcutInfo.FLAG_CACHED_NOTIFICATIONS));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testUncacheOldestCachedShortcut_missingNotificationEvents() {
|
public void testUncacheOldestCachedShortcut_missingNotificationEvents() {
|
||||||
mDataManager.onUserUnlocked(USER_ID_PRIMARY);
|
mDataManager.onUserUnlocked(USER_ID_PRIMARY);
|
||||||
@@ -1234,7 +1233,6 @@ public final class DataManagerTest {
|
|||||||
ShortcutInfo shortcut = buildShortcutInfo(TEST_PKG_NAME, USER_ID_PRIMARY, shortcutId,
|
ShortcutInfo shortcut = buildShortcutInfo(TEST_PKG_NAME, USER_ID_PRIMARY, shortcutId,
|
||||||
buildPerson());
|
buildPerson());
|
||||||
shortcut.setCached(ShortcutInfo.FLAG_CACHED_NOTIFICATIONS);
|
shortcut.setCached(ShortcutInfo.FLAG_CACHED_NOTIFICATIONS);
|
||||||
mockGetShortcuts(Collections.singletonList(shortcut));
|
|
||||||
mShortcutChangeCallback.onShortcutsAddedOrUpdated(
|
mShortcutChangeCallback.onShortcutsAddedOrUpdated(
|
||||||
TEST_PKG_NAME,
|
TEST_PKG_NAME,
|
||||||
Collections.singletonList(shortcut),
|
Collections.singletonList(shortcut),
|
||||||
@@ -1254,6 +1252,7 @@ public final class DataManagerTest {
|
|||||||
eq(ShortcutInfo.FLAG_CACHED_NOTIFICATIONS));
|
eq(ShortcutInfo.FLAG_CACHED_NOTIFICATIONS));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testUncacheOldestCachedShortcut_legacyConversation() {
|
public void testUncacheOldestCachedShortcut_legacyConversation() {
|
||||||
mDataManager.onUserUnlocked(USER_ID_PRIMARY);
|
mDataManager.onUserUnlocked(USER_ID_PRIMARY);
|
||||||
@@ -1275,7 +1274,6 @@ public final class DataManagerTest {
|
|||||||
ShortcutInfo shortcut = buildShortcutInfo(TEST_PKG_NAME, USER_ID_PRIMARY, shortcutId,
|
ShortcutInfo shortcut = buildShortcutInfo(TEST_PKG_NAME, USER_ID_PRIMARY, shortcutId,
|
||||||
buildPerson());
|
buildPerson());
|
||||||
shortcut.setCached(ShortcutInfo.FLAG_CACHED_NOTIFICATIONS);
|
shortcut.setCached(ShortcutInfo.FLAG_CACHED_NOTIFICATIONS);
|
||||||
mockGetShortcuts(Collections.singletonList(shortcut));
|
|
||||||
mShortcutChangeCallback.onShortcutsAddedOrUpdated(
|
mShortcutChangeCallback.onShortcutsAddedOrUpdated(
|
||||||
TEST_PKG_NAME,
|
TEST_PKG_NAME,
|
||||||
Collections.singletonList(shortcut),
|
Collections.singletonList(shortcut),
|
||||||
@@ -1313,8 +1311,7 @@ public final class DataManagerTest {
|
|||||||
mDataManager.reportShareTargetEvent(appTargetEvent, intentFilter);
|
mDataManager.reportShareTargetEvent(appTargetEvent, intentFilter);
|
||||||
byte[] payload = mDataManager.getBackupPayload(USER_ID_PRIMARY);
|
byte[] payload = mDataManager.getBackupPayload(USER_ID_PRIMARY);
|
||||||
|
|
||||||
DataManager dataManager = new DataManager(
|
DataManager dataManager = new DataManager(mContext, mInjector, mLooper.getLooper());
|
||||||
mContext, mInjector, mLooper.getLooper(), mShortcutThrottler);
|
|
||||||
dataManager.onUserUnlocked(USER_ID_PRIMARY);
|
dataManager.onUserUnlocked(USER_ID_PRIMARY);
|
||||||
dataManager.restore(USER_ID_PRIMARY, payload);
|
dataManager.restore(USER_ID_PRIMARY, payload);
|
||||||
ConversationInfo conversationInfo = dataManager.getPackage(TEST_PKG_NAME, USER_ID_PRIMARY)
|
ConversationInfo conversationInfo = dataManager.getPackage(TEST_PKG_NAME, USER_ID_PRIMARY)
|
||||||
@@ -1726,13 +1723,6 @@ public final class DataManagerTest {
|
|||||||
return (queryFlags & flag) != 0;
|
return (queryFlags & flag) != 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void mockGetShortcuts(List<ShortcutInfo> shortcutInfoList) {
|
|
||||||
when(mShortcutServiceInternal.getShortcuts(
|
|
||||||
anyInt(), anyString(), anyLong(), anyString(), any(), any(), any(),
|
|
||||||
anyInt(), anyInt(), anyInt(), anyInt()))
|
|
||||||
.thenReturn(shortcutInfoList);
|
|
||||||
}
|
|
||||||
|
|
||||||
// "Sends" a notification to a non-customized notification channel - the notification channel
|
// "Sends" a notification to a non-customized notification channel - the notification channel
|
||||||
// is something generic like "messages" and the notification has a shortcut id
|
// is something generic like "messages" and the notification has a shortcut id
|
||||||
private void sendGenericNotification() {
|
private void sendGenericNotification() {
|
||||||
@@ -1958,11 +1948,4 @@ public final class DataManagerTest {
|
|||||||
return mUsageStatsQueryHelper;
|
return mUsageStatsQueryHelper;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class TestPerPackageThrottler implements PerPackageThrottler {
|
|
||||||
@Override
|
|
||||||
public void scheduleDebounced(Pair<String, Integer> pkgUserId, Runnable runnable) {
|
|
||||||
runnable.run();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,67 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2023 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.server.people.data;
|
|
||||||
|
|
||||||
import static org.junit.Assert.assertFalse;
|
|
||||||
import static org.junit.Assert.assertTrue;
|
|
||||||
|
|
||||||
import android.util.Pair;
|
|
||||||
|
|
||||||
import com.android.server.testutils.OffsettableClock;
|
|
||||||
import com.android.server.testutils.TestHandler;
|
|
||||||
|
|
||||||
import org.junit.Before;
|
|
||||||
import org.junit.Test;
|
|
||||||
import org.junit.runner.RunWith;
|
|
||||||
import org.junit.runners.JUnit4;
|
|
||||||
|
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
|
||||||
|
|
||||||
@RunWith(JUnit4.class)
|
|
||||||
public class PerPackageThrottlerImplTest {
|
|
||||||
private static final int DEBOUNCE_INTERVAL = 500;
|
|
||||||
private static final String PKG_ONE = "pkg_one";
|
|
||||||
private static final String PKG_TWO = "pkg_two";
|
|
||||||
private static final int USER_ID = 10;
|
|
||||||
|
|
||||||
private final OffsettableClock mClock = new OffsettableClock.Stopped();
|
|
||||||
private final TestHandler mTestHandler = new TestHandler(null, mClock);
|
|
||||||
private PerPackageThrottlerImpl mThrottler;
|
|
||||||
|
|
||||||
@Before
|
|
||||||
public void setUp() {
|
|
||||||
mThrottler = new PerPackageThrottlerImpl(mTestHandler, DEBOUNCE_INTERVAL);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void scheduleDebounced() {
|
|
||||||
AtomicBoolean pkgOneRan = new AtomicBoolean();
|
|
||||||
AtomicBoolean pkgTwoRan = new AtomicBoolean();
|
|
||||||
|
|
||||||
mThrottler.scheduleDebounced(new Pair<>(PKG_ONE, USER_ID), () -> pkgOneRan.set(true));
|
|
||||||
mThrottler.scheduleDebounced(new Pair<>(PKG_ONE, USER_ID), () -> pkgOneRan.set(true));
|
|
||||||
mThrottler.scheduleDebounced(new Pair<>(PKG_TWO, USER_ID), () -> pkgTwoRan.set(true));
|
|
||||||
mThrottler.scheduleDebounced(new Pair<>(PKG_TWO, USER_ID), () -> pkgTwoRan.set(true));
|
|
||||||
|
|
||||||
assertFalse(pkgOneRan.get());
|
|
||||||
assertFalse(pkgTwoRan.get());
|
|
||||||
mClock.fastForward(DEBOUNCE_INTERVAL);
|
|
||||||
mTestHandler.timeAdvance();
|
|
||||||
assertTrue(pkgOneRan.get());
|
|
||||||
assertTrue(pkgTwoRan.get());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user