summaryrefslogtreecommitdiffstats
path: root/src/com/android/launcher3/FocusHelper.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/com/android/launcher3/FocusHelper.java')
-rw-r--r--src/com/android/launcher3/FocusHelper.java905
1 files changed, 380 insertions, 525 deletions
diff --git a/src/com/android/launcher3/FocusHelper.java b/src/com/android/launcher3/FocusHelper.java
index e60704718..b090a7c3f 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.FocusHelper.PagedViewKeyListener;
+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,58 @@ 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 {
+ private static final String TAG = "FocusHelper";
+ private static final boolean DEBUG = false;
+
+ //
+ // Key code handling methods.
+ //
+
/**
- * Returns the Viewgroup containing page contents for the page at the index specified.
+ * A keyboard listener for scrollable folders
*/
- 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();
+ public static class PagedFolderKeyEventListener extends PagedViewKeyListener {
+
+ private final Folder mFolder;
+
+ public PagedFolderKeyEventListener(Folder folder) {
+ mFolder = folder;
+ }
+
+ @Override
+ public void handleNoopKey(int keyCode, View v) {
+ if (keyCode == KeyEvent.KEYCODE_DPAD_DOWN) {
+ mFolder.mFolderName.requestFocus();
+ playSoundEffect(keyCode, v);
+ }
}
- return page;
}
/**
- * 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) {
+ public static class PagedViewKeyListener implements View.OnKeyListener {
+
+ @Override
+ public boolean onKey(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;
@@ -83,591 +112,417 @@ public class FocusHelper {
countX = ((CellLayout) parentLayout).getCountX();
countY = ((CellLayout) parentLayout).getCountY();
} else {
- itemContainer = parentLayout = (ViewGroup) v.getParent();
- countX = ((PagedViewGridLayout) parentLayout).getCellCountX();
- countY = ((PagedViewGridLayout) parentLayout).getCellCountY();
+ if (LauncherAppState.isDogfoodBuild()) {
+ throw new IllegalStateException("Parent of the focused item is not supported.");
+ } else {
+ return false;
+ }
}
- // 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);
- }
- }
- }
- }
+ // TODO(hyunyoungs): this matrix is not applicable on the last page.
+ int[][] matrix = FocusLogic.createFullMatrix(countX, countY, true);
+
+ // Process focus.
+ int newIconIndex = FocusLogic.handleKeyEvent(keyCode, countX, countY, matrix,
+ iconIndex, pageIndex, pageCount);
+ if (newIconIndex == FocusLogic.NOOP) {
+ handleNoopKey(keyCode, v);
+ return consume;
+ }
+ switch (newIconIndex) {
+ case FocusLogic.PREVIOUS_PAGE_RIGHT_COLUMN:
+ newParent = getAppsCustomizePage(container, pageIndex -1);
+ if (newParent != null) {
+ int row = FocusLogic.findRow(matrix, iconIndex);
+ container.snapToPage(pageIndex - 1);
+ // no need to create a new matrix.
+ child = newParent.getChildAt(matrix[countX-1][row]);
}
- 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_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_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.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_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);
- }
- }
+ 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_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);
- }
+ case FocusLogic.NEXT_PAGE_LEFT_COLUMN:
+ newParent = getAppsCustomizePage(container, pageIndex + 1);
+ if (newParent != null) {
+ container.snapToPage(pageIndex + 1);
+ int row = FocusLogic.findRow(matrix, iconIndex);
+ child = newParent.getChildAt(matrix[0][row]);
}
- 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;
+ case FocusLogic.CURRENT_PAGE_FIRST_ITEM:
+ child = container.getChildAt(0);
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);
+ } else {
+ handleNoopKey(keyCode, v);
+ }
+ return consume;
+ }
+
+ public void handleNoopKey(int keyCode, View v) { }
}
/**
- * 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();
+ Hotseat hotseat = (Hotseat) hotseatLayout.getParent();
- /**
- * 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));
+ Workspace workspace = (Workspace) v.getRootView().findViewById(R.id.workspace);
+ int pageIndex = workspace.getCurrentPage();
+ int pageCount = workspace.getChildCount();
+ int countX = -1;
+ int countY = -1;
+ int iconIndex = findIndexOfView(hotseatParent, v);
+
+ final CellLayout iconLayout = (CellLayout) workspace.getChildAt(pageIndex);
+ final ViewGroup iconParent = iconLayout.getShortcutsAndWidgets();
+
+ ViewGroup parent = null;
+ int[][] matrix = null;
+
+ if (keyCode == KeyEvent.KEYCODE_DPAD_UP &&
+ orientation == Configuration.ORIENTATION_PORTRAIT) {
+ matrix = FocusLogic.createSparseMatrix(iconLayout, hotseatLayout, orientation,
+ hotseat.getAllAppsButtonRank(), true /* include all apps icon */);
+ 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,
+ hotseat.getAllAppsButtonRank(), true /* include all apps icon */);
+ iconIndex += iconParent.getChildCount();
+ countX = iconLayout.getCountX() + hotseatLayout.getCountX();
+ countY = iconLayout.getCountY();
+ parent = iconParent;
+ } else if (keyCode == KeyEvent.KEYCODE_DPAD_RIGHT &&
+ orientation == Configuration.ORIENTATION_LANDSCAPE) {
+ keyCode = KeyEvent.KEYCODE_PAGE_DOWN;
+ }else {
+ // For other KEYCODE_DPAD_LEFT and KEYCODE_DPAD_RIGHT navigation, do not use the
+ // matrix extended with hotseat.
+ matrix = FocusLogic.createSparseMatrix(hotseatLayout);
+ countX = hotseatLayout.getCountX();
+ countY = hotseatLayout.getCountY();
+ parent = hotseatParent;
}
- 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);
+
+ View newIcon = null;
+ if (newIconIndex == FocusLogic.NEXT_PAGE_FIRST_ITEM) {
+ parent = getCellLayoutChildrenForIndex(workspace, pageIndex + 1);
+ newIcon = parent.getChildAt(0);
+ // TODO(hyunyoungs): handle cases where the child is not an icon but
+ // a folder or a widget.
+ workspace.snapToPage(pageIndex + 1);
}
- 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 (parent == iconParent && newIconIndex >= iconParent.getChildCount()) {
+ newIconIndex -= iconParent.getChildCount();
+ }
+ if (parent != null) {
+ if (newIcon == null && newIconIndex >=0) {
+ newIcon = parent.getChildAt(newIconIndex);
}
- if (closestIndex > -1) {
- return views.get(closestIndex);
+ 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();
+ CellLayout iconLayout = (CellLayout) parent.getParent();
+ final Workspace workspace = (Workspace) iconLayout.getParent();
final ViewGroup launcher = (ViewGroup) workspace.getParent();
final ViewGroup tabs = (ViewGroup) launcher.findViewById(R.id.search_drop_target_bar);
- final ViewGroup hotseat = (ViewGroup) launcher.findViewById(R.id.hotseat);
- int pageIndex = workspace.indexOfChild(layout);
+ final Hotseat hotseat = (Hotseat) launcher.findViewById(R.id.hotseat);
+ int pageIndex = workspace.indexOfChild(iconLayout);
int pageCount = workspace.getChildCount();
+ int countX = iconLayout.getCountX();
+ int countY = iconLayout.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);
- }
- }
+ CellLayout hotseatLayout = (CellLayout) hotseat.getChildAt(0);
+ ShortcutAndWidgetContainer hotseatParent = hotseatLayout.getShortcutsAndWidgets();
+ int[][] matrix;
+
+ // KEYCODE_DPAD_DOWN in portrait (KEYCODE_DPAD_RIGHT 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(iconLayout, hotseatLayout, orientation,
+ hotseat.getAllAppsButtonRank(), false /* all apps icon is ignored */);
+ countY = countY + 1;
+ } else if (keyCode == KeyEvent.KEYCODE_DPAD_RIGHT &&
+ orientation == Configuration.ORIENTATION_LANDSCAPE) {
+ matrix = FocusLogic.createSparseMatrix(iconLayout, hotseatLayout, orientation,
+ hotseat.getAllAppsButtonRank(), false /* all apps icon is ignored */);
+ countX = countX + 1;
+ } else if (keyCode == KeyEvent.KEYCODE_DEL || keyCode == KeyEvent.KEYCODE_FORWARD_DEL) {
+ workspace.removeWorkspaceItem(v);
+ return consume;
+ } else {
+ matrix = FocusLogic.createSparseMatrix(iconLayout);
+ }
+
+ // 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;
}
- 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);
- }
- }
+ case FocusLogic.PREVIOUS_PAGE_RIGHT_COLUMN:
+ int row = FocusLogic.findRow(matrix, iconIndex);
+ parent = getCellLayoutChildrenForIndex(workspace, pageIndex - 1);
+ if (parent != null) {
+ iconLayout = (CellLayout) parent.getParent();
+ matrix = FocusLogic.createSparseMatrix(iconLayout,
+ iconLayout.getCountX(), row);
+ newIconIndex = FocusLogic.handleKeyEvent(keyCode, countX + 1, countY, matrix,
+ FocusLogic.PIVOT, pageIndex - 1, pageCount);
+ newIcon = parent.getChildAt(newIconIndex);
}
- 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);
- }
+ case FocusLogic.PREVIOUS_PAGE_FIRST_ITEM:
+ parent = getCellLayoutChildrenForIndex(workspace, pageIndex - 1);
+ newIcon = parent.getChildAt(0);
+ workspace.snapToPage(pageIndex - 1);
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_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);
- }
- }
+ case FocusLogic.NEXT_PAGE_LEFT_COLUMN:
+ row = FocusLogic.findRow(matrix, iconIndex);
+ parent = getCellLayoutChildrenForIndex(workspace, pageIndex + 1);
+ if (parent != null) {
+ iconLayout = (CellLayout) parent.getParent();
+ matrix = FocusLogic.createSparseMatrix(iconLayout, -1, row);
+ newIconIndex = FocusLogic.handleKeyEvent(keyCode, countX + 1, countY, matrix,
+ FocusLogic.PIVOT, pageIndex, pageCount);
+ newIcon = parent.getChildAt(newIconIndex);
}
- wasHandled = true;
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_FIRST_ITEM:
+ newIcon = parent.getChildAt(0);
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);
- }
+ case FocusLogic.CURRENT_PAGE_LAST_ITEM:
+ newIcon = parent.getChildAt(parent.getChildCount() - 1);
+ break;
+ 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();
+ final Folder folder = (Folder) layout.getParent().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 */);
- 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;
}
}