Replace ActivityInstanceInfo api with in app lifecycle event tracking
Since adding a public api for the playground app was problematic, we now keep track of onResume and onDestroy calls globally inside the app. This fixes a few bugs in cases where tasks would be hidden from the ActivityManger#.getAppTasks() method and allows us to listen for changes allowing us to do without a periodic refresh of the UI. Test: Manual Change-Id: Idb834cbdc7aad6442dd962c95b5321e1d75695fe
This commit is contained in:
@@ -18,15 +18,14 @@ package com.example.android.intentplayground;
|
||||
|
||||
import static com.example.android.intentplayground.Node.newTaskNode;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Intent;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.support.design.widget.FloatingActionButton;
|
||||
import android.util.Log;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.widget.LinearLayout;
|
||||
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
@@ -35,7 +34,11 @@ import androidx.fragment.app.FragmentManager;
|
||||
import androidx.fragment.app.FragmentTransaction;
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
|
||||
import com.example.android.intentplayground.Tracking.Tracker;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
* Implements the shared functionality for all of the other activities.
|
||||
@@ -53,6 +56,14 @@ public abstract class BaseActivity extends AppCompatActivity implements
|
||||
public boolean userLeaveHintWasCalled = false;
|
||||
protected Mode mStatus = Mode.LAUNCH;
|
||||
|
||||
/**
|
||||
* To display the task / activity overview in {@link TreeFragment} we track onResume and
|
||||
* onDestroy calls in this global location. {@link BaseActivity} should delegate to
|
||||
* {@link Tracker#onResume(Activity)} and {@link Tracker#onDestroy(Activity)} in it's respective
|
||||
* lifecycle callbacks.
|
||||
*/
|
||||
private static Tracker mTracker = new Tracker();
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
@@ -94,12 +105,27 @@ public abstract class BaseActivity extends AppCompatActivity implements
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
mTracker.onResume(this);
|
||||
Intent launchForward = prepareLaunchForward();
|
||||
if (launchForward != null) {
|
||||
startActivity(launchForward);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
super.onDestroy();
|
||||
mTracker.onDestroy(this);
|
||||
}
|
||||
|
||||
static void addTrackerListener(Consumer<List<Tracking.Task>> listener) {
|
||||
mTracker.addListener(listener);
|
||||
}
|
||||
|
||||
static void removeTrackerListener(Consumer<List<Tracking.Task>> listener) {
|
||||
mTracker.removeListener(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the UI for the specified {@link Mode}.
|
||||
*
|
||||
@@ -142,7 +168,6 @@ public abstract class BaseActivity extends AppCompatActivity implements
|
||||
setIntent(intent);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
getMenuInflater().inflate(R.menu.app_bar, menu);
|
||||
@@ -162,7 +187,6 @@ public abstract class BaseActivity extends AppCompatActivity implements
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
|
||||
private void askToLaunchTasks() {
|
||||
AlertDialog dialog = new AlertDialog.Builder(this)
|
||||
.setMessage(R.string.launch_explanation)
|
||||
@@ -237,5 +261,4 @@ public abstract class BaseActivity extends AppCompatActivity implements
|
||||
super.onUserLeaveHint();
|
||||
userLeaveHintWasCalled = true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -20,12 +20,29 @@ import androidx.lifecycle.LiveData;
|
||||
import androidx.lifecycle.MutableLiveData;
|
||||
import androidx.lifecycle.ViewModel;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public class BaseActivityViewModel extends ViewModel {
|
||||
enum FabAction {Show, Hide }
|
||||
enum FabAction {Show, Hide}
|
||||
|
||||
private MutableLiveData<FabAction> mFabActions = new MutableLiveData<>();
|
||||
private final MutableLiveData<FabAction> mFabActions;
|
||||
|
||||
private MutableLiveData<Object> mRefreshTree = new MutableLiveData<>();
|
||||
/**
|
||||
* Republish {@link BaseActivity#addTrackerListener(Consumer)} in a lifecycle safe manner.
|
||||
* The {@link BaseActivity#addTrackerListener(Consumer)} that is registered in this class,
|
||||
* forwards the value to this, to enjoy all the guarantees {@link import
|
||||
* androidx.lifecycle.LiveData;} gives.
|
||||
*/
|
||||
private final MutableLiveData<List<Tracking.Task>> mRefreshTree;
|
||||
private final Consumer<List<Tracking.Task>> mTrackingListener;
|
||||
|
||||
public BaseActivityViewModel() {
|
||||
mFabActions = new MutableLiveData<>();
|
||||
mRefreshTree = new MutableLiveData<>();
|
||||
mTrackingListener = tasks -> mRefreshTree.setValue(tasks);
|
||||
BaseActivity.addTrackerListener(mTrackingListener);
|
||||
}
|
||||
|
||||
|
||||
public void actOnFab(FabAction action) {
|
||||
@@ -36,12 +53,17 @@ public class BaseActivityViewModel extends ViewModel {
|
||||
return mFabActions;
|
||||
}
|
||||
|
||||
public LiveData<Object> getRefresh() {
|
||||
/**
|
||||
* @return LiveData that publishes the new state of {@link com.android.server.wm.Task} and
|
||||
* {@link android.app.Activity}-s whenever that state has been changed.
|
||||
*/
|
||||
public LiveData<List<Tracking.Task>> getRefresh() {
|
||||
return mRefreshTree;
|
||||
}
|
||||
|
||||
public void refresh() {
|
||||
mRefreshTree.setValue(new Object());
|
||||
@Override
|
||||
public void onCleared() {
|
||||
BaseActivity.removeTrackerListener(mTrackingListener);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
package com.example.android.intentplayground;
|
||||
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.ActivityManager;
|
||||
import android.app.ActivityManager.AppTask;
|
||||
import android.app.ActivityManager.RecentTaskInfo;
|
||||
@@ -34,30 +35,30 @@ import androidx.fragment.app.FragmentManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.example.android.intentplayground.InlineAdapter.TaskViewHolder;
|
||||
import com.example.android.intentplayground.Tracking.Task;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
public class InlineAdapter extends RecyclerView.Adapter<TaskViewHolder> {
|
||||
|
||||
private final List<Node> mTasks;
|
||||
private final List<Task> mTasks;
|
||||
private int mCurrentTaskIndex;
|
||||
private FragmentActivity mActivity;
|
||||
|
||||
public InlineAdapter(Node tree, FragmentActivity activity) {
|
||||
public InlineAdapter(List<Task> tasks, FragmentActivity activity) {
|
||||
this.mActivity = activity;
|
||||
this.mTasks = tree.mChildren;
|
||||
this.mTasks = tasks;
|
||||
this.mCurrentTaskIndex = indexOfRunningTask();
|
||||
}
|
||||
|
||||
public int indexOfRunningTask() {
|
||||
List<AppTask> appTasks = mActivity.getSystemService(ActivityManager.class).getAppTasks();
|
||||
RecentTaskInfo currentTask = appTasks.get(0).getTaskInfo();
|
||||
int currentTaskId = mActivity.getTaskId();
|
||||
|
||||
int index = 0;
|
||||
for (int i = 0; i < mTasks.size(); i++) {
|
||||
Node task = mTasks.get(i);
|
||||
if (task.mTaskId == currentTask.id) {
|
||||
Task task = mTasks.get(i);
|
||||
if (task.id == currentTaskId) {
|
||||
index = i;
|
||||
break;
|
||||
}
|
||||
@@ -98,23 +99,23 @@ public class InlineAdapter extends RecyclerView.Adapter<TaskViewHolder> {
|
||||
mActivitiesLayout = itemView.findViewById(R.id.activity_node_container);
|
||||
}
|
||||
|
||||
public void setTask(Node task, FragmentManager manager, boolean highLightRunningActivity) {
|
||||
mTaskIdTextView.setText(task.mTaskId == Node.NEW_TASK_ID
|
||||
public void setTask(Task task, FragmentManager manager, boolean highLightRunningActivity) {
|
||||
mTaskIdTextView.setText(task.id == Node.NEW_TASK_ID
|
||||
? mTaskIdTextView.getContext().getString(R.string.new_task)
|
||||
: String.valueOf(task.mTaskId));
|
||||
: String.valueOf(task.id));
|
||||
int taskColor = mTaskIdTextView.getContext()
|
||||
.getResources().getColor(ColorManager.getColorForTask(task.mTaskId),
|
||||
.getResources().getColor(ColorManager.getColorForTask(task.id),
|
||||
null /* theme */);
|
||||
mTaskIdTextView.setTextColor(taskColor);
|
||||
|
||||
mActivitiesLayout.removeAllViews();
|
||||
for (Node activity : task.mChildren) {
|
||||
for (Activity activity : task.mActivities) {
|
||||
View activityView = LayoutInflater.from(mActivitiesLayout.getContext())
|
||||
.inflate(R.layout.activity_node, mActivitiesLayout, false);
|
||||
|
||||
TextView activityName = activityView.findViewById(R.id.activity_name);
|
||||
|
||||
activityName.setText(activity.mName.getShortClassName());
|
||||
activityName.setText(activity.getComponentName().getShortClassName());
|
||||
activityName.setOnClickListener(clickedView -> {
|
||||
Intent intent = activity.getIntent();
|
||||
List<String> flags;
|
||||
@@ -126,8 +127,8 @@ public class InlineAdapter extends RecyclerView.Adapter<TaskViewHolder> {
|
||||
} else {
|
||||
flags = Collections.singletonList("None");
|
||||
}
|
||||
showDialogWithFlags(manager, activity.mName.getShortClassName(), flags,
|
||||
task.mTaskId);
|
||||
showDialogWithFlags(manager, activity.getComponentName().getShortClassName(),
|
||||
flags, task.id);
|
||||
});
|
||||
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ package com.example.android.intentplayground;
|
||||
|
||||
import android.app.ActivityManager;
|
||||
import android.os.Bundle;
|
||||
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
@@ -28,6 +28,7 @@ import android.widget.Button;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.ListView;
|
||||
import android.widget.ScrollView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.fragment.app.DialogFragment;
|
||||
@@ -35,6 +36,7 @@ import androidx.lifecycle.ViewModelProvider;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* Shows a dialog with an activity name and a list of intent flags.
|
||||
@@ -43,10 +45,10 @@ public class IntentDialogFragment extends DialogFragment {
|
||||
private List<String> mFlags;
|
||||
private String mActivityName;
|
||||
private int mTaskId;
|
||||
private BaseActivityViewModel mViewModel;
|
||||
private static final String ARGUMENT_ACTIVITY_NAME = "activityName";
|
||||
private static final String ARGUMENT_FLAGS = "flags";
|
||||
private static final String TASK_ID = "taskId";
|
||||
private static final String TAG = "IntentDialogFragment";
|
||||
|
||||
/**
|
||||
* Creates a new IntentDialogFragment to display the given flags.
|
||||
@@ -73,7 +75,7 @@ public class IntentDialogFragment extends DialogFragment {
|
||||
mFlags = args.getStringArrayList(ARGUMENT_FLAGS);
|
||||
mActivityName = args.getString(ARGUMENT_ACTIVITY_NAME);
|
||||
mTaskId = args.getInt(TASK_ID);
|
||||
mViewModel = (new ViewModelProvider(getActivity(),
|
||||
BaseActivityViewModel viewModel = (new ViewModelProvider(getActivity(),
|
||||
new ViewModelProvider.NewInstanceFactory())).get(BaseActivityViewModel.class);
|
||||
}
|
||||
|
||||
@@ -96,14 +98,12 @@ public class IntentDialogFragment extends DialogFragment {
|
||||
bringToFront.setOnClickListener(v -> {
|
||||
moveTaskToFront(mTaskId);
|
||||
getDialog().dismiss();
|
||||
mViewModel.refresh();
|
||||
});
|
||||
|
||||
Button removeTask = rootLayout.findViewById(R.id.kill_task_button);
|
||||
removeTask.setOnClickListener(v -> {
|
||||
removeTask(mTaskId);
|
||||
getDialog().dismiss();
|
||||
mViewModel.refresh();
|
||||
});
|
||||
|
||||
Button copyFlagsButton = rootLayout.findViewById(R.id.copy_flags_button);
|
||||
@@ -125,11 +125,20 @@ public class IntentDialogFragment extends DialogFragment {
|
||||
|
||||
private void removeTask(int taskId) {
|
||||
ActivityManager am = getActivity().getSystemService(ActivityManager.class);
|
||||
am.getAppTasks().forEach(task -> {
|
||||
if (task.getTaskInfo().persistentId == taskId) {
|
||||
task.finishAndRemoveTask();
|
||||
}
|
||||
});
|
||||
List<ActivityManager.AppTask> appTasks = am.getAppTasks();
|
||||
|
||||
Optional<ActivityManager.AppTask> taskToKill = appTasks.stream().filter(
|
||||
task -> task.getTaskInfo().persistentId == taskId)
|
||||
.findFirst();
|
||||
|
||||
if (taskToKill.isPresent()) {
|
||||
taskToKill.get().finishAndRemoveTask();
|
||||
} else {
|
||||
String errorMessage = "Task: " + taskId + " not found in recents, can't kill";
|
||||
Log.e(TAG, errorMessage);
|
||||
Toast.makeText(getContext(), errorMessage,
|
||||
Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
}
|
||||
|
||||
private void moveTaskToFront(int taskId) {
|
||||
|
||||
@@ -16,29 +16,9 @@
|
||||
|
||||
package com.example.android.intentplayground;
|
||||
|
||||
import android.app.ActivityManager;
|
||||
import android.app.AlertDialog;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Intent;
|
||||
import android.graphics.PixelFormat;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.provider.Settings;
|
||||
import android.view.Gravity;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.WindowManager;
|
||||
import android.view.WindowManager.LayoutParams;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
|
||||
import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
|
||||
import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
|
||||
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
|
||||
|
||||
import static com.example.android.intentplayground.Node.newTaskNode;
|
||||
|
||||
/**
|
||||
* A singleInstance activity that is responsible for a launching a bootstrap stack of activities.
|
||||
|
||||
@@ -0,0 +1,285 @@
|
||||
package com.example.android.intentplayground;
|
||||
|
||||
import static java.util.stream.Collectors.toList;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.util.Log;
|
||||
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.ListIterator;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
* Provides information about the current runnings tasks and activities in the system, by tracking
|
||||
* all the lifecycle events happening in the app using {@link Tracker}. {@link Tracker} can be
|
||||
* observed for changes in this state. Information regarding the order of activities is kept inside
|
||||
* {@link Task}.
|
||||
*/
|
||||
public class Tracking {
|
||||
|
||||
/**
|
||||
* Stores the {@link com.android.server.wm.Task}-s in MRU order together with the activities
|
||||
* within that task and their order. Classes can be notified of changes in this state through
|
||||
* {@link Tracker#addListener(Consumer)}
|
||||
*/
|
||||
public static class Tracker {
|
||||
private static final String TAG = "Tracker";
|
||||
|
||||
/**
|
||||
* Stores {@link Task} by their id.
|
||||
*/
|
||||
private HashMap<Integer, Task> mTaskOverView = new HashMap<>();
|
||||
|
||||
/**
|
||||
* {@link Task} belonging to this application, most recently resumed
|
||||
* task at front.
|
||||
*/
|
||||
private ArrayDeque<Task> mTaskOrdering = new ArrayDeque<>();
|
||||
|
||||
/**
|
||||
* Listeners that get notified whenever the tasks get modified.
|
||||
* This also includes reordering of activities within the task.
|
||||
*/
|
||||
private List<Consumer<List<Task>>> mListeners = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* When an {@link Activity} becomes resumed, it should be put at the top within it's task.
|
||||
* Furthermore the task it belongs to should become the most recent task.
|
||||
*
|
||||
* We also check if any {@link Activity} we have thinks it's {@link Activity#getTaskId()}
|
||||
* does not correspond to the {@link Task} we associated it to.
|
||||
* If so we move them to the {@link Task} they report they should belong to.
|
||||
*
|
||||
* @param activity the {@link Activity} that has been resumed.
|
||||
*/
|
||||
public synchronized void onResume(Activity activity) {
|
||||
logNameEventAndTask(activity, "onResume");
|
||||
|
||||
int id = activity.getTaskId();
|
||||
Task task = getOrCreateTask(mTaskOverView, id);
|
||||
task.activityResumed(activity);
|
||||
bringToFront(task);
|
||||
|
||||
checkForMovedActivities().ifPresent(this::moveActivitiesInOrder);
|
||||
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
/**
|
||||
* When an {@link Activity} is being destroyed, we remove it from the task it is in.
|
||||
* If this activity was the last activity in the task, we also remove the
|
||||
* {@link Task}.
|
||||
*
|
||||
* @param activity the {@link Activity} that has been resumed.
|
||||
*/
|
||||
public synchronized void onDestroy(Activity activity) {
|
||||
logNameEventAndTask(activity, "onDestroy");
|
||||
|
||||
// Find the activity by identity in case it has been moved.
|
||||
Optional<Task> existingTask = mTaskOverView.values().stream()
|
||||
.filter(t -> t.containsActivity(activity))
|
||||
.findAny();
|
||||
|
||||
if (existingTask.isPresent()) {
|
||||
Task task = existingTask.get();
|
||||
task.activityDestroyed(activity);
|
||||
|
||||
// If this was the last activity in the task, remove it.
|
||||
if (task.mActivities.isEmpty()) {
|
||||
mTaskOverView.remove(task.id);
|
||||
mTaskOrdering.remove(task);
|
||||
}
|
||||
}
|
||||
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
// If it's not already at the front of the queue, remove it and add it at the front.
|
||||
private void bringToFront(Task task) {
|
||||
if (mTaskOrdering.peekFirst() != task) {
|
||||
mTaskOrdering.remove(task);
|
||||
mTaskOrdering.addFirst(task);
|
||||
}
|
||||
}
|
||||
|
||||
// Check if there is a task that has activities that belong to another task.
|
||||
private Optional<Task> checkForMovedActivities() {
|
||||
for (Task task : mTaskOverView.values()) {
|
||||
for (Activity activity : task.mActivities) {
|
||||
if (activity.getTaskId() != task.id) {
|
||||
return Optional.of(task);
|
||||
}
|
||||
}
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
// When a task contains activities that belong to another task, we move them
|
||||
// to the other task, in the same order they had in the current task.
|
||||
private void moveActivitiesInOrder(Task task) {
|
||||
Iterator<Activity> iterator = task.mActivities.iterator();
|
||||
while (iterator.hasNext()) {
|
||||
Activity activity = iterator.next();
|
||||
int id = activity.getTaskId();
|
||||
if (id != task.id) {
|
||||
Task target = mTaskOverView.get(id);
|
||||
//the task the activity moved to was not yet known
|
||||
if (target == null) {
|
||||
Task newTask = Task.newTask(id);
|
||||
mTaskOverView.put(id, newTask);
|
||||
// we're not sure where this task should belong now
|
||||
// we put it behind the current front task
|
||||
putBehindFront(newTask);
|
||||
target = newTask;
|
||||
}
|
||||
target.mActivities.add(activity);
|
||||
iterator.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If activities moved to a new task that we don't know about yet, we put it behind
|
||||
// the most recent task.
|
||||
private void putBehindFront(Task task) {
|
||||
Task first = mTaskOrdering.removeFirst();
|
||||
mTaskOrdering.addFirst(task);
|
||||
mTaskOrdering.addFirst(first);
|
||||
}
|
||||
|
||||
|
||||
public static void logNameEventAndTask(Activity activity, String event) {
|
||||
Log.i(TAG, activity.getClass().getSimpleName() + " " + event + "task id: "
|
||||
+ activity.getTaskId());
|
||||
}
|
||||
|
||||
public synchronized int size() {
|
||||
return mTaskOverView.size();
|
||||
}
|
||||
|
||||
private synchronized void notifyListeners() {
|
||||
List<Task> tasks = mTaskOrdering.stream().map(Task::copyForUi).collect(toList());
|
||||
|
||||
for (Consumer<List<Task>> listener : mListeners) {
|
||||
listener.accept(tasks);
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized void addListener(Consumer<List<Task>> listener) {
|
||||
mListeners.add(listener);
|
||||
}
|
||||
|
||||
public synchronized void removeListener(Consumer<List<Task>> listener) {
|
||||
mListeners.remove(listener);
|
||||
}
|
||||
}
|
||||
|
||||
private static Task getOrCreateTask(Map<Integer, Task> map, int id) {
|
||||
Task backup = Task.newTask(id);
|
||||
Task task = map.putIfAbsent(id, backup);
|
||||
if (task == null) {
|
||||
return backup;
|
||||
} else {
|
||||
return task;
|
||||
}
|
||||
}
|
||||
|
||||
static class Task {
|
||||
public final int id;
|
||||
/**
|
||||
* The activities in this task,
|
||||
* element 0 being the least recent and the last element being the most recent
|
||||
*/
|
||||
protected final List<Activity> mActivities;
|
||||
|
||||
|
||||
Task(int id, List<Activity> activities) {
|
||||
this.id = id;
|
||||
mActivities = activities;
|
||||
}
|
||||
|
||||
static Task newTask(int id) {
|
||||
return new Task(id, new ArrayList<>());
|
||||
}
|
||||
|
||||
|
||||
public void activityResumed(Activity activity) {
|
||||
ensureSameTask(activity);
|
||||
|
||||
Iterator<Activity> activityIterator = mActivities.iterator();
|
||||
while (activityIterator.hasNext()) {
|
||||
Activity next = activityIterator.next();
|
||||
//the activity is being moved up.
|
||||
if (next == activity) {
|
||||
activityIterator.remove();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
mActivities.add(activity);
|
||||
}
|
||||
|
||||
public boolean containsActivity(Activity activity) {
|
||||
for (Activity activity1 : mActivities) {
|
||||
if (activity1 == activity) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private void ensureSameTask(Activity activity) {
|
||||
if (activity.getTaskId() != id) {
|
||||
throw new RuntimeException("adding activity to task with different id");
|
||||
}
|
||||
}
|
||||
|
||||
public void activityDestroyed(Activity activity) {
|
||||
ensureSameTask(activity);
|
||||
mActivities.removeIf(a -> a == activity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
Task task = (Task) o;
|
||||
return id == task.id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Task{" +
|
||||
"id=" + id +
|
||||
", mActivities=" + mActivities +
|
||||
'}';
|
||||
}
|
||||
|
||||
public static Task copyForUi(Task task) {
|
||||
return new Task(task.id, reverseAndCopy(task.mActivities));
|
||||
}
|
||||
|
||||
public static <T> List<T> reverseAndCopy(List<T> ts) {
|
||||
ListIterator<T> iterator = ts.listIterator(ts.size());
|
||||
List<T> result = new ArrayList<>();
|
||||
|
||||
while (iterator.hasPrevious()) {
|
||||
result.add(iterator.previous());
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -17,7 +17,6 @@
|
||||
package com.example.android.intentplayground;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.ActivityManager;
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
@@ -32,6 +31,8 @@ import androidx.lifecycle.ViewModelProvider;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* This fragment displays a hierarchy of tasks and activities in an expandable list.
|
||||
*/
|
||||
@@ -60,33 +61,35 @@ public class TreeFragment extends Fragment {
|
||||
mViewModel = (new ViewModelProvider(getActivity(),
|
||||
new ViewModelProvider.NewInstanceFactory())).get(BaseActivityViewModel.class);
|
||||
|
||||
mViewModel.getRefresh().observe(this, v -> this.onResumeHelper());
|
||||
mViewModel.getRefresh().observe(this, this::onResumeHelper);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
mViewModel.actOnFab(BaseActivityViewModel.FabAction.Show);
|
||||
}
|
||||
|
||||
private void onResumeHelper(List<Tracking.Task> tasks) {
|
||||
mActivity = getActivity();
|
||||
LinearLayout treeLayout = (LinearLayout) getView();
|
||||
|
||||
TextView titleView = treeLayout.findViewById(R.id.task_tree_title);
|
||||
RecyclerView recyclerView = treeLayout.findViewById(R.id.tree_recycler);
|
||||
if (mTitle != null) {
|
||||
titleView.setText(mTitle);
|
||||
}
|
||||
|
||||
displayRecycler(TestBase.describeTaskHierarchy(mActivity), recyclerView);
|
||||
mViewModel.actOnFab(BaseActivityViewModel.FabAction.Show);
|
||||
displayRecycler(tasks, recyclerView);
|
||||
}
|
||||
|
||||
private void displayRecycler(Node root, RecyclerView recyclerView) {
|
||||
private void displayRecycler(List<Tracking.Task> tasks, RecyclerView recyclerView) {
|
||||
recyclerView.setHasFixedSize(true);
|
||||
recyclerView.setNestedScrollingEnabled(false);
|
||||
InlineAdapter adapter = new InlineAdapter(root, getActivity());
|
||||
InlineAdapter adapter = new InlineAdapter(tasks, getActivity());
|
||||
recyclerView.setAdapter(adapter);
|
||||
recyclerView
|
||||
.setLayoutManager(
|
||||
new LinearLayoutManager(getContext(), RecyclerView.VERTICAL, false));
|
||||
recyclerView.setLayoutManager(
|
||||
new LinearLayoutManager(getContext(), RecyclerView.VERTICAL, false));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user