/* * Copyright (C) 2018 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.example.android.intentplayground; import android.content.ComponentName; import android.content.Intent; import android.os.Parcel; import android.os.Parcelable; import java.util.Arrays; import java.util.Collections; import java.util.LinkedList; import java.util.List; /** * This class represents a node in the tree of tasks. It can either represent a task * or an activity. */ public class Node implements Parcelable, Comparable { static final int NEW_TASK_ID = 0xa4d701d; public static final int ROOT_NODE_ID = 0xAABBCCDD; public int mTaskId; public List mChildren = new LinkedList<>(); public ComponentName mName; private static final int CURRENT = 0x1; private static final int MODIFIED = 0x2; private static final int NEW = 0x4; private boolean mIsTaskNode; private int mOptionFlags; private Intent mIntent; Node(ComponentName data) { mIsTaskNode = false; mName = data; } /** * Create a task Node. * @param taskId the id of the task. */ Node(int taskId) { mIsTaskNode = true; mTaskId = taskId; } /** * Creates a Node with the same data as the parameter (copy constructor). * @param other Node to copy over. */ Node(Node other) { if (other.mIsTaskNode) { mIsTaskNode = true; mTaskId = other.mTaskId; } else { mIsTaskNode = false; mName = other.mName.clone(); } mOptionFlags = other.mOptionFlags; mIntent = other.mIntent; other.mChildren.forEach(child -> addChild(new Node(child))); } /** * Adds a child to this Node's children. * @param child The child node to add. * @return returns This Node object for method chaining. */ Node addChild(Node child) { mChildren.add(child); return this; } /** * Adds a child to the beginning of the list of this Node's children. * @param child The child node to add. * @return This Node object for method chaining. */ Node addFirstChild(Node child) { mChildren.add(0, child); return this; } /** * Clear children from this Node. * @return returns This Node object for method chaining. */ Node clearChildren() { mChildren.clear(); return this; } static Node newTaskNode() { return new Node(NEW_TASK_ID); } static Node newRootNode() { return new Node(ROOT_NODE_ID); } boolean isModified() { return (mOptionFlags & MODIFIED) != 0; } void setModified(boolean value) { if (value) { mOptionFlags |= MODIFIED; } else { mOptionFlags &= ~MODIFIED; } } boolean isNew() { return ((mOptionFlags & NEW) != 0) || (mIsTaskNode && (mTaskId == NEW_TASK_ID)); } void setNew(boolean value) { if (value) { mOptionFlags |= NEW; } else { mOptionFlags &= ~NEW; } } boolean isCurrent() { return (mOptionFlags & CURRENT) != 0; } Node setCurrent(boolean value) { if (value) { mOptionFlags |= CURRENT; } else { mOptionFlags &= ~CURRENT; } return this; } public Node setIntent(Intent intent) { mIntent = new Intent(intent); return this; } public Intent getIntent() { return mIntent; } private Node(Parcel in) { mIsTaskNode = in.readInt() == 1; if (mIsTaskNode) { mTaskId = in.readInt(); } else { mName = ComponentName.CREATOR.createFromParcel(in); } if (in.readInt() > 0) { in.readTypedList(mChildren, Node.CREATOR); } else { mChildren = new LinkedList<>(); } mOptionFlags = in.readInt(); if (in.readInt() > 0) { mIntent = Intent.CREATOR.createFromParcel(in); } } /** * Compare the tree represented by this Node to another to determine if * they are isomorphic. * @param other The Node to compare to this. */ public boolean equals(Node other) { if (mIsTaskNode && other.mIsTaskNode) { // Check if taskIds are equal, or if one is a new task (which is essentially a wildcard) if ((mTaskId != other.mTaskId) && (mTaskId != NEW_TASK_ID) && (other.mTaskId != NEW_TASK_ID)) { return false; } } else if (!mIsTaskNode && !other.mIsTaskNode){ if (!other.mName.equals(mName)) return false; } else return false; if (mChildren.size() == 0 && other.mChildren.size() == 0) { return true; } else if (mChildren.size() != other.mChildren.size()){ return false; } else { Collections.sort(mChildren); Collections.sort(other.mChildren); for (int i = 0; i < mChildren.size(); i++) { if (!mChildren.get(i).equals(other.mChildren.get(i))) { return false; } } return true; } } /** * Note: this class has a natural ordering that is inconsistent with equals(). * compareTo() makes comparison based on the {@link ComponentName} that this class * holds, and does not consider its children. */ public int compareTo(Node o) { return mIsTaskNode ? Integer.valueOf(mTaskId).compareTo(o.mTaskId) : mName.compareTo(o.mName); } @Override public String toString() { StringBuilder output = new StringBuilder("Node "); if (isCurrent()) output.append("current "); if (isNew()) output.append("new "); if (isModified()) output.append("modified "); output.append("<<"); if (mIsTaskNode) output.append("taskId=").append(mTaskId); else output.append(mName.toShortString()); if (mIntent != null) { output.append("intent:("); FlagUtils.discoverFlags(mIntent).forEach(flag -> { output.append(flag.replace(FlagUtils.INTENT_FLAG_PREFIX, "")).append(','); }); output.append(")"); } output.append(">> {"); if (!mChildren.isEmpty()) output.append('\n'); mChildren.forEach(child -> Arrays.asList(child.toString().split("\n")).forEach(line -> output.append("\t\t").append(line).append("\n"))); output.append("}\n"); return output.toString(); } @Override public void writeToParcel(Parcel dest, int flags) { dest.writeInt( mIsTaskNode ? 1 : 0); if (mIsTaskNode) { dest.writeInt(mTaskId); } else { mName.writeToParcel(dest, 0); } if (mChildren.size() == 0 || mChildren == null) { dest.writeInt(0); } else { dest.writeInt(1); dest.writeTypedList(mChildren); } dest.writeInt(mOptionFlags); dest.writeInt(mIntent == null ? 0 : 1); if (mIntent != null) mIntent.writeToParcel(dest, 0 /* flags */); } @Override public int describeContents() { return 0; } public static final Creator CREATOR = new Creator() { @Override public Node createFromParcel(Parcel in) { return new Node(in); } @Override public Node[] newArray(int size) { return new Node[size]; } }; }