1 /*
2  * Copyright (C) 2019 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.server.wm.intent;
18 
19 import static java.util.stream.Collectors.toList;
20 
21 import android.content.ComponentName;
22 import android.content.Intent;
23 import android.server.wm.ActivityManagerState;
24 
25 import com.google.common.collect.Lists;
26 
27 import org.json.JSONArray;
28 import org.json.JSONException;
29 import org.json.JSONObject;
30 
31 import java.util.ArrayList;
32 import java.util.Arrays;
33 import java.util.Collections;
34 import java.util.List;
35 import java.util.Map;
36 import java.util.Objects;
37 import java.util.stream.Collectors;
38 import java.util.stream.Stream;
39 
40 /**
41  * The intent tests are generated by running a series of intents and then recording the end state
42  * of the system. This class contains all the models needed to store the intents that were used to
43  * create the test case and the end states so that they can be asserted on.
44  *
45  * All test cases are serialized to JSON and stored in a single file per testcase.
46  */
47 public class Persistence {
48 
49     /**
50      * The highest level entity in the JSON file
51      */
52     public static class TestCase {
53         private static final String SETUP_KEY = "setup";
54         private static final String INITIAL_STATE_KEY = "initialState";
55         private static final String END_STATE_KEY = "endState";
56 
57         /**
58          * Contains the {@link android.content.Intent}-s that will be launched in this test case.
59          */
60         private final Setup mSetup;
61 
62         /**
63          * The state of the system after the {@link Setup#mInitialIntents} have been launched.
64          */
65         private final StateDump mInitialState;
66 
67         /**
68          * The state of the system after the {@link Setup#mAct} have been launched
69          */
70         private final StateDump mEndState;
71 
72         /**
73          * The name of the testCase, usually the file name it is stored in.
74          * Not actually persisted to json, since it is only used for presentation purposes.
75          */
76         private final String mName;
77 
TestCase(Setup setup, StateDump initialState, StateDump endState, String name)78         public TestCase(Setup setup, StateDump initialState,
79                 StateDump endState, String name) {
80             mSetup = setup;
81             mInitialState = initialState;
82             mEndState = endState;
83             mName = name;
84         }
85 
toJson()86         public JSONObject toJson() throws JSONException {
87             return new JSONObject()
88                     .put(SETUP_KEY, mSetup.toJson())
89                     .put(INITIAL_STATE_KEY, mInitialState.toJson())
90                     .put(END_STATE_KEY, mEndState.toJson());
91         }
92 
fromJson(JSONObject object, Map<String, IntentFlag> table, String name)93         public static TestCase fromJson(JSONObject object,
94                 Map<String, IntentFlag> table, String name) throws JSONException {
95             return new TestCase(Setup.fromJson(object.getJSONObject(SETUP_KEY), table),
96                     StateDump.fromJson(object.getJSONObject(INITIAL_STATE_KEY)),
97                     StateDump.fromJson(object.getJSONObject(END_STATE_KEY)), name);
98         }
99 
getSetup()100         public Setup getSetup() {
101             return mSetup;
102         }
103 
getInitialState()104         public StateDump getInitialState() {
105             return mInitialState;
106         }
107 
getName()108         public String getName() {
109             return mName;
110         }
111 
getEndState()112         public StateDump getEndState() {
113             return mEndState;
114         }
115     }
116 
117     /**
118      * Setup consists of two stages. Firstly a list of intents to bring the system in the state we
119      * want to test something in. Secondly a list of intents to bring the system to the final state.
120      */
121     public static class Setup {
122         private static final String INITIAL_INTENT_KEY = "initialIntents";
123         private static final String ACT_KEY = "act";
124         /**
125          * The intent(s) used to bring the system to the initial state.
126          */
127         private final List<GenerationIntent> mInitialIntents;
128 
129         /**
130          * The intent(s) that we actually want to test.
131          */
132         private final List<GenerationIntent> mAct;
133 
Setup(List<GenerationIntent> initialIntents, List<GenerationIntent> act)134         public Setup(List<GenerationIntent> initialIntents, List<GenerationIntent> act) {
135             mInitialIntents = initialIntents;
136             mAct = act;
137         }
138 
componentsInCase()139         public List<ComponentName> componentsInCase() {
140             return Stream.concat(mInitialIntents.stream(), mAct.stream())
141                     .map(GenerationIntent::getActualIntent)
142                     .map(Intent::getComponent)
143                     .collect(Collectors.toList());
144         }
145 
toJson()146         public JSONObject toJson() throws JSONException {
147             return new JSONObject()
148                     .put(INITIAL_INTENT_KEY, intentsToJson(mInitialIntents))
149                     .put(ACT_KEY, intentsToJson(mAct));
150         }
151 
fromJson(JSONObject object, Map<String, IntentFlag> table)152         public static Setup fromJson(JSONObject object,
153                 Map<String, IntentFlag> table) throws JSONException {
154             List<GenerationIntent> initialState = intentsFromJson(
155                     object.getJSONArray(INITIAL_INTENT_KEY), table);
156             List<GenerationIntent> act = intentsFromJson(object.getJSONArray(ACT_KEY), table);
157 
158             return new Setup(initialState, act);
159         }
160 
161 
intentsToJson(List<GenerationIntent> intents)162         public static JSONArray intentsToJson(List<GenerationIntent> intents)
163                 throws JSONException {
164 
165             JSONArray intentArray = new JSONArray();
166             for (GenerationIntent intent : intents) {
167                 intentArray.put(intent.toJson());
168             }
169             return intentArray;
170         }
171 
intentsFromJson(JSONArray intentArray, Map<String, IntentFlag> table)172         public static List<GenerationIntent> intentsFromJson(JSONArray intentArray,
173                 Map<String, IntentFlag> table) throws JSONException {
174             List<GenerationIntent> intents = new ArrayList<>();
175 
176             for (int i = 0; i < intentArray.length(); i++) {
177                 JSONObject object = (JSONObject) intentArray.get(i);
178                 GenerationIntent intent = GenerationIntent.fromJson(object, table);
179 
180                 intents.add(intent);
181             }
182 
183             return intents;
184         }
185 
getInitialIntents()186         public List<GenerationIntent> getInitialIntents() {
187             return mInitialIntents;
188         }
189 
getAct()190         public List<GenerationIntent> getAct() {
191             return mAct;
192         }
193     }
194 
195     /**
196      * An representation of an {@link android.content.Intent} that can be (de)serialized to / from
197      * JSON. It abstracts whether the context it should be started from is implicitly or explicitly
198      * specified.
199      */
200     interface GenerationIntent {
getActualIntent()201         Intent getActualIntent();
202 
toJson()203         JSONObject toJson() throws JSONException;
204 
getLaunchFromIndex(int currentPosition)205         int getLaunchFromIndex(int currentPosition);
206 
startForResult()207         boolean startForResult();
208 
fromJson(JSONObject object, Map<String, IntentFlag> table)209         static GenerationIntent fromJson(JSONObject object, Map<String, IntentFlag> table)
210                 throws JSONException {
211             if (object.has(LaunchFromIntent.LAUNCH_FROM_KEY)) {
212                 return LaunchFromIntent.fromJson(object, table);
213             } else {
214                 return LaunchIntent.fromJson(object, table);
215             }
216         }
217     }
218 
219     /**
220      * Representation of {@link android.content.Intent} used by the {@link LaunchSequence} api.
221      * It be can used to normally start activities, to start activities for result and Intent Flags
222      * can be added using {@link LaunchIntent#withFlags(IntentFlag...)}
223      */
224     static class LaunchIntent implements GenerationIntent {
225         private static final String FLAGS_KEY = "flags";
226         private static final String PACKAGE_KEY = "package";
227         private static final String CLASS_KEY = "class";
228         private static final String START_FOR_RESULT_KEY = "startForResult";
229 
230         private final List<IntentFlag> mIntentFlags;
231         private final ComponentName mComponentName;
232         private final boolean mStartForResult;
233 
LaunchIntent(List<IntentFlag> intentFlags, ComponentName componentName, boolean startForResult)234         public LaunchIntent(List<IntentFlag> intentFlags, ComponentName componentName,
235                 boolean startForResult) {
236             mIntentFlags = intentFlags;
237             mComponentName = componentName;
238             mStartForResult = startForResult;
239         }
240 
241         @Override
getActualIntent()242         public Intent getActualIntent() {
243             return new Intent().setComponent(mComponentName).setFlags(buildFlag());
244         }
245 
246         @Override
getLaunchFromIndex(int currentPosition)247         public int getLaunchFromIndex(int currentPosition) {
248             return currentPosition - 1;
249         }
250 
251         @Override
startForResult()252         public boolean startForResult() {
253             return mStartForResult;
254         }
255 
buildFlag()256         public int buildFlag() {
257             int flag = 0;
258             for (IntentFlag intentFlag : mIntentFlags) {
259                 flag |= intentFlag.flag;
260             }
261 
262             return flag;
263         }
264 
humanReadableFlags()265         public String humanReadableFlags() {
266             return mIntentFlags.stream().map(IntentFlag::toString).collect(
267                     Collectors.joining(" | "));
268         }
269 
fromJson(JSONObject fakeIntent, Map<String, IntentFlag> table)270         public static LaunchIntent fromJson(JSONObject fakeIntent, Map<String, IntentFlag> table)
271                 throws JSONException {
272             List<IntentFlag> flags = IntentFlag.parse(table, fakeIntent.getString(FLAGS_KEY));
273 
274             boolean startForResult = fakeIntent.optBoolean(START_FOR_RESULT_KEY, false);
275             return new LaunchIntent(flags,
276                     new ComponentName(
277                             fakeIntent.getString(PACKAGE_KEY),
278                             fakeIntent.getString(CLASS_KEY)), startForResult);
279 
280         }
281 
282         @Override
toJson()283         public JSONObject toJson() throws JSONException {
284             return new JSONObject().put(FLAGS_KEY, this.humanReadableFlags())
285                     .put(CLASS_KEY, this.mComponentName.getClassName())
286                     .put(PACKAGE_KEY, this.mComponentName.getPackageName())
287                     .put(START_FOR_RESULT_KEY, mStartForResult);
288         }
289 
withFlags(IntentFlag... flags)290         public LaunchIntent withFlags(IntentFlag... flags) {
291             List<IntentFlag> intentFlags = Lists.newArrayList(mIntentFlags);
292             Collections.addAll(intentFlags, flags);
293             return new LaunchIntent(intentFlags, mComponentName, mStartForResult);
294         }
295 
getIntentFlags()296         public List<IntentFlag> getIntentFlags() {
297             return mIntentFlags;
298         }
299 
getComponentName()300         public ComponentName getComponentName() {
301             return mComponentName;
302         }
303 
isStartForResult()304         public boolean isStartForResult() {
305             return mStartForResult;
306         }
307     }
308 
309     /**
310      * Representation of {@link android.content.Intent} used by the {@link LaunchSequence} api.
311      * It can used to normally start activities, to start activities for result and Intent Flags
312      * can
313      * be added using {@link LaunchIntent#withFlags(IntentFlag...)} just like {@link LaunchIntent}
314      *
315      * However {@link LaunchFromIntent}  also supports launching from a activity earlier in the
316      * launch sequence. This can be done using {@link LaunchSequence#act} and related methods.
317      */
318     static class LaunchFromIntent implements GenerationIntent {
319         static final String LAUNCH_FROM_KEY = "launchFrom";
320 
321         /**
322          * The underlying {@link LaunchIntent} that we are wrapping with the launch point behaviour.
323          */
324         private final LaunchIntent mLaunchIntent;
325 
326         /**
327          * The index in the activityLog maintained by {@link LaunchRunner}, used to retrieve the
328          * activity from the log to start this {@link LaunchIntent} from.
329          */
330         private final int mLaunchFrom;
331 
LaunchFromIntent(LaunchIntent fakeIntent, int launchFrom)332         LaunchFromIntent(LaunchIntent fakeIntent, int launchFrom) {
333             mLaunchIntent = fakeIntent;
334             mLaunchFrom = launchFrom;
335         }
336 
337 
338         @Override
getActualIntent()339         public Intent getActualIntent() {
340             return mLaunchIntent.getActualIntent();
341         }
342 
343         @Override
getLaunchFromIndex(int currentPosition)344         public int getLaunchFromIndex(int currentPosition) {
345             return mLaunchFrom;
346         }
347 
348         @Override
startForResult()349         public boolean startForResult() {
350             return mLaunchIntent.mStartForResult;
351         }
352 
353         @Override
toJson()354         public JSONObject toJson() throws JSONException {
355             return mLaunchIntent.toJson()
356                     .put(LAUNCH_FROM_KEY, mLaunchFrom);
357         }
358 
fromJson(JSONObject object, Map<String, IntentFlag> table)359         public static LaunchFromIntent fromJson(JSONObject object, Map<String, IntentFlag> table)
360                 throws JSONException {
361             LaunchIntent fakeIntent = LaunchIntent.fromJson(object, table);
362             int launchFrom = object.optInt(LAUNCH_FROM_KEY, -1);
363 
364             return new LaunchFromIntent(fakeIntent, launchFrom);
365         }
366 
prepareSerialisation(List<LaunchFromIntent> intents)367         static List<GenerationIntent> prepareSerialisation(List<LaunchFromIntent> intents) {
368             return prepareSerialisation(intents, 0);
369         }
370 
371         // In serialized form we only want to store the launch from index if it deviates from the
372         // default, the default being the previous activity.
prepareSerialisation(List<LaunchFromIntent> intents, int base)373         static List<GenerationIntent> prepareSerialisation(List<LaunchFromIntent> intents,
374                 int base) {
375             List<GenerationIntent> serializeIntents = Lists.newArrayList();
376             for (int i = 0; i < intents.size(); i++) {
377                 LaunchFromIntent launchFromIntent = intents.get(i);
378                 serializeIntents.add(launchFromIntent.forget(base + i));
379             }
380 
381             return serializeIntents;
382         }
383 
forget(int currentIndex)384         public GenerationIntent forget(int currentIndex) {
385             if (mLaunchFrom == currentIndex - 1) {
386                 return this.mLaunchIntent;
387             } else {
388                 return this;
389             }
390         }
391 
getLaunchFrom()392         public int getLaunchFrom() {
393             return mLaunchFrom;
394         }
395     }
396 
397     /**
398      * An intent flag that also stores the name of the flag.
399      * It is used to be able to put the flags in human readable form in the JSON file.
400      */
401     static class IntentFlag {
402         /**
403          * The underlying flag, should be a value from Intent.FLAG_ACTIVITY_*.
404          */
405         public final int flag;
406         /**
407          * The name of the flag.
408          */
409         public final String name;
410 
IntentFlag(int flag, String name)411         public IntentFlag(int flag, String name) {
412             this.flag = flag;
413             this.name = name;
414         }
415 
getFlag()416         public int getFlag() {
417             return flag;
418         }
419 
getName()420         public String getName() {
421             return name;
422         }
423 
combine(IntentFlag other)424         public int combine(IntentFlag other) {
425             return other.flag | flag;
426         }
427 
parse(Map<String, IntentFlag> names, String flagsToParse)428         public static List<IntentFlag> parse(Map<String, IntentFlag> names, String flagsToParse) {
429             String[] split = flagsToParse.replaceAll("\\s", "").split("\\|");
430             return Arrays.stream(split).map(names::get).collect(toList());
431         }
432 
toString()433         public String toString() {
434             return name;
435         }
436     }
437 
flag(int flag, String name)438     static IntentFlag flag(int flag, String name) {
439         return new IntentFlag(flag, name);
440     }
441 
442     public static class StateDump {
443         final List<StackState> mStacks;
444 
fromStacks(List<ActivityManagerState.ActivityStack> activityStacks, List<ActivityManagerState.ActivityStack> baseStacks)445         public static StateDump fromStacks(List<ActivityManagerState.ActivityStack> activityStacks,
446                 List<ActivityManagerState.ActivityStack> baseStacks) {
447             List<StackState> stacks = new ArrayList<>();
448             for (ActivityManagerState.ActivityStack stack : trimStacks(activityStacks,
449                     baseStacks)) {
450                 stacks.add(new StackState(stack));
451             }
452 
453             return new StateDump(stacks);
454         }
455 
StateDump(List<StackState> stacks)456         public StateDump(List<StackState> stacks) {
457             mStacks = stacks;
458         }
459 
toJson()460         JSONObject toJson() throws JSONException {
461             JSONArray stacks = new JSONArray();
462             for (StackState stack : mStacks) {
463                 stacks.put(stack.toJson());
464             }
465 
466             return new JSONObject().put("stacks", stacks);
467         }
468 
fromJson(JSONObject object)469         static StateDump fromJson(JSONObject object) throws JSONException {
470             JSONArray jsonTasks = object.getJSONArray("stacks");
471             List<StackState> stacks = new ArrayList<>();
472 
473             for (int i = 0; i < jsonTasks.length(); i++) {
474                 stacks.add(StackState.fromJson((JSONObject) jsonTasks.get(i)));
475             }
476 
477             return new StateDump(stacks);
478         }
479 
480         /**
481          * To make the state dump non device specific we remove every stack that was present
482          * in the system before recording, by their ID. For example a stack containing the launcher
483          * activity.
484          */
trimStacks( List<ActivityManagerState.ActivityStack> toTrim, List<ActivityManagerState.ActivityStack> trimFrom)485         public static List<ActivityManagerState.ActivityStack> trimStacks(
486                 List<ActivityManagerState.ActivityStack> toTrim,
487                 List<ActivityManagerState.ActivityStack> trimFrom) {
488 
489             for (ActivityManagerState.ActivityStack stack : trimFrom) {
490                 toTrim.removeIf(t -> t.getStackId() == stack.getStackId());
491             }
492 
493             return toTrim;
494         }
495 
496         @Override
equals(Object o)497         public boolean equals(Object o) {
498             if (this == o) return true;
499             if (o == null || getClass() != o.getClass()) return false;
500             StateDump stateDump = (StateDump) o;
501             return Objects.equals(mStacks, stateDump.mStacks);
502         }
503 
504         @Override
hashCode()505         public int hashCode() {
506             return Objects.hash(mStacks);
507         }
508     }
509 
510     /**
511      * A simplified JSON version of the information in {@link ActivityManagerState.ActivityStack}
512      */
513     public static class StackState {
514         private static final String TASKS_KEY = "tasks";
515         private static final String RESUMED_ACTIVITY_KEY = "resumedActivity";
516 
517         /**
518          * The component name of the resumedActivity in this Stack, empty string if there is none.
519          */
520         private final String mResumedActivity;
521         /**
522          * The Tasks in this stack ordered from most recent to least recent.
523          */
524         private final List<TaskState> mTasks;
525 
StackState(String resumedActivity, List<TaskState> tasks)526         public StackState(String resumedActivity, List<TaskState> tasks) {
527             mResumedActivity = resumedActivity;
528             mTasks = tasks;
529         }
530 
StackState(ActivityManagerState.ActivityStack stack)531         public StackState(ActivityManagerState.ActivityStack stack) {
532             this.mResumedActivity = stack.getResumedActivity();
533             mTasks = new ArrayList<>();
534             for (ActivityManagerState.ActivityTask task : stack.getTasks()) {
535                 this.mTasks.add(new TaskState(task));
536             }
537         }
538 
toJson()539         JSONObject toJson() throws JSONException {
540             JSONArray jsonTasks = new JSONArray();
541 
542             for (TaskState task : mTasks) {
543                 jsonTasks.put(task.toJson());
544             }
545 
546             return new JSONObject()
547                     .put(TASKS_KEY, jsonTasks)
548                     .put(RESUMED_ACTIVITY_KEY, mResumedActivity);
549         }
550 
fromJson(JSONObject object)551         static StackState fromJson(JSONObject object) throws JSONException {
552             JSONArray jsonTasks = object.getJSONArray(TASKS_KEY);
553             List<TaskState> tasks = new ArrayList<>();
554 
555             for (int i = 0; i < jsonTasks.length(); i++) {
556                 tasks.add(TaskState.fromJson((JSONObject) jsonTasks.get(i)));
557             }
558 
559             return new StackState(object.optString(RESUMED_ACTIVITY_KEY, ""), tasks);
560         }
561 
getResumedActivity()562         public String getResumedActivity() {
563             return mResumedActivity;
564         }
565 
getTasks()566         public List<TaskState> getTasks() {
567             return mTasks;
568         }
569 
570         @Override
equals(Object o)571         public boolean equals(Object o) {
572             if (this == o) return true;
573             if (o == null || getClass() != o.getClass()) return false;
574             StackState stack = (StackState) o;
575             return Objects.equals(mTasks, stack.mTasks);
576         }
577 
578         @Override
hashCode()579         public int hashCode() {
580             return Objects.hash(mResumedActivity, mTasks);
581         }
582     }
583 
584     public static class TaskState {
585 
586         private static final String ACTIVITIES_KEY = "activities";
587 
588         /**
589          * The activities in this task ordered from most recent to least recent.
590          */
591         private List<ActivityState> mActivities = new ArrayList<>();
592 
TaskState(List<ActivityState> activities)593         public TaskState(List<ActivityState> activities) {
594             mActivities = activities;
595         }
596 
TaskState(ActivityManagerState.ActivityTask state)597         public TaskState(ActivityManagerState.ActivityTask state) {
598             for (ActivityManagerState.Activity activity : state.getActivities()) {
599                 this.mActivities.add(new ActivityState(activity));
600             }
601         }
602 
toJson()603         JSONObject toJson() throws JSONException {
604             JSONArray jsonActivities = new JSONArray();
605 
606             for (ActivityState activity : mActivities) {
607                 jsonActivities.put(activity.toJson());
608             }
609 
610             return new JSONObject()
611                     .put(ACTIVITIES_KEY, jsonActivities);
612         }
613 
fromJson(JSONObject object)614         static TaskState fromJson(JSONObject object) throws JSONException {
615             JSONArray jsonActivities = object.getJSONArray(ACTIVITIES_KEY);
616             List<ActivityState> activities = new ArrayList<>();
617 
618             for (int i = 0; i < jsonActivities.length(); i++) {
619                 activities.add(ActivityState.fromJson((JSONObject) jsonActivities.get(i)));
620             }
621 
622             return new TaskState(activities);
623         }
624 
getActivities()625         public List<ActivityState> getActivities() {
626             return mActivities;
627         }
628 
629         @Override
equals(Object o)630         public boolean equals(Object o) {
631             if (this == o) return true;
632             if (o == null || getClass() != o.getClass()) return false;
633             TaskState task = (TaskState) o;
634             return Objects.equals(mActivities, task.mActivities);
635         }
636 
637         @Override
hashCode()638         public int hashCode() {
639             return Objects.hash(mActivities);
640         }
641     }
642 
643     public static class ActivityState {
644         private static final String NAME_KEY = "name";
645         private static final String STATE_KEY = "state";
646         /**
647          * The componentName of this activity.
648          */
649         private final String mComponentName;
650 
651         /**
652          * The lifecycle state this activity is in.
653          */
654         private final String mLifeCycleState;
655 
ActivityState(String name, String state)656         public ActivityState(String name, String state) {
657             mComponentName = name;
658             mLifeCycleState = state;
659         }
660 
ActivityState(ActivityManagerState.Activity activity)661         public ActivityState(ActivityManagerState.Activity activity) {
662             mComponentName = activity.getName();
663             mLifeCycleState = activity.getState();
664         }
665 
666 
toJson()667         JSONObject toJson() throws JSONException {
668             return new JSONObject().put(NAME_KEY, mComponentName).put(STATE_KEY, mLifeCycleState);
669         }
670 
fromJson(JSONObject object)671         static ActivityState fromJson(JSONObject object) throws JSONException {
672             return new ActivityState(object.getString(NAME_KEY), object.getString(STATE_KEY));
673         }
674 
675         @Override
equals(Object o)676         public boolean equals(Object o) {
677             if (this == o) return true;
678             if (o == null || getClass() != o.getClass()) return false;
679             ActivityState activity = (ActivityState) o;
680             return Objects.equals(mComponentName, activity.mComponentName) &&
681                     Objects.equals(mLifeCycleState, activity.mLifeCycleState);
682         }
683 
684         @Override
hashCode()685         public int hashCode() {
686             return Objects.hash(mComponentName, mLifeCycleState);
687         }
688 
getName()689         public String getName() {
690             return mComponentName;
691         }
692 
getState()693         public String getState() {
694             return mLifeCycleState;
695         }
696     }
697 }
698