1 /*
2  * Copyright (C) 2018 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 package com.example.android.intentplayground;
17 
18 import android.content.ComponentName;
19 import android.content.Intent;
20 import android.os.Parcel;
21 import android.os.Parcelable;
22 
23 import java.util.Arrays;
24 import java.util.Collections;
25 import java.util.LinkedList;
26 import java.util.List;
27 
28 /**
29  * This class represents a node in the tree of tasks. It can either represent a task
30  * or an activity.
31  */
32 public class Node implements Parcelable, Comparable<Node> {
33     static final int NEW_TASK_ID = 0xa4d701d;
34     public static final int ROOT_NODE_ID = 0xAABBCCDD;
35     public int mTaskId;
36     public List<Node> mChildren = new LinkedList<>();
37     public ComponentName mName;
38     private static final int CURRENT  = 0x1;
39     private static final int MODIFIED  = 0x2;
40     private static final int NEW  = 0x4;
41     private boolean mIsTaskNode;
42     private int mOptionFlags;
43     private Intent mIntent;
44 
Node(ComponentName data)45     Node(ComponentName data) {
46         mIsTaskNode = false;
47         mName = data;
48     }
49 
50     /**
51      * Create a task Node.
52      * @param taskId the id of the task.
53      */
Node(int taskId)54     Node(int taskId) {
55         mIsTaskNode = true;
56         mTaskId = taskId;
57     }
58 
59     /**
60      * Creates a Node with the same data as the parameter (copy constructor).
61      * @param other Node to copy over.
62      */
Node(Node other)63     Node(Node other) {
64         if (other.mIsTaskNode) {
65             mIsTaskNode = true;
66             mTaskId = other.mTaskId;
67         } else {
68             mIsTaskNode = false;
69             mName = other.mName.clone();
70         }
71         mOptionFlags = other.mOptionFlags;
72         mIntent = other.mIntent;
73         other.mChildren.forEach(child -> addChild(new Node(child)));
74     }
75 
76     /**
77      * Adds a child to this Node's children.
78      * @param child The child node to add.
79      * @return returns This Node object for method chaining.
80      */
addChild(Node child)81     Node addChild(Node child) {
82         mChildren.add(child);
83         return this;
84     }
85 
86     /**
87      * Adds a child to the beginning of the list of this Node's children.
88      * @param child The child node to add.
89      * @return This Node object for method chaining.
90      */
addFirstChild(Node child)91     Node addFirstChild(Node child) {
92         mChildren.add(0, child);
93         return this;
94     }
95 
96     /**
97      * Clear children from this Node.
98      * @return returns This Node object for method chaining.
99      */
clearChildren()100     Node clearChildren() {
101         mChildren.clear();
102         return this;
103     }
104 
newTaskNode()105     static Node newTaskNode() {
106         return new Node(NEW_TASK_ID);
107     }
108 
newRootNode()109     static Node newRootNode() {
110         return new Node(ROOT_NODE_ID);
111     }
112 
isModified()113     boolean isModified() {
114         return (mOptionFlags & MODIFIED) != 0;
115     }
116 
setModified(boolean value)117     void setModified(boolean value) {
118         if (value) {
119             mOptionFlags |= MODIFIED;
120         } else {
121             mOptionFlags &= ~MODIFIED;
122         }
123     }
124 
isNew()125     boolean isNew() {
126         return ((mOptionFlags & NEW) != 0) || (mIsTaskNode && (mTaskId == NEW_TASK_ID));
127     }
setNew(boolean value)128     void setNew(boolean value) {
129         if (value) {
130             mOptionFlags |= NEW;
131         } else {
132             mOptionFlags &= ~NEW;
133         }
134     }
135 
isCurrent()136     boolean isCurrent() {
137         return (mOptionFlags & CURRENT) != 0;
138     }
139 
setCurrent(boolean value)140     Node setCurrent(boolean value) {
141         if (value) {
142             mOptionFlags |= CURRENT;
143         } else {
144             mOptionFlags &= ~CURRENT;
145         }
146         return this;
147     }
148 
setIntent(Intent intent)149     public Node setIntent(Intent intent) {
150         mIntent = new Intent(intent);
151         return this;
152     }
153 
getIntent()154     public Intent getIntent() {
155         return mIntent;
156     }
157 
Node(Parcel in)158     private Node(Parcel in) {
159         mIsTaskNode = in.readInt() == 1;
160         if (mIsTaskNode) {
161             mTaskId = in.readInt();
162         } else {
163             mName = ComponentName.CREATOR.createFromParcel(in);
164         }
165         if (in.readInt() > 0) {
166             in.readTypedList(mChildren, Node.CREATOR);
167         } else {
168             mChildren = new LinkedList<>();
169         }
170         mOptionFlags = in.readInt();
171         if (in.readInt() > 0) {
172             mIntent = Intent.CREATOR.createFromParcel(in);
173         }
174     }
175 
176     /**
177      * Compare the tree represented by this Node to another to determine if
178      * they are isomorphic.
179      * @param other The Node to compare to this.
180      */
equals(Node other)181     public boolean equals(Node other) {
182         if (mIsTaskNode && other.mIsTaskNode) {
183             // Check if taskIds are equal, or if one is a new task (which is essentially a wildcard)
184             if ((mTaskId != other.mTaskId) && (mTaskId != NEW_TASK_ID)
185                     && (other.mTaskId != NEW_TASK_ID)) {
186                 return false;
187             }
188         } else if (!mIsTaskNode && !other.mIsTaskNode){
189             if (!other.mName.equals(mName)) return false;
190         } else return false;
191         if (mChildren.size() == 0 && other.mChildren.size() == 0) {
192             return true;
193         } else if (mChildren.size() != other.mChildren.size()){
194             return false;
195         } else {
196             Collections.sort(mChildren);
197             Collections.sort(other.mChildren);
198             for (int i = 0; i < mChildren.size(); i++) {
199                 if (!mChildren.get(i).equals(other.mChildren.get(i))) {
200                     return false;
201                 }
202             }
203             return true;
204         }
205     }
206 
207     /**
208      * Note: this class has a natural ordering that is inconsistent with equals().
209      * compareTo() makes comparison based on the {@link ComponentName} that this class
210      * holds, and does not consider its children.
211      */
compareTo(Node o)212     public int compareTo(Node o) {
213         return mIsTaskNode ? Integer.valueOf(mTaskId).compareTo(o.mTaskId)
214                 : mName.compareTo(o.mName);
215     }
216 
217     @Override
toString()218     public String toString() {
219         StringBuilder output = new StringBuilder("Node ");
220         if (isCurrent()) output.append("current ");
221         if (isNew()) output.append("new ");
222         if (isModified()) output.append("modified ");
223         output.append("<<");
224         if (mIsTaskNode) output.append("taskId=").append(mTaskId);
225         else output.append(mName.toShortString());
226         if (mIntent != null) {
227             output.append("intent:(");
228             FlagUtils.discoverFlags(mIntent).forEach(flag -> {
229                 output.append(flag.replace(FlagUtils.INTENT_FLAG_PREFIX, "")).append(',');
230             });
231             output.append(")");
232         }
233         output.append(">> {");
234         if (!mChildren.isEmpty()) output.append('\n');
235         mChildren.forEach(child -> Arrays.asList(child.toString().split("\n")).forEach(line ->
236                 output.append("\t\t").append(line).append("\n")));
237         output.append("}\n");
238         return output.toString();
239     }
240 
241     @Override
writeToParcel(Parcel dest, int flags)242     public void writeToParcel(Parcel dest, int flags) {
243         dest.writeInt( mIsTaskNode ? 1 : 0);
244         if (mIsTaskNode) {
245             dest.writeInt(mTaskId);
246         } else {
247             mName.writeToParcel(dest, 0);
248         }
249         if (mChildren.size() == 0 || mChildren == null) {
250             dest.writeInt(0);
251         } else {
252             dest.writeInt(1);
253             dest.writeTypedList(mChildren);
254         }
255         dest.writeInt(mOptionFlags);
256         dest.writeInt(mIntent == null ? 0 : 1);
257         if (mIntent != null) mIntent.writeToParcel(dest, 0 /* flags */);
258     }
259 
260     @Override
describeContents()261     public int describeContents() {
262         return 0;
263     }
264 
265     public static final Creator<Node> CREATOR = new Creator<Node>() {
266         @Override
267         public Node createFromParcel(Parcel in) {
268             return new Node(in);
269         }
270 
271         @Override
272         public Node[] newArray(int size) {
273             return new Node[size];
274         }
275     };
276 }
277