blob: 861a70b2d45d5c1b381e0ae118785557c8f181c2 [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
19import java.util.ArrayList;
20import java.util.Collections;
21import java.util.Comparator;
22
Winson Chung4e6a9762011-05-09 11:56:34 -070023import android.content.res.Configuration;
Winson Chung97d85d22011-04-13 11:27:36 -070024import android.view.KeyEvent;
25import android.view.View;
26import android.view.ViewGroup;
27import android.view.ViewParent;
28import android.widget.TabHost;
29import android.widget.TabWidget;
30
31import com.android.launcher.R;
32
33/**
34 * A keyboard listener we set on all the button bar buttons.
35 */
36class ButtonBarKeyEventListener implements View.OnKeyListener {
37 @Override
38 public boolean onKey(View v, int keyCode, KeyEvent event) {
39 return FocusHelper.handleButtonBarButtonKeyEvent(v, keyCode, event);
40 }
41}
42
43/**
Winson Chung4e6a9762011-05-09 11:56:34 -070044 * A keyboard listener we set on the indicator buttons.
45 */
46class IndicatorKeyEventListener implements View.OnKeyListener {
47 @Override
48 public boolean onKey(View v, int keyCode, KeyEvent event) {
49 return FocusHelper.handleIndicatorButtonKeyEvent(v, keyCode, event);
50 }
51}
52
53/**
54 * A keyboard listener we set on all the dock buttons.
55 */
56class DockKeyEventListener implements View.OnKeyListener {
57 @Override
58 public boolean onKey(View v, int keyCode, KeyEvent event) {
59 final Configuration configuration = v.getResources().getConfiguration();
60 return FocusHelper.handleDockButtonKeyEvent(v, keyCode, event, configuration.orientation);
61 }
62}
63
64/**
Winson Chung97d85d22011-04-13 11:27:36 -070065 * A keyboard listener we set on the last tab button in AllApps to jump to then
66 * market icon and vice versa.
67 */
68class AllAppsTabKeyEventListener implements View.OnKeyListener {
69 @Override
70 public boolean onKey(View v, int keyCode, KeyEvent event) {
71 return FocusHelper.handleAllAppsTabKeyEvent(v, keyCode, event);
72 }
73}
74
75public class FocusHelper {
76 /**
77 * Private helper to get the parent TabHost in the view hiearchy.
78 */
79 private static TabHost findTabHostParent(View v) {
80 ViewParent p = v.getParent();
81 while (p != null && !(p instanceof TabHost)) {
82 p = p.getParent();
83 }
84 return (TabHost) p;
85 }
86
87 /**
88 * Handles key events in a AllApps tab between the last tab view and the shop button.
89 */
90 static boolean handleAllAppsTabKeyEvent(View v, int keyCode, KeyEvent e) {
91 final TabHost tabHost = findTabHostParent(v);
92 final ViewGroup contents = (ViewGroup)
93 tabHost.findViewById(com.android.internal.R.id.tabcontent);
94 final View shop = tabHost.findViewById(R.id.market_button);
95
96 final int action = e.getAction();
97 final boolean handleKeyEvent = (action != KeyEvent.ACTION_UP);
98 boolean wasHandled = false;
99 switch (keyCode) {
100 case KeyEvent.KEYCODE_DPAD_RIGHT:
101 if (handleKeyEvent) {
102 // Select the shop button if we aren't on it
103 if (v != shop) {
104 shop.requestFocus();
105 }
106 }
107 wasHandled = true;
108 break;
109 case KeyEvent.KEYCODE_DPAD_DOWN:
110 if (handleKeyEvent) {
111 // Select the content view (down is handled by the tab key handler otherwise)
112 if (v == shop) {
113 contents.requestFocus();
114 wasHandled = true;
115 }
116 }
117 break;
118 default: break;
119 }
120 return wasHandled;
121 }
122
123 /**
124 * Private helper to determine whether a view is visible.
125 */
126 private static boolean isVisible(View v) {
127 return v.getVisibility() == View.VISIBLE;
128 }
129
130 /**
131 * Handles key events in a PageViewExtendedLayout containing PagedViewWidgets.
Winson Chung4e6a9762011-05-09 11:56:34 -0700132 * To be deprecated.
Winson Chung97d85d22011-04-13 11:27:36 -0700133 */
134 static boolean handlePagedViewWidgetKeyEvent(PagedViewWidget w, int keyCode, KeyEvent e) {
Michael Jurkaa2eb1702011-05-12 14:57:05 -0700135 if (!LauncherApplication.isScreenLarge()) return false;
Winson Chung97d85d22011-04-13 11:27:36 -0700136
137 final PagedViewExtendedLayout parent = (PagedViewExtendedLayout) w.getParent();
138 final ViewGroup container = (ViewGroup) parent.getParent();
139 final TabHost tabHost = findTabHostParent(container);
140 final TabWidget tabs = (TabWidget) tabHost.findViewById(com.android.internal.R.id.tabs);
141 final int widgetIndex = parent.indexOfChild(w);
142 final int widgetCount = parent.getChildCount();
143 final int pageIndex = container.indexOfChild(parent);
144 final int pageCount = container.getChildCount();
145
146 final int action = e.getAction();
147 final boolean handleKeyEvent = (action != KeyEvent.ACTION_UP);
148 PagedViewExtendedLayout newParent = null;
149 boolean wasHandled = false;
150 switch (keyCode) {
151 case KeyEvent.KEYCODE_DPAD_LEFT:
152 if (handleKeyEvent) {
153 // Select the previous widget or the last widget on the previous page
154 if (widgetIndex > 0) {
155 parent.getChildAt(widgetIndex - 1).requestFocus();
156 } else {
157 if (pageIndex > 0) {
158 newParent = (PagedViewExtendedLayout)
159 container.getChildAt(pageIndex - 1);
160 newParent.getChildAt(newParent.getChildCount() - 1).requestFocus();
161 }
162 }
163 }
164 wasHandled = true;
165 break;
166 case KeyEvent.KEYCODE_DPAD_RIGHT:
167 if (handleKeyEvent) {
168 // Select the next widget or the first widget on the next page
169 if (widgetIndex < (widgetCount - 1)) {
170 parent.getChildAt(widgetIndex + 1).requestFocus();
171 } else {
172 if (pageIndex < (pageCount - 1)) {
173 newParent = (PagedViewExtendedLayout)
174 container.getChildAt(pageIndex + 1);
175 newParent.getChildAt(0).requestFocus();
176 }
177 }
178 }
179 wasHandled = true;
180 break;
181 case KeyEvent.KEYCODE_DPAD_UP:
182 if (handleKeyEvent) {
183 // Select widgets tab on the tab bar
184 tabs.requestFocus();
185 }
186 wasHandled = true;
187 break;
188 case KeyEvent.KEYCODE_DPAD_DOWN:
189 if (handleKeyEvent) {
190 // TODO: Should focus the global search bar
191 }
192 wasHandled = true;
193 break;
194 case KeyEvent.KEYCODE_ENTER:
195 case KeyEvent.KEYCODE_DPAD_CENTER:
196 if (handleKeyEvent) {
197 // Simulate a click on the widget
198 View.OnClickListener clickListener = (View.OnClickListener) container;
199 clickListener.onClick(w);
200 }
201 wasHandled = true;
202 break;
203 case KeyEvent.KEYCODE_PAGE_UP:
204 if (handleKeyEvent) {
205 // Select the first item on the previous page, or the first item on this page
206 // if there is no previous page
207 if (pageIndex > 0) {
208 newParent = (PagedViewExtendedLayout) container.getChildAt(pageIndex - 1);
209 newParent.getChildAt(0).requestFocus();
210 } else {
211 parent.getChildAt(0).requestFocus();
212 }
213 }
214 wasHandled = true;
215 break;
216 case KeyEvent.KEYCODE_PAGE_DOWN:
217 if (handleKeyEvent) {
218 // Select the first item on the next page, or the last item on this page
219 // if there is no next page
220 if (pageIndex < (pageCount - 1)) {
221 newParent = (PagedViewExtendedLayout) container.getChildAt(pageIndex + 1);
222 newParent.getChildAt(0).requestFocus();
223 } else {
224 parent.getChildAt(widgetCount - 1).requestFocus();
225 }
226 }
227 wasHandled = true;
228 break;
229 case KeyEvent.KEYCODE_MOVE_HOME:
230 if (handleKeyEvent) {
231 // Select the first item on this page
232 parent.getChildAt(0).requestFocus();
233 }
234 wasHandled = true;
235 break;
236 case KeyEvent.KEYCODE_MOVE_END:
237 if (handleKeyEvent) {
238 // Select the last item on this page
239 parent.getChildAt(widgetCount - 1).requestFocus();
240 }
241 wasHandled = true;
242 break;
243 default: break;
244 }
245 return wasHandled;
246 }
247
248 /**
Winson Chung4e6a9762011-05-09 11:56:34 -0700249 * Handles key events in a PageViewExtendedLayout containing PagedViewWidgets.
250 */
251 static boolean handlePagedViewGridLayoutWidgetKeyEvent(PagedViewWidget w, int keyCode,
252 KeyEvent e) {
253
254 final PagedViewGridLayout parent = (PagedViewGridLayout) w.getParent();
255 final ViewGroup container = (ViewGroup) parent.getParent();
256 final TabHost tabHost = findTabHostParent(container);
257 final TabWidget tabs = (TabWidget) tabHost.findViewById(com.android.internal.R.id.tabs);
258 final int widgetIndex = parent.indexOfChild(w);
259 final int widgetCount = parent.getChildCount();
260 final int pageIndex = container.indexOfChild(parent);
261 final int pageCount = container.getChildCount();
262 final int cellCountX = parent.getCellCountX();
263 final int cellCountY = parent.getCellCountY();
264 final int x = widgetIndex % cellCountX;
265 final int y = widgetIndex / cellCountX;
266
267 final int action = e.getAction();
268 final boolean handleKeyEvent = (action != KeyEvent.ACTION_UP);
269 PagedViewGridLayout newParent = null;
270 boolean wasHandled = false;
271 switch (keyCode) {
272 case KeyEvent.KEYCODE_DPAD_LEFT:
273 if (handleKeyEvent) {
274 // Select the previous widget or the last widget on the previous page
275 if (widgetIndex > 0) {
276 parent.getChildAt(widgetIndex - 1).requestFocus();
277 } else {
278 if (pageIndex > 0) {
279 newParent = (PagedViewGridLayout)
280 container.getChildAt(pageIndex - 1);
281 newParent.getChildAt(newParent.getChildCount() - 1).requestFocus();
282 }
283 }
284 }
285 wasHandled = true;
286 break;
287 case KeyEvent.KEYCODE_DPAD_RIGHT:
288 if (handleKeyEvent) {
289 // Select the next widget or the first widget on the next page
290 if (widgetIndex < (widgetCount - 1)) {
291 parent.getChildAt(widgetIndex + 1).requestFocus();
292 } else {
293 if (pageIndex < (pageCount - 1)) {
294 newParent = (PagedViewGridLayout)
295 container.getChildAt(pageIndex + 1);
296 newParent.getChildAt(0).requestFocus();
297 }
298 }
299 }
300 wasHandled = true;
301 break;
302 case KeyEvent.KEYCODE_DPAD_UP:
303 if (handleKeyEvent) {
304 // Select the closest icon in the previous row, otherwise select the tab bar
305 if (y > 0) {
306 int newWidgetIndex = ((y - 1) * cellCountX) + x;
307 parent.getChildAt(newWidgetIndex).requestFocus();
308 } else {
309 tabs.requestFocus();
310 }
311 }
312 wasHandled = true;
313 break;
314 case KeyEvent.KEYCODE_DPAD_DOWN:
315 if (handleKeyEvent) {
316 // Select the closest icon in the previous row, otherwise do nothing
317 if (y < (cellCountY - 1)) {
318 int newWidgetIndex = Math.min(widgetCount - 1, ((y + 1) * cellCountX) + x);
319 parent.getChildAt(newWidgetIndex).requestFocus();
320 }
321 }
322 wasHandled = true;
323 break;
324 case KeyEvent.KEYCODE_ENTER:
325 case KeyEvent.KEYCODE_DPAD_CENTER:
326 if (handleKeyEvent) {
327 // Simulate a click on the widget
328 View.OnClickListener clickListener = (View.OnClickListener) container;
329 clickListener.onClick(w);
330 }
331 wasHandled = true;
332 break;
333 case KeyEvent.KEYCODE_PAGE_UP:
334 if (handleKeyEvent) {
335 // Select the first item on the previous page, or the first item on this page
336 // if there is no previous page
337 if (pageIndex > 0) {
338 newParent = (PagedViewGridLayout) container.getChildAt(pageIndex - 1);
339 newParent.getChildAt(0).requestFocus();
340 } else {
341 parent.getChildAt(0).requestFocus();
342 }
343 }
344 wasHandled = true;
345 break;
346 case KeyEvent.KEYCODE_PAGE_DOWN:
347 if (handleKeyEvent) {
348 // Select the first item on the next page, or the last item on this page
349 // if there is no next page
350 if (pageIndex < (pageCount - 1)) {
351 newParent = (PagedViewGridLayout) container.getChildAt(pageIndex + 1);
352 newParent.getChildAt(0).requestFocus();
353 } else {
354 parent.getChildAt(widgetCount - 1).requestFocus();
355 }
356 }
357 wasHandled = true;
358 break;
359 case KeyEvent.KEYCODE_MOVE_HOME:
360 if (handleKeyEvent) {
361 // Select the first item on this page
362 parent.getChildAt(0).requestFocus();
363 }
364 wasHandled = true;
365 break;
366 case KeyEvent.KEYCODE_MOVE_END:
367 if (handleKeyEvent) {
368 // Select the last item on this page
369 parent.getChildAt(widgetCount - 1).requestFocus();
370 }
371 wasHandled = true;
372 break;
373 default: break;
374 }
375 return wasHandled;
376 }
377
378 /**
Winson Chung97d85d22011-04-13 11:27:36 -0700379 * Private helper method to get the PagedViewCellLayoutChildren given a PagedViewCellLayout
380 * index.
381 */
382 private static PagedViewCellLayoutChildren getPagedViewCellLayoutChildrenForIndex(
383 ViewGroup container, int i) {
384 ViewGroup parent = (ViewGroup) container.getChildAt(i);
385 return (PagedViewCellLayoutChildren) parent.getChildAt(0);
386 }
387
388 /**
389 * Handles key events in a PageViewCellLayout containing PagedViewIcons.
390 */
391 static boolean handlePagedViewIconKeyEvent(PagedViewIcon v, int keyCode, KeyEvent e) {
Winson Chung97d85d22011-04-13 11:27:36 -0700392 final PagedViewCellLayoutChildren parent = (PagedViewCellLayoutChildren) v.getParent();
393 final PagedViewCellLayout parentLayout = (PagedViewCellLayout) parent.getParent();
394 // Note we have an extra parent because of the
395 // PagedViewCellLayout/PagedViewCellLayoutChildren relationship
396 final ViewGroup container = (ViewGroup) parentLayout.getParent();
397 final TabHost tabHost = findTabHostParent(container);
398 final TabWidget tabs = (TabWidget) tabHost.findViewById(com.android.internal.R.id.tabs);
399 final int widgetIndex = parent.indexOfChild(v);
400 final int widgetCount = parent.getChildCount();
401 final int pageIndex = container.indexOfChild(parentLayout);
402 final int pageCount = container.getChildCount();
403 final int cellCountX = parentLayout.getCellCountX();
404 final int cellCountY = parentLayout.getCellCountY();
405 final int x = widgetIndex % cellCountX;
406 final int y = widgetIndex / cellCountX;
407
408 final int action = e.getAction();
409 final boolean handleKeyEvent = (action != KeyEvent.ACTION_UP);
410 PagedViewCellLayoutChildren newParent = null;
411 boolean wasHandled = false;
412 switch (keyCode) {
413 case KeyEvent.KEYCODE_DPAD_LEFT:
414 if (handleKeyEvent) {
415 // Select the previous icon or the last icon on the previous page
416 if (widgetIndex > 0) {
417 parent.getChildAt(widgetIndex - 1).requestFocus();
418 } else {
419 if (pageIndex > 0) {
420 newParent = getPagedViewCellLayoutChildrenForIndex(container,
421 pageIndex - 1);
422 newParent.getChildAt(newParent.getChildCount() - 1).requestFocus();
423 }
424 }
425 }
426 wasHandled = true;
427 break;
428 case KeyEvent.KEYCODE_DPAD_RIGHT:
429 if (handleKeyEvent) {
430 // Select the next icon or the first icon on the next page
431 if (widgetIndex < (widgetCount - 1)) {
432 parent.getChildAt(widgetIndex + 1).requestFocus();
433 } else {
434 if (pageIndex < (pageCount - 1)) {
435 newParent = getPagedViewCellLayoutChildrenForIndex(container,
436 pageIndex + 1);
437 newParent.getChildAt(0).requestFocus();
438 }
439 }
440 }
441 wasHandled = true;
442 break;
443 case KeyEvent.KEYCODE_DPAD_UP:
444 if (handleKeyEvent) {
445 // Select the closest icon in the previous row, otherwise select the tab bar
446 if (y > 0) {
447 int newWidgetIndex = ((y - 1) * cellCountX) + x;
448 parent.getChildAt(newWidgetIndex).requestFocus();
449 } else {
450 tabs.requestFocus();
451 }
452 }
453 wasHandled = true;
454 break;
455 case KeyEvent.KEYCODE_DPAD_DOWN:
456 if (handleKeyEvent) {
457 // Select the closest icon in the previous row, otherwise do nothing
458 if (y < (cellCountY - 1)) {
459 int newWidgetIndex = Math.min(widgetCount - 1, ((y + 1) * cellCountX) + x);
460 parent.getChildAt(newWidgetIndex).requestFocus();
461 }
462 }
463 wasHandled = true;
464 break;
465 case KeyEvent.KEYCODE_ENTER:
466 case KeyEvent.KEYCODE_DPAD_CENTER:
467 if (handleKeyEvent) {
468 // Simulate a click on the icon
469 View.OnClickListener clickListener = (View.OnClickListener) container;
470 clickListener.onClick(v);
471 }
472 wasHandled = true;
473 break;
474 case KeyEvent.KEYCODE_PAGE_UP:
475 if (handleKeyEvent) {
476 // Select the first icon on the previous page, or the first icon on this page
477 // if there is no previous page
478 if (pageIndex > 0) {
479 newParent = getPagedViewCellLayoutChildrenForIndex(container,
480 pageIndex - 1);
481 newParent.getChildAt(0).requestFocus();
482 } else {
483 parent.getChildAt(0).requestFocus();
484 }
485 }
486 wasHandled = true;
487 break;
488 case KeyEvent.KEYCODE_PAGE_DOWN:
489 if (handleKeyEvent) {
490 // Select the first icon on the next page, or the last icon on this page
491 // if there is no next page
492 if (pageIndex < (pageCount - 1)) {
493 newParent = getPagedViewCellLayoutChildrenForIndex(container,
494 pageIndex + 1);
495 newParent.getChildAt(0).requestFocus();
496 } else {
497 parent.getChildAt(widgetCount - 1).requestFocus();
498 }
499 }
500 wasHandled = true;
501 break;
502 case KeyEvent.KEYCODE_MOVE_HOME:
503 if (handleKeyEvent) {
504 // Select the first icon on this page
505 parent.getChildAt(0).requestFocus();
506 }
507 wasHandled = true;
508 break;
509 case KeyEvent.KEYCODE_MOVE_END:
510 if (handleKeyEvent) {
511 // Select the last icon on this page
512 parent.getChildAt(widgetCount - 1).requestFocus();
513 }
514 wasHandled = true;
515 break;
516 default: break;
517 }
518 return wasHandled;
519 }
520
521 /**
522 * Handles key events in the tab widget.
523 */
524 static boolean handleTabKeyEvent(AccessibleTabView v, int keyCode, KeyEvent e) {
Michael Jurkaa2eb1702011-05-12 14:57:05 -0700525 if (!LauncherApplication.isScreenLarge()) return false;
Winson Chung97d85d22011-04-13 11:27:36 -0700526
527 final FocusOnlyTabWidget parent = (FocusOnlyTabWidget) v.getParent();
528 final TabHost tabHost = findTabHostParent(parent);
529 final ViewGroup contents = (ViewGroup)
530 tabHost.findViewById(com.android.internal.R.id.tabcontent);
531 final int tabCount = parent.getTabCount();
532 final int tabIndex = parent.getChildTabIndex(v);
533
534 final int action = e.getAction();
535 final boolean handleKeyEvent = (action != KeyEvent.ACTION_UP);
536 boolean wasHandled = false;
537 switch (keyCode) {
538 case KeyEvent.KEYCODE_DPAD_LEFT:
539 if (handleKeyEvent) {
540 // Select the previous tab
541 if (tabIndex > 0) {
542 parent.getChildTabViewAt(tabIndex - 1).requestFocus();
543 }
544 }
545 wasHandled = true;
546 break;
547 case KeyEvent.KEYCODE_DPAD_RIGHT:
548 if (handleKeyEvent) {
549 // Select the next tab, or if the last tab has a focus right id, select that
550 if (tabIndex < (tabCount - 1)) {
551 parent.getChildTabViewAt(tabIndex + 1).requestFocus();
552 } else {
553 if (v.getNextFocusRightId() != View.NO_ID) {
554 tabHost.findViewById(v.getNextFocusRightId()).requestFocus();
555 }
556 }
557 }
558 wasHandled = true;
559 break;
560 case KeyEvent.KEYCODE_DPAD_UP:
561 // Do nothing
562 wasHandled = true;
563 break;
564 case KeyEvent.KEYCODE_DPAD_DOWN:
565 if (handleKeyEvent) {
566 // Select the content view
567 contents.requestFocus();
568 }
569 wasHandled = true;
570 break;
571 default: break;
572 }
573 return wasHandled;
574 }
575
576 /**
Winson Chung4e6a9762011-05-09 11:56:34 -0700577 * Handles key events in the workspace button bar.
Winson Chung97d85d22011-04-13 11:27:36 -0700578 */
579 static boolean handleButtonBarButtonKeyEvent(View v, int keyCode, KeyEvent e) {
Michael Jurkaa2eb1702011-05-12 14:57:05 -0700580 if (!LauncherApplication.isScreenLarge()) return false;
Winson Chung97d85d22011-04-13 11:27:36 -0700581
582 final ViewGroup parent = (ViewGroup) v.getParent();
583 final ViewGroup launcher = (ViewGroup) parent.getParent();
584 final Workspace workspace = (Workspace) launcher.findViewById(R.id.workspace);
585 final int buttonIndex = parent.indexOfChild(v);
586 final int buttonCount = parent.getChildCount();
587 final int pageIndex = workspace.getCurrentPage();
588 final int pageCount = workspace.getChildCount();
589 final int firstButtonIndex = parent.indexOfChild(parent.findViewById(R.id.search_button));
590 final int lastButtonIndex = parent.indexOfChild(parent.findViewById(R.id.configure_button));
591
592 final int action = e.getAction();
593 final boolean handleKeyEvent = (action != KeyEvent.ACTION_UP);
594 boolean wasHandled = false;
595 switch (keyCode) {
596 case KeyEvent.KEYCODE_DPAD_LEFT:
597 if (handleKeyEvent) {
598 // Select the previous button, otherwise do nothing (since the button bar is
599 // static)
600 if (buttonIndex > firstButtonIndex) {
601 int newButtonIndex = buttonIndex - 1;
602 while (newButtonIndex >= firstButtonIndex) {
603 View prev = parent.getChildAt(newButtonIndex);
604 if (isVisible(prev) && prev.isFocusable()) {
605 prev.requestFocus();
606 break;
607 }
608 --newButtonIndex;
609 }
610 } else {
611 if (pageIndex > 0) {
612 // Snap to previous page and clear focus
613 workspace.snapToPage(pageIndex - 1);
614 }
615 }
616 }
617 wasHandled = true;
618 break;
619 case KeyEvent.KEYCODE_DPAD_RIGHT:
620 if (handleKeyEvent) {
621 // Select the next button, otherwise do nothing (since the button bar is
622 // static)
623 if (buttonIndex < lastButtonIndex) {
624 int newButtonIndex = buttonIndex + 1;
625 while (newButtonIndex <= lastButtonIndex) {
626 View next = parent.getChildAt(newButtonIndex);
627 if (isVisible(next) && next.isFocusable()) {
628 next.requestFocus();
629 break;
630 }
631 ++newButtonIndex;
632 }
633 } else {
634 if (pageIndex < (pageCount - 1)) {
635 // Snap to next page and clear focus
636 workspace.snapToPage(pageIndex + 1);
637 }
638 }
639 }
640 wasHandled = true;
641 break;
642 case KeyEvent.KEYCODE_DPAD_UP:
643 // Do nothing
644 wasHandled = true;
645 break;
646 case KeyEvent.KEYCODE_DPAD_DOWN:
647 if (handleKeyEvent) {
648 // Select the first bubble text view in the current page of the workspace
649 final CellLayout layout = (CellLayout) workspace.getChildAt(pageIndex);
650 final CellLayoutChildren children = layout.getChildrenLayout();
651 final View newIcon = getBubbleTextViewInDirection(layout, children, -1, 1);
652 if (newIcon != null) {
653 newIcon.requestFocus();
654 } else {
655 workspace.requestFocus();
656 }
657 }
658 wasHandled = true;
659 break;
660 default: break;
661 }
662 return wasHandled;
663 }
664
665 /**
Winson Chung4e6a9762011-05-09 11:56:34 -0700666 * Handles key events in the prev/next indicators.
667 */
668 static boolean handleIndicatorButtonKeyEvent(View v, int keyCode, KeyEvent e) {
669 final ViewGroup launcher = (ViewGroup) v.getParent();
670 final Workspace workspace = (Workspace) launcher.findViewById(R.id.workspace);
671 final ViewGroup hotseat = (ViewGroup) launcher.findViewById(R.id.all_apps_button_cluster);
672 final View previousIndicator = launcher.findViewById(R.id.previous_screen);
673 final View nextIndicator = launcher.findViewById(R.id.next_screen);
674 final int pageIndex = workspace.getCurrentPage();
675 final int pageCount = workspace.getChildCount();
676
677 final int action = e.getAction();
678 final boolean handleKeyEvent = (action != KeyEvent.ACTION_UP);
679 boolean wasHandled = false;
680 switch (keyCode) {
681 case KeyEvent.KEYCODE_DPAD_LEFT:
682 if (handleKeyEvent) {
683 if (v == previousIndicator) {
684 if (pageIndex > 0) {
685 // Snap to previous page and clear focus
686 workspace.snapToPage(pageIndex - 1);
687 }
688 } else if (v == nextIndicator) {
689 // Select the last button in the hot seat
690 hotseat.getChildAt(hotseat.getChildCount() - 1).requestFocus();
691 }
692 }
693 wasHandled = true;
694 break;
695 case KeyEvent.KEYCODE_DPAD_RIGHT:
696 if (handleKeyEvent) {
697 if (v == previousIndicator) {
698 // Select the first button in the hot seat
699 hotseat.getChildAt(0).requestFocus();
700 } else if (v == nextIndicator) {
701 if (pageIndex < (pageCount - 1)) {
702 // Snap to next page and clear focus
703 workspace.snapToPage(pageIndex + 1);
704 }
705 }
706 }
707 wasHandled = true;
708 break;
709 case KeyEvent.KEYCODE_DPAD_UP:
710 if (handleKeyEvent) {
711 // Select the first bubble text view in the current page of the workspace
712 final CellLayout layout = (CellLayout) workspace.getChildAt(pageIndex);
713 final CellLayoutChildren children = layout.getChildrenLayout();
714 final View newIcon = getBubbleTextViewInDirection(layout, children, -1, 1);
715 if (newIcon != null) {
716 newIcon.requestFocus();
717 } else {
718 workspace.requestFocus();
719 }
720 }
721 wasHandled = true;
722 break;
723 case KeyEvent.KEYCODE_DPAD_DOWN:
724 // Do nothing
725 wasHandled = true;
726 break;
727 default: break;
728 }
729 return wasHandled;
730 }
731
732 /**
733 * Handles key events in the workspace dock (bottom of the screen).
734 */
735 static boolean handleDockButtonKeyEvent(View v, int keyCode, KeyEvent e, int orientation) {
736 final ViewGroup parent = (ViewGroup) v.getParent();
737 final ViewGroup launcher = (ViewGroup) parent.getParent();
738 final Workspace workspace = (Workspace) launcher.findViewById(R.id.workspace);
739 final int buttonIndex = parent.indexOfChild(v);
740 final int buttonCount = parent.getChildCount();
741 final int pageIndex = workspace.getCurrentPage();
742 final int pageCount = workspace.getChildCount();
743 final View previousIndicator = launcher.findViewById(R.id.previous_screen);
744 final View nextIndicator = launcher.findViewById(R.id.next_screen);
745
746 // NOTE: currently we don't special case for the phone UI in different
747 // orientations, even though the dock is on the side in landscape mode. This
748 // is to ensure that accessibility consistency is maintained across rotations.
749
750 final int action = e.getAction();
751 final boolean handleKeyEvent = (action != KeyEvent.ACTION_UP);
752 boolean wasHandled = false;
753 switch (keyCode) {
754 case KeyEvent.KEYCODE_DPAD_LEFT:
755 if (handleKeyEvent) {
756
757 // Select the previous button, otherwise select the previous page indicator
758 if (buttonIndex > 0) {
759 parent.getChildAt(buttonIndex - 1).requestFocus();
760 } else {
761 previousIndicator.requestFocus();
762 }
763 }
764 wasHandled = true;
765 break;
766 case KeyEvent.KEYCODE_DPAD_RIGHT:
767 if (handleKeyEvent) {
768 // Select the next button, otherwise select the next page indicator
769 if (buttonIndex < (buttonCount - 1)) {
770 parent.getChildAt(buttonIndex + 1).requestFocus();
771 } else {
772 nextIndicator.requestFocus();
773 }
774 }
775 wasHandled = true;
776 break;
777 case KeyEvent.KEYCODE_DPAD_UP:
778 if (handleKeyEvent) {
779 // Select the first bubble text view in the current page of the workspace
780 final CellLayout layout = (CellLayout) workspace.getChildAt(pageIndex);
781 final CellLayoutChildren children = layout.getChildrenLayout();
782 final View newIcon = getBubbleTextViewInDirection(layout, children, -1, 1);
783 if (newIcon != null) {
784 newIcon.requestFocus();
785 } else {
786 workspace.requestFocus();
787 }
788 }
789 wasHandled = true;
790 break;
791 case KeyEvent.KEYCODE_DPAD_DOWN:
792 // Do nothing
793 wasHandled = true;
794 break;
795 default: break;
796 }
797 return wasHandled;
798 }
799
800 /**
Winson Chung97d85d22011-04-13 11:27:36 -0700801 * Private helper method to get the CellLayoutChildren given a CellLayout index.
802 */
803 private static CellLayoutChildren getCellLayoutChildrenForIndex(ViewGroup container, int i) {
804 ViewGroup parent = (ViewGroup) container.getChildAt(i);
805 return (CellLayoutChildren) parent.getChildAt(0);
806 }
807
808 /**
809 * Private helper method to sort all the CellLayout children in order of their (x,y) spatially
810 * from top left to bottom right.
811 */
812 private static ArrayList<View> getCellLayoutChildrenSortedSpatially(CellLayout layout,
813 ViewGroup parent) {
814 // First we order each the CellLayout children by their x,y coordinates
815 final int cellCountX = layout.getCountX();
816 final int count = parent.getChildCount();
817 ArrayList<View> views = new ArrayList<View>();
818 for (int j = 0; j < count; ++j) {
819 views.add(parent.getChildAt(j));
820 }
821 Collections.sort(views, new Comparator<View>() {
822 @Override
823 public int compare(View lhs, View rhs) {
824 CellLayout.LayoutParams llp = (CellLayout.LayoutParams) lhs.getLayoutParams();
825 CellLayout.LayoutParams rlp = (CellLayout.LayoutParams) rhs.getLayoutParams();
826 int lvIndex = (llp.cellY * cellCountX) + llp.cellX;
827 int rvIndex = (rlp.cellY * cellCountX) + rlp.cellX;
828 return lvIndex - rvIndex;
829 }
830 });
831 return views;
832 }
833 /**
834 * Private helper method to find the index of the next BubbleTextView in the delta direction.
835 * @param delta either -1 or 1 depending on the direction we want to search
836 */
837 private static View findIndexOfBubbleTextView(ArrayList<View> views, int i, int delta) {
838 // Then we find the next BubbleTextView offset by delta from i
839 final int count = views.size();
840 int newI = i + delta;
841 while (0 <= newI && newI < count) {
842 View newV = views.get(newI);
843 if (newV instanceof BubbleTextView) {
844 return newV;
845 }
846 newI += delta;
847 }
848 return null;
849 }
850 private static View getBubbleTextViewInDirection(CellLayout layout, ViewGroup parent, int i,
851 int delta) {
852 final ArrayList<View> views = getCellLayoutChildrenSortedSpatially(layout, parent);
853 return findIndexOfBubbleTextView(views, i, delta);
854 }
855 private static View getBubbleTextViewInDirection(CellLayout layout, ViewGroup parent, View v,
856 int delta) {
857 final ArrayList<View> views = getCellLayoutChildrenSortedSpatially(layout, parent);
858 return findIndexOfBubbleTextView(views, views.indexOf(v), delta);
859 }
860 /**
861 * Private helper method to find the next closest BubbleTextView in the delta direction on the
862 * next line.
863 * @param delta either -1 or 1 depending on the line and direction we want to search
864 */
865 private static View getClosestBubbleTextViewOnLine(CellLayout layout, ViewGroup parent, View v,
866 int lineDelta) {
867 final ArrayList<View> views = getCellLayoutChildrenSortedSpatially(layout, parent);
868 final CellLayout.LayoutParams lp = (CellLayout.LayoutParams) v.getLayoutParams();
869 final int cellCountX = layout.getCountX();
870 final int cellCountY = layout.getCountY();
871 final int row = lp.cellY;
872 final int newRow = row + lineDelta;
873 if (0 <= newRow && newRow < cellCountY) {
874 float closestDistance = Float.MAX_VALUE;
875 int closestIndex = -1;
876 int index = views.indexOf(v);
877 int endIndex = (lineDelta < 0) ? -1 : views.size();
878 while (index != endIndex) {
879 View newV = views.get(index);
880 CellLayout.LayoutParams tmpLp = (CellLayout.LayoutParams) newV.getLayoutParams();
881 boolean satisfiesRow = (lineDelta < 0) ? (tmpLp.cellY < row) : (tmpLp.cellY > row);
882 if (satisfiesRow && newV instanceof BubbleTextView) {
883 float tmpDistance = (float) Math.sqrt(Math.pow(tmpLp.cellX - lp.cellX, 2) +
884 Math.pow(tmpLp.cellY - lp.cellY, 2));
885 if (tmpDistance < closestDistance) {
886 closestIndex = index;
887 closestDistance = tmpDistance;
888 }
889 }
890 if (index <= endIndex) {
891 ++index;
892 } else {
893 --index;
894 }
895 }
896 if (closestIndex > -1) {
897 return views.get(closestIndex);
898 }
899 }
900 return null;
901 }
902
903 /**
904 * Handles key events in a Workspace containing BubbleTextView.
905 */
906 static boolean handleBubbleTextViewKeyEvent(BubbleTextView v, int keyCode, KeyEvent e) {
Winson Chung97d85d22011-04-13 11:27:36 -0700907 CellLayoutChildren parent = (CellLayoutChildren) v.getParent();
908 final CellLayout layout = (CellLayout) parent.getParent();
909 final Workspace workspace = (Workspace) layout.getParent();
910 final ViewGroup launcher = (ViewGroup) workspace.getParent();
911 final ViewGroup tabs = (ViewGroup) launcher.findViewById(R.id.all_apps_button_cluster);
912 int iconIndex = parent.indexOfChild(v);
913 int iconCount = parent.getChildCount();
914 int pageIndex = workspace.indexOfChild(layout);
915 int pageCount = workspace.getChildCount();
916
917 final int action = e.getAction();
918 final boolean handleKeyEvent = (action != KeyEvent.ACTION_UP);
919 boolean wasHandled = false;
920 switch (keyCode) {
921 case KeyEvent.KEYCODE_DPAD_LEFT:
922 if (handleKeyEvent) {
923 // Select the previous icon or the last icon on the previous page if possible
924 View newIcon = getBubbleTextViewInDirection(layout, parent, v, -1);
925 if (newIcon != null) {
926 newIcon.requestFocus();
927 } else {
928 if (pageIndex > 0) {
929 parent = getCellLayoutChildrenForIndex(workspace, pageIndex - 1);
930 newIcon = getBubbleTextViewInDirection(layout, parent,
931 parent.getChildCount(), -1);
932 if (newIcon != null) {
933 newIcon.requestFocus();
934 } else {
935 // Snap to the previous page
936 workspace.snapToPage(pageIndex - 1);
937 }
938 }
939 }
940 }
941 wasHandled = true;
942 break;
943 case KeyEvent.KEYCODE_DPAD_RIGHT:
944 if (handleKeyEvent) {
945 // Select the next icon or the first icon on the next page if possible
946 View newIcon = getBubbleTextViewInDirection(layout, parent, v, 1);
947 if (newIcon != null) {
948 newIcon.requestFocus();
949 } else {
950 if (pageIndex < (pageCount - 1)) {
951 parent = getCellLayoutChildrenForIndex(workspace, pageIndex + 1);
952 newIcon = getBubbleTextViewInDirection(layout, parent, -1, 1);
953 if (newIcon != null) {
954 newIcon.requestFocus();
955 } else {
956 // Snap to the next page
957 workspace.snapToPage(pageIndex + 1);
958 }
959 }
960 }
961 }
962 wasHandled = true;
963 break;
964 case KeyEvent.KEYCODE_DPAD_UP:
965 if (handleKeyEvent) {
966 // Select the closest icon in the previous line, otherwise select the tab bar
967 View newIcon = getClosestBubbleTextViewOnLine(layout, parent, v, -1);
968 if (newIcon != null) {
969 newIcon.requestFocus();
970 wasHandled = true;
971 } else {
972 tabs.requestFocus();
973 }
974 }
975 break;
976 case KeyEvent.KEYCODE_DPAD_DOWN:
977 if (handleKeyEvent) {
978 // Select the closest icon in the next line, otherwise select the tab bar
979 View newIcon = getClosestBubbleTextViewOnLine(layout, parent, v, 1);
980 if (newIcon != null) {
981 newIcon.requestFocus();
982 wasHandled = true;
983 }
984 }
985 break;
986 case KeyEvent.KEYCODE_PAGE_UP:
987 if (handleKeyEvent) {
988 // Select the first icon on the previous page or the first icon on this page
989 // if there is no previous page
990 if (pageIndex > 0) {
991 parent = getCellLayoutChildrenForIndex(workspace, pageIndex - 1);
992 View newIcon = getBubbleTextViewInDirection(layout, parent, -1, 1);
993 if (newIcon != null) {
994 newIcon.requestFocus();
995 } else {
996 // Snap to the previous page
997 workspace.snapToPage(pageIndex - 1);
998 }
999 } else {
1000 View newIcon = getBubbleTextViewInDirection(layout, parent, -1, 1);
1001 if (newIcon != null) {
1002 newIcon.requestFocus();
1003 }
1004 }
1005 }
1006 wasHandled = true;
1007 break;
1008 case KeyEvent.KEYCODE_PAGE_DOWN:
1009 if (handleKeyEvent) {
1010 // Select the first icon on the next page or the last icon on this page
1011 // if there is no previous page
1012 if (pageIndex < (pageCount - 1)) {
1013 parent = getCellLayoutChildrenForIndex(workspace, pageIndex + 1);
1014 View newIcon = getBubbleTextViewInDirection(layout, parent, -1, 1);
1015 if (newIcon != null) {
1016 newIcon.requestFocus();
1017 } else {
1018 // Snap to the next page
1019 workspace.snapToPage(pageIndex + 1);
1020 }
1021 } else {
1022 View newIcon = getBubbleTextViewInDirection(layout, parent,
1023 parent.getChildCount(), -1);
1024 if (newIcon != null) {
1025 newIcon.requestFocus();
1026 }
1027 }
1028 }
1029 wasHandled = true;
1030 break;
1031 case KeyEvent.KEYCODE_MOVE_HOME:
1032 if (handleKeyEvent) {
1033 // Select the first icon on this page
1034 View newIcon = getBubbleTextViewInDirection(layout, parent, -1, 1);
1035 if (newIcon != null) {
1036 newIcon.requestFocus();
1037 }
1038 }
1039 wasHandled = true;
1040 break;
1041 case KeyEvent.KEYCODE_MOVE_END:
1042 if (handleKeyEvent) {
1043 // Select the last icon on this page
1044 View newIcon = getBubbleTextViewInDirection(layout, parent,
1045 parent.getChildCount(), -1);
1046 if (newIcon != null) {
1047 newIcon.requestFocus();
1048 }
1049 }
1050 wasHandled = true;
1051 break;
1052 default: break;
1053 }
1054 return wasHandled;
1055 }
1056}