11/ Update MultiStateCallbacks to support multiple callbacks

- Allow multiple callbacks to be set for the same state
- Expose method to set state on ui thread directly
- Ensure callbacks are made immediately if the state is already set
- Clarify that the one shot callbacks vs the state listeners

Bug: 141886704
Change-Id: I8ea0dcd2821ee18d071706eaddeb2852afa13f30
This commit is contained in:
Winson Chung 2019-10-08 14:32:15 -07:00
parent c80b3224aa
commit 981ef3a88a
7 changed files with 117 additions and 87 deletions

View File

@ -130,7 +130,6 @@ public abstract class BaseSwipeUpHandler<T extends BaseDraggingActivity, Q exten
protected Runnable mGestureEndCallback;
protected final Handler mMainThreadHandler = MAIN_EXECUTOR.getHandler();
protected MultiStateCallback mStateCallback;
protected boolean mCanceled;
@ -157,14 +156,6 @@ public abstract class BaseSwipeUpHandler<T extends BaseDraggingActivity, Q exten
.getDeviceProfile(mContext));
}
protected void setStateOnUiThread(int stateFlag) {
if (Looper.myLooper() == mMainThreadHandler.getLooper()) {
mStateCallback.setState(stateFlag);
} else {
postAsyncCallback(mMainThreadHandler, () -> mStateCallback.setState(stateFlag));
}
}
protected void performHapticFeedback() {
if (!mVibrator.hasVibrator()) {
return;
@ -253,9 +244,9 @@ public abstract class BaseSwipeUpHandler<T extends BaseDraggingActivity, Q exten
} else {
mActivityInterface.onLaunchTaskSuccess(mActivity);
}
}, mMainThreadHandler);
}, MAIN_EXECUTOR.getHandler());
}
setStateOnUiThread(successStateFlag);
mStateCallback.setStateOnUiThread(successStateFlag);
}
mCanceled = false;
mFinishingRecentsAnimationForNewTaskId = -1;

View File

@ -211,58 +211,57 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity>
private void initStateCallbacks() {
mStateCallback = new MultiStateCallback(STATE_NAMES);
mStateCallback.addCallback(STATE_LAUNCHER_PRESENT | STATE_GESTURE_STARTED,
mStateCallback.runOnceAtState(STATE_LAUNCHER_PRESENT | STATE_GESTURE_STARTED,
this::onLauncherPresentAndGestureStarted);
mStateCallback.addCallback(STATE_LAUNCHER_DRAWN | STATE_GESTURE_STARTED,
mStateCallback.runOnceAtState(STATE_LAUNCHER_DRAWN | STATE_GESTURE_STARTED,
this::initializeLauncherAnimationController);
mStateCallback.addCallback(STATE_LAUNCHER_PRESENT | STATE_LAUNCHER_DRAWN,
mStateCallback.runOnceAtState(STATE_LAUNCHER_PRESENT | STATE_LAUNCHER_DRAWN,
this::launcherFrameDrawn);
mStateCallback.addCallback(STATE_LAUNCHER_PRESENT | STATE_LAUNCHER_STARTED
mStateCallback.runOnceAtState(STATE_LAUNCHER_PRESENT | STATE_LAUNCHER_STARTED
| STATE_GESTURE_CANCELLED,
this::resetStateForAnimationCancel);
mStateCallback.addCallback(STATE_LAUNCHER_STARTED | STATE_APP_CONTROLLER_RECEIVED,
mStateCallback.runOnceAtState(STATE_LAUNCHER_STARTED | STATE_APP_CONTROLLER_RECEIVED,
this::sendRemoteAnimationsToAnimationFactory);
mStateCallback.addCallback(STATE_RESUME_LAST_TASK | STATE_APP_CONTROLLER_RECEIVED,
mStateCallback.runOnceAtState(STATE_RESUME_LAST_TASK | STATE_APP_CONTROLLER_RECEIVED,
this::resumeLastTask);
mStateCallback.addCallback(STATE_START_NEW_TASK | STATE_SCREENSHOT_CAPTURED,
mStateCallback.runOnceAtState(STATE_START_NEW_TASK | STATE_SCREENSHOT_CAPTURED,
this::startNewTask);
mStateCallback.addCallback(STATE_LAUNCHER_PRESENT | STATE_APP_CONTROLLER_RECEIVED
mStateCallback.runOnceAtState(STATE_LAUNCHER_PRESENT | STATE_APP_CONTROLLER_RECEIVED
| STATE_LAUNCHER_DRAWN | STATE_CAPTURE_SCREENSHOT,
this::switchToScreenshot);
mStateCallback.addCallback(STATE_SCREENSHOT_CAPTURED | STATE_GESTURE_COMPLETED
mStateCallback.runOnceAtState(STATE_SCREENSHOT_CAPTURED | STATE_GESTURE_COMPLETED
| STATE_SCALED_CONTROLLER_RECENTS,
this::finishCurrentTransitionToRecents);
mStateCallback.addCallback(STATE_SCREENSHOT_CAPTURED | STATE_GESTURE_COMPLETED
mStateCallback.runOnceAtState(STATE_SCREENSHOT_CAPTURED | STATE_GESTURE_COMPLETED
| STATE_SCALED_CONTROLLER_HOME,
this::finishCurrentTransitionToHome);
mStateCallback.addCallback(STATE_SCALED_CONTROLLER_HOME | STATE_CURRENT_TASK_FINISHED,
mStateCallback.runOnceAtState(STATE_SCALED_CONTROLLER_HOME | STATE_CURRENT_TASK_FINISHED,
this::reset);
mStateCallback.addCallback(STATE_LAUNCHER_PRESENT | STATE_APP_CONTROLLER_RECEIVED
mStateCallback.runOnceAtState(STATE_LAUNCHER_PRESENT | STATE_APP_CONTROLLER_RECEIVED
| STATE_LAUNCHER_DRAWN | STATE_SCALED_CONTROLLER_RECENTS
| STATE_CURRENT_TASK_FINISHED | STATE_GESTURE_COMPLETED
| STATE_GESTURE_STARTED,
this::setupLauncherUiAfterSwipeUpToRecentsAnimation);
mGestureState.addCallback(STATE_END_TARGET_ANIMATION_FINISHED,
this::onEndTargetSet);
mGestureState.runOnceAtState(STATE_END_TARGET_ANIMATION_FINISHED, this::onEndTargetSet);
mStateCallback.addCallback(STATE_HANDLER_INVALIDATED, this::invalidateHandler);
mStateCallback.addCallback(STATE_LAUNCHER_PRESENT | STATE_HANDLER_INVALIDATED,
mStateCallback.runOnceAtState(STATE_HANDLER_INVALIDATED, this::invalidateHandler);
mStateCallback.runOnceAtState(STATE_LAUNCHER_PRESENT | STATE_HANDLER_INVALIDATED,
this::invalidateHandlerWithLauncher);
mStateCallback.addCallback(STATE_HANDLER_INVALIDATED | STATE_RESUME_LAST_TASK,
mStateCallback.runOnceAtState(STATE_HANDLER_INVALIDATED | STATE_RESUME_LAST_TASK,
this::notifyTransitionCancelled);
if (!ENABLE_QUICKSTEP_LIVE_TILE.get()) {
mStateCallback.addChangeHandler(STATE_APP_CONTROLLER_RECEIVED | STATE_LAUNCHER_PRESENT
mStateCallback.addChangeListener(STATE_APP_CONTROLLER_RECEIVED | STATE_LAUNCHER_PRESENT
| STATE_SCREENSHOT_VIEW_SHOWN | STATE_CAPTURE_SCREENSHOT,
(b) -> mRecentsView.setRunningTaskHidden(!b));
}
@ -330,7 +329,7 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity>
// Launcher is visible, but might be about to stop. Thus, if we prepare recents
// now, it might get overridden by moveToRestState() in onStop(). To avoid this,
// wait until the next gesture (and possibly launcher) starts.
mStateCallback.addCallback(STATE_GESTURE_STARTED, initAnimFactory);
mStateCallback.runOnceAtState(STATE_GESTURE_STARTED, initAnimFactory);
} else {
initAnimFactory.run();
}
@ -596,9 +595,9 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity>
super.onRecentsAnimationStart(controller, targets);
// Only add the callback to enable the input consumer after we actually have the controller
mStateCallback.addCallback(STATE_APP_CONTROLLER_RECEIVED | STATE_GESTURE_STARTED,
mStateCallback.runOnceAtState(STATE_APP_CONTROLLER_RECEIVED | STATE_GESTURE_STARTED,
mRecentsAnimationController::enableInputConsumer);
setStateOnUiThread(STATE_APP_CONTROLLER_RECEIVED);
mStateCallback.setStateOnUiThread(STATE_APP_CONTROLLER_RECEIVED);
mPassedOverviewThreshold = false;
}
@ -608,7 +607,7 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity>
super.onRecentsAnimationCanceled(thumbnailData);
mRecentsView.setRecentsAnimationTargets(null, null);
mActivityInitListener.unregister();
setStateOnUiThread(STATE_GESTURE_CANCELLED | STATE_HANDLER_INVALIDATED);
mStateCallback.setStateOnUiThread(STATE_GESTURE_CANCELLED | STATE_HANDLER_INVALIDATED);
ActiveGestureLog.INSTANCE.addLog("cancelRecentsAnimation");
}
@ -616,7 +615,7 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity>
public void onGestureStarted() {
notifyGestureStartedAsync();
mShiftAtGestureStart = mCurrentShift.value;
setStateOnUiThread(STATE_GESTURE_STARTED);
mStateCallback.setStateOnUiThread(STATE_GESTURE_STARTED);
mGestureStarted = true;
}
@ -639,7 +638,7 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity>
@Override
public void onGestureCancelled() {
updateDisplacement(0);
setStateOnUiThread(STATE_GESTURE_COMPLETED);
mStateCallback.setStateOnUiThread(STATE_GESTURE_COMPLETED);
mLogAction = Touch.SWIPE_NOOP;
handleNormalGestureEnd(0, false, new PointF(), true /* isCancel */);
}
@ -654,7 +653,7 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity>
float flingThreshold = mContext.getResources()
.getDimension(R.dimen.quickstep_fling_threshold_velocity);
boolean isFling = mGestureStarted && Math.abs(endVelocity) > flingThreshold;
setStateOnUiThread(STATE_GESTURE_COMPLETED);
mStateCallback.setStateOnUiThread(STATE_GESTURE_COMPLETED);
mLogAction = isFling ? Touch.FLING : Touch.SWIPE;
boolean isVelocityVertical = Math.abs(velocity.y) > Math.abs(velocity.x);
@ -906,7 +905,7 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity>
return AnimatorPlaybackController.wrap(new AnimatorSet(), duration);
}
};
mStateCallback.addChangeHandler(STATE_LAUNCHER_PRESENT | STATE_HANDLER_INVALIDATED,
mStateCallback.addChangeListener(STATE_LAUNCHER_PRESENT | STATE_HANDLER_INVALIDATED,
isPresent -> mRecentsView.startHome());
}
RectFSpringAnim windowAnim = createWindowAnimationToHome(start, homeAnimFactory);
@ -1042,7 +1041,7 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity>
}
private void reset() {
setStateOnUiThread(STATE_HANDLER_INVALIDATED);
mStateCallback.setStateOnUiThread(STATE_HANDLER_INVALIDATED);
}
/**
@ -1122,10 +1121,10 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity>
}
mRecentsView.updateThumbnail(mRunningTaskId, mTaskSnapshot, false /* refreshNow */);
}
setStateOnUiThread(STATE_SCREENSHOT_CAPTURED);
mStateCallback.setStateOnUiThread(STATE_SCREENSHOT_CAPTURED);
} else if (!hasTargets()) {
// If there are no targets, then we don't need to capture anything
setStateOnUiThread(STATE_SCREENSHOT_CAPTURED);
mStateCallback.setStateOnUiThread(STATE_SCREENSHOT_CAPTURED);
} else {
boolean finishTransitionPosted = false;
if (mRecentsAnimationController != null) {
@ -1145,14 +1144,15 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity>
// Defer finishing the animation until the next launcher frame with the
// new thumbnail
finishTransitionPosted = ViewUtils.postDraw(taskView,
() -> setStateOnUiThread(STATE_SCREENSHOT_CAPTURED), this::isCanceled);
() -> mStateCallback.setStateOnUiThread(STATE_SCREENSHOT_CAPTURED),
this::isCanceled);
}
}
if (!finishTransitionPosted) {
// If we haven't posted a draw callback, set the state immediately.
Object traceToken = TraceHelper.INSTANCE.beginSection(SCREENSHOT_CAPTURED_EVT,
TraceHelper.FLAG_CHECK_FOR_RACE_CONDITIONS);
setStateOnUiThread(STATE_SCREENSHOT_CAPTURED);
mStateCallback.setStateOnUiThread(STATE_SCREENSHOT_CAPTURED);
TraceHelper.INSTANCE.endSection(traceToken);
}
}
@ -1160,14 +1160,14 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity>
private void finishCurrentTransitionToRecents() {
if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
setStateOnUiThread(STATE_CURRENT_TASK_FINISHED);
mStateCallback.setStateOnUiThread(STATE_CURRENT_TASK_FINISHED);
} else if (!hasTargets()) {
// If there are no targets, then there is nothing to finish
setStateOnUiThread(STATE_CURRENT_TASK_FINISHED);
mStateCallback.setStateOnUiThread(STATE_CURRENT_TASK_FINISHED);
} else {
synchronized (mRecentsAnimationController) {
mRecentsAnimationController.finish(true /* toRecents */,
() -> setStateOnUiThread(STATE_CURRENT_TASK_FINISHED));
() -> mStateCallback.setStateOnUiThread(STATE_CURRENT_TASK_FINISHED));
}
}
ActiveGestureLog.INSTANCE.addLog("finishRecentsAnimation", true);
@ -1176,7 +1176,7 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity>
private void finishCurrentTransitionToHome() {
synchronized (mRecentsAnimationController) {
mRecentsAnimationController.finish(true /* toRecents */,
() -> setStateOnUiThread(STATE_CURRENT_TASK_FINISHED),
() -> mStateCallback.setStateOnUiThread(STATE_CURRENT_TASK_FINISHED),
true /* sendUserLeaveHint */);
}
ActiveGestureLog.INSTANCE.addLog("finishRecentsAnimation", true);

View File

@ -113,7 +113,7 @@ public class DeviceLockedInputConsumer implements InputConsumer,
// Init states
mStateCallback = new MultiStateCallback(STATE_NAMES);
mStateCallback.addCallback(STATE_TARGET_RECEIVED | STATE_HANDLER_INVALIDATED,
mStateCallback.runOnceAtState(STATE_TARGET_RECEIVED | STATE_HANDLER_INVALIDATED,
this::endRemoteAnimation);
mVelocityTracker = VelocityTracker.obtain();

View File

@ -145,20 +145,20 @@ public class FallbackNoButtonInputConsumer extends
private void initStateCallbacks() {
mStateCallback = new MultiStateCallback(STATE_NAMES);
mStateCallback.addCallback(STATE_HANDLER_INVALIDATED,
mStateCallback.runOnceAtState(STATE_HANDLER_INVALIDATED,
this::onHandlerInvalidated);
mStateCallback.addCallback(STATE_RECENTS_PRESENT | STATE_HANDLER_INVALIDATED,
mStateCallback.runOnceAtState(STATE_RECENTS_PRESENT | STATE_HANDLER_INVALIDATED,
this::onHandlerInvalidatedWithRecents);
mStateCallback.addCallback(STATE_GESTURE_CANCELLED | STATE_APP_CONTROLLER_RECEIVED,
mStateCallback.runOnceAtState(STATE_GESTURE_CANCELLED | STATE_APP_CONTROLLER_RECEIVED,
this::finishAnimationTargetSetAnimationComplete);
if (mInQuickSwitchMode) {
mStateCallback.addCallback(STATE_GESTURE_COMPLETED | STATE_APP_CONTROLLER_RECEIVED
mStateCallback.runOnceAtState(STATE_GESTURE_COMPLETED | STATE_APP_CONTROLLER_RECEIVED
| STATE_RECENTS_PRESENT,
this::finishAnimationTargetSet);
} else {
mStateCallback.addCallback(STATE_GESTURE_COMPLETED | STATE_APP_CONTROLLER_RECEIVED,
mStateCallback.runOnceAtState(STATE_GESTURE_COMPLETED | STATE_APP_CONTROLLER_RECEIVED,
this::finishAnimationTargetSet);
}
}
@ -186,7 +186,7 @@ public class FallbackNoButtonInputConsumer extends
mRecentsView.onGestureAnimationStart(mRunningTaskId);
}
}
setStateOnUiThread(STATE_RECENTS_PRESENT);
mStateCallback.setStateOnUiThread(STATE_RECENTS_PRESENT);
return true;
}
@ -251,7 +251,7 @@ public class FallbackNoButtonInputConsumer extends
public void onGestureCancelled() {
updateDisplacement(0);
mGestureState.setEndTarget(LAST_TASK);
setStateOnUiThread(STATE_GESTURE_CANCELLED);
mStateCallback.setStateOnUiThread(STATE_GESTURE_CANCELLED);
}
@Override
@ -275,7 +275,7 @@ public class FallbackNoButtonInputConsumer extends
: LAST_TASK);
}
}
setStateOnUiThread(STATE_GESTURE_COMPLETED);
mStateCallback.setStateOnUiThread(STATE_GESTURE_COMPLETED);
}
@Override
@ -302,7 +302,7 @@ public class FallbackNoButtonInputConsumer extends
mRecentsView.setOnScrollChangeListener(null);
}
} else {
setStateOnUiThread(STATE_HANDLER_INVALIDATED);
mStateCallback.setStateOnUiThread(STATE_HANDLER_INVALIDATED);
}
}
@ -366,7 +366,7 @@ public class FallbackNoButtonInputConsumer extends
}
}
setStateOnUiThread(STATE_HANDLER_INVALIDATED);
mStateCallback.setStateOnUiThread(STATE_HANDLER_INVALIDATED);
}
private void finishAnimationTargetSet() {
@ -436,14 +436,14 @@ public class FallbackNoButtonInputConsumer extends
}
applyTransformUnchecked();
setStateOnUiThread(STATE_APP_CONTROLLER_RECEIVED);
mStateCallback.setStateOnUiThread(STATE_APP_CONTROLLER_RECEIVED);
}
@Override
public void onRecentsAnimationCanceled(ThumbnailData thumbnailData) {
super.onRecentsAnimationCanceled(thumbnailData);
mRecentsView.setRecentsAnimationTargets(null, null);
setStateOnUiThread(STATE_HANDLER_INVALIDATED);
mStateCallback.setStateOnUiThread(STATE_HANDLER_INVALIDATED);
}
/**

View File

@ -19,7 +19,6 @@ import android.view.MotionEvent;
import com.android.quickstep.InputConsumer;
import com.android.quickstep.TaskAnimationManager;
import com.android.quickstep.TouchInteractionService;
/**
* A NO_OP input consumer which also resets any pending gesture

View File

@ -129,8 +129,8 @@ public class GestureState implements RecentsAnimationCallbacks.RecentsAnimationL
/**
* Adds a callback for when the states matching the given {@param stateMask} is set.
*/
public void addCallback(int stateMask, Runnable callback) {
mStateCallback.addCallback(stateMask, callback);
public void runOnceAtState(int stateMask, Runnable callback) {
mStateCallback.runOnceAtState(stateMask, callback);
}
/**
@ -196,7 +196,7 @@ public class GestureState implements RecentsAnimationCallbacks.RecentsAnimationL
*/
public void setFinishingRecentsAnimationTaskId(int taskId) {
mFinishingRecentsAnimationTaskId = taskId;
mStateCallback.addCallback(STATE_RECENTS_ANIMATION_FINISHED, () -> {
mStateCallback.runOnceAtState(STATE_RECENTS_ANIMATION_FINISHED, () -> {
mFinishingRecentsAnimationTaskId = -1;
});
}

View File

@ -15,11 +15,17 @@
*/
package com.android.quickstep;
import static com.android.launcher3.Utilities.postAsyncCallback;
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import android.os.Looper;
import android.util.Log;
import android.util.SparseArray;
import com.android.launcher3.config.FeatureFlags;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.StringJoiner;
import java.util.function.Consumer;
@ -31,16 +37,29 @@ public class MultiStateCallback {
private static final String TAG = "MultiStateCallback";
public static final boolean DEBUG_STATES = false;
private final SparseArray<Runnable> mCallbacks = new SparseArray<>();
private final SparseArray<Consumer<Boolean>> mStateChangeHandlers = new SparseArray<>();
private final SparseArray<LinkedList<Runnable>> mCallbacks = new SparseArray<>();
private final SparseArray<ArrayList<Consumer<Boolean>>> mStateChangeListeners =
new SparseArray<>();
private final String[] mStateNames;
private int mState = 0;
public MultiStateCallback(String[] stateNames) {
mStateNames = DEBUG_STATES ? stateNames : null;
}
private int mState = 0;
/**
* Adds the provided state flags to the global state on the UI thread and executes any callbacks
* as a result.
*/
public void setStateOnUiThread(int stateFlag) {
if (Looper.myLooper() == Looper.getMainLooper()) {
setState(stateFlag);
} else {
postAsyncCallback(MAIN_EXECUTOR.getHandler(), () -> setState(stateFlag));
}
}
/**
* Adds the provided state flags to the global state and executes any callbacks as a result.
@ -51,7 +70,7 @@ public class MultiStateCallback {
+ convertToFlagNames(stateFlag) + " to " + convertToFlagNames(mState));
}
int oldState = mState;
final int oldState = mState;
mState = mState | stateFlag;
int count = mCallbacks.size();
@ -59,15 +78,13 @@ public class MultiStateCallback {
int state = mCallbacks.keyAt(i);
if ((mState & state) == state) {
Runnable callback = mCallbacks.valueAt(i);
if (callback != null) {
// Set the callback to null, so that it does not run again.
mCallbacks.setValueAt(i, null);
callback.run();
LinkedList<Runnable> callbacks = mCallbacks.valueAt(i);
while (!callbacks.isEmpty()) {
callbacks.pollFirst().run();
}
}
}
notifyStateChangeHandlers(oldState);
notifyStateChangeListeners(oldState);
}
/**
@ -82,38 +99,61 @@ public class MultiStateCallback {
int oldState = mState;
mState = mState & ~stateFlag;
notifyStateChangeHandlers(oldState);
notifyStateChangeListeners(oldState);
}
private void notifyStateChangeHandlers(int oldState) {
int count = mStateChangeHandlers.size();
private void notifyStateChangeListeners(int oldState) {
int count = mStateChangeListeners.size();
for (int i = 0; i < count; i++) {
int state = mStateChangeHandlers.keyAt(i);
int state = mStateChangeListeners.keyAt(i);
boolean wasOn = (state & oldState) == state;
boolean isOn = (state & mState) == state;
if (wasOn != isOn) {
mStateChangeHandlers.valueAt(i).accept(isOn);
ArrayList<Consumer<Boolean>> listeners = mStateChangeListeners.valueAt(i);
for (Consumer<Boolean> listener : listeners) {
listener.accept(isOn);
}
}
}
}
/**
* Sets the callbacks to be run when the provided states are enabled.
* The callback is only run once.
* Sets a callback to be run when the provided states in the given {@param stateMask} is
* enabled. The callback is only run *once*, and if the states are already set at the time of
* this call then the callback will be made immediately.
*/
public void addCallback(int stateMask, Runnable callback) {
if (FeatureFlags.IS_DOGFOOD_BUILD && mCallbacks.get(stateMask) != null) {
throw new IllegalStateException("Multiple callbacks on same state");
public void runOnceAtState(int stateMask, Runnable callback) {
if ((mState & stateMask) == stateMask) {
callback.run();
} else {
final LinkedList<Runnable> callbacks;
if (mCallbacks.indexOfKey(stateMask) >= 0) {
callbacks = mCallbacks.get(stateMask);
if (FeatureFlags.IS_DOGFOOD_BUILD && callbacks.contains(callback)) {
throw new IllegalStateException("Existing callback for state found");
}
} else {
callbacks = new LinkedList<>();
mCallbacks.put(stateMask, callbacks);
}
callbacks.add(callback);
}
mCallbacks.put(stateMask, callback);
}
/**
* Sets the handler to be called when the provided states are enabled or disabled.
* Adds a persistent listener to be called states in the given {@param stateMask} are enabled
* or disabled.
*/
public void addChangeHandler(int stateMask, Consumer<Boolean> handler) {
mStateChangeHandlers.put(stateMask, handler);
public void addChangeListener(int stateMask, Consumer<Boolean> listener) {
final ArrayList<Consumer<Boolean>> listeners;
if (mStateChangeListeners.indexOfKey(stateMask) >= 0) {
listeners = mStateChangeListeners.get(stateMask);
} else {
listeners = new ArrayList<>();
mStateChangeListeners.put(stateMask, listeners);
}
listeners.add(listener);
}
public int getState() {