blob: 34e752b85c22fb012d330a3e0e6d191f5727eb09 [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;
Winson Chunga1f133d2013-07-25 11:14:30 -070024import android.widget.ScrollView;
Winson Chung97d85d22011-04-13 11:27:36 -070025
Winson Chungfaa13252011-06-13 18:15:54 -070026import java.util.ArrayList;
27import java.util.Collections;
28import java.util.Comparator;
29
Winson Chung97d85d22011-04-13 11:27:36 -070030/**
Winson Chung4d279d92011-07-21 11:46:32 -070031 * A keyboard listener we set on all the workspace icons.
32 */
Adam Cohenac56cff2011-09-28 20:45:37 -070033class IconKeyEventListener implements View.OnKeyListener {
Winson Chung4d279d92011-07-21 11:46:32 -070034 public boolean onKey(View v, int keyCode, KeyEvent event) {
Adam Cohenac56cff2011-09-28 20:45:37 -070035 return FocusHelper.handleIconKeyEvent(v, keyCode, event);
36 }
37}
38
39/**
40 * A keyboard listener we set on all the workspace icons.
41 */
42class FolderKeyEventListener implements View.OnKeyListener {
43 public boolean onKey(View v, int keyCode, KeyEvent event) {
44 return FocusHelper.handleFolderKeyEvent(v, keyCode, event);
Winson Chung4d279d92011-07-21 11:46:32 -070045 }
46}
47
48/**
Winson Chung3d503fb2011-07-13 17:25:49 -070049 * A keyboard listener we set on all the hotseat buttons.
Winson Chung4e6a9762011-05-09 11:56:34 -070050 */
Adam Cohenac56cff2011-09-28 20:45:37 -070051class HotseatIconKeyEventListener implements View.OnKeyListener {
Winson Chung4e6a9762011-05-09 11:56:34 -070052 public boolean onKey(View v, int keyCode, KeyEvent event) {
53 final Configuration configuration = v.getResources().getConfiguration();
Winson Chung3d503fb2011-07-13 17:25:49 -070054 return FocusHelper.handleHotseatButtonKeyEvent(v, keyCode, event, configuration.orientation);
Winson Chung4e6a9762011-05-09 11:56:34 -070055 }
56}
57
Winson Chung97d85d22011-04-13 11:27:36 -070058public class FocusHelper {
59 /**
60 * Private helper to get the parent TabHost in the view hiearchy.
61 */
Adam Cohenc956cba2014-07-24 09:17:37 -070062 private static AppsCustomizeTabHost findTabHostParent(View v) {
Winson Chung97d85d22011-04-13 11:27:36 -070063 ViewParent p = v.getParent();
Adam Cohenc956cba2014-07-24 09:17:37 -070064 while (p != null && !(p instanceof AppsCustomizeTabHost)) {
Winson Chung97d85d22011-04-13 11:27:36 -070065 p = p.getParent();
66 }
Adam Cohenc956cba2014-07-24 09:17:37 -070067 return (AppsCustomizeTabHost) p;
Winson Chung97d85d22011-04-13 11:27:36 -070068 }
69
70 /**
Adam Cohenae4f1552011-10-20 00:15:42 -070071 * Returns the Viewgroup containing page contents for the page at the index specified.
72 */
73 private static ViewGroup getAppsCustomizePage(ViewGroup container, int index) {
74 ViewGroup page = (ViewGroup) ((PagedView) container).getPageAt(index);
Winson Chungc58497e2013-09-03 17:48:37 -070075 if (page instanceof CellLayout) {
Adam Cohenae4f1552011-10-20 00:15:42 -070076 // There are two layers, a PagedViewCellLayout and PagedViewCellLayoutChildren
Winson Chungc58497e2013-09-03 17:48:37 -070077 page = ((CellLayout) page).getShortcutsAndWidgets();
Adam Cohenae4f1552011-10-20 00:15:42 -070078 }
79 return page;
80 }
81
82 /**
Winson Chung97d85d22011-04-13 11:27:36 -070083 * Handles key events in a PageViewExtendedLayout containing PagedViewWidgets.
Winson Chung4e6a9762011-05-09 11:56:34 -070084 */
85 static boolean handlePagedViewGridLayoutWidgetKeyEvent(PagedViewWidget w, int keyCode,
86 KeyEvent e) {
87
88 final PagedViewGridLayout parent = (PagedViewGridLayout) w.getParent();
Adam Cohenae4f1552011-10-20 00:15:42 -070089 final PagedView container = (PagedView) parent.getParent();
Winson Chung4e6a9762011-05-09 11:56:34 -070090 final int widgetIndex = parent.indexOfChild(w);
91 final int widgetCount = parent.getChildCount();
Adam Cohenae4f1552011-10-20 00:15:42 -070092 final int pageIndex = ((PagedView) container).indexToPage(container.indexOfChild(parent));
Winson Chung4e6a9762011-05-09 11:56:34 -070093 final int pageCount = container.getChildCount();
94 final int cellCountX = parent.getCellCountX();
95 final int cellCountY = parent.getCellCountY();
96 final int x = widgetIndex % cellCountX;
97 final int y = widgetIndex / cellCountX;
98
99 final int action = e.getAction();
100 final boolean handleKeyEvent = (action != KeyEvent.ACTION_UP);
Adam Cohenae4f1552011-10-20 00:15:42 -0700101 ViewGroup newParent = null;
Winson Chung3b0b46a2011-07-28 10:45:09 -0700102 // Now that we load items in the bg asynchronously, we can't just focus
103 // child siblings willy-nilly
104 View child = null;
Winson Chung4e6a9762011-05-09 11:56:34 -0700105 boolean wasHandled = false;
106 switch (keyCode) {
107 case KeyEvent.KEYCODE_DPAD_LEFT:
108 if (handleKeyEvent) {
109 // Select the previous widget or the last widget on the previous page
110 if (widgetIndex > 0) {
111 parent.getChildAt(widgetIndex - 1).requestFocus();
112 } else {
113 if (pageIndex > 0) {
Adam Cohenae4f1552011-10-20 00:15:42 -0700114 newParent = getAppsCustomizePage(container, pageIndex - 1);
115 if (newParent != null) {
116 child = newParent.getChildAt(newParent.getChildCount() - 1);
117 if (child != null) child.requestFocus();
118 }
Winson Chung4e6a9762011-05-09 11:56:34 -0700119 }
120 }
121 }
122 wasHandled = true;
123 break;
124 case KeyEvent.KEYCODE_DPAD_RIGHT:
125 if (handleKeyEvent) {
126 // Select the next widget or the first widget on the next page
127 if (widgetIndex < (widgetCount - 1)) {
128 parent.getChildAt(widgetIndex + 1).requestFocus();
129 } else {
130 if (pageIndex < (pageCount - 1)) {
Adam Cohenae4f1552011-10-20 00:15:42 -0700131 newParent = getAppsCustomizePage(container, pageIndex + 1);
132 if (newParent != null) {
133 child = newParent.getChildAt(0);
134 if (child != null) child.requestFocus();
135 }
Winson Chung4e6a9762011-05-09 11:56:34 -0700136 }
137 }
138 }
139 wasHandled = true;
140 break;
141 case KeyEvent.KEYCODE_DPAD_UP:
142 if (handleKeyEvent) {
143 // Select the closest icon in the previous row, otherwise select the tab bar
144 if (y > 0) {
145 int newWidgetIndex = ((y - 1) * cellCountX) + x;
Winson Chung3b0b46a2011-07-28 10:45:09 -0700146 child = parent.getChildAt(newWidgetIndex);
147 if (child != null) child.requestFocus();
Winson Chung4e6a9762011-05-09 11:56:34 -0700148 }
149 }
150 wasHandled = true;
151 break;
152 case KeyEvent.KEYCODE_DPAD_DOWN:
153 if (handleKeyEvent) {
154 // Select the closest icon in the previous row, otherwise do nothing
155 if (y < (cellCountY - 1)) {
156 int newWidgetIndex = Math.min(widgetCount - 1, ((y + 1) * cellCountX) + x);
Winson Chung3b0b46a2011-07-28 10:45:09 -0700157 child = parent.getChildAt(newWidgetIndex);
158 if (child != null) child.requestFocus();
Winson Chung4e6a9762011-05-09 11:56:34 -0700159 }
160 }
161 wasHandled = true;
162 break;
163 case KeyEvent.KEYCODE_ENTER:
164 case KeyEvent.KEYCODE_DPAD_CENTER:
165 if (handleKeyEvent) {
166 // Simulate a click on the widget
167 View.OnClickListener clickListener = (View.OnClickListener) container;
168 clickListener.onClick(w);
169 }
170 wasHandled = true;
171 break;
172 case KeyEvent.KEYCODE_PAGE_UP:
173 if (handleKeyEvent) {
174 // Select the first item on the previous page, or the first item on this page
175 // if there is no previous page
176 if (pageIndex > 0) {
Adam Cohenae4f1552011-10-20 00:15:42 -0700177 newParent = getAppsCustomizePage(container, pageIndex - 1);
178 if (newParent != null) {
179 child = newParent.getChildAt(0);
180 }
Winson Chung4e6a9762011-05-09 11:56:34 -0700181 } else {
Winson Chung3b0b46a2011-07-28 10:45:09 -0700182 child = parent.getChildAt(0);
Winson Chung4e6a9762011-05-09 11:56:34 -0700183 }
Winson Chung3b0b46a2011-07-28 10:45:09 -0700184 if (child != null) child.requestFocus();
Winson Chung4e6a9762011-05-09 11:56:34 -0700185 }
186 wasHandled = true;
187 break;
188 case KeyEvent.KEYCODE_PAGE_DOWN:
189 if (handleKeyEvent) {
190 // Select the first item on the next page, or the last item on this page
191 // if there is no next page
192 if (pageIndex < (pageCount - 1)) {
Adam Cohenae4f1552011-10-20 00:15:42 -0700193 newParent = getAppsCustomizePage(container, pageIndex + 1);
194 if (newParent != null) {
195 child = newParent.getChildAt(0);
196 }
Winson Chung4e6a9762011-05-09 11:56:34 -0700197 } else {
Winson Chung3b0b46a2011-07-28 10:45:09 -0700198 child = parent.getChildAt(widgetCount - 1);
Winson Chung4e6a9762011-05-09 11:56:34 -0700199 }
Winson Chung3b0b46a2011-07-28 10:45:09 -0700200 if (child != null) child.requestFocus();
Winson Chung4e6a9762011-05-09 11:56:34 -0700201 }
202 wasHandled = true;
203 break;
204 case KeyEvent.KEYCODE_MOVE_HOME:
205 if (handleKeyEvent) {
206 // Select the first item on this page
Winson Chung3b0b46a2011-07-28 10:45:09 -0700207 child = parent.getChildAt(0);
208 if (child != null) child.requestFocus();
Winson Chung4e6a9762011-05-09 11:56:34 -0700209 }
210 wasHandled = true;
211 break;
212 case KeyEvent.KEYCODE_MOVE_END:
213 if (handleKeyEvent) {
214 // Select the last item on this page
215 parent.getChildAt(widgetCount - 1).requestFocus();
216 }
217 wasHandled = true;
218 break;
219 default: break;
220 }
221 return wasHandled;
222 }
223
224 /**
Winson Chung97d85d22011-04-13 11:27:36 -0700225 * Handles key events in a PageViewCellLayout containing PagedViewIcons.
226 */
Adam Cohenae4f1552011-10-20 00:15:42 -0700227 static boolean handleAppsCustomizeKeyEvent(View v, int keyCode, KeyEvent e) {
Sunny Goyal508da152014-08-14 10:53:27 -0700228 final int action = e.getAction();
229 if (((action == KeyEvent.ACTION_DOWN) && v.onKeyDown(keyCode, e))
230 || ((action == KeyEvent.ACTION_UP) && v.onKeyUp(keyCode, e))) {
231 // Let the view handle the confirmation key.
232 return true;
233 }
234
Adam Cohenae4f1552011-10-20 00:15:42 -0700235 ViewGroup parentLayout;
236 ViewGroup itemContainer;
237 int countX;
238 int countY;
Winson Chungc58497e2013-09-03 17:48:37 -0700239 if (v.getParent() instanceof ShortcutAndWidgetContainer) {
Adam Cohenae4f1552011-10-20 00:15:42 -0700240 itemContainer = (ViewGroup) v.getParent();
241 parentLayout = (ViewGroup) itemContainer.getParent();
Winson Chungc58497e2013-09-03 17:48:37 -0700242 countX = ((CellLayout) parentLayout).getCountX();
243 countY = ((CellLayout) parentLayout).getCountY();
Adam Cohenae4f1552011-10-20 00:15:42 -0700244 } else {
245 itemContainer = parentLayout = (ViewGroup) v.getParent();
246 countX = ((PagedViewGridLayout) parentLayout).getCellCountX();
247 countY = ((PagedViewGridLayout) parentLayout).getCellCountY();
248 }
249
Winson Chung97d85d22011-04-13 11:27:36 -0700250 // Note we have an extra parent because of the
251 // PagedViewCellLayout/PagedViewCellLayoutChildren relationship
Adam Cohenae4f1552011-10-20 00:15:42 -0700252 final PagedView container = (PagedView) parentLayout.getParent();
Adam Cohenae4f1552011-10-20 00:15:42 -0700253 final int iconIndex = itemContainer.indexOfChild(v);
254 final int itemCount = itemContainer.getChildCount();
255 final int pageIndex = ((PagedView) container).indexToPage(container.indexOfChild(parentLayout));
Winson Chung97d85d22011-04-13 11:27:36 -0700256 final int pageCount = container.getChildCount();
Adam Cohenae4f1552011-10-20 00:15:42 -0700257
258 final int x = iconIndex % countX;
259 final int y = iconIndex / countX;
Winson Chung97d85d22011-04-13 11:27:36 -0700260
Winson Chung97d85d22011-04-13 11:27:36 -0700261 final boolean handleKeyEvent = (action != KeyEvent.ACTION_UP);
Adam Cohenae4f1552011-10-20 00:15:42 -0700262 ViewGroup newParent = null;
Winson Chung90576b52011-09-27 17:45:27 -0700263 // Side pages do not always load synchronously, so check before focusing child siblings
264 // willy-nilly
265 View child = null;
Winson Chung97d85d22011-04-13 11:27:36 -0700266 boolean wasHandled = false;
267 switch (keyCode) {
268 case KeyEvent.KEYCODE_DPAD_LEFT:
269 if (handleKeyEvent) {
270 // Select the previous icon or the last icon on the previous page
Winson Chung90576b52011-09-27 17:45:27 -0700271 if (iconIndex > 0) {
Adam Cohenae4f1552011-10-20 00:15:42 -0700272 itemContainer.getChildAt(iconIndex - 1).requestFocus();
Winson Chung97d85d22011-04-13 11:27:36 -0700273 } else {
274 if (pageIndex > 0) {
Adam Cohenae4f1552011-10-20 00:15:42 -0700275 newParent = getAppsCustomizePage(container, pageIndex - 1);
Winson Chung90576b52011-09-27 17:45:27 -0700276 if (newParent != null) {
Adam Cohenae4f1552011-10-20 00:15:42 -0700277 container.snapToPage(pageIndex - 1);
Winson Chung90576b52011-09-27 17:45:27 -0700278 child = newParent.getChildAt(newParent.getChildCount() - 1);
279 if (child != null) child.requestFocus();
280 }
Winson Chung97d85d22011-04-13 11:27:36 -0700281 }
282 }
283 }
284 wasHandled = true;
285 break;
286 case KeyEvent.KEYCODE_DPAD_RIGHT:
287 if (handleKeyEvent) {
288 // Select the next icon or the first icon on the next page
Adam Cohenae4f1552011-10-20 00:15:42 -0700289 if (iconIndex < (itemCount - 1)) {
290 itemContainer.getChildAt(iconIndex + 1).requestFocus();
Winson Chung97d85d22011-04-13 11:27:36 -0700291 } else {
292 if (pageIndex < (pageCount - 1)) {
Adam Cohenae4f1552011-10-20 00:15:42 -0700293 newParent = getAppsCustomizePage(container, pageIndex + 1);
Winson Chung90576b52011-09-27 17:45:27 -0700294 if (newParent != null) {
Adam Cohenae4f1552011-10-20 00:15:42 -0700295 container.snapToPage(pageIndex + 1);
Winson Chung90576b52011-09-27 17:45:27 -0700296 child = newParent.getChildAt(0);
297 if (child != null) child.requestFocus();
298 }
Winson Chung97d85d22011-04-13 11:27:36 -0700299 }
300 }
301 }
302 wasHandled = true;
303 break;
304 case KeyEvent.KEYCODE_DPAD_UP:
305 if (handleKeyEvent) {
306 // Select the closest icon in the previous row, otherwise select the tab bar
307 if (y > 0) {
Adam Cohenae4f1552011-10-20 00:15:42 -0700308 int newiconIndex = ((y - 1) * countX) + x;
309 itemContainer.getChildAt(newiconIndex).requestFocus();
Winson Chung97d85d22011-04-13 11:27:36 -0700310 }
311 }
312 wasHandled = true;
313 break;
314 case KeyEvent.KEYCODE_DPAD_DOWN:
315 if (handleKeyEvent) {
316 // Select the closest icon in the previous row, otherwise do nothing
Adam Cohenae4f1552011-10-20 00:15:42 -0700317 if (y < (countY - 1)) {
318 int newiconIndex = Math.min(itemCount - 1, ((y + 1) * countX) + x);
319 itemContainer.getChildAt(newiconIndex).requestFocus();
Winson Chung97d85d22011-04-13 11:27:36 -0700320 }
321 }
322 wasHandled = true;
323 break;
Winson Chung97d85d22011-04-13 11:27:36 -0700324 case KeyEvent.KEYCODE_PAGE_UP:
325 if (handleKeyEvent) {
326 // Select the first icon on the previous page, or the first icon on this page
327 // if there is no previous page
328 if (pageIndex > 0) {
Adam Cohenae4f1552011-10-20 00:15:42 -0700329 newParent = getAppsCustomizePage(container, pageIndex - 1);
Winson Chung90576b52011-09-27 17:45:27 -0700330 if (newParent != null) {
Adam Cohenae4f1552011-10-20 00:15:42 -0700331 container.snapToPage(pageIndex - 1);
Winson Chung90576b52011-09-27 17:45:27 -0700332 child = newParent.getChildAt(0);
333 if (child != null) child.requestFocus();
334 }
Winson Chung97d85d22011-04-13 11:27:36 -0700335 } else {
Adam Cohenae4f1552011-10-20 00:15:42 -0700336 itemContainer.getChildAt(0).requestFocus();
Winson Chung97d85d22011-04-13 11:27:36 -0700337 }
338 }
339 wasHandled = true;
340 break;
341 case KeyEvent.KEYCODE_PAGE_DOWN:
342 if (handleKeyEvent) {
343 // Select the first icon on the next page, or the last icon on this page
344 // if there is no next page
345 if (pageIndex < (pageCount - 1)) {
Adam Cohenae4f1552011-10-20 00:15:42 -0700346 newParent = getAppsCustomizePage(container, pageIndex + 1);
Winson Chung90576b52011-09-27 17:45:27 -0700347 if (newParent != null) {
Adam Cohenae4f1552011-10-20 00:15:42 -0700348 container.snapToPage(pageIndex + 1);
Winson Chung90576b52011-09-27 17:45:27 -0700349 child = newParent.getChildAt(0);
350 if (child != null) child.requestFocus();
351 }
Winson Chung97d85d22011-04-13 11:27:36 -0700352 } else {
Adam Cohenae4f1552011-10-20 00:15:42 -0700353 itemContainer.getChildAt(itemCount - 1).requestFocus();
Winson Chung97d85d22011-04-13 11:27:36 -0700354 }
355 }
356 wasHandled = true;
357 break;
358 case KeyEvent.KEYCODE_MOVE_HOME:
359 if (handleKeyEvent) {
360 // Select the first icon on this page
Adam Cohenae4f1552011-10-20 00:15:42 -0700361 itemContainer.getChildAt(0).requestFocus();
Winson Chung97d85d22011-04-13 11:27:36 -0700362 }
363 wasHandled = true;
364 break;
365 case KeyEvent.KEYCODE_MOVE_END:
366 if (handleKeyEvent) {
367 // Select the last icon on this page
Adam Cohenae4f1552011-10-20 00:15:42 -0700368 itemContainer.getChildAt(itemCount - 1).requestFocus();
Winson Chung97d85d22011-04-13 11:27:36 -0700369 }
370 wasHandled = true;
371 break;
372 default: break;
373 }
374 return wasHandled;
375 }
376
377 /**
378 * Handles key events in the tab widget.
379 */
380 static boolean handleTabKeyEvent(AccessibleTabView v, int keyCode, KeyEvent e) {
Daniel Sandlere4f98912013-06-25 15:13:26 -0400381 if (!LauncherAppState.getInstance().isScreenLarge()) return false;
Winson Chung97d85d22011-04-13 11:27:36 -0700382
383 final FocusOnlyTabWidget parent = (FocusOnlyTabWidget) v.getParent();
Adam Cohenc956cba2014-07-24 09:17:37 -0700384 final AppsCustomizeTabHost tabHost = findTabHostParent(parent);
385 final ViewGroup contents = tabHost.getContent();
Winson Chung97d85d22011-04-13 11:27:36 -0700386 final int tabCount = parent.getTabCount();
387 final int tabIndex = parent.getChildTabIndex(v);
388
389 final int action = e.getAction();
390 final boolean handleKeyEvent = (action != KeyEvent.ACTION_UP);
391 boolean wasHandled = false;
392 switch (keyCode) {
393 case KeyEvent.KEYCODE_DPAD_LEFT:
394 if (handleKeyEvent) {
395 // Select the previous tab
396 if (tabIndex > 0) {
397 parent.getChildTabViewAt(tabIndex - 1).requestFocus();
398 }
399 }
400 wasHandled = true;
401 break;
402 case KeyEvent.KEYCODE_DPAD_RIGHT:
403 if (handleKeyEvent) {
404 // Select the next tab, or if the last tab has a focus right id, select that
405 if (tabIndex < (tabCount - 1)) {
406 parent.getChildTabViewAt(tabIndex + 1).requestFocus();
407 } else {
408 if (v.getNextFocusRightId() != View.NO_ID) {
409 tabHost.findViewById(v.getNextFocusRightId()).requestFocus();
410 }
411 }
412 }
413 wasHandled = true;
414 break;
415 case KeyEvent.KEYCODE_DPAD_UP:
416 // Do nothing
417 wasHandled = true;
418 break;
419 case KeyEvent.KEYCODE_DPAD_DOWN:
420 if (handleKeyEvent) {
421 // Select the content view
422 contents.requestFocus();
423 }
424 wasHandled = true;
425 break;
426 default: break;
427 }
428 return wasHandled;
429 }
430
431 /**
Winson Chung3d503fb2011-07-13 17:25:49 -0700432 * Handles key events in the workspace hotseat (bottom of the screen).
Winson Chung4e6a9762011-05-09 11:56:34 -0700433 */
Winson Chung3d503fb2011-07-13 17:25:49 -0700434 static boolean handleHotseatButtonKeyEvent(View v, int keyCode, KeyEvent e, int orientation) {
Winson Chung4e6a9762011-05-09 11:56:34 -0700435 final ViewGroup parent = (ViewGroup) v.getParent();
436 final ViewGroup launcher = (ViewGroup) parent.getParent();
437 final Workspace workspace = (Workspace) launcher.findViewById(R.id.workspace);
438 final int buttonIndex = parent.indexOfChild(v);
439 final int buttonCount = parent.getChildCount();
440 final int pageIndex = workspace.getCurrentPage();
Winson Chung4e6a9762011-05-09 11:56:34 -0700441
442 // NOTE: currently we don't special case for the phone UI in different
Winson Chung3d503fb2011-07-13 17:25:49 -0700443 // orientations, even though the hotseat is on the side in landscape mode. This
Winson Chung4e6a9762011-05-09 11:56:34 -0700444 // is to ensure that accessibility consistency is maintained across rotations.
445
446 final int action = e.getAction();
447 final boolean handleKeyEvent = (action != KeyEvent.ACTION_UP);
448 boolean wasHandled = false;
449 switch (keyCode) {
450 case KeyEvent.KEYCODE_DPAD_LEFT:
451 if (handleKeyEvent) {
Winson Chung74608b52011-06-23 13:23:20 -0700452 // Select the previous button, otherwise snap to the previous page
Winson Chung4e6a9762011-05-09 11:56:34 -0700453 if (buttonIndex > 0) {
454 parent.getChildAt(buttonIndex - 1).requestFocus();
455 } else {
Winson Chung74608b52011-06-23 13:23:20 -0700456 workspace.snapToPage(pageIndex - 1);
Winson Chung4e6a9762011-05-09 11:56:34 -0700457 }
458 }
459 wasHandled = true;
460 break;
461 case KeyEvent.KEYCODE_DPAD_RIGHT:
462 if (handleKeyEvent) {
Winson Chung74608b52011-06-23 13:23:20 -0700463 // Select the next button, otherwise snap to the next page
Winson Chung4e6a9762011-05-09 11:56:34 -0700464 if (buttonIndex < (buttonCount - 1)) {
465 parent.getChildAt(buttonIndex + 1).requestFocus();
466 } else {
Winson Chung74608b52011-06-23 13:23:20 -0700467 workspace.snapToPage(pageIndex + 1);
Winson Chung4e6a9762011-05-09 11:56:34 -0700468 }
469 }
470 wasHandled = true;
471 break;
472 case KeyEvent.KEYCODE_DPAD_UP:
473 if (handleKeyEvent) {
474 // Select the first bubble text view in the current page of the workspace
475 final CellLayout layout = (CellLayout) workspace.getChildAt(pageIndex);
Michael Jurkaa52570f2012-03-20 03:18:20 -0700476 final ShortcutAndWidgetContainer children = layout.getShortcutsAndWidgets();
Adam Cohenac56cff2011-09-28 20:45:37 -0700477 final View newIcon = getIconInDirection(layout, children, -1, 1);
Winson Chung4e6a9762011-05-09 11:56:34 -0700478 if (newIcon != null) {
479 newIcon.requestFocus();
480 } else {
481 workspace.requestFocus();
482 }
483 }
484 wasHandled = true;
485 break;
486 case KeyEvent.KEYCODE_DPAD_DOWN:
487 // Do nothing
488 wasHandled = true;
489 break;
490 default: break;
491 }
492 return wasHandled;
493 }
494
495 /**
Winson Chung97d85d22011-04-13 11:27:36 -0700496 * Private helper method to get the CellLayoutChildren given a CellLayout index.
497 */
Michael Jurkaa52570f2012-03-20 03:18:20 -0700498 private static ShortcutAndWidgetContainer getCellLayoutChildrenForIndex(
499 ViewGroup container, int i) {
Winson Chung97d85d22011-04-13 11:27:36 -0700500 ViewGroup parent = (ViewGroup) container.getChildAt(i);
Michael Jurkaa52570f2012-03-20 03:18:20 -0700501 return (ShortcutAndWidgetContainer) parent.getChildAt(0);
Winson Chung97d85d22011-04-13 11:27:36 -0700502 }
503
504 /**
505 * Private helper method to sort all the CellLayout children in order of their (x,y) spatially
506 * from top left to bottom right.
507 */
508 private static ArrayList<View> getCellLayoutChildrenSortedSpatially(CellLayout layout,
509 ViewGroup parent) {
510 // First we order each the CellLayout children by their x,y coordinates
511 final int cellCountX = layout.getCountX();
512 final int count = parent.getChildCount();
513 ArrayList<View> views = new ArrayList<View>();
514 for (int j = 0; j < count; ++j) {
515 views.add(parent.getChildAt(j));
516 }
517 Collections.sort(views, new Comparator<View>() {
518 @Override
519 public int compare(View lhs, View rhs) {
520 CellLayout.LayoutParams llp = (CellLayout.LayoutParams) lhs.getLayoutParams();
521 CellLayout.LayoutParams rlp = (CellLayout.LayoutParams) rhs.getLayoutParams();
522 int lvIndex = (llp.cellY * cellCountX) + llp.cellX;
523 int rvIndex = (rlp.cellY * cellCountX) + rlp.cellX;
524 return lvIndex - rvIndex;
525 }
526 });
527 return views;
528 }
529 /**
Adam Cohenac56cff2011-09-28 20:45:37 -0700530 * Private helper method to find the index of the next BubbleTextView or FolderIcon in the
531 * direction delta.
532 *
Winson Chung97d85d22011-04-13 11:27:36 -0700533 * @param delta either -1 or 1 depending on the direction we want to search
534 */
Adam Cohenac56cff2011-09-28 20:45:37 -0700535 private static View findIndexOfIcon(ArrayList<View> views, int i, int delta) {
Winson Chung97d85d22011-04-13 11:27:36 -0700536 // Then we find the next BubbleTextView offset by delta from i
537 final int count = views.size();
538 int newI = i + delta;
539 while (0 <= newI && newI < count) {
540 View newV = views.get(newI);
Adam Cohenac56cff2011-09-28 20:45:37 -0700541 if (newV instanceof BubbleTextView || newV instanceof FolderIcon) {
Winson Chung97d85d22011-04-13 11:27:36 -0700542 return newV;
543 }
544 newI += delta;
545 }
546 return null;
547 }
Adam Cohenac56cff2011-09-28 20:45:37 -0700548 private static View getIconInDirection(CellLayout layout, ViewGroup parent, int i,
Winson Chung97d85d22011-04-13 11:27:36 -0700549 int delta) {
550 final ArrayList<View> views = getCellLayoutChildrenSortedSpatially(layout, parent);
Adam Cohenac56cff2011-09-28 20:45:37 -0700551 return findIndexOfIcon(views, i, delta);
Winson Chung97d85d22011-04-13 11:27:36 -0700552 }
Adam Cohenac56cff2011-09-28 20:45:37 -0700553 private static View getIconInDirection(CellLayout layout, ViewGroup parent, View v,
Winson Chung97d85d22011-04-13 11:27:36 -0700554 int delta) {
555 final ArrayList<View> views = getCellLayoutChildrenSortedSpatially(layout, parent);
Adam Cohenac56cff2011-09-28 20:45:37 -0700556 return findIndexOfIcon(views, views.indexOf(v), delta);
Winson Chung97d85d22011-04-13 11:27:36 -0700557 }
558 /**
Adam Cohenac56cff2011-09-28 20:45:37 -0700559 * Private helper method to find the next closest BubbleTextView or FolderIcon in the direction
560 * delta on the next line.
561 *
Winson Chung97d85d22011-04-13 11:27:36 -0700562 * @param delta either -1 or 1 depending on the line and direction we want to search
563 */
Adam Cohenac56cff2011-09-28 20:45:37 -0700564 private static View getClosestIconOnLine(CellLayout layout, ViewGroup parent, View v,
Winson Chung97d85d22011-04-13 11:27:36 -0700565 int lineDelta) {
566 final ArrayList<View> views = getCellLayoutChildrenSortedSpatially(layout, parent);
567 final CellLayout.LayoutParams lp = (CellLayout.LayoutParams) v.getLayoutParams();
Winson Chung97d85d22011-04-13 11:27:36 -0700568 final int cellCountY = layout.getCountY();
569 final int row = lp.cellY;
570 final int newRow = row + lineDelta;
571 if (0 <= newRow && newRow < cellCountY) {
572 float closestDistance = Float.MAX_VALUE;
573 int closestIndex = -1;
574 int index = views.indexOf(v);
575 int endIndex = (lineDelta < 0) ? -1 : views.size();
576 while (index != endIndex) {
577 View newV = views.get(index);
578 CellLayout.LayoutParams tmpLp = (CellLayout.LayoutParams) newV.getLayoutParams();
579 boolean satisfiesRow = (lineDelta < 0) ? (tmpLp.cellY < row) : (tmpLp.cellY > row);
Adam Cohenac56cff2011-09-28 20:45:37 -0700580 if (satisfiesRow &&
581 (newV instanceof BubbleTextView || newV instanceof FolderIcon)) {
Winson Chung97d85d22011-04-13 11:27:36 -0700582 float tmpDistance = (float) Math.sqrt(Math.pow(tmpLp.cellX - lp.cellX, 2) +
583 Math.pow(tmpLp.cellY - lp.cellY, 2));
584 if (tmpDistance < closestDistance) {
585 closestIndex = index;
586 closestDistance = tmpDistance;
587 }
588 }
589 if (index <= endIndex) {
590 ++index;
591 } else {
592 --index;
593 }
594 }
595 if (closestIndex > -1) {
596 return views.get(closestIndex);
597 }
598 }
599 return null;
600 }
601
602 /**
Adam Cohenac56cff2011-09-28 20:45:37 -0700603 * Handles key events in a Workspace containing.
Winson Chung97d85d22011-04-13 11:27:36 -0700604 */
Adam Cohenac56cff2011-09-28 20:45:37 -0700605 static boolean handleIconKeyEvent(View v, int keyCode, KeyEvent e) {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700606 ShortcutAndWidgetContainer parent = (ShortcutAndWidgetContainer) v.getParent();
Winson Chung97d85d22011-04-13 11:27:36 -0700607 final CellLayout layout = (CellLayout) parent.getParent();
608 final Workspace workspace = (Workspace) layout.getParent();
609 final ViewGroup launcher = (ViewGroup) workspace.getParent();
Adam Cohen24ce0b32014-01-14 16:18:14 -0800610 final ViewGroup tabs = (ViewGroup) launcher.findViewById(R.id.search_drop_target_bar);
Winson Chung4d279d92011-07-21 11:46:32 -0700611 final ViewGroup hotseat = (ViewGroup) launcher.findViewById(R.id.hotseat);
Winson Chung97d85d22011-04-13 11:27:36 -0700612 int pageIndex = workspace.indexOfChild(layout);
613 int pageCount = workspace.getChildCount();
614
615 final int action = e.getAction();
616 final boolean handleKeyEvent = (action != KeyEvent.ACTION_UP);
617 boolean wasHandled = false;
618 switch (keyCode) {
619 case KeyEvent.KEYCODE_DPAD_LEFT:
620 if (handleKeyEvent) {
621 // Select the previous icon or the last icon on the previous page if possible
Adam Cohenac56cff2011-09-28 20:45:37 -0700622 View newIcon = getIconInDirection(layout, parent, v, -1);
Winson Chung97d85d22011-04-13 11:27:36 -0700623 if (newIcon != null) {
624 newIcon.requestFocus();
625 } else {
626 if (pageIndex > 0) {
627 parent = getCellLayoutChildrenForIndex(workspace, pageIndex - 1);
Adam Cohenac56cff2011-09-28 20:45:37 -0700628 newIcon = getIconInDirection(layout, parent,
Winson Chung97d85d22011-04-13 11:27:36 -0700629 parent.getChildCount(), -1);
630 if (newIcon != null) {
631 newIcon.requestFocus();
632 } else {
633 // Snap to the previous page
634 workspace.snapToPage(pageIndex - 1);
635 }
636 }
637 }
638 }
639 wasHandled = true;
640 break;
641 case KeyEvent.KEYCODE_DPAD_RIGHT:
642 if (handleKeyEvent) {
643 // Select the next icon or the first icon on the next page if possible
Adam Cohenac56cff2011-09-28 20:45:37 -0700644 View newIcon = getIconInDirection(layout, parent, v, 1);
Winson Chung97d85d22011-04-13 11:27:36 -0700645 if (newIcon != null) {
646 newIcon.requestFocus();
647 } else {
648 if (pageIndex < (pageCount - 1)) {
649 parent = getCellLayoutChildrenForIndex(workspace, pageIndex + 1);
Adam Cohenac56cff2011-09-28 20:45:37 -0700650 newIcon = getIconInDirection(layout, parent, -1, 1);
Winson Chung97d85d22011-04-13 11:27:36 -0700651 if (newIcon != null) {
652 newIcon.requestFocus();
653 } else {
654 // Snap to the next page
655 workspace.snapToPage(pageIndex + 1);
656 }
657 }
658 }
659 }
660 wasHandled = true;
661 break;
662 case KeyEvent.KEYCODE_DPAD_UP:
663 if (handleKeyEvent) {
664 // Select the closest icon in the previous line, otherwise select the tab bar
Adam Cohenac56cff2011-09-28 20:45:37 -0700665 View newIcon = getClosestIconOnLine(layout, parent, v, -1);
Winson Chung97d85d22011-04-13 11:27:36 -0700666 if (newIcon != null) {
667 newIcon.requestFocus();
668 wasHandled = true;
669 } else {
670 tabs.requestFocus();
671 }
672 }
673 break;
674 case KeyEvent.KEYCODE_DPAD_DOWN:
675 if (handleKeyEvent) {
Winson Chung4d279d92011-07-21 11:46:32 -0700676 // Select the closest icon in the next line, otherwise select the button bar
Adam Cohenac56cff2011-09-28 20:45:37 -0700677 View newIcon = getClosestIconOnLine(layout, parent, v, 1);
Winson Chung97d85d22011-04-13 11:27:36 -0700678 if (newIcon != null) {
679 newIcon.requestFocus();
680 wasHandled = true;
Winson Chung4d279d92011-07-21 11:46:32 -0700681 } else if (hotseat != null) {
682 hotseat.requestFocus();
Winson Chung97d85d22011-04-13 11:27:36 -0700683 }
684 }
685 break;
686 case KeyEvent.KEYCODE_PAGE_UP:
687 if (handleKeyEvent) {
688 // Select the first icon on the previous page or the first icon on this page
689 // if there is no previous page
690 if (pageIndex > 0) {
691 parent = getCellLayoutChildrenForIndex(workspace, pageIndex - 1);
Adam Cohenac56cff2011-09-28 20:45:37 -0700692 View newIcon = getIconInDirection(layout, parent, -1, 1);
Winson Chung97d85d22011-04-13 11:27:36 -0700693 if (newIcon != null) {
694 newIcon.requestFocus();
695 } else {
696 // Snap to the previous page
697 workspace.snapToPage(pageIndex - 1);
698 }
699 } else {
Adam Cohenac56cff2011-09-28 20:45:37 -0700700 View newIcon = getIconInDirection(layout, parent, -1, 1);
Winson Chung97d85d22011-04-13 11:27:36 -0700701 if (newIcon != null) {
702 newIcon.requestFocus();
703 }
704 }
705 }
706 wasHandled = true;
707 break;
708 case KeyEvent.KEYCODE_PAGE_DOWN:
709 if (handleKeyEvent) {
710 // Select the first icon on the next page or the last icon on this page
711 // if there is no previous page
712 if (pageIndex < (pageCount - 1)) {
713 parent = getCellLayoutChildrenForIndex(workspace, pageIndex + 1);
Adam Cohenac56cff2011-09-28 20:45:37 -0700714 View newIcon = getIconInDirection(layout, parent, -1, 1);
Winson Chung97d85d22011-04-13 11:27:36 -0700715 if (newIcon != null) {
716 newIcon.requestFocus();
717 } else {
718 // Snap to the next page
719 workspace.snapToPage(pageIndex + 1);
720 }
721 } else {
Adam Cohenac56cff2011-09-28 20:45:37 -0700722 View newIcon = getIconInDirection(layout, parent,
Winson Chung97d85d22011-04-13 11:27:36 -0700723 parent.getChildCount(), -1);
724 if (newIcon != null) {
725 newIcon.requestFocus();
726 }
727 }
728 }
729 wasHandled = true;
730 break;
731 case KeyEvent.KEYCODE_MOVE_HOME:
732 if (handleKeyEvent) {
733 // Select the first icon on this page
Adam Cohenac56cff2011-09-28 20:45:37 -0700734 View newIcon = getIconInDirection(layout, parent, -1, 1);
Winson Chung97d85d22011-04-13 11:27:36 -0700735 if (newIcon != null) {
736 newIcon.requestFocus();
737 }
738 }
739 wasHandled = true;
740 break;
741 case KeyEvent.KEYCODE_MOVE_END:
742 if (handleKeyEvent) {
743 // Select the last icon on this page
Adam Cohenac56cff2011-09-28 20:45:37 -0700744 View newIcon = getIconInDirection(layout, parent,
745 parent.getChildCount(), -1);
746 if (newIcon != null) {
747 newIcon.requestFocus();
748 }
749 }
750 wasHandled = true;
751 break;
752 default: break;
753 }
754 return wasHandled;
755 }
756
757 /**
758 * Handles key events for items in a Folder.
759 */
760 static boolean handleFolderKeyEvent(View v, int keyCode, KeyEvent e) {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700761 ShortcutAndWidgetContainer parent = (ShortcutAndWidgetContainer) v.getParent();
Adam Cohenac56cff2011-09-28 20:45:37 -0700762 final CellLayout layout = (CellLayout) parent.getParent();
Winson Chunga1f133d2013-07-25 11:14:30 -0700763 final ScrollView scrollView = (ScrollView) layout.getParent();
764 final Folder folder = (Folder) scrollView.getParent();
Adam Cohenac56cff2011-09-28 20:45:37 -0700765 View title = folder.mFolderName;
766
767 final int action = e.getAction();
768 final boolean handleKeyEvent = (action != KeyEvent.ACTION_UP);
769 boolean wasHandled = false;
770 switch (keyCode) {
771 case KeyEvent.KEYCODE_DPAD_LEFT:
772 if (handleKeyEvent) {
773 // Select the previous icon
774 View newIcon = getIconInDirection(layout, parent, v, -1);
775 if (newIcon != null) {
776 newIcon.requestFocus();
777 }
778 }
779 wasHandled = true;
780 break;
781 case KeyEvent.KEYCODE_DPAD_RIGHT:
782 if (handleKeyEvent) {
783 // Select the next icon
784 View newIcon = getIconInDirection(layout, parent, v, 1);
785 if (newIcon != null) {
786 newIcon.requestFocus();
787 } else {
788 title.requestFocus();
789 }
790 }
791 wasHandled = true;
792 break;
793 case KeyEvent.KEYCODE_DPAD_UP:
794 if (handleKeyEvent) {
795 // Select the closest icon in the previous line
796 View newIcon = getClosestIconOnLine(layout, parent, v, -1);
797 if (newIcon != null) {
798 newIcon.requestFocus();
799 }
800 }
801 wasHandled = true;
802 break;
803 case KeyEvent.KEYCODE_DPAD_DOWN:
804 if (handleKeyEvent) {
805 // Select the closest icon in the next line
806 View newIcon = getClosestIconOnLine(layout, parent, v, 1);
807 if (newIcon != null) {
808 newIcon.requestFocus();
809 } else {
810 title.requestFocus();
811 }
812 }
813 wasHandled = true;
814 break;
815 case KeyEvent.KEYCODE_MOVE_HOME:
816 if (handleKeyEvent) {
817 // Select the first icon on this page
818 View newIcon = getIconInDirection(layout, parent, -1, 1);
819 if (newIcon != null) {
820 newIcon.requestFocus();
821 }
822 }
823 wasHandled = true;
824 break;
825 case KeyEvent.KEYCODE_MOVE_END:
826 if (handleKeyEvent) {
827 // Select the last icon on this page
828 View newIcon = getIconInDirection(layout, parent,
Winson Chung97d85d22011-04-13 11:27:36 -0700829 parent.getChildCount(), -1);
830 if (newIcon != null) {
831 newIcon.requestFocus();
832 }
833 }
834 wasHandled = true;
835 break;
836 default: break;
837 }
838 return wasHandled;
839 }
840}