Merge "Add recent tasks to Taskbar" into sc-dev

This commit is contained in:
Tony Wickham 2021-02-04 06:30:37 +00:00 committed by Android (Google) Code Review
commit d10b587003
8 changed files with 361 additions and 3 deletions

View File

@ -0,0 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2021 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<View
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="@dimen/taskbar_divider_thickness"
android:layout_height="@dimen/taskbar_divider_height"
android:layout_marginStart="@dimen/taskbar_icon_spacing"
android:layout_marginEnd="@dimen/taskbar_icon_spacing"
android:background="@color/taskbar_divider" />

View File

@ -27,4 +27,5 @@
<!-- Taskbar -->
<color name="taskbar_background">#101010</color>
<color name="taskbar_divider">#C0C0C0</color>
</resources>

View File

@ -127,4 +127,6 @@
<dimen name="taskbar_icon_drag_icon_size">54dp</dimen>
<!-- Note that this applies to both sides of all icons, so visible space is double this. -->
<dimen name="taskbar_icon_spacing">14dp</dimen>
<dimen name="taskbar_divider_thickness">1dp</dimen>
<dimen name="taskbar_divider_height">24dp</dimen>
</resources>

View File

@ -23,6 +23,8 @@ import static com.android.systemui.shared.system.WindowManagerWrapper.ITYPE_BOTT
import static com.android.systemui.shared.system.WindowManagerWrapper.ITYPE_EXTRA_NAVIGATION_BAR;
import android.animation.Animator;
import android.app.ActivityOptions;
import android.content.ComponentName;
import android.graphics.PixelFormat;
import android.graphics.Point;
import android.view.Gravity;
@ -42,8 +44,13 @@ import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.states.StateAnimationConfig;
import com.android.launcher3.touch.ItemClickHandler;
import com.android.quickstep.AnimatedFloat;
import com.android.systemui.shared.recents.model.Task;
import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.shared.system.WindowManagerWrapper;
import java.util.ArrayList;
import java.util.List;
/**
* Interfaces with Launcher/WindowManager/SystemUI to determine what to show in TaskbarView.
*/
@ -60,11 +67,17 @@ public class TaskbarController {
private final TaskbarStateHandler mTaskbarStateHandler;
private final TaskbarVisibilityController mTaskbarVisibilityController;
private final TaskbarHotseatController mHotseatController;
private final TaskbarRecentsController mRecentsController;
private final TaskbarDragController mDragController;
// Initialized in init().
private WindowManager.LayoutParams mWindowLayoutParams;
// Contains all loaded Tasks, not yet deduped from Hotseat items.
private List<Task> mLatestLoadedRecentTasks;
// Contains all loaded Hotseat items.
private ItemInfo[] mLatestLoadedHotseatItems;
public TaskbarController(BaseQuickstepLauncher launcher,
TaskbarContainerView taskbarContainerView) {
mLauncher = launcher;
@ -79,6 +92,8 @@ public class TaskbarController {
createTaskbarVisibilityControllerCallbacks());
mHotseatController = new TaskbarHotseatController(mLauncher,
createTaskbarHotseatControllerCallbacks());
mRecentsController = new TaskbarRecentsController(mLauncher,
createTaskbarRecentsControllerCallbacks());
mDragController = new TaskbarDragController(mLauncher);
}
@ -101,7 +116,16 @@ public class TaskbarController {
return new TaskbarViewCallbacks() {
@Override
public View.OnClickListener getItemOnClickListener() {
return ItemClickHandler.INSTANCE;
return view -> {
Object tag = view.getTag();
if (tag instanceof Task) {
Task task = (Task) tag;
ActivityManagerWrapper.getInstance().startActivityFromRecents(task.key,
ActivityOptions.makeBasic());
} else {
ItemClickHandler.INSTANCE.onClick(view);
}
};
}
@Override
@ -116,6 +140,23 @@ public class TaskbarController {
@Override
public void updateHotseatItems(ItemInfo[] hotseatItemInfos) {
mTaskbarView.updateHotseatItems(hotseatItemInfos);
mLatestLoadedHotseatItems = hotseatItemInfos;
dedupeAndUpdateRecentItems();
}
};
}
private TaskbarRecentsControllerCallbacks createTaskbarRecentsControllerCallbacks() {
return new TaskbarRecentsControllerCallbacks() {
@Override
public void updateRecentItems(ArrayList<Task> recentTasks) {
mLatestLoadedRecentTasks = recentTasks;
dedupeAndUpdateRecentItems();
}
@Override
public void updateRecentTaskAtIndex(int taskIndex, Task task) {
mTaskbarView.updateRecentTaskAtIndex(taskIndex, task);
}
};
}
@ -124,11 +165,13 @@ public class TaskbarController {
* Initializes the Taskbar, including adding it to the screen.
*/
public void init() {
mTaskbarView.init(mHotseatController.getNumHotseatIcons());
mTaskbarView.init(mHotseatController.getNumHotseatIcons(),
mRecentsController.getNumRecentIcons());
addToWindowManager();
mTaskbarStateHandler.setTaskbarCallbacks(createTaskbarStateHandlerCallbacks());
mTaskbarVisibilityController.init();
mHotseatController.init();
mRecentsController.init();
}
private TaskbarStateHandlerCallbacks createTaskbarStateHandlerCallbacks() {
@ -149,6 +192,7 @@ public class TaskbarController {
mTaskbarStateHandler.setTaskbarCallbacks(null);
mTaskbarVisibilityController.cleanup();
mHotseatController.cleanup();
mRecentsController.cleanup();
}
private void removeFromWindowManager() {
@ -246,6 +290,52 @@ public class TaskbarController {
return mTaskbarView.isDraggingItem();
}
private void dedupeAndUpdateRecentItems() {
if (mLatestLoadedRecentTasks == null || mLatestLoadedHotseatItems == null) {
return;
}
final int numRecentIcons = mRecentsController.getNumRecentIcons();
// From most recent to least recently opened.
List<Task> dedupedTasksInDescendingOrder = new ArrayList<>();
for (int i = mLatestLoadedRecentTasks.size() - 1; i >= 0; i--) {
Task task = mLatestLoadedRecentTasks.get(i);
boolean isTaskInHotseat = false;
for (ItemInfo hotseatItem : mLatestLoadedHotseatItems) {
if (hotseatItem == null) {
continue;
}
ComponentName hotseatActivity = hotseatItem.getTargetComponent();
if (hotseatActivity != null && task.key.sourceComponent.getPackageName()
.equals(hotseatActivity.getPackageName())) {
isTaskInHotseat = true;
break;
}
}
if (!isTaskInHotseat) {
dedupedTasksInDescendingOrder.add(task);
if (dedupedTasksInDescendingOrder.size() == numRecentIcons) {
break;
}
}
}
// TaskbarView expects an array of all the recent tasks to show, in the order to show them.
// So we create an array of the proper size, then fill it in such that the most recent items
// are at the end. If there aren't enough elements to fill the array, leave them null.
Task[] tasksArray = new Task[numRecentIcons];
for (int i = 0; i < tasksArray.length; i++) {
Task task = i >= dedupedTasksInDescendingOrder.size()
? null
: dedupedTasksInDescendingOrder.get(i);
tasksArray[tasksArray.length - 1 - i] = task;
}
mTaskbarView.updateRecentTasks(tasksArray);
mRecentsController.loadIconsForTasks(tasksArray);
}
/**
* @return Whether the given View is in the same window as Taskbar.
*/
@ -283,4 +373,12 @@ public class TaskbarController {
protected interface TaskbarHotseatControllerCallbacks {
void updateHotseatItems(ItemInfo[] hotseatItemInfos);
}
/**
* Contains methods that TaskbarRecentsController can call to interface with TaskbarController.
*/
protected interface TaskbarRecentsControllerCallbacks {
void updateRecentItems(ArrayList<Task> recentTasks);
void updateRecentTaskAtIndex(int taskIndex, Task task);
}
}

View File

@ -25,6 +25,7 @@ import android.content.pm.LauncherApps;
import android.content.res.Resources;
import android.graphics.Canvas;
import android.graphics.Point;
import android.os.UserHandle;
import android.view.DragEvent;
import android.view.View;
@ -33,6 +34,7 @@ import com.android.launcher3.BubbleTextView;
import com.android.launcher3.LauncherSettings;
import com.android.launcher3.R;
import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.systemui.shared.recents.model.Task;
import com.android.systemui.shared.system.ClipDescriptionCompat;
import com.android.systemui.shared.system.LauncherAppsCompat;
@ -102,6 +104,15 @@ public class TaskbarDragController {
item.getIntent().getComponent(), null, item.user));
}
intent.putExtra(Intent.EXTRA_USER, item.user);
} else if (tag instanceof Task) {
Task task = (Task) tag;
clipDescription = new ClipDescription(task.titleDescription,
new String[] {
ClipDescriptionCompat.MIMETYPE_APPLICATION_TASK
});
intent = new Intent();
intent.putExtra(ClipDescriptionCompat.EXTRA_TASK_ID, task.key.id);
intent.putExtra(Intent.EXTRA_USER, UserHandle.of(task.key.userId));
}
if (clipDescription != null && intent != null) {

View File

@ -0,0 +1,114 @@
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.launcher3.taskbar;
import com.android.launcher3.BaseQuickstepLauncher;
import com.android.quickstep.RecentsModel;
import com.android.quickstep.util.CancellableTask;
import com.android.systemui.shared.recents.model.Task;
import com.android.systemui.shared.system.TaskStackChangeListener;
import com.android.systemui.shared.system.TaskStackChangeListeners;
import java.util.ArrayList;
/**
* Works with TaskbarController to update the TaskbarView's Recent items.
*/
public class TaskbarRecentsController {
private final int mNumRecentIcons = 2;
private final BaseQuickstepLauncher mLauncher;
private final TaskbarController.TaskbarRecentsControllerCallbacks mTaskbarCallbacks;
private final RecentsModel mRecentsModel;
private final TaskStackChangeListener mTaskStackChangeListener = new TaskStackChangeListener() {
@Override
public void onTaskStackChanged() {
reloadRecentTasksIfNeeded();
}
};
// TODO: add TaskbarVisualsChangedListener as well (for calendar/clock?)
// Used to keep track of the last requested task list id, so that we do not request to load the
// tasks again if we have already requested it and the task list has not changed
private int mTaskListChangeId = -1;
// The current background requests to load the task icons
private CancellableTask[] mIconLoadRequests = new CancellableTask[mNumRecentIcons];
public TaskbarRecentsController(BaseQuickstepLauncher launcher,
TaskbarController.TaskbarRecentsControllerCallbacks taskbarCallbacks) {
mLauncher = launcher;
mTaskbarCallbacks = taskbarCallbacks;
mRecentsModel = RecentsModel.INSTANCE.get(mLauncher);
}
protected void init() {
TaskStackChangeListeners.getInstance().registerTaskStackListener(mTaskStackChangeListener);
reloadRecentTasksIfNeeded();
}
protected void cleanup() {
TaskStackChangeListeners.getInstance().unregisterTaskStackListener(
mTaskStackChangeListener);
cancelAllPendingIconLoadTasks();
}
private void reloadRecentTasksIfNeeded() {
if (!mRecentsModel.isTaskListValid(mTaskListChangeId)) {
mTaskListChangeId = mRecentsModel.getTasks(this::onRecentTasksChanged);
}
}
private void cancelAllPendingIconLoadTasks() {
for (int i = 0; i < mIconLoadRequests.length; i++) {
if (mIconLoadRequests[i] != null) {
mIconLoadRequests[i].cancel();
}
mIconLoadRequests[i] = null;
}
}
private void onRecentTasksChanged(ArrayList<Task> tasks) {
mTaskbarCallbacks.updateRecentItems(tasks);
}
/**
* For each Task, loads its icon from the cache in the background, then calls
* {@link TaskbarController.TaskbarRecentsControllerCallbacks#updateRecentTaskAtIndex}.
*/
protected void loadIconsForTasks(Task[] tasks) {
cancelAllPendingIconLoadTasks();
for (int i = 0; i < tasks.length; i++) {
Task task = tasks[i];
if (task == null) {
continue;
}
final int taskIndex = i;
mIconLoadRequests[i] = mRecentsModel.getIconCache().updateIconInBackground(
task, updatedTask -> onTaskIconLoaded(task, taskIndex));
}
}
private void onTaskIconLoaded(Task task, int taskIndex) {
mTaskbarCallbacks.updateRecentTaskAtIndex(taskIndex, task);
}
protected int getNumRecentIcons() {
return mNumRecentIcons;
}
}

View File

@ -19,6 +19,7 @@ import android.content.Context;
import android.content.res.Resources;
import android.graphics.RectF;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.view.DragEvent;
import android.view.LayoutInflater;
@ -35,6 +36,7 @@ import com.android.launcher3.BubbleTextView;
import com.android.launcher3.R;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.systemui.shared.recents.model.Task;
/**
* Hosts the Taskbar content such as Hotseat and Recent Apps. Drawn on top of other apps.
@ -52,6 +54,9 @@ public class TaskbarView extends LinearLayout {
// Initialized in init().
private int mHotseatStartIndex;
private int mHotseatEndIndex;
private View mHotseatRecentsDivider;
private int mRecentsStartIndex;
private int mRecentsEndIndex;
private TaskbarController.TaskbarViewCallbacks mControllerCallbacks;
@ -89,10 +94,17 @@ public class TaskbarView extends LinearLayout {
mControllerCallbacks = taskbarViewCallbacks;
}
protected void init(int numHotseatIcons) {
protected void init(int numHotseatIcons, int numRecentIcons) {
mHotseatStartIndex = 0;
mHotseatEndIndex = mHotseatStartIndex + numHotseatIcons - 1;
updateHotseatItems(new ItemInfo[numHotseatIcons]);
int dividerIndex = mHotseatEndIndex + 1;
mHotseatRecentsDivider = addDivider(dividerIndex);
mRecentsStartIndex = dividerIndex + 1;
mRecentsEndIndex = mRecentsStartIndex + numRecentIcons - 1;
updateRecentTasks(new Task[numRecentIcons]);
}
protected void cleanup() {
@ -147,6 +159,93 @@ public class TaskbarView extends LinearLayout {
hotseatView.setOnLongClickListener(null);
}
}
updateHotseatRecentsDividerVisibility();
}
private View addDivider(int dividerIndex) {
View divider = inflate(R.layout.taskbar_divider);
addView(divider, dividerIndex);
return divider;
}
/**
* Inflates/binds the Recents items to show in the Taskbar given their Tasks.
*/
protected void updateRecentTasks(Task[] tasks) {
for (int i = 0; i < tasks.length; i++) {
Task task = tasks[i];
int recentsIndex = mRecentsStartIndex + i;
View recentsView = getChildAt(recentsIndex);
// Inflate empty icon Views.
if (recentsView == null) {
BubbleTextView btv = (BubbleTextView) inflate(R.layout.taskbar_app_icon);
LayoutParams lp = new LayoutParams(btv.getIconSize(), btv.getIconSize());
lp.setMargins(mItemMarginLeftRight, 0, mItemMarginLeftRight, 0);
recentsView = btv;
addView(recentsView, recentsIndex, lp);
}
// Apply the Task, or hide the view if there is none for a given index.
if (recentsView instanceof BubbleTextView && task != null) {
applyTaskToBubbleTextView((BubbleTextView) recentsView, task);
recentsView.setVisibility(VISIBLE);
recentsView.setOnClickListener(mControllerCallbacks.getItemOnClickListener());
recentsView.setOnLongClickListener(
mControllerCallbacks.getItemOnLongClickListener());
} else {
recentsView.setVisibility(GONE);
recentsView.setOnClickListener(null);
recentsView.setOnLongClickListener(null);
}
}
updateHotseatRecentsDividerVisibility();
}
private void applyTaskToBubbleTextView(BubbleTextView btv, Task task) {
if (task.icon != null) {
Drawable icon = task.icon.getConstantState().newDrawable().mutate();
btv.applyIconAndLabel(icon, task.titleDescription);
}
btv.setTag(task);
}
protected void updateRecentTaskAtIndex(int taskIndex, Task task) {
View taskView = getChildAt(mRecentsStartIndex + taskIndex);
if (taskView instanceof BubbleTextView) {
applyTaskToBubbleTextView((BubbleTextView) taskView, task);
}
}
/**
* Make the divider VISIBLE between the Hotseat and Recents if there is at least one icon in
* each, otherwise make it GONE.
*/
private void updateHotseatRecentsDividerVisibility() {
if (mHotseatRecentsDivider == null) {
return;
}
boolean hasAtLeastOneHotseatItem = false;
for (int i = mHotseatStartIndex; i <= mHotseatEndIndex; i++) {
if (getChildAt(i).getVisibility() != GONE) {
hasAtLeastOneHotseatItem = true;
break;
}
}
boolean hasAtLeastOneRecentItem = false;
for (int i = mRecentsStartIndex; i <= mRecentsEndIndex; i++) {
if (getChildAt(i).getVisibility() != GONE) {
hasAtLeastOneRecentItem = true;
break;
}
}
mHotseatRecentsDivider.setVisibility(hasAtLeastOneHotseatItem && hasAtLeastOneRecentItem
? VISIBLE : GONE);
}
@Override

View File

@ -358,6 +358,16 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver,
}
}
/**
* Directly set the icon and label.
*/
@UiThread
public void applyIconAndLabel(Drawable icon, CharSequence label) {
setIcon(icon);
setText(label);
setContentDescription(label);
}
/**
* Overrides the default long press timeout.
*/