blob: 25d941b65e1cf1e2ba9814ee4f473da75a925f63 [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;
26
27import com.android.launcher.R;
28
Winson Chungfaa13252011-06-13 18:15:54 -070029import java.util.ArrayList;
30import java.util.Collections;
31import java.util.Comparator;
32
Winson Chung97d85d22011-04-13 11:27:36 -070033/**
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 Chungfaa13252011-06-13 18:15:54 -070065 * A keyboard listener we set on the last tab button in AppsCustomize to jump to then
Winson Chung97d85d22011-04-13 11:27:36 -070066 * market icon and vice versa.
67 */
Winson Chungfaa13252011-06-13 18:15:54 -070068class AppsCustomizeTabKeyEventListener implements View.OnKeyListener {
Winson Chung97d85d22011-04-13 11:27:36 -070069 @Override
70 public boolean onKey(View v, int keyCode, KeyEvent event) {
Winson Chungfaa13252011-06-13 18:15:54 -070071 return FocusHelper.handleAppsCustomizeTabKeyEvent(v, keyCode, event);
Winson Chung97d85d22011-04-13 11:27:36 -070072 }
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 /**
Winson Chungfaa13252011-06-13 18:15:54 -070088 * Handles key events in a AppsCustomize tab between the last tab view and the shop button.
Winson Chung97d85d22011-04-13 11:27:36 -070089 */
Winson Chungfaa13252011-06-13 18:15:54 -070090 static boolean handleAppsCustomizeTabKeyEvent(View v, int keyCode, KeyEvent e) {
Winson Chung97d85d22011-04-13 11:27:36 -070091 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 */
133 static boolean handlePagedViewGridLayoutWidgetKeyEvent(PagedViewWidget w, int keyCode,
134 KeyEvent e) {
135
136 final PagedViewGridLayout parent = (PagedViewGridLayout) w.getParent();
137 final ViewGroup container = (ViewGroup) parent.getParent();
138 final TabHost tabHost = findTabHostParent(container);
139 final TabWidget tabs = (TabWidget) tabHost.findViewById(com.android.internal.R.id.tabs);
140 final int widgetIndex = parent.indexOfChild(w);
141 final int widgetCount = parent.getChildCount();
142 final int pageIndex = container.indexOfChild(parent);
143 final int pageCount = container.getChildCount();
144 final int cellCountX = parent.getCellCountX();
145 final int cellCountY = parent.getCellCountY();
146 final int x = widgetIndex % cellCountX;
147 final int y = widgetIndex / cellCountX;
148
149 final int action = e.getAction();
150 final boolean handleKeyEvent = (action != KeyEvent.ACTION_UP);
151 PagedViewGridLayout newParent = null;
152 boolean wasHandled = false;
153 switch (keyCode) {
154 case KeyEvent.KEYCODE_DPAD_LEFT:
155 if (handleKeyEvent) {
156 // Select the previous widget or the last widget on the previous page
157 if (widgetIndex > 0) {
158 parent.getChildAt(widgetIndex - 1).requestFocus();
159 } else {
160 if (pageIndex > 0) {
161 newParent = (PagedViewGridLayout)
162 container.getChildAt(pageIndex - 1);
163 newParent.getChildAt(newParent.getChildCount() - 1).requestFocus();
164 }
165 }
166 }
167 wasHandled = true;
168 break;
169 case KeyEvent.KEYCODE_DPAD_RIGHT:
170 if (handleKeyEvent) {
171 // Select the next widget or the first widget on the next page
172 if (widgetIndex < (widgetCount - 1)) {
173 parent.getChildAt(widgetIndex + 1).requestFocus();
174 } else {
175 if (pageIndex < (pageCount - 1)) {
176 newParent = (PagedViewGridLayout)
177 container.getChildAt(pageIndex + 1);
178 newParent.getChildAt(0).requestFocus();
179 }
180 }
181 }
182 wasHandled = true;
183 break;
184 case KeyEvent.KEYCODE_DPAD_UP:
185 if (handleKeyEvent) {
186 // Select the closest icon in the previous row, otherwise select the tab bar
187 if (y > 0) {
188 int newWidgetIndex = ((y - 1) * cellCountX) + x;
189 parent.getChildAt(newWidgetIndex).requestFocus();
190 } else {
191 tabs.requestFocus();
192 }
193 }
194 wasHandled = true;
195 break;
196 case KeyEvent.KEYCODE_DPAD_DOWN:
197 if (handleKeyEvent) {
198 // Select the closest icon in the previous row, otherwise do nothing
199 if (y < (cellCountY - 1)) {
200 int newWidgetIndex = Math.min(widgetCount - 1, ((y + 1) * cellCountX) + x);
201 parent.getChildAt(newWidgetIndex).requestFocus();
202 }
203 }
204 wasHandled = true;
205 break;
206 case KeyEvent.KEYCODE_ENTER:
207 case KeyEvent.KEYCODE_DPAD_CENTER:
208 if (handleKeyEvent) {
209 // Simulate a click on the widget
210 View.OnClickListener clickListener = (View.OnClickListener) container;
211 clickListener.onClick(w);
212 }
213 wasHandled = true;
214 break;
215 case KeyEvent.KEYCODE_PAGE_UP:
216 if (handleKeyEvent) {
217 // Select the first item on the previous page, or the first item on this page
218 // if there is no previous page
219 if (pageIndex > 0) {
220 newParent = (PagedViewGridLayout) container.getChildAt(pageIndex - 1);
221 newParent.getChildAt(0).requestFocus();
222 } else {
223 parent.getChildAt(0).requestFocus();
224 }
225 }
226 wasHandled = true;
227 break;
228 case KeyEvent.KEYCODE_PAGE_DOWN:
229 if (handleKeyEvent) {
230 // Select the first item on the next page, or the last item on this page
231 // if there is no next page
232 if (pageIndex < (pageCount - 1)) {
233 newParent = (PagedViewGridLayout) container.getChildAt(pageIndex + 1);
234 newParent.getChildAt(0).requestFocus();
235 } else {
236 parent.getChildAt(widgetCount - 1).requestFocus();
237 }
238 }
239 wasHandled = true;
240 break;
241 case KeyEvent.KEYCODE_MOVE_HOME:
242 if (handleKeyEvent) {
243 // Select the first item on this page
244 parent.getChildAt(0).requestFocus();
245 }
246 wasHandled = true;
247 break;
248 case KeyEvent.KEYCODE_MOVE_END:
249 if (handleKeyEvent) {
250 // Select the last item on this page
251 parent.getChildAt(widgetCount - 1).requestFocus();
252 }
253 wasHandled = true;
254 break;
255 default: break;
256 }
257 return wasHandled;
258 }
259
260 /**
Winson Chung97d85d22011-04-13 11:27:36 -0700261 * Private helper method to get the PagedViewCellLayoutChildren given a PagedViewCellLayout
262 * index.
263 */
264 private static PagedViewCellLayoutChildren getPagedViewCellLayoutChildrenForIndex(
265 ViewGroup container, int i) {
266 ViewGroup parent = (ViewGroup) container.getChildAt(i);
267 return (PagedViewCellLayoutChildren) parent.getChildAt(0);
268 }
269
270 /**
271 * Handles key events in a PageViewCellLayout containing PagedViewIcons.
272 */
273 static boolean handlePagedViewIconKeyEvent(PagedViewIcon v, int keyCode, KeyEvent e) {
Winson Chung97d85d22011-04-13 11:27:36 -0700274 final PagedViewCellLayoutChildren parent = (PagedViewCellLayoutChildren) v.getParent();
275 final PagedViewCellLayout parentLayout = (PagedViewCellLayout) parent.getParent();
276 // Note we have an extra parent because of the
277 // PagedViewCellLayout/PagedViewCellLayoutChildren relationship
278 final ViewGroup container = (ViewGroup) parentLayout.getParent();
279 final TabHost tabHost = findTabHostParent(container);
280 final TabWidget tabs = (TabWidget) tabHost.findViewById(com.android.internal.R.id.tabs);
281 final int widgetIndex = parent.indexOfChild(v);
282 final int widgetCount = parent.getChildCount();
283 final int pageIndex = container.indexOfChild(parentLayout);
284 final int pageCount = container.getChildCount();
285 final int cellCountX = parentLayout.getCellCountX();
286 final int cellCountY = parentLayout.getCellCountY();
287 final int x = widgetIndex % cellCountX;
288 final int y = widgetIndex / cellCountX;
289
290 final int action = e.getAction();
291 final boolean handleKeyEvent = (action != KeyEvent.ACTION_UP);
292 PagedViewCellLayoutChildren newParent = null;
293 boolean wasHandled = false;
294 switch (keyCode) {
295 case KeyEvent.KEYCODE_DPAD_LEFT:
296 if (handleKeyEvent) {
297 // Select the previous icon or the last icon on the previous page
298 if (widgetIndex > 0) {
299 parent.getChildAt(widgetIndex - 1).requestFocus();
300 } else {
301 if (pageIndex > 0) {
302 newParent = getPagedViewCellLayoutChildrenForIndex(container,
303 pageIndex - 1);
304 newParent.getChildAt(newParent.getChildCount() - 1).requestFocus();
305 }
306 }
307 }
308 wasHandled = true;
309 break;
310 case KeyEvent.KEYCODE_DPAD_RIGHT:
311 if (handleKeyEvent) {
312 // Select the next icon or the first icon on the next page
313 if (widgetIndex < (widgetCount - 1)) {
314 parent.getChildAt(widgetIndex + 1).requestFocus();
315 } else {
316 if (pageIndex < (pageCount - 1)) {
317 newParent = getPagedViewCellLayoutChildrenForIndex(container,
318 pageIndex + 1);
319 newParent.getChildAt(0).requestFocus();
320 }
321 }
322 }
323 wasHandled = true;
324 break;
325 case KeyEvent.KEYCODE_DPAD_UP:
326 if (handleKeyEvent) {
327 // Select the closest icon in the previous row, otherwise select the tab bar
328 if (y > 0) {
329 int newWidgetIndex = ((y - 1) * cellCountX) + x;
330 parent.getChildAt(newWidgetIndex).requestFocus();
331 } else {
332 tabs.requestFocus();
333 }
334 }
335 wasHandled = true;
336 break;
337 case KeyEvent.KEYCODE_DPAD_DOWN:
338 if (handleKeyEvent) {
339 // Select the closest icon in the previous row, otherwise do nothing
340 if (y < (cellCountY - 1)) {
341 int newWidgetIndex = Math.min(widgetCount - 1, ((y + 1) * cellCountX) + x);
342 parent.getChildAt(newWidgetIndex).requestFocus();
343 }
344 }
345 wasHandled = true;
346 break;
347 case KeyEvent.KEYCODE_ENTER:
348 case KeyEvent.KEYCODE_DPAD_CENTER:
349 if (handleKeyEvent) {
350 // Simulate a click on the icon
351 View.OnClickListener clickListener = (View.OnClickListener) container;
352 clickListener.onClick(v);
353 }
354 wasHandled = true;
355 break;
356 case KeyEvent.KEYCODE_PAGE_UP:
357 if (handleKeyEvent) {
358 // Select the first icon on the previous page, or the first icon on this page
359 // if there is no previous page
360 if (pageIndex > 0) {
361 newParent = getPagedViewCellLayoutChildrenForIndex(container,
362 pageIndex - 1);
363 newParent.getChildAt(0).requestFocus();
364 } else {
365 parent.getChildAt(0).requestFocus();
366 }
367 }
368 wasHandled = true;
369 break;
370 case KeyEvent.KEYCODE_PAGE_DOWN:
371 if (handleKeyEvent) {
372 // Select the first icon on the next page, or the last icon on this page
373 // if there is no next page
374 if (pageIndex < (pageCount - 1)) {
375 newParent = getPagedViewCellLayoutChildrenForIndex(container,
376 pageIndex + 1);
377 newParent.getChildAt(0).requestFocus();
378 } else {
379 parent.getChildAt(widgetCount - 1).requestFocus();
380 }
381 }
382 wasHandled = true;
383 break;
384 case KeyEvent.KEYCODE_MOVE_HOME:
385 if (handleKeyEvent) {
386 // Select the first icon on this page
387 parent.getChildAt(0).requestFocus();
388 }
389 wasHandled = true;
390 break;
391 case KeyEvent.KEYCODE_MOVE_END:
392 if (handleKeyEvent) {
393 // Select the last icon on this page
394 parent.getChildAt(widgetCount - 1).requestFocus();
395 }
396 wasHandled = true;
397 break;
398 default: break;
399 }
400 return wasHandled;
401 }
402
403 /**
404 * Handles key events in the tab widget.
405 */
406 static boolean handleTabKeyEvent(AccessibleTabView v, int keyCode, KeyEvent e) {
Michael Jurkaa2eb1702011-05-12 14:57:05 -0700407 if (!LauncherApplication.isScreenLarge()) return false;
Winson Chung97d85d22011-04-13 11:27:36 -0700408
409 final FocusOnlyTabWidget parent = (FocusOnlyTabWidget) v.getParent();
410 final TabHost tabHost = findTabHostParent(parent);
411 final ViewGroup contents = (ViewGroup)
412 tabHost.findViewById(com.android.internal.R.id.tabcontent);
413 final int tabCount = parent.getTabCount();
414 final int tabIndex = parent.getChildTabIndex(v);
415
416 final int action = e.getAction();
417 final boolean handleKeyEvent = (action != KeyEvent.ACTION_UP);
418 boolean wasHandled = false;
419 switch (keyCode) {
420 case KeyEvent.KEYCODE_DPAD_LEFT:
421 if (handleKeyEvent) {
422 // Select the previous tab
423 if (tabIndex > 0) {
424 parent.getChildTabViewAt(tabIndex - 1).requestFocus();
425 }
426 }
427 wasHandled = true;
428 break;
429 case KeyEvent.KEYCODE_DPAD_RIGHT:
430 if (handleKeyEvent) {
431 // Select the next tab, or if the last tab has a focus right id, select that
432 if (tabIndex < (tabCount - 1)) {
433 parent.getChildTabViewAt(tabIndex + 1).requestFocus();
434 } else {
435 if (v.getNextFocusRightId() != View.NO_ID) {
436 tabHost.findViewById(v.getNextFocusRightId()).requestFocus();
437 }
438 }
439 }
440 wasHandled = true;
441 break;
442 case KeyEvent.KEYCODE_DPAD_UP:
443 // Do nothing
444 wasHandled = true;
445 break;
446 case KeyEvent.KEYCODE_DPAD_DOWN:
447 if (handleKeyEvent) {
448 // Select the content view
449 contents.requestFocus();
450 }
451 wasHandled = true;
452 break;
453 default: break;
454 }
455 return wasHandled;
456 }
457
458 /**
Winson Chung4e6a9762011-05-09 11:56:34 -0700459 * Handles key events in the workspace button bar.
Winson Chung97d85d22011-04-13 11:27:36 -0700460 */
461 static boolean handleButtonBarButtonKeyEvent(View v, int keyCode, KeyEvent e) {
Michael Jurkaa2eb1702011-05-12 14:57:05 -0700462 if (!LauncherApplication.isScreenLarge()) return false;
Winson Chung97d85d22011-04-13 11:27:36 -0700463
464 final ViewGroup parent = (ViewGroup) v.getParent();
465 final ViewGroup launcher = (ViewGroup) parent.getParent();
466 final Workspace workspace = (Workspace) launcher.findViewById(R.id.workspace);
467 final int buttonIndex = parent.indexOfChild(v);
468 final int buttonCount = parent.getChildCount();
469 final int pageIndex = workspace.getCurrentPage();
470 final int pageCount = workspace.getChildCount();
471 final int firstButtonIndex = parent.indexOfChild(parent.findViewById(R.id.search_button));
472 final int lastButtonIndex = parent.indexOfChild(parent.findViewById(R.id.configure_button));
473
474 final int action = e.getAction();
475 final boolean handleKeyEvent = (action != KeyEvent.ACTION_UP);
476 boolean wasHandled = false;
477 switch (keyCode) {
478 case KeyEvent.KEYCODE_DPAD_LEFT:
479 if (handleKeyEvent) {
480 // Select the previous button, otherwise do nothing (since the button bar is
481 // static)
482 if (buttonIndex > firstButtonIndex) {
483 int newButtonIndex = buttonIndex - 1;
484 while (newButtonIndex >= firstButtonIndex) {
485 View prev = parent.getChildAt(newButtonIndex);
486 if (isVisible(prev) && prev.isFocusable()) {
487 prev.requestFocus();
488 break;
489 }
490 --newButtonIndex;
491 }
492 } else {
493 if (pageIndex > 0) {
494 // Snap to previous page and clear focus
495 workspace.snapToPage(pageIndex - 1);
496 }
497 }
498 }
499 wasHandled = true;
500 break;
501 case KeyEvent.KEYCODE_DPAD_RIGHT:
502 if (handleKeyEvent) {
503 // Select the next button, otherwise do nothing (since the button bar is
504 // static)
505 if (buttonIndex < lastButtonIndex) {
506 int newButtonIndex = buttonIndex + 1;
507 while (newButtonIndex <= lastButtonIndex) {
508 View next = parent.getChildAt(newButtonIndex);
509 if (isVisible(next) && next.isFocusable()) {
510 next.requestFocus();
511 break;
512 }
513 ++newButtonIndex;
514 }
515 } else {
516 if (pageIndex < (pageCount - 1)) {
517 // Snap to next page and clear focus
518 workspace.snapToPage(pageIndex + 1);
519 }
520 }
521 }
522 wasHandled = true;
523 break;
524 case KeyEvent.KEYCODE_DPAD_UP:
525 // Do nothing
526 wasHandled = true;
527 break;
528 case KeyEvent.KEYCODE_DPAD_DOWN:
529 if (handleKeyEvent) {
530 // Select the first bubble text view in the current page of the workspace
531 final CellLayout layout = (CellLayout) workspace.getChildAt(pageIndex);
532 final CellLayoutChildren children = layout.getChildrenLayout();
533 final View newIcon = getBubbleTextViewInDirection(layout, children, -1, 1);
534 if (newIcon != null) {
535 newIcon.requestFocus();
536 } else {
537 workspace.requestFocus();
538 }
539 }
540 wasHandled = true;
541 break;
542 default: break;
543 }
544 return wasHandled;
545 }
546
547 /**
Winson Chung4e6a9762011-05-09 11:56:34 -0700548 * Handles key events in the prev/next indicators.
549 */
550 static boolean handleIndicatorButtonKeyEvent(View v, int keyCode, KeyEvent e) {
551 final ViewGroup launcher = (ViewGroup) v.getParent();
552 final Workspace workspace = (Workspace) launcher.findViewById(R.id.workspace);
553 final ViewGroup hotseat = (ViewGroup) launcher.findViewById(R.id.all_apps_button_cluster);
554 final View previousIndicator = launcher.findViewById(R.id.previous_screen);
555 final View nextIndicator = launcher.findViewById(R.id.next_screen);
556 final int pageIndex = workspace.getCurrentPage();
557 final int pageCount = workspace.getChildCount();
558
559 final int action = e.getAction();
560 final boolean handleKeyEvent = (action != KeyEvent.ACTION_UP);
561 boolean wasHandled = false;
562 switch (keyCode) {
563 case KeyEvent.KEYCODE_DPAD_LEFT:
564 if (handleKeyEvent) {
565 if (v == previousIndicator) {
566 if (pageIndex > 0) {
567 // Snap to previous page and clear focus
568 workspace.snapToPage(pageIndex - 1);
569 }
570 } else if (v == nextIndicator) {
571 // Select the last button in the hot seat
572 hotseat.getChildAt(hotseat.getChildCount() - 1).requestFocus();
573 }
574 }
575 wasHandled = true;
576 break;
577 case KeyEvent.KEYCODE_DPAD_RIGHT:
578 if (handleKeyEvent) {
579 if (v == previousIndicator) {
580 // Select the first button in the hot seat
581 hotseat.getChildAt(0).requestFocus();
582 } else if (v == nextIndicator) {
583 if (pageIndex < (pageCount - 1)) {
584 // Snap to next page and clear focus
585 workspace.snapToPage(pageIndex + 1);
586 }
587 }
588 }
589 wasHandled = true;
590 break;
591 case KeyEvent.KEYCODE_DPAD_UP:
592 if (handleKeyEvent) {
593 // Select the first bubble text view in the current page of the workspace
594 final CellLayout layout = (CellLayout) workspace.getChildAt(pageIndex);
595 final CellLayoutChildren children = layout.getChildrenLayout();
596 final View newIcon = getBubbleTextViewInDirection(layout, children, -1, 1);
597 if (newIcon != null) {
598 newIcon.requestFocus();
599 } else {
600 workspace.requestFocus();
601 }
602 }
603 wasHandled = true;
604 break;
605 case KeyEvent.KEYCODE_DPAD_DOWN:
606 // Do nothing
607 wasHandled = true;
608 break;
609 default: break;
610 }
611 return wasHandled;
612 }
613
614 /**
615 * Handles key events in the workspace dock (bottom of the screen).
616 */
617 static boolean handleDockButtonKeyEvent(View v, int keyCode, KeyEvent e, int orientation) {
618 final ViewGroup parent = (ViewGroup) v.getParent();
619 final ViewGroup launcher = (ViewGroup) parent.getParent();
620 final Workspace workspace = (Workspace) launcher.findViewById(R.id.workspace);
621 final int buttonIndex = parent.indexOfChild(v);
622 final int buttonCount = parent.getChildCount();
623 final int pageIndex = workspace.getCurrentPage();
624 final int pageCount = workspace.getChildCount();
625 final View previousIndicator = launcher.findViewById(R.id.previous_screen);
626 final View nextIndicator = launcher.findViewById(R.id.next_screen);
627
628 // NOTE: currently we don't special case for the phone UI in different
629 // orientations, even though the dock is on the side in landscape mode. This
630 // is to ensure that accessibility consistency is maintained across rotations.
631
632 final int action = e.getAction();
633 final boolean handleKeyEvent = (action != KeyEvent.ACTION_UP);
634 boolean wasHandled = false;
635 switch (keyCode) {
636 case KeyEvent.KEYCODE_DPAD_LEFT:
637 if (handleKeyEvent) {
638
639 // Select the previous button, otherwise select the previous page indicator
640 if (buttonIndex > 0) {
641 parent.getChildAt(buttonIndex - 1).requestFocus();
642 } else {
643 previousIndicator.requestFocus();
644 }
645 }
646 wasHandled = true;
647 break;
648 case KeyEvent.KEYCODE_DPAD_RIGHT:
649 if (handleKeyEvent) {
650 // Select the next button, otherwise select the next page indicator
651 if (buttonIndex < (buttonCount - 1)) {
652 parent.getChildAt(buttonIndex + 1).requestFocus();
653 } else {
654 nextIndicator.requestFocus();
655 }
656 }
657 wasHandled = true;
658 break;
659 case KeyEvent.KEYCODE_DPAD_UP:
660 if (handleKeyEvent) {
661 // Select the first bubble text view in the current page of the workspace
662 final CellLayout layout = (CellLayout) workspace.getChildAt(pageIndex);
663 final CellLayoutChildren children = layout.getChildrenLayout();
664 final View newIcon = getBubbleTextViewInDirection(layout, children, -1, 1);
665 if (newIcon != null) {
666 newIcon.requestFocus();
667 } else {
668 workspace.requestFocus();
669 }
670 }
671 wasHandled = true;
672 break;
673 case KeyEvent.KEYCODE_DPAD_DOWN:
674 // Do nothing
675 wasHandled = true;
676 break;
677 default: break;
678 }
679 return wasHandled;
680 }
681
682 /**
Winson Chung97d85d22011-04-13 11:27:36 -0700683 * Private helper method to get the CellLayoutChildren given a CellLayout index.
684 */
685 private static CellLayoutChildren getCellLayoutChildrenForIndex(ViewGroup container, int i) {
686 ViewGroup parent = (ViewGroup) container.getChildAt(i);
687 return (CellLayoutChildren) parent.getChildAt(0);
688 }
689
690 /**
691 * Private helper method to sort all the CellLayout children in order of their (x,y) spatially
692 * from top left to bottom right.
693 */
694 private static ArrayList<View> getCellLayoutChildrenSortedSpatially(CellLayout layout,
695 ViewGroup parent) {
696 // First we order each the CellLayout children by their x,y coordinates
697 final int cellCountX = layout.getCountX();
698 final int count = parent.getChildCount();
699 ArrayList<View> views = new ArrayList<View>();
700 for (int j = 0; j < count; ++j) {
701 views.add(parent.getChildAt(j));
702 }
703 Collections.sort(views, new Comparator<View>() {
704 @Override
705 public int compare(View lhs, View rhs) {
706 CellLayout.LayoutParams llp = (CellLayout.LayoutParams) lhs.getLayoutParams();
707 CellLayout.LayoutParams rlp = (CellLayout.LayoutParams) rhs.getLayoutParams();
708 int lvIndex = (llp.cellY * cellCountX) + llp.cellX;
709 int rvIndex = (rlp.cellY * cellCountX) + rlp.cellX;
710 return lvIndex - rvIndex;
711 }
712 });
713 return views;
714 }
715 /**
716 * Private helper method to find the index of the next BubbleTextView in the delta direction.
717 * @param delta either -1 or 1 depending on the direction we want to search
718 */
719 private static View findIndexOfBubbleTextView(ArrayList<View> views, int i, int delta) {
720 // Then we find the next BubbleTextView offset by delta from i
721 final int count = views.size();
722 int newI = i + delta;
723 while (0 <= newI && newI < count) {
724 View newV = views.get(newI);
725 if (newV instanceof BubbleTextView) {
726 return newV;
727 }
728 newI += delta;
729 }
730 return null;
731 }
732 private static View getBubbleTextViewInDirection(CellLayout layout, ViewGroup parent, int i,
733 int delta) {
734 final ArrayList<View> views = getCellLayoutChildrenSortedSpatially(layout, parent);
735 return findIndexOfBubbleTextView(views, i, delta);
736 }
737 private static View getBubbleTextViewInDirection(CellLayout layout, ViewGroup parent, View v,
738 int delta) {
739 final ArrayList<View> views = getCellLayoutChildrenSortedSpatially(layout, parent);
740 return findIndexOfBubbleTextView(views, views.indexOf(v), delta);
741 }
742 /**
743 * Private helper method to find the next closest BubbleTextView in the delta direction on the
744 * next line.
745 * @param delta either -1 or 1 depending on the line and direction we want to search
746 */
747 private static View getClosestBubbleTextViewOnLine(CellLayout layout, ViewGroup parent, View v,
748 int lineDelta) {
749 final ArrayList<View> views = getCellLayoutChildrenSortedSpatially(layout, parent);
750 final CellLayout.LayoutParams lp = (CellLayout.LayoutParams) v.getLayoutParams();
751 final int cellCountX = layout.getCountX();
752 final int cellCountY = layout.getCountY();
753 final int row = lp.cellY;
754 final int newRow = row + lineDelta;
755 if (0 <= newRow && newRow < cellCountY) {
756 float closestDistance = Float.MAX_VALUE;
757 int closestIndex = -1;
758 int index = views.indexOf(v);
759 int endIndex = (lineDelta < 0) ? -1 : views.size();
760 while (index != endIndex) {
761 View newV = views.get(index);
762 CellLayout.LayoutParams tmpLp = (CellLayout.LayoutParams) newV.getLayoutParams();
763 boolean satisfiesRow = (lineDelta < 0) ? (tmpLp.cellY < row) : (tmpLp.cellY > row);
764 if (satisfiesRow && newV instanceof BubbleTextView) {
765 float tmpDistance = (float) Math.sqrt(Math.pow(tmpLp.cellX - lp.cellX, 2) +
766 Math.pow(tmpLp.cellY - lp.cellY, 2));
767 if (tmpDistance < closestDistance) {
768 closestIndex = index;
769 closestDistance = tmpDistance;
770 }
771 }
772 if (index <= endIndex) {
773 ++index;
774 } else {
775 --index;
776 }
777 }
778 if (closestIndex > -1) {
779 return views.get(closestIndex);
780 }
781 }
782 return null;
783 }
784
785 /**
786 * Handles key events in a Workspace containing BubbleTextView.
787 */
788 static boolean handleBubbleTextViewKeyEvent(BubbleTextView v, int keyCode, KeyEvent e) {
Winson Chung97d85d22011-04-13 11:27:36 -0700789 CellLayoutChildren parent = (CellLayoutChildren) v.getParent();
790 final CellLayout layout = (CellLayout) parent.getParent();
791 final Workspace workspace = (Workspace) layout.getParent();
792 final ViewGroup launcher = (ViewGroup) workspace.getParent();
Winson Chungfaa13252011-06-13 18:15:54 -0700793 final ViewGroup tabs = (ViewGroup) launcher.findViewById(R.id.qsb_bar);
Winson Chung97d85d22011-04-13 11:27:36 -0700794 int iconIndex = parent.indexOfChild(v);
795 int iconCount = parent.getChildCount();
796 int pageIndex = workspace.indexOfChild(layout);
797 int pageCount = workspace.getChildCount();
798
799 final int action = e.getAction();
800 final boolean handleKeyEvent = (action != KeyEvent.ACTION_UP);
801 boolean wasHandled = false;
802 switch (keyCode) {
803 case KeyEvent.KEYCODE_DPAD_LEFT:
804 if (handleKeyEvent) {
805 // Select the previous icon or the last icon on the previous page if possible
806 View newIcon = getBubbleTextViewInDirection(layout, parent, v, -1);
807 if (newIcon != null) {
808 newIcon.requestFocus();
809 } else {
810 if (pageIndex > 0) {
811 parent = getCellLayoutChildrenForIndex(workspace, pageIndex - 1);
812 newIcon = getBubbleTextViewInDirection(layout, parent,
813 parent.getChildCount(), -1);
814 if (newIcon != null) {
815 newIcon.requestFocus();
816 } else {
817 // Snap to the previous page
818 workspace.snapToPage(pageIndex - 1);
819 }
820 }
821 }
822 }
823 wasHandled = true;
824 break;
825 case KeyEvent.KEYCODE_DPAD_RIGHT:
826 if (handleKeyEvent) {
827 // Select the next icon or the first icon on the next page if possible
828 View newIcon = getBubbleTextViewInDirection(layout, parent, v, 1);
829 if (newIcon != null) {
830 newIcon.requestFocus();
831 } else {
832 if (pageIndex < (pageCount - 1)) {
833 parent = getCellLayoutChildrenForIndex(workspace, pageIndex + 1);
834 newIcon = getBubbleTextViewInDirection(layout, parent, -1, 1);
835 if (newIcon != null) {
836 newIcon.requestFocus();
837 } else {
838 // Snap to the next page
839 workspace.snapToPage(pageIndex + 1);
840 }
841 }
842 }
843 }
844 wasHandled = true;
845 break;
846 case KeyEvent.KEYCODE_DPAD_UP:
847 if (handleKeyEvent) {
848 // Select the closest icon in the previous line, otherwise select the tab bar
849 View newIcon = getClosestBubbleTextViewOnLine(layout, parent, v, -1);
850 if (newIcon != null) {
851 newIcon.requestFocus();
852 wasHandled = true;
853 } else {
854 tabs.requestFocus();
855 }
856 }
857 break;
858 case KeyEvent.KEYCODE_DPAD_DOWN:
859 if (handleKeyEvent) {
860 // Select the closest icon in the next line, otherwise select the tab bar
861 View newIcon = getClosestBubbleTextViewOnLine(layout, parent, v, 1);
862 if (newIcon != null) {
863 newIcon.requestFocus();
864 wasHandled = true;
865 }
866 }
867 break;
868 case KeyEvent.KEYCODE_PAGE_UP:
869 if (handleKeyEvent) {
870 // Select the first icon on the previous page or the first icon on this page
871 // if there is no previous page
872 if (pageIndex > 0) {
873 parent = getCellLayoutChildrenForIndex(workspace, pageIndex - 1);
874 View newIcon = getBubbleTextViewInDirection(layout, parent, -1, 1);
875 if (newIcon != null) {
876 newIcon.requestFocus();
877 } else {
878 // Snap to the previous page
879 workspace.snapToPage(pageIndex - 1);
880 }
881 } else {
882 View newIcon = getBubbleTextViewInDirection(layout, parent, -1, 1);
883 if (newIcon != null) {
884 newIcon.requestFocus();
885 }
886 }
887 }
888 wasHandled = true;
889 break;
890 case KeyEvent.KEYCODE_PAGE_DOWN:
891 if (handleKeyEvent) {
892 // Select the first icon on the next page or the last icon on this page
893 // if there is no previous page
894 if (pageIndex < (pageCount - 1)) {
895 parent = getCellLayoutChildrenForIndex(workspace, pageIndex + 1);
896 View newIcon = getBubbleTextViewInDirection(layout, parent, -1, 1);
897 if (newIcon != null) {
898 newIcon.requestFocus();
899 } else {
900 // Snap to the next page
901 workspace.snapToPage(pageIndex + 1);
902 }
903 } else {
904 View newIcon = getBubbleTextViewInDirection(layout, parent,
905 parent.getChildCount(), -1);
906 if (newIcon != null) {
907 newIcon.requestFocus();
908 }
909 }
910 }
911 wasHandled = true;
912 break;
913 case KeyEvent.KEYCODE_MOVE_HOME:
914 if (handleKeyEvent) {
915 // Select the first icon on this page
916 View newIcon = getBubbleTextViewInDirection(layout, parent, -1, 1);
917 if (newIcon != null) {
918 newIcon.requestFocus();
919 }
920 }
921 wasHandled = true;
922 break;
923 case KeyEvent.KEYCODE_MOVE_END:
924 if (handleKeyEvent) {
925 // Select the last icon on this page
926 View newIcon = getBubbleTextViewInDirection(layout, parent,
927 parent.getChildCount(), -1);
928 if (newIcon != null) {
929 newIcon.requestFocus();
930 }
931 }
932 wasHandled = true;
933 break;
934 default: break;
935 }
936 return wasHandled;
937 }
938}