0

[Android] Make ElementFactories depend on Elements and not Conditions

ElementFactories only make sense when they require something that isn't
produced yet. They could depend on Condition, but there is no practical
use for this - it always makes sense to wait on a ConditionWithResult,
and it's simpler to always depend on an Element.

Elements' Conditions will be able to be set soon, and that would cause
ElementFactories to wait on Conditions that won't be part of the
transition anymore.

Bug: 414438679
Change-Id: I1dd5b7bfe394b3ae27bd77855e848d591aad960a
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/6500920
Reviewed-by: Andrew Grieve <agrieve@chromium.org>
Owners-Override: Henrique Nakashima <hnakashima@chromium.org>
Commit-Queue: Henrique Nakashima <hnakashima@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1454556}
This commit is contained in:
Henrique Nakashima
2025-05-01 11:36:22 -07:00
committed by Chromium LUCI CQ
parent 83a635178a
commit 6cb04b4435
12 changed files with 124 additions and 63 deletions
base/test/android/javatests/src/org/chromium/base/test/transit
content/public/test/android/javatests/src/org/chromium/content_public/browser/test/transit
ui/android/javatests/src/org/chromium/ui/test/transit

@ -30,7 +30,7 @@ public class ActivityElement<ActivityT extends Activity> extends Element<Activit
}
@Override
public ConditionWithResult<ActivityT> createEnterCondition() {
public @Nullable ConditionWithResult<ActivityT> createEnterCondition() {
return new ActivityExistsCondition();
}

@ -17,7 +17,7 @@ import java.util.Set;
*/
class BaseElements {
protected ArrayList<Element<?>> mElements = new ArrayList<>();
protected Map<Condition, ElementFactory> mElementFactories = new HashMap<>();
protected Map<Element<?>, ElementFactory> mElementFactories = new HashMap<>();
protected ArrayList<Condition> mOtherEnterConditions = new ArrayList<>();
protected ArrayList<Condition> mOtherExitConditions = new ArrayList<>();
@ -33,7 +33,7 @@ class BaseElements {
return mElements;
}
Map<Condition, ElementFactory> getElementFactories() {
Map<Element<?>, ElementFactory> getElementFactories() {
return mElementFactories;
}

@ -351,7 +351,11 @@ public class ConditionWaiter {
for (ConditionalState conditionalState : mTransition.getEnteredStates()) {
final Elements destinationElements = conditionalState.getElements();
allConditionsGuardingFactories.putAll(destinationElements.getElementFactories());
for (Map.Entry<Element<?>, ElementFactory> entry :
destinationElements.getElementFactories().entrySet()) {
allConditionsGuardingFactories.put(
entry.getKey().getEnterConditionChecked(), entry.getValue());
}
}
return allConditionsGuardingFactories;
@ -426,7 +430,11 @@ public class ConditionWaiter {
}
}
mConditionsGuardingFactories.putAll(newElements.getElementFactories());
for (Map.Entry<Element<?>, ElementFactory> entry :
newElements.getElementFactories().entrySet()) {
mConditionsGuardingFactories.put(
entry.getKey().getEnterConditionChecked(), entry.getValue());
}
newElementIds.addAll(newElements.getElementIds());
}

@ -20,7 +20,7 @@ import java.util.Set;
public abstract class Element<ProductT extends @Nullable Object> implements Supplier<ProductT> {
private final String mId;
protected ConditionalState mOwner;
private ConditionWithResult<ProductT> mEnterCondition;
private @Nullable ConditionWithResult<ProductT> mEnterCondition;
private @Nullable Condition mExitCondition;
/**
@ -38,7 +38,9 @@ public abstract class Element<ProductT extends @Nullable Object> implements Supp
mOwner = owner;
mEnterCondition = createEnterCondition();
mEnterCondition.bindToState(owner);
if (mEnterCondition != null) {
mEnterCondition.bindToState(owner);
}
mExitCondition = createExitCondition();
if (mExitCondition != null) {
@ -47,7 +49,7 @@ public abstract class Element<ProductT extends @Nullable Object> implements Supp
}
/** Must create an ENTER Condition to ensure the element is present in the ConditionalState. */
public abstract ConditionWithResult<ProductT> createEnterCondition();
public abstract @Nullable ConditionWithResult<ProductT> createEnterCondition();
/**
* May create an EXIT Condition to ensure the element is not present after leaving the
@ -55,6 +57,14 @@ public abstract class Element<ProductT extends @Nullable Object> implements Supp
*/
public abstract @Nullable Condition createExitCondition();
/** Replace the enter Condition. */
protected void replaceEnterCondition(ConditionWithResult<ProductT> newEnterCondition) {
assert mOwner != null : "Must be called after bind()";
mEnterCondition = newEnterCondition;
mEnterCondition.bindToState(mOwner);
}
// Supplier implementation
/**
* @return the product of the element (View, Activity, etc.)
@ -63,13 +73,13 @@ public abstract class Element<ProductT extends @Nullable Object> implements Supp
*/
@Override
public ProductT get() {
return getEnterCondition().get();
return getEnterConditionChecked().get();
}
// Supplier implementation
@Override
public boolean hasValue() {
return getEnterCondition().hasValue();
return getEnterConditionChecked().hasValue();
}
/**
@ -79,16 +89,23 @@ public abstract class Element<ProductT extends @Nullable Object> implements Supp
* @return the product of the element (View, Activity, etc.) from a non-NEW ConditionalState
*/
public ProductT getFromPast() {
return getEnterCondition().getFromPast();
return getEnterConditionChecked().getFromPast();
}
/**
* @return an ENTER Condition to ensure the element is present in the ConditionalState.
*/
ConditionWithResult<ProductT> getEnterCondition() {
@Nullable ConditionWithResult<ProductT> getEnterCondition() {
return mEnterCondition;
}
ConditionWithResult<ProductT> getEnterConditionChecked() {
ConditionWithResult<ProductT> enterCondition = getEnterCondition();
assert enterCondition != null
: String.format("Element %s is missing an enter condition", this);
return enterCondition;
}
/**
* @param destinationElementIds ids of the elements in the destination for matching
* @return an EXIT Condition to ensure the element is not present after transitioning to the

@ -49,7 +49,7 @@ public class Elements extends BaseElements {
public static class Builder {
private @Nullable Elements mOwner;
private final ArrayList<Element<?>> mElements = new ArrayList<>();
private final Map<Condition, ElementFactory> mElementFactories = new HashMap<>();
private final Map<Element<?>, ElementFactory> mElementFactories = new HashMap<>();
private final ArrayList<Condition> mOtherEnterConditions = new ArrayList<>();
private final ArrayList<Condition> mOtherExitConditions = new ArrayList<>();
@ -76,22 +76,11 @@ public class Elements extends BaseElements {
return declareElement(element);
}
/**
* See {@link ConditionalState#declareElementFactory(Condition, Callback)}.
*
* @deprecated Use {@link #declareElementFactory(Element, Callback)} instead.}
*/
@Deprecated
public void declareElementFactory(
Condition condition, Callback<Builder> delayedDeclarations) {
assertNotBuilt();
mElementFactories.put(condition, new ElementFactory(mOwner, delayedDeclarations));
}
/** See {@link ConditionalState#declareElementFactory(Element, Callback)}. */
public void declareElementFactory(
Element<?> element, Callback<Elements.Builder> delayedDeclarations) {
declareElementFactory(element.getEnterCondition(), delayedDeclarations);
assertNotBuilt();
mElementFactories.put(element, new ElementFactory(mOwner, delayedDeclarations));
}
/** See {@link ConditionalState#declareNoView(ViewSpec)}. */
@ -111,14 +100,14 @@ public class Elements extends BaseElements {
mOtherEnterConditions.add(condition);
}
/** See {@link ConditionalState#declareEnterConditionAsElement(Condition)}. */
/** See {@link ConditionalState#declareEnterConditionAsElement(ConditionWithResult)}. */
public <ProductT, T extends ConditionWithResult<ProductT>>
Element<ProductT> declareEnterConditionAsElement(T condition) {
assertNotBuilt();
Element<ProductT> element =
new Element<>("CE/" + condition.getDescription()) {
@Override
public ConditionWithResult<ProductT> createEnterCondition() {
public @Nullable ConditionWithResult<ProductT> createEnterCondition() {
return condition;
}

@ -38,7 +38,7 @@ public class FragmentElement<FragmentT extends Fragment, ActivityT extends Fragm
}
@Override
public ConditionWithResult<FragmentT> createEnterCondition() {
public @Nullable ConditionWithResult<FragmentT> createEnterCondition() {
return new FragmentExistsCondition();
}

@ -158,7 +158,7 @@ public class LogicalElement<ParamT> extends Element<Void> {
}
@Override
public ConditionWithResult<Void> createEnterCondition() {
public @Nullable ConditionWithResult<Void> createEnterCondition() {
return new EnterCondition(mIsRunOnUiThread);
}

@ -49,6 +49,16 @@ public class TrafficControl {
sActiveStation = newActiveStation;
}
/**
* Hop off Public Transit - set the active station to null so that a subsequent test can go
* through an entry point again on the same process.
*
* <p>Useful in Robolectric tests.
*/
public static void hopOffPublicTransit() {
sActiveStation = null;
}
public static List<Pair<String, String>> getAllStationsNames() {
return sAllStationNames;
}

@ -9,6 +9,7 @@ import static org.junit.Assert.assertTrue;
import android.app.Activity;
import org.junit.After;
import org.junit.Test;
import org.junit.runner.RunWith;
@ -24,34 +25,30 @@ public class TripUnitTest {
private static final String TAG = "TripUnitTest";
public static class NestedFactoryStation extends Station<Activity> {
public final Condition mOuterCondition;
public final Condition mInnerCondition;
private static class NestedFactoryStation extends Station<Activity> {
public final CallbackHelper mDeclareElementsCallbackHelper = new CallbackHelper();
public final CallbackHelper mOuterCallbackHelper = new CallbackHelper();
public final CallbackHelper mInnerCallbackHelper = new CallbackHelper();
public final Element<String> outerElement;
public Element<String> innerElement;
public NestedFactoryStation(Condition outerCondition, Condition innerCondition) {
public NestedFactoryStation(
ConditionWithResult<String> outerCondition,
ConditionWithResult<String> innerCondition) {
super(null);
mOuterCondition = outerCondition;
mInnerCondition = innerCondition;
}
@Override
public void declareElements(Elements.Builder elements) {
super.declareElements(elements);
elements.declareElement(
declareElement(
LogicalElement.instrumentationThreadLogicalElement(
"LogicalElement 1, always True", () -> Condition.fulfilled()));
elements.declareEnterCondition(
declareEnterCondition(
InstrumentationThreadCondition.from(
"Enter Condition 1, always True", () -> Condition.fulfilled()));
elements.declareExitCondition(
declareExitCondition(
InstrumentationThreadCondition.from(
"Exit Condition 1, always True", () -> Condition.fulfilled()));
elements.declareEnterCondition(mOuterCondition);
elements.declareElementFactory(
mOuterCondition,
outerElement = declareEnterConditionAsElement(outerCondition);
declareElementFactory(
outerElement,
(nestedElements) -> {
nestedElements.declareElement(
LogicalElement.instrumentationThreadLogicalElement(
@ -65,9 +62,10 @@ public class TripUnitTest {
InstrumentationThreadCondition.from(
"Exit Condition 2, always True",
() -> Condition.fulfilled()));
nestedElements.declareEnterCondition(mInnerCondition);
innerElement =
nestedElements.declareEnterConditionAsElement(innerCondition);
nestedElements.declareElementFactory(
mInnerCondition,
innerElement,
(nestedNestedElements) -> {
nestedNestedElements.declareElement(
LogicalElement.instrumentationThreadLogicalElement(
@ -89,12 +87,13 @@ public class TripUnitTest {
}
}
public static class TestCondition extends InstrumentationThreadCondition {
public static class TestCondition extends ConditionWithResult<String> {
public ConditionStatus mConditionStatus =
Condition.awaiting("Waiting for a call to setConditionStatus");
private String mDescription;
TestCondition(String description) {
super(/* isRunOnUiThread= */ false);
mDescription = description;
}
@ -104,8 +103,12 @@ public class TripUnitTest {
}
@Override
public ConditionStatus checkWithSuppliers() {
return mConditionStatus;
public ConditionStatusWithResult<String> resolveWithSuppliers() {
if (mConditionStatus.isFulfilled()) {
return mConditionStatus.withResult("TestCondition's result");
} else {
return mConditionStatus.withoutResult();
}
}
public void setConditionStatus(ConditionStatus conditionStatus) {
@ -113,20 +116,46 @@ public class TripUnitTest {
}
}
@After
public void tearDown() {
TrafficControl.hopOffPublicTransit();
}
@Test
public void testTransitionWithNestedElementFactory() throws Throwable {
Condition alwaysTrueCondition =
InstrumentationThreadCondition.from(
"AlwaysTrueCondition", () -> Condition.fulfilled());
NestedFactoryStation sourceStation =
new NestedFactoryStation(alwaysTrueCondition, alwaysTrueCondition);
sourceStation.setStateActiveWithoutTransition();
TestCondition outerCondition = new TestCondition("outer condition");
TestCondition innerCondition = new TestCondition("inner condition");
NestedFactoryStation destinationStation =
new NestedFactoryStation(outerCondition, innerCondition);
doTestTransitionWithNestedElementFactory(
destinationStation, outerCondition, innerCondition);
}
@Test
public void testTransitionWithNestedElementFactory_replaceCondition() throws Throwable {
TestCondition alwaysFalseCondition = new TestCondition("always false condition");
TestCondition outerCondition = new TestCondition("outer condition");
TestCondition innerCondition = new TestCondition("inner condition");
NestedFactoryStation destinationStation =
new NestedFactoryStation(alwaysFalseCondition, innerCondition);
destinationStation.outerElement.replaceEnterCondition(outerCondition);
doTestTransitionWithNestedElementFactory(
destinationStation, outerCondition, innerCondition);
}
private void doTestTransitionWithNestedElementFactory(
NestedFactoryStation destinationStation,
TestCondition outerCondition,
TestCondition innerCondition)
throws Throwable {
TestCondition alwaysTrueCondition = new TestCondition("always true condition");
alwaysTrueCondition.setConditionStatus(Condition.fulfilled());
NestedFactoryStation sourceStation =
new NestedFactoryStation(alwaysTrueCondition, alwaysTrueCondition);
sourceStation.setStateActiveWithoutTransition();
Thread transitionThread =
new Thread(
() -> {
@ -171,9 +200,15 @@ public class TripUnitTest {
}
// All elements from nested factories added to the destination elements.
assertEquals(3, destinationStation.getElements().getElements().size());
// 3 LogicalElements: constructor, outer factory, inner factory
// +2 outerElement and innerElement
assertEquals(5, destinationStation.getElements().getElements().size());
// 2 factories: outer factory, inner factory
assertEquals(2, destinationStation.getElements().getElementFactories().size());
assertEquals(5, destinationStation.getElements().getOtherEnterConditions().size());
// 3 enter Conditions: constructor, outer factory, inner factory
assertEquals(3, destinationStation.getElements().getOtherEnterConditions().size());
// 3 exit Conditions: constructor, outer factory, inner factory
assertEquals(3, destinationStation.getElements().getOtherExitConditions().size());
// Conditions started and stopped monitoring during transition.

@ -67,7 +67,7 @@ public class ViewElement<ViewT extends View> extends Element<ViewT> {
}
@Override
public ConditionWithResult<ViewT> createEnterCondition() {
public @Nullable ConditionWithResult<ViewT> createEnterCondition() {
Matcher<View> viewMatcher = mViewSpec.getViewMatcher();
DisplayedCondition.Options conditionOptions =
DisplayedCondition.newOptions()

@ -12,6 +12,7 @@ import org.chromium.base.test.transit.ConditionWithResult;
import org.chromium.base.test.transit.Element;
import org.chromium.base.test.transit.Transition;
import org.chromium.base.test.transit.TravelException;
import org.chromium.build.annotations.Nullable;
import org.chromium.content_public.browser.WebContents;
import org.chromium.content_public.browser.test.transit.HtmlConditions.DisplayedCondition;
import org.chromium.content_public.browser.test.transit.HtmlConditions.NotDisplayedCondition;
@ -34,7 +35,7 @@ public class HtmlElement extends Element<Rect> {
}
@Override
public ConditionWithResult<Rect> createEnterCondition() {
public @Nullable ConditionWithResult<Rect> createEnterCondition() {
return new DisplayedCondition(mWebContentsSupplier, mHtmlElementSpec.getHtmlId());
}

@ -10,6 +10,7 @@ import org.chromium.base.supplier.Supplier;
import org.chromium.base.test.transit.Condition;
import org.chromium.base.test.transit.ConditionWithResult;
import org.chromium.base.test.transit.Element;
import org.chromium.build.annotations.Nullable;
/** Represents the soft keyboard shown, expecting it to hide after exiting the ConditionalState. */
public class SoftKeyboardElement extends Element<Boolean> {
@ -22,7 +23,7 @@ public class SoftKeyboardElement extends Element<Boolean> {
}
@Override
public ConditionWithResult<Boolean> createEnterCondition() {
public @Nullable ConditionWithResult<Boolean> createEnterCondition() {
return new SoftKeyboardCondition(mActivitySupplier, /* expectShowing= */ true);
}