blob: 1a7416a992dfbb45dc5618dd07ae0712c0960920 [file] [log] [blame]
Winson Chung97d85d22011-04-13 11:27:36 -07001/*
2 * Copyright (C) 2011 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.launcher2;
18
Winson Chung4e6a9762011-05-09 11:56:34 -070019import android.content.res.Configuration;
Winson Chung97d85d22011-04-13 11:27:36 -070020import android.view.KeyEvent;
21import android.view.View;
22import android.view.ViewGroup;
23import android.view.ViewParent;
24import android.widget.TabHost;
25import android.widget.TabWidget;
Adam Cohenac56cff2011-09-28 20:45:37 -070026import android.widget.TextView;
Winson Chung97d85d22011-04-13 11:27:36 -070027
28import com.android.launcher.R;
29
Winson Chungfaa13252011-06-13 18:15:54 -070030import java.util.ArrayList;
31import java.util.Collections;
32import java.util.Comparator;
33
Winson Chung97d85d22011-04-13 11:27:36 -070034/**
Winson Chung4d279d92011-07-21 11:46:32 -070035 * A keyboard listener we set on all the workspace icons.
36 */
Adam Cohenac56cff2011-09-28 20:45:37 -070037class IconKeyEventListener implements View.OnKeyListener {
Winson Chung4d279d92011-07-21 11:46:32 -070038 public boolean onKey(View v, int keyCode, KeyEvent event) {
Adam Cohenac56cff2011-09-28 20:45:37 -070039 return FocusHelper.handleIconKeyEvent(v, keyCode, event);
40 }
41}
42
43/**
44 * A keyboard listener we set on all the workspace icons.
45 */
46class FolderKeyEventListener implements View.OnKeyListener {
47 public boolean onKey(View v, int keyCode, KeyEvent event) {
48 return FocusHelper.handleFolderKeyEvent(v, keyCode, event);
Winson Chung4d279d92011-07-21 11:46:32 -070049 }
50}
51
52/**
Winson Chung3d503fb2011-07-13 17:25:49 -070053 * A keyboard listener we set on all the hotseat buttons.
Winson Chung4e6a9762011-05-09 11:56:34 -070054 */
Adam Cohenac56cff2011-09-28 20:45:37 -070055class HotseatIconKeyEventListener implements View.OnKeyListener {
Winson Chung4e6a9762011-05-09 11:56:34 -070056 public boolean onKey(View v, int keyCode, KeyEvent event) {
57 final Configuration configuration = v.getResources().getConfiguration();
Winson Chung3d503fb2011-07-13 17:25:49 -070058 return FocusHelper.handleHotseatButtonKeyEvent(v, keyCode, event, configuration.orientation);
Winson Chung4e6a9762011-05-09 11:56:34 -070059 }
60}
61
62/**
Winson Chungfaa13252011-06-13 18:15:54 -070063 * A keyboard listener we set on the last tab button in AppsCustomize to jump to then
Winson Chung97d85d22011-04-13 11:27:36 -070064 * market icon and vice versa.
65 */
Winson Chungfaa13252011-06-13 18:15:54 -070066class AppsCustomizeTabKeyEventListener implements View.OnKeyListener {
Winson Chung97d85d22011-04-13 11:27:36 -070067 public boolean onKey(View v, int keyCode, KeyEvent event) {
Winson Chungfaa13252011-06-13 18:15:54 -070068 return FocusHelper.handleAppsCustomizeTabKeyEvent(v, keyCode, event);
Winson Chung97d85d22011-04-13 11:27:36 -070069 }
70}
71
72public class FocusHelper {
73 /**
74 * Private helper to get the parent TabHost in the view hiearchy.
75 */
76 private static TabHost findTabHostParent(View v) {
77 ViewParent p = v.getParent();
78 while (p != null && !(p instanceof TabHost)) {
79 p = p.getParent();
80 }
81 return (TabHost) p;
82 }
83
84 /**
Winson Chungfaa13252011-06-13 18:15:54 -070085 * Handles key events in a AppsCustomize tab between the last tab view and the shop button.
Winson Chung97d85d22011-04-13 11:27:36 -070086 */
Winson Chungfaa13252011-06-13 18:15:54 -070087 static boolean handleAppsCustomizeTabKeyEvent(View v, int keyCode, KeyEvent e) {
Winson Chung97d85d22011-04-13 11:27:36 -070088 final TabHost tabHost = findTabHostParent(v);
89 final ViewGroup contents = (ViewGroup)
90 tabHost.findViewById(com.android.internal.R.id.tabcontent);
91 final View shop = tabHost.findViewById(R.id.market_button);
92
93 final int action = e.getAction();
94 final boolean handleKeyEvent = (action != KeyEvent.ACTION_UP);
95 boolean wasHandled = false;
96 switch (keyCode) {
97 case KeyEvent.KEYCODE_DPAD_RIGHT:
98 if (handleKeyEvent) {
99 // Select the shop button if we aren't on it
100 if (v != shop) {
101 shop.requestFocus();
102 }
103 }
104 wasHandled = true;
105 break;
106 case KeyEvent.KEYCODE_DPAD_DOWN:
107 if (handleKeyEvent) {
108 // Select the content view (down is handled by the tab key handler otherwise)
109 if (v == shop) {
110 contents.requestFocus();
111 wasHandled = true;
112 }
113 }
114 break;
115 default: break;
116 }
117 return wasHandled;
118 }
119
120 /**
121 * Private helper to determine whether a view is visible.
122 */
123 private static boolean isVisible(View v) {
124 return v.getVisibility() == View.VISIBLE;
125 }
126
127 /**
Adam Cohenae4f1552011-10-20 00:15:42 -0700128 * Returns the Viewgroup containing page contents for the page at the index specified.
129 */
130 private static ViewGroup getAppsCustomizePage(ViewGroup container, int index) {
131 ViewGroup page = (ViewGroup) ((PagedView) container).getPageAt(index);
132 if (page instanceof PagedViewCellLayout) {
133 // There are two layers, a PagedViewCellLayout and PagedViewCellLayoutChildren
134 page = (ViewGroup) page.getChildAt(0);
135 }
136 return page;
137 }
138
139 /**
Winson Chung97d85d22011-04-13 11:27:36 -0700140 * Handles key events in a PageViewExtendedLayout containing PagedViewWidgets.
Winson Chung4e6a9762011-05-09 11:56:34 -0700141 */
142 static boolean handlePagedViewGridLayoutWidgetKeyEvent(PagedViewWidget w, int keyCode,
143 KeyEvent e) {
144
145 final PagedViewGridLayout parent = (PagedViewGridLayout) w.getParent();
Adam Cohenae4f1552011-10-20 00:15:42 -0700146 final PagedView container = (PagedView) parent.getParent();
Winson Chung4e6a9762011-05-09 11:56:34 -0700147 final TabHost tabHost = findTabHostParent(container);
148 final TabWidget tabs = (TabWidget) tabHost.findViewById(com.android.internal.R.id.tabs);
149 final int widgetIndex = parent.indexOfChild(w);
150 final int widgetCount = parent.getChildCount();
Adam Cohenae4f1552011-10-20 00:15:42 -0700151 final int pageIndex = ((PagedView) container).indexToPage(container.indexOfChild(parent));
Winson Chung4e6a9762011-05-09 11:56:34 -0700152 final int pageCount = container.getChildCount();
153 final int cellCountX = parent.getCellCountX();
154 final int cellCountY = parent.getCellCountY();
155 final int x = widgetIndex % cellCountX;
156 final int y = widgetIndex / cellCountX;
157
158 final int action = e.getAction();
159 final boolean handleKeyEvent = (action != KeyEvent.ACTION_UP);
Adam Cohenae4f1552011-10-20 00:15:42 -0700160 ViewGroup newParent = null;
Winson Chung3b0b46a2011-07-28 10:45:09 -0700161 // Now that we load items in the bg asynchronously, we can't just focus
162 // child siblings willy-nilly
163 View child = null;
Winson Chung4e6a9762011-05-09 11:56:34 -0700164 boolean wasHandled = false;
165 switch (keyCode) {
166 case KeyEvent.KEYCODE_DPAD_LEFT:
167 if (handleKeyEvent) {
168 // Select the previous widget or the last widget on the previous page
169 if (widgetIndex > 0) {
170 parent.getChildAt(widgetIndex - 1).requestFocus();
171 } else {
172 if (pageIndex > 0) {
Adam Cohenae4f1552011-10-20 00:15:42 -0700173 newParent = getAppsCustomizePage(container, pageIndex - 1);
174 if (newParent != null) {
175 child = newParent.getChildAt(newParent.getChildCount() - 1);
176 if (child != null) child.requestFocus();
177 }
Winson Chung4e6a9762011-05-09 11:56:34 -0700178 }
179 }
180 }
181 wasHandled = true;
182 break;
183 case KeyEvent.KEYCODE_DPAD_RIGHT:
184 if (handleKeyEvent) {
185 // Select the next widget or the first widget on the next page
186 if (widgetIndex < (widgetCount - 1)) {
187 parent.getChildAt(widgetIndex + 1).requestFocus();
188 } else {
189 if (pageIndex < (pageCount - 1)) {
Adam Cohenae4f1552011-10-20 00:15:42 -0700190 newParent = getAppsCustomizePage(container, pageIndex + 1);
191 if (newParent != null) {
192 child = newParent.getChildAt(0);
193 if (child != null) child.requestFocus();
194 }
Winson Chung4e6a9762011-05-09 11:56:34 -0700195 }
196 }
197 }
198 wasHandled = true;
199 break;
200 case KeyEvent.KEYCODE_DPAD_UP:
201 if (handleKeyEvent) {
202 // Select the closest icon in the previous row, otherwise select the tab bar
203 if (y > 0) {
204 int newWidgetIndex = ((y - 1) * cellCountX) + x;
Winson Chung3b0b46a2011-07-28 10:45:09 -0700205 child = parent.getChildAt(newWidgetIndex);
206 if (child != null) child.requestFocus();
Winson Chung4e6a9762011-05-09 11:56:34 -0700207 } else {
208 tabs.requestFocus();
209 }
210 }
211 wasHandled = true;
212 break;
213 case KeyEvent.KEYCODE_DPAD_DOWN:
214 if (handleKeyEvent) {
215 // Select the closest icon in the previous row, otherwise do nothing
216 if (y < (cellCountY - 1)) {
217 int newWidgetIndex = Math.min(widgetCount - 1, ((y + 1) * cellCountX) + x);
Winson Chung3b0b46a2011-07-28 10:45:09 -0700218 child = parent.getChildAt(newWidgetIndex);
219 if (child != null) child.requestFocus();
Winson Chung4e6a9762011-05-09 11:56:34 -0700220 }
221 }
222 wasHandled = true;
223 break;
224 case KeyEvent.KEYCODE_ENTER:
225 case KeyEvent.KEYCODE_DPAD_CENTER:
226 if (handleKeyEvent) {
227 // Simulate a click on the widget
228 View.OnClickListener clickListener = (View.OnClickListener) container;
229 clickListener.onClick(w);
230 }
231 wasHandled = true;
232 break;
233 case KeyEvent.KEYCODE_PAGE_UP:
234 if (handleKeyEvent) {
235 // Select the first item on the previous page, or the first item on this page
236 // if there is no previous page
237 if (pageIndex > 0) {
Adam Cohenae4f1552011-10-20 00:15:42 -0700238 newParent = getAppsCustomizePage(container, pageIndex - 1);
239 if (newParent != null) {
240 child = newParent.getChildAt(0);
241 }
Winson Chung4e6a9762011-05-09 11:56:34 -0700242 } else {
Winson Chung3b0b46a2011-07-28 10:45:09 -0700243 child = parent.getChildAt(0);
Winson Chung4e6a9762011-05-09 11:56:34 -0700244 }
Winson Chung3b0b46a2011-07-28 10:45:09 -0700245 if (child != null) child.requestFocus();
Winson Chung4e6a9762011-05-09 11:56:34 -0700246 }
247 wasHandled = true;
248 break;
249 case KeyEvent.KEYCODE_PAGE_DOWN:
250 if (handleKeyEvent) {
251 // Select the first item on the next page, or the last item on this page
252 // if there is no next page
253 if (pageIndex < (pageCount - 1)) {
Adam Cohenae4f1552011-10-20 00:15:42 -0700254 newParent = getAppsCustomizePage(container, pageIndex + 1);
255 if (newParent != null) {
256 child = newParent.getChildAt(0);
257 }
Winson Chung4e6a9762011-05-09 11:56:34 -0700258 } else {
Winson Chung3b0b46a2011-07-28 10:45:09 -0700259 child = parent.getChildAt(widgetCount - 1);
Winson Chung4e6a9762011-05-09 11:56:34 -0700260 }
Winson Chung3b0b46a2011-07-28 10:45:09 -0700261 if (child != null) child.requestFocus();
Winson Chung4e6a9762011-05-09 11:56:34 -0700262 }
263 wasHandled = true;
264 break;
265 case KeyEvent.KEYCODE_MOVE_HOME:
266 if (handleKeyEvent) {
267 // Select the first item on this page
Winson Chung3b0b46a2011-07-28 10:45:09 -0700268 child = parent.getChildAt(0);
269 if (child != null) child.requestFocus();
Winson Chung4e6a9762011-05-09 11:56:34 -0700270 }
271 wasHandled = true;
272 break;
273 case KeyEvent.KEYCODE_MOVE_END:
274 if (handleKeyEvent) {
275 // Select the last item on this page
276 parent.getChildAt(widgetCount - 1).requestFocus();
277 }
278 wasHandled = true;
279 break;
280 default: break;
281 }
282 return wasHandled;
283 }
284
285 /**
Winson Chung97d85d22011-04-13 11:27:36 -0700286 * Handles key events in a PageViewCellLayout containing PagedViewIcons.
287 */
Adam Cohenae4f1552011-10-20 00:15:42 -0700288 static boolean handleAppsCustomizeKeyEvent(View v, int keyCode, KeyEvent e) {
289 ViewGroup parentLayout;
290 ViewGroup itemContainer;
291 int countX;
292 int countY;
293 if (v.getParent() instanceof PagedViewCellLayoutChildren) {
294 itemContainer = (ViewGroup) v.getParent();
295 parentLayout = (ViewGroup) itemContainer.getParent();
296 countX = ((PagedViewCellLayout) parentLayout).getCellCountX();
297 countY = ((PagedViewCellLayout) parentLayout).getCellCountY();
298 } else {
299 itemContainer = parentLayout = (ViewGroup) v.getParent();
300 countX = ((PagedViewGridLayout) parentLayout).getCellCountX();
301 countY = ((PagedViewGridLayout) parentLayout).getCellCountY();
302 }
303
Winson Chung97d85d22011-04-13 11:27:36 -0700304 // Note we have an extra parent because of the
305 // PagedViewCellLayout/PagedViewCellLayoutChildren relationship
Adam Cohenae4f1552011-10-20 00:15:42 -0700306 final PagedView container = (PagedView) parentLayout.getParent();
Winson Chung97d85d22011-04-13 11:27:36 -0700307 final TabHost tabHost = findTabHostParent(container);
308 final TabWidget tabs = (TabWidget) tabHost.findViewById(com.android.internal.R.id.tabs);
Adam Cohenae4f1552011-10-20 00:15:42 -0700309 final int iconIndex = itemContainer.indexOfChild(v);
310 final int itemCount = itemContainer.getChildCount();
311 final int pageIndex = ((PagedView) container).indexToPage(container.indexOfChild(parentLayout));
Winson Chung97d85d22011-04-13 11:27:36 -0700312 final int pageCount = container.getChildCount();
Adam Cohenae4f1552011-10-20 00:15:42 -0700313
314 final int x = iconIndex % countX;
315 final int y = iconIndex / countX;
Winson Chung97d85d22011-04-13 11:27:36 -0700316
317 final int action = e.getAction();
318 final boolean handleKeyEvent = (action != KeyEvent.ACTION_UP);
Adam Cohenae4f1552011-10-20 00:15:42 -0700319 ViewGroup newParent = null;
Winson Chung90576b52011-09-27 17:45:27 -0700320 // Side pages do not always load synchronously, so check before focusing child siblings
321 // willy-nilly
322 View child = null;
Winson Chung97d85d22011-04-13 11:27:36 -0700323 boolean wasHandled = false;
324 switch (keyCode) {
325 case KeyEvent.KEYCODE_DPAD_LEFT:
326 if (handleKeyEvent) {
327 // Select the previous icon or the last icon on the previous page
Winson Chung90576b52011-09-27 17:45:27 -0700328 if (iconIndex > 0) {
Adam Cohenae4f1552011-10-20 00:15:42 -0700329 itemContainer.getChildAt(iconIndex - 1).requestFocus();
Winson Chung97d85d22011-04-13 11:27:36 -0700330 } else {
331 if (pageIndex > 0) {
Adam Cohenae4f1552011-10-20 00:15:42 -0700332 newParent = getAppsCustomizePage(container, pageIndex - 1);
Winson Chung90576b52011-09-27 17:45:27 -0700333 if (newParent != null) {
Adam Cohenae4f1552011-10-20 00:15:42 -0700334 container.snapToPage(pageIndex - 1);
Winson Chung90576b52011-09-27 17:45:27 -0700335 child = newParent.getChildAt(newParent.getChildCount() - 1);
336 if (child != null) child.requestFocus();
337 }
Winson Chung97d85d22011-04-13 11:27:36 -0700338 }
339 }
340 }
341 wasHandled = true;
342 break;
343 case KeyEvent.KEYCODE_DPAD_RIGHT:
344 if (handleKeyEvent) {
345 // Select the next icon or the first icon on the next page
Adam Cohenae4f1552011-10-20 00:15:42 -0700346 if (iconIndex < (itemCount - 1)) {
347 itemContainer.getChildAt(iconIndex + 1).requestFocus();
Winson Chung97d85d22011-04-13 11:27:36 -0700348 } else {
349 if (pageIndex < (pageCount - 1)) {
Adam Cohenae4f1552011-10-20 00:15:42 -0700350 newParent = getAppsCustomizePage(container, pageIndex + 1);
Winson Chung90576b52011-09-27 17:45:27 -0700351 if (newParent != null) {
Adam Cohenae4f1552011-10-20 00:15:42 -0700352 container.snapToPage(pageIndex + 1);
Winson Chung90576b52011-09-27 17:45:27 -0700353 child = newParent.getChildAt(0);
354 if (child != null) child.requestFocus();
355 }
Winson Chung97d85d22011-04-13 11:27:36 -0700356 }
357 }
358 }
359 wasHandled = true;
360 break;
361 case KeyEvent.KEYCODE_DPAD_UP:
362 if (handleKeyEvent) {
363 // Select the closest icon in the previous row, otherwise select the tab bar
364 if (y > 0) {
Adam Cohenae4f1552011-10-20 00:15:42 -0700365 int newiconIndex = ((y - 1) * countX) + x;
366 itemContainer.getChildAt(newiconIndex).requestFocus();
Winson Chung97d85d22011-04-13 11:27:36 -0700367 } else {
368 tabs.requestFocus();
369 }
370 }
371 wasHandled = true;
372 break;
373 case KeyEvent.KEYCODE_DPAD_DOWN:
374 if (handleKeyEvent) {
375 // Select the closest icon in the previous row, otherwise do nothing
Adam Cohenae4f1552011-10-20 00:15:42 -0700376 if (y < (countY - 1)) {
377 int newiconIndex = Math.min(itemCount - 1, ((y + 1) * countX) + x);
378 itemContainer.getChildAt(newiconIndex).requestFocus();
Winson Chung97d85d22011-04-13 11:27:36 -0700379 }
380 }
381 wasHandled = true;
382 break;
383 case KeyEvent.KEYCODE_ENTER:
384 case KeyEvent.KEYCODE_DPAD_CENTER:
385 if (handleKeyEvent) {
386 // Simulate a click on the icon
387 View.OnClickListener clickListener = (View.OnClickListener) container;
388 clickListener.onClick(v);
389 }
390 wasHandled = true;
391 break;
392 case KeyEvent.KEYCODE_PAGE_UP:
393 if (handleKeyEvent) {
394 // Select the first icon on the previous page, or the first icon on this page
395 // if there is no previous page
396 if (pageIndex > 0) {
Adam Cohenae4f1552011-10-20 00:15:42 -0700397 newParent = getAppsCustomizePage(container, pageIndex - 1);
Winson Chung90576b52011-09-27 17:45:27 -0700398 if (newParent != null) {
Adam Cohenae4f1552011-10-20 00:15:42 -0700399 container.snapToPage(pageIndex - 1);
Winson Chung90576b52011-09-27 17:45:27 -0700400 child = newParent.getChildAt(0);
401 if (child != null) child.requestFocus();
402 }
Winson Chung97d85d22011-04-13 11:27:36 -0700403 } else {
Adam Cohenae4f1552011-10-20 00:15:42 -0700404 itemContainer.getChildAt(0).requestFocus();
Winson Chung97d85d22011-04-13 11:27:36 -0700405 }
406 }
407 wasHandled = true;
408 break;
409 case KeyEvent.KEYCODE_PAGE_DOWN:
410 if (handleKeyEvent) {
411 // Select the first icon on the next page, or the last icon on this page
412 // if there is no next page
413 if (pageIndex < (pageCount - 1)) {
Adam Cohenae4f1552011-10-20 00:15:42 -0700414 newParent = getAppsCustomizePage(container, pageIndex + 1);
Winson Chung90576b52011-09-27 17:45:27 -0700415 if (newParent != null) {
Adam Cohenae4f1552011-10-20 00:15:42 -0700416 container.snapToPage(pageIndex + 1);
Winson Chung90576b52011-09-27 17:45:27 -0700417 child = newParent.getChildAt(0);
418 if (child != null) child.requestFocus();
419 }
Winson Chung97d85d22011-04-13 11:27:36 -0700420 } else {
Adam Cohenae4f1552011-10-20 00:15:42 -0700421 itemContainer.getChildAt(itemCount - 1).requestFocus();
Winson Chung97d85d22011-04-13 11:27:36 -0700422 }
423 }
424 wasHandled = true;
425 break;
426 case KeyEvent.KEYCODE_MOVE_HOME:
427 if (handleKeyEvent) {
428 // Select the first icon on this page
Adam Cohenae4f1552011-10-20 00:15:42 -0700429 itemContainer.getChildAt(0).requestFocus();
Winson Chung97d85d22011-04-13 11:27:36 -0700430 }
431 wasHandled = true;
432 break;
433 case KeyEvent.KEYCODE_MOVE_END:
434 if (handleKeyEvent) {
435 // Select the last icon on this page
Adam Cohenae4f1552011-10-20 00:15:42 -0700436 itemContainer.getChildAt(itemCount - 1).requestFocus();
Winson Chung97d85d22011-04-13 11:27:36 -0700437 }
438 wasHandled = true;
439 break;
440 default: break;
441 }
442 return wasHandled;
443 }
444
445 /**
446 * Handles key events in the tab widget.
447 */
448 static boolean handleTabKeyEvent(AccessibleTabView v, int keyCode, KeyEvent e) {
Michael Jurkaa2eb1702011-05-12 14:57:05 -0700449 if (!LauncherApplication.isScreenLarge()) return false;
Winson Chung97d85d22011-04-13 11:27:36 -0700450
451 final FocusOnlyTabWidget parent = (FocusOnlyTabWidget) v.getParent();
452 final TabHost tabHost = findTabHostParent(parent);
453 final ViewGroup contents = (ViewGroup)
454 tabHost.findViewById(com.android.internal.R.id.tabcontent);
455 final int tabCount = parent.getTabCount();
456 final int tabIndex = parent.getChildTabIndex(v);
457
458 final int action = e.getAction();
459 final boolean handleKeyEvent = (action != KeyEvent.ACTION_UP);
460 boolean wasHandled = false;
461 switch (keyCode) {
462 case KeyEvent.KEYCODE_DPAD_LEFT:
463 if (handleKeyEvent) {
464 // Select the previous tab
465 if (tabIndex > 0) {
466 parent.getChildTabViewAt(tabIndex - 1).requestFocus();
467 }
468 }
469 wasHandled = true;
470 break;
471 case KeyEvent.KEYCODE_DPAD_RIGHT:
472 if (handleKeyEvent) {
473 // Select the next tab, or if the last tab has a focus right id, select that
474 if (tabIndex < (tabCount - 1)) {
475 parent.getChildTabViewAt(tabIndex + 1).requestFocus();
476 } else {
477 if (v.getNextFocusRightId() != View.NO_ID) {
478 tabHost.findViewById(v.getNextFocusRightId()).requestFocus();
479 }
480 }
481 }
482 wasHandled = true;
483 break;
484 case KeyEvent.KEYCODE_DPAD_UP:
485 // Do nothing
486 wasHandled = true;
487 break;
488 case KeyEvent.KEYCODE_DPAD_DOWN:
489 if (handleKeyEvent) {
490 // Select the content view
491 contents.requestFocus();
492 }
493 wasHandled = true;
494 break;
495 default: break;
496 }
497 return wasHandled;
498 }
499
500 /**
Winson Chung3d503fb2011-07-13 17:25:49 -0700501 * Handles key events in the workspace hotseat (bottom of the screen).
Winson Chung4e6a9762011-05-09 11:56:34 -0700502 */
Winson Chung3d503fb2011-07-13 17:25:49 -0700503 static boolean handleHotseatButtonKeyEvent(View v, int keyCode, KeyEvent e, int orientation) {
Winson Chung4e6a9762011-05-09 11:56:34 -0700504 final ViewGroup parent = (ViewGroup) v.getParent();
505 final ViewGroup launcher = (ViewGroup) parent.getParent();
506 final Workspace workspace = (Workspace) launcher.findViewById(R.id.workspace);
507 final int buttonIndex = parent.indexOfChild(v);
508 final int buttonCount = parent.getChildCount();
509 final int pageIndex = workspace.getCurrentPage();
Winson Chung4e6a9762011-05-09 11:56:34 -0700510
511 // NOTE: currently we don't special case for the phone UI in different
Winson Chung3d503fb2011-07-13 17:25:49 -0700512 // orientations, even though the hotseat is on the side in landscape mode. This
Winson Chung4e6a9762011-05-09 11:56:34 -0700513 // is to ensure that accessibility consistency is maintained across rotations.
514
515 final int action = e.getAction();
516 final boolean handleKeyEvent = (action != KeyEvent.ACTION_UP);
517 boolean wasHandled = false;
518 switch (keyCode) {
519 case KeyEvent.KEYCODE_DPAD_LEFT:
520 if (handleKeyEvent) {
Winson Chung74608b52011-06-23 13:23:20 -0700521 // Select the previous button, otherwise snap to the previous page
Winson Chung4e6a9762011-05-09 11:56:34 -0700522 if (buttonIndex > 0) {
523 parent.getChildAt(buttonIndex - 1).requestFocus();
524 } else {
Winson Chung74608b52011-06-23 13:23:20 -0700525 workspace.snapToPage(pageIndex - 1);
Winson Chung4e6a9762011-05-09 11:56:34 -0700526 }
527 }
528 wasHandled = true;
529 break;
530 case KeyEvent.KEYCODE_DPAD_RIGHT:
531 if (handleKeyEvent) {
Winson Chung74608b52011-06-23 13:23:20 -0700532 // Select the next button, otherwise snap to the next page
Winson Chung4e6a9762011-05-09 11:56:34 -0700533 if (buttonIndex < (buttonCount - 1)) {
534 parent.getChildAt(buttonIndex + 1).requestFocus();
535 } else {
Winson Chung74608b52011-06-23 13:23:20 -0700536 workspace.snapToPage(pageIndex + 1);
Winson Chung4e6a9762011-05-09 11:56:34 -0700537 }
538 }
539 wasHandled = true;
540 break;
541 case KeyEvent.KEYCODE_DPAD_UP:
542 if (handleKeyEvent) {
543 // Select the first bubble text view in the current page of the workspace
544 final CellLayout layout = (CellLayout) workspace.getChildAt(pageIndex);
545 final CellLayoutChildren children = layout.getChildrenLayout();
Adam Cohenac56cff2011-09-28 20:45:37 -0700546 final View newIcon = getIconInDirection(layout, children, -1, 1);
Winson Chung4e6a9762011-05-09 11:56:34 -0700547 if (newIcon != null) {
548 newIcon.requestFocus();
549 } else {
550 workspace.requestFocus();
551 }
552 }
553 wasHandled = true;
554 break;
555 case KeyEvent.KEYCODE_DPAD_DOWN:
556 // Do nothing
557 wasHandled = true;
558 break;
559 default: break;
560 }
561 return wasHandled;
562 }
563
564 /**
Winson Chung97d85d22011-04-13 11:27:36 -0700565 * Private helper method to get the CellLayoutChildren given a CellLayout index.
566 */
567 private static CellLayoutChildren getCellLayoutChildrenForIndex(ViewGroup container, int i) {
568 ViewGroup parent = (ViewGroup) container.getChildAt(i);
569 return (CellLayoutChildren) parent.getChildAt(0);
570 }
571
572 /**
573 * Private helper method to sort all the CellLayout children in order of their (x,y) spatially
574 * from top left to bottom right.
575 */
576 private static ArrayList<View> getCellLayoutChildrenSortedSpatially(CellLayout layout,
577 ViewGroup parent) {
578 // First we order each the CellLayout children by their x,y coordinates
579 final int cellCountX = layout.getCountX();
580 final int count = parent.getChildCount();
581 ArrayList<View> views = new ArrayList<View>();
582 for (int j = 0; j < count; ++j) {
583 views.add(parent.getChildAt(j));
584 }
585 Collections.sort(views, new Comparator<View>() {
586 @Override
587 public int compare(View lhs, View rhs) {
588 CellLayout.LayoutParams llp = (CellLayout.LayoutParams) lhs.getLayoutParams();
589 CellLayout.LayoutParams rlp = (CellLayout.LayoutParams) rhs.getLayoutParams();
590 int lvIndex = (llp.cellY * cellCountX) + llp.cellX;
591 int rvIndex = (rlp.cellY * cellCountX) + rlp.cellX;
592 return lvIndex - rvIndex;
593 }
594 });
595 return views;
596 }
597 /**
Adam Cohenac56cff2011-09-28 20:45:37 -0700598 * Private helper method to find the index of the next BubbleTextView or FolderIcon in the
599 * direction delta.
600 *
Winson Chung97d85d22011-04-13 11:27:36 -0700601 * @param delta either -1 or 1 depending on the direction we want to search
602 */
Adam Cohenac56cff2011-09-28 20:45:37 -0700603 private static View findIndexOfIcon(ArrayList<View> views, int i, int delta) {
Winson Chung97d85d22011-04-13 11:27:36 -0700604 // Then we find the next BubbleTextView offset by delta from i
605 final int count = views.size();
606 int newI = i + delta;
607 while (0 <= newI && newI < count) {
608 View newV = views.get(newI);
Adam Cohenac56cff2011-09-28 20:45:37 -0700609 if (newV instanceof BubbleTextView || newV instanceof FolderIcon) {
Winson Chung97d85d22011-04-13 11:27:36 -0700610 return newV;
611 }
612 newI += delta;
613 }
614 return null;
615 }
Adam Cohenac56cff2011-09-28 20:45:37 -0700616 private static View getIconInDirection(CellLayout layout, ViewGroup parent, int i,
Winson Chung97d85d22011-04-13 11:27:36 -0700617 int delta) {
618 final ArrayList<View> views = getCellLayoutChildrenSortedSpatially(layout, parent);
Adam Cohenac56cff2011-09-28 20:45:37 -0700619 return findIndexOfIcon(views, i, delta);
Winson Chung97d85d22011-04-13 11:27:36 -0700620 }
Adam Cohenac56cff2011-09-28 20:45:37 -0700621 private static View getIconInDirection(CellLayout layout, ViewGroup parent, View v,
Winson Chung97d85d22011-04-13 11:27:36 -0700622 int delta) {
623 final ArrayList<View> views = getCellLayoutChildrenSortedSpatially(layout, parent);
Adam Cohenac56cff2011-09-28 20:45:37 -0700624 return findIndexOfIcon(views, views.indexOf(v), delta);
Winson Chung97d85d22011-04-13 11:27:36 -0700625 }
626 /**
Adam Cohenac56cff2011-09-28 20:45:37 -0700627 * Private helper method to find the next closest BubbleTextView or FolderIcon in the direction
628 * delta on the next line.
629 *
Winson Chung97d85d22011-04-13 11:27:36 -0700630 * @param delta either -1 or 1 depending on the line and direction we want to search
631 */
Adam Cohenac56cff2011-09-28 20:45:37 -0700632 private static View getClosestIconOnLine(CellLayout layout, ViewGroup parent, View v,
Winson Chung97d85d22011-04-13 11:27:36 -0700633 int lineDelta) {
634 final ArrayList<View> views = getCellLayoutChildrenSortedSpatially(layout, parent);
635 final CellLayout.LayoutParams lp = (CellLayout.LayoutParams) v.getLayoutParams();
636 final int cellCountX = layout.getCountX();
637 final int cellCountY = layout.getCountY();
638 final int row = lp.cellY;
639 final int newRow = row + lineDelta;
640 if (0 <= newRow && newRow < cellCountY) {
641 float closestDistance = Float.MAX_VALUE;
642 int closestIndex = -1;
643 int index = views.indexOf(v);
644 int endIndex = (lineDelta < 0) ? -1 : views.size();
645 while (index != endIndex) {
646 View newV = views.get(index);
647 CellLayout.LayoutParams tmpLp = (CellLayout.LayoutParams) newV.getLayoutParams();
648 boolean satisfiesRow = (lineDelta < 0) ? (tmpLp.cellY < row) : (tmpLp.cellY > row);
Adam Cohenac56cff2011-09-28 20:45:37 -0700649 if (satisfiesRow &&
650 (newV instanceof BubbleTextView || newV instanceof FolderIcon)) {
Winson Chung97d85d22011-04-13 11:27:36 -0700651 float tmpDistance = (float) Math.sqrt(Math.pow(tmpLp.cellX - lp.cellX, 2) +
652 Math.pow(tmpLp.cellY - lp.cellY, 2));
653 if (tmpDistance < closestDistance) {
654 closestIndex = index;
655 closestDistance = tmpDistance;
656 }
657 }
658 if (index <= endIndex) {
659 ++index;
660 } else {
661 --index;
662 }
663 }
664 if (closestIndex > -1) {
665 return views.get(closestIndex);
666 }
667 }
668 return null;
669 }
670
671 /**
Adam Cohenac56cff2011-09-28 20:45:37 -0700672 * Handles key events in a Workspace containing.
Winson Chung97d85d22011-04-13 11:27:36 -0700673 */
Adam Cohenac56cff2011-09-28 20:45:37 -0700674 static boolean handleIconKeyEvent(View v, int keyCode, KeyEvent e) {
Winson Chung97d85d22011-04-13 11:27:36 -0700675 CellLayoutChildren parent = (CellLayoutChildren) v.getParent();
676 final CellLayout layout = (CellLayout) parent.getParent();
677 final Workspace workspace = (Workspace) layout.getParent();
678 final ViewGroup launcher = (ViewGroup) workspace.getParent();
Winson Chungfaa13252011-06-13 18:15:54 -0700679 final ViewGroup tabs = (ViewGroup) launcher.findViewById(R.id.qsb_bar);
Winson Chung4d279d92011-07-21 11:46:32 -0700680 final ViewGroup hotseat = (ViewGroup) launcher.findViewById(R.id.hotseat);
Winson Chung97d85d22011-04-13 11:27:36 -0700681 int pageIndex = workspace.indexOfChild(layout);
682 int pageCount = workspace.getChildCount();
683
684 final int action = e.getAction();
685 final boolean handleKeyEvent = (action != KeyEvent.ACTION_UP);
686 boolean wasHandled = false;
687 switch (keyCode) {
688 case KeyEvent.KEYCODE_DPAD_LEFT:
689 if (handleKeyEvent) {
690 // Select the previous icon or the last icon on the previous page if possible
Adam Cohenac56cff2011-09-28 20:45:37 -0700691 View newIcon = getIconInDirection(layout, parent, v, -1);
Winson Chung97d85d22011-04-13 11:27:36 -0700692 if (newIcon != null) {
693 newIcon.requestFocus();
694 } else {
695 if (pageIndex > 0) {
696 parent = getCellLayoutChildrenForIndex(workspace, pageIndex - 1);
Adam Cohenac56cff2011-09-28 20:45:37 -0700697 newIcon = getIconInDirection(layout, parent,
Winson Chung97d85d22011-04-13 11:27:36 -0700698 parent.getChildCount(), -1);
699 if (newIcon != null) {
700 newIcon.requestFocus();
701 } else {
702 // Snap to the previous page
703 workspace.snapToPage(pageIndex - 1);
704 }
705 }
706 }
707 }
708 wasHandled = true;
709 break;
710 case KeyEvent.KEYCODE_DPAD_RIGHT:
711 if (handleKeyEvent) {
712 // Select the next icon or the first icon on the next page if possible
Adam Cohenac56cff2011-09-28 20:45:37 -0700713 View newIcon = getIconInDirection(layout, parent, v, 1);
Winson Chung97d85d22011-04-13 11:27:36 -0700714 if (newIcon != null) {
715 newIcon.requestFocus();
716 } else {
717 if (pageIndex < (pageCount - 1)) {
718 parent = getCellLayoutChildrenForIndex(workspace, pageIndex + 1);
Adam Cohenac56cff2011-09-28 20:45:37 -0700719 newIcon = getIconInDirection(layout, parent, -1, 1);
Winson Chung97d85d22011-04-13 11:27:36 -0700720 if (newIcon != null) {
721 newIcon.requestFocus();
722 } else {
723 // Snap to the next page
724 workspace.snapToPage(pageIndex + 1);
725 }
726 }
727 }
728 }
729 wasHandled = true;
730 break;
731 case KeyEvent.KEYCODE_DPAD_UP:
732 if (handleKeyEvent) {
733 // Select the closest icon in the previous line, otherwise select the tab bar
Adam Cohenac56cff2011-09-28 20:45:37 -0700734 View newIcon = getClosestIconOnLine(layout, parent, v, -1);
Winson Chung97d85d22011-04-13 11:27:36 -0700735 if (newIcon != null) {
736 newIcon.requestFocus();
737 wasHandled = true;
738 } else {
739 tabs.requestFocus();
740 }
741 }
742 break;
743 case KeyEvent.KEYCODE_DPAD_DOWN:
744 if (handleKeyEvent) {
Winson Chung4d279d92011-07-21 11:46:32 -0700745 // Select the closest icon in the next line, otherwise select the button bar
Adam Cohenac56cff2011-09-28 20:45:37 -0700746 View newIcon = getClosestIconOnLine(layout, parent, v, 1);
Winson Chung97d85d22011-04-13 11:27:36 -0700747 if (newIcon != null) {
748 newIcon.requestFocus();
749 wasHandled = true;
Winson Chung4d279d92011-07-21 11:46:32 -0700750 } else if (hotseat != null) {
751 hotseat.requestFocus();
Winson Chung97d85d22011-04-13 11:27:36 -0700752 }
753 }
754 break;
755 case KeyEvent.KEYCODE_PAGE_UP:
756 if (handleKeyEvent) {
757 // Select the first icon on the previous page or the first icon on this page
758 // if there is no previous page
759 if (pageIndex > 0) {
760 parent = getCellLayoutChildrenForIndex(workspace, pageIndex - 1);
Adam Cohenac56cff2011-09-28 20:45:37 -0700761 View newIcon = getIconInDirection(layout, parent, -1, 1);
Winson Chung97d85d22011-04-13 11:27:36 -0700762 if (newIcon != null) {
763 newIcon.requestFocus();
764 } else {
765 // Snap to the previous page
766 workspace.snapToPage(pageIndex - 1);
767 }
768 } else {
Adam Cohenac56cff2011-09-28 20:45:37 -0700769 View newIcon = getIconInDirection(layout, parent, -1, 1);
Winson Chung97d85d22011-04-13 11:27:36 -0700770 if (newIcon != null) {
771 newIcon.requestFocus();
772 }
773 }
774 }
775 wasHandled = true;
776 break;
777 case KeyEvent.KEYCODE_PAGE_DOWN:
778 if (handleKeyEvent) {
779 // Select the first icon on the next page or the last icon on this page
780 // if there is no previous page
781 if (pageIndex < (pageCount - 1)) {
782 parent = getCellLayoutChildrenForIndex(workspace, pageIndex + 1);
Adam Cohenac56cff2011-09-28 20:45:37 -0700783 View newIcon = getIconInDirection(layout, parent, -1, 1);
Winson Chung97d85d22011-04-13 11:27:36 -0700784 if (newIcon != null) {
785 newIcon.requestFocus();
786 } else {
787 // Snap to the next page
788 workspace.snapToPage(pageIndex + 1);
789 }
790 } else {
Adam Cohenac56cff2011-09-28 20:45:37 -0700791 View newIcon = getIconInDirection(layout, parent,
Winson Chung97d85d22011-04-13 11:27:36 -0700792 parent.getChildCount(), -1);
793 if (newIcon != null) {
794 newIcon.requestFocus();
795 }
796 }
797 }
798 wasHandled = true;
799 break;
800 case KeyEvent.KEYCODE_MOVE_HOME:
801 if (handleKeyEvent) {
802 // Select the first icon on this page
Adam Cohenac56cff2011-09-28 20:45:37 -0700803 View newIcon = getIconInDirection(layout, parent, -1, 1);
Winson Chung97d85d22011-04-13 11:27:36 -0700804 if (newIcon != null) {
805 newIcon.requestFocus();
806 }
807 }
808 wasHandled = true;
809 break;
810 case KeyEvent.KEYCODE_MOVE_END:
811 if (handleKeyEvent) {
812 // Select the last icon on this page
Adam Cohenac56cff2011-09-28 20:45:37 -0700813 View newIcon = getIconInDirection(layout, parent,
814 parent.getChildCount(), -1);
815 if (newIcon != null) {
816 newIcon.requestFocus();
817 }
818 }
819 wasHandled = true;
820 break;
821 default: break;
822 }
823 return wasHandled;
824 }
825
826 /**
827 * Handles key events for items in a Folder.
828 */
829 static boolean handleFolderKeyEvent(View v, int keyCode, KeyEvent e) {
830 CellLayoutChildren parent = (CellLayoutChildren) v.getParent();
831 final CellLayout layout = (CellLayout) parent.getParent();
832 final Folder folder = (Folder) layout.getParent();
833 View title = folder.mFolderName;
834
835 final int action = e.getAction();
836 final boolean handleKeyEvent = (action != KeyEvent.ACTION_UP);
837 boolean wasHandled = false;
838 switch (keyCode) {
839 case KeyEvent.KEYCODE_DPAD_LEFT:
840 if (handleKeyEvent) {
841 // Select the previous icon
842 View newIcon = getIconInDirection(layout, parent, v, -1);
843 if (newIcon != null) {
844 newIcon.requestFocus();
845 }
846 }
847 wasHandled = true;
848 break;
849 case KeyEvent.KEYCODE_DPAD_RIGHT:
850 if (handleKeyEvent) {
851 // Select the next icon
852 View newIcon = getIconInDirection(layout, parent, v, 1);
853 if (newIcon != null) {
854 newIcon.requestFocus();
855 } else {
856 title.requestFocus();
857 }
858 }
859 wasHandled = true;
860 break;
861 case KeyEvent.KEYCODE_DPAD_UP:
862 if (handleKeyEvent) {
863 // Select the closest icon in the previous line
864 View newIcon = getClosestIconOnLine(layout, parent, v, -1);
865 if (newIcon != null) {
866 newIcon.requestFocus();
867 }
868 }
869 wasHandled = true;
870 break;
871 case KeyEvent.KEYCODE_DPAD_DOWN:
872 if (handleKeyEvent) {
873 // Select the closest icon in the next line
874 View newIcon = getClosestIconOnLine(layout, parent, v, 1);
875 if (newIcon != null) {
876 newIcon.requestFocus();
877 } else {
878 title.requestFocus();
879 }
880 }
881 wasHandled = true;
882 break;
883 case KeyEvent.KEYCODE_MOVE_HOME:
884 if (handleKeyEvent) {
885 // Select the first icon on this page
886 View newIcon = getIconInDirection(layout, parent, -1, 1);
887 if (newIcon != null) {
888 newIcon.requestFocus();
889 }
890 }
891 wasHandled = true;
892 break;
893 case KeyEvent.KEYCODE_MOVE_END:
894 if (handleKeyEvent) {
895 // Select the last icon on this page
896 View newIcon = getIconInDirection(layout, parent,
Winson Chung97d85d22011-04-13 11:27:36 -0700897 parent.getChildCount(), -1);
898 if (newIcon != null) {
899 newIcon.requestFocus();
900 }
901 }
902 wasHandled = true;
903 break;
904 default: break;
905 }
906 return wasHandled;
907 }
908}