From b589241fd683cda2da005093c891a89b39fc08a3 Mon Sep 17 00:00:00 2001 From: Kevin Date: Tue, 19 Mar 2019 16:59:32 -0700 Subject: [PATCH] Show ASAP for Recents Go and load content after Recent UX discussion led to decision to show as soon as we have the list and order and then load the content in after as soon as it comes. This introduces API in TaskListLoader to facilitate this and default values for the view when the content is not yet loaded. Bug: 114136250 Test: Go to recents, see content load in Change-Id: I6766cf014e3de78894353614157dbc8798031c2f --- .../com/android/quickstep/TaskAdapter.java | 16 ++- .../src/com/android/quickstep/TaskHolder.java | 11 +- .../com/android/quickstep/TaskListLoader.java | 116 +++++++++--------- .../android/quickstep/views/TaskItemView.java | 42 ++++++- 4 files changed, 116 insertions(+), 69 deletions(-) diff --git a/go/quickstep/src/com/android/quickstep/TaskAdapter.java b/go/quickstep/src/com/android/quickstep/TaskAdapter.java index e56cc51257..c98eca6221 100644 --- a/go/quickstep/src/com/android/quickstep/TaskAdapter.java +++ b/go/quickstep/src/com/android/quickstep/TaskAdapter.java @@ -75,8 +75,20 @@ public final class TaskAdapter extends Adapter { // 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 diff --git a/go/quickstep/src/com/android/quickstep/TaskHolder.java b/go/quickstep/src/com/android/quickstep/TaskHolder.java index a89229f2fe..744afd766e 100644 --- a/go/quickstep/src/com/android/quickstep/TaskHolder.java +++ b/go/quickstep/src/com/android/quickstep/TaskHolder.java @@ -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(); } /** diff --git a/go/quickstep/src/com/android/quickstep/TaskListLoader.java b/go/quickstep/src/com/android/quickstep/TaskListLoader.java index c86c24e8f3..1234989d24 100644 --- a/go/quickstep/src/com/android/quickstep/TaskListLoader.java +++ b/go/quickstep/src/com/android/quickstep/TaskListLoader.java @@ -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; /** @@ -39,35 +38,48 @@ public final class TaskListLoader { private ArrayList 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 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> onTasksLoadedCallback) { + public void loadTaskList(@Nullable Consumer> 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; } @@ -76,15 +88,45 @@ 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); }); } + /** + * 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 + */ + public void loadTaskIconAndLabel(Task task, @Nullable Runnable onLoadedCallback) { + mRecentsModel.getIconCache().updateIconInBackground(task, + loadedTask -> onLoadedCallback.run()); + } + + /** + * 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 + */ + public void loadTaskThumbnail(Task task, @Nullable Runnable onLoadedCallback) { + mRecentsModel.getThumbnailCache().updateThumbnailInBackground(task, + thumbnail -> onLoadedCallback.run()); + } + /** * Removes the task from the current task list. */ @@ -98,42 +140,4 @@ public final class TaskListLoader { void clearAllTasks() { mTaskList.clear(); } - - /** - * 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 - */ - private void loadTaskContents(ArrayList 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()); - } - - // Load the thumbnail. May return immediately and synchronously if the thumbnail is - // cached. - mRecentsModel.getThumbnailCache().updateThumbnailInBackground(task, - thumbnail -> itemLoadedRunnable.run()); - } - } } diff --git a/go/quickstep/src/com/android/quickstep/views/TaskItemView.java b/go/quickstep/src/com/android/quickstep/views/TaskItemView.java index 373f107910..d831b206a6 100644 --- a/go/quickstep/src/com/android/quickstep/views/TaskItemView.java +++ b/go/quickstep/src/com/android/quickstep/views/TaskItemView.java @@ -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; @@ -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; /** @@ -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 @@ -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); }