Loading go/quickstep/src/com/android/quickstep/TaskAdapter.java +14 −2 Original line number Diff line number Diff line Loading @@ -75,8 +75,20 @@ public final class TaskAdapter extends Adapter<TaskHolder> { // Task list has updated. return; } holder.bindTask(tasks.get(position)); Task task = tasks.get(position); holder.bindTask(task); mLoader.loadTaskIconAndLabel(task, () -> { // Ensure holder still has the same task. if (task.equals(holder.getTask())) { holder.getTaskItemView().setIcon(task.icon); holder.getTaskItemView().setLabel(task.titleDescription); } }); mLoader.loadTaskThumbnail(task, () -> { if (task.equals(holder.getTask())) { holder.getTaskItemView().setThumbnail(task.thumbnail.thumbnail); } }); } @Override Loading go/quickstep/src/com/android/quickstep/TaskHolder.java +6 −5 Original line number Diff line number Diff line Loading @@ -35,17 +35,18 @@ public final class TaskHolder extends ViewHolder { mTaskItemView = itemView; } public TaskItemView getTaskItemView() { return mTaskItemView; } /** * Bind task content to the view. This includes the task icon and title as well as binding * input handlers such as which task to launch/remove. * Bind a task to the holder, resetting the view and preparing it for content to load in. * * @param task the task to bind to the view */ public void bindTask(Task task) { mTask = task; mTaskItemView.setLabel(task.titleDescription); mTaskItemView.setIcon(task.icon); mTaskItemView.setThumbnail(task.thumbnail.thumbnail); mTaskItemView.resetTaskItemView(); } /** Loading go/quickstep/src/com/android/quickstep/TaskListLoader.java +60 −56 Original line number Diff line number Diff line Loading @@ -25,7 +25,6 @@ import com.android.systemui.shared.recents.model.Task; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Consumer; /** Loading @@ -39,35 +38,48 @@ public final class TaskListLoader { private ArrayList<Task> mTaskList = new ArrayList<>(); private int mTaskListChangeId; private RecentsModel.TaskThumbnailChangeListener listener = (taskId, thumbnailData) -> { Task foundTask = null; for (Task task : mTaskList) { if (task.key.id == taskId) { foundTask = task; break; } } if (foundTask != null) { foundTask.thumbnail = thumbnailData; } return foundTask; }; public TaskListLoader(Context context) { mRecentsModel = RecentsModel.INSTANCE.get(context); mRecentsModel.addThumbnailChangeListener(listener); } /** * Returns the current task list as of the last completed load (see * {@link #loadTaskList}) as a read-only list. This list of tasks is guaranteed to always have * all its task content loaded. * Returns the current task list as of the last completed load (see {@link #loadTaskList}) as a * read-only list. This list of tasks is not guaranteed to have all content loaded. * * @return the current list of tasks w/ all content loaded * @return the current list of tasks */ public List<Task> getCurrentTaskList() { return Collections.unmodifiableList(mTaskList); } /** * Fetches the most recent tasks and updates the task list asynchronously. In addition it * loads the content for each task (icon and label). The callback and task list being updated * only occur when all task content is fully loaded and up-to-date. * Fetches the most recent tasks and updates the task list asynchronously. This call does not * provide guarantees the task content (icon, thumbnail, label) are loaded but will fill in * what it has. May run the callback immediately if there have been no changes in the task * list. * * @param onTasksLoadedCallback callback for when the tasks are fully loaded. Done on the UI * thread * @param onLoadedCallback callback to run when task list is loaded */ public void loadTaskList(@Nullable Consumer<ArrayList<Task>> onTasksLoadedCallback) { public void loadTaskList(@Nullable Consumer<ArrayList<Task>> onLoadedCallback) { if (mRecentsModel.isTaskListValid(mTaskListChangeId)) { // Current task list is already up to date. No need to update. if (onTasksLoadedCallback != null) { onTasksLoadedCallback.accept(mTaskList); if (onLoadedCallback != null) { onLoadedCallback.accept(mTaskList); } return; } Loading @@ -76,64 +88,56 @@ public final class TaskListLoader { // Reverse tasks to put most recent at the bottom of the view Collections.reverse(tasks); // Load task content loadTaskContents(tasks, () -> { mTaskList = tasks; if (onTasksLoadedCallback != null) { onTasksLoadedCallback.accept(mTaskList); for (Task task : tasks) { int loadedPos = mTaskList.indexOf(task); if (loadedPos == -1) { continue; } }); Task loadedTask = mTaskList.get(loadedPos); task.icon = loadedTask.icon; task.titleDescription = loadedTask.titleDescription; task.thumbnail = loadedTask.thumbnail; } mTaskList = tasks; onLoadedCallback.accept(tasks); }); } /** * Removes the task from the current task list. * Load task icon and label asynchronously if it is not already loaded in the task. If the task * already has an icon, this calls the callback immediately. * * @param task task to update with icon + label * @param onLoadedCallback callback to run when task has icon and label */ void removeTask(Task task) { mTaskList.remove(task); public void loadTaskIconAndLabel(Task task, @Nullable Runnable onLoadedCallback) { mRecentsModel.getIconCache().updateIconInBackground(task, loadedTask -> onLoadedCallback.run()); } /** * Clears the current task list. * Load thumbnail asynchronously if not already loaded in the task. If the task already has a * thumbnail or if the thumbnail is cached, this calls the callback immediately. * * @param task task to update with the thumbnail * @param onLoadedCallback callback to run when task has thumbnail */ void clearAllTasks() { mTaskList.clear(); public void loadTaskThumbnail(Task task, @Nullable Runnable onLoadedCallback) { mRecentsModel.getThumbnailCache().updateThumbnailInBackground(task, thumbnail -> onLoadedCallback.run()); } /** * Loads task content for a list of tasks, including the label, icon, and thumbnail. For content * that isn't cached, load the content asynchronously in the background. * * @param tasksToLoad list of tasks that need to load their content * @param onFullyLoadedCallback runnable to run after all tasks have loaded their content * Removes the task from the current task list. */ private void loadTaskContents(ArrayList<Task> tasksToLoad, @Nullable Runnable onFullyLoadedCallback) { // Make two load requests per task, one for the icon/title and one for the thumbnail. AtomicInteger loadRequestsCount = new AtomicInteger(tasksToLoad.size() * 2); Runnable itemLoadedRunnable = () -> { if (loadRequestsCount.decrementAndGet() == 0 && onFullyLoadedCallback != null) { onFullyLoadedCallback.run(); } }; for (Task task : tasksToLoad) { // Load icon and title. int index = mTaskList.indexOf(task); if (index >= 0) { // If we've already loaded the task and have its content then just copy it over. Task loadedTask = mTaskList.get(index); task.titleDescription = loadedTask.titleDescription; task.icon = loadedTask.icon; itemLoadedRunnable.run(); } else { // Otherwise, load the content in the background. mRecentsModel.getIconCache().updateIconInBackground(task, loadedTask -> itemLoadedRunnable.run()); void removeTask(Task task) { mTaskList.remove(task); } // Load the thumbnail. May return immediately and synchronously if the thumbnail is // cached. mRecentsModel.getThumbnailCache().updateThumbnailInBackground(task, thumbnail -> itemLoadedRunnable.run()); } /** * Clears the current task list. */ void clearAllTasks() { mTaskList.clear(); } } go/quickstep/src/com/android/quickstep/views/TaskItemView.java +36 −6 Original line number Diff line number Diff line Loading @@ -17,6 +17,7 @@ package com.android.quickstep.views; import android.content.Context; import android.graphics.Bitmap; import android.graphics.Color; import android.graphics.drawable.Drawable; import android.util.AttributeSet; import android.view.View; Loading @@ -24,6 +25,8 @@ import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.TextView; import androidx.annotation.Nullable; import com.android.launcher3.R; /** Loading @@ -31,12 +34,16 @@ import com.android.launcher3.R; */ public final class TaskItemView extends LinearLayout { private static final String DEFAULT_LABEL = "..."; private final Drawable mDefaultIcon; private TextView mLabelView; private ImageView mIconView; private ImageView mThumbnailView; public TaskItemView(Context context, AttributeSet attrs) { super(context, attrs); mDefaultIcon = context.getResources().getDrawable( android.R.drawable.sym_def_app_icon, context.getTheme()); } @Override Loading @@ -48,33 +55,56 @@ public final class TaskItemView extends LinearLayout { } /** * Set the label for the task item. * Resets task item view to default values. */ public void resetTaskItemView() { setLabel(DEFAULT_LABEL); setIcon(null); setThumbnail(null); } /** * Set the label for the task item. Sets to a default label if null. * * @param label task label */ public void setLabel(String label) { public void setLabel(@Nullable String label) { if (label == null) { mLabelView.setText(DEFAULT_LABEL); return; } mLabelView.setText(label); } /** * Set the icon for the task item. * Set the icon for the task item. Sets to a default icon if null. * * @param icon task icon */ public void setIcon(Drawable icon) { public void setIcon(@Nullable Drawable icon) { // TODO: Scale the icon up based off the padding on the side // The icon proper is actually smaller than the drawable and has "padding" on the side for // the purpose of drawing the shadow, allowing the icon to pop up, so we need to scale the // view if we want the icon to be flush with the bottom of the thumbnail. if (icon == null) { mIconView.setImageDrawable(mDefaultIcon); return; } mIconView.setImageDrawable(icon); } /** * Set the task thumbnail for the task. * Set the task thumbnail for the task. Sets to a default thumbnail if null. * * @param thumbnail task thumbnail for the task */ public void setThumbnail(Bitmap thumbnail) { public void setThumbnail(@Nullable Bitmap thumbnail) { if (thumbnail == null) { mThumbnailView.setImageBitmap(null); mThumbnailView.setBackgroundColor(Color.GRAY); return; } mThumbnailView.setBackgroundColor(Color.TRANSPARENT); mThumbnailView.setImageBitmap(thumbnail); } Loading Loading
go/quickstep/src/com/android/quickstep/TaskAdapter.java +14 −2 Original line number Diff line number Diff line Loading @@ -75,8 +75,20 @@ public final class TaskAdapter extends Adapter<TaskHolder> { // Task list has updated. return; } holder.bindTask(tasks.get(position)); Task task = tasks.get(position); holder.bindTask(task); mLoader.loadTaskIconAndLabel(task, () -> { // Ensure holder still has the same task. if (task.equals(holder.getTask())) { holder.getTaskItemView().setIcon(task.icon); holder.getTaskItemView().setLabel(task.titleDescription); } }); mLoader.loadTaskThumbnail(task, () -> { if (task.equals(holder.getTask())) { holder.getTaskItemView().setThumbnail(task.thumbnail.thumbnail); } }); } @Override Loading
go/quickstep/src/com/android/quickstep/TaskHolder.java +6 −5 Original line number Diff line number Diff line Loading @@ -35,17 +35,18 @@ public final class TaskHolder extends ViewHolder { mTaskItemView = itemView; } public TaskItemView getTaskItemView() { return mTaskItemView; } /** * Bind task content to the view. This includes the task icon and title as well as binding * input handlers such as which task to launch/remove. * Bind a task to the holder, resetting the view and preparing it for content to load in. * * @param task the task to bind to the view */ public void bindTask(Task task) { mTask = task; mTaskItemView.setLabel(task.titleDescription); mTaskItemView.setIcon(task.icon); mTaskItemView.setThumbnail(task.thumbnail.thumbnail); mTaskItemView.resetTaskItemView(); } /** Loading
go/quickstep/src/com/android/quickstep/TaskListLoader.java +60 −56 Original line number Diff line number Diff line Loading @@ -25,7 +25,6 @@ import com.android.systemui.shared.recents.model.Task; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Consumer; /** Loading @@ -39,35 +38,48 @@ public final class TaskListLoader { private ArrayList<Task> mTaskList = new ArrayList<>(); private int mTaskListChangeId; private RecentsModel.TaskThumbnailChangeListener listener = (taskId, thumbnailData) -> { Task foundTask = null; for (Task task : mTaskList) { if (task.key.id == taskId) { foundTask = task; break; } } if (foundTask != null) { foundTask.thumbnail = thumbnailData; } return foundTask; }; public TaskListLoader(Context context) { mRecentsModel = RecentsModel.INSTANCE.get(context); mRecentsModel.addThumbnailChangeListener(listener); } /** * Returns the current task list as of the last completed load (see * {@link #loadTaskList}) as a read-only list. This list of tasks is guaranteed to always have * all its task content loaded. * Returns the current task list as of the last completed load (see {@link #loadTaskList}) as a * read-only list. This list of tasks is not guaranteed to have all content loaded. * * @return the current list of tasks w/ all content loaded * @return the current list of tasks */ public List<Task> getCurrentTaskList() { return Collections.unmodifiableList(mTaskList); } /** * Fetches the most recent tasks and updates the task list asynchronously. In addition it * loads the content for each task (icon and label). The callback and task list being updated * only occur when all task content is fully loaded and up-to-date. * Fetches the most recent tasks and updates the task list asynchronously. This call does not * provide guarantees the task content (icon, thumbnail, label) are loaded but will fill in * what it has. May run the callback immediately if there have been no changes in the task * list. * * @param onTasksLoadedCallback callback for when the tasks are fully loaded. Done on the UI * thread * @param onLoadedCallback callback to run when task list is loaded */ public void loadTaskList(@Nullable Consumer<ArrayList<Task>> onTasksLoadedCallback) { public void loadTaskList(@Nullable Consumer<ArrayList<Task>> onLoadedCallback) { if (mRecentsModel.isTaskListValid(mTaskListChangeId)) { // Current task list is already up to date. No need to update. if (onTasksLoadedCallback != null) { onTasksLoadedCallback.accept(mTaskList); if (onLoadedCallback != null) { onLoadedCallback.accept(mTaskList); } return; } Loading @@ -76,64 +88,56 @@ public final class TaskListLoader { // Reverse tasks to put most recent at the bottom of the view Collections.reverse(tasks); // Load task content loadTaskContents(tasks, () -> { mTaskList = tasks; if (onTasksLoadedCallback != null) { onTasksLoadedCallback.accept(mTaskList); for (Task task : tasks) { int loadedPos = mTaskList.indexOf(task); if (loadedPos == -1) { continue; } }); Task loadedTask = mTaskList.get(loadedPos); task.icon = loadedTask.icon; task.titleDescription = loadedTask.titleDescription; task.thumbnail = loadedTask.thumbnail; } mTaskList = tasks; onLoadedCallback.accept(tasks); }); } /** * Removes the task from the current task list. * Load task icon and label asynchronously if it is not already loaded in the task. If the task * already has an icon, this calls the callback immediately. * * @param task task to update with icon + label * @param onLoadedCallback callback to run when task has icon and label */ void removeTask(Task task) { mTaskList.remove(task); public void loadTaskIconAndLabel(Task task, @Nullable Runnable onLoadedCallback) { mRecentsModel.getIconCache().updateIconInBackground(task, loadedTask -> onLoadedCallback.run()); } /** * Clears the current task list. * Load thumbnail asynchronously if not already loaded in the task. If the task already has a * thumbnail or if the thumbnail is cached, this calls the callback immediately. * * @param task task to update with the thumbnail * @param onLoadedCallback callback to run when task has thumbnail */ void clearAllTasks() { mTaskList.clear(); public void loadTaskThumbnail(Task task, @Nullable Runnable onLoadedCallback) { mRecentsModel.getThumbnailCache().updateThumbnailInBackground(task, thumbnail -> onLoadedCallback.run()); } /** * Loads task content for a list of tasks, including the label, icon, and thumbnail. For content * that isn't cached, load the content asynchronously in the background. * * @param tasksToLoad list of tasks that need to load their content * @param onFullyLoadedCallback runnable to run after all tasks have loaded their content * Removes the task from the current task list. */ private void loadTaskContents(ArrayList<Task> tasksToLoad, @Nullable Runnable onFullyLoadedCallback) { // Make two load requests per task, one for the icon/title and one for the thumbnail. AtomicInteger loadRequestsCount = new AtomicInteger(tasksToLoad.size() * 2); Runnable itemLoadedRunnable = () -> { if (loadRequestsCount.decrementAndGet() == 0 && onFullyLoadedCallback != null) { onFullyLoadedCallback.run(); } }; for (Task task : tasksToLoad) { // Load icon and title. int index = mTaskList.indexOf(task); if (index >= 0) { // If we've already loaded the task and have its content then just copy it over. Task loadedTask = mTaskList.get(index); task.titleDescription = loadedTask.titleDescription; task.icon = loadedTask.icon; itemLoadedRunnable.run(); } else { // Otherwise, load the content in the background. mRecentsModel.getIconCache().updateIconInBackground(task, loadedTask -> itemLoadedRunnable.run()); void removeTask(Task task) { mTaskList.remove(task); } // Load the thumbnail. May return immediately and synchronously if the thumbnail is // cached. mRecentsModel.getThumbnailCache().updateThumbnailInBackground(task, thumbnail -> itemLoadedRunnable.run()); } /** * Clears the current task list. */ void clearAllTasks() { mTaskList.clear(); } }
go/quickstep/src/com/android/quickstep/views/TaskItemView.java +36 −6 Original line number Diff line number Diff line Loading @@ -17,6 +17,7 @@ package com.android.quickstep.views; import android.content.Context; import android.graphics.Bitmap; import android.graphics.Color; import android.graphics.drawable.Drawable; import android.util.AttributeSet; import android.view.View; Loading @@ -24,6 +25,8 @@ import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.TextView; import androidx.annotation.Nullable; import com.android.launcher3.R; /** Loading @@ -31,12 +34,16 @@ import com.android.launcher3.R; */ public final class TaskItemView extends LinearLayout { private static final String DEFAULT_LABEL = "..."; private final Drawable mDefaultIcon; private TextView mLabelView; private ImageView mIconView; private ImageView mThumbnailView; public TaskItemView(Context context, AttributeSet attrs) { super(context, attrs); mDefaultIcon = context.getResources().getDrawable( android.R.drawable.sym_def_app_icon, context.getTheme()); } @Override Loading @@ -48,33 +55,56 @@ public final class TaskItemView extends LinearLayout { } /** * Set the label for the task item. * Resets task item view to default values. */ public void resetTaskItemView() { setLabel(DEFAULT_LABEL); setIcon(null); setThumbnail(null); } /** * Set the label for the task item. Sets to a default label if null. * * @param label task label */ public void setLabel(String label) { public void setLabel(@Nullable String label) { if (label == null) { mLabelView.setText(DEFAULT_LABEL); return; } mLabelView.setText(label); } /** * Set the icon for the task item. * Set the icon for the task item. Sets to a default icon if null. * * @param icon task icon */ public void setIcon(Drawable icon) { public void setIcon(@Nullable Drawable icon) { // TODO: Scale the icon up based off the padding on the side // The icon proper is actually smaller than the drawable and has "padding" on the side for // the purpose of drawing the shadow, allowing the icon to pop up, so we need to scale the // view if we want the icon to be flush with the bottom of the thumbnail. if (icon == null) { mIconView.setImageDrawable(mDefaultIcon); return; } mIconView.setImageDrawable(icon); } /** * Set the task thumbnail for the task. * Set the task thumbnail for the task. Sets to a default thumbnail if null. * * @param thumbnail task thumbnail for the task */ public void setThumbnail(Bitmap thumbnail) { public void setThumbnail(@Nullable Bitmap thumbnail) { if (thumbnail == null) { mThumbnailView.setImageBitmap(null); mThumbnailView.setBackgroundColor(Color.GRAY); return; } mThumbnailView.setBackgroundColor(Color.TRANSPARENT); mThumbnailView.setImageBitmap(thumbnail); } Loading