Introducing teleportation between sections.
The key combos differ from the ones in the spec because key combos including Meta key don’t get delivered to apps. To fix this, I’ll implement necessary plumbing after the feature freeze. Meanwhile, temporary combos are used. Given that the section and cluster teleportation have a lot in common, I’m not introducing new methods, but adding a param to the cluster teleportation ones. I should have also changed the names to something like findNextKeyboardNavigationCluster => findNextFocusGroup, where “FocusGroup” is a generalized name for clusters and sections. However, that name depends on b/33708251, so I’m not doing it now. I don’t rename existing identifiers, and using “focusGroup” for new ones. Admittedly, this creates mess that will be resolved based on the outcome of the mentioned bug. Bug: 32151632 Test: Manual checks; CTS are coming after feature freeze Change-Id: I01b5d6e5a9689b8f643fa4af695d2ce61265f374
This commit is contained in:
@@ -16,12 +16,16 @@
|
||||
|
||||
package android.view;
|
||||
|
||||
import static android.view.View.FOCUS_GROUP_CLUSTER;
|
||||
import static android.view.View.FOCUS_GROUP_SECTION;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.graphics.Rect;
|
||||
import android.util.ArrayMap;
|
||||
import android.util.SparseArray;
|
||||
import android.util.SparseBooleanArray;
|
||||
import android.view.View.FocusGroupType;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
@@ -107,21 +111,26 @@ public class FocusFinder {
|
||||
|
||||
/**
|
||||
* Find the root of the next keyboard navigation cluster after the current one.
|
||||
* @param root Thew view tree to look inside. Cannot be null
|
||||
* @param focusGroupType Type of the focus group
|
||||
* @param root The view tree to look inside. Cannot be null
|
||||
* @param currentCluster The starting point of the search. Null means the default cluster
|
||||
* @param direction Direction to look
|
||||
* @return The next cluster, or null if none exists
|
||||
*/
|
||||
public View findNextKeyboardNavigationCluster(
|
||||
@NonNull ViewGroup root, @Nullable View currentCluster, int direction) {
|
||||
@FocusGroupType int focusGroupType,
|
||||
@NonNull View root,
|
||||
@Nullable View currentCluster,
|
||||
int direction) {
|
||||
View next = null;
|
||||
|
||||
final ArrayList<View> clusters = mTempList;
|
||||
try {
|
||||
clusters.clear();
|
||||
root.addKeyboardNavigationClusters(clusters, direction);
|
||||
root.addKeyboardNavigationClusters(focusGroupType, clusters, direction);
|
||||
if (!clusters.isEmpty()) {
|
||||
next = findNextKeyboardNavigationCluster(root, currentCluster, clusters, direction);
|
||||
next = findNextKeyboardNavigationCluster(
|
||||
focusGroupType, root, currentCluster, clusters, direction);
|
||||
}
|
||||
} finally {
|
||||
clusters.clear();
|
||||
@@ -197,19 +206,25 @@ public class FocusFinder {
|
||||
}
|
||||
}
|
||||
|
||||
private View findNextKeyboardNavigationCluster(ViewGroup root, View currentCluster,
|
||||
List<View> clusters, int direction) {
|
||||
private View findNextKeyboardNavigationCluster(
|
||||
@FocusGroupType int focusGroupType,
|
||||
View root,
|
||||
View currentCluster,
|
||||
List<View> clusters,
|
||||
int direction) {
|
||||
final int count = clusters.size();
|
||||
|
||||
switch (direction) {
|
||||
case View.FOCUS_FORWARD:
|
||||
case View.FOCUS_DOWN:
|
||||
case View.FOCUS_RIGHT:
|
||||
return getNextKeyboardNavigationCluster(root, currentCluster, clusters, count);
|
||||
return getNextKeyboardNavigationCluster(
|
||||
focusGroupType, root, currentCluster, clusters, count);
|
||||
case View.FOCUS_BACKWARD:
|
||||
case View.FOCUS_UP:
|
||||
case View.FOCUS_LEFT:
|
||||
return getPreviousKeyboardNavigationCluster(root, currentCluster, clusters, count);
|
||||
return getPreviousKeyboardNavigationCluster(
|
||||
focusGroupType, root, currentCluster, clusters, count);
|
||||
default:
|
||||
throw new IllegalArgumentException("Unknown direction: " + direction);
|
||||
}
|
||||
@@ -315,8 +330,12 @@ public class FocusFinder {
|
||||
return null;
|
||||
}
|
||||
|
||||
private static View getNextKeyboardNavigationCluster(ViewGroup root, View currentCluster,
|
||||
List<View> clusters, int count) {
|
||||
private static View getNextKeyboardNavigationCluster(
|
||||
@FocusGroupType int focusGroupType,
|
||||
View root,
|
||||
View currentCluster,
|
||||
List<View> clusters,
|
||||
int count) {
|
||||
if (currentCluster == null) {
|
||||
// The current cluster is the default one.
|
||||
// The next cluster after the default one is the first one.
|
||||
@@ -330,12 +349,25 @@ public class FocusFinder {
|
||||
return clusters.get(position + 1);
|
||||
}
|
||||
|
||||
// The current cluster is the last one. The next one is the default one, i.e. the root.
|
||||
return root;
|
||||
switch (focusGroupType) {
|
||||
case FOCUS_GROUP_CLUSTER:
|
||||
// The current cluster is the last one. The next one is the default one, i.e. the
|
||||
// root.
|
||||
return root;
|
||||
case FOCUS_GROUP_SECTION:
|
||||
// There is no "default section", hence returning the first one.
|
||||
return clusters.get(0);
|
||||
default:
|
||||
throw new IllegalArgumentException("Unknown focus group type: " + focusGroupType);
|
||||
}
|
||||
}
|
||||
|
||||
private static View getPreviousKeyboardNavigationCluster(ViewGroup root, View currentCluster,
|
||||
List<View> clusters, int count) {
|
||||
private static View getPreviousKeyboardNavigationCluster(
|
||||
@FocusGroupType int focusGroupType,
|
||||
View root,
|
||||
View currentCluster,
|
||||
List<View> clusters,
|
||||
int count) {
|
||||
if (currentCluster == null) {
|
||||
// The current cluster is the default one.
|
||||
// The previous cluster before the default one is the last one.
|
||||
@@ -349,9 +381,17 @@ public class FocusFinder {
|
||||
return clusters.get(position - 1);
|
||||
}
|
||||
|
||||
// The current cluster is the first one. The previous one is the default one, i.e. the
|
||||
// root.
|
||||
return root;
|
||||
switch (focusGroupType) {
|
||||
case FOCUS_GROUP_CLUSTER:
|
||||
// The current cluster is the first one. The previous one is the default one, i.e.
|
||||
// the root.
|
||||
return root;
|
||||
case FOCUS_GROUP_SECTION:
|
||||
// There is no "default section", hence returning the last one.
|
||||
return clusters.get(count - 1);
|
||||
default:
|
||||
throw new IllegalArgumentException("Unknown focus group type: " + focusGroupType);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1248,6 +1248,14 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
public @interface FocusRealDirection {} // Like @FocusDirection, but without forward/backward
|
||||
|
||||
/** @hide */
|
||||
@IntDef({
|
||||
FOCUS_GROUP_CLUSTER,
|
||||
FOCUS_GROUP_SECTION
|
||||
})
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
public @interface FocusGroupType {}
|
||||
|
||||
/**
|
||||
* Use with {@link #focusSearch(int)}. Move focus to the previous selectable
|
||||
* item.
|
||||
@@ -1280,6 +1288,18 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
|
||||
*/
|
||||
public static final int FOCUS_DOWN = 0x00000082;
|
||||
|
||||
/**
|
||||
* Use with {@link #keyboardNavigationClusterSearch(int, View, int)}. Search for a keyboard
|
||||
* navigation cluster.
|
||||
*/
|
||||
public static final int FOCUS_GROUP_CLUSTER = 1;
|
||||
|
||||
/**
|
||||
* Use with {@link #keyboardNavigationClusterSearch(int, View, int)}. Search for a keyboard
|
||||
* navigation section.
|
||||
*/
|
||||
public static final int FOCUS_GROUP_SECTION = 2;
|
||||
|
||||
/**
|
||||
* Bits of {@link #getMeasuredWidthAndState()} and
|
||||
* {@link #getMeasuredWidthAndState()} that provide the actual measured size.
|
||||
@@ -9096,22 +9116,47 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
|
||||
}
|
||||
}
|
||||
|
||||
final boolean isFocusGroupOfType(@FocusGroupType int focusGroupType) {
|
||||
switch (focusGroupType) {
|
||||
case FOCUS_GROUP_CLUSTER:
|
||||
return isKeyboardNavigationCluster();
|
||||
case FOCUS_GROUP_SECTION:
|
||||
return isKeyboardNavigationSection();
|
||||
default:
|
||||
throw new IllegalArgumentException("Unknown focus group type: " + focusGroupType);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the nearest keyboard navigation cluster in the specified direction.
|
||||
* This does not actually give focus to that cluster.
|
||||
*
|
||||
* @param focusGroupType Type of the focus group
|
||||
* @param currentCluster The starting point of the search. Null means the current cluster is not
|
||||
* found yet
|
||||
* @param direction Direction to look
|
||||
*
|
||||
* @return The nearest keyboard navigation cluster in the specified direction, or null if none
|
||||
* can be found
|
||||
*/
|
||||
public View keyboardNavigationClusterSearch(int direction) {
|
||||
if (mParent != null) {
|
||||
final View currentCluster = isKeyboardNavigationCluster() ? this : null;
|
||||
return mParent.keyboardNavigationClusterSearch(currentCluster, direction);
|
||||
} else {
|
||||
return null;
|
||||
public View keyboardNavigationClusterSearch(
|
||||
@FocusGroupType int focusGroupType, View currentCluster, int direction) {
|
||||
if (isFocusGroupOfType(focusGroupType)) {
|
||||
currentCluster = this;
|
||||
}
|
||||
if (isRootNamespace()
|
||||
|| focusGroupType == FOCUS_GROUP_SECTION && isKeyboardNavigationCluster()) {
|
||||
// Root namespace means we should consider ourselves the top of the
|
||||
// tree for cluster searching; otherwise we could be focus searching
|
||||
// into other tabs. see LocalActivityManager and TabHost for more info.
|
||||
// In addition, a cluster node works as a root for section searches.
|
||||
return FocusFinder.getInstance().findNextKeyboardNavigationCluster(
|
||||
focusGroupType, this, currentCluster, direction);
|
||||
} else if (mParent != null) {
|
||||
return mParent.keyboardNavigationClusterSearch(
|
||||
focusGroupType, currentCluster, direction);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -9240,11 +9285,15 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
|
||||
* Adds any keyboard navigation cluster roots that are descendants of this view (possibly
|
||||
* including this view if it is a cluster root itself) to views.
|
||||
*
|
||||
* @param focusGroupType Type of the focus group
|
||||
* @param views Cluster roots found so far
|
||||
* @param direction Direction to look
|
||||
*/
|
||||
public void addKeyboardNavigationClusters(@NonNull Collection<View> views, int direction) {
|
||||
if (!isKeyboardNavigationCluster()) {
|
||||
public void addKeyboardNavigationClusters(
|
||||
@FocusGroupType int focusGroupType,
|
||||
@NonNull Collection<View> views,
|
||||
int direction) {
|
||||
if (!(isFocusGroupOfType(focusGroupType))) {
|
||||
return;
|
||||
}
|
||||
views.add(this);
|
||||
|
||||
@@ -901,23 +901,6 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public View keyboardNavigationClusterSearch(View currentCluster, int direction) {
|
||||
if (isKeyboardNavigationCluster()) {
|
||||
currentCluster = this;
|
||||
}
|
||||
if (isRootNamespace()) {
|
||||
// root namespace means we should consider ourselves the top of the
|
||||
// tree for cluster searching; otherwise we could be focus searching
|
||||
// into other tabs. see LocalActivityManager and TabHost for more info
|
||||
return FocusFinder.getInstance().findNextKeyboardNavigationCluster(
|
||||
this, currentCluster, direction);
|
||||
} else if (mParent != null) {
|
||||
return mParent.keyboardNavigationClusterSearch(currentCluster, direction);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean requestChildRectangleOnScreen(View child, Rect rectangle, boolean immediate) {
|
||||
return false;
|
||||
@@ -1164,10 +1147,11 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addKeyboardNavigationClusters(Collection<View> views, int direction) {
|
||||
public void addKeyboardNavigationClusters(
|
||||
@FocusGroupType int focusGroupType, Collection<View> views, int direction) {
|
||||
final int focusableCount = views.size();
|
||||
|
||||
super.addKeyboardNavigationClusters(views, direction);
|
||||
super.addKeyboardNavigationClusters(focusGroupType, views, direction);
|
||||
|
||||
if (focusableCount != views.size()) {
|
||||
// No need to look for clusters inside a cluster.
|
||||
@@ -1183,8 +1167,13 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
|
||||
|
||||
for (int i = 0; i < count; i++) {
|
||||
final View child = children[i];
|
||||
if (focusGroupType == FOCUS_GROUP_SECTION && child.isKeyboardNavigationCluster()) {
|
||||
// When the current cluster is the default cluster, and we are searching for
|
||||
// sections, ignore sections inside non-default clusters.
|
||||
continue;
|
||||
}
|
||||
if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE) {
|
||||
child.addKeyboardNavigationClusters(views, direction);
|
||||
child.addKeyboardNavigationClusters(focusGroupType, views, direction);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ package android.view;
|
||||
|
||||
import android.graphics.Rect;
|
||||
import android.os.Bundle;
|
||||
import android.view.View.FocusGroupType;
|
||||
import android.view.accessibility.AccessibilityEvent;
|
||||
|
||||
/**
|
||||
@@ -150,6 +151,7 @@ public interface ViewParent {
|
||||
* Find the nearest keyboard navigation cluster in the specified direction.
|
||||
* This does not actually give focus to that cluster.
|
||||
*
|
||||
* @param focusGroupType Type of the focus group
|
||||
* @param currentCluster The starting point of the search. Null means the current cluster is not
|
||||
* found yet
|
||||
* @param direction Direction to look
|
||||
@@ -157,7 +159,8 @@ public interface ViewParent {
|
||||
* @return The nearest keyboard navigation cluster in the specified direction, or null if none
|
||||
* can be found
|
||||
*/
|
||||
View keyboardNavigationClusterSearch(View currentCluster, int direction);
|
||||
View keyboardNavigationClusterSearch(
|
||||
@FocusGroupType int focusGroupType, View currentCluster, int direction);
|
||||
|
||||
/**
|
||||
* Change the z order of the child so it's on top of all other children.
|
||||
|
||||
@@ -16,6 +16,8 @@
|
||||
|
||||
package android.view;
|
||||
|
||||
import static android.view.View.FOCUS_GROUP_CLUSTER;
|
||||
import static android.view.View.FOCUS_GROUP_SECTION;
|
||||
import static android.view.WindowCallbacks.RESIZE_MODE_DOCKED_DIVIDER;
|
||||
import static android.view.WindowCallbacks.RESIZE_MODE_FREEFORM;
|
||||
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_DECOR_VIEW_VISIBILITY;
|
||||
@@ -71,6 +73,7 @@ import android.util.TimeUtils;
|
||||
import android.util.TypedValue;
|
||||
import android.view.Surface.OutOfResourcesException;
|
||||
import android.view.View.AttachInfo;
|
||||
import android.view.View.FocusGroupType;
|
||||
import android.view.View.MeasureSpec;
|
||||
import android.view.accessibility.AccessibilityEvent;
|
||||
import android.view.accessibility.AccessibilityManager;
|
||||
@@ -4392,11 +4395,12 @@ public final class ViewRootImpl implements ViewParent,
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean performClusterNavigation(int direction) {
|
||||
private boolean performClusterNavigation(
|
||||
@FocusGroupType int focusGroupType, int direction) {
|
||||
final View focused = mView.findFocus();
|
||||
final View cluster = focused != null
|
||||
? focused.keyboardNavigationClusterSearch(direction)
|
||||
: keyboardNavigationClusterSearch(null, direction);
|
||||
? focused.keyboardNavigationClusterSearch(focusGroupType, null, direction)
|
||||
: keyboardNavigationClusterSearch(focusGroupType, null, direction);
|
||||
|
||||
if (cluster != null && cluster.restoreLastFocus()) {
|
||||
return true;
|
||||
@@ -4418,15 +4422,32 @@ public final class ViewRootImpl implements ViewParent,
|
||||
}
|
||||
|
||||
int clusterNavigationDirection = 0;
|
||||
@FocusGroupType int focusGroupType = 0;
|
||||
|
||||
if (event.getAction() == KeyEvent.ACTION_DOWN && event.isCtrlPressed()) {
|
||||
final int character =
|
||||
event.getUnicodeChar(event.getMetaState() & ~KeyEvent.META_CTRL_MASK);
|
||||
if (character == '+') {
|
||||
focusGroupType = FOCUS_GROUP_CLUSTER;
|
||||
clusterNavigationDirection = View.FOCUS_FORWARD;
|
||||
}
|
||||
|
||||
if (character == '_') {
|
||||
focusGroupType = FOCUS_GROUP_CLUSTER;
|
||||
clusterNavigationDirection = View.FOCUS_BACKWARD;
|
||||
}
|
||||
}
|
||||
|
||||
if (event.getAction() == KeyEvent.ACTION_DOWN && event.isAltPressed()) {
|
||||
final int character =
|
||||
event.getUnicodeChar(event.getMetaState() & ~KeyEvent.META_ALT_MASK);
|
||||
if (character == '+') {
|
||||
focusGroupType = FOCUS_GROUP_SECTION;
|
||||
clusterNavigationDirection = View.FOCUS_FORWARD;
|
||||
}
|
||||
|
||||
if (character == '_') {
|
||||
focusGroupType = FOCUS_GROUP_SECTION;
|
||||
clusterNavigationDirection = View.FOCUS_BACKWARD;
|
||||
}
|
||||
}
|
||||
@@ -4456,7 +4477,7 @@ public final class ViewRootImpl implements ViewParent,
|
||||
// Handle automatic focus changes.
|
||||
if (event.getAction() == KeyEvent.ACTION_DOWN) {
|
||||
if (clusterNavigationDirection != 0) {
|
||||
if (performClusterNavigation(clusterNavigationDirection)) {
|
||||
if (performClusterNavigation(focusGroupType, clusterNavigationDirection)) {
|
||||
return FINISH_HANDLED;
|
||||
}
|
||||
} else {
|
||||
@@ -5887,13 +5908,11 @@ public final class ViewRootImpl implements ViewParent,
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public View keyboardNavigationClusterSearch(View currentCluster, int direction) {
|
||||
public View keyboardNavigationClusterSearch(
|
||||
@FocusGroupType int focusGroupType, View currentCluster, int direction) {
|
||||
checkThread();
|
||||
if (!(mView instanceof ViewGroup)) {
|
||||
return null;
|
||||
}
|
||||
return FocusFinder.getInstance().findNextKeyboardNavigationCluster(
|
||||
(ViewGroup) mView, currentCluster, direction);
|
||||
return FocusFinder.getInstance().findNextKeyboardNavigationCluster(focusGroupType,
|
||||
mView, currentCluster, direction);
|
||||
}
|
||||
|
||||
public void debug() {
|
||||
|
||||
Reference in New Issue
Block a user