From 97d85d23b013347bead4e2f5fa430a79ce69431e Mon Sep 17 00:00:00 2001 From: Winson Chung Date: Wed, 13 Apr 2011 11:27:36 -0700 Subject: Fixing focus issues in Launcher (keyboard support). Change-Id: Ieafd713393daf5628f229a66441bd3ed293245da --- src/com/android/launcher2/AccessibleTabView.java | 48 ++ src/com/android/launcher2/AllAppsTabbed.java | 17 + src/com/android/launcher2/BubbleTextView.java | 15 +- src/com/android/launcher2/CachedTextView.java | 13 +- src/com/android/launcher2/CustomizePagedView.java | 2 - .../android/launcher2/CustomizeTrayTabHost.java | 19 +- src/com/android/launcher2/FocusHelper.java | 772 +++++++++++++++++++++ src/com/android/launcher2/FocusOnlyTabWidget.java | 86 +++ src/com/android/launcher2/Launcher.java | 37 +- .../launcher2/LauncherAppWidgetHostView.java | 6 + src/com/android/launcher2/PagedView.java | 2 +- src/com/android/launcher2/PagedViewCellLayout.java | 8 + .../android/launcher2/PagedViewExtendedLayout.java | 2 +- src/com/android/launcher2/PagedViewIcon.java | 15 +- src/com/android/launcher2/PagedViewWidget.java | 13 + src/com/android/launcher2/Utilities.java | 6 + src/com/android/launcher2/Workspace.java | 8 + 17 files changed, 1047 insertions(+), 22 deletions(-) create mode 100644 src/com/android/launcher2/AccessibleTabView.java create mode 100644 src/com/android/launcher2/FocusHelper.java create mode 100644 src/com/android/launcher2/FocusOnlyTabWidget.java (limited to 'src') diff --git a/src/com/android/launcher2/AccessibleTabView.java b/src/com/android/launcher2/AccessibleTabView.java new file mode 100644 index 000000000..a41991155 --- /dev/null +++ b/src/com/android/launcher2/AccessibleTabView.java @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2011 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.launcher2; + +import android.content.Context; +import android.util.AttributeSet; +import android.view.KeyEvent; +import android.widget.TextView; + +public class AccessibleTabView extends TextView { + public AccessibleTabView(Context context) { + super(context); + } + + public AccessibleTabView(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public AccessibleTabView(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + } + + @Override + public boolean onKeyDown(int keyCode, KeyEvent event) { + return FocusHelper.handleTabKeyEvent(this, keyCode, event) + || super.onKeyDown(keyCode, event); + } + + @Override + public boolean onKeyUp(int keyCode, KeyEvent event) { + return FocusHelper.handleTabKeyEvent(this, keyCode, event) + || super.onKeyUp(keyCode, event); + } +} diff --git a/src/com/android/launcher2/AllAppsTabbed.java b/src/com/android/launcher2/AllAppsTabbed.java index 059d0ae25..2deec8e1a 100644 --- a/src/com/android/launcher2/AllAppsTabbed.java +++ b/src/com/android/launcher2/AllAppsTabbed.java @@ -29,11 +29,13 @@ import android.util.Log; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; +import android.view.ViewGroup; import android.widget.TabHost; import android.widget.TabWidget; import android.widget.TextView; import java.util.ArrayList; +import java.util.Random; /** * Implements a tabbed version of AllApps2D. @@ -80,6 +82,7 @@ public class AllAppsTabbed extends TabHost implements AllAppsView, LauncherTrans }; // Create the tabs and wire them up properly + AllAppsTabKeyEventListener keyListener = new AllAppsTabKeyEventListener(); TextView tabView; TabWidget tabWidget = (TabWidget) findViewById(com.android.internal.R.id.tabs); tabView = (TextView) mInflater.inflate(R.layout.tab_widget_indicator, tabWidget, false); @@ -90,6 +93,12 @@ public class AllAppsTabbed extends TabHost implements AllAppsView, LauncherTrans tabView.setText(mContext.getString(R.string.all_apps_tab_downloaded)); addTab(newTabSpec(TAG_DOWNLOADED).setIndicator(tabView).setContent(contentFactory)); + // Setup the key listener to jump between the last tab view and the market icon + View lastTab = tabWidget.getChildTabViewAt(tabWidget.getTabCount() - 1); + lastTab.setOnKeyListener(keyListener); + View shopButton = findViewById(R.id.market_button); + shopButton.setOnKeyListener(keyListener); + setOnTabChangedListener(new OnTabChangeListener() { public void onTabChanged(String tabId) { // animate the changing of the tab content by fading pages in and out @@ -259,4 +268,12 @@ public class AllAppsTabbed extends TabHost implements AllAppsView, LauncherTrans } return true; } + + @Override + public int getDescendantFocusability() { + if (getVisibility() != View.VISIBLE) { + return ViewGroup.FOCUS_BLOCK_DESCENDANTS; + } + return super.getDescendantFocusability(); + } } diff --git a/src/com/android/launcher2/BubbleTextView.java b/src/com/android/launcher2/BubbleTextView.java index 146485489..703b3a8cb 100644 --- a/src/com/android/launcher2/BubbleTextView.java +++ b/src/com/android/launcher2/BubbleTextView.java @@ -29,6 +29,7 @@ import android.graphics.Region; import android.graphics.Region.Op; import android.graphics.drawable.Drawable; import android.util.AttributeSet; +import android.view.KeyEvent; import android.view.MotionEvent; import android.view.View; import android.widget.TextView; @@ -87,8 +88,6 @@ public class BubbleTextView extends TextView implements VisibilityChangedBroadca private void init() { mBackground = getBackground(); - setFocusable(true); - setBackgroundDrawable(null); final Resources res = getContext().getResources(); int bubbleColor = res.getColor(R.color.bubble_dark_background); @@ -330,4 +329,16 @@ public class BubbleTextView extends TextView implements VisibilityChangedBroadca } return true; } + + @Override + public boolean onKeyDown(int keyCode, KeyEvent event) { + return FocusHelper.handleBubbleTextViewKeyEvent(this, keyCode, event) + || super.onKeyDown(keyCode, event); + } + + @Override + public boolean onKeyUp(int keyCode, KeyEvent event) { + return FocusHelper.handleBubbleTextViewKeyEvent(this, keyCode, event) + || super.onKeyUp(keyCode, event); + } } diff --git a/src/com/android/launcher2/CachedTextView.java b/src/com/android/launcher2/CachedTextView.java index 403d856c0..d0f6dd804 100644 --- a/src/com/android/launcher2/CachedTextView.java +++ b/src/com/android/launcher2/CachedTextView.java @@ -18,10 +18,11 @@ package com.android.launcher2; import android.content.Context; import android.graphics.Bitmap; +import android.graphics.Bitmap.Config; import android.graphics.Canvas; import android.graphics.Paint; -import android.graphics.Bitmap.Config; import android.graphics.PorterDuff.Mode; +import android.graphics.drawable.Drawable; import android.text.Layout; import android.util.AttributeSet; import android.widget.TextView; @@ -163,6 +164,16 @@ public class CachedTextView extends TextView { if (mPrevAlpha != alpha) { mPrevAlpha = alpha; mCachePaint.setAlpha(alpha); + + // We manually update the drawables alpha since the default TextView implementation may + // not do this if there is a background set (which we may due to the focus bg) + final Drawable[] dr = getCompoundDrawables(); + for (int i = 0; i < dr.length; ++i) { + if (dr[i] != null) { + dr[i].mutate().setAlpha(alpha); + } + } + super.onSetAlpha(alpha); } return true; diff --git a/src/com/android/launcher2/CustomizePagedView.java b/src/com/android/launcher2/CustomizePagedView.java index 55b22fcd3..ca60361b3 100644 --- a/src/com/android/launcher2/CustomizePagedView.java +++ b/src/com/android/launcher2/CustomizePagedView.java @@ -488,8 +488,6 @@ public class CustomizePagedView extends PagedViewWithDraggableItems @Override public void onClick(final View v) { - // Return early if this is not initiated from a touch - if (!v.isInTouchMode()) return; // Return early if we are still animating the pages if (mNextPage != INVALID_PAGE) return; diff --git a/src/com/android/launcher2/CustomizeTrayTabHost.java b/src/com/android/launcher2/CustomizeTrayTabHost.java index 3e0402509..5c683c486 100644 --- a/src/com/android/launcher2/CustomizeTrayTabHost.java +++ b/src/com/android/launcher2/CustomizeTrayTabHost.java @@ -16,8 +16,7 @@ package com.android.launcher2; -import com.android.launcher.R; -import com.android.launcher2.CustomizePagedView.CustomizationType; +import java.util.Random; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; @@ -28,10 +27,14 @@ import android.content.res.Resources; import android.util.AttributeSet; import android.view.LayoutInflater; import android.view.View; +import android.view.ViewGroup; import android.widget.TabHost; import android.widget.TabWidget; import android.widget.TextView; +import com.android.launcher.R; +import com.android.launcher2.CustomizePagedView.CustomizationType; + public class CustomizeTrayTabHost extends TabHost implements LauncherTransitionable { // tags for the customization tabs private static final String WIDGETS_TAG = "widgets"; @@ -69,7 +72,8 @@ public class CustomizeTrayTabHost extends TabHost implements LauncherTransitiona tabView = (TextView) mInflater.inflate(R.layout.tab_widget_indicator, tabWidget, false); tabView.setText(mContext.getString(R.string.widgets_tab_label)); - addTab(newTabSpec(WIDGETS_TAG).setIndicator(tabView).setContent(contentFactory)); + addTab(newTabSpec(WIDGETS_TAG) + .setIndicator(tabView).setContent(contentFactory)); tabView = (TextView) mInflater.inflate(R.layout.tab_widget_indicator, tabWidget, false); tabView.setText(mContext.getString(R.string.applications_tab_label)); addTab(newTabSpec(APPLICATIONS_TAG) @@ -82,6 +86,7 @@ public class CustomizeTrayTabHost extends TabHost implements LauncherTransitiona tabView.setText(mContext.getString(R.string.shortcuts_tab_label)); addTab(newTabSpec(SHORTCUTS_TAG) .setIndicator(tabView).setContent(contentFactory)); + setOnTabChangedListener(new OnTabChangeListener() { public void onTabChanged(String tabId) { final CustomizePagedView.CustomizationType newType = @@ -149,6 +154,14 @@ public class CustomizeTrayTabHost extends TabHost implements LauncherTransitiona super.onLayout(changed, l, t, r, b); } + @Override + public int getDescendantFocusability() { + if (getVisibility() != View.VISIBLE) { + return ViewGroup.FOCUS_BLOCK_DESCENDANTS; + } + return super.getDescendantFocusability(); + } + CustomizationType getCustomizeFilterForTabTag(String tag) { if (tag.equals(WIDGETS_TAG)) { return CustomizationType.WidgetCustomization; diff --git a/src/com/android/launcher2/FocusHelper.java b/src/com/android/launcher2/FocusHelper.java new file mode 100644 index 000000000..c9bd58cd0 --- /dev/null +++ b/src/com/android/launcher2/FocusHelper.java @@ -0,0 +1,772 @@ +/* + * Copyright (C) 2011 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.launcher2; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; + +import android.view.KeyEvent; +import android.view.View; +import android.view.ViewGroup; +import android.view.ViewParent; +import android.widget.TabHost; +import android.widget.TabWidget; + +import com.android.launcher.R; + +/** + * A keyboard listener we set on all the button bar buttons. + */ +class ButtonBarKeyEventListener implements View.OnKeyListener { + @Override + public boolean onKey(View v, int keyCode, KeyEvent event) { + return FocusHelper.handleButtonBarButtonKeyEvent(v, keyCode, event); + } +} + +/** + * A keyboard listener we set on the last tab button in AllApps to jump to then + * market icon and vice versa. + */ +class AllAppsTabKeyEventListener implements View.OnKeyListener { + @Override + public boolean onKey(View v, int keyCode, KeyEvent event) { + return FocusHelper.handleAllAppsTabKeyEvent(v, keyCode, event); + } +} + +public class FocusHelper { + /** + * Private helper to get the parent TabHost in the view hiearchy. + */ + private static TabHost findTabHostParent(View v) { + ViewParent p = v.getParent(); + while (p != null && !(p instanceof TabHost)) { + p = p.getParent(); + } + return (TabHost) p; + } + + /** + * Handles key events in a AllApps tab between the last tab view and the shop button. + */ + static boolean handleAllAppsTabKeyEvent(View v, int keyCode, KeyEvent e) { + final TabHost tabHost = findTabHostParent(v); + final ViewGroup contents = (ViewGroup) + tabHost.findViewById(com.android.internal.R.id.tabcontent); + final View shop = tabHost.findViewById(R.id.market_button); + + final int action = e.getAction(); + final boolean handleKeyEvent = (action != KeyEvent.ACTION_UP); + boolean wasHandled = false; + switch (keyCode) { + case KeyEvent.KEYCODE_DPAD_RIGHT: + if (handleKeyEvent) { + // Select the shop button if we aren't on it + if (v != shop) { + shop.requestFocus(); + } + } + wasHandled = true; + break; + case KeyEvent.KEYCODE_DPAD_DOWN: + if (handleKeyEvent) { + // Select the content view (down is handled by the tab key handler otherwise) + if (v == shop) { + contents.requestFocus(); + wasHandled = true; + } + } + break; + default: break; + } + return wasHandled; + } + + /** + * Private helper to determine whether a view is visible. + */ + private static boolean isVisible(View v) { + return v.getVisibility() == View.VISIBLE; + } + + /** + * Handles key events in a PageViewExtendedLayout containing PagedViewWidgets. + */ + static boolean handlePagedViewWidgetKeyEvent(PagedViewWidget w, int keyCode, KeyEvent e) { + if (!LauncherApplication.isScreenXLarge()) return false; + + final PagedViewExtendedLayout parent = (PagedViewExtendedLayout) w.getParent(); + final ViewGroup container = (ViewGroup) parent.getParent(); + final TabHost tabHost = findTabHostParent(container); + final TabWidget tabs = (TabWidget) tabHost.findViewById(com.android.internal.R.id.tabs); + final int widgetIndex = parent.indexOfChild(w); + final int widgetCount = parent.getChildCount(); + final int pageIndex = container.indexOfChild(parent); + final int pageCount = container.getChildCount(); + + final int action = e.getAction(); + final boolean handleKeyEvent = (action != KeyEvent.ACTION_UP); + PagedViewExtendedLayout newParent = null; + boolean wasHandled = false; + switch (keyCode) { + case KeyEvent.KEYCODE_DPAD_LEFT: + if (handleKeyEvent) { + // Select the previous widget or the last widget on the previous page + if (widgetIndex > 0) { + parent.getChildAt(widgetIndex - 1).requestFocus(); + } else { + if (pageIndex > 0) { + newParent = (PagedViewExtendedLayout) + container.getChildAt(pageIndex - 1); + newParent.getChildAt(newParent.getChildCount() - 1).requestFocus(); + } + } + } + wasHandled = true; + break; + case KeyEvent.KEYCODE_DPAD_RIGHT: + if (handleKeyEvent) { + // Select the next widget or the first widget on the next page + if (widgetIndex < (widgetCount - 1)) { + parent.getChildAt(widgetIndex + 1).requestFocus(); + } else { + if (pageIndex < (pageCount - 1)) { + newParent = (PagedViewExtendedLayout) + container.getChildAt(pageIndex + 1); + newParent.getChildAt(0).requestFocus(); + } + } + } + wasHandled = true; + break; + case KeyEvent.KEYCODE_DPAD_UP: + if (handleKeyEvent) { + // Select widgets tab on the tab bar + tabs.requestFocus(); + } + wasHandled = true; + break; + case KeyEvent.KEYCODE_DPAD_DOWN: + if (handleKeyEvent) { + // TODO: Should focus the global search bar + } + wasHandled = true; + break; + case KeyEvent.KEYCODE_ENTER: + case KeyEvent.KEYCODE_DPAD_CENTER: + if (handleKeyEvent) { + // Simulate a click on the widget + View.OnClickListener clickListener = (View.OnClickListener) container; + clickListener.onClick(w); + } + wasHandled = true; + break; + case KeyEvent.KEYCODE_PAGE_UP: + if (handleKeyEvent) { + // Select the first item on the previous page, or the first item on this page + // if there is no previous page + if (pageIndex > 0) { + newParent = (PagedViewExtendedLayout) container.getChildAt(pageIndex - 1); + newParent.getChildAt(0).requestFocus(); + } else { + parent.getChildAt(0).requestFocus(); + } + } + wasHandled = true; + break; + case KeyEvent.KEYCODE_PAGE_DOWN: + if (handleKeyEvent) { + // Select the first item on the next page, or the last item on this page + // if there is no next page + if (pageIndex < (pageCount - 1)) { + newParent = (PagedViewExtendedLayout) container.getChildAt(pageIndex + 1); + newParent.getChildAt(0).requestFocus(); + } else { + parent.getChildAt(widgetCount - 1).requestFocus(); + } + } + wasHandled = true; + break; + case KeyEvent.KEYCODE_MOVE_HOME: + if (handleKeyEvent) { + // Select the first item on this page + parent.getChildAt(0).requestFocus(); + } + wasHandled = true; + break; + case KeyEvent.KEYCODE_MOVE_END: + if (handleKeyEvent) { + // Select the last item on this page + parent.getChildAt(widgetCount - 1).requestFocus(); + } + wasHandled = true; + break; + default: break; + } + return wasHandled; + } + + /** + * Private helper method to get the PagedViewCellLayoutChildren given a PagedViewCellLayout + * index. + */ + private static PagedViewCellLayoutChildren getPagedViewCellLayoutChildrenForIndex( + ViewGroup container, int i) { + ViewGroup parent = (ViewGroup) container.getChildAt(i); + return (PagedViewCellLayoutChildren) parent.getChildAt(0); + } + + /** + * Handles key events in a PageViewCellLayout containing PagedViewIcons. + */ + static boolean handlePagedViewIconKeyEvent(PagedViewIcon v, int keyCode, KeyEvent e) { + if (!LauncherApplication.isScreenXLarge()) return false; + + final PagedViewCellLayoutChildren parent = (PagedViewCellLayoutChildren) v.getParent(); + final PagedViewCellLayout parentLayout = (PagedViewCellLayout) parent.getParent(); + // Note we have an extra parent because of the + // PagedViewCellLayout/PagedViewCellLayoutChildren relationship + final ViewGroup container = (ViewGroup) parentLayout.getParent(); + final TabHost tabHost = findTabHostParent(container); + final TabWidget tabs = (TabWidget) tabHost.findViewById(com.android.internal.R.id.tabs); + final int widgetIndex = parent.indexOfChild(v); + final int widgetCount = parent.getChildCount(); + final int pageIndex = container.indexOfChild(parentLayout); + final int pageCount = container.getChildCount(); + final int cellCountX = parentLayout.getCellCountX(); + final int cellCountY = parentLayout.getCellCountY(); + final int x = widgetIndex % cellCountX; + final int y = widgetIndex / cellCountX; + + final int action = e.getAction(); + final boolean handleKeyEvent = (action != KeyEvent.ACTION_UP); + PagedViewCellLayoutChildren newParent = 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 (widgetIndex > 0) { + parent.getChildAt(widgetIndex - 1).requestFocus(); + } else { + if (pageIndex > 0) { + newParent = getPagedViewCellLayoutChildrenForIndex(container, + pageIndex - 1); + newParent.getChildAt(newParent.getChildCount() - 1).requestFocus(); + } + } + } + wasHandled = true; + break; + case KeyEvent.KEYCODE_DPAD_RIGHT: + if (handleKeyEvent) { + // Select the next icon or the first icon on the next page + if (widgetIndex < (widgetCount - 1)) { + parent.getChildAt(widgetIndex + 1).requestFocus(); + } else { + if (pageIndex < (pageCount - 1)) { + newParent = getPagedViewCellLayoutChildrenForIndex(container, + pageIndex + 1); + newParent.getChildAt(0).requestFocus(); + } + } + } + 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 newWidgetIndex = ((y - 1) * cellCountX) + x; + parent.getChildAt(newWidgetIndex).requestFocus(); + } else { + tabs.requestFocus(); + } + } + wasHandled = true; + break; + case KeyEvent.KEYCODE_DPAD_DOWN: + if (handleKeyEvent) { + // Select the closest icon in the previous row, otherwise do nothing + if (y < (cellCountY - 1)) { + int newWidgetIndex = Math.min(widgetCount - 1, ((y + 1) * cellCountX) + x); + parent.getChildAt(newWidgetIndex).requestFocus(); + } + } + wasHandled = true; + break; + case KeyEvent.KEYCODE_ENTER: + case KeyEvent.KEYCODE_DPAD_CENTER: + if (handleKeyEvent) { + // Simulate a click on the icon + View.OnClickListener clickListener = (View.OnClickListener) container; + clickListener.onClick(v); + } + 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 = getPagedViewCellLayoutChildrenForIndex(container, + pageIndex - 1); + newParent.getChildAt(0).requestFocus(); + } else { + parent.getChildAt(0).requestFocus(); + } + } + 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 = getPagedViewCellLayoutChildrenForIndex(container, + pageIndex + 1); + newParent.getChildAt(0).requestFocus(); + } else { + parent.getChildAt(widgetCount - 1).requestFocus(); + } + } + wasHandled = true; + break; + case KeyEvent.KEYCODE_MOVE_HOME: + if (handleKeyEvent) { + // Select the first icon on this page + parent.getChildAt(0).requestFocus(); + } + wasHandled = true; + break; + case KeyEvent.KEYCODE_MOVE_END: + if (handleKeyEvent) { + // Select the last icon on this page + parent.getChildAt(widgetCount - 1).requestFocus(); + } + wasHandled = true; + break; + default: break; + } + return wasHandled; + } + + /** + * Handles key events in the tab widget. + */ + static boolean handleTabKeyEvent(AccessibleTabView v, int keyCode, KeyEvent e) { + if (!LauncherApplication.isScreenXLarge()) return false; + + final FocusOnlyTabWidget parent = (FocusOnlyTabWidget) v.getParent(); + final TabHost tabHost = findTabHostParent(parent); + final ViewGroup contents = (ViewGroup) + tabHost.findViewById(com.android.internal.R.id.tabcontent); + final int tabCount = parent.getTabCount(); + final int tabIndex = parent.getChildTabIndex(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 tab + if (tabIndex > 0) { + parent.getChildTabViewAt(tabIndex - 1).requestFocus(); + } + } + wasHandled = true; + break; + case KeyEvent.KEYCODE_DPAD_RIGHT: + if (handleKeyEvent) { + // Select the next tab, or if the last tab has a focus right id, select that + if (tabIndex < (tabCount - 1)) { + parent.getChildTabViewAt(tabIndex + 1).requestFocus(); + } else { + if (v.getNextFocusRightId() != View.NO_ID) { + tabHost.findViewById(v.getNextFocusRightId()).requestFocus(); + } + } + } + wasHandled = true; + break; + case KeyEvent.KEYCODE_DPAD_UP: + // Do nothing + wasHandled = true; + break; + case KeyEvent.KEYCODE_DPAD_DOWN: + if (handleKeyEvent) { + // Select the content view + contents.requestFocus(); + } + wasHandled = true; + break; + default: break; + } + return wasHandled; + } + + /** + * Handles key events in a the workspace button bar. + */ + static boolean handleButtonBarButtonKeyEvent(View v, int keyCode, KeyEvent e) { + if (!LauncherApplication.isScreenXLarge()) return false; + + final ViewGroup parent = (ViewGroup) v.getParent(); + final ViewGroup launcher = (ViewGroup) parent.getParent(); + final Workspace workspace = (Workspace) launcher.findViewById(R.id.workspace); + final int buttonIndex = parent.indexOfChild(v); + final int buttonCount = parent.getChildCount(); + final int pageIndex = workspace.getCurrentPage(); + final int pageCount = workspace.getChildCount(); + final int firstButtonIndex = parent.indexOfChild(parent.findViewById(R.id.search_button)); + final int lastButtonIndex = parent.indexOfChild(parent.findViewById(R.id.configure_button)); + + 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 button, otherwise do nothing (since the button bar is + // static) + if (buttonIndex > firstButtonIndex) { + int newButtonIndex = buttonIndex - 1; + while (newButtonIndex >= firstButtonIndex) { + View prev = parent.getChildAt(newButtonIndex); + if (isVisible(prev) && prev.isFocusable()) { + prev.requestFocus(); + break; + } + --newButtonIndex; + } + } else { + if (pageIndex > 0) { + // Snap to previous page and clear focus + workspace.snapToPage(pageIndex - 1); + } + } + } + wasHandled = true; + break; + case KeyEvent.KEYCODE_DPAD_RIGHT: + if (handleKeyEvent) { + // Select the next button, otherwise do nothing (since the button bar is + // static) + if (buttonIndex < lastButtonIndex) { + int newButtonIndex = buttonIndex + 1; + while (newButtonIndex <= lastButtonIndex) { + View next = parent.getChildAt(newButtonIndex); + if (isVisible(next) && next.isFocusable()) { + next.requestFocus(); + break; + } + ++newButtonIndex; + } + } else { + if (pageIndex < (pageCount - 1)) { + // Snap to next page and clear focus + workspace.snapToPage(pageIndex + 1); + } + } + } + wasHandled = true; + break; + case KeyEvent.KEYCODE_DPAD_UP: + // Do nothing + wasHandled = true; + break; + case KeyEvent.KEYCODE_DPAD_DOWN: + if (handleKeyEvent) { + // Select the first bubble text view in the current page of the workspace + final CellLayout layout = (CellLayout) workspace.getChildAt(pageIndex); + final CellLayoutChildren children = layout.getChildrenLayout(); + final View newIcon = getBubbleTextViewInDirection(layout, children, -1, 1); + if (newIcon != null) { + newIcon.requestFocus(); + } else { + workspace.requestFocus(); + } + } + wasHandled = true; + break; + default: break; + } + return wasHandled; + } + + /** + * Private helper method to get the CellLayoutChildren given a CellLayout index. + */ + private static CellLayoutChildren getCellLayoutChildrenForIndex(ViewGroup container, int i) { + ViewGroup parent = (ViewGroup) container.getChildAt(i); + return (CellLayoutChildren) parent.getChildAt(0); + } + + /** + * 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 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 views = new ArrayList(); + for (int j = 0; j < count; ++j) { + views.add(parent.getChildAt(j)); + } + Collections.sort(views, new Comparator() { + @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 in the delta direction. + * @param delta either -1 or 1 depending on the direction we want to search + */ + private static View findIndexOfBubbleTextView(ArrayList 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) { + return newV; + } + newI += delta; + } + return null; + } + private static View getBubbleTextViewInDirection(CellLayout layout, ViewGroup parent, int i, + int delta) { + final ArrayList views = getCellLayoutChildrenSortedSpatially(layout, parent); + return findIndexOfBubbleTextView(views, i, delta); + } + private static View getBubbleTextViewInDirection(CellLayout layout, ViewGroup parent, View v, + int delta) { + final ArrayList views = getCellLayoutChildrenSortedSpatially(layout, parent); + return findIndexOfBubbleTextView(views, views.indexOf(v), delta); + } + /** + * Private helper method to find the next closest BubbleTextView in the delta direction on the + * next line. + * @param delta either -1 or 1 depending on the line and direction we want to search + */ + private static View getClosestBubbleTextViewOnLine(CellLayout layout, ViewGroup parent, View v, + int lineDelta) { + final ArrayList views = getCellLayoutChildrenSortedSpatially(layout, parent); + final CellLayout.LayoutParams lp = (CellLayout.LayoutParams) v.getLayoutParams(); + final int cellCountX = layout.getCountX(); + 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) { + 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); + } + } + return null; + } + + /** + * Handles key events in a Workspace containing BubbleTextView. + */ + static boolean handleBubbleTextViewKeyEvent(BubbleTextView v, int keyCode, KeyEvent e) { + if (!LauncherApplication.isScreenXLarge()) return false; + + CellLayoutChildren parent = (CellLayoutChildren) v.getParent(); + final CellLayout layout = (CellLayout) parent.getParent(); + final Workspace workspace = (Workspace) layout.getParent(); + final ViewGroup launcher = (ViewGroup) workspace.getParent(); + final ViewGroup tabs = (ViewGroup) launcher.findViewById(R.id.all_apps_button_cluster); + int iconIndex = parent.indexOfChild(v); + int iconCount = parent.getChildCount(); + int pageIndex = workspace.indexOfChild(layout); + int pageCount = workspace.getChildCount(); + + 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 = getBubbleTextViewInDirection(layout, parent, v, -1); + if (newIcon != null) { + newIcon.requestFocus(); + } else { + if (pageIndex > 0) { + parent = getCellLayoutChildrenForIndex(workspace, pageIndex - 1); + newIcon = getBubbleTextViewInDirection(layout, parent, + parent.getChildCount(), -1); + if (newIcon != null) { + newIcon.requestFocus(); + } else { + // Snap to the previous page + workspace.snapToPage(pageIndex - 1); + } + } + } + } + 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 = getBubbleTextViewInDirection(layout, parent, v, 1); + if (newIcon != null) { + newIcon.requestFocus(); + } else { + if (pageIndex < (pageCount - 1)) { + parent = getCellLayoutChildrenForIndex(workspace, pageIndex + 1); + newIcon = getBubbleTextViewInDirection(layout, parent, -1, 1); + if (newIcon != null) { + newIcon.requestFocus(); + } else { + // Snap to the next page + workspace.snapToPage(pageIndex + 1); + } + } + } + } + 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 = getClosestBubbleTextViewOnLine(layout, parent, v, -1); + if (newIcon != null) { + newIcon.requestFocus(); + wasHandled = true; + } else { + tabs.requestFocus(); + } + } + break; + case KeyEvent.KEYCODE_DPAD_DOWN: + if (handleKeyEvent) { + // Select the closest icon in the next line, otherwise select the tab bar + View newIcon = getClosestBubbleTextViewOnLine(layout, parent, v, 1); + if (newIcon != null) { + newIcon.requestFocus(); + 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) { + parent = getCellLayoutChildrenForIndex(workspace, pageIndex - 1); + View newIcon = getBubbleTextViewInDirection(layout, parent, -1, 1); + if (newIcon != null) { + newIcon.requestFocus(); + } else { + // Snap to the previous page + workspace.snapToPage(pageIndex - 1); + } + } else { + View newIcon = getBubbleTextViewInDirection(layout, parent, -1, 1); + if (newIcon != null) { + newIcon.requestFocus(); + } + } + } + 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 previous page + if (pageIndex < (pageCount - 1)) { + parent = getCellLayoutChildrenForIndex(workspace, pageIndex + 1); + View newIcon = getBubbleTextViewInDirection(layout, parent, -1, 1); + if (newIcon != null) { + newIcon.requestFocus(); + } else { + // Snap to the next page + workspace.snapToPage(pageIndex + 1); + } + } else { + View newIcon = getBubbleTextViewInDirection(layout, parent, + parent.getChildCount(), -1); + if (newIcon != null) { + newIcon.requestFocus(); + } + } + } + wasHandled = true; + break; + case KeyEvent.KEYCODE_MOVE_HOME: + if (handleKeyEvent) { + // Select the first icon on this page + View newIcon = getBubbleTextViewInDirection(layout, parent, -1, 1); + if (newIcon != null) { + newIcon.requestFocus(); + } + } + wasHandled = true; + break; + case KeyEvent.KEYCODE_MOVE_END: + if (handleKeyEvent) { + // Select the last icon on this page + View newIcon = getBubbleTextViewInDirection(layout, parent, + parent.getChildCount(), -1); + if (newIcon != null) { + newIcon.requestFocus(); + } + } + wasHandled = true; + break; + default: break; + } + return wasHandled; + } +} diff --git a/src/com/android/launcher2/FocusOnlyTabWidget.java b/src/com/android/launcher2/FocusOnlyTabWidget.java new file mode 100644 index 000000000..8e9f58c61 --- /dev/null +++ b/src/com/android/launcher2/FocusOnlyTabWidget.java @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2011 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.launcher2; + +import android.content.Context; +import android.util.AttributeSet; +import android.view.View; +import android.widget.TabWidget; + +public class FocusOnlyTabWidget extends TabWidget { + public FocusOnlyTabWidget(Context context) { + super(context); + } + + public FocusOnlyTabWidget(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public FocusOnlyTabWidget(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + } + + public View getSelectedTab() { + final int count = getTabCount(); + for (int i = 0; i < count; ++i) { + View v = getChildTabViewAt(i); + if (v.isSelected()) { + return v; + } + } + return null; + } + + public int getChildTabIndex(View v) { + final int tabCount = getTabCount(); + for (int i = 0; i < tabCount; ++i) { + if (getChildTabViewAt(i) == v) { + return i; + } + } + return -1; + } + + public void setCurrentTabToFocusedTab() { + View tab = null; + int index = -1; + final int count = getTabCount(); + for (int i = 0; i < count; ++i) { + View v = getChildTabViewAt(i); + if (v.hasFocus()) { + tab = v; + index = i; + break; + } + } + if (index > -1) { + super.setCurrentTab(index); + super.onFocusChange(tab, true); + } + } + public void superOnFocusChange(View v, boolean hasFocus) { + super.onFocusChange(v, hasFocus); + } + + @Override + public void onFocusChange(android.view.View v, boolean hasFocus) { + if (v == this && hasFocus && getTabCount() > 0) { + getSelectedTab().requestFocus(); + return; + } + } +} diff --git a/src/com/android/launcher2/Launcher.java b/src/com/android/launcher2/Launcher.java index 2f4763602..19a943b3c 100644 --- a/src/com/android/launcher2/Launcher.java +++ b/src/com/android/launcher2/Launcher.java @@ -206,7 +206,7 @@ public final class Launcher extends Activity private CustomizeTrayTabHost mHomeCustomizationDrawer; private boolean mAutoAdvanceRunning = false; - private View mButtonCluster; + private ViewGroup mButtonCluster; private View mAllAppsButton; private View mDivider; private View mConfigureButton; @@ -771,8 +771,10 @@ public final class Launcher extends Activity @Override public boolean onKeyDown(int keyCode, KeyEvent event) { - boolean handled = super.onKeyDown(keyCode, event); - if (!handled && acceptFilter() && keyCode != KeyEvent.KEYCODE_ENTER) { + final int uniChar = event.getUnicodeChar(); + final boolean handled = super.onKeyDown(keyCode, event); + final boolean isKeyNotWhitespace = uniChar > 0 && !Character.isWhitespace(uniChar); + if (!handled && acceptFilter() && isKeyNotWhitespace) { boolean gotKey = TextKeyListener.getInstance().onKeyDown(mWorkspace, mDefaultKeySsb, keyCode, event); if (gotKey && mDefaultKeySsb != null && mDefaultKeySsb.length() > 0) { @@ -960,7 +962,7 @@ public final class Launcher extends Activity deleteZone.setDragController(dragController); final View allAppsButton = findViewById(R.id.all_apps_button); - final View divider = findViewById(R.id.divider); + final View divider = findViewById(R.id.all_apps_divider); final View configureButton = findViewById(R.id.configure_button); if (LauncherApplication.isScreenXLarge()) { @@ -1009,10 +1011,15 @@ public final class Launcher extends Activity if (allAppsDeleteZone != null) { dragController.addDropTarget(allAppsDeleteZone); } - mButtonCluster = findViewById(R.id.all_apps_button_cluster); + mButtonCluster = (ViewGroup) findViewById(R.id.all_apps_button_cluster); + View.OnKeyListener listener = new ButtonBarKeyEventListener(); + int buttonCount = mButtonCluster.getChildCount(); + for (int i = 0; i < buttonCount; ++i) { + mButtonCluster.getChildAt(i).setOnKeyListener(listener); + } mAllAppsButton = findViewById(R.id.all_apps_button); - mDivider = findViewById(R.id.divider); + mDivider = findViewById(R.id.all_apps_divider); mConfigureButton = findViewById(R.id.configure_button); // We had previously set these click handlers in XML, but the first time we launched @@ -2576,7 +2583,6 @@ public final class Launcher extends Activity private void showAndEnableToolbarButton(View button) { button.setVisibility(View.VISIBLE); - button.setFocusable(true); button.setClickable(true); } @@ -2587,7 +2593,6 @@ public final class Launcher extends Activity } private void disableToolbarButton(View button) { - button.setFocusable(false); button.setClickable(false); } @@ -3146,14 +3151,20 @@ public final class Launcher extends Activity private void updateGlobalSearchIcon() { if (LauncherApplication.isScreenXLarge()) { + final View searchButton = findViewById(R.id.search_button); + final View searchDivider = findViewById(R.id.search_divider); + final SearchManager searchManager = (SearchManager) getSystemService(Context.SEARCH_SERVICE); ComponentName activityName = searchManager.getGlobalSearchActivity(); if (activityName != null) { sGlobalSearchIcon = updateButtonWithIconFromExternalActivity( R.id.search_button, activityName, R.drawable.ic_generic_search); + searchButton.setVisibility(View.VISIBLE); + searchDivider.setVisibility(View.VISIBLE); } else { - findViewById(R.id.search_button).setVisibility(View.GONE); + searchButton.setVisibility(View.GONE); + searchDivider.setVisibility(View.GONE); } } } @@ -3164,13 +3175,19 @@ public final class Launcher extends Activity private void updateVoiceSearchIcon() { if (LauncherApplication.isScreenXLarge()) { + final View searchDivider = findViewById(R.id.search_divider); + final View voiceButton = findViewById(R.id.voice_button); + Intent intent = new Intent(RecognizerIntent.ACTION_WEB_SEARCH); ComponentName activityName = intent.resolveActivity(getPackageManager()); if (activityName != null) { sVoiceSearchIcon = updateButtonWithIconFromExternalActivity( R.id.voice_button, activityName, R.drawable.ic_voice_search); + searchDivider.setVisibility(View.VISIBLE); + voiceButton.setVisibility(View.VISIBLE); } else { - findViewById(R.id.voice_button).setVisibility(View.GONE); + searchDivider.setVisibility(View.GONE); + voiceButton.setVisibility(View.GONE); } } } diff --git a/src/com/android/launcher2/LauncherAppWidgetHostView.java b/src/com/android/launcher2/LauncherAppWidgetHostView.java index 7f60cac3d..0dd1d8398 100644 --- a/src/com/android/launcher2/LauncherAppWidgetHostView.java +++ b/src/com/android/launcher2/LauncherAppWidgetHostView.java @@ -22,6 +22,7 @@ import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; import android.view.ViewConfiguration; +import android.view.ViewGroup; import com.android.launcher.R; @@ -122,4 +123,9 @@ public class LauncherAppWidgetHostView extends AppWidgetHostView } super.onVisibilityChanged(changedView, visibility); } + + @Override + public int getDescendantFocusability() { + return ViewGroup.FOCUS_BLOCK_DESCENDANTS; + } } diff --git a/src/com/android/launcher2/PagedView.java b/src/com/android/launcher2/PagedView.java index e0978b5de..9eae64758 100644 --- a/src/com/android/launcher2/PagedView.java +++ b/src/com/android/launcher2/PagedView.java @@ -1166,7 +1166,7 @@ public abstract class PagedView extends ViewGroup { public void requestChildFocus(View child, View focused) { super.requestChildFocus(child, focused); int page = indexOfChild(child); - if (page >= 0 && !isInTouchMode()) { + if (page >= 0 && page != getCurrentPage() && !isInTouchMode()) { snapToPage(page); } } diff --git a/src/com/android/launcher2/PagedViewCellLayout.java b/src/com/android/launcher2/PagedViewCellLayout.java index 762ec58a5..28e092e88 100644 --- a/src/com/android/launcher2/PagedViewCellLayout.java +++ b/src/com/android/launcher2/PagedViewCellLayout.java @@ -180,6 +180,14 @@ public class PagedViewCellLayout extends ViewGroup implements Page { return mChildren.indexOfChild(v); } + public int getCellCountX() { + return mCellCountX; + } + + public int getCellCountY() { + return mCellCountY; + } + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { // TODO: currently ignoring padding diff --git a/src/com/android/launcher2/PagedViewExtendedLayout.java b/src/com/android/launcher2/PagedViewExtendedLayout.java index e54d26176..81f14982f 100644 --- a/src/com/android/launcher2/PagedViewExtendedLayout.java +++ b/src/com/android/launcher2/PagedViewExtendedLayout.java @@ -26,7 +26,7 @@ import android.widget.LinearLayout; * The linear layout used strictly for the widget/wallpaper tab of the customization tray */ public class PagedViewExtendedLayout extends LinearLayout implements Page { - static final String TAG = "PagedViewWidgetLayout"; + static final String TAG = "PagedViewExtendedLayout"; public PagedViewExtendedLayout(Context context) { this(context, null); diff --git a/src/com/android/launcher2/PagedViewIcon.java b/src/com/android/launcher2/PagedViewIcon.java index bde65593a..f46b63c02 100644 --- a/src/com/android/launcher2/PagedViewIcon.java +++ b/src/com/android/launcher2/PagedViewIcon.java @@ -31,6 +31,7 @@ import android.os.Handler; import android.os.HandlerThread; import android.os.Message; import android.util.AttributeSet; +import android.view.KeyEvent; import android.widget.Checkable; import android.widget.TextView; @@ -128,8 +129,6 @@ public class PagedViewIcon extends CachedTextView implements Checkable { mCheckedFadeOutDuration = r.getInteger(R.integer.icon_allAppsCustomizeFadeOutTime); } - setFocusable(true); - setBackgroundDrawable(null); mHolographicOutlineView = new HolographicPagedViewIcon(context, this); } @@ -244,6 +243,18 @@ public class PagedViewIcon extends CachedTextView implements Checkable { sWorker.removeMessages(MESSAGE_CREATE_HOLOGRAPHIC_OUTLINE, this); } + @Override + public boolean onKeyDown(int keyCode, KeyEvent event) { + return FocusHelper.handlePagedViewIconKeyEvent(this, keyCode, event) + || super.onKeyDown(keyCode, event); + } + + @Override + public boolean onKeyUp(int keyCode, KeyEvent event) { + return FocusHelper.handlePagedViewIconKeyEvent(this, keyCode, event) + || super.onKeyUp(keyCode, event); + } + @Override public boolean isChecked() { return mIsChecked; diff --git a/src/com/android/launcher2/PagedViewWidget.java b/src/com/android/launcher2/PagedViewWidget.java index 72f928b54..c2d609e13 100644 --- a/src/com/android/launcher2/PagedViewWidget.java +++ b/src/com/android/launcher2/PagedViewWidget.java @@ -36,6 +36,7 @@ import android.os.Handler; import android.os.HandlerThread; import android.os.Message; import android.util.AttributeSet; +import android.view.KeyEvent; import android.view.MotionEvent; import android.view.View; import android.widget.Checkable; @@ -227,6 +228,18 @@ public class PagedViewWidget extends LinearLayout implements Checkable { return super.onTouchEvent(event) || true; } + @Override + public boolean onKeyDown(int keyCode, KeyEvent event) { + return FocusHelper.handlePagedViewWidgetKeyEvent(this, keyCode, event) + || super.onKeyDown(keyCode, event); + } + + @Override + public boolean onKeyUp(int keyCode, KeyEvent event) { + return FocusHelper.handlePagedViewWidgetKeyEvent(this, keyCode, event) + || super.onKeyUp(keyCode, event); + } + @Override protected void onDraw(Canvas canvas) { if (mAlpha > 0) { diff --git a/src/com/android/launcher2/Utilities.java b/src/com/android/launcher2/Utilities.java index 8ab22ebc8..cd98cab7f 100644 --- a/src/com/android/launcher2/Utilities.java +++ b/src/com/android/launcher2/Utilities.java @@ -16,6 +16,8 @@ package com.android.launcher2; +import java.util.Random; + import android.content.Context; import android.content.res.Resources; import android.graphics.Bitmap; @@ -389,4 +391,8 @@ final class Utilities { } return n; } + + static int generateRandomId() { + return new Random(System.currentTimeMillis()).nextInt(1 << 24); + } } diff --git a/src/com/android/launcher2/Workspace.java b/src/com/android/launcher2/Workspace.java index 61a0e4894..1c027ad22 100644 --- a/src/com/android/launcher2/Workspace.java +++ b/src/com/android/launcher2/Workspace.java @@ -1252,6 +1252,14 @@ public class Workspace extends SmoothPagedView return false; } + @Override + public int getDescendantFocusability() { + if (mIsSmall) { + return ViewGroup.FOCUS_BLOCK_DESCENDANTS; + } + return super.getDescendantFocusability(); + } + @Override public void addFocusables(ArrayList views, int direction, int focusableMode) { if (!mLauncher.isAllAppsVisible()) { -- cgit v1.2.3