blob: 233fd6fbb6f622a8497c895312d9026b9b134d22 [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/**
Winson Chung3d503fb2011-07-13 17:25:49 -070034 * A keyboard listener we set on all the hotseat buttons.
Winson Chung4e6a9762011-05-09 11:56:34 -070035 */
Winson Chung3d503fb2011-07-13 17:25:49 -070036class HotseatKeyEventListener implements View.OnKeyListener {
Winson Chung4e6a9762011-05-09 11:56:34 -070037 @Override
38 public boolean onKey(View v, int keyCode, KeyEvent event) {
39 final Configuration configuration = v.getResources().getConfiguration();
Winson Chung3d503fb2011-07-13 17:25:49 -070040 return FocusHelper.handleHotseatButtonKeyEvent(v, keyCode, event, configuration.orientation);
Winson Chung4e6a9762011-05-09 11:56:34 -070041 }
42}
43
44/**
Winson Chungfaa13252011-06-13 18:15:54 -070045 * A keyboard listener we set on the last tab button in AppsCustomize to jump to then
Winson Chung97d85d22011-04-13 11:27:36 -070046 * market icon and vice versa.
47 */
Winson Chungfaa13252011-06-13 18:15:54 -070048class AppsCustomizeTabKeyEventListener implements View.OnKeyListener {
Winson Chung97d85d22011-04-13 11:27:36 -070049 @Override
50 public boolean onKey(View v, int keyCode, KeyEvent event) {
Winson Chungfaa13252011-06-13 18:15:54 -070051 return FocusHelper.handleAppsCustomizeTabKeyEvent(v, keyCode, event);
Winson Chung97d85d22011-04-13 11:27:36 -070052 }
53}
54
55public class FocusHelper {
56 /**
57 * Private helper to get the parent TabHost in the view hiearchy.
58 */
59 private static TabHost findTabHostParent(View v) {
60 ViewParent p = v.getParent();
61 while (p != null && !(p instanceof TabHost)) {
62 p = p.getParent();
63 }
64 return (TabHost) p;
65 }
66
67 /**
Winson Chungfaa13252011-06-13 18:15:54 -070068 * Handles key events in a AppsCustomize tab between the last tab view and the shop button.
Winson Chung97d85d22011-04-13 11:27:36 -070069 */
Winson Chungfaa13252011-06-13 18:15:54 -070070 static boolean handleAppsCustomizeTabKeyEvent(View v, int keyCode, KeyEvent e) {
Winson Chung97d85d22011-04-13 11:27:36 -070071 final TabHost tabHost = findTabHostParent(v);
72 final ViewGroup contents = (ViewGroup)
73 tabHost.findViewById(com.android.internal.R.id.tabcontent);
74 final View shop = tabHost.findViewById(R.id.market_button);
75
76 final int action = e.getAction();
77 final boolean handleKeyEvent = (action != KeyEvent.ACTION_UP);
78 boolean wasHandled = false;
79 switch (keyCode) {
80 case KeyEvent.KEYCODE_DPAD_RIGHT:
81 if (handleKeyEvent) {
82 // Select the shop button if we aren't on it
83 if (v != shop) {
84 shop.requestFocus();
85 }
86 }
87 wasHandled = true;
88 break;
89 case KeyEvent.KEYCODE_DPAD_DOWN:
90 if (handleKeyEvent) {
91 // Select the content view (down is handled by the tab key handler otherwise)
92 if (v == shop) {
93 contents.requestFocus();
94 wasHandled = true;
95 }
96 }
97 break;
98 default: break;
99 }
100 return wasHandled;
101 }
102
103 /**
104 * Private helper to determine whether a view is visible.
105 */
106 private static boolean isVisible(View v) {
107 return v.getVisibility() == View.VISIBLE;
108 }
109
110 /**
111 * Handles key events in a PageViewExtendedLayout containing PagedViewWidgets.
Winson Chung4e6a9762011-05-09 11:56:34 -0700112 */
113 static boolean handlePagedViewGridLayoutWidgetKeyEvent(PagedViewWidget w, int keyCode,
114 KeyEvent e) {
115
116 final PagedViewGridLayout parent = (PagedViewGridLayout) w.getParent();
117 final ViewGroup container = (ViewGroup) parent.getParent();
118 final TabHost tabHost = findTabHostParent(container);
119 final TabWidget tabs = (TabWidget) tabHost.findViewById(com.android.internal.R.id.tabs);
120 final int widgetIndex = parent.indexOfChild(w);
121 final int widgetCount = parent.getChildCount();
122 final int pageIndex = container.indexOfChild(parent);
123 final int pageCount = container.getChildCount();
124 final int cellCountX = parent.getCellCountX();
125 final int cellCountY = parent.getCellCountY();
126 final int x = widgetIndex % cellCountX;
127 final int y = widgetIndex / cellCountX;
128
129 final int action = e.getAction();
130 final boolean handleKeyEvent = (action != KeyEvent.ACTION_UP);
131 PagedViewGridLayout newParent = null;
132 boolean wasHandled = false;
133 switch (keyCode) {
134 case KeyEvent.KEYCODE_DPAD_LEFT:
135 if (handleKeyEvent) {
136 // Select the previous widget or the last widget on the previous page
137 if (widgetIndex > 0) {
138 parent.getChildAt(widgetIndex - 1).requestFocus();
139 } else {
140 if (pageIndex > 0) {
141 newParent = (PagedViewGridLayout)
142 container.getChildAt(pageIndex - 1);
143 newParent.getChildAt(newParent.getChildCount() - 1).requestFocus();
144 }
145 }
146 }
147 wasHandled = true;
148 break;
149 case KeyEvent.KEYCODE_DPAD_RIGHT:
150 if (handleKeyEvent) {
151 // Select the next widget or the first widget on the next page
152 if (widgetIndex < (widgetCount - 1)) {
153 parent.getChildAt(widgetIndex + 1).requestFocus();
154 } else {
155 if (pageIndex < (pageCount - 1)) {
156 newParent = (PagedViewGridLayout)
157 container.getChildAt(pageIndex + 1);
158 newParent.getChildAt(0).requestFocus();
159 }
160 }
161 }
162 wasHandled = true;
163 break;
164 case KeyEvent.KEYCODE_DPAD_UP:
165 if (handleKeyEvent) {
166 // Select the closest icon in the previous row, otherwise select the tab bar
167 if (y > 0) {
168 int newWidgetIndex = ((y - 1) * cellCountX) + x;
169 parent.getChildAt(newWidgetIndex).requestFocus();
170 } else {
171 tabs.requestFocus();
172 }
173 }
174 wasHandled = true;
175 break;
176 case KeyEvent.KEYCODE_DPAD_DOWN:
177 if (handleKeyEvent) {
178 // Select the closest icon in the previous row, otherwise do nothing
179 if (y < (cellCountY - 1)) {
180 int newWidgetIndex = Math.min(widgetCount - 1, ((y + 1) * cellCountX) + x);
181 parent.getChildAt(newWidgetIndex).requestFocus();
182 }
183 }
184 wasHandled = true;
185 break;
186 case KeyEvent.KEYCODE_ENTER:
187 case KeyEvent.KEYCODE_DPAD_CENTER:
188 if (handleKeyEvent) {
189 // Simulate a click on the widget
190 View.OnClickListener clickListener = (View.OnClickListener) container;
191 clickListener.onClick(w);
192 }
193 wasHandled = true;
194 break;
195 case KeyEvent.KEYCODE_PAGE_UP:
196 if (handleKeyEvent) {
197 // Select the first item on the previous page, or the first item on this page
198 // if there is no previous page
199 if (pageIndex > 0) {
200 newParent = (PagedViewGridLayout) container.getChildAt(pageIndex - 1);
201 newParent.getChildAt(0).requestFocus();
202 } else {
203 parent.getChildAt(0).requestFocus();
204 }
205 }
206 wasHandled = true;
207 break;
208 case KeyEvent.KEYCODE_PAGE_DOWN:
209 if (handleKeyEvent) {
210 // Select the first item on the next page, or the last item on this page
211 // if there is no next page
212 if (pageIndex < (pageCount - 1)) {
213 newParent = (PagedViewGridLayout) container.getChildAt(pageIndex + 1);
214 newParent.getChildAt(0).requestFocus();
215 } else {
216 parent.getChildAt(widgetCount - 1).requestFocus();
217 }
218 }
219 wasHandled = true;
220 break;
221 case KeyEvent.KEYCODE_MOVE_HOME:
222 if (handleKeyEvent) {
223 // Select the first item on this page
224 parent.getChildAt(0).requestFocus();
225 }
226 wasHandled = true;
227 break;
228 case KeyEvent.KEYCODE_MOVE_END:
229 if (handleKeyEvent) {
230 // Select the last item on this page
231 parent.getChildAt(widgetCount - 1).requestFocus();
232 }
233 wasHandled = true;
234 break;
235 default: break;
236 }
237 return wasHandled;
238 }
239
240 /**
Winson Chung97d85d22011-04-13 11:27:36 -0700241 * Private helper method to get the PagedViewCellLayoutChildren given a PagedViewCellLayout
242 * index.
243 */
244 private static PagedViewCellLayoutChildren getPagedViewCellLayoutChildrenForIndex(
245 ViewGroup container, int i) {
246 ViewGroup parent = (ViewGroup) container.getChildAt(i);
247 return (PagedViewCellLayoutChildren) parent.getChildAt(0);
248 }
249
250 /**
251 * Handles key events in a PageViewCellLayout containing PagedViewIcons.
252 */
253 static boolean handlePagedViewIconKeyEvent(PagedViewIcon v, int keyCode, KeyEvent e) {
Winson Chung97d85d22011-04-13 11:27:36 -0700254 final PagedViewCellLayoutChildren parent = (PagedViewCellLayoutChildren) v.getParent();
255 final PagedViewCellLayout parentLayout = (PagedViewCellLayout) parent.getParent();
256 // Note we have an extra parent because of the
257 // PagedViewCellLayout/PagedViewCellLayoutChildren relationship
258 final ViewGroup container = (ViewGroup) parentLayout.getParent();
259 final TabHost tabHost = findTabHostParent(container);
260 final TabWidget tabs = (TabWidget) tabHost.findViewById(com.android.internal.R.id.tabs);
261 final int widgetIndex = parent.indexOfChild(v);
262 final int widgetCount = parent.getChildCount();
263 final int pageIndex = container.indexOfChild(parentLayout);
264 final int pageCount = container.getChildCount();
265 final int cellCountX = parentLayout.getCellCountX();
266 final int cellCountY = parentLayout.getCellCountY();
267 final int x = widgetIndex % cellCountX;
268 final int y = widgetIndex / cellCountX;
269
270 final int action = e.getAction();
271 final boolean handleKeyEvent = (action != KeyEvent.ACTION_UP);
272 PagedViewCellLayoutChildren newParent = null;
273 boolean wasHandled = false;
274 switch (keyCode) {
275 case KeyEvent.KEYCODE_DPAD_LEFT:
276 if (handleKeyEvent) {
277 // Select the previous icon or the last icon on the previous page
278 if (widgetIndex > 0) {
279 parent.getChildAt(widgetIndex - 1).requestFocus();
280 } else {
281 if (pageIndex > 0) {
282 newParent = getPagedViewCellLayoutChildrenForIndex(container,
283 pageIndex - 1);
284 newParent.getChildAt(newParent.getChildCount() - 1).requestFocus();
285 }
286 }
287 }
288 wasHandled = true;
289 break;
290 case KeyEvent.KEYCODE_DPAD_RIGHT:
291 if (handleKeyEvent) {
292 // Select the next icon or the first icon on the next page
293 if (widgetIndex < (widgetCount - 1)) {
294 parent.getChildAt(widgetIndex + 1).requestFocus();
295 } else {
296 if (pageIndex < (pageCount - 1)) {
297 newParent = getPagedViewCellLayoutChildrenForIndex(container,
298 pageIndex + 1);
299 newParent.getChildAt(0).requestFocus();
300 }
301 }
302 }
303 wasHandled = true;
304 break;
305 case KeyEvent.KEYCODE_DPAD_UP:
306 if (handleKeyEvent) {
307 // Select the closest icon in the previous row, otherwise select the tab bar
308 if (y > 0) {
309 int newWidgetIndex = ((y - 1) * cellCountX) + x;
310 parent.getChildAt(newWidgetIndex).requestFocus();
311 } else {
312 tabs.requestFocus();
313 }
314 }
315 wasHandled = true;
316 break;
317 case KeyEvent.KEYCODE_DPAD_DOWN:
318 if (handleKeyEvent) {
319 // Select the closest icon in the previous row, otherwise do nothing
320 if (y < (cellCountY - 1)) {
321 int newWidgetIndex = Math.min(widgetCount - 1, ((y + 1) * cellCountX) + x);
322 parent.getChildAt(newWidgetIndex).requestFocus();
323 }
324 }
325 wasHandled = true;
326 break;
327 case KeyEvent.KEYCODE_ENTER:
328 case KeyEvent.KEYCODE_DPAD_CENTER:
329 if (handleKeyEvent) {
330 // Simulate a click on the icon
331 View.OnClickListener clickListener = (View.OnClickListener) container;
332 clickListener.onClick(v);
333 }
334 wasHandled = true;
335 break;
336 case KeyEvent.KEYCODE_PAGE_UP:
337 if (handleKeyEvent) {
338 // Select the first icon on the previous page, or the first icon on this page
339 // if there is no previous page
340 if (pageIndex > 0) {
341 newParent = getPagedViewCellLayoutChildrenForIndex(container,
342 pageIndex - 1);
343 newParent.getChildAt(0).requestFocus();
344 } else {
345 parent.getChildAt(0).requestFocus();
346 }
347 }
348 wasHandled = true;
349 break;
350 case KeyEvent.KEYCODE_PAGE_DOWN:
351 if (handleKeyEvent) {
352 // Select the first icon on the next page, or the last icon on this page
353 // if there is no next page
354 if (pageIndex < (pageCount - 1)) {
355 newParent = getPagedViewCellLayoutChildrenForIndex(container,
356 pageIndex + 1);
357 newParent.getChildAt(0).requestFocus();
358 } else {
359 parent.getChildAt(widgetCount - 1).requestFocus();
360 }
361 }
362 wasHandled = true;
363 break;
364 case KeyEvent.KEYCODE_MOVE_HOME:
365 if (handleKeyEvent) {
366 // Select the first icon on this page
367 parent.getChildAt(0).requestFocus();
368 }
369 wasHandled = true;
370 break;
371 case KeyEvent.KEYCODE_MOVE_END:
372 if (handleKeyEvent) {
373 // Select the last icon on this page
374 parent.getChildAt(widgetCount - 1).requestFocus();
375 }
376 wasHandled = true;
377 break;
378 default: break;
379 }
380 return wasHandled;
381 }
382
383 /**
384 * Handles key events in the tab widget.
385 */
386 static boolean handleTabKeyEvent(AccessibleTabView v, int keyCode, KeyEvent e) {
Michael Jurkaa2eb1702011-05-12 14:57:05 -0700387 if (!LauncherApplication.isScreenLarge()) return false;
Winson Chung97d85d22011-04-13 11:27:36 -0700388
389 final FocusOnlyTabWidget parent = (FocusOnlyTabWidget) v.getParent();
390 final TabHost tabHost = findTabHostParent(parent);
391 final ViewGroup contents = (ViewGroup)
392 tabHost.findViewById(com.android.internal.R.id.tabcontent);
393 final int tabCount = parent.getTabCount();
394 final int tabIndex = parent.getChildTabIndex(v);
395
396 final int action = e.getAction();
397 final boolean handleKeyEvent = (action != KeyEvent.ACTION_UP);
398 boolean wasHandled = false;
399 switch (keyCode) {
400 case KeyEvent.KEYCODE_DPAD_LEFT:
401 if (handleKeyEvent) {
402 // Select the previous tab
403 if (tabIndex > 0) {
404 parent.getChildTabViewAt(tabIndex - 1).requestFocus();
405 }
406 }
407 wasHandled = true;
408 break;
409 case KeyEvent.KEYCODE_DPAD_RIGHT:
410 if (handleKeyEvent) {
411 // Select the next tab, or if the last tab has a focus right id, select that
412 if (tabIndex < (tabCount - 1)) {
413 parent.getChildTabViewAt(tabIndex + 1).requestFocus();
414 } else {
415 if (v.getNextFocusRightId() != View.NO_ID) {
416 tabHost.findViewById(v.getNextFocusRightId()).requestFocus();
417 }
418 }
419 }
420 wasHandled = true;
421 break;
422 case KeyEvent.KEYCODE_DPAD_UP:
423 // Do nothing
424 wasHandled = true;
425 break;
426 case KeyEvent.KEYCODE_DPAD_DOWN:
427 if (handleKeyEvent) {
428 // Select the content view
429 contents.requestFocus();
430 }
431 wasHandled = true;
432 break;
433 default: break;
434 }
435 return wasHandled;
436 }
437
438 /**
Winson Chung3d503fb2011-07-13 17:25:49 -0700439 * Handles key events in the workspace hotseat (bottom of the screen).
Winson Chung4e6a9762011-05-09 11:56:34 -0700440 */
Winson Chung3d503fb2011-07-13 17:25:49 -0700441 static boolean handleHotseatButtonKeyEvent(View v, int keyCode, KeyEvent e, int orientation) {
Winson Chung4e6a9762011-05-09 11:56:34 -0700442 final ViewGroup parent = (ViewGroup) v.getParent();
443 final ViewGroup launcher = (ViewGroup) parent.getParent();
444 final Workspace workspace = (Workspace) launcher.findViewById(R.id.workspace);
445 final int buttonIndex = parent.indexOfChild(v);
446 final int buttonCount = parent.getChildCount();
447 final int pageIndex = workspace.getCurrentPage();
448 final int pageCount = workspace.getChildCount();
Winson Chung4e6a9762011-05-09 11:56:34 -0700449
450 // NOTE: currently we don't special case for the phone UI in different
Winson Chung3d503fb2011-07-13 17:25:49 -0700451 // orientations, even though the hotseat is on the side in landscape mode. This
Winson Chung4e6a9762011-05-09 11:56:34 -0700452 // is to ensure that accessibility consistency is maintained across rotations.
453
454 final int action = e.getAction();
455 final boolean handleKeyEvent = (action != KeyEvent.ACTION_UP);
456 boolean wasHandled = false;
457 switch (keyCode) {
458 case KeyEvent.KEYCODE_DPAD_LEFT:
459 if (handleKeyEvent) {
Winson Chung74608b52011-06-23 13:23:20 -0700460 // Select the previous button, otherwise snap to the previous page
Winson Chung4e6a9762011-05-09 11:56:34 -0700461 if (buttonIndex > 0) {
462 parent.getChildAt(buttonIndex - 1).requestFocus();
463 } else {
Winson Chung74608b52011-06-23 13:23:20 -0700464 workspace.snapToPage(pageIndex - 1);
Winson Chung4e6a9762011-05-09 11:56:34 -0700465 }
466 }
467 wasHandled = true;
468 break;
469 case KeyEvent.KEYCODE_DPAD_RIGHT:
470 if (handleKeyEvent) {
Winson Chung74608b52011-06-23 13:23:20 -0700471 // Select the next button, otherwise snap to the next page
Winson Chung4e6a9762011-05-09 11:56:34 -0700472 if (buttonIndex < (buttonCount - 1)) {
473 parent.getChildAt(buttonIndex + 1).requestFocus();
474 } else {
Winson Chung74608b52011-06-23 13:23:20 -0700475 workspace.snapToPage(pageIndex + 1);
Winson Chung4e6a9762011-05-09 11:56:34 -0700476 }
477 }
478 wasHandled = true;
479 break;
480 case KeyEvent.KEYCODE_DPAD_UP:
481 if (handleKeyEvent) {
482 // Select the first bubble text view in the current page of the workspace
483 final CellLayout layout = (CellLayout) workspace.getChildAt(pageIndex);
484 final CellLayoutChildren children = layout.getChildrenLayout();
485 final View newIcon = getBubbleTextViewInDirection(layout, children, -1, 1);
486 if (newIcon != null) {
487 newIcon.requestFocus();
488 } else {
489 workspace.requestFocus();
490 }
491 }
492 wasHandled = true;
493 break;
494 case KeyEvent.KEYCODE_DPAD_DOWN:
495 // Do nothing
496 wasHandled = true;
497 break;
498 default: break;
499 }
500 return wasHandled;
501 }
502
503 /**
Winson Chung97d85d22011-04-13 11:27:36 -0700504 * Private helper method to get the CellLayoutChildren given a CellLayout index.
505 */
506 private static CellLayoutChildren getCellLayoutChildrenForIndex(ViewGroup container, int i) {
507 ViewGroup parent = (ViewGroup) container.getChildAt(i);
508 return (CellLayoutChildren) parent.getChildAt(0);
509 }
510
511 /**
512 * Private helper method to sort all the CellLayout children in order of their (x,y) spatially
513 * from top left to bottom right.
514 */
515 private static ArrayList<View> getCellLayoutChildrenSortedSpatially(CellLayout layout,
516 ViewGroup parent) {
517 // First we order each the CellLayout children by their x,y coordinates
518 final int cellCountX = layout.getCountX();
519 final int count = parent.getChildCount();
520 ArrayList<View> views = new ArrayList<View>();
521 for (int j = 0; j < count; ++j) {
522 views.add(parent.getChildAt(j));
523 }
524 Collections.sort(views, new Comparator<View>() {
525 @Override
526 public int compare(View lhs, View rhs) {
527 CellLayout.LayoutParams llp = (CellLayout.LayoutParams) lhs.getLayoutParams();
528 CellLayout.LayoutParams rlp = (CellLayout.LayoutParams) rhs.getLayoutParams();
529 int lvIndex = (llp.cellY * cellCountX) + llp.cellX;
530 int rvIndex = (rlp.cellY * cellCountX) + rlp.cellX;
531 return lvIndex - rvIndex;
532 }
533 });
534 return views;
535 }
536 /**
537 * Private helper method to find the index of the next BubbleTextView in the delta direction.
538 * @param delta either -1 or 1 depending on the direction we want to search
539 */
540 private static View findIndexOfBubbleTextView(ArrayList<View> views, int i, int delta) {
541 // Then we find the next BubbleTextView offset by delta from i
542 final int count = views.size();
543 int newI = i + delta;
544 while (0 <= newI && newI < count) {
545 View newV = views.get(newI);
546 if (newV instanceof BubbleTextView) {
547 return newV;
548 }
549 newI += delta;
550 }
551 return null;
552 }
553 private static View getBubbleTextViewInDirection(CellLayout layout, ViewGroup parent, int i,
554 int delta) {
555 final ArrayList<View> views = getCellLayoutChildrenSortedSpatially(layout, parent);
556 return findIndexOfBubbleTextView(views, i, delta);
557 }
558 private static View getBubbleTextViewInDirection(CellLayout layout, ViewGroup parent, View v,
559 int delta) {
560 final ArrayList<View> views = getCellLayoutChildrenSortedSpatially(layout, parent);
561 return findIndexOfBubbleTextView(views, views.indexOf(v), delta);
562 }
563 /**
564 * Private helper method to find the next closest BubbleTextView in the delta direction on the
565 * next line.
566 * @param delta either -1 or 1 depending on the line and direction we want to search
567 */
568 private static View getClosestBubbleTextViewOnLine(CellLayout layout, ViewGroup parent, View v,
569 int lineDelta) {
570 final ArrayList<View> views = getCellLayoutChildrenSortedSpatially(layout, parent);
571 final CellLayout.LayoutParams lp = (CellLayout.LayoutParams) v.getLayoutParams();
572 final int cellCountX = layout.getCountX();
573 final int cellCountY = layout.getCountY();
574 final int row = lp.cellY;
575 final int newRow = row + lineDelta;
576 if (0 <= newRow && newRow < cellCountY) {
577 float closestDistance = Float.MAX_VALUE;
578 int closestIndex = -1;
579 int index = views.indexOf(v);
580 int endIndex = (lineDelta < 0) ? -1 : views.size();
581 while (index != endIndex) {
582 View newV = views.get(index);
583 CellLayout.LayoutParams tmpLp = (CellLayout.LayoutParams) newV.getLayoutParams();
584 boolean satisfiesRow = (lineDelta < 0) ? (tmpLp.cellY < row) : (tmpLp.cellY > row);
585 if (satisfiesRow && newV instanceof BubbleTextView) {
586 float tmpDistance = (float) Math.sqrt(Math.pow(tmpLp.cellX - lp.cellX, 2) +
587 Math.pow(tmpLp.cellY - lp.cellY, 2));
588 if (tmpDistance < closestDistance) {
589 closestIndex = index;
590 closestDistance = tmpDistance;
591 }
592 }
593 if (index <= endIndex) {
594 ++index;
595 } else {
596 --index;
597 }
598 }
599 if (closestIndex > -1) {
600 return views.get(closestIndex);
601 }
602 }
603 return null;
604 }
605
606 /**
607 * Handles key events in a Workspace containing BubbleTextView.
608 */
609 static boolean handleBubbleTextViewKeyEvent(BubbleTextView v, int keyCode, KeyEvent e) {
Winson Chung97d85d22011-04-13 11:27:36 -0700610 CellLayoutChildren parent = (CellLayoutChildren) v.getParent();
611 final CellLayout layout = (CellLayout) parent.getParent();
612 final Workspace workspace = (Workspace) layout.getParent();
613 final ViewGroup launcher = (ViewGroup) workspace.getParent();
Winson Chungfaa13252011-06-13 18:15:54 -0700614 final ViewGroup tabs = (ViewGroup) launcher.findViewById(R.id.qsb_bar);
Winson Chung97d85d22011-04-13 11:27:36 -0700615 int iconIndex = parent.indexOfChild(v);
616 int iconCount = parent.getChildCount();
617 int pageIndex = workspace.indexOfChild(layout);
618 int pageCount = workspace.getChildCount();
619
620 final int action = e.getAction();
621 final boolean handleKeyEvent = (action != KeyEvent.ACTION_UP);
622 boolean wasHandled = false;
623 switch (keyCode) {
624 case KeyEvent.KEYCODE_DPAD_LEFT:
625 if (handleKeyEvent) {
626 // Select the previous icon or the last icon on the previous page if possible
627 View newIcon = getBubbleTextViewInDirection(layout, parent, v, -1);
628 if (newIcon != null) {
629 newIcon.requestFocus();
630 } else {
631 if (pageIndex > 0) {
632 parent = getCellLayoutChildrenForIndex(workspace, pageIndex - 1);
633 newIcon = getBubbleTextViewInDirection(layout, parent,
634 parent.getChildCount(), -1);
635 if (newIcon != null) {
636 newIcon.requestFocus();
637 } else {
638 // Snap to the previous page
639 workspace.snapToPage(pageIndex - 1);
640 }
641 }
642 }
643 }
644 wasHandled = true;
645 break;
646 case KeyEvent.KEYCODE_DPAD_RIGHT:
647 if (handleKeyEvent) {
648 // Select the next icon or the first icon on the next page if possible
649 View newIcon = getBubbleTextViewInDirection(layout, parent, v, 1);
650 if (newIcon != null) {
651 newIcon.requestFocus();
652 } else {
653 if (pageIndex < (pageCount - 1)) {
654 parent = getCellLayoutChildrenForIndex(workspace, pageIndex + 1);
655 newIcon = getBubbleTextViewInDirection(layout, parent, -1, 1);
656 if (newIcon != null) {
657 newIcon.requestFocus();
658 } else {
659 // Snap to the next page
660 workspace.snapToPage(pageIndex + 1);
661 }
662 }
663 }
664 }
665 wasHandled = true;
666 break;
667 case KeyEvent.KEYCODE_DPAD_UP:
668 if (handleKeyEvent) {
669 // Select the closest icon in the previous line, otherwise select the tab bar
670 View newIcon = getClosestBubbleTextViewOnLine(layout, parent, v, -1);
671 if (newIcon != null) {
672 newIcon.requestFocus();
673 wasHandled = true;
674 } else {
675 tabs.requestFocus();
676 }
677 }
678 break;
679 case KeyEvent.KEYCODE_DPAD_DOWN:
680 if (handleKeyEvent) {
681 // Select the closest icon in the next line, otherwise select the tab bar
682 View newIcon = getClosestBubbleTextViewOnLine(layout, parent, v, 1);
683 if (newIcon != null) {
684 newIcon.requestFocus();
685 wasHandled = true;
686 }
687 }
688 break;
689 case KeyEvent.KEYCODE_PAGE_UP:
690 if (handleKeyEvent) {
691 // Select the first icon on the previous page or the first icon on this page
692 // if there is no previous page
693 if (pageIndex > 0) {
694 parent = getCellLayoutChildrenForIndex(workspace, pageIndex - 1);
695 View newIcon = getBubbleTextViewInDirection(layout, parent, -1, 1);
696 if (newIcon != null) {
697 newIcon.requestFocus();
698 } else {
699 // Snap to the previous page
700 workspace.snapToPage(pageIndex - 1);
701 }
702 } else {
703 View newIcon = getBubbleTextViewInDirection(layout, parent, -1, 1);
704 if (newIcon != null) {
705 newIcon.requestFocus();
706 }
707 }
708 }
709 wasHandled = true;
710 break;
711 case KeyEvent.KEYCODE_PAGE_DOWN:
712 if (handleKeyEvent) {
713 // Select the first icon on the next page or the last icon on this page
714 // if there is no previous page
715 if (pageIndex < (pageCount - 1)) {
716 parent = getCellLayoutChildrenForIndex(workspace, pageIndex + 1);
717 View newIcon = getBubbleTextViewInDirection(layout, parent, -1, 1);
718 if (newIcon != null) {
719 newIcon.requestFocus();
720 } else {
721 // Snap to the next page
722 workspace.snapToPage(pageIndex + 1);
723 }
724 } else {
725 View newIcon = getBubbleTextViewInDirection(layout, parent,
726 parent.getChildCount(), -1);
727 if (newIcon != null) {
728 newIcon.requestFocus();
729 }
730 }
731 }
732 wasHandled = true;
733 break;
734 case KeyEvent.KEYCODE_MOVE_HOME:
735 if (handleKeyEvent) {
736 // Select the first icon on this page
737 View newIcon = getBubbleTextViewInDirection(layout, parent, -1, 1);
738 if (newIcon != null) {
739 newIcon.requestFocus();
740 }
741 }
742 wasHandled = true;
743 break;
744 case KeyEvent.KEYCODE_MOVE_END:
745 if (handleKeyEvent) {
746 // Select the last icon on this page
747 View newIcon = getBubbleTextViewInDirection(layout, parent,
748 parent.getChildCount(), -1);
749 if (newIcon != null) {
750 newIcon.requestFocus();
751 }
752 }
753 wasHandled = true;
754 break;
755 default: break;
756 }
757 return wasHandled;
758 }
759}