The app allows a user to explore the behaviour of different launch modes, task affinities and intent flags. It displays the current state of all tasks in the application and their corresponding flags. It allows the user to launch a set amount of activities on launch. This bring the user directly into a state where many options for exploration are available, rather than having to go through a complicated setup first. Access the activity field of RecentTaskInfo using reflection and mirror the ActivityInstanceInfo into our own value object. This breaks the compile time dependency on the ActivityInstanceInfo api and turns it into a runtime dependency. If the api is missing on the device we can still show the task structure and log an error with the missing activity instance info. Known bug: The enable suggestion button crashes the application. Test: Build and Run Change-Id: Id0274bae159c16aee6dccd805deb53851ffcf21d
277 lines
8.0 KiB
Java
277 lines
8.0 KiB
Java
/*
|
|
* 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<Node> {
|
|
static final int NEW_TASK_ID = 0xa4d701d;
|
|
public static final int ROOT_NODE_ID = 0xAABBCCDD;
|
|
public int mTaskId;
|
|
public List<Node> 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<Node> CREATOR = new Creator<Node>() {
|
|
@Override
|
|
public Node createFromParcel(Parcel in) {
|
|
return new Node(in);
|
|
}
|
|
|
|
@Override
|
|
public Node[] newArray(int size) {
|
|
return new Node[size];
|
|
}
|
|
};
|
|
}
|