blob: 00664409afb25f992f12f79c0718d7e9efa4f5ca [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 */
Adam Cohenac56cff2011-09-28 20:45:37 -070036class IconKeyEventListener implements View.OnKeyListener {
Winson Chung4d279d92011-07-21 11:46:32 -070037 public boolean onKey(View v, int keyCode, KeyEvent event) {
Adam Cohenac56cff2011-09-28 20:45:37 -070038 return FocusHelper.handleIconKeyEvent(v, keyCode, event);
39 }
40}
41
42/**
43 * A keyboard listener we set on all the workspace icons.
44 */
45class FolderKeyEventListener implements View.OnKeyListener {
46 public boolean onKey(View v, int keyCode, KeyEvent event) {
47 return FocusHelper.handleFolderKeyEvent(v, keyCode, event);
Winson Chung4d279d92011-07-21 11:46:32 -070048 }
49}
50
51/**
Winson Chung3d503fb2011-07-13 17:25:49 -070052 * A keyboard listener we set on all the hotseat buttons.
Winson Chung4e6a9762011-05-09 11:56:34 -070053 */
Adam Cohenac56cff2011-09-28 20:45:37 -070054class HotseatIconKeyEventListener implements View.OnKeyListener {
Winson Chung4e6a9762011-05-09 11:56:34 -070055 public boolean onKey(View v, int keyCode, KeyEvent event) {
56 final Configuration configuration = v.getResources().getConfiguration();
Winson Chung3d503fb2011-07-13 17:25:49 -070057 return FocusHelper.handleHotseatButtonKeyEvent(v, keyCode, event, configuration.orientation);
Winson Chung4e6a9762011-05-09 11:56:34 -070058 }
59}
60
61/**
Winson Chungfaa13252011-06-13 18:15:54 -070062 * A keyboard listener we set on the last tab button in AppsCustomize to jump to then
Winson Chung97d85d22011-04-13 11:27:36 -070063 * market icon and vice versa.
64 */
Winson Chungfaa13252011-06-13 18:15:54 -070065class AppsCustomizeTabKeyEventListener implements View.OnKeyListener {
Winson Chung97d85d22011-04-13 11:27:36 -070066 public boolean onKey(View v, int keyCode, KeyEvent event) {
Winson Chungfaa13252011-06-13 18:15:54 -070067 return FocusHelper.handleAppsCustomizeTabKeyEvent(v, keyCode, event);
Winson Chung97d85d22011-04-13 11:27:36 -070068 }
69}
70
71public class FocusHelper {
72 /**
73 * Private helper to get the parent TabHost in the view hiearchy.
74 */
75 private static TabHost findTabHostParent(View v) {
76 ViewParent p = v.getParent();
77 while (p != null && !(p instanceof TabHost)) {
78 p = p.getParent();
79 }
80 return (TabHost) p;
81 }
82
83 /**
Winson Chungfaa13252011-06-13 18:15:54 -070084 * Handles key events in a AppsCustomize tab between the last tab view and the shop button.
Winson Chung97d85d22011-04-13 11:27:36 -070085 */
Winson Chungfaa13252011-06-13 18:15:54 -070086 static boolean handleAppsCustomizeTabKeyEvent(View v, int keyCode, KeyEvent e) {
Winson Chung97d85d22011-04-13 11:27:36 -070087 final TabHost tabHost = findTabHostParent(v);
88 final ViewGroup contents = (ViewGroup)
89 tabHost.findViewById(com.android.internal.R.id.tabcontent);
90 final View shop = tabHost.findViewById(R.id.market_button);
91
92 final int action = e.getAction();
93 final boolean handleKeyEvent = (action != KeyEvent.ACTION_UP);
94 boolean wasHandled = false;
95 switch (keyCode) {
96 case KeyEvent.KEYCODE_DPAD_RIGHT:
97 if (handleKeyEvent) {
98 // Select the shop button if we aren't on it
99 if (v != shop) {
100 shop.requestFocus();
101 }
102 }
103 wasHandled = true;
104 break;
105 case KeyEvent.KEYCODE_DPAD_DOWN:
106 if (handleKeyEvent) {
107 // Select the content view (down is handled by the tab key handler otherwise)
108 if (v == shop) {
109 contents.requestFocus();
110 wasHandled = true;
111 }
112 }
113 break;
114 default: break;
115 }
116 return wasHandled;
117 }
118
119 /**
Adam Cohenae4f1552011-10-20 00:15:42 -0700120 * Returns the Viewgroup containing page contents for the page at the index specified.
121 */
122 private static ViewGroup getAppsCustomizePage(ViewGroup container, int index) {
123 ViewGroup page = (ViewGroup) ((PagedView) container).getPageAt(index);
124 if (page instanceof PagedViewCellLayout) {
125 // There are two layers, a PagedViewCellLayout and PagedViewCellLayoutChildren
126 page = (ViewGroup) page.getChildAt(0);
127 }
128 return page;
129 }
130
131 /**
Winson Chung97d85d22011-04-13 11:27:36 -0700132 * Handles key events in a PageViewExtendedLayout containing PagedViewWidgets.
Winson Chung4e6a9762011-05-09 11:56:34 -0700133 */
134 static boolean handlePagedViewGridLayoutWidgetKeyEvent(PagedViewWidget w, int keyCode,
135 KeyEvent e) {
136
137 final PagedViewGridLayout parent = (PagedViewGridLayout) w.getParent();
Adam Cohenae4f1552011-10-20 00:15:42 -0700138 final PagedView container = (PagedView) parent.getParent();
Winson Chung4e6a9762011-05-09 11:56:34 -0700139 final TabHost tabHost = findTabHostParent(container);
140 final TabWidget tabs = (TabWidget) tabHost.findViewById(com.android.internal.R.id.tabs);
141 final int widgetIndex = parent.indexOfChild(w);
142 final int widgetCount = parent.getChildCount();
Adam Cohenae4f1552011-10-20 00:15:42 -0700143 final int pageIndex = ((PagedView) container).indexToPage(container.indexOfChild(parent));
Winson Chung4e6a9762011-05-09 11:56:34 -0700144 final int pageCount = container.getChildCount();
145 final int cellCountX = parent.getCellCountX();
146 final int cellCountY = parent.getCellCountY();
147 final int x = widgetIndex % cellCountX;
148 final int y = widgetIndex / cellCountX;
149
150 final int action = e.getAction();
151 final boolean handleKeyEvent = (action != KeyEvent.ACTION_UP);
Adam Cohenae4f1552011-10-20 00:15:42 -0700152 ViewGroup newParent = null;
Winson Chung3b0b46a2011-07-28 10:45:09 -0700153 // Now that we load items in the bg asynchronously, we can't just focus
154 // child siblings willy-nilly
155 View child = null;
Winson Chung4e6a9762011-05-09 11:56:34 -0700156 boolean wasHandled = false;
157 switch (keyCode) {
158 case KeyEvent.KEYCODE_DPAD_LEFT:
159 if (handleKeyEvent) {
160 // Select the previous widget or the last widget on the previous page
161 if (widgetIndex > 0) {
162 parent.getChildAt(widgetIndex - 1).requestFocus();
163 } else {
164 if (pageIndex > 0) {
Adam Cohenae4f1552011-10-20 00:15:42 -0700165 newParent = getAppsCustomizePage(container, pageIndex - 1);
166 if (newParent != null) {
167 child = newParent.getChildAt(newParent.getChildCount() - 1);
168 if (child != null) child.requestFocus();
169 }
Winson Chung4e6a9762011-05-09 11:56:34 -0700170 }
171 }
172 }
173 wasHandled = true;
174 break;
175 case KeyEvent.KEYCODE_DPAD_RIGHT:
176 if (handleKeyEvent) {
177 // Select the next widget or the first widget on the next page
178 if (widgetIndex < (widgetCount - 1)) {
179 parent.getChildAt(widgetIndex + 1).requestFocus();
180 } else {
181 if (pageIndex < (pageCount - 1)) {
Adam Cohenae4f1552011-10-20 00:15:42 -0700182 newParent = getAppsCustomizePage(container, pageIndex + 1);
183 if (newParent != null) {
184 child = newParent.getChildAt(0);
185 if (child != null) child.requestFocus();
186 }
Winson Chung4e6a9762011-05-09 11:56:34 -0700187 }
188 }
189 }
190 wasHandled = true;
191 break;
192 case KeyEvent.KEYCODE_DPAD_UP:
193 if (handleKeyEvent) {
194 // Select the closest icon in the previous row, otherwise select the tab bar
195 if (y > 0) {
196 int newWidgetIndex = ((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 } else {
200 tabs.requestFocus();
201 }
202 }
203 wasHandled = true;
204 break;
205 case KeyEvent.KEYCODE_DPAD_DOWN:
206 if (handleKeyEvent) {
207 // Select the closest icon in the previous row, otherwise do nothing
208 if (y < (cellCountY - 1)) {
209 int newWidgetIndex = Math.min(widgetCount - 1, ((y + 1) * cellCountX) + x);
Winson Chung3b0b46a2011-07-28 10:45:09 -0700210 child = parent.getChildAt(newWidgetIndex);
211 if (child != null) child.requestFocus();
Winson Chung4e6a9762011-05-09 11:56:34 -0700212 }
213 }
214 wasHandled = true;
215 break;
216 case KeyEvent.KEYCODE_ENTER:
217 case KeyEvent.KEYCODE_DPAD_CENTER:
218 if (handleKeyEvent) {
219 // Simulate a click on the widget
220 View.OnClickListener clickListener = (View.OnClickListener) container;
221 clickListener.onClick(w);
222 }
223 wasHandled = true;
224 break;
225 case KeyEvent.KEYCODE_PAGE_UP:
226 if (handleKeyEvent) {
227 // Select the first item on the previous page, or the first item on this page
228 // if there is no previous page
229 if (pageIndex > 0) {
Adam Cohenae4f1552011-10-20 00:15:42 -0700230 newParent = getAppsCustomizePage(container, pageIndex - 1);
231 if (newParent != null) {
232 child = newParent.getChildAt(0);
233 }
Winson Chung4e6a9762011-05-09 11:56:34 -0700234 } else {
Winson Chung3b0b46a2011-07-28 10:45:09 -0700235 child = parent.getChildAt(0);
Winson Chung4e6a9762011-05-09 11:56:34 -0700236 }
Winson Chung3b0b46a2011-07-28 10:45:09 -0700237 if (child != null) child.requestFocus();
Winson Chung4e6a9762011-05-09 11:56:34 -0700238 }
239 wasHandled = true;
240 break;
241 case KeyEvent.KEYCODE_PAGE_DOWN:
242 if (handleKeyEvent) {
243 // Select the first item on the next page, or the last item on this page
244 // if there is no next page
245 if (pageIndex < (pageCount - 1)) {
Adam Cohenae4f1552011-10-20 00:15:42 -0700246 newParent = getAppsCustomizePage(container, pageIndex + 1);
247 if (newParent != null) {
248 child = newParent.getChildAt(0);
249 }
Winson Chung4e6a9762011-05-09 11:56:34 -0700250 } else {
Winson Chung3b0b46a2011-07-28 10:45:09 -0700251 child = parent.getChildAt(widgetCount - 1);
Winson Chung4e6a9762011-05-09 11:56:34 -0700252 }
Winson Chung3b0b46a2011-07-28 10:45:09 -0700253 if (child != null) child.requestFocus();
Winson Chung4e6a9762011-05-09 11:56:34 -0700254 }
255 wasHandled = true;
256 break;
257 case KeyEvent.KEYCODE_MOVE_HOME:
258 if (handleKeyEvent) {
259 // Select the first item on this page
Winson Chung3b0b46a2011-07-28 10:45:09 -0700260 child = parent.getChildAt(0);
261 if (child != null) child.requestFocus();
Winson Chung4e6a9762011-05-09 11:56:34 -0700262 }
263 wasHandled = true;
264 break;
265 case KeyEvent.KEYCODE_MOVE_END:
266 if (handleKeyEvent) {
267 // Select the last item on this page
268 parent.getChildAt(widgetCount - 1).requestFocus();
269 }
270 wasHandled = true;
271 break;
272 default: break;
273 }
274 return wasHandled;
275 }
276
277 /**
Winson Chung97d85d22011-04-13 11:27:36 -0700278 * Handles key events in a PageViewCellLayout containing PagedViewIcons.
279 */
Adam Cohenae4f1552011-10-20 00:15:42 -0700280 static boolean handleAppsCustomizeKeyEvent(View v, int keyCode, KeyEvent e) {
281 ViewGroup parentLayout;
282 ViewGroup itemContainer;
283 int countX;
284 int countY;
285 if (v.getParent() instanceof PagedViewCellLayoutChildren) {
286 itemContainer = (ViewGroup) v.getParent();
287 parentLayout = (ViewGroup) itemContainer.getParent();
288 countX = ((PagedViewCellLayout) parentLayout).getCellCountX();
289 countY = ((PagedViewCellLayout) parentLayout).getCellCountY();
290 } else {
291 itemContainer = parentLayout = (ViewGroup) v.getParent();
292 countX = ((PagedViewGridLayout) parentLayout).getCellCountX();
293 countY = ((PagedViewGridLayout) parentLayout).getCellCountY();
294 }
295
Winson Chung97d85d22011-04-13 11:27:36 -0700296 // Note we have an extra parent because of the
297 // PagedViewCellLayout/PagedViewCellLayoutChildren relationship
Adam Cohenae4f1552011-10-20 00:15:42 -0700298 final PagedView container = (PagedView) parentLayout.getParent();
Winson Chung97d85d22011-04-13 11:27:36 -0700299 final TabHost tabHost = findTabHostParent(container);
300 final TabWidget tabs = (TabWidget) tabHost.findViewById(com.android.internal.R.id.tabs);
Adam Cohenae4f1552011-10-20 00:15:42 -0700301 final int iconIndex = itemContainer.indexOfChild(v);
302 final int itemCount = itemContainer.getChildCount();
303 final int pageIndex = ((PagedView) container).indexToPage(container.indexOfChild(parentLayout));
Winson Chung97d85d22011-04-13 11:27:36 -0700304 final int pageCount = container.getChildCount();
Adam Cohenae4f1552011-10-20 00:15:42 -0700305
306 final int x = iconIndex % countX;
307 final int y = iconIndex / countX;
Winson Chung97d85d22011-04-13 11:27:36 -0700308
309 final int action = e.getAction();
310 final boolean handleKeyEvent = (action != KeyEvent.ACTION_UP);
Adam Cohenae4f1552011-10-20 00:15:42 -0700311 ViewGroup newParent = null;
Winson Chung90576b52011-09-27 17:45:27 -0700312 // Side pages do not always load synchronously, so check before focusing child siblings
313 // willy-nilly
314 View child = null;
Winson Chung97d85d22011-04-13 11:27:36 -0700315 boolean wasHandled = false;
316 switch (keyCode) {
317 case KeyEvent.KEYCODE_DPAD_LEFT:
318 if (handleKeyEvent) {
319 // Select the previous icon or the last icon on the previous page
Winson Chung90576b52011-09-27 17:45:27 -0700320 if (iconIndex > 0) {
Adam Cohenae4f1552011-10-20 00:15:42 -0700321 itemContainer.getChildAt(iconIndex - 1).requestFocus();
Winson Chung97d85d22011-04-13 11:27:36 -0700322 } else {
323 if (pageIndex > 0) {
Adam Cohenae4f1552011-10-20 00:15:42 -0700324 newParent = getAppsCustomizePage(container, pageIndex - 1);
Winson Chung90576b52011-09-27 17:45:27 -0700325 if (newParent != null) {
Adam Cohenae4f1552011-10-20 00:15:42 -0700326 container.snapToPage(pageIndex - 1);
Winson Chung90576b52011-09-27 17:45:27 -0700327 child = newParent.getChildAt(newParent.getChildCount() - 1);
328 if (child != null) child.requestFocus();
329 }
Winson Chung97d85d22011-04-13 11:27:36 -0700330 }
331 }
332 }
333 wasHandled = true;
334 break;
335 case KeyEvent.KEYCODE_DPAD_RIGHT:
336 if (handleKeyEvent) {
337 // Select the next icon or the first icon on the next page
Adam Cohenae4f1552011-10-20 00:15:42 -0700338 if (iconIndex < (itemCount - 1)) {
339 itemContainer.getChildAt(iconIndex + 1).requestFocus();
Winson Chung97d85d22011-04-13 11:27:36 -0700340 } else {
341 if (pageIndex < (pageCount - 1)) {
Adam Cohenae4f1552011-10-20 00:15:42 -0700342 newParent = getAppsCustomizePage(container, pageIndex + 1);
Winson Chung90576b52011-09-27 17:45:27 -0700343 if (newParent != null) {
Adam Cohenae4f1552011-10-20 00:15:42 -0700344 container.snapToPage(pageIndex + 1);
Winson Chung90576b52011-09-27 17:45:27 -0700345 child = newParent.getChildAt(0);
346 if (child != null) child.requestFocus();
347 }
Winson Chung97d85d22011-04-13 11:27:36 -0700348 }
349 }
350 }
351 wasHandled = true;
352 break;
353 case KeyEvent.KEYCODE_DPAD_UP:
354 if (handleKeyEvent) {
355 // Select the closest icon in the previous row, otherwise select the tab bar
356 if (y > 0) {
Adam Cohenae4f1552011-10-20 00:15:42 -0700357 int newiconIndex = ((y - 1) * countX) + x;
358 itemContainer.getChildAt(newiconIndex).requestFocus();
Winson Chung97d85d22011-04-13 11:27:36 -0700359 } else {
360 tabs.requestFocus();
361 }
362 }
363 wasHandled = true;
364 break;
365 case KeyEvent.KEYCODE_DPAD_DOWN:
366 if (handleKeyEvent) {
367 // Select the closest icon in the previous row, otherwise do nothing
Adam Cohenae4f1552011-10-20 00:15:42 -0700368 if (y < (countY - 1)) {
369 int newiconIndex = Math.min(itemCount - 1, ((y + 1) * countX) + x);
370 itemContainer.getChildAt(newiconIndex).requestFocus();
Winson Chung97d85d22011-04-13 11:27:36 -0700371 }
372 }
373 wasHandled = true;
374 break;
375 case KeyEvent.KEYCODE_ENTER:
376 case KeyEvent.KEYCODE_DPAD_CENTER:
377 if (handleKeyEvent) {
378 // Simulate a click on the icon
379 View.OnClickListener clickListener = (View.OnClickListener) container;
380 clickListener.onClick(v);
381 }
382 wasHandled = true;
383 break;
384 case KeyEvent.KEYCODE_PAGE_UP:
385 if (handleKeyEvent) {
386 // Select the first icon on the previous page, or the first icon on this page
387 // if there is no previous page
388 if (pageIndex > 0) {
Adam Cohenae4f1552011-10-20 00:15:42 -0700389 newParent = getAppsCustomizePage(container, pageIndex - 1);
Winson Chung90576b52011-09-27 17:45:27 -0700390 if (newParent != null) {
Adam Cohenae4f1552011-10-20 00:15:42 -0700391 container.snapToPage(pageIndex - 1);
Winson Chung90576b52011-09-27 17:45:27 -0700392 child = newParent.getChildAt(0);
393 if (child != null) child.requestFocus();
394 }
Winson Chung97d85d22011-04-13 11:27:36 -0700395 } else {
Adam Cohenae4f1552011-10-20 00:15:42 -0700396 itemContainer.getChildAt(0).requestFocus();
Winson Chung97d85d22011-04-13 11:27:36 -0700397 }
398 }
399 wasHandled = true;
400 break;
401 case KeyEvent.KEYCODE_PAGE_DOWN:
402 if (handleKeyEvent) {
403 // Select the first icon on the next page, or the last icon on this page
404 // if there is no next page
405 if (pageIndex < (pageCount - 1)) {
Adam Cohenae4f1552011-10-20 00:15:42 -0700406 newParent = getAppsCustomizePage(container, pageIndex + 1);
Winson Chung90576b52011-09-27 17:45:27 -0700407 if (newParent != null) {
Adam Cohenae4f1552011-10-20 00:15:42 -0700408 container.snapToPage(pageIndex + 1);
Winson Chung90576b52011-09-27 17:45:27 -0700409 child = newParent.getChildAt(0);
410 if (child != null) child.requestFocus();
411 }
Winson Chung97d85d22011-04-13 11:27:36 -0700412 } else {
Adam Cohenae4f1552011-10-20 00:15:42 -0700413 itemContainer.getChildAt(itemCount - 1).requestFocus();
Winson Chung97d85d22011-04-13 11:27:36 -0700414 }
415 }
416 wasHandled = true;
417 break;
418 case KeyEvent.KEYCODE_MOVE_HOME:
419 if (handleKeyEvent) {
420 // Select the first icon on this page
Adam Cohenae4f1552011-10-20 00:15:42 -0700421 itemContainer.getChildAt(0).requestFocus();
Winson Chung97d85d22011-04-13 11:27:36 -0700422 }
423 wasHandled = true;
424 break;
425 case KeyEvent.KEYCODE_MOVE_END:
426 if (handleKeyEvent) {
427 // Select the last icon on this page
Adam Cohenae4f1552011-10-20 00:15:42 -0700428 itemContainer.getChildAt(itemCount - 1).requestFocus();
Winson Chung97d85d22011-04-13 11:27:36 -0700429 }
430 wasHandled = true;
431 break;
432 default: break;
433 }
434 return wasHandled;
435 }
436
437 /**
438 * Handles key events in the tab widget.
439 */
440 static boolean handleTabKeyEvent(AccessibleTabView v, int keyCode, KeyEvent e) {
Michael Jurkaa2eb1702011-05-12 14:57:05 -0700441 if (!LauncherApplication.isScreenLarge()) return false;
Winson Chung97d85d22011-04-13 11:27:36 -0700442
443 final FocusOnlyTabWidget parent = (FocusOnlyTabWidget) v.getParent();
444 final TabHost tabHost = findTabHostParent(parent);
445 final ViewGroup contents = (ViewGroup)
446 tabHost.findViewById(com.android.internal.R.id.tabcontent);
447 final int tabCount = parent.getTabCount();
448 final int tabIndex = parent.getChildTabIndex(v);
449
450 final int action = e.getAction();
451 final boolean handleKeyEvent = (action != KeyEvent.ACTION_UP);
452 boolean wasHandled = false;
453 switch (keyCode) {
454 case KeyEvent.KEYCODE_DPAD_LEFT:
455 if (handleKeyEvent) {
456 // Select the previous tab
457 if (tabIndex > 0) {
458 parent.getChildTabViewAt(tabIndex - 1).requestFocus();
459 }
460 }
461 wasHandled = true;
462 break;
463 case KeyEvent.KEYCODE_DPAD_RIGHT:
464 if (handleKeyEvent) {
465 // Select the next tab, or if the last tab has a focus right id, select that
466 if (tabIndex < (tabCount - 1)) {
467 parent.getChildTabViewAt(tabIndex + 1).requestFocus();
468 } else {
469 if (v.getNextFocusRightId() != View.NO_ID) {
470 tabHost.findViewById(v.getNextFocusRightId()).requestFocus();
471 }
472 }
473 }
474 wasHandled = true;
475 break;
476 case KeyEvent.KEYCODE_DPAD_UP:
477 // Do nothing
478 wasHandled = true;
479 break;
480 case KeyEvent.KEYCODE_DPAD_DOWN:
481 if (handleKeyEvent) {
482 // Select the content view
483 contents.requestFocus();
484 }
485 wasHandled = true;
486 break;
487 default: break;
488 }
489 return wasHandled;
490 }
491
492 /**
Winson Chung3d503fb2011-07-13 17:25:49 -0700493 * Handles key events in the workspace hotseat (bottom of the screen).
Winson Chung4e6a9762011-05-09 11:56:34 -0700494 */
Winson Chung3d503fb2011-07-13 17:25:49 -0700495 static boolean handleHotseatButtonKeyEvent(View v, int keyCode, KeyEvent e, int orientation) {
Winson Chung4e6a9762011-05-09 11:56:34 -0700496 final ViewGroup parent = (ViewGroup) v.getParent();
497 final ViewGroup launcher = (ViewGroup) parent.getParent();
498 final Workspace workspace = (Workspace) launcher.findViewById(R.id.workspace);
499 final int buttonIndex = parent.indexOfChild(v);
500 final int buttonCount = parent.getChildCount();
501 final int pageIndex = workspace.getCurrentPage();
Winson Chung4e6a9762011-05-09 11:56:34 -0700502
503 // NOTE: currently we don't special case for the phone UI in different
Winson Chung3d503fb2011-07-13 17:25:49 -0700504 // orientations, even though the hotseat is on the side in landscape mode. This
Winson Chung4e6a9762011-05-09 11:56:34 -0700505 // is to ensure that accessibility consistency is maintained across rotations.
506
507 final int action = e.getAction();
508 final boolean handleKeyEvent = (action != KeyEvent.ACTION_UP);
509 boolean wasHandled = false;
510 switch (keyCode) {
511 case KeyEvent.KEYCODE_DPAD_LEFT:
512 if (handleKeyEvent) {
Winson Chung74608b52011-06-23 13:23:20 -0700513 // Select the previous button, otherwise snap to the previous page
Winson Chung4e6a9762011-05-09 11:56:34 -0700514 if (buttonIndex > 0) {
515 parent.getChildAt(buttonIndex - 1).requestFocus();
516 } else {
Winson Chung74608b52011-06-23 13:23:20 -0700517 workspace.snapToPage(pageIndex - 1);
Winson Chung4e6a9762011-05-09 11:56:34 -0700518 }
519 }
520 wasHandled = true;
521 break;
522 case KeyEvent.KEYCODE_DPAD_RIGHT:
523 if (handleKeyEvent) {
Winson Chung74608b52011-06-23 13:23:20 -0700524 // Select the next button, otherwise snap to the next page
Winson Chung4e6a9762011-05-09 11:56:34 -0700525 if (buttonIndex < (buttonCount - 1)) {
526 parent.getChildAt(buttonIndex + 1).requestFocus();
527 } else {
Winson Chung74608b52011-06-23 13:23:20 -0700528 workspace.snapToPage(pageIndex + 1);
Winson Chung4e6a9762011-05-09 11:56:34 -0700529 }
530 }
531 wasHandled = true;
532 break;
533 case KeyEvent.KEYCODE_DPAD_UP:
534 if (handleKeyEvent) {
535 // Select the first bubble text view in the current page of the workspace
536 final CellLayout layout = (CellLayout) workspace.getChildAt(pageIndex);
Michael Jurkaa52570f2012-03-20 03:18:20 -0700537 final ShortcutAndWidgetContainer children = layout.getShortcutsAndWidgets();
Adam Cohenac56cff2011-09-28 20:45:37 -0700538 final View newIcon = getIconInDirection(layout, children, -1, 1);
Winson Chung4e6a9762011-05-09 11:56:34 -0700539 if (newIcon != null) {
540 newIcon.requestFocus();
541 } else {
542 workspace.requestFocus();
543 }
544 }
545 wasHandled = true;
546 break;
547 case KeyEvent.KEYCODE_DPAD_DOWN:
548 // Do nothing
549 wasHandled = true;
550 break;
551 default: break;
552 }
553 return wasHandled;
554 }
555
556 /**
Winson Chung97d85d22011-04-13 11:27:36 -0700557 * Private helper method to get the CellLayoutChildren given a CellLayout index.
558 */
Michael Jurkaa52570f2012-03-20 03:18:20 -0700559 private static ShortcutAndWidgetContainer getCellLayoutChildrenForIndex(
560 ViewGroup container, int i) {
Winson Chung97d85d22011-04-13 11:27:36 -0700561 ViewGroup parent = (ViewGroup) container.getChildAt(i);
Michael Jurkaa52570f2012-03-20 03:18:20 -0700562 return (ShortcutAndWidgetContainer) parent.getChildAt(0);
Winson Chung97d85d22011-04-13 11:27:36 -0700563 }
564
565 /**
566 * Private helper method to sort all the CellLayout children in order of their (x,y) spatially
567 * from top left to bottom right.
568 */
569 private static ArrayList<View> getCellLayoutChildrenSortedSpatially(CellLayout layout,
570 ViewGroup parent) {
571 // First we order each the CellLayout children by their x,y coordinates
572 final int cellCountX = layout.getCountX();
573 final int count = parent.getChildCount();
574 ArrayList<View> views = new ArrayList<View>();
575 for (int j = 0; j < count; ++j) {
576 views.add(parent.getChildAt(j));
577 }
578 Collections.sort(views, new Comparator<View>() {
579 @Override
580 public int compare(View lhs, View rhs) {
581 CellLayout.LayoutParams llp = (CellLayout.LayoutParams) lhs.getLayoutParams();
582 CellLayout.LayoutParams rlp = (CellLayout.LayoutParams) rhs.getLayoutParams();
583 int lvIndex = (llp.cellY * cellCountX) + llp.cellX;
584 int rvIndex = (rlp.cellY * cellCountX) + rlp.cellX;
585 return lvIndex - rvIndex;
586 }
587 });
588 return views;
589 }
590 /**
Adam Cohenac56cff2011-09-28 20:45:37 -0700591 * Private helper method to find the index of the next BubbleTextView or FolderIcon in the
592 * direction delta.
593 *
Winson Chung97d85d22011-04-13 11:27:36 -0700594 * @param delta either -1 or 1 depending on the direction we want to search
595 */
Adam Cohenac56cff2011-09-28 20:45:37 -0700596 private static View findIndexOfIcon(ArrayList<View> views, int i, int delta) {
Winson Chung97d85d22011-04-13 11:27:36 -0700597 // Then we find the next BubbleTextView offset by delta from i
598 final int count = views.size();
599 int newI = i + delta;
600 while (0 <= newI && newI < count) {
601 View newV = views.get(newI);
Adam Cohenac56cff2011-09-28 20:45:37 -0700602 if (newV instanceof BubbleTextView || newV instanceof FolderIcon) {
Winson Chung97d85d22011-04-13 11:27:36 -0700603 return newV;
604 }
605 newI += delta;
606 }
607 return null;
608 }
Adam Cohenac56cff2011-09-28 20:45:37 -0700609 private static View getIconInDirection(CellLayout layout, ViewGroup parent, int i,
Winson Chung97d85d22011-04-13 11:27:36 -0700610 int delta) {
611 final ArrayList<View> views = getCellLayoutChildrenSortedSpatially(layout, parent);
Adam Cohenac56cff2011-09-28 20:45:37 -0700612 return findIndexOfIcon(views, i, delta);
Winson Chung97d85d22011-04-13 11:27:36 -0700613 }
Adam Cohenac56cff2011-09-28 20:45:37 -0700614 private static View getIconInDirection(CellLayout layout, ViewGroup parent, View v,
Winson Chung97d85d22011-04-13 11:27:36 -0700615 int delta) {
616 final ArrayList<View> views = getCellLayoutChildrenSortedSpatially(layout, parent);
Adam Cohenac56cff2011-09-28 20:45:37 -0700617 return findIndexOfIcon(views, views.indexOf(v), delta);
Winson Chung97d85d22011-04-13 11:27:36 -0700618 }
619 /**
Adam Cohenac56cff2011-09-28 20:45:37 -0700620 * Private helper method to find the next closest BubbleTextView or FolderIcon in the direction
621 * delta on the next line.
622 *
Winson Chung97d85d22011-04-13 11:27:36 -0700623 * @param delta either -1 or 1 depending on the line and direction we want to search
624 */
Adam Cohenac56cff2011-09-28 20:45:37 -0700625 private static View getClosestIconOnLine(CellLayout layout, ViewGroup parent, View v,
Winson Chung97d85d22011-04-13 11:27:36 -0700626 int lineDelta) {
627 final ArrayList<View> views = getCellLayoutChildrenSortedSpatially(layout, parent);
628 final CellLayout.LayoutParams lp = (CellLayout.LayoutParams) v.getLayoutParams();
Winson Chung97d85d22011-04-13 11:27:36 -0700629 final int cellCountY = layout.getCountY();
630 final int row = lp.cellY;
631 final int newRow = row + lineDelta;
632 if (0 <= newRow && newRow < cellCountY) {
633 float closestDistance = Float.MAX_VALUE;
634 int closestIndex = -1;
635 int index = views.indexOf(v);
636 int endIndex = (lineDelta < 0) ? -1 : views.size();
637 while (index != endIndex) {
638 View newV = views.get(index);
639 CellLayout.LayoutParams tmpLp = (CellLayout.LayoutParams) newV.getLayoutParams();
640 boolean satisfiesRow = (lineDelta < 0) ? (tmpLp.cellY < row) : (tmpLp.cellY > row);
Adam Cohenac56cff2011-09-28 20:45:37 -0700641 if (satisfiesRow &&
642 (newV instanceof BubbleTextView || newV instanceof FolderIcon)) {
Winson Chung97d85d22011-04-13 11:27:36 -0700643 float tmpDistance = (float) Math.sqrt(Math.pow(tmpLp.cellX - lp.cellX, 2) +
644 Math.pow(tmpLp.cellY - lp.cellY, 2));
645 if (tmpDistance < closestDistance) {
646 closestIndex = index;
647 closestDistance = tmpDistance;
648 }
649 }
650 if (index <= endIndex) {
651 ++index;
652 } else {
653 --index;
654 }
655 }
656 if (closestIndex > -1) {
657 return views.get(closestIndex);
658 }
659 }
660 return null;
661 }
662
663 /**
Adam Cohenac56cff2011-09-28 20:45:37 -0700664 * Handles key events in a Workspace containing.
Winson Chung97d85d22011-04-13 11:27:36 -0700665 */
Adam Cohenac56cff2011-09-28 20:45:37 -0700666 static boolean handleIconKeyEvent(View v, int keyCode, KeyEvent e) {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700667 ShortcutAndWidgetContainer parent = (ShortcutAndWidgetContainer) v.getParent();
Winson Chung97d85d22011-04-13 11:27:36 -0700668 final CellLayout layout = (CellLayout) parent.getParent();
669 final Workspace workspace = (Workspace) layout.getParent();
670 final ViewGroup launcher = (ViewGroup) workspace.getParent();
Winson Chungfaa13252011-06-13 18:15:54 -0700671 final ViewGroup tabs = (ViewGroup) launcher.findViewById(R.id.qsb_bar);
Winson Chung4d279d92011-07-21 11:46:32 -0700672 final ViewGroup hotseat = (ViewGroup) launcher.findViewById(R.id.hotseat);
Winson Chung97d85d22011-04-13 11:27:36 -0700673 int pageIndex = workspace.indexOfChild(layout);
674 int pageCount = workspace.getChildCount();
675
676 final int action = e.getAction();
677 final boolean handleKeyEvent = (action != KeyEvent.ACTION_UP);
678 boolean wasHandled = false;
679 switch (keyCode) {
680 case KeyEvent.KEYCODE_DPAD_LEFT:
681 if (handleKeyEvent) {
682 // Select the previous icon or the last icon on the previous page if possible
Adam Cohenac56cff2011-09-28 20:45:37 -0700683 View newIcon = getIconInDirection(layout, parent, v, -1);
Winson Chung97d85d22011-04-13 11:27:36 -0700684 if (newIcon != null) {
685 newIcon.requestFocus();
686 } else {
687 if (pageIndex > 0) {
688 parent = getCellLayoutChildrenForIndex(workspace, pageIndex - 1);
Adam Cohenac56cff2011-09-28 20:45:37 -0700689 newIcon = getIconInDirection(layout, parent,
Winson Chung97d85d22011-04-13 11:27:36 -0700690 parent.getChildCount(), -1);
691 if (newIcon != null) {
692 newIcon.requestFocus();
693 } else {
694 // Snap to the previous page
695 workspace.snapToPage(pageIndex - 1);
696 }
697 }
698 }
699 }
700 wasHandled = true;
701 break;
702 case KeyEvent.KEYCODE_DPAD_RIGHT:
703 if (handleKeyEvent) {
704 // Select the next icon or the first icon on the next page if possible
Adam Cohenac56cff2011-09-28 20:45:37 -0700705 View newIcon = getIconInDirection(layout, parent, v, 1);
Winson Chung97d85d22011-04-13 11:27:36 -0700706 if (newIcon != null) {
707 newIcon.requestFocus();
708 } else {
709 if (pageIndex < (pageCount - 1)) {
710 parent = getCellLayoutChildrenForIndex(workspace, pageIndex + 1);
Adam Cohenac56cff2011-09-28 20:45:37 -0700711 newIcon = getIconInDirection(layout, parent, -1, 1);
Winson Chung97d85d22011-04-13 11:27:36 -0700712 if (newIcon != null) {
713 newIcon.requestFocus();
714 } else {
715 // Snap to the next page
716 workspace.snapToPage(pageIndex + 1);
717 }
718 }
719 }
720 }
721 wasHandled = true;
722 break;
723 case KeyEvent.KEYCODE_DPAD_UP:
724 if (handleKeyEvent) {
725 // Select the closest icon in the previous line, otherwise select the tab bar
Adam Cohenac56cff2011-09-28 20:45:37 -0700726 View newIcon = getClosestIconOnLine(layout, parent, v, -1);
Winson Chung97d85d22011-04-13 11:27:36 -0700727 if (newIcon != null) {
728 newIcon.requestFocus();
729 wasHandled = true;
730 } else {
731 tabs.requestFocus();
732 }
733 }
734 break;
735 case KeyEvent.KEYCODE_DPAD_DOWN:
736 if (handleKeyEvent) {
Winson Chung4d279d92011-07-21 11:46:32 -0700737 // Select the closest icon in the next line, otherwise select the button bar
Adam Cohenac56cff2011-09-28 20:45:37 -0700738 View newIcon = getClosestIconOnLine(layout, parent, v, 1);
Winson Chung97d85d22011-04-13 11:27:36 -0700739 if (newIcon != null) {
740 newIcon.requestFocus();
741 wasHandled = true;
Winson Chung4d279d92011-07-21 11:46:32 -0700742 } else if (hotseat != null) {
743 hotseat.requestFocus();
Winson Chung97d85d22011-04-13 11:27:36 -0700744 }
745 }
746 break;
747 case KeyEvent.KEYCODE_PAGE_UP:
748 if (handleKeyEvent) {
749 // Select the first icon on the previous page or the first icon on this page
750 // if there is no previous page
751 if (pageIndex > 0) {
752 parent = getCellLayoutChildrenForIndex(workspace, pageIndex - 1);
Adam Cohenac56cff2011-09-28 20:45:37 -0700753 View newIcon = getIconInDirection(layout, parent, -1, 1);
Winson Chung97d85d22011-04-13 11:27:36 -0700754 if (newIcon != null) {
755 newIcon.requestFocus();
756 } else {
757 // Snap to the previous page
758 workspace.snapToPage(pageIndex - 1);
759 }
760 } else {
Adam Cohenac56cff2011-09-28 20:45:37 -0700761 View newIcon = getIconInDirection(layout, parent, -1, 1);
Winson Chung97d85d22011-04-13 11:27:36 -0700762 if (newIcon != null) {
763 newIcon.requestFocus();
764 }
765 }
766 }
767 wasHandled = true;
768 break;
769 case KeyEvent.KEYCODE_PAGE_DOWN:
770 if (handleKeyEvent) {
771 // Select the first icon on the next page or the last icon on this page
772 // if there is no previous page
773 if (pageIndex < (pageCount - 1)) {
774 parent = getCellLayoutChildrenForIndex(workspace, pageIndex + 1);
Adam Cohenac56cff2011-09-28 20:45:37 -0700775 View newIcon = getIconInDirection(layout, parent, -1, 1);
Winson Chung97d85d22011-04-13 11:27:36 -0700776 if (newIcon != null) {
777 newIcon.requestFocus();
778 } else {
779 // Snap to the next page
780 workspace.snapToPage(pageIndex + 1);
781 }
782 } else {
Adam Cohenac56cff2011-09-28 20:45:37 -0700783 View newIcon = getIconInDirection(layout, parent,
Winson Chung97d85d22011-04-13 11:27:36 -0700784 parent.getChildCount(), -1);
785 if (newIcon != null) {
786 newIcon.requestFocus();
787 }
788 }
789 }
790 wasHandled = true;
791 break;
792 case KeyEvent.KEYCODE_MOVE_HOME:
793 if (handleKeyEvent) {
794 // Select the first icon on this page
Adam Cohenac56cff2011-09-28 20:45:37 -0700795 View newIcon = getIconInDirection(layout, parent, -1, 1);
Winson Chung97d85d22011-04-13 11:27:36 -0700796 if (newIcon != null) {
797 newIcon.requestFocus();
798 }
799 }
800 wasHandled = true;
801 break;
802 case KeyEvent.KEYCODE_MOVE_END:
803 if (handleKeyEvent) {
804 // Select the last icon on this page
Adam Cohenac56cff2011-09-28 20:45:37 -0700805 View newIcon = getIconInDirection(layout, parent,
806 parent.getChildCount(), -1);
807 if (newIcon != null) {
808 newIcon.requestFocus();
809 }
810 }
811 wasHandled = true;
812 break;
813 default: break;
814 }
815 return wasHandled;
816 }
817
818 /**
819 * Handles key events for items in a Folder.
820 */
821 static boolean handleFolderKeyEvent(View v, int keyCode, KeyEvent e) {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700822 ShortcutAndWidgetContainer parent = (ShortcutAndWidgetContainer) v.getParent();
Adam Cohenac56cff2011-09-28 20:45:37 -0700823 final CellLayout layout = (CellLayout) parent.getParent();
824 final Folder folder = (Folder) layout.getParent();
825 View title = folder.mFolderName;
826
827 final int action = e.getAction();
828 final boolean handleKeyEvent = (action != KeyEvent.ACTION_UP);
829 boolean wasHandled = false;
830 switch (keyCode) {
831 case KeyEvent.KEYCODE_DPAD_LEFT:
832 if (handleKeyEvent) {
833 // Select the previous icon
834 View newIcon = getIconInDirection(layout, parent, v, -1);
835 if (newIcon != null) {
836 newIcon.requestFocus();
837 }
838 }
839 wasHandled = true;
840 break;
841 case KeyEvent.KEYCODE_DPAD_RIGHT:
842 if (handleKeyEvent) {
843 // Select the next icon
844 View newIcon = getIconInDirection(layout, parent, v, 1);
845 if (newIcon != null) {
846 newIcon.requestFocus();
847 } else {
848 title.requestFocus();
849 }
850 }
851 wasHandled = true;
852 break;
853 case KeyEvent.KEYCODE_DPAD_UP:
854 if (handleKeyEvent) {
855 // Select the closest icon in the previous line
856 View newIcon = getClosestIconOnLine(layout, parent, v, -1);
857 if (newIcon != null) {
858 newIcon.requestFocus();
859 }
860 }
861 wasHandled = true;
862 break;
863 case KeyEvent.KEYCODE_DPAD_DOWN:
864 if (handleKeyEvent) {
865 // Select the closest icon in the next line
866 View newIcon = getClosestIconOnLine(layout, parent, v, 1);
867 if (newIcon != null) {
868 newIcon.requestFocus();
869 } else {
870 title.requestFocus();
871 }
872 }
873 wasHandled = true;
874 break;
875 case KeyEvent.KEYCODE_MOVE_HOME:
876 if (handleKeyEvent) {
877 // Select the first icon on this page
878 View newIcon = getIconInDirection(layout, parent, -1, 1);
879 if (newIcon != null) {
880 newIcon.requestFocus();
881 }
882 }
883 wasHandled = true;
884 break;
885 case KeyEvent.KEYCODE_MOVE_END:
886 if (handleKeyEvent) {
887 // Select the last icon on this page
888 View newIcon = getIconInDirection(layout, parent,
Winson Chung97d85d22011-04-13 11:27:36 -0700889 parent.getChildCount(), -1);
890 if (newIcon != null) {
891 newIcon.requestFocus();
892 }
893 }
894 wasHandled = true;
895 break;
896 default: break;
897 }
898 return wasHandled;
899 }
900}