diff options
Diffstat (limited to 'src/com/android')
20 files changed, 1101 insertions, 76 deletions
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/AllAppsPagedView.java b/src/com/android/launcher2/AllAppsPagedView.java index 1f38d8d6e..3d3c1ff3a 100644 --- a/src/com/android/launcher2/AllAppsPagedView.java +++ b/src/com/android/launcher2/AllAppsPagedView.java @@ -67,6 +67,7 @@ public class AllAppsPagedView extends PagedViewWithDraggableItems implements All private boolean mAllowHardwareLayerCreation; private int mPageContentWidth; + private boolean mHasMadeSuccessfulDrop; public AllAppsPagedView(Context context) { this(context, null); @@ -139,6 +140,10 @@ public class AllAppsPagedView extends PagedViewWithDraggableItems implements All } } + void resetSuccessfulDropFlag() { + mHasMadeSuccessfulDrop = false; + } + @Override public void zoom(float zoom, boolean animate) { mZoom = zoom; @@ -333,6 +338,12 @@ public class AllAppsPagedView extends PagedViewWithDraggableItems implements All tearDownDragMode(); mLauncher.getWorkspace().onDragStopped(success); mLauncher.unlockScreenOrientation(); + + if (!success && !mHasMadeSuccessfulDrop) { + mLauncher.getWorkspace().shrink(Workspace.ShrinkState.BOTTOM_HIDDEN); + } else { + mHasMadeSuccessfulDrop |= success; + } } int getPageContentWidth() { 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/CellLayout.java b/src/com/android/launcher2/CellLayout.java index 9cb963c02..ac1a4bde3 100644 --- a/src/com/android/launcher2/CellLayout.java +++ b/src/com/android/launcher2/CellLayout.java @@ -1661,7 +1661,7 @@ out: for (int i = x; i < x + spanX - 1 && x < xCount; i++) { // 2. When long clicking on an empty cell in a CellLayout, we save information about the // cellX and cellY coordinates and which page was clicked. We then set this as a tag on // the CellLayout that was long clicked - static final class CellInfo implements ContextMenu.ContextMenuInfo { + static final class CellInfo { View cell; int cellX = -1; int cellY = -1; diff --git a/src/com/android/launcher2/CustomizePagedView.java b/src/com/android/launcher2/CustomizePagedView.java index 3dfa7c267..ca60361b3 100644 --- a/src/com/android/launcher2/CustomizePagedView.java +++ b/src/com/android/launcher2/CustomizePagedView.java @@ -172,12 +172,12 @@ public class CustomizePagedView extends PagedViewWithDraggableItems final Resources r = context.getResources(); setDragSlopeThreshold( r.getInteger(R.integer.config_customizationDrawerDragSlopeThreshold) / 100.0f); - mMinPageWidth = r.getDimensionPixelSize(R.dimen.customization_drawer_content_min_width); // Create a dummy page and set it up to find out the content width (used by our parent) PagedViewCellLayout layout = new PagedViewCellLayout(getContext()); setupPage(layout); mPageContentWidth = layout.getContentWidth(); + mMinPageWidth = layout.getWidthBeforeFirstLayout(); setVisibility(View.GONE); setSoundEffectsEnabled(false); @@ -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<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 in the delta direction. + * @param delta either -1 or 1 depending on the direction we want to search + */ + private static View findIndexOfBubbleTextView(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) { + return newV; + } + newI += delta; + } + return null; + } + private static View getBubbleTextViewInDirection(CellLayout layout, ViewGroup parent, int i, + int delta) { + final ArrayList<View> views = getCellLayoutChildrenSortedSpatially(layout, parent); + return findIndexOfBubbleTextView(views, i, delta); + } + private static View getBubbleTextViewInDirection(CellLayout layout, ViewGroup parent, View v, + int delta) { + final ArrayList<View> 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<View> 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 bb58081ce..60cab8ecb 100644 --- a/src/com/android/launcher2/Launcher.java +++ b/src/com/android/launcher2/Launcher.java @@ -205,7 +205,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; @@ -769,8 +769,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) { @@ -958,7 +960,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()) { @@ -1007,10 +1009,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 @@ -2575,7 +2582,6 @@ public final class Launcher extends Activity private void showAndEnableToolbarButton(View button) { button.setVisibility(View.VISIBLE); - button.setFocusable(true); button.setClickable(true); } @@ -2586,7 +2592,6 @@ public final class Launcher extends Activity } private void disableToolbarButton(View button) { - button.setFocusable(false); button.setClickable(false); } @@ -2694,7 +2699,7 @@ public final class Launcher extends Activity * of the screen. * @param toState The state to zoom out to. Must be ALL_APPS or CUSTOMIZE. */ - private void cameraZoomOut(State toState, boolean animated) { + private void cameraZoomOut(State toState, boolean animated, boolean springLoaded) { final Resources res = getResources(); final boolean toAllApps = (toState == State.ALL_APPS); @@ -2714,7 +2719,15 @@ public final class Launcher extends Activity setPivotsForZoom(toView, toState, scale); if (toAllApps) { - mWorkspace.shrink(ShrinkState.BOTTOM_HIDDEN, animated); + if (!springLoaded) { + mWorkspace.shrink(ShrinkState.BOTTOM_HIDDEN, animated); + + // Everytime we launch into AllApps, we reset the successful drop flag which + // controls when it should hide/show the mini workspaces + mAllAppsPagedView.resetSuccessfulDropFlag(); + } else { + mWorkspace.shrink(ShrinkState.BOTTOM_VISIBLE, animated); + } } else { mWorkspace.shrink(ShrinkState.TOP, animated); } @@ -2806,10 +2819,6 @@ public final class Launcher extends Activity * @param fromState The current state (must be ALL_APPS or CUSTOMIZE). * @param animated If true, the transition will be animated. */ - private void cameraZoomIn(State fromState, boolean animated) { - cameraZoomIn(fromState, animated, false); - } - private void cameraZoomIn(State fromState, boolean animated, boolean springLoaded) { Resources res = getResources(); final boolean fromAllApps = (fromState == State.ALL_APPS); @@ -2901,7 +2910,7 @@ public final class Launcher extends Activity } if (LauncherApplication.isScreenXLarge()) { - cameraZoomOut(State.ALL_APPS, animated); + cameraZoomOut(State.ALL_APPS, animated, false); } else { mAllAppsGrid.zoom(1.0f, animated); } @@ -2955,11 +2964,11 @@ public final class Launcher extends Activity void enterSpringLoadedDragMode(CellLayout layout) { mWorkspace.enterSpringLoadedDragMode(layout); if (mState == State.ALL_APPS) { - cameraZoomIn(State.ALL_APPS, true, true); mState = State.ALL_APPS_SPRING_LOADED; + cameraZoomIn(State.ALL_APPS, true, true); } else if (mState == State.CUSTOMIZE) { - cameraZoomIn(State.CUSTOMIZE, true, true); mState = State.CUSTOMIZE_SPRING_LOADED; + cameraZoomIn(State.CUSTOMIZE, true, true); }/* else { // we're already in spring loaded mode; don't do anything }*/ @@ -2968,11 +2977,11 @@ public final class Launcher extends Activity void exitSpringLoadedDragMode() { if (mState == State.ALL_APPS_SPRING_LOADED) { mWorkspace.exitSpringLoadedDragMode(Workspace.ShrinkState.BOTTOM_VISIBLE); - cameraZoomOut(State.ALL_APPS, true); + cameraZoomOut(State.ALL_APPS, true, true); mState = State.ALL_APPS; } else if (mState == State.CUSTOMIZE_SPRING_LOADED) { mWorkspace.exitSpringLoadedDragMode(Workspace.ShrinkState.TOP); - cameraZoomOut(State.CUSTOMIZE, true); + cameraZoomOut(State.CUSTOMIZE, true, true); mState = State.CUSTOMIZE; }/* else { // we're not in spring loaded mode; don't do anything @@ -3022,7 +3031,7 @@ public final class Launcher extends Activity if (mState == State.ALL_APPS || mState == State.ALL_APPS_SPRING_LOADED) { mWorkspace.setVisibility(View.VISIBLE); if (LauncherApplication.isScreenXLarge()) { - cameraZoomIn(State.ALL_APPS, animated); + cameraZoomIn(State.ALL_APPS, animated, false); } else { mAllAppsGrid.zoom(0.0f, animated); } @@ -3045,7 +3054,7 @@ public final class Launcher extends Activity return; } - cameraZoomOut(State.CUSTOMIZE, animated); + cameraZoomOut(State.CUSTOMIZE, animated, false); // Change the state *after* we've called all the transition code mState = State.CUSTOMIZE; @@ -3058,7 +3067,7 @@ public final class Launcher extends Activity // Hide the customization drawer (only exists in x-large configuration) void hideCustomizationDrawer(boolean animated) { if (mState == State.CUSTOMIZE || mState == State.CUSTOMIZE_SPRING_LOADED) { - cameraZoomIn(State.CUSTOMIZE, animated); + cameraZoomIn(State.CUSTOMIZE, animated, false); } } @@ -3141,14 +3150,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); } } } @@ -3159,13 +3174,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 9a0b79c2f..9eae64758 100644 --- a/src/com/android/launcher2/PagedView.java +++ b/src/com/android/launcher2/PagedView.java @@ -110,6 +110,7 @@ public abstract class PagedView extends ViewGroup { protected int mPageLayoutPaddingRight; protected int mPageLayoutWidthGap; protected int mPageLayoutHeightGap; + protected int mPageLayoutMaxHeight; protected int mCellCountX; protected int mCellCountY; protected boolean mCenterPagesVertically; @@ -189,6 +190,8 @@ public abstract class PagedView extends ViewGroup { R.styleable.PagedView_pageLayoutWidthGap, -1); mPageLayoutHeightGap = a.getDimensionPixelSize( R.styleable.PagedView_pageLayoutHeightGap, -1); + mPageLayoutMaxHeight = a.getDimensionPixelSize( + R.styleable.PagedView_pageLayoutMaxHeight, -1); a.recycle(); setHapticFeedbackEnabled(false); @@ -388,6 +391,10 @@ public abstract class PagedView extends ViewGroup { final int verticalPadding = mPaddingTop + mPaddingBottom; + if (mPageLayoutMaxHeight != -1) { + heightSize = Math.min(mPageLayoutMaxHeight, heightSize); + } + // The children are given the same width and height as the workspace // unless they were set to WRAP_CONTENT final int childCount = getChildCount(); @@ -1159,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 53657e5cb..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 @@ -247,9 +255,12 @@ public class PagedViewCellLayout extends ViewGroup implements Page { // Return the distance from the left edge of the content of the leftmost icon to // the right edge of the content of the rightmost icon - // icons are centered within cells, find out how much offset that accounts for - int iconHorizontalOffset = (mCellWidth - Utilities.getIconContentSize()); - return mCellCountX * mCellWidth + (mCellCountX - 1) * mWidthGap - iconHorizontalOffset; + // icons are centered within cells, find out how much padding that accounts for + return getWidthBeforeFirstLayout() - (mCellWidth - Utilities.getIconContentSize()); + } + + int getWidthBeforeFirstLayout() { + return mCellCountX * mCellWidth + (mCellCountX - 1) * mWidthGap; } @Override 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); } @@ -245,6 +244,18 @@ public class PagedViewIcon extends CachedTextView implements Checkable { } @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; @@ -228,6 +229,18 @@ public class PagedViewWidget extends LinearLayout implements Checkable { } @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) { super.onDraw(canvas); diff --git a/src/com/android/launcher2/ShortcutsAdapter.java b/src/com/android/launcher2/ShortcutsAdapter.java index 93c500a44..de73c3e3a 100644 --- a/src/com/android/launcher2/ShortcutsAdapter.java +++ b/src/com/android/launcher2/ShortcutsAdapter.java @@ -30,7 +30,7 @@ import com.android.launcher.R; /** * GridView adapter to show the list of applications and shortcuts */ -public class ShortcutsAdapter extends ArrayAdapter<ShortcutInfo> { +public class ShortcutsAdapter extends ArrayAdapter<ShortcutInfo> { private final LayoutInflater mInflater; private final IconCache mIconCache; 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 324942c60..66f1ceb9c 100644 --- a/src/com/android/launcher2/Workspace.java +++ b/src/com/android/launcher2/Workspace.java @@ -342,58 +342,41 @@ public class Workspace extends SmoothPagedView } } - @Override - public void addView(View child, int index, LayoutParams params) { + private void onAddView(View child) { if (!(child instanceof CellLayout)) { throw new IllegalArgumentException("A Workspace can only have CellLayout children."); } ((CellLayout) child).setOnInterceptTouchListener(this); child.setOnClickListener(this); child.setClickable(true); + } + @Override + public void addView(View child, int index, LayoutParams params) { + onAddView(child); super.addView(child, index, params); } @Override public void addView(View child) { - if (!(child instanceof CellLayout)) { - throw new IllegalArgumentException("A Workspace can only have CellLayout children."); - } - ((CellLayout) child).setOnInterceptTouchListener(this); - child.setOnClickListener(this); - child.setClickable(true); + onAddView(child); super.addView(child); } @Override public void addView(View child, int index) { - if (!(child instanceof CellLayout)) { - throw new IllegalArgumentException("A Workspace can only have CellLayout children."); - } - ((CellLayout) child).setOnInterceptTouchListener(this); - child.setOnClickListener(this); - child.setClickable(true); + onAddView(child); super.addView(child, index); } @Override public void addView(View child, int width, int height) { - if (!(child instanceof CellLayout)) { - throw new IllegalArgumentException("A Workspace can only have CellLayout children."); - } - ((CellLayout) child).setOnInterceptTouchListener(this); - child.setOnClickListener(this); - child.setClickable(true); + onAddView(child); super.addView(child, width, height); } @Override public void addView(View child, LayoutParams params) { - if (!(child instanceof CellLayout)) { - throw new IllegalArgumentException("A Workspace can only have CellLayout children."); - } - ((CellLayout) child).setOnInterceptTouchListener(this); - child.setOnClickListener(this); - child.setClickable(true); + onAddView(child); super.addView(child, params); } @@ -1254,6 +1237,14 @@ public class Workspace extends SmoothPagedView } @Override + public int getDescendantFocusability() { + if (mIsSmall) { + return ViewGroup.FOCUS_BLOCK_DESCENDANTS; + } + return super.getDescendantFocusability(); + } + + @Override public void addFocusables(ArrayList<View> views, int direction, int focusableMode) { if (!mLauncher.isAllAppsVisible()) { final Folder openFolder = getOpenFolder(); @@ -1402,14 +1393,6 @@ public class Workspace extends SmoothPagedView // we use this to shrink the workspace for the all apps view and the customize view public void shrink(ShrinkState shrinkState, boolean animated) { - // In the launcher interaction model, we're never in the state where we're shrunken and - // visible in the bottom of the screen, and then want to fade to being invisible. - // After spring loaded mode ends, this method was getting called twice, the first time - // with BOTTOM_VISIBLE (what we want) and a second time with BOTTOM_INVISIBLE (not - // what we want). As a temporary solution, we just change the second call to BOTTOM_VISIBLE - if (mIsSmall && mShrinkState == ShrinkState.BOTTOM_VISIBLE) { - shrinkState = ShrinkState.BOTTOM_VISIBLE; - } if (mFirstLayout) { // (mFirstLayout == "first layout has not happened yet") // if we get a call to shrink() as part of our initialization (for example, if |