blob: 94c5820cece4742147f9aea1addbcdd1fecd414a [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
Daniel Sandler325dc232013-06-05 22:57:57 -040017package com.android.launcher3;
Winson Chung97d85d22011-04-13 11:27:36 -070018
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
Daniel Sandler325dc232013-06-05 22:57:57 -040027import com.android.launcher3.R;
Winson Chung97d85d22011-04-13 11:27:36 -070028
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);
Michael Jurka8b805b12012-04-18 14:23:14 -070088 final ViewGroup contents = tabHost.getTabContentView();
Winson Chung97d85d22011-04-13 11:27:36 -070089 final View shop = tabHost.findViewById(R.id.market_button);
90
91 final int action = e.getAction();
92 final boolean handleKeyEvent = (action != KeyEvent.ACTION_UP);
93 boolean wasHandled = false;
94 switch (keyCode) {
95 case KeyEvent.KEYCODE_DPAD_RIGHT:
96 if (handleKeyEvent) {
97 // Select the shop button if we aren't on it
98 if (v != shop) {
99 shop.requestFocus();
100 }
101 }
102 wasHandled = true;
103 break;
104 case KeyEvent.KEYCODE_DPAD_DOWN:
105 if (handleKeyEvent) {
106 // Select the content view (down is handled by the tab key handler otherwise)
107 if (v == shop) {
108 contents.requestFocus();
109 wasHandled = true;
110 }
111 }
112 break;
113 default: break;
114 }
115 return wasHandled;
116 }
117
118 /**
Adam Cohenae4f1552011-10-20 00:15:42 -0700119 * Returns the Viewgroup containing page contents for the page at the index specified.
120 */
121 private static ViewGroup getAppsCustomizePage(ViewGroup container, int index) {
122 ViewGroup page = (ViewGroup) ((PagedView) container).getPageAt(index);
123 if (page instanceof PagedViewCellLayout) {
124 // There are two layers, a PagedViewCellLayout and PagedViewCellLayoutChildren
125 page = (ViewGroup) page.getChildAt(0);
126 }
127 return page;
128 }
129
130 /**
Winson Chung97d85d22011-04-13 11:27:36 -0700131 * Handles key events in a PageViewExtendedLayout containing PagedViewWidgets.
Winson Chung4e6a9762011-05-09 11:56:34 -0700132 */
133 static boolean handlePagedViewGridLayoutWidgetKeyEvent(PagedViewWidget w, int keyCode,
134 KeyEvent e) {
135
136 final PagedViewGridLayout parent = (PagedViewGridLayout) w.getParent();
Adam Cohenae4f1552011-10-20 00:15:42 -0700137 final PagedView container = (PagedView) parent.getParent();
Winson Chung4e6a9762011-05-09 11:56:34 -0700138 final TabHost tabHost = findTabHostParent(container);
Michael Jurka8b805b12012-04-18 14:23:14 -0700139 final TabWidget tabs = tabHost.getTabWidget();
Winson Chung4e6a9762011-05-09 11:56:34 -0700140 final int widgetIndex = parent.indexOfChild(w);
141 final int widgetCount = parent.getChildCount();
Adam Cohenae4f1552011-10-20 00:15:42 -0700142 final int pageIndex = ((PagedView) container).indexToPage(container.indexOfChild(parent));
Winson Chung4e6a9762011-05-09 11:56:34 -0700143 final int pageCount = container.getChildCount();
144 final int cellCountX = parent.getCellCountX();
145 final int cellCountY = parent.getCellCountY();
146 final int x = widgetIndex % cellCountX;
147 final int y = widgetIndex / cellCountX;
148
149 final int action = e.getAction();
150 final boolean handleKeyEvent = (action != KeyEvent.ACTION_UP);
Adam Cohenae4f1552011-10-20 00:15:42 -0700151 ViewGroup newParent = null;
Winson Chung3b0b46a2011-07-28 10:45:09 -0700152 // Now that we load items in the bg asynchronously, we can't just focus
153 // child siblings willy-nilly
154 View child = null;
Winson Chung4e6a9762011-05-09 11:56:34 -0700155 boolean wasHandled = false;
156 switch (keyCode) {
157 case KeyEvent.KEYCODE_DPAD_LEFT:
158 if (handleKeyEvent) {
159 // Select the previous widget or the last widget on the previous page
160 if (widgetIndex > 0) {
161 parent.getChildAt(widgetIndex - 1).requestFocus();
162 } else {
163 if (pageIndex > 0) {
Adam Cohenae4f1552011-10-20 00:15:42 -0700164 newParent = getAppsCustomizePage(container, pageIndex - 1);
165 if (newParent != null) {
166 child = newParent.getChildAt(newParent.getChildCount() - 1);
167 if (child != null) child.requestFocus();
168 }
Winson Chung4e6a9762011-05-09 11:56:34 -0700169 }
170 }
171 }
172 wasHandled = true;
173 break;
174 case KeyEvent.KEYCODE_DPAD_RIGHT:
175 if (handleKeyEvent) {
176 // Select the next widget or the first widget on the next page
177 if (widgetIndex < (widgetCount - 1)) {
178 parent.getChildAt(widgetIndex + 1).requestFocus();
179 } else {
180 if (pageIndex < (pageCount - 1)) {
Adam Cohenae4f1552011-10-20 00:15:42 -0700181 newParent = getAppsCustomizePage(container, pageIndex + 1);
182 if (newParent != null) {
183 child = newParent.getChildAt(0);
184 if (child != null) child.requestFocus();
185 }
Winson Chung4e6a9762011-05-09 11:56:34 -0700186 }
187 }
188 }
189 wasHandled = true;
190 break;
191 case KeyEvent.KEYCODE_DPAD_UP:
192 if (handleKeyEvent) {
193 // Select the closest icon in the previous row, otherwise select the tab bar
194 if (y > 0) {
195 int newWidgetIndex = ((y - 1) * cellCountX) + x;
Winson Chung3b0b46a2011-07-28 10:45:09 -0700196 child = parent.getChildAt(newWidgetIndex);
197 if (child != null) child.requestFocus();
Winson Chung4e6a9762011-05-09 11:56:34 -0700198 } else {
199 tabs.requestFocus();
200 }
201 }
202 wasHandled = true;
203 break;
204 case KeyEvent.KEYCODE_DPAD_DOWN:
205 if (handleKeyEvent) {
206 // Select the closest icon in the previous row, otherwise do nothing
207 if (y < (cellCountY - 1)) {
208 int newWidgetIndex = Math.min(widgetCount - 1, ((y + 1) * cellCountX) + x);
Winson Chung3b0b46a2011-07-28 10:45:09 -0700209 child = parent.getChildAt(newWidgetIndex);
210 if (child != null) child.requestFocus();
Winson Chung4e6a9762011-05-09 11:56:34 -0700211 }
212 }
213 wasHandled = true;
214 break;
215 case KeyEvent.KEYCODE_ENTER:
216 case KeyEvent.KEYCODE_DPAD_CENTER:
217 if (handleKeyEvent) {
218 // Simulate a click on the widget
219 View.OnClickListener clickListener = (View.OnClickListener) container;
220 clickListener.onClick(w);
221 }
222 wasHandled = true;
223 break;
224 case KeyEvent.KEYCODE_PAGE_UP:
225 if (handleKeyEvent) {
226 // Select the first item on the previous page, or the first item on this page
227 // if there is no previous page
228 if (pageIndex > 0) {
Adam Cohenae4f1552011-10-20 00:15:42 -0700229 newParent = getAppsCustomizePage(container, pageIndex - 1);
230 if (newParent != null) {
231 child = newParent.getChildAt(0);
232 }
Winson Chung4e6a9762011-05-09 11:56:34 -0700233 } else {
Winson Chung3b0b46a2011-07-28 10:45:09 -0700234 child = parent.getChildAt(0);
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_PAGE_DOWN:
241 if (handleKeyEvent) {
242 // Select the first item on the next page, or the last item on this page
243 // if there is no next page
244 if (pageIndex < (pageCount - 1)) {
Adam Cohenae4f1552011-10-20 00:15:42 -0700245 newParent = getAppsCustomizePage(container, pageIndex + 1);
246 if (newParent != null) {
247 child = newParent.getChildAt(0);
248 }
Winson Chung4e6a9762011-05-09 11:56:34 -0700249 } else {
Winson Chung3b0b46a2011-07-28 10:45:09 -0700250 child = parent.getChildAt(widgetCount - 1);
Winson Chung4e6a9762011-05-09 11:56:34 -0700251 }
Winson Chung3b0b46a2011-07-28 10:45:09 -0700252 if (child != null) child.requestFocus();
Winson Chung4e6a9762011-05-09 11:56:34 -0700253 }
254 wasHandled = true;
255 break;
256 case KeyEvent.KEYCODE_MOVE_HOME:
257 if (handleKeyEvent) {
258 // Select the first item on this page
Winson Chung3b0b46a2011-07-28 10:45:09 -0700259 child = parent.getChildAt(0);
260 if (child != null) child.requestFocus();
Winson Chung4e6a9762011-05-09 11:56:34 -0700261 }
262 wasHandled = true;
263 break;
264 case KeyEvent.KEYCODE_MOVE_END:
265 if (handleKeyEvent) {
266 // Select the last item on this page
267 parent.getChildAt(widgetCount - 1).requestFocus();
268 }
269 wasHandled = true;
270 break;
271 default: break;
272 }
273 return wasHandled;
274 }
275
276 /**
Winson Chung97d85d22011-04-13 11:27:36 -0700277 * Handles key events in a PageViewCellLayout containing PagedViewIcons.
278 */
Adam Cohenae4f1552011-10-20 00:15:42 -0700279 static boolean handleAppsCustomizeKeyEvent(View v, int keyCode, KeyEvent e) {
280 ViewGroup parentLayout;
281 ViewGroup itemContainer;
282 int countX;
283 int countY;
284 if (v.getParent() instanceof PagedViewCellLayoutChildren) {
285 itemContainer = (ViewGroup) v.getParent();
286 parentLayout = (ViewGroup) itemContainer.getParent();
287 countX = ((PagedViewCellLayout) parentLayout).getCellCountX();
288 countY = ((PagedViewCellLayout) parentLayout).getCellCountY();
289 } else {
290 itemContainer = parentLayout = (ViewGroup) v.getParent();
291 countX = ((PagedViewGridLayout) parentLayout).getCellCountX();
292 countY = ((PagedViewGridLayout) parentLayout).getCellCountY();
293 }
294
Winson Chung97d85d22011-04-13 11:27:36 -0700295 // Note we have an extra parent because of the
296 // PagedViewCellLayout/PagedViewCellLayoutChildren relationship
Adam Cohenae4f1552011-10-20 00:15:42 -0700297 final PagedView container = (PagedView) parentLayout.getParent();
Winson Chung97d85d22011-04-13 11:27:36 -0700298 final TabHost tabHost = findTabHostParent(container);
Michael Jurka8b805b12012-04-18 14:23:14 -0700299 final TabWidget tabs = tabHost.getTabWidget();
Adam Cohenae4f1552011-10-20 00:15:42 -0700300 final int iconIndex = itemContainer.indexOfChild(v);
301 final int itemCount = itemContainer.getChildCount();
302 final int pageIndex = ((PagedView) container).indexToPage(container.indexOfChild(parentLayout));
Winson Chung97d85d22011-04-13 11:27:36 -0700303 final int pageCount = container.getChildCount();
Adam Cohenae4f1552011-10-20 00:15:42 -0700304
305 final int x = iconIndex % countX;
306 final int y = iconIndex / countX;
Winson Chung97d85d22011-04-13 11:27:36 -0700307
308 final int action = e.getAction();
309 final boolean handleKeyEvent = (action != KeyEvent.ACTION_UP);
Adam Cohenae4f1552011-10-20 00:15:42 -0700310 ViewGroup newParent = null;
Winson Chung90576b52011-09-27 17:45:27 -0700311 // Side pages do not always load synchronously, so check before focusing child siblings
312 // willy-nilly
313 View child = null;
Winson Chung97d85d22011-04-13 11:27:36 -0700314 boolean wasHandled = false;
315 switch (keyCode) {
316 case KeyEvent.KEYCODE_DPAD_LEFT:
317 if (handleKeyEvent) {
318 // Select the previous icon or the last icon on the previous page
Winson Chung90576b52011-09-27 17:45:27 -0700319 if (iconIndex > 0) {
Adam Cohenae4f1552011-10-20 00:15:42 -0700320 itemContainer.getChildAt(iconIndex - 1).requestFocus();
Winson Chung97d85d22011-04-13 11:27:36 -0700321 } else {
322 if (pageIndex > 0) {
Adam Cohenae4f1552011-10-20 00:15:42 -0700323 newParent = getAppsCustomizePage(container, pageIndex - 1);
Winson Chung90576b52011-09-27 17:45:27 -0700324 if (newParent != null) {
Adam Cohenae4f1552011-10-20 00:15:42 -0700325 container.snapToPage(pageIndex - 1);
Winson Chung90576b52011-09-27 17:45:27 -0700326 child = newParent.getChildAt(newParent.getChildCount() - 1);
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_RIGHT:
335 if (handleKeyEvent) {
336 // Select the next icon or the first icon on the next page
Adam Cohenae4f1552011-10-20 00:15:42 -0700337 if (iconIndex < (itemCount - 1)) {
338 itemContainer.getChildAt(iconIndex + 1).requestFocus();
Winson Chung97d85d22011-04-13 11:27:36 -0700339 } else {
340 if (pageIndex < (pageCount - 1)) {
Adam Cohenae4f1552011-10-20 00:15:42 -0700341 newParent = getAppsCustomizePage(container, pageIndex + 1);
Winson Chung90576b52011-09-27 17:45:27 -0700342 if (newParent != null) {
Adam Cohenae4f1552011-10-20 00:15:42 -0700343 container.snapToPage(pageIndex + 1);
Winson Chung90576b52011-09-27 17:45:27 -0700344 child = newParent.getChildAt(0);
345 if (child != null) child.requestFocus();
346 }
Winson Chung97d85d22011-04-13 11:27:36 -0700347 }
348 }
349 }
350 wasHandled = true;
351 break;
352 case KeyEvent.KEYCODE_DPAD_UP:
353 if (handleKeyEvent) {
354 // Select the closest icon in the previous row, otherwise select the tab bar
355 if (y > 0) {
Adam Cohenae4f1552011-10-20 00:15:42 -0700356 int newiconIndex = ((y - 1) * countX) + x;
357 itemContainer.getChildAt(newiconIndex).requestFocus();
Winson Chung97d85d22011-04-13 11:27:36 -0700358 } else {
359 tabs.requestFocus();
360 }
361 }
362 wasHandled = true;
363 break;
364 case KeyEvent.KEYCODE_DPAD_DOWN:
365 if (handleKeyEvent) {
366 // Select the closest icon in the previous row, otherwise do nothing
Adam Cohenae4f1552011-10-20 00:15:42 -0700367 if (y < (countY - 1)) {
368 int newiconIndex = Math.min(itemCount - 1, ((y + 1) * countX) + x);
369 itemContainer.getChildAt(newiconIndex).requestFocus();
Winson Chung97d85d22011-04-13 11:27:36 -0700370 }
371 }
372 wasHandled = true;
373 break;
374 case KeyEvent.KEYCODE_ENTER:
375 case KeyEvent.KEYCODE_DPAD_CENTER:
376 if (handleKeyEvent) {
377 // Simulate a click on the icon
378 View.OnClickListener clickListener = (View.OnClickListener) container;
379 clickListener.onClick(v);
380 }
381 wasHandled = true;
382 break;
383 case KeyEvent.KEYCODE_PAGE_UP:
384 if (handleKeyEvent) {
385 // Select the first icon on the previous page, or the first icon on this page
386 // if there is no previous page
387 if (pageIndex > 0) {
Adam Cohenae4f1552011-10-20 00:15:42 -0700388 newParent = getAppsCustomizePage(container, pageIndex - 1);
Winson Chung90576b52011-09-27 17:45:27 -0700389 if (newParent != null) {
Adam Cohenae4f1552011-10-20 00:15:42 -0700390 container.snapToPage(pageIndex - 1);
Winson Chung90576b52011-09-27 17:45:27 -0700391 child = newParent.getChildAt(0);
392 if (child != null) child.requestFocus();
393 }
Winson Chung97d85d22011-04-13 11:27:36 -0700394 } else {
Adam Cohenae4f1552011-10-20 00:15:42 -0700395 itemContainer.getChildAt(0).requestFocus();
Winson Chung97d85d22011-04-13 11:27:36 -0700396 }
397 }
398 wasHandled = true;
399 break;
400 case KeyEvent.KEYCODE_PAGE_DOWN:
401 if (handleKeyEvent) {
402 // Select the first icon on the next page, or the last icon on this page
403 // if there is no next page
404 if (pageIndex < (pageCount - 1)) {
Adam Cohenae4f1552011-10-20 00:15:42 -0700405 newParent = getAppsCustomizePage(container, pageIndex + 1);
Winson Chung90576b52011-09-27 17:45:27 -0700406 if (newParent != null) {
Adam Cohenae4f1552011-10-20 00:15:42 -0700407 container.snapToPage(pageIndex + 1);
Winson Chung90576b52011-09-27 17:45:27 -0700408 child = newParent.getChildAt(0);
409 if (child != null) child.requestFocus();
410 }
Winson Chung97d85d22011-04-13 11:27:36 -0700411 } else {
Adam Cohenae4f1552011-10-20 00:15:42 -0700412 itemContainer.getChildAt(itemCount - 1).requestFocus();
Winson Chung97d85d22011-04-13 11:27:36 -0700413 }
414 }
415 wasHandled = true;
416 break;
417 case KeyEvent.KEYCODE_MOVE_HOME:
418 if (handleKeyEvent) {
419 // Select the first icon on this page
Adam Cohenae4f1552011-10-20 00:15:42 -0700420 itemContainer.getChildAt(0).requestFocus();
Winson Chung97d85d22011-04-13 11:27:36 -0700421 }
422 wasHandled = true;
423 break;
424 case KeyEvent.KEYCODE_MOVE_END:
425 if (handleKeyEvent) {
426 // Select the last icon on this page
Adam Cohenae4f1552011-10-20 00:15:42 -0700427 itemContainer.getChildAt(itemCount - 1).requestFocus();
Winson Chung97d85d22011-04-13 11:27:36 -0700428 }
429 wasHandled = true;
430 break;
431 default: break;
432 }
433 return wasHandled;
434 }
435
436 /**
437 * Handles key events in the tab widget.
438 */
439 static boolean handleTabKeyEvent(AccessibleTabView v, int keyCode, KeyEvent e) {
Michael Jurkaa2eb1702011-05-12 14:57:05 -0700440 if (!LauncherApplication.isScreenLarge()) return false;
Winson Chung97d85d22011-04-13 11:27:36 -0700441
442 final FocusOnlyTabWidget parent = (FocusOnlyTabWidget) v.getParent();
443 final TabHost tabHost = findTabHostParent(parent);
Michael Jurka8b805b12012-04-18 14:23:14 -0700444 final ViewGroup contents = tabHost.getTabContentView();
Winson Chung97d85d22011-04-13 11:27:36 -0700445 final int tabCount = parent.getTabCount();
446 final int tabIndex = parent.getChildTabIndex(v);
447
448 final int action = e.getAction();
449 final boolean handleKeyEvent = (action != KeyEvent.ACTION_UP);
450 boolean wasHandled = false;
451 switch (keyCode) {
452 case KeyEvent.KEYCODE_DPAD_LEFT:
453 if (handleKeyEvent) {
454 // Select the previous tab
455 if (tabIndex > 0) {
456 parent.getChildTabViewAt(tabIndex - 1).requestFocus();
457 }
458 }
459 wasHandled = true;
460 break;
461 case KeyEvent.KEYCODE_DPAD_RIGHT:
462 if (handleKeyEvent) {
463 // Select the next tab, or if the last tab has a focus right id, select that
464 if (tabIndex < (tabCount - 1)) {
465 parent.getChildTabViewAt(tabIndex + 1).requestFocus();
466 } else {
467 if (v.getNextFocusRightId() != View.NO_ID) {
468 tabHost.findViewById(v.getNextFocusRightId()).requestFocus();
469 }
470 }
471 }
472 wasHandled = true;
473 break;
474 case KeyEvent.KEYCODE_DPAD_UP:
475 // Do nothing
476 wasHandled = true;
477 break;
478 case KeyEvent.KEYCODE_DPAD_DOWN:
479 if (handleKeyEvent) {
480 // Select the content view
481 contents.requestFocus();
482 }
483 wasHandled = true;
484 break;
485 default: break;
486 }
487 return wasHandled;
488 }
489
490 /**
Winson Chung3d503fb2011-07-13 17:25:49 -0700491 * Handles key events in the workspace hotseat (bottom of the screen).
Winson Chung4e6a9762011-05-09 11:56:34 -0700492 */
Winson Chung3d503fb2011-07-13 17:25:49 -0700493 static boolean handleHotseatButtonKeyEvent(View v, int keyCode, KeyEvent e, int orientation) {
Winson Chung4e6a9762011-05-09 11:56:34 -0700494 final ViewGroup parent = (ViewGroup) v.getParent();
495 final ViewGroup launcher = (ViewGroup) parent.getParent();
496 final Workspace workspace = (Workspace) launcher.findViewById(R.id.workspace);
497 final int buttonIndex = parent.indexOfChild(v);
498 final int buttonCount = parent.getChildCount();
499 final int pageIndex = workspace.getCurrentPage();
Winson Chung4e6a9762011-05-09 11:56:34 -0700500
501 // NOTE: currently we don't special case for the phone UI in different
Winson Chung3d503fb2011-07-13 17:25:49 -0700502 // orientations, even though the hotseat is on the side in landscape mode. This
Winson Chung4e6a9762011-05-09 11:56:34 -0700503 // is to ensure that accessibility consistency is maintained across rotations.
504
505 final int action = e.getAction();
506 final boolean handleKeyEvent = (action != KeyEvent.ACTION_UP);
507 boolean wasHandled = false;
508 switch (keyCode) {
509 case KeyEvent.KEYCODE_DPAD_LEFT:
510 if (handleKeyEvent) {
Winson Chung74608b52011-06-23 13:23:20 -0700511 // Select the previous button, otherwise snap to the previous page
Winson Chung4e6a9762011-05-09 11:56:34 -0700512 if (buttonIndex > 0) {
513 parent.getChildAt(buttonIndex - 1).requestFocus();
514 } else {
Winson Chung74608b52011-06-23 13:23:20 -0700515 workspace.snapToPage(pageIndex - 1);
Winson Chung4e6a9762011-05-09 11:56:34 -0700516 }
517 }
518 wasHandled = true;
519 break;
520 case KeyEvent.KEYCODE_DPAD_RIGHT:
521 if (handleKeyEvent) {
Winson Chung74608b52011-06-23 13:23:20 -0700522 // Select the next button, otherwise snap to the next page
Winson Chung4e6a9762011-05-09 11:56:34 -0700523 if (buttonIndex < (buttonCount - 1)) {
524 parent.getChildAt(buttonIndex + 1).requestFocus();
525 } else {
Winson Chung74608b52011-06-23 13:23:20 -0700526 workspace.snapToPage(pageIndex + 1);
Winson Chung4e6a9762011-05-09 11:56:34 -0700527 }
528 }
529 wasHandled = true;
530 break;
531 case KeyEvent.KEYCODE_DPAD_UP:
532 if (handleKeyEvent) {
533 // Select the first bubble text view in the current page of the workspace
534 final CellLayout layout = (CellLayout) workspace.getChildAt(pageIndex);
Michael Jurkaa52570f2012-03-20 03:18:20 -0700535 final ShortcutAndWidgetContainer children = layout.getShortcutsAndWidgets();
Adam Cohenac56cff2011-09-28 20:45:37 -0700536 final View newIcon = getIconInDirection(layout, children, -1, 1);
Winson Chung4e6a9762011-05-09 11:56:34 -0700537 if (newIcon != null) {
538 newIcon.requestFocus();
539 } else {
540 workspace.requestFocus();
541 }
542 }
543 wasHandled = true;
544 break;
545 case KeyEvent.KEYCODE_DPAD_DOWN:
546 // Do nothing
547 wasHandled = true;
548 break;
549 default: break;
550 }
551 return wasHandled;
552 }
553
554 /**
Winson Chung97d85d22011-04-13 11:27:36 -0700555 * Private helper method to get the CellLayoutChildren given a CellLayout index.
556 */
Michael Jurkaa52570f2012-03-20 03:18:20 -0700557 private static ShortcutAndWidgetContainer getCellLayoutChildrenForIndex(
558 ViewGroup container, int i) {
Winson Chung97d85d22011-04-13 11:27:36 -0700559 ViewGroup parent = (ViewGroup) container.getChildAt(i);
Michael Jurkaa52570f2012-03-20 03:18:20 -0700560 return (ShortcutAndWidgetContainer) parent.getChildAt(0);
Winson Chung97d85d22011-04-13 11:27:36 -0700561 }
562
563 /**
564 * Private helper method to sort all the CellLayout children in order of their (x,y) spatially
565 * from top left to bottom right.
566 */
567 private static ArrayList<View> getCellLayoutChildrenSortedSpatially(CellLayout layout,
568 ViewGroup parent) {
569 // First we order each the CellLayout children by their x,y coordinates
570 final int cellCountX = layout.getCountX();
571 final int count = parent.getChildCount();
572 ArrayList<View> views = new ArrayList<View>();
573 for (int j = 0; j < count; ++j) {
574 views.add(parent.getChildAt(j));
575 }
576 Collections.sort(views, new Comparator<View>() {
577 @Override
578 public int compare(View lhs, View rhs) {
579 CellLayout.LayoutParams llp = (CellLayout.LayoutParams) lhs.getLayoutParams();
580 CellLayout.LayoutParams rlp = (CellLayout.LayoutParams) rhs.getLayoutParams();
581 int lvIndex = (llp.cellY * cellCountX) + llp.cellX;
582 int rvIndex = (rlp.cellY * cellCountX) + rlp.cellX;
583 return lvIndex - rvIndex;
584 }
585 });
586 return views;
587 }
588 /**
Adam Cohenac56cff2011-09-28 20:45:37 -0700589 * Private helper method to find the index of the next BubbleTextView or FolderIcon in the
590 * direction delta.
591 *
Winson Chung97d85d22011-04-13 11:27:36 -0700592 * @param delta either -1 or 1 depending on the direction we want to search
593 */
Adam Cohenac56cff2011-09-28 20:45:37 -0700594 private static View findIndexOfIcon(ArrayList<View> views, int i, int delta) {
Winson Chung97d85d22011-04-13 11:27:36 -0700595 // Then we find the next BubbleTextView offset by delta from i
596 final int count = views.size();
597 int newI = i + delta;
598 while (0 <= newI && newI < count) {
599 View newV = views.get(newI);
Adam Cohenac56cff2011-09-28 20:45:37 -0700600 if (newV instanceof BubbleTextView || newV instanceof FolderIcon) {
Winson Chung97d85d22011-04-13 11:27:36 -0700601 return newV;
602 }
603 newI += delta;
604 }
605 return null;
606 }
Adam Cohenac56cff2011-09-28 20:45:37 -0700607 private static View getIconInDirection(CellLayout layout, ViewGroup parent, int i,
Winson Chung97d85d22011-04-13 11:27:36 -0700608 int delta) {
609 final ArrayList<View> views = getCellLayoutChildrenSortedSpatially(layout, parent);
Adam Cohenac56cff2011-09-28 20:45:37 -0700610 return findIndexOfIcon(views, i, delta);
Winson Chung97d85d22011-04-13 11:27:36 -0700611 }
Adam Cohenac56cff2011-09-28 20:45:37 -0700612 private static View getIconInDirection(CellLayout layout, ViewGroup parent, View v,
Winson Chung97d85d22011-04-13 11:27:36 -0700613 int delta) {
614 final ArrayList<View> views = getCellLayoutChildrenSortedSpatially(layout, parent);
Adam Cohenac56cff2011-09-28 20:45:37 -0700615 return findIndexOfIcon(views, views.indexOf(v), delta);
Winson Chung97d85d22011-04-13 11:27:36 -0700616 }
617 /**
Adam Cohenac56cff2011-09-28 20:45:37 -0700618 * Private helper method to find the next closest BubbleTextView or FolderIcon in the direction
619 * delta on the next line.
620 *
Winson Chung97d85d22011-04-13 11:27:36 -0700621 * @param delta either -1 or 1 depending on the line and direction we want to search
622 */
Adam Cohenac56cff2011-09-28 20:45:37 -0700623 private static View getClosestIconOnLine(CellLayout layout, ViewGroup parent, View v,
Winson Chung97d85d22011-04-13 11:27:36 -0700624 int lineDelta) {
625 final ArrayList<View> views = getCellLayoutChildrenSortedSpatially(layout, parent);
626 final CellLayout.LayoutParams lp = (CellLayout.LayoutParams) v.getLayoutParams();
Winson Chung97d85d22011-04-13 11:27:36 -0700627 final int cellCountY = layout.getCountY();
628 final int row = lp.cellY;
629 final int newRow = row + lineDelta;
630 if (0 <= newRow && newRow < cellCountY) {
631 float closestDistance = Float.MAX_VALUE;
632 int closestIndex = -1;
633 int index = views.indexOf(v);
634 int endIndex = (lineDelta < 0) ? -1 : views.size();
635 while (index != endIndex) {
636 View newV = views.get(index);
637 CellLayout.LayoutParams tmpLp = (CellLayout.LayoutParams) newV.getLayoutParams();
638 boolean satisfiesRow = (lineDelta < 0) ? (tmpLp.cellY < row) : (tmpLp.cellY > row);
Adam Cohenac56cff2011-09-28 20:45:37 -0700639 if (satisfiesRow &&
640 (newV instanceof BubbleTextView || newV instanceof FolderIcon)) {
Winson Chung97d85d22011-04-13 11:27:36 -0700641 float tmpDistance = (float) Math.sqrt(Math.pow(tmpLp.cellX - lp.cellX, 2) +
642 Math.pow(tmpLp.cellY - lp.cellY, 2));
643 if (tmpDistance < closestDistance) {
644 closestIndex = index;
645 closestDistance = tmpDistance;
646 }
647 }
648 if (index <= endIndex) {
649 ++index;
650 } else {
651 --index;
652 }
653 }
654 if (closestIndex > -1) {
655 return views.get(closestIndex);
656 }
657 }
658 return null;
659 }
660
661 /**
Adam Cohenac56cff2011-09-28 20:45:37 -0700662 * Handles key events in a Workspace containing.
Winson Chung97d85d22011-04-13 11:27:36 -0700663 */
Adam Cohenac56cff2011-09-28 20:45:37 -0700664 static boolean handleIconKeyEvent(View v, int keyCode, KeyEvent e) {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700665 ShortcutAndWidgetContainer parent = (ShortcutAndWidgetContainer) v.getParent();
Winson Chung97d85d22011-04-13 11:27:36 -0700666 final CellLayout layout = (CellLayout) parent.getParent();
667 final Workspace workspace = (Workspace) layout.getParent();
668 final ViewGroup launcher = (ViewGroup) workspace.getParent();
Winson Chungfaa13252011-06-13 18:15:54 -0700669 final ViewGroup tabs = (ViewGroup) launcher.findViewById(R.id.qsb_bar);
Winson Chung4d279d92011-07-21 11:46:32 -0700670 final ViewGroup hotseat = (ViewGroup) launcher.findViewById(R.id.hotseat);
Winson Chung97d85d22011-04-13 11:27:36 -0700671 int pageIndex = workspace.indexOfChild(layout);
672 int pageCount = workspace.getChildCount();
673
674 final int action = e.getAction();
675 final boolean handleKeyEvent = (action != KeyEvent.ACTION_UP);
676 boolean wasHandled = false;
677 switch (keyCode) {
678 case KeyEvent.KEYCODE_DPAD_LEFT:
679 if (handleKeyEvent) {
680 // Select the previous icon or the last icon on the previous page if possible
Adam Cohenac56cff2011-09-28 20:45:37 -0700681 View newIcon = getIconInDirection(layout, parent, v, -1);
Winson Chung97d85d22011-04-13 11:27:36 -0700682 if (newIcon != null) {
683 newIcon.requestFocus();
684 } else {
685 if (pageIndex > 0) {
686 parent = getCellLayoutChildrenForIndex(workspace, pageIndex - 1);
Adam Cohenac56cff2011-09-28 20:45:37 -0700687 newIcon = getIconInDirection(layout, parent,
Winson Chung97d85d22011-04-13 11:27:36 -0700688 parent.getChildCount(), -1);
689 if (newIcon != null) {
690 newIcon.requestFocus();
691 } else {
692 // Snap to the previous page
693 workspace.snapToPage(pageIndex - 1);
694 }
695 }
696 }
697 }
698 wasHandled = true;
699 break;
700 case KeyEvent.KEYCODE_DPAD_RIGHT:
701 if (handleKeyEvent) {
702 // Select the next icon or the first icon on the next page if possible
Adam Cohenac56cff2011-09-28 20:45:37 -0700703 View newIcon = getIconInDirection(layout, parent, v, 1);
Winson Chung97d85d22011-04-13 11:27:36 -0700704 if (newIcon != null) {
705 newIcon.requestFocus();
706 } else {
707 if (pageIndex < (pageCount - 1)) {
708 parent = getCellLayoutChildrenForIndex(workspace, pageIndex + 1);
Adam Cohenac56cff2011-09-28 20:45:37 -0700709 newIcon = getIconInDirection(layout, parent, -1, 1);
Winson Chung97d85d22011-04-13 11:27:36 -0700710 if (newIcon != null) {
711 newIcon.requestFocus();
712 } else {
713 // Snap to the next page
714 workspace.snapToPage(pageIndex + 1);
715 }
716 }
717 }
718 }
719 wasHandled = true;
720 break;
721 case KeyEvent.KEYCODE_DPAD_UP:
722 if (handleKeyEvent) {
723 // Select the closest icon in the previous line, otherwise select the tab bar
Adam Cohenac56cff2011-09-28 20:45:37 -0700724 View newIcon = getClosestIconOnLine(layout, parent, v, -1);
Winson Chung97d85d22011-04-13 11:27:36 -0700725 if (newIcon != null) {
726 newIcon.requestFocus();
727 wasHandled = true;
728 } else {
729 tabs.requestFocus();
730 }
731 }
732 break;
733 case KeyEvent.KEYCODE_DPAD_DOWN:
734 if (handleKeyEvent) {
Winson Chung4d279d92011-07-21 11:46:32 -0700735 // Select the closest icon in the next line, otherwise select the button bar
Adam Cohenac56cff2011-09-28 20:45:37 -0700736 View newIcon = getClosestIconOnLine(layout, parent, v, 1);
Winson Chung97d85d22011-04-13 11:27:36 -0700737 if (newIcon != null) {
738 newIcon.requestFocus();
739 wasHandled = true;
Winson Chung4d279d92011-07-21 11:46:32 -0700740 } else if (hotseat != null) {
741 hotseat.requestFocus();
Winson Chung97d85d22011-04-13 11:27:36 -0700742 }
743 }
744 break;
745 case KeyEvent.KEYCODE_PAGE_UP:
746 if (handleKeyEvent) {
747 // Select the first icon on the previous page or the first icon on this page
748 // if there is no previous page
749 if (pageIndex > 0) {
750 parent = getCellLayoutChildrenForIndex(workspace, pageIndex - 1);
Adam Cohenac56cff2011-09-28 20:45:37 -0700751 View newIcon = getIconInDirection(layout, parent, -1, 1);
Winson Chung97d85d22011-04-13 11:27:36 -0700752 if (newIcon != null) {
753 newIcon.requestFocus();
754 } else {
755 // Snap to the previous page
756 workspace.snapToPage(pageIndex - 1);
757 }
758 } else {
Adam Cohenac56cff2011-09-28 20:45:37 -0700759 View newIcon = getIconInDirection(layout, parent, -1, 1);
Winson Chung97d85d22011-04-13 11:27:36 -0700760 if (newIcon != null) {
761 newIcon.requestFocus();
762 }
763 }
764 }
765 wasHandled = true;
766 break;
767 case KeyEvent.KEYCODE_PAGE_DOWN:
768 if (handleKeyEvent) {
769 // Select the first icon on the next page or the last icon on this page
770 // if there is no previous page
771 if (pageIndex < (pageCount - 1)) {
772 parent = getCellLayoutChildrenForIndex(workspace, pageIndex + 1);
Adam Cohenac56cff2011-09-28 20:45:37 -0700773 View newIcon = getIconInDirection(layout, parent, -1, 1);
Winson Chung97d85d22011-04-13 11:27:36 -0700774 if (newIcon != null) {
775 newIcon.requestFocus();
776 } else {
777 // Snap to the next page
778 workspace.snapToPage(pageIndex + 1);
779 }
780 } else {
Adam Cohenac56cff2011-09-28 20:45:37 -0700781 View newIcon = getIconInDirection(layout, parent,
Winson Chung97d85d22011-04-13 11:27:36 -0700782 parent.getChildCount(), -1);
783 if (newIcon != null) {
784 newIcon.requestFocus();
785 }
786 }
787 }
788 wasHandled = true;
789 break;
790 case KeyEvent.KEYCODE_MOVE_HOME:
791 if (handleKeyEvent) {
792 // Select the first icon on this page
Adam Cohenac56cff2011-09-28 20:45:37 -0700793 View newIcon = getIconInDirection(layout, parent, -1, 1);
Winson Chung97d85d22011-04-13 11:27:36 -0700794 if (newIcon != null) {
795 newIcon.requestFocus();
796 }
797 }
798 wasHandled = true;
799 break;
800 case KeyEvent.KEYCODE_MOVE_END:
801 if (handleKeyEvent) {
802 // Select the last icon on this page
Adam Cohenac56cff2011-09-28 20:45:37 -0700803 View newIcon = getIconInDirection(layout, parent,
804 parent.getChildCount(), -1);
805 if (newIcon != null) {
806 newIcon.requestFocus();
807 }
808 }
809 wasHandled = true;
810 break;
811 default: break;
812 }
813 return wasHandled;
814 }
815
816 /**
817 * Handles key events for items in a Folder.
818 */
819 static boolean handleFolderKeyEvent(View v, int keyCode, KeyEvent e) {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700820 ShortcutAndWidgetContainer parent = (ShortcutAndWidgetContainer) v.getParent();
Adam Cohenac56cff2011-09-28 20:45:37 -0700821 final CellLayout layout = (CellLayout) parent.getParent();
822 final Folder folder = (Folder) layout.getParent();
823 View title = folder.mFolderName;
824
825 final int action = e.getAction();
826 final boolean handleKeyEvent = (action != KeyEvent.ACTION_UP);
827 boolean wasHandled = false;
828 switch (keyCode) {
829 case KeyEvent.KEYCODE_DPAD_LEFT:
830 if (handleKeyEvent) {
831 // Select the previous icon
832 View newIcon = getIconInDirection(layout, parent, v, -1);
833 if (newIcon != null) {
834 newIcon.requestFocus();
835 }
836 }
837 wasHandled = true;
838 break;
839 case KeyEvent.KEYCODE_DPAD_RIGHT:
840 if (handleKeyEvent) {
841 // Select the next icon
842 View newIcon = getIconInDirection(layout, parent, v, 1);
843 if (newIcon != null) {
844 newIcon.requestFocus();
845 } else {
846 title.requestFocus();
847 }
848 }
849 wasHandled = true;
850 break;
851 case KeyEvent.KEYCODE_DPAD_UP:
852 if (handleKeyEvent) {
853 // Select the closest icon in the previous line
854 View newIcon = getClosestIconOnLine(layout, parent, v, -1);
855 if (newIcon != null) {
856 newIcon.requestFocus();
857 }
858 }
859 wasHandled = true;
860 break;
861 case KeyEvent.KEYCODE_DPAD_DOWN:
862 if (handleKeyEvent) {
863 // Select the closest icon in the next line
864 View newIcon = getClosestIconOnLine(layout, parent, v, 1);
865 if (newIcon != null) {
866 newIcon.requestFocus();
867 } else {
868 title.requestFocus();
869 }
870 }
871 wasHandled = true;
872 break;
873 case KeyEvent.KEYCODE_MOVE_HOME:
874 if (handleKeyEvent) {
875 // Select the first icon on this page
876 View newIcon = getIconInDirection(layout, parent, -1, 1);
877 if (newIcon != null) {
878 newIcon.requestFocus();
879 }
880 }
881 wasHandled = true;
882 break;
883 case KeyEvent.KEYCODE_MOVE_END:
884 if (handleKeyEvent) {
885 // Select the last icon on this page
886 View newIcon = getIconInDirection(layout, parent,
Winson Chung97d85d22011-04-13 11:27:36 -0700887 parent.getChildCount(), -1);
888 if (newIcon != null) {
889 newIcon.requestFocus();
890 }
891 }
892 wasHandled = true;
893 break;
894 default: break;
895 }
896 return wasHandled;
897 }
898}