1 /*
2  * Copyright (C) 2019 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.android.quickstep;
17 
18 import android.view.LayoutInflater;
19 import android.view.View;
20 import android.view.View.OnClickListener;
21 import android.view.ViewGroup;
22 import android.widget.Button;
23 
24 import androidx.annotation.NonNull;
25 import androidx.recyclerview.widget.RecyclerView.Adapter;
26 import androidx.recyclerview.widget.RecyclerView.ViewHolder;
27 
28 import com.android.launcher3.R;
29 import com.android.quickstep.views.TaskItemView;
30 import com.android.systemui.shared.recents.model.Task;
31 
32 import java.util.List;
33 import java.util.Objects;
34 import java.util.Optional;
35 
36 /**
37  * Recycler view adapter that dynamically inflates and binds {@link TaskHolder} instances with the
38  * appropriate {@link Task} from the recents task list.
39  */
40 public final class TaskAdapter extends Adapter<ViewHolder> {
41 
42     public static final int CHANGE_EVENT_TYPE_EMPTY_TO_CONTENT = 0;
43     public static final int MAX_TASKS_TO_DISPLAY = 6;
44     public static final int TASKS_START_POSITION = 1;
45 
46     public static final int ITEM_TYPE_TASK = 0;
47     public static final int ITEM_TYPE_CLEAR_ALL = 1;
48 
49     private static final String TAG = "TaskAdapter";
50     private final TaskListLoader mLoader;
51     private TaskActionController mTaskActionController;
52     private OnClickListener mClearAllListener;
53     private boolean mIsShowingLoadingUi;
54 
TaskAdapter(@onNull TaskListLoader loader)55     public TaskAdapter(@NonNull TaskListLoader loader) {
56         mLoader = loader;
57     }
58 
setActionController(TaskActionController taskActionController)59     public void setActionController(TaskActionController taskActionController) {
60         mTaskActionController = taskActionController;
61     }
62 
setOnClearAllClickListener(OnClickListener listener)63     public void setOnClearAllClickListener(OnClickListener listener) {
64         mClearAllListener = listener;
65     }
66 
67     /**
68      * Sets all positions in the task adapter to loading views, binding new views if necessary.
69      * This changes the task adapter's view of the data, so the appropriate notify events should be
70      * called in addition to this method to reflect the changes.
71      *
72      * @param isShowingLoadingUi true to bind loading task views to all positions, false to return
73      *                           to the real data
74      */
setIsShowingLoadingUi(boolean isShowingLoadingUi)75     public void setIsShowingLoadingUi(boolean isShowingLoadingUi) {
76         mIsShowingLoadingUi = isShowingLoadingUi;
77     }
78 
79     @Override
onCreateViewHolder(ViewGroup parent, int viewType)80     public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
81         switch (viewType) {
82             case ITEM_TYPE_TASK:
83                 TaskItemView itemView = (TaskItemView) LayoutInflater.from(parent.getContext())
84                         .inflate(R.layout.task_item_view, parent, false);
85                 TaskHolder taskHolder = new TaskHolder(itemView);
86                 itemView.setOnClickListener(
87                         view -> mTaskActionController.launchTaskFromView(taskHolder));
88                 return taskHolder;
89             case ITEM_TYPE_CLEAR_ALL:
90                 View clearView = LayoutInflater.from(parent.getContext())
91                         .inflate(R.layout.clear_all_button, parent, false);
92                 ClearAllHolder clearAllHolder = new ClearAllHolder(clearView);
93                 Button clearViewButton = clearView.findViewById(R.id.clear_all_button);
94                 clearViewButton.setOnClickListener(mClearAllListener);
95                 return clearAllHolder;
96             default:
97                 throw new IllegalArgumentException("No known holder for item type: " + viewType);
98         }
99     }
100 
101     @Override
onBindViewHolder(ViewHolder holder, int position)102     public void onBindViewHolder(ViewHolder holder, int position) {
103         onBindViewHolderInternal(holder, position, false /* willAnimate */);
104     }
105 
106     @Override
onBindViewHolder(@onNull ViewHolder holder, int position, @NonNull List<Object> payloads)107     public void onBindViewHolder(@NonNull ViewHolder holder, int position,
108             @NonNull List<Object> payloads) {
109         if (payloads.isEmpty()) {
110             super.onBindViewHolder(holder, position, payloads);
111             return;
112         }
113         int changeType = (int) payloads.get(0);
114         if (changeType == CHANGE_EVENT_TYPE_EMPTY_TO_CONTENT) {
115             // Bind in preparation for animation
116             onBindViewHolderInternal(holder, position, true /* willAnimate */);
117         } else {
118             throw new IllegalArgumentException("Payload content is not a valid change event type: "
119                     + changeType);
120         }
121     }
122 
onBindViewHolderInternal(@onNull ViewHolder holder, int position, boolean willAnimate)123     private void onBindViewHolderInternal(@NonNull ViewHolder holder, int position,
124             boolean willAnimate) {
125         int itemType = getItemViewType(position);
126         switch (itemType) {
127             case ITEM_TYPE_TASK:
128                 TaskHolder taskHolder = (TaskHolder) holder;
129                 if (mIsShowingLoadingUi) {
130                     taskHolder.bindEmptyUi();
131                     return;
132                 }
133                 List<Task> tasks = mLoader.getCurrentTaskList();
134                 int taskPos = position - TASKS_START_POSITION;
135                 if (taskPos >= tasks.size()) {
136                     // Task list has updated.
137                     return;
138                 }
139                 Task task = tasks.get(taskPos);
140                 taskHolder.bindTask(task, willAnimate /* willAnimate */);
141                 mLoader.loadTaskIconAndLabel(task, () -> {
142                     // Ensure holder still has the same task.
143                     if (Objects.equals(Optional.of(task), taskHolder.getTask())) {
144                         taskHolder.getTaskItemView().setIcon(task.icon);
145                         taskHolder.getTaskItemView().setLabel(task.titleDescription);
146                     }
147                 });
148                 mLoader.loadTaskThumbnail(task, () -> {
149                     if (Objects.equals(Optional.of(task), taskHolder.getTask())) {
150                         taskHolder.getTaskItemView().setThumbnail(task.thumbnail);
151                     }
152                 });
153                 break;
154             case ITEM_TYPE_CLEAR_ALL:
155                 // Nothing to bind.
156                 break;
157             default:
158                 throw new IllegalArgumentException("No known holder for item type: " + itemType);
159         }
160     }
161 
162     @Override
getItemViewType(int position)163     public int getItemViewType(int position) {
164         // Bottom is always clear all button.
165         return (position == 0) ? ITEM_TYPE_CLEAR_ALL : ITEM_TYPE_TASK;
166     }
167 
168     @Override
getItemCount()169     public int getItemCount() {
170         int itemCount = TASKS_START_POSITION;
171         if (mIsShowingLoadingUi) {
172             // Show loading version of all items.
173             itemCount += MAX_TASKS_TO_DISPLAY;
174         } else {
175             itemCount += Math.min(mLoader.getCurrentTaskList().size(), MAX_TASKS_TO_DISPLAY);
176         }
177         return itemCount;
178     }
179 }
180