Home | History | Annotate | Download | only in intentplayground
      1 package com.example.android.intentplayground;
      2 
      3 import static java.util.stream.Collectors.toList;
      4 
      5 import android.app.Activity;
      6 import android.util.Log;
      7 
      8 import java.util.ArrayDeque;
      9 import java.util.ArrayList;
     10 import java.util.HashMap;
     11 import java.util.Iterator;
     12 import java.util.List;
     13 import java.util.ListIterator;
     14 import java.util.Map;
     15 import java.util.Objects;
     16 import java.util.Optional;
     17 import java.util.function.Consumer;
     18 
     19 /**
     20  * Provides information about the current runnings tasks and activities in the system, by tracking
     21  * all the lifecycle events happening in the app using {@link Tracker}. {@link Tracker} can be
     22  * observed for changes in this state. Information regarding the order of activities is kept inside
     23  * {@link Task}.
     24  */
     25 public class Tracking {
     26 
     27     /**
     28      * Stores the {@link com.android.server.wm.Task}-s in MRU order together with the activities
     29      * within that task and their order. Classes can be notified of changes in this state through
     30      * {@link Tracker#addListener(Consumer)}
     31      */
     32     public static class Tracker {
     33         private static final String TAG = "Tracker";
     34 
     35         /**
     36          * Stores {@link Task} by their id.
     37          */
     38         private HashMap<Integer, Task> mTaskOverView = new HashMap<>();
     39 
     40         /**
     41          * {@link Task} belonging to this application, most recently resumed
     42          * task at front.
     43          */
     44         private ArrayDeque<Task> mTaskOrdering = new ArrayDeque<>();
     45 
     46         /**
     47          * Listeners that get notified whenever the tasks get modified.
     48          * This also includes reordering of activities within the task.
     49          */
     50         private List<Consumer<List<Task>>> mListeners = new ArrayList<>();
     51 
     52         /**
     53          * When an {@link Activity} becomes resumed, it should be put at the top within it's task.
     54          * Furthermore the task it belongs to should become the most recent task.
     55          *
     56          * We also check if any {@link Activity} we have thinks it's {@link Activity#getTaskId()}
     57          * does not correspond to the {@link Task} we associated it to.
     58          * If so we move them to the {@link Task} they report they should belong to.
     59          *
     60          * @param activity the {@link Activity} that has been resumed.
     61          */
     62         public synchronized void onResume(Activity activity) {
     63             logNameEventAndTask(activity, "onResume");
     64 
     65             int id = activity.getTaskId();
     66             Task task = getOrCreateTask(mTaskOverView, id);
     67             task.activityResumed(activity);
     68             bringToFront(task);
     69 
     70             checkForMovedActivities().ifPresent(this::moveActivitiesInOrder);
     71 
     72             notifyListeners();
     73         }
     74 
     75         /**
     76          * When an {@link Activity} is being destroyed, we remove it from the task it is in.
     77          * If this activity was the last activity in the task, we also remove the
     78          * {@link Task}.
     79          *
     80          * @param activity the {@link Activity} that has been resumed.
     81          */
     82         public synchronized void onDestroy(Activity activity) {
     83             logNameEventAndTask(activity, "onDestroy");
     84 
     85             // Find the activity by identity in case it has been moved.
     86             Optional<Task> existingTask = mTaskOverView.values().stream()
     87                     .filter(t -> t.containsActivity(activity))
     88                     .findAny();
     89 
     90             if (existingTask.isPresent()) {
     91                 Task task = existingTask.get();
     92                 task.activityDestroyed(activity);
     93 
     94                 // If this was the last activity in the task, remove it.
     95                 if (task.mActivities.isEmpty()) {
     96                     mTaskOverView.remove(task.id);
     97                     mTaskOrdering.remove(task);
     98                 }
     99             }
    100 
    101             notifyListeners();
    102         }
    103 
    104         // If it's not already at the front of the queue, remove it and add it at the front.
    105         private void bringToFront(Task task) {
    106             if (mTaskOrdering.peekFirst() != task) {
    107                 mTaskOrdering.remove(task);
    108                 mTaskOrdering.addFirst(task);
    109             }
    110         }
    111 
    112         // Check if there is a task that has activities that belong to another task.
    113         private Optional<Task> checkForMovedActivities() {
    114             for (Task task : mTaskOverView.values()) {
    115                 for (Activity activity : task.mActivities) {
    116                     if (activity.getTaskId() != task.id) {
    117                         return Optional.of(task);
    118                     }
    119                 }
    120             }
    121             return Optional.empty();
    122         }
    123 
    124         // When a task contains activities that belong to another task, we move them
    125         // to the other task, in the same order they had in the current task.
    126         private void moveActivitiesInOrder(Task task) {
    127             Iterator<Activity> iterator = task.mActivities.iterator();
    128             while (iterator.hasNext()) {
    129                 Activity activity = iterator.next();
    130                 int id = activity.getTaskId();
    131                 if (id != task.id) {
    132                     Task target = mTaskOverView.get(id);
    133                     //the task the activity moved to was not yet known
    134                     if (target == null) {
    135                         Task newTask = Task.newTask(id);
    136                         mTaskOverView.put(id, newTask);
    137                         // we're not sure where this task should belong now
    138                         // we put it behind the current front task
    139                         putBehindFront(newTask);
    140                         target = newTask;
    141                     }
    142                     target.mActivities.add(activity);
    143                     iterator.remove();
    144                 }
    145             }
    146         }
    147 
    148         // If activities moved to a new task that we don't know about yet, we put it behind
    149         // the most recent task.
    150         private void putBehindFront(Task task) {
    151             Task first = mTaskOrdering.removeFirst();
    152             mTaskOrdering.addFirst(task);
    153             mTaskOrdering.addFirst(first);
    154         }
    155 
    156 
    157         public static void logNameEventAndTask(Activity activity, String event) {
    158             Log.i(TAG, activity.getClass().getSimpleName() + " " + event + "task id: "
    159                     + activity.getTaskId());
    160         }
    161 
    162         public synchronized int size() {
    163             return mTaskOverView.size();
    164         }
    165 
    166         private synchronized void notifyListeners() {
    167             List<Task> tasks = mTaskOrdering.stream().map(Task::copyForUi).collect(toList());
    168 
    169             for (Consumer<List<Task>> listener : mListeners) {
    170                 listener.accept(tasks);
    171             }
    172         }
    173 
    174         public synchronized void addListener(Consumer<List<Task>> listener) {
    175             mListeners.add(listener);
    176         }
    177 
    178         public synchronized void removeListener(Consumer<List<Task>> listener) {
    179             mListeners.remove(listener);
    180         }
    181     }
    182 
    183     private static Task getOrCreateTask(Map<Integer, Task> map, int id) {
    184         Task backup = Task.newTask(id);
    185         Task task = map.putIfAbsent(id, backup);
    186         if (task == null) {
    187             return backup;
    188         } else {
    189             return task;
    190         }
    191     }
    192 
    193     static class Task {
    194         public final int id;
    195         /**
    196          * The activities in this task,
    197          * element 0 being the least recent and the last element being the most recent
    198          */
    199         protected final List<Activity> mActivities;
    200 
    201 
    202         Task(int id, List<Activity> activities) {
    203             this.id = id;
    204             mActivities = activities;
    205         }
    206 
    207         static Task newTask(int id) {
    208             return new Task(id, new ArrayList<>());
    209         }
    210 
    211 
    212         public void activityResumed(Activity activity) {
    213             ensureSameTask(activity);
    214 
    215             Iterator<Activity> activityIterator = mActivities.iterator();
    216             while (activityIterator.hasNext()) {
    217                 Activity next = activityIterator.next();
    218                 //the activity is being moved up.
    219                 if (next == activity) {
    220                     activityIterator.remove();
    221                     break;
    222                 }
    223             }
    224 
    225             mActivities.add(activity);
    226         }
    227 
    228         public boolean containsActivity(Activity activity) {
    229             for (Activity activity1 : mActivities) {
    230                 if (activity1 == activity) {
    231                     return true;
    232                 }
    233             }
    234 
    235             return false;
    236         }
    237 
    238         private void ensureSameTask(Activity activity) {
    239             if (activity.getTaskId() != id) {
    240                 throw new RuntimeException("adding activity to task with different id");
    241             }
    242         }
    243 
    244         public void activityDestroyed(Activity activity) {
    245             ensureSameTask(activity);
    246             mActivities.removeIf(a -> a == activity);
    247         }
    248 
    249         @Override
    250         public boolean equals(Object o) {
    251             if (this == o) return true;
    252             if (o == null || getClass() != o.getClass()) return false;
    253             Task task = (Task) o;
    254             return id == task.id;
    255         }
    256 
    257         @Override
    258         public int hashCode() {
    259             return Objects.hash(id);
    260         }
    261 
    262         @Override
    263         public String toString() {
    264             return "Task{" +
    265                     "id=" + id +
    266                     ", mActivities=" + mActivities +
    267                     '}';
    268         }
    269 
    270         public static Task copyForUi(Task task) {
    271             return new Task(task.id, reverseAndCopy(task.mActivities));
    272         }
    273 
    274         public static <T> List<T> reverseAndCopy(List<T> ts) {
    275             ListIterator<T> iterator = ts.listIterator(ts.size());
    276             List<T> result = new ArrayList<>();
    277 
    278             while (iterator.hasPrevious()) {
    279                 result.add(iterator.previous());
    280             }
    281 
    282             return result;
    283         }
    284     }
    285 }
    286