• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2023 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 package org.chromium.base.test.transit;
6 
7 import static org.junit.Assert.fail;
8 
9 import androidx.annotation.IntDef;
10 
11 import java.lang.annotation.Retention;
12 import java.lang.annotation.RetentionPolicy;
13 import java.util.List;
14 
15 /**
16  * Base class for states with conditions for entering and exiting them.
17  *
18  * <p>Conditions include the existence of {@link Elements}, e.g. Views.
19  *
20  * <pre>ConditionalStates can be in the following phases:
21  * - NEW: Inactive, just created. No transition has started.
22  * - TRANSITIONING_TO: A transition into the state has started, but enter conditions might not be
23  *     fulfilled yet.
24  * - ACTIVE: Active, declared elements should exist.
25  * - TRANSITIONING_FROM: A transition out of the state has started, but exit conditions are not
26  *     fulfilled yet.
27  * - FINISHED: Inactive, transition away is done.
28  * </pre>
29  *
30  * <p>The lifecycle of ConditionalStates is linear:
31  *
32  * <p>NEW > TRANSITIONING_TO > ACTIVE > TRANSITIONING_FROM > FINISHED
33  *
34  * <p>Once FINISHED, the ConditionalState does not change state anymore.
35  *
36  * <p>This is the base class for {@link TransitStation} and {@link StationFacility}.
37  */
38 public abstract class ConditionalState {
39     @Phase private int mLifecyclePhase = Phase.NEW;
40     private Elements mElements;
41 
42     /** Lifecycle phases of ConditionalState. */
43     @IntDef({
44         Phase.NEW,
45         Phase.TRANSITIONING_TO,
46         Phase.ACTIVE,
47         Phase.TRANSITIONING_FROM,
48         Phase.FINISHED
49     })
50     @Retention(RetentionPolicy.SOURCE)
51     public @interface Phase {
52         int NEW = 0;
53         int TRANSITIONING_TO = 1;
54         int ACTIVE = 2;
55         int TRANSITIONING_FROM = 3;
56         int FINISHED = 4;
57     }
58 
59     /**
60      * Declare the {@link Elements} that define this ConditionalState, such as Views.
61      *
62      * <p>Transit-layer {@link TransitStation}s and {@link StationFacility}s should override this
63      * and use the |elements| param to declare what elements need to be waited for for the state to
64      * be considered active.
65      *
66      * @param elements use the #declare___() methods to describe the Elements that define the state.
67      */
declareElements(Elements.Builder elements)68     public abstract void declareElements(Elements.Builder elements);
69 
getEnterConditions()70     List<Condition> getEnterConditions() {
71         initElements();
72         return mElements.getEnterConditions();
73     }
74 
getExitConditions()75     List<Condition> getExitConditions() {
76         initElements();
77         return mElements.getExitConditions();
78     }
79 
initElements()80     private void initElements() {
81         if (mElements == null) {
82             Elements.Builder builder = new Elements.Builder();
83             declareElements(builder);
84             mElements = builder.build(this);
85         }
86     }
87 
setStateTransitioningTo()88     void setStateTransitioningTo() {
89         assertInPhase(Phase.NEW);
90         mLifecyclePhase = Phase.TRANSITIONING_TO;
91         onStartMonitoringTransitionTo();
92         for (Condition condition : getEnterConditions()) {
93             condition.onStartMonitoring();
94         }
95     }
96 
97     /** Hook to setup observers for the transition into the ConditionalState. */
onStartMonitoringTransitionTo()98     protected void onStartMonitoringTransitionTo() {}
99 
setStateActive()100     void setStateActive() {
101         assertInPhase(Phase.TRANSITIONING_TO);
102         mLifecyclePhase = Phase.ACTIVE;
103         onStopMonitoringTransitionTo();
104     }
105 
106     /** Hook to cleanup observers for the transition into the ConditionalState. */
onStopMonitoringTransitionTo()107     protected void onStopMonitoringTransitionTo() {}
108 
setStateTransitioningFrom()109     void setStateTransitioningFrom() {
110         assertInPhase(Phase.ACTIVE);
111         mLifecyclePhase = Phase.TRANSITIONING_FROM;
112         onStartMonitoringTransitionFrom();
113         for (Condition condition : getExitConditions()) {
114             condition.onStartMonitoring();
115         }
116     }
117 
118     /** Hook to setup observers for the transition from the ConditionalState. */
onStartMonitoringTransitionFrom()119     protected void onStartMonitoringTransitionFrom() {}
120 
setStateFinished()121     void setStateFinished() {
122         assertInPhase(Phase.TRANSITIONING_FROM);
123         mLifecyclePhase = Phase.FINISHED;
124         onStopMonitoringTransitionFrom();
125     }
126 
127     /** Hook to cleanup observers for the transition from the ConditionalState. */
onStopMonitoringTransitionFrom()128     protected void onStopMonitoringTransitionFrom() {}
129 
130     /**
131      * @return the lifecycle {@link Phase} this ConditionalState is in.
132      */
getPhase()133     public @Phase int getPhase() {
134         return mLifecyclePhase;
135     }
136 
137     /** Assert this ConditionalState is in an expected lifecycle {@link Phase}. */
assertInPhase(@hase int expectedPhase)138     public void assertInPhase(@Phase int expectedPhase) {
139         if (mLifecyclePhase != expectedPhase) {
140             fail(
141                     String.format(
142                             "%s should have been in %s, but was %s",
143                             this, phaseToString(expectedPhase), phaseToString(mLifecyclePhase)));
144         }
145     }
146 
147     /** Check the enter Conditions are still fulfilled. */
recheckEnterConditions()148     public final void recheckEnterConditions() {
149         assertInPhase(Phase.ACTIVE);
150         ConditionChecker.check(getEnterConditions());
151     }
152 
153     /**
154      * @return a String representation of a lifecycle {@link Phase}.
155      */
phaseToString(@hase int phase)156     public static String phaseToString(@Phase int phase) {
157         switch (phase) {
158             case Phase.NEW:
159                 return "Phase.NEW";
160             case Phase.TRANSITIONING_TO:
161                 return "Phase.TRANSITIONING_TO";
162             case Phase.ACTIVE:
163                 return "Phase.ACTIVE";
164             case Phase.TRANSITIONING_FROM:
165                 return "Phase.TRANSITIONING_AWAY";
166             case Phase.FINISHED:
167                 return "Phase.FINISHED";
168             default:
169                 throw new IllegalArgumentException("No string representation for phase " + phase);
170         }
171     }
172 
phaseToShortString(@hase int phase)173     public static String phaseToShortString(@Phase int phase) {
174         switch (phase) {
175             case Phase.NEW:
176                 return "NEW";
177             case Phase.TRANSITIONING_TO:
178                 return "TRANSITIONING_TO";
179             case Phase.ACTIVE:
180                 return "ACTIVE";
181             case Phase.TRANSITIONING_FROM:
182                 return "TRANSITIONING_AWAY";
183             case Phase.FINISHED:
184                 return "FINISHED";
185             default:
186                 throw new IllegalArgumentException(
187                         "No short string representation for phase " + phase);
188         }
189     }
190 }
191