Add keyboard support to Grid-based Recents.
Test: Checked that pressing tab, shift + tab, alt + tab, alt + shift + tab work for task navigation in Recents on local sw600dp device. Also checked that Recents on phones work properly. Bug: 32101881 Change-Id: I3e4cd212d2900523ece30a85cae7fb73a9594efb
This commit is contained in:
@@ -0,0 +1,19 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright (C) 2017 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.
|
||||
-->
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<solid android:color="#61FFFFFF" />
|
||||
<corners android:radius="8dp"/>
|
||||
</shape>
|
||||
@@ -21,5 +21,6 @@
|
||||
<dimen name="recents_grid_padding_task_view">20dp</dimen>
|
||||
<dimen name="recents_grid_task_view_header_height">44dp</dimen>
|
||||
<dimen name="recents_grid_task_view_header_button_padding">8dp</dimen>
|
||||
<dimen name="recents_grid_task_view_focused_frame_thickness">8dp</dimen>
|
||||
</resources>
|
||||
|
||||
|
||||
@@ -50,7 +50,7 @@ public class RecentsActivityLaunchState {
|
||||
/**
|
||||
* Returns the task to focus given the current launch state.
|
||||
*/
|
||||
public int getInitialFocusTaskIndex(int numTasks) {
|
||||
public int getInitialFocusTaskIndex(int numTasks, boolean useGridLayout) {
|
||||
RecentsDebugFlags debugFlags = Recents.getDebugFlags();
|
||||
RecentsActivityLaunchState launchState = Recents.getConfiguration().getLaunchState();
|
||||
if (launchedFromApp) {
|
||||
@@ -66,6 +66,11 @@ public class RecentsActivityLaunchState {
|
||||
return numTasks - 1;
|
||||
}
|
||||
|
||||
if (useGridLayout) {
|
||||
// If coming from another app to the grid layout, focus the front most task
|
||||
return numTasks - 1;
|
||||
}
|
||||
|
||||
// If coming from another app, focus the next task
|
||||
return Math.max(0, numTasks - 2);
|
||||
} else {
|
||||
|
||||
@@ -70,6 +70,7 @@ import com.android.systemui.recents.events.activity.LaunchTaskStartedEvent;
|
||||
import com.android.systemui.recents.events.activity.MultiWindowStateChangedEvent;
|
||||
import com.android.systemui.recents.events.activity.PackagesChangedEvent;
|
||||
import com.android.systemui.recents.events.activity.ShowStackActionButtonEvent;
|
||||
import com.android.systemui.recents.events.component.RecentsVisibilityChangedEvent;
|
||||
import com.android.systemui.recents.events.ui.AllTaskViewsDismissedEvent;
|
||||
import com.android.systemui.recents.events.ui.DeleteTaskDataEvent;
|
||||
import com.android.systemui.recents.events.ui.DismissAllTaskViewsEvent;
|
||||
@@ -93,6 +94,7 @@ import com.android.systemui.recents.model.Task;
|
||||
import com.android.systemui.recents.model.TaskStack;
|
||||
|
||||
import com.android.systemui.recents.views.grid.GridTaskView;
|
||||
import com.android.systemui.recents.views.grid.TaskViewFocusFrame;
|
||||
import java.io.PrintWriter;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
@@ -206,6 +208,10 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
|
||||
private int mLastWidth;
|
||||
private int mLastHeight;
|
||||
|
||||
// We keep track of the task view focused by user interaction and draw a frame around it in the
|
||||
// grid layout.
|
||||
private TaskViewFocusFrame mTaskViewFocusFrame;
|
||||
|
||||
// A convenience update listener to request updating clipping of tasks
|
||||
private ValueAnimator.AnimatorUpdateListener mRequestUpdateClippingListener =
|
||||
new ValueAnimator.AnimatorUpdateListener() {
|
||||
@@ -265,6 +271,14 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
|
||||
mDisplayOrientation = Utilities.getAppConfiguration(mContext).orientation;
|
||||
mDisplayRect = ssp.getDisplayRect();
|
||||
|
||||
// Create a frame to draw around the focused task view
|
||||
if (Recents.getConfiguration().isGridEnabled) {
|
||||
mTaskViewFocusFrame = new TaskViewFocusFrame(mContext, this,
|
||||
mLayoutAlgorithm.mTaskGridLayoutAlgorithm);
|
||||
addView(mTaskViewFocusFrame);
|
||||
getViewTreeObserver().addOnGlobalFocusChangeListener(mTaskViewFocusFrame);
|
||||
}
|
||||
|
||||
int taskBarDismissDozeDelaySeconds = getResources().getInteger(
|
||||
R.integer.recents_task_bar_dismiss_delay_seconds);
|
||||
mUIDozeTrigger = new DozeTrigger(taskBarDismissDozeDelaySeconds, new Runnable() {
|
||||
@@ -878,7 +892,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
|
||||
*
|
||||
* @return whether or not the stack will scroll as a part of this focus change
|
||||
*/
|
||||
private boolean setFocusedTask(int taskIndex, boolean scrollToTask,
|
||||
public boolean setFocusedTask(int taskIndex, boolean scrollToTask,
|
||||
final boolean requestViewFocus) {
|
||||
return setFocusedTask(taskIndex, scrollToTask, requestViewFocus, 0);
|
||||
}
|
||||
@@ -888,7 +902,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
|
||||
*
|
||||
* @return whether or not the stack will scroll as a part of this focus change
|
||||
*/
|
||||
private boolean setFocusedTask(int focusTaskIndex, boolean scrollToTask,
|
||||
public boolean setFocusedTask(int focusTaskIndex, boolean scrollToTask,
|
||||
boolean requestViewFocus, int timerIndicatorDuration) {
|
||||
// Find the next task to focus
|
||||
int newFocusedTaskIndex = mStack.getTaskCount() > 0 ?
|
||||
@@ -940,6 +954,10 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
|
||||
newFocusedTaskView.setFocusedState(true, requestViewFocus);
|
||||
}
|
||||
}
|
||||
// Any time a task view gets the focus, we move the focus frame around it.
|
||||
if (mTaskViewFocusFrame != null) {
|
||||
mTaskViewFocusFrame.moveGridTaskViewFocus(getChildViewForTask(newFocusedTask));
|
||||
}
|
||||
}
|
||||
return willScroll;
|
||||
}
|
||||
@@ -1005,20 +1023,28 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
|
||||
float stackScroll = mStackScroller.getStackScroll();
|
||||
ArrayList<Task> tasks = mStack.getStackTasks();
|
||||
int taskCount = tasks.size();
|
||||
if (forward) {
|
||||
// Walk backwards and focus the next task smaller than the current stack scroll
|
||||
for (newIndex = taskCount - 1; newIndex >= 0; newIndex--) {
|
||||
float taskP = mLayoutAlgorithm.getStackScrollForTask(tasks.get(newIndex));
|
||||
if (Float.compare(taskP, stackScroll) <= 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (useGridLayout()) {
|
||||
// For the grid layout, we directly set focus to the most recently used task
|
||||
// no matter we're moving forwards or backwards.
|
||||
newIndex = taskCount - 1;
|
||||
} else {
|
||||
// Walk forwards and focus the next task larger than the current stack scroll
|
||||
for (newIndex = 0; newIndex < taskCount; newIndex++) {
|
||||
float taskP = mLayoutAlgorithm.getStackScrollForTask(tasks.get(newIndex));
|
||||
if (Float.compare(taskP, stackScroll) >= 0) {
|
||||
break;
|
||||
// For the grid layout we pick a proper task to focus, according to the current
|
||||
// stack scroll.
|
||||
if (forward) {
|
||||
// Walk backwards and focus the next task smaller than the current stack scroll
|
||||
for (newIndex = taskCount - 1; newIndex >= 0; newIndex--) {
|
||||
float taskP = mLayoutAlgorithm.getStackScrollForTask(tasks.get(newIndex));
|
||||
if (Float.compare(taskP, stackScroll) <= 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Walk forwards and focus the next task larger than the current stack scroll
|
||||
for (newIndex = 0; newIndex < taskCount; newIndex++) {
|
||||
float taskP = mLayoutAlgorithm.getStackScrollForTask(tasks.get(newIndex));
|
||||
if (Float.compare(taskP, stackScroll) >= 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1037,20 +1063,23 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
|
||||
/**
|
||||
* Resets the focused task.
|
||||
*/
|
||||
void resetFocusedTask(Task task) {
|
||||
public void resetFocusedTask(Task task) {
|
||||
if (task != null) {
|
||||
TaskView tv = getChildViewForTask(task);
|
||||
if (tv != null) {
|
||||
tv.setFocusedState(false, false /* requestViewFocus */);
|
||||
}
|
||||
}
|
||||
if (mTaskViewFocusFrame != null) {
|
||||
mTaskViewFocusFrame.moveGridTaskViewFocus(null);
|
||||
}
|
||||
mFocusedTask = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the focused task.
|
||||
*/
|
||||
Task getFocusedTask() {
|
||||
public Task getFocusedTask() {
|
||||
return mFocusedTask;
|
||||
}
|
||||
|
||||
@@ -1253,6 +1282,9 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
|
||||
for (int i = 0; i < taskViewCount; i++) {
|
||||
measureTaskView(mTmpTaskViews.get(i));
|
||||
}
|
||||
if (mTaskViewFocusFrame != null) {
|
||||
mTaskViewFocusFrame.measure();
|
||||
}
|
||||
|
||||
setMeasuredDimension(width, height);
|
||||
mLastWidth = width;
|
||||
@@ -1287,6 +1319,9 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
|
||||
for (int i = 0; i < taskViewCount; i++) {
|
||||
layoutTaskView(changed, mTmpTaskViews.get(i));
|
||||
}
|
||||
if (mTaskViewFocusFrame != null) {
|
||||
mTaskViewFocusFrame.layout();
|
||||
}
|
||||
|
||||
if (changed) {
|
||||
if (mStackScroller.isScrollOutOfBounds()) {
|
||||
@@ -1339,10 +1374,19 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
|
||||
// until after the enter-animation
|
||||
RecentsConfiguration config = Recents.getConfiguration();
|
||||
RecentsActivityLaunchState launchState = config.getLaunchState();
|
||||
int focusedTaskIndex = launchState.getInitialFocusTaskIndex(mStack.getTaskCount());
|
||||
if (focusedTaskIndex != -1) {
|
||||
setFocusedTask(focusedTaskIndex, false /* scrollToTask */,
|
||||
false /* requestViewFocus */);
|
||||
|
||||
// We set the initial focused task view iff the following conditions are satisfied:
|
||||
// 1. Recents is showing task views in stack layout.
|
||||
// 2. Recents is launched with ALT + TAB.
|
||||
boolean setFocusOnFirstLayout = !useGridLayout() ||
|
||||
Recents.getConfiguration().getLaunchState().launchedWithAltTab;
|
||||
if (setFocusOnFirstLayout) {
|
||||
int focusedTaskIndex = launchState.getInitialFocusTaskIndex(mStack.getTaskCount(),
|
||||
useGridLayout());
|
||||
if (focusedTaskIndex != -1) {
|
||||
setFocusedTask(focusedTaskIndex, false /* scrollToTask */,
|
||||
false /* requestViewFocus */);
|
||||
}
|
||||
}
|
||||
updateStackActionButtonVisibility();
|
||||
}
|
||||
@@ -1443,6 +1487,11 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
|
||||
// Remove the task from the ignored set
|
||||
removeIgnoreTask(removedTask);
|
||||
|
||||
// Resize the grid layout task view focus frame
|
||||
if (mTaskViewFocusFrame != null) {
|
||||
mTaskViewFocusFrame.resize();
|
||||
}
|
||||
|
||||
// If requested, relayout with the given animation
|
||||
if (animation != null) {
|
||||
updateLayoutAlgorithm(true /* boundScroll */);
|
||||
@@ -1740,10 +1789,18 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
|
||||
int taskViewExitToHomeDuration = TaskStackAnimationHelper.EXIT_TO_HOME_TRANSLATION_DURATION;
|
||||
animateFreeformWorkspaceBackgroundAlpha(0, new AnimationProps(taskViewExitToHomeDuration,
|
||||
Interpolators.FAST_OUT_SLOW_IN));
|
||||
|
||||
// Dismiss the grid task view focus frame
|
||||
if (mTaskViewFocusFrame != null) {
|
||||
mTaskViewFocusFrame.moveGridTaskViewFocus(null);
|
||||
}
|
||||
}
|
||||
|
||||
public final void onBusEvent(DismissFocusedTaskViewEvent event) {
|
||||
if (mFocusedTask != null) {
|
||||
if (mTaskViewFocusFrame != null) {
|
||||
mTaskViewFocusFrame.moveGridTaskViewFocus(null);
|
||||
}
|
||||
TaskView tv = getChildViewForTask(mFocusedTask);
|
||||
if (tv != null) {
|
||||
tv.dismissTask();
|
||||
@@ -2073,6 +2130,12 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
|
||||
mResetToInitialStateWhenResized = true;
|
||||
}
|
||||
|
||||
public final void onBusEvent(RecentsVisibilityChangedEvent event) {
|
||||
if (!event.visible && mTaskViewFocusFrame != null) {
|
||||
mTaskViewFocusFrame.moveGridTaskViewFocus(null);
|
||||
}
|
||||
}
|
||||
|
||||
public void reloadOnConfigurationChange() {
|
||||
mStableLayoutAlgorithm.reloadOnConfigurationChange(getContext());
|
||||
mLayoutAlgorithm.reloadOnConfigurationChange(getContext());
|
||||
|
||||
@@ -342,8 +342,9 @@ class TaskStackViewTouchHandler implements SwipeHelper.Callback {
|
||||
mSv.invalidate();
|
||||
}
|
||||
|
||||
// Reset the focused task after the user has scrolled
|
||||
if (!mSv.mTouchExplorationEnabled) {
|
||||
// Reset the focused task after the user has scrolled, but we have no scrolling
|
||||
// in grid layout and therefore we don't want to reset the focus there.
|
||||
if (!mSv.mTouchExplorationEnabled && !mSv.useGridLayout()) {
|
||||
mSv.resetFocusedTask(mSv.getFocusedTask());
|
||||
}
|
||||
} else if (mActiveTaskView == null) {
|
||||
|
||||
@@ -51,6 +51,9 @@ public class TaskGridLayoutAlgorithm {
|
||||
private float mAppAspectRatio;
|
||||
private Rect mSystemInsets = new Rect();
|
||||
|
||||
/** The thickness of the focused task view frame. */
|
||||
private int mFocusedFrameThickness;
|
||||
|
||||
/**
|
||||
* When the amount of tasks is determined, the size and position of every task view can be
|
||||
* decided. Each instance of TaskGridRectInfo store the task view information for a certain
|
||||
@@ -137,6 +140,9 @@ public class TaskGridLayoutAlgorithm {
|
||||
public void reloadOnConfigurationChange(Context context) {
|
||||
Resources res = context.getResources();
|
||||
mPaddingTaskView = res.getDimensionPixelSize(R.dimen.recents_grid_padding_task_view);
|
||||
mFocusedFrameThickness = res.getDimensionPixelSize(
|
||||
R.dimen.recents_grid_task_view_focused_frame_thickness);
|
||||
|
||||
mTaskGridRect = new Rect();
|
||||
mTitleBarHeight = res.getDimensionPixelSize(R.dimen.recents_grid_task_view_header_height);
|
||||
|
||||
@@ -223,7 +229,18 @@ public class TaskGridLayoutAlgorithm {
|
||||
return buttonRect;
|
||||
}
|
||||
|
||||
public void updateTaskGridRect(int taskCount) {
|
||||
if (taskCount > 0) {
|
||||
TaskGridRectInfo gridInfo = mTaskGridRectInfoList[taskCount - 1];
|
||||
mTaskGridRect.set(gridInfo.size);
|
||||
}
|
||||
}
|
||||
|
||||
public Rect getTaskGridRect() {
|
||||
return mTaskGridRect;
|
||||
}
|
||||
|
||||
public int getFocusFrameThickness() {
|
||||
return mFocusedFrameThickness;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,141 @@
|
||||
/*
|
||||
* Copyright (C) 2017 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.systemui.recents.views.grid;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Rect;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
|
||||
import android.view.ViewTreeObserver.OnGlobalFocusChangeListener;
|
||||
import com.android.systemui.R;
|
||||
import com.android.systemui.recents.model.TaskStack;
|
||||
import com.android.systemui.recents.views.TaskStackView;
|
||||
|
||||
public class TaskViewFocusFrame extends View implements OnGlobalFocusChangeListener {
|
||||
|
||||
private TaskStackView mSv;
|
||||
private TaskGridLayoutAlgorithm mTaskGridLayoutAlgorithm;
|
||||
public TaskViewFocusFrame(Context context) {
|
||||
this(context, null);
|
||||
}
|
||||
|
||||
public TaskViewFocusFrame(Context context, AttributeSet attrs) {
|
||||
this(context, attrs, 0);
|
||||
}
|
||||
|
||||
public TaskViewFocusFrame(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
this(context, attrs, defStyleAttr, 0);
|
||||
}
|
||||
|
||||
public TaskViewFocusFrame(Context context, AttributeSet attrs, int defStyleAttr,
|
||||
int defStyleRes) {
|
||||
super(context, attrs, defStyleAttr, defStyleRes);
|
||||
setBackground(mContext.getDrawable(
|
||||
R.drawable.recents_grid_task_view_focus_frame_background));
|
||||
setFocusable(false);
|
||||
hide();
|
||||
}
|
||||
|
||||
public TaskViewFocusFrame(Context context, TaskStackView stackView,
|
||||
TaskGridLayoutAlgorithm taskGridLayoutAlgorithm) {
|
||||
this(context);
|
||||
mSv = stackView;
|
||||
mTaskGridLayoutAlgorithm = taskGridLayoutAlgorithm;
|
||||
}
|
||||
|
||||
/**
|
||||
* Measure the width and height of the focus frame according to the current grid task view size.
|
||||
*/
|
||||
public void measure() {
|
||||
int thickness = mTaskGridLayoutAlgorithm.getFocusFrameThickness();
|
||||
Rect rect = mTaskGridLayoutAlgorithm.getTaskGridRect();
|
||||
measure(
|
||||
MeasureSpec.makeMeasureSpec(rect.width() + thickness * 2, MeasureSpec.EXACTLY),
|
||||
MeasureSpec.makeMeasureSpec(rect.height() + thickness * 2, MeasureSpec.EXACTLY));
|
||||
}
|
||||
|
||||
/**
|
||||
* Layout the focus frame with its size.
|
||||
*/
|
||||
public void layout() {
|
||||
layout(0, 0, getMeasuredWidth(), getMeasuredHeight());
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the current size of grid task view and the focus frame.
|
||||
*/
|
||||
public void resize() {
|
||||
if (mSv.useGridLayout()) {
|
||||
mTaskGridLayoutAlgorithm.updateTaskGridRect(mSv.getStack().getTaskCount());
|
||||
measure();
|
||||
requestLayout();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Move the task view focus frame to surround the newly focused view. If it's {@code null} or
|
||||
* it's not an instance of GridTaskView, we hide the focus frame.
|
||||
* @param newFocus The newly focused view.
|
||||
*/
|
||||
public void moveGridTaskViewFocus(View newFocus) {
|
||||
if (mSv.useGridLayout()) {
|
||||
// The frame only shows up in the grid layout. It shouldn't show up in the stack
|
||||
// layout including when we're in the split screen.
|
||||
if (newFocus instanceof GridTaskView) {
|
||||
// If the focus goes to a GridTaskView, we show the frame and layout it.
|
||||
int[] location = new int[2];
|
||||
newFocus.getLocationInWindow(location);
|
||||
int thickness = mTaskGridLayoutAlgorithm.getFocusFrameThickness();
|
||||
setTranslationX(location[0] - thickness);
|
||||
setTranslationY(location[1] - thickness);
|
||||
show();
|
||||
} else {
|
||||
// If focus goes to other views, we hide the frame.
|
||||
hide();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onGlobalFocusChanged(View oldFocus, View newFocus) {
|
||||
if (!mSv.useGridLayout()) {
|
||||
return;
|
||||
}
|
||||
if (newFocus == null) {
|
||||
// We're going to touch mode, unset the focus.
|
||||
moveGridTaskViewFocus(null);
|
||||
return;
|
||||
}
|
||||
if (oldFocus == null) {
|
||||
// We're returning from touch mode, set the focus to the previously focused task.
|
||||
final TaskStack stack = mSv.getStack();
|
||||
final int taskCount = stack.getTaskCount();
|
||||
final int k = stack.indexOfStackTask(mSv.getFocusedTask());
|
||||
final int taskIndexToFocus = k == -1 ? (taskCount - 1) : (k % taskCount);
|
||||
mSv.setFocusedTask(taskIndexToFocus, false, true);
|
||||
}
|
||||
}
|
||||
|
||||
private void show() {
|
||||
setAlpha(1f);
|
||||
}
|
||||
|
||||
private void hide() {
|
||||
setAlpha(0f);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user