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