blob: f03073998399f740563592779a88569c4d5f94e3 [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 Chung4d279d92011-07-21 11:46:32 -070034 * A keyboard listener we set on all the workspace icons.
35 */
36class BubbleTextViewKeyEventListener implements View.OnKeyListener {
37 @Override
38 public boolean onKey(View v, int keyCode, KeyEvent event) {
39 return FocusHelper.handleBubbleTextViewKeyEvent((BubbleTextView) v, keyCode, event);
40 }
41}
42
43/**
Winson Chung3d503fb2011-07-13 17:25:49 -070044 * A keyboard listener we set on all the hotseat buttons.
Winson Chung4e6a9762011-05-09 11:56:34 -070045 */
Winson Chung4d279d92011-07-21 11:46:32 -070046class HotseatBubbleTextViewKeyEventListener implements View.OnKeyListener {
Winson Chung4e6a9762011-05-09 11:56:34 -070047 @Override
48 public boolean onKey(View v, int keyCode, KeyEvent event) {
49 final Configuration configuration = v.getResources().getConfiguration();
Winson Chung3d503fb2011-07-13 17:25:49 -070050 return FocusHelper.handleHotseatButtonKeyEvent(v, keyCode, event, configuration.orientation);
Winson Chung4e6a9762011-05-09 11:56:34 -070051 }
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;
Winson Chung3b0b46a2011-07-28 10:45:09 -0700142 // Now that we load items in the bg asynchronously, we can't just focus
143 // child siblings willy-nilly
144 View child = null;
Winson Chung4e6a9762011-05-09 11:56:34 -0700145 boolean wasHandled = false;
146 switch (keyCode) {
147 case KeyEvent.KEYCODE_DPAD_LEFT:
148 if (handleKeyEvent) {
149 // Select the previous widget or the last widget on the previous page
150 if (widgetIndex > 0) {
151 parent.getChildAt(widgetIndex - 1).requestFocus();
152 } else {
153 if (pageIndex > 0) {
154 newParent = (PagedViewGridLayout)
155 container.getChildAt(pageIndex - 1);
Winson Chung3b0b46a2011-07-28 10:45:09 -0700156 child = newParent.getChildAt(newParent.getChildCount() - 1);
157 if (child != null) child.requestFocus();
Winson Chung4e6a9762011-05-09 11:56:34 -0700158 }
159 }
160 }
161 wasHandled = true;
162 break;
163 case KeyEvent.KEYCODE_DPAD_RIGHT:
164 if (handleKeyEvent) {
165 // Select the next widget or the first widget on the next page
166 if (widgetIndex < (widgetCount - 1)) {
167 parent.getChildAt(widgetIndex + 1).requestFocus();
168 } else {
169 if (pageIndex < (pageCount - 1)) {
170 newParent = (PagedViewGridLayout)
171 container.getChildAt(pageIndex + 1);
Winson Chung3b0b46a2011-07-28 10:45:09 -0700172 child = newParent.getChildAt(0);
173 if (child != null) child.requestFocus();
Winson Chung4e6a9762011-05-09 11:56:34 -0700174 }
175 }
176 }
177 wasHandled = true;
178 break;
179 case KeyEvent.KEYCODE_DPAD_UP:
180 if (handleKeyEvent) {
181 // Select the closest icon in the previous row, otherwise select the tab bar
182 if (y > 0) {
183 int newWidgetIndex = ((y - 1) * cellCountX) + x;
Winson Chung3b0b46a2011-07-28 10:45:09 -0700184 child = parent.getChildAt(newWidgetIndex);
185 if (child != null) child.requestFocus();
Winson Chung4e6a9762011-05-09 11:56:34 -0700186 } else {
187 tabs.requestFocus();
188 }
189 }
190 wasHandled = true;
191 break;
192 case KeyEvent.KEYCODE_DPAD_DOWN:
193 if (handleKeyEvent) {
194 // Select the closest icon in the previous row, otherwise do nothing
195 if (y < (cellCountY - 1)) {
196 int newWidgetIndex = Math.min(widgetCount - 1, ((y + 1) * cellCountX) + x);
Winson Chung3b0b46a2011-07-28 10:45:09 -0700197 child = parent.getChildAt(newWidgetIndex);
198 if (child != null) child.requestFocus();
Winson Chung4e6a9762011-05-09 11:56:34 -0700199 }
200 }
201 wasHandled = true;
202 break;
203 case KeyEvent.KEYCODE_ENTER:
204 case KeyEvent.KEYCODE_DPAD_CENTER:
205 if (handleKeyEvent) {
206 // Simulate a click on the widget
207 View.OnClickListener clickListener = (View.OnClickListener) container;
208 clickListener.onClick(w);
209 }
210 wasHandled = true;
211 break;
212 case KeyEvent.KEYCODE_PAGE_UP:
213 if (handleKeyEvent) {
214 // Select the first item on the previous page, or the first item on this page
215 // if there is no previous page
216 if (pageIndex > 0) {
217 newParent = (PagedViewGridLayout) container.getChildAt(pageIndex - 1);
Winson Chung3b0b46a2011-07-28 10:45:09 -0700218 child = newParent.getChildAt(0);
Winson Chung4e6a9762011-05-09 11:56:34 -0700219 } else {
Winson Chung3b0b46a2011-07-28 10:45:09 -0700220 child = parent.getChildAt(0);
Winson Chung4e6a9762011-05-09 11:56:34 -0700221 }
Winson Chung3b0b46a2011-07-28 10:45:09 -0700222 if (child != null) child.requestFocus();
Winson Chung4e6a9762011-05-09 11:56:34 -0700223 }
224 wasHandled = true;
225 break;
226 case KeyEvent.KEYCODE_PAGE_DOWN:
227 if (handleKeyEvent) {
228 // Select the first item on the next page, or the last item on this page
229 // if there is no next page
230 if (pageIndex < (pageCount - 1)) {
231 newParent = (PagedViewGridLayout) container.getChildAt(pageIndex + 1);
Winson Chung3b0b46a2011-07-28 10:45:09 -0700232 child = newParent.getChildAt(0);
Winson Chung4e6a9762011-05-09 11:56:34 -0700233 } else {
Winson Chung3b0b46a2011-07-28 10:45:09 -0700234 child = parent.getChildAt(widgetCount - 1);
Winson Chung4e6a9762011-05-09 11:56:34 -0700235 }
Winson Chung3b0b46a2011-07-28 10:45:09 -0700236 if (child != null) child.requestFocus();
Winson Chung4e6a9762011-05-09 11:56:34 -0700237 }
238 wasHandled = true;
239 break;
240 case KeyEvent.KEYCODE_MOVE_HOME:
241 if (handleKeyEvent) {
242 // Select the first item on this page
Winson Chung3b0b46a2011-07-28 10:45:09 -0700243 child = parent.getChildAt(0);
244 if (child != null) child.requestFocus();
Winson Chung4e6a9762011-05-09 11:56:34 -0700245 }
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);
Winson Chung90576b52011-09-27 17:45:27 -0700281 final int iconIndex = parent.indexOfChild(v);
Winson Chung97d85d22011-04-13 11:27:36 -0700282 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();
Winson Chung90576b52011-09-27 17:45:27 -0700287 final int x = iconIndex % cellCountX;
288 final int y = iconIndex / cellCountX;
Winson Chung97d85d22011-04-13 11:27:36 -0700289
290 final int action = e.getAction();
291 final boolean handleKeyEvent = (action != KeyEvent.ACTION_UP);
292 PagedViewCellLayoutChildren newParent = null;
Winson Chung90576b52011-09-27 17:45:27 -0700293 // Side pages do not always load synchronously, so check before focusing child siblings
294 // willy-nilly
295 View child = null;
Winson Chung97d85d22011-04-13 11:27:36 -0700296 boolean wasHandled = false;
297 switch (keyCode) {
298 case KeyEvent.KEYCODE_DPAD_LEFT:
299 if (handleKeyEvent) {
300 // Select the previous icon or the last icon on the previous page
Winson Chung90576b52011-09-27 17:45:27 -0700301 if (iconIndex > 0) {
302 parent.getChildAt(iconIndex - 1).requestFocus();
Winson Chung97d85d22011-04-13 11:27:36 -0700303 } else {
304 if (pageIndex > 0) {
305 newParent = getPagedViewCellLayoutChildrenForIndex(container,
306 pageIndex - 1);
Winson Chung90576b52011-09-27 17:45:27 -0700307 if (newParent != null) {
308 child = newParent.getChildAt(newParent.getChildCount() - 1);
309 if (child != null) child.requestFocus();
310 }
Winson Chung97d85d22011-04-13 11:27:36 -0700311 }
312 }
313 }
314 wasHandled = true;
315 break;
316 case KeyEvent.KEYCODE_DPAD_RIGHT:
317 if (handleKeyEvent) {
318 // Select the next icon or the first icon on the next page
Winson Chung90576b52011-09-27 17:45:27 -0700319 if (iconIndex < (widgetCount - 1)) {
320 parent.getChildAt(iconIndex + 1).requestFocus();
Winson Chung97d85d22011-04-13 11:27:36 -0700321 } else {
322 if (pageIndex < (pageCount - 1)) {
323 newParent = getPagedViewCellLayoutChildrenForIndex(container,
324 pageIndex + 1);
Winson Chung90576b52011-09-27 17:45:27 -0700325 if (newParent != null) {
326 child = newParent.getChildAt(0);
327 if (child != null) child.requestFocus();
328 }
Winson Chung97d85d22011-04-13 11:27:36 -0700329 }
330 }
331 }
332 wasHandled = true;
333 break;
334 case KeyEvent.KEYCODE_DPAD_UP:
335 if (handleKeyEvent) {
336 // Select the closest icon in the previous row, otherwise select the tab bar
337 if (y > 0) {
Winson Chung90576b52011-09-27 17:45:27 -0700338 int newiconIndex = ((y - 1) * cellCountX) + x;
339 parent.getChildAt(newiconIndex).requestFocus();
Winson Chung97d85d22011-04-13 11:27:36 -0700340 } else {
341 tabs.requestFocus();
342 }
343 }
344 wasHandled = true;
345 break;
346 case KeyEvent.KEYCODE_DPAD_DOWN:
347 if (handleKeyEvent) {
348 // Select the closest icon in the previous row, otherwise do nothing
349 if (y < (cellCountY - 1)) {
Winson Chung90576b52011-09-27 17:45:27 -0700350 int newiconIndex = Math.min(widgetCount - 1, ((y + 1) * cellCountX) + x);
351 parent.getChildAt(newiconIndex).requestFocus();
Winson Chung97d85d22011-04-13 11:27:36 -0700352 }
353 }
354 wasHandled = true;
355 break;
356 case KeyEvent.KEYCODE_ENTER:
357 case KeyEvent.KEYCODE_DPAD_CENTER:
358 if (handleKeyEvent) {
359 // Simulate a click on the icon
360 View.OnClickListener clickListener = (View.OnClickListener) container;
361 clickListener.onClick(v);
362 }
363 wasHandled = true;
364 break;
365 case KeyEvent.KEYCODE_PAGE_UP:
366 if (handleKeyEvent) {
367 // Select the first icon on the previous page, or the first icon on this page
368 // if there is no previous page
369 if (pageIndex > 0) {
370 newParent = getPagedViewCellLayoutChildrenForIndex(container,
371 pageIndex - 1);
Winson Chung90576b52011-09-27 17:45:27 -0700372 if (newParent != null) {
373 child = newParent.getChildAt(0);
374 if (child != null) child.requestFocus();
375 }
Winson Chung97d85d22011-04-13 11:27:36 -0700376 } else {
377 parent.getChildAt(0).requestFocus();
378 }
379 }
380 wasHandled = true;
381 break;
382 case KeyEvent.KEYCODE_PAGE_DOWN:
383 if (handleKeyEvent) {
384 // Select the first icon on the next page, or the last icon on this page
385 // if there is no next page
386 if (pageIndex < (pageCount - 1)) {
387 newParent = getPagedViewCellLayoutChildrenForIndex(container,
388 pageIndex + 1);
Winson Chung90576b52011-09-27 17:45:27 -0700389 if (newParent != null) {
390 child = newParent.getChildAt(0);
391 if (child != null) child.requestFocus();
392 }
Winson Chung97d85d22011-04-13 11:27:36 -0700393 } else {
394 parent.getChildAt(widgetCount - 1).requestFocus();
395 }
396 }
397 wasHandled = true;
398 break;
399 case KeyEvent.KEYCODE_MOVE_HOME:
400 if (handleKeyEvent) {
401 // Select the first icon on this page
402 parent.getChildAt(0).requestFocus();
403 }
404 wasHandled = true;
405 break;
406 case KeyEvent.KEYCODE_MOVE_END:
407 if (handleKeyEvent) {
408 // Select the last icon on this page
409 parent.getChildAt(widgetCount - 1).requestFocus();
410 }
411 wasHandled = true;
412 break;
413 default: break;
414 }
415 return wasHandled;
416 }
417
418 /**
419 * Handles key events in the tab widget.
420 */
421 static boolean handleTabKeyEvent(AccessibleTabView v, int keyCode, KeyEvent e) {
Michael Jurkaa2eb1702011-05-12 14:57:05 -0700422 if (!LauncherApplication.isScreenLarge()) return false;
Winson Chung97d85d22011-04-13 11:27:36 -0700423
424 final FocusOnlyTabWidget parent = (FocusOnlyTabWidget) v.getParent();
425 final TabHost tabHost = findTabHostParent(parent);
426 final ViewGroup contents = (ViewGroup)
427 tabHost.findViewById(com.android.internal.R.id.tabcontent);
428 final int tabCount = parent.getTabCount();
429 final int tabIndex = parent.getChildTabIndex(v);
430
431 final int action = e.getAction();
432 final boolean handleKeyEvent = (action != KeyEvent.ACTION_UP);
433 boolean wasHandled = false;
434 switch (keyCode) {
435 case KeyEvent.KEYCODE_DPAD_LEFT:
436 if (handleKeyEvent) {
437 // Select the previous tab
438 if (tabIndex > 0) {
439 parent.getChildTabViewAt(tabIndex - 1).requestFocus();
440 }
441 }
442 wasHandled = true;
443 break;
444 case KeyEvent.KEYCODE_DPAD_RIGHT:
445 if (handleKeyEvent) {
446 // Select the next tab, or if the last tab has a focus right id, select that
447 if (tabIndex < (tabCount - 1)) {
448 parent.getChildTabViewAt(tabIndex + 1).requestFocus();
449 } else {
450 if (v.getNextFocusRightId() != View.NO_ID) {
451 tabHost.findViewById(v.getNextFocusRightId()).requestFocus();
452 }
453 }
454 }
455 wasHandled = true;
456 break;
457 case KeyEvent.KEYCODE_DPAD_UP:
458 // Do nothing
459 wasHandled = true;
460 break;
461 case KeyEvent.KEYCODE_DPAD_DOWN:
462 if (handleKeyEvent) {
463 // Select the content view
464 contents.requestFocus();
465 }
466 wasHandled = true;
467 break;
468 default: break;
469 }
470 return wasHandled;
471 }
472
473 /**
Winson Chung3d503fb2011-07-13 17:25:49 -0700474 * Handles key events in the workspace hotseat (bottom of the screen).
Winson Chung4e6a9762011-05-09 11:56:34 -0700475 */
Winson Chung3d503fb2011-07-13 17:25:49 -0700476 static boolean handleHotseatButtonKeyEvent(View v, int keyCode, KeyEvent e, int orientation) {
Winson Chung4e6a9762011-05-09 11:56:34 -0700477 final ViewGroup parent = (ViewGroup) v.getParent();
478 final ViewGroup launcher = (ViewGroup) parent.getParent();
479 final Workspace workspace = (Workspace) launcher.findViewById(R.id.workspace);
480 final int buttonIndex = parent.indexOfChild(v);
481 final int buttonCount = parent.getChildCount();
482 final int pageIndex = workspace.getCurrentPage();
483 final int pageCount = workspace.getChildCount();
Winson Chung4e6a9762011-05-09 11:56:34 -0700484
485 // NOTE: currently we don't special case for the phone UI in different
Winson Chung3d503fb2011-07-13 17:25:49 -0700486 // orientations, even though the hotseat is on the side in landscape mode. This
Winson Chung4e6a9762011-05-09 11:56:34 -0700487 // is to ensure that accessibility consistency is maintained across rotations.
488
489 final int action = e.getAction();
490 final boolean handleKeyEvent = (action != KeyEvent.ACTION_UP);
491 boolean wasHandled = false;
492 switch (keyCode) {
493 case KeyEvent.KEYCODE_DPAD_LEFT:
494 if (handleKeyEvent) {
Winson Chung74608b52011-06-23 13:23:20 -0700495 // Select the previous button, otherwise snap to the previous page
Winson Chung4e6a9762011-05-09 11:56:34 -0700496 if (buttonIndex > 0) {
497 parent.getChildAt(buttonIndex - 1).requestFocus();
498 } else {
Winson Chung74608b52011-06-23 13:23:20 -0700499 workspace.snapToPage(pageIndex - 1);
Winson Chung4e6a9762011-05-09 11:56:34 -0700500 }
501 }
502 wasHandled = true;
503 break;
504 case KeyEvent.KEYCODE_DPAD_RIGHT:
505 if (handleKeyEvent) {
Winson Chung74608b52011-06-23 13:23:20 -0700506 // Select the next button, otherwise snap to the next page
Winson Chung4e6a9762011-05-09 11:56:34 -0700507 if (buttonIndex < (buttonCount - 1)) {
508 parent.getChildAt(buttonIndex + 1).requestFocus();
509 } else {
Winson Chung74608b52011-06-23 13:23:20 -0700510 workspace.snapToPage(pageIndex + 1);
Winson Chung4e6a9762011-05-09 11:56:34 -0700511 }
512 }
513 wasHandled = true;
514 break;
515 case KeyEvent.KEYCODE_DPAD_UP:
516 if (handleKeyEvent) {
517 // Select the first bubble text view in the current page of the workspace
518 final CellLayout layout = (CellLayout) workspace.getChildAt(pageIndex);
519 final CellLayoutChildren children = layout.getChildrenLayout();
520 final View newIcon = getBubbleTextViewInDirection(layout, children, -1, 1);
521 if (newIcon != null) {
522 newIcon.requestFocus();
523 } else {
524 workspace.requestFocus();
525 }
526 }
527 wasHandled = true;
528 break;
529 case KeyEvent.KEYCODE_DPAD_DOWN:
530 // Do nothing
531 wasHandled = true;
532 break;
533 default: break;
534 }
535 return wasHandled;
536 }
537
538 /**
Winson Chung97d85d22011-04-13 11:27:36 -0700539 * Private helper method to get the CellLayoutChildren given a CellLayout index.
540 */
541 private static CellLayoutChildren getCellLayoutChildrenForIndex(ViewGroup container, int i) {
542 ViewGroup parent = (ViewGroup) container.getChildAt(i);
543 return (CellLayoutChildren) parent.getChildAt(0);
544 }
545
546 /**
547 * Private helper method to sort all the CellLayout children in order of their (x,y) spatially
548 * from top left to bottom right.
549 */
550 private static ArrayList<View> getCellLayoutChildrenSortedSpatially(CellLayout layout,
551 ViewGroup parent) {
552 // First we order each the CellLayout children by their x,y coordinates
553 final int cellCountX = layout.getCountX();
554 final int count = parent.getChildCount();
555 ArrayList<View> views = new ArrayList<View>();
556 for (int j = 0; j < count; ++j) {
557 views.add(parent.getChildAt(j));
558 }
559 Collections.sort(views, new Comparator<View>() {
560 @Override
561 public int compare(View lhs, View rhs) {
562 CellLayout.LayoutParams llp = (CellLayout.LayoutParams) lhs.getLayoutParams();
563 CellLayout.LayoutParams rlp = (CellLayout.LayoutParams) rhs.getLayoutParams();
564 int lvIndex = (llp.cellY * cellCountX) + llp.cellX;
565 int rvIndex = (rlp.cellY * cellCountX) + rlp.cellX;
566 return lvIndex - rvIndex;
567 }
568 });
569 return views;
570 }
571 /**
572 * Private helper method to find the index of the next BubbleTextView in the delta direction.
573 * @param delta either -1 or 1 depending on the direction we want to search
574 */
575 private static View findIndexOfBubbleTextView(ArrayList<View> views, int i, int delta) {
576 // Then we find the next BubbleTextView offset by delta from i
577 final int count = views.size();
578 int newI = i + delta;
579 while (0 <= newI && newI < count) {
580 View newV = views.get(newI);
581 if (newV instanceof BubbleTextView) {
582 return newV;
583 }
584 newI += delta;
585 }
586 return null;
587 }
588 private static View getBubbleTextViewInDirection(CellLayout layout, ViewGroup parent, int i,
589 int delta) {
590 final ArrayList<View> views = getCellLayoutChildrenSortedSpatially(layout, parent);
591 return findIndexOfBubbleTextView(views, i, delta);
592 }
593 private static View getBubbleTextViewInDirection(CellLayout layout, ViewGroup parent, View v,
594 int delta) {
595 final ArrayList<View> views = getCellLayoutChildrenSortedSpatially(layout, parent);
596 return findIndexOfBubbleTextView(views, views.indexOf(v), delta);
597 }
598 /**
599 * Private helper method to find the next closest BubbleTextView in the delta direction on the
600 * next line.
601 * @param delta either -1 or 1 depending on the line and direction we want to search
602 */
603 private static View getClosestBubbleTextViewOnLine(CellLayout layout, ViewGroup parent, View v,
604 int lineDelta) {
605 final ArrayList<View> views = getCellLayoutChildrenSortedSpatially(layout, parent);
606 final CellLayout.LayoutParams lp = (CellLayout.LayoutParams) v.getLayoutParams();
607 final int cellCountX = layout.getCountX();
608 final int cellCountY = layout.getCountY();
609 final int row = lp.cellY;
610 final int newRow = row + lineDelta;
611 if (0 <= newRow && newRow < cellCountY) {
612 float closestDistance = Float.MAX_VALUE;
613 int closestIndex = -1;
614 int index = views.indexOf(v);
615 int endIndex = (lineDelta < 0) ? -1 : views.size();
616 while (index != endIndex) {
617 View newV = views.get(index);
618 CellLayout.LayoutParams tmpLp = (CellLayout.LayoutParams) newV.getLayoutParams();
619 boolean satisfiesRow = (lineDelta < 0) ? (tmpLp.cellY < row) : (tmpLp.cellY > row);
620 if (satisfiesRow && newV instanceof BubbleTextView) {
621 float tmpDistance = (float) Math.sqrt(Math.pow(tmpLp.cellX - lp.cellX, 2) +
622 Math.pow(tmpLp.cellY - lp.cellY, 2));
623 if (tmpDistance < closestDistance) {
624 closestIndex = index;
625 closestDistance = tmpDistance;
626 }
627 }
628 if (index <= endIndex) {
629 ++index;
630 } else {
631 --index;
632 }
633 }
634 if (closestIndex > -1) {
635 return views.get(closestIndex);
636 }
637 }
638 return null;
639 }
640
641 /**
642 * Handles key events in a Workspace containing BubbleTextView.
643 */
644 static boolean handleBubbleTextViewKeyEvent(BubbleTextView v, int keyCode, KeyEvent e) {
Winson Chung97d85d22011-04-13 11:27:36 -0700645 CellLayoutChildren parent = (CellLayoutChildren) v.getParent();
646 final CellLayout layout = (CellLayout) parent.getParent();
647 final Workspace workspace = (Workspace) layout.getParent();
648 final ViewGroup launcher = (ViewGroup) workspace.getParent();
Winson Chungfaa13252011-06-13 18:15:54 -0700649 final ViewGroup tabs = (ViewGroup) launcher.findViewById(R.id.qsb_bar);
Winson Chung4d279d92011-07-21 11:46:32 -0700650 final ViewGroup hotseat = (ViewGroup) launcher.findViewById(R.id.hotseat);
Winson Chung97d85d22011-04-13 11:27:36 -0700651 int iconIndex = parent.indexOfChild(v);
652 int iconCount = parent.getChildCount();
653 int pageIndex = workspace.indexOfChild(layout);
654 int pageCount = workspace.getChildCount();
655
656 final int action = e.getAction();
657 final boolean handleKeyEvent = (action != KeyEvent.ACTION_UP);
658 boolean wasHandled = false;
659 switch (keyCode) {
660 case KeyEvent.KEYCODE_DPAD_LEFT:
661 if (handleKeyEvent) {
662 // Select the previous icon or the last icon on the previous page if possible
663 View newIcon = getBubbleTextViewInDirection(layout, parent, v, -1);
664 if (newIcon != null) {
665 newIcon.requestFocus();
666 } else {
667 if (pageIndex > 0) {
668 parent = getCellLayoutChildrenForIndex(workspace, pageIndex - 1);
669 newIcon = getBubbleTextViewInDirection(layout, parent,
670 parent.getChildCount(), -1);
671 if (newIcon != null) {
672 newIcon.requestFocus();
673 } else {
674 // Snap to the previous page
675 workspace.snapToPage(pageIndex - 1);
676 }
677 }
678 }
679 }
680 wasHandled = true;
681 break;
682 case KeyEvent.KEYCODE_DPAD_RIGHT:
683 if (handleKeyEvent) {
684 // Select the next icon or the first icon on the next page if possible
685 View newIcon = getBubbleTextViewInDirection(layout, parent, v, 1);
686 if (newIcon != null) {
687 newIcon.requestFocus();
688 } else {
689 if (pageIndex < (pageCount - 1)) {
690 parent = getCellLayoutChildrenForIndex(workspace, pageIndex + 1);
691 newIcon = getBubbleTextViewInDirection(layout, parent, -1, 1);
692 if (newIcon != null) {
693 newIcon.requestFocus();
694 } else {
695 // Snap to the next page
696 workspace.snapToPage(pageIndex + 1);
697 }
698 }
699 }
700 }
701 wasHandled = true;
702 break;
703 case KeyEvent.KEYCODE_DPAD_UP:
704 if (handleKeyEvent) {
705 // Select the closest icon in the previous line, otherwise select the tab bar
706 View newIcon = getClosestBubbleTextViewOnLine(layout, parent, v, -1);
707 if (newIcon != null) {
708 newIcon.requestFocus();
709 wasHandled = true;
710 } else {
711 tabs.requestFocus();
712 }
713 }
714 break;
715 case KeyEvent.KEYCODE_DPAD_DOWN:
716 if (handleKeyEvent) {
Winson Chung4d279d92011-07-21 11:46:32 -0700717 // Select the closest icon in the next line, otherwise select the button bar
Winson Chung97d85d22011-04-13 11:27:36 -0700718 View newIcon = getClosestBubbleTextViewOnLine(layout, parent, v, 1);
719 if (newIcon != null) {
720 newIcon.requestFocus();
721 wasHandled = true;
Winson Chung4d279d92011-07-21 11:46:32 -0700722 } else if (hotseat != null) {
723 hotseat.requestFocus();
Winson Chung97d85d22011-04-13 11:27:36 -0700724 }
725 }
726 break;
727 case KeyEvent.KEYCODE_PAGE_UP:
728 if (handleKeyEvent) {
729 // Select the first icon on the previous page or the first icon on this page
730 // if there is no previous page
731 if (pageIndex > 0) {
732 parent = getCellLayoutChildrenForIndex(workspace, pageIndex - 1);
733 View newIcon = getBubbleTextViewInDirection(layout, parent, -1, 1);
734 if (newIcon != null) {
735 newIcon.requestFocus();
736 } else {
737 // Snap to the previous page
738 workspace.snapToPage(pageIndex - 1);
739 }
740 } else {
741 View newIcon = getBubbleTextViewInDirection(layout, parent, -1, 1);
742 if (newIcon != null) {
743 newIcon.requestFocus();
744 }
745 }
746 }
747 wasHandled = true;
748 break;
749 case KeyEvent.KEYCODE_PAGE_DOWN:
750 if (handleKeyEvent) {
751 // Select the first icon on the next page or the last icon on this page
752 // if there is no previous page
753 if (pageIndex < (pageCount - 1)) {
754 parent = getCellLayoutChildrenForIndex(workspace, pageIndex + 1);
755 View newIcon = getBubbleTextViewInDirection(layout, parent, -1, 1);
756 if (newIcon != null) {
757 newIcon.requestFocus();
758 } else {
759 // Snap to the next page
760 workspace.snapToPage(pageIndex + 1);
761 }
762 } else {
763 View newIcon = getBubbleTextViewInDirection(layout, parent,
764 parent.getChildCount(), -1);
765 if (newIcon != null) {
766 newIcon.requestFocus();
767 }
768 }
769 }
770 wasHandled = true;
771 break;
772 case KeyEvent.KEYCODE_MOVE_HOME:
773 if (handleKeyEvent) {
774 // Select the first icon on this page
775 View newIcon = getBubbleTextViewInDirection(layout, parent, -1, 1);
776 if (newIcon != null) {
777 newIcon.requestFocus();
778 }
779 }
780 wasHandled = true;
781 break;
782 case KeyEvent.KEYCODE_MOVE_END:
783 if (handleKeyEvent) {
784 // Select the last icon on this page
785 View newIcon = getBubbleTextViewInDirection(layout, parent,
786 parent.getChildCount(), -1);
787 if (newIcon != null) {
788 newIcon.requestFocus();
789 }
790 }
791 wasHandled = true;
792 break;
793 default: break;
794 }
795 return wasHandled;
796 }
797}