Fixing focus issues in Launcher (keyboard support).

Change-Id: Ieafd713393daf5628f229a66441bd3ed293245da
This commit is contained in:
Winson Chung 2011-04-13 11:27:36 -07:00
parent 721a06bb65
commit 97d85d23b0
31 changed files with 1163 additions and 93 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

View File

@ -15,6 +15,7 @@
-->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_focused="true" android:drawable="@drawable/focused_bg" />
<item android:state_pressed="true" android:drawable="@drawable/home_press" />
<item android:drawable="@android:color/transparent" />
</selector>

View File

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2010 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.
-->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_focused="true" android:drawable="@drawable/focused_bg" />
</selector>

View File

@ -15,4 +15,6 @@
-->
<com.android.launcher2.BubbleTextView xmlns:android="http://schemas.android.com/apk/res/android"
style="@style/WorkspaceIcon.Landscape" />
style="@style/WorkspaceIcon.Landscape"
android:focusable="true"
android:background="@drawable/focusable_view_bg" />

View File

@ -32,4 +32,5 @@
android:drawablePadding="0dip"
android:maxLines="2"
android:fadingEdge="horizontal" />
android:fadingEdge="horizontal"
android:focusable="false" />

View File

@ -21,9 +21,12 @@
launcher:blurColor="#FF6B8CF0"
launcher:outlineColor="#FF8CD2FF"
style="@style/WorkspaceIcon.AllApps"
android:id="@+id/application_icon"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center_horizontal"
style="@style/WorkspaceIcon.AllApps" />
android:focusable="true"
android:background="@drawable/focusable_view_bg" />

View File

@ -32,7 +32,7 @@
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:background="@drawable/tab_unselected_holo">
<TabWidget
<com.android.launcher2.FocusOnlyTabWidget
android:id="@android:id/tabs"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
@ -50,7 +50,9 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:visibility="invisible"/>
android:visibility="invisible"
android:background="@drawable/focusable_view_bg"
android:focusable="true" />
<TextView
android:id="@+id/market_button"
android:layout_width="wrap_content"
@ -64,7 +66,9 @@
android:shadowColor="@color/workspace_all_apps_and_delete_zone_text_shadow_color"
android:shadowDx="0.0"
android:shadowDy="0.0"
android:shadowRadius="2.0" />
android:shadowRadius="2.0"
android:background="@drawable/focusable_view_bg"
android:focusable="true" />
</FrameLayout>
<com.android.launcher2.DeleteZone
android:id="@+id/all_apps_delete_zone"
@ -84,7 +88,10 @@
android:shadowColor="@color/workspace_all_apps_and_delete_zone_text_shadow_color"
android:shadowDx="0.0"
android:shadowDy="0.0"
android:shadowRadius="2.0" />
android:shadowRadius="2.0"
android:background="@drawable/focusable_view_bg"
android:focusable="true" />
</RelativeLayout>
<FrameLayout
android:id="@android:id/tabcontent"

View File

@ -15,31 +15,33 @@
-->
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:launcher="http://schemas.android.com/apk/res/com.android.launcher">
xmlns:launcher="http://schemas.android.com/apk/res/com.android.launcher"
android:focusable="false">
<!-- Global search icon -->
<ImageView
android:id="@+id/search_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_alignParentLeft="true"
android:paddingLeft="@dimen/toolbar_button_horizontal_padding"
android:paddingRight="@dimen/toolbar_button_horizontal_padding"
android:paddingTop="@dimen/toolbar_button_vertical_padding"
android:paddingBottom="@dimen/toolbar_button_vertical_padding"
android:src="@drawable/ic_generic_search"
android:background="@drawable/button_bg"
android:onClick="onClickSearchButton"
android:focusable="true"
android:clickable="true" />
android:id="@+id/search_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_alignParentLeft="true"
android:paddingLeft="@dimen/toolbar_button_horizontal_padding"
android:paddingRight="@dimen/toolbar_button_horizontal_padding"
android:paddingTop="@dimen/toolbar_button_vertical_padding"
android:paddingBottom="@dimen/toolbar_button_vertical_padding"
android:src="@drawable/ic_generic_search"
android:background="@drawable/button_bg"
android:onClick="onClickSearchButton"
android:focusable="true"
android:clickable="true" />
<ImageView
android:id="@+id/search_divider"
android:src="@drawable/divider_launcher_holo"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_toRightOf="@id/search_button"
android:layout_toRightOf="@+id/search_button"
android:paddingTop="@dimen/toolbar_button_vertical_padding"
android:paddingBottom="@dimen/toolbar_button_vertical_padding"
@ -52,7 +54,7 @@
android:id="@+id/voice_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_toRightOf="@id/search_divider"
android:layout_toRightOf="@+id/search_divider"
android:paddingLeft="@dimen/toolbar_button_horizontal_padding"
android:paddingRight="@dimen/toolbar_button_horizontal_padding"
android:paddingTop="@dimen/toolbar_button_vertical_padding"
@ -60,35 +62,11 @@
android:src="@drawable/ic_voice_search"
android:background="@drawable/button_bg"
android:onClick="onClickVoiceButton"
android:focusable="true"
android:clickable="true"/>
<ImageView
android:id="@+id/configure_button"
android:src="@drawable/ic_home_add_holo_dark"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_alignParentRight="true"
android:paddingLeft="@dimen/toolbar_button_horizontal_padding"
android:paddingRight="@dimen/toolbar_button_horizontal_padding"
android:paddingTop="@dimen/toolbar_button_vertical_padding"
android:paddingBottom="@dimen/toolbar_button_vertical_padding"
android:background="@drawable/button_bg"
android:focusable="true"
android:clickable="true" />
<ImageView
android:id="@+id/divider"
android:src="@drawable/divider_launcher_holo"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_toLeftOf="@id/configure_button"
android:paddingTop="@dimen/toolbar_button_vertical_padding"
android:paddingBottom="@dimen/toolbar_button_vertical_padding"
android:focusable="false"
android:clickable="true" />
<!-- AllApps icon -->
<com.android.launcher2.StrokedTextView
android:id="@+id/all_apps_button"
android:text="@string/all_apps_button_label"
@ -96,12 +74,12 @@
android:drawableLeft="@drawable/ic_home_all_apps_holo_dark"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_toLeftOf="@id/divider"
android:layout_toLeftOf="@+id/all_apps_divider"
android:paddingLeft="@dimen/toolbar_button_horizontal_padding"
android:paddingRight="@dimen/toolbar_button_horizontal_padding"
android:paddingTop="@dimen/all_apps_button_vertical_padding"
android:paddingBottom="@dimen/all_apps_button_vertical_padding"
android:background="@drawable/button_bg"
android:background="@drawable/button_bg"
android:gravity="center_horizontal|center_vertical"
android:textColor="#CCFFFFFF"
@ -118,15 +96,36 @@
android:focusable="true"
android:clickable="true" />
<ImageView
android:id="@+id/divider_during_drag"
android:id="@+id/all_apps_divider"
android:src="@drawable/divider_launcher_holo"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_toLeftOf="@id/configure_button"
android:layout_toLeftOf="@+id/configure_button"
android:paddingTop="@dimen/toolbar_button_vertical_padding"
android:paddingBottom="@dimen/toolbar_button_vertical_padding"
android:visibility="gone" />
android:focusable="false"
android:clickable="true" />
<!-- Customize icon -->
<ImageView
android:id="@+id/configure_button"
android:src="@drawable/ic_home_add_holo_dark"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_alignParentRight="true"
android:paddingLeft="@dimen/toolbar_button_horizontal_padding"
android:paddingRight="@dimen/toolbar_button_horizontal_padding"
android:paddingTop="@dimen/toolbar_button_vertical_padding"
android:paddingBottom="@dimen/toolbar_button_vertical_padding"
android:background="@drawable/button_bg"
android:focusable="true"
/>
<!-- Delete icon -->
<com.android.launcher2.DeleteZone
android:id="@+id/delete_zone"
android:text="@string/delete_zone_label_workspace"
@ -134,12 +133,12 @@
android:drawableLeft="@drawable/delete_zone_selector"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignRight="@id/configure_button"
android:layout_alignRight="@+id/configure_button"
android:paddingLeft="@dimen/toolbar_button_horizontal_padding"
android:paddingRight="@dimen/toolbar_button_horizontal_padding"
android:paddingTop="@dimen/toolbar_button_vertical_padding"
android:paddingBottom="@dimen/toolbar_button_vertical_padding"
android:background="@drawable/button_bg"
android:background="@drawable/button_bg"
android:gravity="center_horizontal|center_vertical"
android:textColor="@color/workspace_all_apps_and_delete_zone_text_color"
@ -150,5 +149,8 @@
android:shadowRadius="2.0"
android:visibility="gone"
launcher:direction="horizontal" />
</RelativeLayout>
launcher:direction="horizontal"
android:focusable="true"
/>
</RelativeLayout>

View File

@ -22,7 +22,7 @@
android:layout_height="match_parent">
<!-- The layout_width of this RelativeLayout gets overwritten in
CustomizeTrayTabHost.onFinishInflate -->
<TabWidget
<com.android.launcher2.FocusOnlyTabWidget
android:id="@android:id/tabs"
android:layout_width="wrap_content"
android:layout_height="wrap_content"

View File

@ -25,4 +25,7 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
style="@style/WorkspaceIcon.Landscape" />
style="@style/WorkspaceIcon.Landscape"
android:background="@drawable/focusable_view_bg"
android:focusable="true" />

View File

@ -25,7 +25,10 @@
android:paddingBottom="50dp"
launcher:blurColor="#FF6B8CF0"
launcher:outlineColor="#FF8CD2FF">
launcher:outlineColor="#FF8CD2FF"
android:background="@drawable/focusable_view_bg"
android:focusable="true">
<!-- The preview image for the wallpaper. -->
<ImageView

View File

@ -25,7 +25,10 @@
android:paddingBottom="50dp"
launcher:blurColor="#FF6B8CF0"
launcher:outlineColor="#FF8CD2FF">
launcher:outlineColor="#FF8CD2FF"
android:background="@drawable/focusable_view_bg"
android:focusable="true">
<!-- The icon of the widget. -->
<ImageView

View File

@ -20,7 +20,8 @@
android:id="@+id/drag_layer"
android:layout_width="match_parent"
android:layout_height="match_parent">
android:layout_height="match_parent"
android:focusable="false">
<!-- The workspace contains 5 screens of cells -->
<com.android.launcher2.Workspace
@ -41,6 +42,12 @@
<include android:id="@+id/cell5" layout="@layout/workspace_screen" />
</com.android.launcher2.Workspace>
<include layout="@layout/button_bar"
android:id="@+id/all_apps_button_cluster"
android:layout_width="fill_parent"
android:layout_height="?android:attr/actionBarSize"
android:layout_gravity="top" />
<include
layout="@layout/all_apps_tabbed"
android:id="@+id/all_apps_view"
@ -48,12 +55,6 @@
android:layout_height="match_parent"
android:layout_gravity="top" />
<include layout="@layout/button_bar"
android:id="@+id/all_apps_button_cluster"
android:layout_width="fill_parent"
android:layout_height="?android:attr/actionBarSize"
android:layout_gravity="top" />
<include layout="@layout/customization_drawer"
android:id="@+id/customization_drawer"
android:layout_width="match_parent"

View File

@ -14,6 +14,6 @@
limitations under the License.
-->
<TextView
<com.android.launcher2.AccessibleTabView
xmlns:android="http://schemas.android.com/apk/res/android"
style="@style/TabIndicator" />

View File

@ -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);
}
}

View File

@ -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();
}
}

View File

@ -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);
}
}

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;
}
}

View File

@ -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;
}
}
}

View File

@ -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);
}
}
}

View File

@ -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;
}
}

View File

@ -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);
}
}

View File

@ -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

View File

@ -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);

View File

@ -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;

View File

@ -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) {

View File

@ -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);
}
}

View File

@ -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<View> views, int direction, int focusableMode) {
if (!mLauncher.isAllAppsVisible()) {