blob: d45c9ac15a66ebabfb63ad53922a7ad007881a16 [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 all the dock buttons.
45 */
46class DockKeyEventListener implements View.OnKeyListener {
47 @Override
48 public boolean onKey(View v, int keyCode, KeyEvent event) {
49 final Configuration configuration = v.getResources().getConfiguration();
50 return FocusHelper.handleDockButtonKeyEvent(v, keyCode, event, configuration.orientation);
51 }
52}
53
54/**
Winson Chungfaa13252011-06-13 18:15:54 -070055 * A keyboard listener we set on the last tab button in AppsCustomize to jump to then
Winson Chung97d85d22011-04-13 11:27:36 -070056 * market icon and vice versa.
57 */
Winson Chungfaa13252011-06-13 18:15:54 -070058class AppsCustomizeTabKeyEventListener implements View.OnKeyListener {
Winson Chung97d85d22011-04-13 11:27:36 -070059 @Override
60 public boolean onKey(View v, int keyCode, KeyEvent event) {
Winson Chungfaa13252011-06-13 18:15:54 -070061 return FocusHelper.handleAppsCustomizeTabKeyEvent(v, keyCode, event);
Winson Chung97d85d22011-04-13 11:27:36 -070062 }
63}
64
65public class FocusHelper {
66 /**
67 * Private helper to get the parent TabHost in the view hiearchy.
68 */
69 private static TabHost findTabHostParent(View v) {
70 ViewParent p = v.getParent();
71 while (p != null && !(p instanceof TabHost)) {
72 p = p.getParent();
73 }
74 return (TabHost) p;
75 }
76
77 /**
Winson Chungfaa13252011-06-13 18:15:54 -070078 * Handles key events in a AppsCustomize tab between the last tab view and the shop button.
Winson Chung97d85d22011-04-13 11:27:36 -070079 */
Winson Chungfaa13252011-06-13 18:15:54 -070080 static boolean handleAppsCustomizeTabKeyEvent(View v, int keyCode, KeyEvent e) {
Winson Chung97d85d22011-04-13 11:27:36 -070081 final TabHost tabHost = findTabHostParent(v);
82 final ViewGroup contents = (ViewGroup)
83 tabHost.findViewById(com.android.internal.R.id.tabcontent);
84 final View shop = tabHost.findViewById(R.id.market_button);
85
86 final int action = e.getAction();
87 final boolean handleKeyEvent = (action != KeyEvent.ACTION_UP);
88 boolean wasHandled = false;
89 switch (keyCode) {
90 case KeyEvent.KEYCODE_DPAD_RIGHT:
91 if (handleKeyEvent) {
92 // Select the shop button if we aren't on it
93 if (v != shop) {
94 shop.requestFocus();
95 }
96 }
97 wasHandled = true;
98 break;
99 case KeyEvent.KEYCODE_DPAD_DOWN:
100 if (handleKeyEvent) {
101 // Select the content view (down is handled by the tab key handler otherwise)
102 if (v == shop) {
103 contents.requestFocus();
104 wasHandled = true;
105 }
106 }
107 break;
108 default: break;
109 }
110 return wasHandled;
111 }
112
113 /**
114 * Private helper to determine whether a view is visible.
115 */
116 private static boolean isVisible(View v) {
117 return v.getVisibility() == View.VISIBLE;
118 }
119
120 /**
121 * Handles key events in a PageViewExtendedLayout containing PagedViewWidgets.
Winson Chung4e6a9762011-05-09 11:56:34 -0700122 */
123 static boolean handlePagedViewGridLayoutWidgetKeyEvent(PagedViewWidget w, int keyCode,
124 KeyEvent e) {
125
126 final PagedViewGridLayout parent = (PagedViewGridLayout) w.getParent();
127 final ViewGroup container = (ViewGroup) parent.getParent();
128 final TabHost tabHost = findTabHostParent(container);
129 final TabWidget tabs = (TabWidget) tabHost.findViewById(com.android.internal.R.id.tabs);
130 final int widgetIndex = parent.indexOfChild(w);
131 final int widgetCount = parent.getChildCount();
132 final int pageIndex = container.indexOfChild(parent);
133 final int pageCount = container.getChildCount();
134 final int cellCountX = parent.getCellCountX();
135 final int cellCountY = parent.getCellCountY();
136 final int x = widgetIndex % cellCountX;
137 final int y = widgetIndex / cellCountX;
138
139 final int action = e.getAction();
140 final boolean handleKeyEvent = (action != KeyEvent.ACTION_UP);
141 PagedViewGridLayout newParent = null;
142 boolean wasHandled = false;
143 switch (keyCode) {
144 case KeyEvent.KEYCODE_DPAD_LEFT:
145 if (handleKeyEvent) {
146 // Select the previous widget or the last widget on the previous page
147 if (widgetIndex > 0) {
148 parent.getChildAt(widgetIndex - 1).requestFocus();
149 } else {
150 if (pageIndex > 0) {
151 newParent = (PagedViewGridLayout)
152 container.getChildAt(pageIndex - 1);
153 newParent.getChildAt(newParent.getChildCount() - 1).requestFocus();
154 }
155 }
156 }
157 wasHandled = true;
158 break;
159 case KeyEvent.KEYCODE_DPAD_RIGHT:
160 if (handleKeyEvent) {
161 // Select the next widget or the first widget on the next page
162 if (widgetIndex < (widgetCount - 1)) {
163 parent.getChildAt(widgetIndex + 1).requestFocus();
164 } else {
165 if (pageIndex < (pageCount - 1)) {
166 newParent = (PagedViewGridLayout)
167 container.getChildAt(pageIndex + 1);
168 newParent.getChildAt(0).requestFocus();
169 }
170 }
171 }
172 wasHandled = true;
173 break;
174 case KeyEvent.KEYCODE_DPAD_UP:
175 if (handleKeyEvent) {
176 // Select the closest icon in the previous row, otherwise select the tab bar
177 if (y > 0) {
178 int newWidgetIndex = ((y - 1) * cellCountX) + x;
179 parent.getChildAt(newWidgetIndex).requestFocus();
180 } else {
181 tabs.requestFocus();
182 }
183 }
184 wasHandled = true;
185 break;
186 case KeyEvent.KEYCODE_DPAD_DOWN:
187 if (handleKeyEvent) {
188 // Select the closest icon in the previous row, otherwise do nothing
189 if (y < (cellCountY - 1)) {
190 int newWidgetIndex = Math.min(widgetCount - 1, ((y + 1) * cellCountX) + x);
191 parent.getChildAt(newWidgetIndex).requestFocus();
192 }
193 }
194 wasHandled = true;
195 break;
196 case KeyEvent.KEYCODE_ENTER:
197 case KeyEvent.KEYCODE_DPAD_CENTER:
198 if (handleKeyEvent) {
199 // Simulate a click on the widget
200 View.OnClickListener clickListener = (View.OnClickListener) container;
201 clickListener.onClick(w);
202 }
203 wasHandled = true;
204 break;
205 case KeyEvent.KEYCODE_PAGE_UP:
206 if (handleKeyEvent) {
207 // Select the first item on the previous page, or the first item on this page
208 // if there is no previous page
209 if (pageIndex > 0) {
210 newParent = (PagedViewGridLayout) container.getChildAt(pageIndex - 1);
211 newParent.getChildAt(0).requestFocus();
212 } else {
213 parent.getChildAt(0).requestFocus();
214 }
215 }
216 wasHandled = true;
217 break;
218 case KeyEvent.KEYCODE_PAGE_DOWN:
219 if (handleKeyEvent) {
220 // Select the first item on the next page, or the last item on this page
221 // if there is no next page
222 if (pageIndex < (pageCount - 1)) {
223 newParent = (PagedViewGridLayout) container.getChildAt(pageIndex + 1);
224 newParent.getChildAt(0).requestFocus();
225 } else {
226 parent.getChildAt(widgetCount - 1).requestFocus();
227 }
228 }
229 wasHandled = true;
230 break;
231 case KeyEvent.KEYCODE_MOVE_HOME:
232 if (handleKeyEvent) {
233 // Select the first item on this page
234 parent.getChildAt(0).requestFocus();
235 }
236 wasHandled = true;
237 break;
238 case KeyEvent.KEYCODE_MOVE_END:
239 if (handleKeyEvent) {
240 // Select the last item on this page
241 parent.getChildAt(widgetCount - 1).requestFocus();
242 }
243 wasHandled = true;
244 break;
245 default: break;
246 }
247 return wasHandled;
248 }
249
250 /**
Winson Chung97d85d22011-04-13 11:27:36 -0700251 * Private helper method to get the PagedViewCellLayoutChildren given a PagedViewCellLayout
252 * index.
253 */
254 private static PagedViewCellLayoutChildren getPagedViewCellLayoutChildrenForIndex(
255 ViewGroup container, int i) {
256 ViewGroup parent = (ViewGroup) container.getChildAt(i);
257 return (PagedViewCellLayoutChildren) parent.getChildAt(0);
258 }
259
260 /**
261 * Handles key events in a PageViewCellLayout containing PagedViewIcons.
262 */
263 static boolean handlePagedViewIconKeyEvent(PagedViewIcon v, int keyCode, KeyEvent e) {
Winson Chung97d85d22011-04-13 11:27:36 -0700264 final PagedViewCellLayoutChildren parent = (PagedViewCellLayoutChildren) v.getParent();
265 final PagedViewCellLayout parentLayout = (PagedViewCellLayout) parent.getParent();
266 // Note we have an extra parent because of the
267 // PagedViewCellLayout/PagedViewCellLayoutChildren relationship
268 final ViewGroup container = (ViewGroup) parentLayout.getParent();
269 final TabHost tabHost = findTabHostParent(container);
270 final TabWidget tabs = (TabWidget) tabHost.findViewById(com.android.internal.R.id.tabs);
271 final int widgetIndex = parent.indexOfChild(v);
272 final int widgetCount = parent.getChildCount();
273 final int pageIndex = container.indexOfChild(parentLayout);
274 final int pageCount = container.getChildCount();
275 final int cellCountX = parentLayout.getCellCountX();
276 final int cellCountY = parentLayout.getCellCountY();
277 final int x = widgetIndex % cellCountX;
278 final int y = widgetIndex / cellCountX;
279
280 final int action = e.getAction();
281 final boolean handleKeyEvent = (action != KeyEvent.ACTION_UP);
282 PagedViewCellLayoutChildren newParent = null;
283 boolean wasHandled = false;
284 switch (keyCode) {
285 case KeyEvent.KEYCODE_DPAD_LEFT:
286 if (handleKeyEvent) {
287 // Select the previous icon or the last icon on the previous page
288 if (widgetIndex > 0) {
289 parent.getChildAt(widgetIndex - 1).requestFocus();
290 } else {
291 if (pageIndex > 0) {
292 newParent = getPagedViewCellLayoutChildrenForIndex(container,
293 pageIndex - 1);
294 newParent.getChildAt(newParent.getChildCount() - 1).requestFocus();
295 }
296 }
297 }
298 wasHandled = true;
299 break;
300 case KeyEvent.KEYCODE_DPAD_RIGHT:
301 if (handleKeyEvent) {
302 // Select the next icon or the first icon on the next page
303 if (widgetIndex < (widgetCount - 1)) {
304 parent.getChildAt(widgetIndex + 1).requestFocus();
305 } else {
306 if (pageIndex < (pageCount - 1)) {
307 newParent = getPagedViewCellLayoutChildrenForIndex(container,
308 pageIndex + 1);
309 newParent.getChildAt(0).requestFocus();
310 }
311 }
312 }
313 wasHandled = true;
314 break;
315 case KeyEvent.KEYCODE_DPAD_UP:
316 if (handleKeyEvent) {
317 // Select the closest icon in the previous row, otherwise select the tab bar
318 if (y > 0) {
319 int newWidgetIndex = ((y - 1) * cellCountX) + x;
320 parent.getChildAt(newWidgetIndex).requestFocus();
321 } else {
322 tabs.requestFocus();
323 }
324 }
325 wasHandled = true;
326 break;
327 case KeyEvent.KEYCODE_DPAD_DOWN:
328 if (handleKeyEvent) {
329 // Select the closest icon in the previous row, otherwise do nothing
330 if (y < (cellCountY - 1)) {
331 int newWidgetIndex = Math.min(widgetCount - 1, ((y + 1) * cellCountX) + x);
332 parent.getChildAt(newWidgetIndex).requestFocus();
333 }
334 }
335 wasHandled = true;
336 break;
337 case KeyEvent.KEYCODE_ENTER:
338 case KeyEvent.KEYCODE_DPAD_CENTER:
339 if (handleKeyEvent) {
340 // Simulate a click on the icon
341 View.OnClickListener clickListener = (View.OnClickListener) container;
342 clickListener.onClick(v);
343 }
344 wasHandled = true;
345 break;
346 case KeyEvent.KEYCODE_PAGE_UP:
347 if (handleKeyEvent) {
348 // Select the first icon on the previous page, or the first icon on this page
349 // if there is no previous page
350 if (pageIndex > 0) {
351 newParent = getPagedViewCellLayoutChildrenForIndex(container,
352 pageIndex - 1);
353 newParent.getChildAt(0).requestFocus();
354 } else {
355 parent.getChildAt(0).requestFocus();
356 }
357 }
358 wasHandled = true;
359 break;
360 case KeyEvent.KEYCODE_PAGE_DOWN:
361 if (handleKeyEvent) {
362 // Select the first icon on the next page, or the last icon on this page
363 // if there is no next page
364 if (pageIndex < (pageCount - 1)) {
365 newParent = getPagedViewCellLayoutChildrenForIndex(container,
366 pageIndex + 1);
367 newParent.getChildAt(0).requestFocus();
368 } else {
369 parent.getChildAt(widgetCount - 1).requestFocus();
370 }
371 }
372 wasHandled = true;
373 break;
374 case KeyEvent.KEYCODE_MOVE_HOME:
375 if (handleKeyEvent) {
376 // Select the first icon on this page
377 parent.getChildAt(0).requestFocus();
378 }
379 wasHandled = true;
380 break;
381 case KeyEvent.KEYCODE_MOVE_END:
382 if (handleKeyEvent) {
383 // Select the last icon on this page
384 parent.getChildAt(widgetCount - 1).requestFocus();
385 }
386 wasHandled = true;
387 break;
388 default: break;
389 }
390 return wasHandled;
391 }
392
393 /**
394 * Handles key events in the tab widget.
395 */
396 static boolean handleTabKeyEvent(AccessibleTabView v, int keyCode, KeyEvent e) {
Michael Jurkaa2eb1702011-05-12 14:57:05 -0700397 if (!LauncherApplication.isScreenLarge()) return false;
Winson Chung97d85d22011-04-13 11:27:36 -0700398
399 final FocusOnlyTabWidget parent = (FocusOnlyTabWidget) v.getParent();
400 final TabHost tabHost = findTabHostParent(parent);
401 final ViewGroup contents = (ViewGroup)
402 tabHost.findViewById(com.android.internal.R.id.tabcontent);
403 final int tabCount = parent.getTabCount();
404 final int tabIndex = parent.getChildTabIndex(v);
405
406 final int action = e.getAction();
407 final boolean handleKeyEvent = (action != KeyEvent.ACTION_UP);
408 boolean wasHandled = false;
409 switch (keyCode) {
410 case KeyEvent.KEYCODE_DPAD_LEFT:
411 if (handleKeyEvent) {
412 // Select the previous tab
413 if (tabIndex > 0) {
414 parent.getChildTabViewAt(tabIndex - 1).requestFocus();
415 }
416 }
417 wasHandled = true;
418 break;
419 case KeyEvent.KEYCODE_DPAD_RIGHT:
420 if (handleKeyEvent) {
421 // Select the next tab, or if the last tab has a focus right id, select that
422 if (tabIndex < (tabCount - 1)) {
423 parent.getChildTabViewAt(tabIndex + 1).requestFocus();
424 } else {
425 if (v.getNextFocusRightId() != View.NO_ID) {
426 tabHost.findViewById(v.getNextFocusRightId()).requestFocus();
427 }
428 }
429 }
430 wasHandled = true;
431 break;
432 case KeyEvent.KEYCODE_DPAD_UP:
433 // Do nothing
434 wasHandled = true;
435 break;
436 case KeyEvent.KEYCODE_DPAD_DOWN:
437 if (handleKeyEvent) {
438 // Select the content view
439 contents.requestFocus();
440 }
441 wasHandled = true;
442 break;
443 default: break;
444 }
445 return wasHandled;
446 }
447
448 /**
Winson Chung4e6a9762011-05-09 11:56:34 -0700449 * Handles key events in the workspace button bar.
Winson Chung97d85d22011-04-13 11:27:36 -0700450 */
451 static boolean handleButtonBarButtonKeyEvent(View v, int keyCode, KeyEvent e) {
Michael Jurkaa2eb1702011-05-12 14:57:05 -0700452 if (!LauncherApplication.isScreenLarge()) return false;
Winson Chung97d85d22011-04-13 11:27:36 -0700453
454 final ViewGroup parent = (ViewGroup) v.getParent();
455 final ViewGroup launcher = (ViewGroup) parent.getParent();
456 final Workspace workspace = (Workspace) launcher.findViewById(R.id.workspace);
457 final int buttonIndex = parent.indexOfChild(v);
458 final int buttonCount = parent.getChildCount();
459 final int pageIndex = workspace.getCurrentPage();
460 final int pageCount = workspace.getChildCount();
461 final int firstButtonIndex = parent.indexOfChild(parent.findViewById(R.id.search_button));
462 final int lastButtonIndex = parent.indexOfChild(parent.findViewById(R.id.configure_button));
463
464 final int action = e.getAction();
465 final boolean handleKeyEvent = (action != KeyEvent.ACTION_UP);
466 boolean wasHandled = false;
467 switch (keyCode) {
468 case KeyEvent.KEYCODE_DPAD_LEFT:
469 if (handleKeyEvent) {
470 // Select the previous button, otherwise do nothing (since the button bar is
471 // static)
472 if (buttonIndex > firstButtonIndex) {
473 int newButtonIndex = buttonIndex - 1;
474 while (newButtonIndex >= firstButtonIndex) {
475 View prev = parent.getChildAt(newButtonIndex);
476 if (isVisible(prev) && prev.isFocusable()) {
477 prev.requestFocus();
478 break;
479 }
480 --newButtonIndex;
481 }
482 } else {
483 if (pageIndex > 0) {
484 // Snap to previous page and clear focus
485 workspace.snapToPage(pageIndex - 1);
486 }
487 }
488 }
489 wasHandled = true;
490 break;
491 case KeyEvent.KEYCODE_DPAD_RIGHT:
492 if (handleKeyEvent) {
493 // Select the next button, otherwise do nothing (since the button bar is
494 // static)
495 if (buttonIndex < lastButtonIndex) {
496 int newButtonIndex = buttonIndex + 1;
497 while (newButtonIndex <= lastButtonIndex) {
498 View next = parent.getChildAt(newButtonIndex);
499 if (isVisible(next) && next.isFocusable()) {
500 next.requestFocus();
501 break;
502 }
503 ++newButtonIndex;
504 }
505 } else {
506 if (pageIndex < (pageCount - 1)) {
507 // Snap to next page and clear focus
508 workspace.snapToPage(pageIndex + 1);
509 }
510 }
511 }
512 wasHandled = true;
513 break;
514 case KeyEvent.KEYCODE_DPAD_UP:
515 // Do nothing
516 wasHandled = true;
517 break;
518 case KeyEvent.KEYCODE_DPAD_DOWN:
519 if (handleKeyEvent) {
520 // Select the first bubble text view in the current page of the workspace
521 final CellLayout layout = (CellLayout) workspace.getChildAt(pageIndex);
522 final CellLayoutChildren children = layout.getChildrenLayout();
523 final View newIcon = getBubbleTextViewInDirection(layout, children, -1, 1);
524 if (newIcon != null) {
525 newIcon.requestFocus();
526 } else {
527 workspace.requestFocus();
528 }
529 }
530 wasHandled = true;
531 break;
532 default: break;
533 }
534 return wasHandled;
535 }
536
537 /**
Winson Chung4e6a9762011-05-09 11:56:34 -0700538 * Handles key events in the workspace dock (bottom of the screen).
539 */
540 static boolean handleDockButtonKeyEvent(View v, int keyCode, KeyEvent e, int orientation) {
541 final ViewGroup parent = (ViewGroup) v.getParent();
542 final ViewGroup launcher = (ViewGroup) parent.getParent();
543 final Workspace workspace = (Workspace) launcher.findViewById(R.id.workspace);
544 final int buttonIndex = parent.indexOfChild(v);
545 final int buttonCount = parent.getChildCount();
546 final int pageIndex = workspace.getCurrentPage();
547 final int pageCount = workspace.getChildCount();
Winson Chung4e6a9762011-05-09 11:56:34 -0700548
549 // NOTE: currently we don't special case for the phone UI in different
550 // orientations, even though the dock is on the side in landscape mode. This
551 // is to ensure that accessibility consistency is maintained across rotations.
552
553 final int action = e.getAction();
554 final boolean handleKeyEvent = (action != KeyEvent.ACTION_UP);
555 boolean wasHandled = false;
556 switch (keyCode) {
557 case KeyEvent.KEYCODE_DPAD_LEFT:
558 if (handleKeyEvent) {
Winson Chung74608b52011-06-23 13:23:20 -0700559 // Select the previous button, otherwise snap to the previous page
Winson Chung4e6a9762011-05-09 11:56:34 -0700560 if (buttonIndex > 0) {
561 parent.getChildAt(buttonIndex - 1).requestFocus();
562 } else {
Winson Chung74608b52011-06-23 13:23:20 -0700563 workspace.snapToPage(pageIndex - 1);
Winson Chung4e6a9762011-05-09 11:56:34 -0700564 }
565 }
566 wasHandled = true;
567 break;
568 case KeyEvent.KEYCODE_DPAD_RIGHT:
569 if (handleKeyEvent) {
Winson Chung74608b52011-06-23 13:23:20 -0700570 // Select the next button, otherwise snap to the next page
Winson Chung4e6a9762011-05-09 11:56:34 -0700571 if (buttonIndex < (buttonCount - 1)) {
572 parent.getChildAt(buttonIndex + 1).requestFocus();
573 } else {
Winson Chung74608b52011-06-23 13:23:20 -0700574 workspace.snapToPage(pageIndex + 1);
Winson Chung4e6a9762011-05-09 11:56:34 -0700575 }
576 }
577 wasHandled = true;
578 break;
579 case KeyEvent.KEYCODE_DPAD_UP:
580 if (handleKeyEvent) {
581 // Select the first bubble text view in the current page of the workspace
582 final CellLayout layout = (CellLayout) workspace.getChildAt(pageIndex);
583 final CellLayoutChildren children = layout.getChildrenLayout();
584 final View newIcon = getBubbleTextViewInDirection(layout, children, -1, 1);
585 if (newIcon != null) {
586 newIcon.requestFocus();
587 } else {
588 workspace.requestFocus();
589 }
590 }
591 wasHandled = true;
592 break;
593 case KeyEvent.KEYCODE_DPAD_DOWN:
594 // Do nothing
595 wasHandled = true;
596 break;
597 default: break;
598 }
599 return wasHandled;
600 }
601
602 /**
Winson Chung97d85d22011-04-13 11:27:36 -0700603 * Private helper method to get the CellLayoutChildren given a CellLayout index.
604 */
605 private static CellLayoutChildren getCellLayoutChildrenForIndex(ViewGroup container, int i) {
606 ViewGroup parent = (ViewGroup) container.getChildAt(i);
607 return (CellLayoutChildren) parent.getChildAt(0);
608 }
609
610 /**
611 * Private helper method to sort all the CellLayout children in order of their (x,y) spatially
612 * from top left to bottom right.
613 */
614 private static ArrayList<View> getCellLayoutChildrenSortedSpatially(CellLayout layout,
615 ViewGroup parent) {
616 // First we order each the CellLayout children by their x,y coordinates
617 final int cellCountX = layout.getCountX();
618 final int count = parent.getChildCount();
619 ArrayList<View> views = new ArrayList<View>();
620 for (int j = 0; j < count; ++j) {
621 views.add(parent.getChildAt(j));
622 }
623 Collections.sort(views, new Comparator<View>() {
624 @Override
625 public int compare(View lhs, View rhs) {
626 CellLayout.LayoutParams llp = (CellLayout.LayoutParams) lhs.getLayoutParams();
627 CellLayout.LayoutParams rlp = (CellLayout.LayoutParams) rhs.getLayoutParams();
628 int lvIndex = (llp.cellY * cellCountX) + llp.cellX;
629 int rvIndex = (rlp.cellY * cellCountX) + rlp.cellX;
630 return lvIndex - rvIndex;
631 }
632 });
633 return views;
634 }
635 /**
636 * Private helper method to find the index of the next BubbleTextView in the delta direction.
637 * @param delta either -1 or 1 depending on the direction we want to search
638 */
639 private static View findIndexOfBubbleTextView(ArrayList<View> views, int i, int delta) {
640 // Then we find the next BubbleTextView offset by delta from i
641 final int count = views.size();
642 int newI = i + delta;
643 while (0 <= newI && newI < count) {
644 View newV = views.get(newI);
645 if (newV instanceof BubbleTextView) {
646 return newV;
647 }
648 newI += delta;
649 }
650 return null;
651 }
652 private static View getBubbleTextViewInDirection(CellLayout layout, ViewGroup parent, int i,
653 int delta) {
654 final ArrayList<View> views = getCellLayoutChildrenSortedSpatially(layout, parent);
655 return findIndexOfBubbleTextView(views, i, delta);
656 }
657 private static View getBubbleTextViewInDirection(CellLayout layout, ViewGroup parent, View v,
658 int delta) {
659 final ArrayList<View> views = getCellLayoutChildrenSortedSpatially(layout, parent);
660 return findIndexOfBubbleTextView(views, views.indexOf(v), delta);
661 }
662 /**
663 * Private helper method to find the next closest BubbleTextView in the delta direction on the
664 * next line.
665 * @param delta either -1 or 1 depending on the line and direction we want to search
666 */
667 private static View getClosestBubbleTextViewOnLine(CellLayout layout, ViewGroup parent, View v,
668 int lineDelta) {
669 final ArrayList<View> views = getCellLayoutChildrenSortedSpatially(layout, parent);
670 final CellLayout.LayoutParams lp = (CellLayout.LayoutParams) v.getLayoutParams();
671 final int cellCountX = layout.getCountX();
672 final int cellCountY = layout.getCountY();
673 final int row = lp.cellY;
674 final int newRow = row + lineDelta;
675 if (0 <= newRow && newRow < cellCountY) {
676 float closestDistance = Float.MAX_VALUE;
677 int closestIndex = -1;
678 int index = views.indexOf(v);
679 int endIndex = (lineDelta < 0) ? -1 : views.size();
680 while (index != endIndex) {
681 View newV = views.get(index);
682 CellLayout.LayoutParams tmpLp = (CellLayout.LayoutParams) newV.getLayoutParams();
683 boolean satisfiesRow = (lineDelta < 0) ? (tmpLp.cellY < row) : (tmpLp.cellY > row);
684 if (satisfiesRow && newV instanceof BubbleTextView) {
685 float tmpDistance = (float) Math.sqrt(Math.pow(tmpLp.cellX - lp.cellX, 2) +
686 Math.pow(tmpLp.cellY - lp.cellY, 2));
687 if (tmpDistance < closestDistance) {
688 closestIndex = index;
689 closestDistance = tmpDistance;
690 }
691 }
692 if (index <= endIndex) {
693 ++index;
694 } else {
695 --index;
696 }
697 }
698 if (closestIndex > -1) {
699 return views.get(closestIndex);
700 }
701 }
702 return null;
703 }
704
705 /**
706 * Handles key events in a Workspace containing BubbleTextView.
707 */
708 static boolean handleBubbleTextViewKeyEvent(BubbleTextView v, int keyCode, KeyEvent e) {
Winson Chung97d85d22011-04-13 11:27:36 -0700709 CellLayoutChildren parent = (CellLayoutChildren) v.getParent();
710 final CellLayout layout = (CellLayout) parent.getParent();
711 final Workspace workspace = (Workspace) layout.getParent();
712 final ViewGroup launcher = (ViewGroup) workspace.getParent();
Winson Chungfaa13252011-06-13 18:15:54 -0700713 final ViewGroup tabs = (ViewGroup) launcher.findViewById(R.id.qsb_bar);
Winson Chung97d85d22011-04-13 11:27:36 -0700714 int iconIndex = parent.indexOfChild(v);
715 int iconCount = parent.getChildCount();
716 int pageIndex = workspace.indexOfChild(layout);
717 int pageCount = workspace.getChildCount();
718
719 final int action = e.getAction();
720 final boolean handleKeyEvent = (action != KeyEvent.ACTION_UP);
721 boolean wasHandled = false;
722 switch (keyCode) {
723 case KeyEvent.KEYCODE_DPAD_LEFT:
724 if (handleKeyEvent) {
725 // Select the previous icon or the last icon on the previous page if possible
726 View newIcon = getBubbleTextViewInDirection(layout, parent, v, -1);
727 if (newIcon != null) {
728 newIcon.requestFocus();
729 } else {
730 if (pageIndex > 0) {
731 parent = getCellLayoutChildrenForIndex(workspace, pageIndex - 1);
732 newIcon = getBubbleTextViewInDirection(layout, parent,
733 parent.getChildCount(), -1);
734 if (newIcon != null) {
735 newIcon.requestFocus();
736 } else {
737 // Snap to the previous page
738 workspace.snapToPage(pageIndex - 1);
739 }
740 }
741 }
742 }
743 wasHandled = true;
744 break;
745 case KeyEvent.KEYCODE_DPAD_RIGHT:
746 if (handleKeyEvent) {
747 // Select the next icon or the first icon on the next page if possible
748 View newIcon = getBubbleTextViewInDirection(layout, parent, v, 1);
749 if (newIcon != null) {
750 newIcon.requestFocus();
751 } else {
752 if (pageIndex < (pageCount - 1)) {
753 parent = getCellLayoutChildrenForIndex(workspace, pageIndex + 1);
754 newIcon = getBubbleTextViewInDirection(layout, parent, -1, 1);
755 if (newIcon != null) {
756 newIcon.requestFocus();
757 } else {
758 // Snap to the next page
759 workspace.snapToPage(pageIndex + 1);
760 }
761 }
762 }
763 }
764 wasHandled = true;
765 break;
766 case KeyEvent.KEYCODE_DPAD_UP:
767 if (handleKeyEvent) {
768 // Select the closest icon in the previous line, otherwise select the tab bar
769 View newIcon = getClosestBubbleTextViewOnLine(layout, parent, v, -1);
770 if (newIcon != null) {
771 newIcon.requestFocus();
772 wasHandled = true;
773 } else {
774 tabs.requestFocus();
775 }
776 }
777 break;
778 case KeyEvent.KEYCODE_DPAD_DOWN:
779 if (handleKeyEvent) {
780 // Select the closest icon in the next line, otherwise select the tab bar
781 View newIcon = getClosestBubbleTextViewOnLine(layout, parent, v, 1);
782 if (newIcon != null) {
783 newIcon.requestFocus();
784 wasHandled = true;
785 }
786 }
787 break;
788 case KeyEvent.KEYCODE_PAGE_UP:
789 if (handleKeyEvent) {
790 // Select the first icon on the previous page or the first icon on this page
791 // if there is no previous page
792 if (pageIndex > 0) {
793 parent = getCellLayoutChildrenForIndex(workspace, pageIndex - 1);
794 View newIcon = getBubbleTextViewInDirection(layout, parent, -1, 1);
795 if (newIcon != null) {
796 newIcon.requestFocus();
797 } else {
798 // Snap to the previous page
799 workspace.snapToPage(pageIndex - 1);
800 }
801 } else {
802 View newIcon = getBubbleTextViewInDirection(layout, parent, -1, 1);
803 if (newIcon != null) {
804 newIcon.requestFocus();
805 }
806 }
807 }
808 wasHandled = true;
809 break;
810 case KeyEvent.KEYCODE_PAGE_DOWN:
811 if (handleKeyEvent) {
812 // Select the first icon on the next page or the last icon on this page
813 // if there is no previous page
814 if (pageIndex < (pageCount - 1)) {
815 parent = getCellLayoutChildrenForIndex(workspace, pageIndex + 1);
816 View newIcon = getBubbleTextViewInDirection(layout, parent, -1, 1);
817 if (newIcon != null) {
818 newIcon.requestFocus();
819 } else {
820 // Snap to the next page
821 workspace.snapToPage(pageIndex + 1);
822 }
823 } else {
824 View newIcon = getBubbleTextViewInDirection(layout, parent,
825 parent.getChildCount(), -1);
826 if (newIcon != null) {
827 newIcon.requestFocus();
828 }
829 }
830 }
831 wasHandled = true;
832 break;
833 case KeyEvent.KEYCODE_MOVE_HOME:
834 if (handleKeyEvent) {
835 // Select the first icon on this page
836 View newIcon = getBubbleTextViewInDirection(layout, parent, -1, 1);
837 if (newIcon != null) {
838 newIcon.requestFocus();
839 }
840 }
841 wasHandled = true;
842 break;
843 case KeyEvent.KEYCODE_MOVE_END:
844 if (handleKeyEvent) {
845 // Select the last icon on this page
846 View newIcon = getBubbleTextViewInDirection(layout, parent,
847 parent.getChildCount(), -1);
848 if (newIcon != null) {
849 newIcon.requestFocus();
850 }
851 }
852 wasHandled = true;
853 break;
854 default: break;
855 }
856 return wasHandled;
857 }
858}