summaryrefslogtreecommitdiffstats
path: root/src/com/android/launcher3/FocusHelper.java
diff options
context:
space:
mode:
authorHyunyoung Song <hyunyoungs@google.com>2015-02-20 14:25:27 -0800
committerHyunyoung Song <hyunyoungs@google.com>2015-02-20 14:25:27 -0800
commitee3e6a7b777e58552a26ab8a10641886588e9196 (patch)
tree0d77b5694a003f0d7b97f2e0d4025df880927763 /src/com/android/launcher3/FocusHelper.java
parentd3f035797e85cae0d64ddc1be58dcc88ac246109 (diff)
downloadandroid_packages_apps_Trebuchet-ee3e6a7b777e58552a26ab8a10641886588e9196.tar.gz
android_packages_apps_Trebuchet-ee3e6a7b777e58552a26ab8a10641886588e9196.tar.bz2
android_packages_apps_Trebuchet-ee3e6a7b777e58552a26ab8a10641886588e9196.zip
[key event focus handling] Cleanup/Refactor/Feature
1) Focus navigation handling is refactored to Focus utility class. New 2 step dpad navigation algorithm is inside Focus class 2) Introduced a map (or matrix) that indicates where sparse icons are located inside a grid. This enables getting rid of the icon sorting logic which was costly. 3) Unified all the dpad handling logic inside the handleXXKeyEvent methods 4) DOWN/UP key will allow navigation between workspace icons and the hotseat 5) Folder icons allow DOWN/UP to navigate to the title b/19381790 Change-Id: Id45b3f215ef7c1ca5f99b08e3d721e219298627a
Diffstat (limited to 'src/com/android/launcher3/FocusHelper.java')
-rw-r--r--src/com/android/launcher3/FocusHelper.java826
1 files changed, 296 insertions, 530 deletions
diff --git a/src/com/android/launcher3/FocusHelper.java b/src/com/android/launcher3/FocusHelper.java
index e60704718..c02d73cb6 100644
--- a/src/com/android/launcher3/FocusHelper.java
+++ b/src/com/android/launcher3/FocusHelper.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2011 The Android Open Source Project
+ * Copyright (C) 2015 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.
@@ -17,20 +17,20 @@
package com.android.launcher3;
import android.content.res.Configuration;
+import android.util.Log;
import android.view.KeyEvent;
import android.view.SoundEffectConstants;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ScrollView;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Comparator;
+import com.android.launcher3.util.FocusLogic;
/**
* A keyboard listener we set on all the workspace icons.
*/
class IconKeyEventListener implements View.OnKeyListener {
+ @Override
public boolean onKey(View v, int keyCode, KeyEvent event) {
return FocusHelper.handleIconKeyEvent(v, keyCode, event);
}
@@ -40,6 +40,7 @@ class IconKeyEventListener implements View.OnKeyListener {
* A keyboard listener we set on all the workspace icons.
*/
class FolderKeyEventListener implements View.OnKeyListener {
+ @Override
public boolean onKey(View v, int keyCode, KeyEvent event) {
return FocusHelper.handleFolderKeyEvent(v, keyCode, event);
}
@@ -49,30 +50,35 @@ class FolderKeyEventListener implements View.OnKeyListener {
* A keyboard listener we set on all the hotseat buttons.
*/
class HotseatIconKeyEventListener implements View.OnKeyListener {
+ @Override
public boolean onKey(View v, int keyCode, KeyEvent event) {
- final Configuration configuration = v.getResources().getConfiguration();
- return FocusHelper.handleHotseatButtonKeyEvent(v, keyCode, event, configuration.orientation);
+ return FocusHelper.handleHotseatButtonKeyEvent(v, keyCode, event);
}
}
public class FocusHelper {
- /**
- * Returns the Viewgroup containing page contents for the page at the index specified.
- */
- private static ViewGroup getAppsCustomizePage(ViewGroup container, int index) {
- ViewGroup page = (ViewGroup) ((PagedView) container).getPageAt(index);
- if (page instanceof CellLayout) {
- // There are two layers, a PagedViewCellLayout and PagedViewCellLayoutChildren
- page = ((CellLayout) page).getShortcutsAndWidgets();
- }
- return page;
- }
+ private static final String TAG = "FocusHelper";
+ private static final boolean DEBUG = false;
+
+ //
+ // Key code handling methods.
+ //
/**
- * Handles key events in a PageViewCellLayout containing PagedViewIcons.
+ * Handles key events in the all apps screen.
*/
static boolean handleAppsCustomizeKeyEvent(View v, int keyCode, KeyEvent e) {
+ boolean consume = FocusLogic.shouldConsume(keyCode);
+ if (e.getAction() == KeyEvent.ACTION_UP) {
+ return consume;
+ }
+ if (DEBUG) {
+ Log.v(TAG, String.format("Handle ALL APPS keyevent=[%s].",
+ KeyEvent.keyCodeToString(keyCode)));
+ }
+
+ // Initialize variables.
ViewGroup parentLayout;
ViewGroup itemContainer;
int countX;
@@ -82,341 +88,159 @@ public class FocusHelper {
parentLayout = (ViewGroup) itemContainer.getParent();
countX = ((CellLayout) parentLayout).getCountX();
countY = ((CellLayout) parentLayout).getCountY();
- } else {
+ } else if (v.getParent() instanceof ViewGroup) {
itemContainer = parentLayout = (ViewGroup) v.getParent();
countX = ((PagedViewGridLayout) parentLayout).getCellCountX();
countY = ((PagedViewGridLayout) parentLayout).getCellCountY();
+ } else {
+ throw new IllegalStateException(
+ "Parent of the focused item inside all apps screen is not a supported type.");
}
-
- // Note we have an extra parent because of the
- // PagedViewCellLayout/PagedViewCellLayoutChildren relationship
- final PagedView container = (PagedView) parentLayout.getParent();
final int iconIndex = itemContainer.indexOfChild(v);
- final int itemCount = itemContainer.getChildCount();
- final int pageIndex = ((PagedView) container).indexToPage(container.indexOfChild(parentLayout));
+ final PagedView container = (PagedView) parentLayout.getParent();
+ final int pageIndex = container.indexToPage(container.indexOfChild(parentLayout));
final int pageCount = container.getChildCount();
-
- final int x = iconIndex % countX;
- final int y = iconIndex / countX;
-
- final int action = e.getAction();
- final boolean handleKeyEvent = (action != KeyEvent.ACTION_UP);
ViewGroup newParent = null;
- // Side pages do not always load synchronously, so check before focusing child siblings
- // willy-nilly
View child = null;
- boolean wasHandled = false;
- switch (keyCode) {
- case KeyEvent.KEYCODE_DPAD_LEFT:
- if (handleKeyEvent) {
- // Select the previous icon or the last icon on the previous page
- if (iconIndex > 0) {
- itemContainer.getChildAt(iconIndex - 1).requestFocus();
- v.playSoundEffect(SoundEffectConstants.NAVIGATION_LEFT);
- } else {
- if (pageIndex > 0) {
- newParent = getAppsCustomizePage(container, pageIndex - 1);
- if (newParent != null) {
- container.snapToPage(pageIndex - 1);
- child = newParent.getChildAt(newParent.getChildCount() - 1);
- if (child != null) {
- child.requestFocus();
- v.playSoundEffect(SoundEffectConstants.NAVIGATION_LEFT);
- }
- }
- }
- }
+ int[][] matrix = FocusLogic.createFullMatrix(countX, countY, true);
+
+ // Process focus.
+ int newIconIndex = FocusLogic.handleKeyEvent(keyCode, countX, countY, matrix,
+ iconIndex, pageIndex, pageCount);
+ if (newIconIndex == FocusLogic.NOOP) {
+ return consume;
+ }
+ switch (newIconIndex) {
+ case FocusLogic.PREVIOUS_PAGE_FIRST_ITEM:
+ newParent = getAppsCustomizePage(container, pageIndex - 1);
+ if (newParent != null) {
+ container.snapToPage(pageIndex - 1);
+ child = newParent.getChildAt(0);
}
- wasHandled = true;
- break;
- case KeyEvent.KEYCODE_DPAD_RIGHT:
- if (handleKeyEvent) {
- // Select the next icon or the first icon on the next page
- if (iconIndex < (itemCount - 1)) {
- itemContainer.getChildAt(iconIndex + 1).requestFocus();
- v.playSoundEffect(SoundEffectConstants.NAVIGATION_RIGHT);
- } else {
- if (pageIndex < (pageCount - 1)) {
- newParent = getAppsCustomizePage(container, pageIndex + 1);
- if (newParent != null) {
- container.snapToPage(pageIndex + 1);
- child = newParent.getChildAt(0);
- if (child != null) {
- child.requestFocus();
- v.playSoundEffect(SoundEffectConstants.NAVIGATION_RIGHT);
- }
- }
- }
- }
+ case FocusLogic.PREVIOUS_PAGE_LAST_ITEM:
+ newParent = getAppsCustomizePage(container, pageIndex - 1);
+ if (newParent != null) {
+ container.snapToPage(pageIndex - 1);
+ child = newParent.getChildAt(newParent.getChildCount() - 1);
}
- wasHandled = true;
break;
- case KeyEvent.KEYCODE_DPAD_UP:
- if (handleKeyEvent) {
- // Select the closest icon in the previous row, otherwise select the tab bar
- if (y > 0) {
- int newiconIndex = ((y - 1) * countX) + x;
- itemContainer.getChildAt(newiconIndex).requestFocus();
- v.playSoundEffect(SoundEffectConstants.NAVIGATION_UP);
- }
+ case FocusLogic.NEXT_PAGE_FIRST_ITEM:
+ newParent = getAppsCustomizePage(container, pageIndex + 1);
+ if (newParent != null) {
+ container.snapToPage(pageIndex + 1);
+ child = newParent.getChildAt(0);
}
- wasHandled = true;
break;
- case KeyEvent.KEYCODE_DPAD_DOWN:
- if (handleKeyEvent) {
- // Select the closest icon in the next row, otherwise do nothing
- if (y < (countY - 1)) {
- int newiconIndex = Math.min(itemCount - 1, ((y + 1) * countX) + x);
- int newIconY = newiconIndex / countX;
- if (newIconY != y) {
- itemContainer.getChildAt(newiconIndex).requestFocus();
- v.playSoundEffect(SoundEffectConstants.NAVIGATION_DOWN);
- }
- }
- }
- wasHandled = true;
+ case FocusLogic.CURRENT_PAGE_FIRST_ITEM:
+ child = container.getChildAt(0);
break;
- case KeyEvent.KEYCODE_PAGE_UP:
- if (handleKeyEvent) {
- // Select the first icon on the previous page, or the first icon on this page
- // if there is no previous page
- if (pageIndex > 0) {
- newParent = getAppsCustomizePage(container, pageIndex - 1);
- if (newParent != null) {
- container.snapToPage(pageIndex - 1);
- child = newParent.getChildAt(0);
- if (child != null) {
- child.requestFocus();
- v.playSoundEffect(SoundEffectConstants.NAVIGATION_UP);
- }
- }
- } else {
- itemContainer.getChildAt(0).requestFocus();
- v.playSoundEffect(SoundEffectConstants.NAVIGATION_UP);
- }
- }
- wasHandled = true;
- break;
- case KeyEvent.KEYCODE_PAGE_DOWN:
- if (handleKeyEvent) {
- // Select the first icon on the next page, or the last icon on this page
- // if there is no next page
- if (pageIndex < (pageCount - 1)) {
- newParent = getAppsCustomizePage(container, pageIndex + 1);
- if (newParent != null) {
- container.snapToPage(pageIndex + 1);
- child = newParent.getChildAt(0);
- if (child != null) {
- child.requestFocus();
- v.playSoundEffect(SoundEffectConstants.NAVIGATION_DOWN);
- }
- }
- } else {
- itemContainer.getChildAt(itemCount - 1).requestFocus();
- v.playSoundEffect(SoundEffectConstants.NAVIGATION_DOWN);
- }
- }
- wasHandled = true;
- break;
- case KeyEvent.KEYCODE_MOVE_HOME:
- if (handleKeyEvent) {
- // Select the first icon on this page
- itemContainer.getChildAt(0).requestFocus();
- v.playSoundEffect(SoundEffectConstants.NAVIGATION_UP);
- }
- wasHandled = true;
+ case FocusLogic.CURRENT_PAGE_LAST_ITEM:
+ child = itemContainer.getChildAt(itemContainer.getChildCount() - 1);
break;
- case KeyEvent.KEYCODE_MOVE_END:
- if (handleKeyEvent) {
- // Select the last icon on this page
- itemContainer.getChildAt(itemCount - 1).requestFocus();
- v.playSoundEffect(SoundEffectConstants.NAVIGATION_DOWN);
- }
- wasHandled = true;
+ default: // Go to some item on the current page.
+ child = itemContainer.getChildAt(newIconIndex);
break;
- default: break;
}
- return wasHandled;
+ if (child != null) {
+ child.requestFocus();
+ playSoundEffect(keyCode, v);
+ }
+ return consume;
}
/**
- * Handles key events in the workspace hotseat (bottom of the screen).
+ * Handles key events in the workspace hot seat (bottom of the screen).
+ * <p>Currently we don't special case for the phone UI in different orientations, even though
+ * the hotseat is on the side in landscape mode. This is to ensure that accessibility
+ * consistency is maintained across rotations.
*/
- static boolean handleHotseatButtonKeyEvent(View v, int keyCode, KeyEvent e, int orientation) {
- ShortcutAndWidgetContainer parent = (ShortcutAndWidgetContainer) v.getParent();
- final CellLayout layout = (CellLayout) parent.getParent();
+ static boolean handleHotseatButtonKeyEvent(View v, int keyCode, KeyEvent e) {
+ boolean consume = FocusLogic.shouldConsume(keyCode);
+ if (e.getAction() == KeyEvent.ACTION_UP || !consume) {
+ return consume;
+ }
+ int orientation = v.getResources().getConfiguration().orientation;
- // NOTE: currently we don't special case for the phone UI in different
- // orientations, even though the hotseat is on the side in landscape mode. This
- // is to ensure that accessibility consistency is maintained across rotations.
- final int action = e.getAction();
- final boolean handleKeyEvent = (action != KeyEvent.ACTION_UP);
- boolean wasHandled = false;
- switch (keyCode) {
- case KeyEvent.KEYCODE_DPAD_LEFT:
- if (handleKeyEvent) {
- ArrayList<View> views = getCellLayoutChildrenSortedSpatially(layout, parent);
- int myIndex = views.indexOf(v);
- // Select the previous button, otherwise do nothing
- if (myIndex > 0) {
- views.get(myIndex - 1).requestFocus();
- v.playSoundEffect(SoundEffectConstants.NAVIGATION_LEFT);
- }
- }
- wasHandled = true;
- break;
- case KeyEvent.KEYCODE_DPAD_RIGHT:
- if (handleKeyEvent) {
- ArrayList<View> views = getCellLayoutChildrenSortedSpatially(layout, parent);
- int myIndex = views.indexOf(v);
- // Select the next button, otherwise do nothing
- if (myIndex < views.size() - 1) {
- views.get(myIndex + 1).requestFocus();
- v.playSoundEffect(SoundEffectConstants.NAVIGATION_RIGHT);
- }
- }
- wasHandled = true;
- break;
- case KeyEvent.KEYCODE_DPAD_UP:
- if (handleKeyEvent) {
- final Workspace workspace = (Workspace)
- v.getRootView().findViewById(R.id.workspace);
- if (workspace != null) {
- int pageIndex = workspace.getCurrentPage();
- CellLayout topLayout = (CellLayout) workspace.getChildAt(pageIndex);
- ShortcutAndWidgetContainer children = topLayout.getShortcutsAndWidgets();
- final View newIcon = getIconInDirection(layout, children, -1, 1);
- // Select the first bubble text view in the current page of the workspace
- if (newIcon != null) {
- newIcon.requestFocus();
- v.playSoundEffect(SoundEffectConstants.NAVIGATION_UP);
- } else {
- workspace.requestFocus();
- }
- }
- }
- wasHandled = true;
- break;
- case KeyEvent.KEYCODE_DPAD_DOWN:
- // Do nothing
- wasHandled = true;
- break;
- default: break;
+ if (DEBUG) {
+ Log.v(TAG, String.format(
+ "Handle HOTSEAT BUTTONS keyevent=[%s] on hotseat buttons, orientation=%d",
+ KeyEvent.keyCodeToString(keyCode), orientation));
}
- return wasHandled;
- }
- /**
- * Private helper method to get the CellLayoutChildren given a CellLayout index.
- */
- private static ShortcutAndWidgetContainer getCellLayoutChildrenForIndex(
- ViewGroup container, int i) {
- CellLayout parent = (CellLayout) container.getChildAt(i);
- return parent.getShortcutsAndWidgets();
- }
+ // Initialize the variables.
+ final ShortcutAndWidgetContainer hotseatParent = (ShortcutAndWidgetContainer) v.getParent();
+ final CellLayout hotseatLayout = (CellLayout) hotseatParent.getParent();
+ Workspace workspace = (Workspace) v.getRootView().findViewById(R.id.workspace);
+ int pageIndex = workspace.getCurrentPage();
+ int pageCount = workspace.getChildCount();
+ int countX, countY;
+ int iconIndex = findIndexOfView(hotseatParent, v);
+
+ final CellLayout iconLayout = (CellLayout) workspace.getChildAt(pageIndex);
+ final ViewGroup iconParent = iconLayout.getShortcutsAndWidgets();
+
+ ViewGroup parent = null;
+ int[][] matrix;
+
+ if (keyCode == KeyEvent.KEYCODE_DPAD_UP &&
+ orientation == Configuration.ORIENTATION_PORTRAIT) {
+ matrix = FocusLogic.createSparseMatrix(iconLayout, hotseatLayout, orientation);
+ // TODO: hotseat indexing should be symmetric.
+ iconIndex += iconParent.getChildCount();
+ countX = iconLayout.getCountX();
+ countY = iconLayout.getCountY() + hotseatLayout.getCountY();
+ parent = iconParent;
+ } else if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT &&
+ orientation == Configuration.ORIENTATION_LANDSCAPE) {
+ matrix = FocusLogic.createSparseMatrix(iconLayout, hotseatLayout, orientation);
+ // TODO: hotseat indexing should be symmetric.
+ iconIndex += iconParent.getChildCount();
+ countX = iconLayout.getCountX() + hotseatLayout.getCountX();
+ countY = iconLayout.getCountY();
+ parent = iconParent;
+ } else {
+ // For other KEYCODE_DPAD_LEFT and KEYCODE_DPAD_RIGHT navigation, do not use the
+ // matrix extended with hotseat.
+ matrix = FocusLogic.createSparseMatrix(hotseatLayout);
+ parent = hotseatParent;
+ countX = hotseatLayout.getCountX();
+ countY = hotseatLayout.getCountY();
- /**
- * Private helper method to sort all the CellLayout children in order of their (x,y) spatially
- * from top left to bottom right.
- */
- private static ArrayList<View> getCellLayoutChildrenSortedSpatially(CellLayout layout,
- ViewGroup parent) {
- // First we order each the CellLayout children by their x,y coordinates
- final int cellCountX = layout.getCountX();
- final int count = parent.getChildCount();
- ArrayList<View> views = new ArrayList<View>();
- for (int j = 0; j < count; ++j) {
- views.add(parent.getChildAt(j));
}
- Collections.sort(views, new Comparator<View>() {
- @Override
- public int compare(View lhs, View rhs) {
- CellLayout.LayoutParams llp = (CellLayout.LayoutParams) lhs.getLayoutParams();
- CellLayout.LayoutParams rlp = (CellLayout.LayoutParams) rhs.getLayoutParams();
- int lvIndex = (llp.cellY * cellCountX) + llp.cellX;
- int rvIndex = (rlp.cellY * cellCountX) + rlp.cellX;
- return lvIndex - rvIndex;
- }
- });
- return views;
- }
- /**
- * Private helper method to find the index of the next BubbleTextView or FolderIcon in the
- * direction delta.
- *
- * @param delta either -1 or 1 depending on the direction we want to search
- */
- private static View findIndexOfIcon(ArrayList<View> views, int i, int delta) {
- // Then we find the next BubbleTextView offset by delta from i
- final int count = views.size();
- int newI = i + delta;
- while (0 <= newI && newI < count) {
- View newV = views.get(newI);
- if (newV instanceof BubbleTextView || newV instanceof FolderIcon) {
- return newV;
- }
- newI += delta;
+
+ // Process the focus.
+ int newIconIndex = FocusLogic.handleKeyEvent(keyCode, countX, countY, matrix,
+ iconIndex, pageIndex, pageCount);
+
+ if (iconParent.getChildCount() <= newIconIndex &&
+ newIconIndex < iconParent.getChildCount() + hotseatParent.getChildCount()) {
+ newIconIndex -= iconParent.getChildCount();
}
- return null;
- }
- private static View getIconInDirection(CellLayout layout, ViewGroup parent, int i,
- int delta) {
- final ArrayList<View> views = getCellLayoutChildrenSortedSpatially(layout, parent);
- return findIndexOfIcon(views, i, delta);
- }
- private static View getIconInDirection(CellLayout layout, ViewGroup parent, View v,
- int delta) {
- final ArrayList<View> views = getCellLayoutChildrenSortedSpatially(layout, parent);
- return findIndexOfIcon(views, views.indexOf(v), delta);
- }
- /**
- * Private helper method to find the next closest BubbleTextView or FolderIcon in the direction
- * delta on the next line.
- *
- * @param delta either -1 or 1 depending on the line and direction we want to search
- */
- private static View getClosestIconOnLine(CellLayout layout, ViewGroup parent, View v,
- int lineDelta) {
- final ArrayList<View> views = getCellLayoutChildrenSortedSpatially(layout, parent);
- final CellLayout.LayoutParams lp = (CellLayout.LayoutParams) v.getLayoutParams();
- final int cellCountY = layout.getCountY();
- final int row = lp.cellY;
- final int newRow = row + lineDelta;
- if (0 <= newRow && newRow < cellCountY) {
- float closestDistance = Float.MAX_VALUE;
- int closestIndex = -1;
- int index = views.indexOf(v);
- int endIndex = (lineDelta < 0) ? -1 : views.size();
- while (index != endIndex) {
- View newV = views.get(index);
- CellLayout.LayoutParams tmpLp = (CellLayout.LayoutParams) newV.getLayoutParams();
- boolean satisfiesRow = (lineDelta < 0) ? (tmpLp.cellY < row) : (tmpLp.cellY > row);
- if (satisfiesRow &&
- (newV instanceof BubbleTextView || newV instanceof FolderIcon)) {
- float tmpDistance = (float) Math.sqrt(Math.pow(tmpLp.cellX - lp.cellX, 2) +
- Math.pow(tmpLp.cellY - lp.cellY, 2));
- if (tmpDistance < closestDistance) {
- closestIndex = index;
- closestDistance = tmpDistance;
- }
- }
- if (index <= endIndex) {
- ++index;
- } else {
- --index;
- }
- }
- if (closestIndex > -1) {
- return views.get(closestIndex);
+ if (parent != null) {
+ View newIcon = parent.getChildAt(newIconIndex);
+ if (newIcon != null) {
+ newIcon.requestFocus();
+ playSoundEffect(keyCode, v);
}
}
- return null;
+ return consume;
}
/**
- * Handles key events in a Workspace containing.
+ * Handles key events in a workspace containing icons.
*/
static boolean handleIconKeyEvent(View v, int keyCode, KeyEvent e) {
+ boolean consume = FocusLogic.shouldConsume(keyCode);
+ if (e.getAction() == KeyEvent.ACTION_UP || !consume) {
+ return consume;
+ }
+ int orientation = v.getResources().getConfiguration().orientation;
+ if (DEBUG) {
+ Log.v(TAG, String.format("Handle WORKSPACE ICONS keyevent=[%s] orientation=%d",
+ KeyEvent.keyCodeToString(keyCode), orientation));
+ }
+
+ // Initialize the variables.
ShortcutAndWidgetContainer parent = (ShortcutAndWidgetContainer) v.getParent();
final CellLayout layout = (CellLayout) parent.getParent();
final Workspace workspace = (Workspace) layout.getParent();
@@ -425,249 +249,191 @@ public class FocusHelper {
final ViewGroup hotseat = (ViewGroup) launcher.findViewById(R.id.hotseat);
int pageIndex = workspace.indexOfChild(layout);
int pageCount = workspace.getChildCount();
+ final int countX = layout.getCountX();
+ int countY = layout.getCountY();
+ final int iconIndex = findIndexOfView(parent, v);
- final int action = e.getAction();
- final boolean handleKeyEvent = (action != KeyEvent.ACTION_UP);
- boolean wasHandled = false;
- switch (keyCode) {
- case KeyEvent.KEYCODE_DPAD_LEFT:
- if (handleKeyEvent) {
- // Select the previous icon or the last icon on the previous page if possible
- View newIcon = getIconInDirection(layout, parent, v, -1);
- if (newIcon != null) {
- newIcon.requestFocus();
- v.playSoundEffect(SoundEffectConstants.NAVIGATION_LEFT);
- } else {
- if (pageIndex > 0) {
- parent = getCellLayoutChildrenForIndex(workspace, pageIndex - 1);
- newIcon = getIconInDirection(layout, parent,
- parent.getChildCount(), -1);
- if (newIcon != null) {
- newIcon.requestFocus();
- } else {
- // Snap to the previous page
- workspace.snapToPage(pageIndex - 1);
- }
- v.playSoundEffect(SoundEffectConstants.NAVIGATION_LEFT);
- }
- }
- }
- wasHandled = true;
- break;
- case KeyEvent.KEYCODE_DPAD_RIGHT:
- if (handleKeyEvent) {
- // Select the next icon or the first icon on the next page if possible
- View newIcon = getIconInDirection(layout, parent, v, 1);
- if (newIcon != null) {
- newIcon.requestFocus();
- v.playSoundEffect(SoundEffectConstants.NAVIGATION_RIGHT);
- } else {
- if (pageIndex < (pageCount - 1)) {
- parent = getCellLayoutChildrenForIndex(workspace, pageIndex + 1);
- newIcon = getIconInDirection(layout, parent, -1, 1);
- if (newIcon != null) {
- newIcon.requestFocus();
- } else {
- // Snap to the next page
- workspace.snapToPage(pageIndex + 1);
- }
- v.playSoundEffect(SoundEffectConstants.NAVIGATION_RIGHT);
- }
- }
- }
- wasHandled = true;
- break;
- case KeyEvent.KEYCODE_DPAD_UP:
- if (handleKeyEvent) {
- // Select the closest icon in the previous line, otherwise select the tab bar
- View newIcon = getClosestIconOnLine(layout, parent, v, -1);
- if (newIcon != null) {
- newIcon.requestFocus();
- wasHandled = true;
- } else {
- tabs.requestFocus();
- }
- v.playSoundEffect(SoundEffectConstants.NAVIGATION_UP);
+ CellLayout hotseatLayout = (CellLayout) hotseat.getChildAt(0);
+ ShortcutAndWidgetContainer hotseatParent = hotseatLayout.getShortcutsAndWidgets();
+ int[][] matrix;
+
+ // KEYCODE_DPAD_DOWN in portrait (KEYCODE_DPAD_LEFT in landscape) is the only key allowed
+ // to take a user to the hotseat. For other dpad navigation, do not use the matrix extended
+ // with the hotseat.
+ if (keyCode == KeyEvent.KEYCODE_DPAD_DOWN &&
+ orientation == Configuration.ORIENTATION_PORTRAIT) {
+ matrix = FocusLogic.createSparseMatrix(layout, hotseatLayout, orientation);
+ countY = countY + 1;
+ } else if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT &&
+ orientation == Configuration.ORIENTATION_LANDSCAPE) {
+ matrix = FocusLogic.createSparseMatrix(layout, hotseatLayout, orientation);
+ countY = countY + 1;
+ } else {
+ matrix = FocusLogic.createSparseMatrix(layout);
+ }
+
+ // Process the focus.
+ int newIconIndex = FocusLogic.handleKeyEvent(keyCode, countX, countY, matrix,
+ iconIndex, pageIndex, pageCount);
+ View newIcon = null;
+ switch (newIconIndex) {
+ case FocusLogic.NOOP:
+ if (keyCode == KeyEvent.KEYCODE_DPAD_UP) {
+ newIcon = tabs;
}
break;
- case KeyEvent.KEYCODE_DPAD_DOWN:
- if (handleKeyEvent) {
- // Select the closest icon in the next line, otherwise select the button bar
- View newIcon = getClosestIconOnLine(layout, parent, v, 1);
- if (newIcon != null) {
- newIcon.requestFocus();
- v.playSoundEffect(SoundEffectConstants.NAVIGATION_DOWN);
- wasHandled = true;
- } else if (hotseat != null) {
- hotseat.requestFocus();
- v.playSoundEffect(SoundEffectConstants.NAVIGATION_DOWN);
- }
- }
+ case FocusLogic.PREVIOUS_PAGE_FIRST_ITEM:
+ parent = getCellLayoutChildrenForIndex(workspace, pageIndex - 1);
+ newIcon = parent.getChildAt(0);
+ workspace.snapToPage(pageIndex - 1);
+ case FocusLogic.PREVIOUS_PAGE_LAST_ITEM:
+ parent = getCellLayoutChildrenForIndex(workspace, pageIndex - 1);
+ newIcon = parent.getChildAt(parent.getChildCount() - 1);
+ workspace.snapToPage(pageIndex - 1);
break;
- case KeyEvent.KEYCODE_PAGE_UP:
- if (handleKeyEvent) {
- // Select the first icon on the previous page or the first icon on this page
- // if there is no previous page
- if (pageIndex > 0) {
- parent = getCellLayoutChildrenForIndex(workspace, pageIndex - 1);
- View newIcon = getIconInDirection(layout, parent, -1, 1);
- if (newIcon != null) {
- newIcon.requestFocus();
- } else {
- // Snap to the previous page
- workspace.snapToPage(pageIndex - 1);
- }
- v.playSoundEffect(SoundEffectConstants.NAVIGATION_UP);
- } else {
- View newIcon = getIconInDirection(layout, parent, -1, 1);
- if (newIcon != null) {
- newIcon.requestFocus();
- v.playSoundEffect(SoundEffectConstants.NAVIGATION_UP);
- }
- }
- }
- wasHandled = true;
+ case FocusLogic.NEXT_PAGE_FIRST_ITEM:
+ parent = getCellLayoutChildrenForIndex(workspace, pageIndex + 1);
+ newIcon = parent.getChildAt(0);
+ workspace.snapToPage(pageIndex - 1);
break;
- case KeyEvent.KEYCODE_PAGE_DOWN:
- if (handleKeyEvent) {
- // Select the first icon on the next page or the last icon on this page
- // if there is no previous page
- if (pageIndex < (pageCount - 1)) {
- parent = getCellLayoutChildrenForIndex(workspace, pageIndex + 1);
- View newIcon = getIconInDirection(layout, parent, -1, 1);
- if (newIcon != null) {
- newIcon.requestFocus();
- } else {
- // Snap to the next page
- workspace.snapToPage(pageIndex + 1);
- }
- v.playSoundEffect(SoundEffectConstants.NAVIGATION_DOWN);
- } else {
- View newIcon = getIconInDirection(layout, parent,
- parent.getChildCount(), -1);
- if (newIcon != null) {
- newIcon.requestFocus();
- v.playSoundEffect(SoundEffectConstants.NAVIGATION_DOWN);
- }
- }
- }
- wasHandled = true;
+ case FocusLogic.CURRENT_PAGE_FIRST_ITEM:
+ newIcon = parent.getChildAt(0);
break;
- case KeyEvent.KEYCODE_MOVE_HOME:
- if (handleKeyEvent) {
- // Select the first icon on this page
- View newIcon = getIconInDirection(layout, parent, -1, 1);
- if (newIcon != null) {
- newIcon.requestFocus();
- v.playSoundEffect(SoundEffectConstants.NAVIGATION_UP);
- }
- }
- wasHandled = true;
+ case FocusLogic.CURRENT_PAGE_LAST_ITEM:
+ newIcon = parent.getChildAt(parent.getChildCount() - 1);
break;
- case KeyEvent.KEYCODE_MOVE_END:
- if (handleKeyEvent) {
- // Select the last icon on this page
- View newIcon = getIconInDirection(layout, parent,
- parent.getChildCount(), -1);
- if (newIcon != null) {
- newIcon.requestFocus();
- v.playSoundEffect(SoundEffectConstants.NAVIGATION_DOWN);
- }
+ default:
+ // current page, some item.
+ if (0 <= newIconIndex && newIconIndex < parent.getChildCount()) {
+ newIcon = parent.getChildAt(newIconIndex);
+ } else if (parent.getChildCount() <= newIconIndex &&
+ newIconIndex < parent.getChildCount() + hotseatParent.getChildCount()) {
+ newIcon = hotseatParent.getChildAt(newIconIndex - parent.getChildCount());
}
- wasHandled = true;
break;
- default: break;
}
- return wasHandled;
+ if (newIcon != null) {
+ newIcon.requestFocus();
+ playSoundEffect(keyCode, v);
+ }
+ return consume;
}
/**
* Handles key events for items in a Folder.
*/
static boolean handleFolderKeyEvent(View v, int keyCode, KeyEvent e) {
+ boolean consume = FocusLogic.shouldConsume(keyCode);
+ if (e.getAction() == KeyEvent.ACTION_UP || !consume) {
+ return consume;
+ }
+ if (DEBUG) {
+ Log.v(TAG, String.format("Handle FOLDER keyevent=[%s].",
+ KeyEvent.keyCodeToString(keyCode)));
+ }
+
+ // Initialize the variables.
ShortcutAndWidgetContainer parent = (ShortcutAndWidgetContainer) v.getParent();
final CellLayout layout = (CellLayout) parent.getParent();
final ScrollView scrollView = (ScrollView) layout.getParent();
final Folder folder = (Folder) scrollView.getParent();
View title = folder.mFolderName;
+ Workspace workspace = (Workspace) v.getRootView().findViewById(R.id.workspace);
+ final int countX = layout.getCountX();
+ final int countY = layout.getCountY();
+ final int iconIndex = findIndexOfView(parent, v);
+ int pageIndex = workspace.indexOfChild(layout);
+ int pageCount = workspace.getChildCount();
+ int[][] map = FocusLogic.createFullMatrix(countX, countY, true /* incremental order index */
+ );
- final int action = e.getAction();
- final boolean handleKeyEvent = (action != KeyEvent.ACTION_UP);
- boolean wasHandled = false;
- switch (keyCode) {
- case KeyEvent.KEYCODE_DPAD_LEFT:
- if (handleKeyEvent) {
- // Select the previous icon
- View newIcon = getIconInDirection(layout, parent, v, -1);
- if (newIcon != null) {
- newIcon.requestFocus();
- v.playSoundEffect(SoundEffectConstants.NAVIGATION_LEFT);
- }
+ // Process the focus.
+ int newIconIndex = FocusLogic.handleKeyEvent(keyCode, countX, countY, map, iconIndex,
+ pageIndex, pageCount);
+ View newIcon = null;
+ switch (newIconIndex) {
+ case FocusLogic.NOOP:
+ if (keyCode == KeyEvent.KEYCODE_DPAD_DOWN) {
+ newIcon = title;
}
- wasHandled = true;
break;
- case KeyEvent.KEYCODE_DPAD_RIGHT:
- if (handleKeyEvent) {
- // Select the next icon
- View newIcon = getIconInDirection(layout, parent, v, 1);
- if (newIcon != null) {
- newIcon.requestFocus();
- } else {
- title.requestFocus();
- }
- v.playSoundEffect(SoundEffectConstants.NAVIGATION_RIGHT);
+ case FocusLogic.PREVIOUS_PAGE_FIRST_ITEM:
+ case FocusLogic.PREVIOUS_PAGE_LAST_ITEM:
+ case FocusLogic.NEXT_PAGE_FIRST_ITEM:
+ case FocusLogic.CURRENT_PAGE_FIRST_ITEM:
+ case FocusLogic.CURRENT_PAGE_LAST_ITEM:
+ if (DEBUG) {
+ Log.v(TAG, "Page advance handling not supported on folder icons.");
}
- wasHandled = true;
break;
- case KeyEvent.KEYCODE_DPAD_UP:
- if (handleKeyEvent) {
- // Select the closest icon in the previous line
- View newIcon = getClosestIconOnLine(layout, parent, v, -1);
- if (newIcon != null) {
- newIcon.requestFocus();
- v.playSoundEffect(SoundEffectConstants.NAVIGATION_UP);
- }
- }
- wasHandled = true;
+ default: // current page some item.
+ newIcon = parent.getChildAt(newIconIndex);
+ break;
+ }
+ if (newIcon != null) {
+ newIcon.requestFocus();
+ playSoundEffect(keyCode, v);
+ }
+ return consume;
+ }
+
+ //
+ // Helper methods.
+ //
+
+ /**
+ * Returns the Viewgroup containing page contents for the page at the index specified.
+ */
+ private static ViewGroup getAppsCustomizePage(ViewGroup container, int index) {
+ ViewGroup page = (ViewGroup) ((PagedView) container).getPageAt(index);
+ if (page instanceof CellLayout) {
+ // There are two layers, a PagedViewCellLayout and PagedViewCellLayoutChildren
+ page = ((CellLayout) page).getShortcutsAndWidgets();
+ }
+ return page;
+ }
+
+ /**
+ * Private helper method to get the CellLayoutChildren given a CellLayout index.
+ */
+ private static ShortcutAndWidgetContainer getCellLayoutChildrenForIndex(
+ ViewGroup container, int i) {
+ CellLayout parent = (CellLayout) container.getChildAt(i);
+ return parent.getShortcutsAndWidgets();
+ }
+
+ private static int findIndexOfView(ViewGroup parent, View v) {
+ for (int i = 0; i < parent.getChildCount(); i++) {
+ if (v != null && v.equals(parent.getChildAt(i))) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ /**
+ * Helper method to be used for playing sound effects.
+ */
+ private static void playSoundEffect(int keyCode, View v) {
+ switch (keyCode) {
+ case KeyEvent.KEYCODE_DPAD_LEFT:
+ v.playSoundEffect(SoundEffectConstants.NAVIGATION_LEFT);
+ break;
+ case KeyEvent.KEYCODE_DPAD_RIGHT:
+ v.playSoundEffect(SoundEffectConstants.NAVIGATION_RIGHT);
break;
case KeyEvent.KEYCODE_DPAD_DOWN:
- if (handleKeyEvent) {
- // Select the closest icon in the next line
- View newIcon = getClosestIconOnLine(layout, parent, v, 1);
- if (newIcon != null) {
- newIcon.requestFocus();
- } else {
- title.requestFocus();
- }
- v.playSoundEffect(SoundEffectConstants.NAVIGATION_DOWN);
- }
- wasHandled = true;
+ case KeyEvent.KEYCODE_PAGE_DOWN:
+ case KeyEvent.KEYCODE_MOVE_END:
+ v.playSoundEffect(SoundEffectConstants.NAVIGATION_DOWN);
break;
+ case KeyEvent.KEYCODE_DPAD_UP:
+ case KeyEvent.KEYCODE_PAGE_UP:
case KeyEvent.KEYCODE_MOVE_HOME:
- if (handleKeyEvent) {
- // Select the first icon on this page
- View newIcon = getIconInDirection(layout, parent, -1, 1);
- if (newIcon != null) {
- newIcon.requestFocus();
- v.playSoundEffect(SoundEffectConstants.NAVIGATION_UP);
- }
- }
- wasHandled = true;
+ v.playSoundEffect(SoundEffectConstants.NAVIGATION_UP);
break;
- case KeyEvent.KEYCODE_MOVE_END:
- if (handleKeyEvent) {
- // Select the last icon on this page
- View newIcon = getIconInDirection(layout, parent,
- parent.getChildCount(), -1);
- if (newIcon != null) {
- newIcon.requestFocus();
- v.playSoundEffect(SoundEffectConstants.NAVIGATION_DOWN);
- }
- }
- wasHandled = true;
+ default:
break;
- default: break;
}
- return wasHandled;
}
}