diff --git a/packages/SystemUI/res/drawable/recents_grid_task_view_focus_frame_background.xml b/packages/SystemUI/res/drawable/recents_grid_task_view_focus_frame_background.xml
new file mode 100644
index 0000000000000..a85beb8b68c88
--- /dev/null
+++ b/packages/SystemUI/res/drawable/recents_grid_task_view_focus_frame_background.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/packages/SystemUI/res/values/dimens_grid.xml b/packages/SystemUI/res/values/dimens_grid.xml
index 94ffdb18a80de..0b9836ffcebb8 100644
--- a/packages/SystemUI/res/values/dimens_grid.xml
+++ b/packages/SystemUI/res/values/dimens_grid.xml
@@ -21,5 +21,6 @@
20dp
44dp
8dp
+ 8dp
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivityLaunchState.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivityLaunchState.java
index 914035bc525cb..a7f6b70d281e1 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivityLaunchState.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivityLaunchState.java
@@ -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 {
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
index 87a68461eff06..bc2c4249dd84a 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
@@ -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 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());
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java
index aeb85d064e84a..003138fa75aec 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java
@@ -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) {
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/grid/TaskGridLayoutAlgorithm.java b/packages/SystemUI/src/com/android/systemui/recents/views/grid/TaskGridLayoutAlgorithm.java
index 6fc4ad74d0f8f..365613336fc94 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/grid/TaskGridLayoutAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/grid/TaskGridLayoutAlgorithm.java
@@ -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;
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/grid/TaskViewFocusFrame.java b/packages/SystemUI/src/com/android/systemui/recents/views/grid/TaskViewFocusFrame.java
new file mode 100644
index 0000000000000..86ed583b07aa8
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/grid/TaskViewFocusFrame.java
@@ -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);
+ }
+}