blob: d529b39016f14c962bc47a6dbcd5d48fe7a48bdd [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;
Sunny Goyalb3726d92014-08-20 16:58:17 -070020import android.util.Log;
Winson Chung97d85d22011-04-13 11:27:36 -070021import android.view.KeyEvent;
Sunny Goyalb3726d92014-08-20 16:58:17 -070022import android.view.SoundEffectConstants;
Winson Chung97d85d22011-04-13 11:27:36 -070023import android.view.View;
24import android.view.ViewGroup;
25import android.view.ViewParent;
Winson Chunga1f133d2013-07-25 11:14:30 -070026import android.widget.ScrollView;
Winson Chung97d85d22011-04-13 11:27:36 -070027
Winson Chungfaa13252011-06-13 18:15:54 -070028import java.util.ArrayList;
29import java.util.Collections;
30import java.util.Comparator;
31
Winson Chung97d85d22011-04-13 11:27:36 -070032/**
Winson Chung4d279d92011-07-21 11:46:32 -070033 * A keyboard listener we set on all the workspace icons.
34 */
Adam Cohenac56cff2011-09-28 20:45:37 -070035class IconKeyEventListener implements View.OnKeyListener {
Winson Chung4d279d92011-07-21 11:46:32 -070036 public boolean onKey(View v, int keyCode, KeyEvent event) {
Adam Cohenac56cff2011-09-28 20:45:37 -070037 return FocusHelper.handleIconKeyEvent(v, keyCode, event);
38 }
39}
40
41/**
42 * A keyboard listener we set on all the workspace icons.
43 */
44class FolderKeyEventListener implements View.OnKeyListener {
45 public boolean onKey(View v, int keyCode, KeyEvent event) {
46 return FocusHelper.handleFolderKeyEvent(v, keyCode, event);
Winson Chung4d279d92011-07-21 11:46:32 -070047 }
48}
49
50/**
Winson Chung3d503fb2011-07-13 17:25:49 -070051 * A keyboard listener we set on all the hotseat buttons.
Winson Chung4e6a9762011-05-09 11:56:34 -070052 */
Adam Cohenac56cff2011-09-28 20:45:37 -070053class HotseatIconKeyEventListener implements View.OnKeyListener {
Winson Chung4e6a9762011-05-09 11:56:34 -070054 public boolean onKey(View v, int keyCode, KeyEvent event) {
55 final Configuration configuration = v.getResources().getConfiguration();
Winson Chung3d503fb2011-07-13 17:25:49 -070056 return FocusHelper.handleHotseatButtonKeyEvent(v, keyCode, event, configuration.orientation);
Winson Chung4e6a9762011-05-09 11:56:34 -070057 }
58}
59
Winson Chung97d85d22011-04-13 11:27:36 -070060public class FocusHelper {
61 /**
62 * Private helper to get the parent TabHost in the view hiearchy.
63 */
Adam Cohenc956cba2014-07-24 09:17:37 -070064 private static AppsCustomizeTabHost findTabHostParent(View v) {
Winson Chung97d85d22011-04-13 11:27:36 -070065 ViewParent p = v.getParent();
Adam Cohenc956cba2014-07-24 09:17:37 -070066 while (p != null && !(p instanceof AppsCustomizeTabHost)) {
Winson Chung97d85d22011-04-13 11:27:36 -070067 p = p.getParent();
68 }
Adam Cohenc956cba2014-07-24 09:17:37 -070069 return (AppsCustomizeTabHost) p;
Winson Chung97d85d22011-04-13 11:27:36 -070070 }
71
72 /**
Adam Cohenae4f1552011-10-20 00:15:42 -070073 * Returns the Viewgroup containing page contents for the page at the index specified.
74 */
75 private static ViewGroup getAppsCustomizePage(ViewGroup container, int index) {
76 ViewGroup page = (ViewGroup) ((PagedView) container).getPageAt(index);
Winson Chungc58497e2013-09-03 17:48:37 -070077 if (page instanceof CellLayout) {
Adam Cohenae4f1552011-10-20 00:15:42 -070078 // There are two layers, a PagedViewCellLayout and PagedViewCellLayoutChildren
Winson Chungc58497e2013-09-03 17:48:37 -070079 page = ((CellLayout) page).getShortcutsAndWidgets();
Adam Cohenae4f1552011-10-20 00:15:42 -070080 }
81 return page;
82 }
83
84 /**
Winson Chung97d85d22011-04-13 11:27:36 -070085 * Handles key events in a PageViewExtendedLayout containing PagedViewWidgets.
Winson Chung4e6a9762011-05-09 11:56:34 -070086 */
87 static boolean handlePagedViewGridLayoutWidgetKeyEvent(PagedViewWidget w, int keyCode,
88 KeyEvent e) {
89
90 final PagedViewGridLayout parent = (PagedViewGridLayout) w.getParent();
Adam Cohenae4f1552011-10-20 00:15:42 -070091 final PagedView container = (PagedView) parent.getParent();
Winson Chung4e6a9762011-05-09 11:56:34 -070092 final int widgetIndex = parent.indexOfChild(w);
93 final int widgetCount = parent.getChildCount();
Adam Cohenae4f1552011-10-20 00:15:42 -070094 final int pageIndex = ((PagedView) container).indexToPage(container.indexOfChild(parent));
Winson Chung4e6a9762011-05-09 11:56:34 -070095 final int pageCount = container.getChildCount();
96 final int cellCountX = parent.getCellCountX();
97 final int cellCountY = parent.getCellCountY();
98 final int x = widgetIndex % cellCountX;
99 final int y = widgetIndex / cellCountX;
100
101 final int action = e.getAction();
102 final boolean handleKeyEvent = (action != KeyEvent.ACTION_UP);
Adam Cohenae4f1552011-10-20 00:15:42 -0700103 ViewGroup newParent = null;
Winson Chung3b0b46a2011-07-28 10:45:09 -0700104 // Now that we load items in the bg asynchronously, we can't just focus
105 // child siblings willy-nilly
106 View child = null;
Winson Chung4e6a9762011-05-09 11:56:34 -0700107 boolean wasHandled = false;
108 switch (keyCode) {
109 case KeyEvent.KEYCODE_DPAD_LEFT:
110 if (handleKeyEvent) {
111 // Select the previous widget or the last widget on the previous page
112 if (widgetIndex > 0) {
113 parent.getChildAt(widgetIndex - 1).requestFocus();
114 } else {
115 if (pageIndex > 0) {
Adam Cohenae4f1552011-10-20 00:15:42 -0700116 newParent = getAppsCustomizePage(container, pageIndex - 1);
117 if (newParent != null) {
118 child = newParent.getChildAt(newParent.getChildCount() - 1);
119 if (child != null) child.requestFocus();
120 }
Winson Chung4e6a9762011-05-09 11:56:34 -0700121 }
122 }
123 }
124 wasHandled = true;
125 break;
126 case KeyEvent.KEYCODE_DPAD_RIGHT:
127 if (handleKeyEvent) {
128 // Select the next widget or the first widget on the next page
129 if (widgetIndex < (widgetCount - 1)) {
130 parent.getChildAt(widgetIndex + 1).requestFocus();
131 } else {
132 if (pageIndex < (pageCount - 1)) {
Adam Cohenae4f1552011-10-20 00:15:42 -0700133 newParent = getAppsCustomizePage(container, pageIndex + 1);
134 if (newParent != null) {
135 child = newParent.getChildAt(0);
136 if (child != null) child.requestFocus();
137 }
Winson Chung4e6a9762011-05-09 11:56:34 -0700138 }
139 }
140 }
141 wasHandled = true;
142 break;
143 case KeyEvent.KEYCODE_DPAD_UP:
144 if (handleKeyEvent) {
145 // Select the closest icon in the previous row, otherwise select the tab bar
146 if (y > 0) {
147 int newWidgetIndex = ((y - 1) * cellCountX) + x;
Winson Chung3b0b46a2011-07-28 10:45:09 -0700148 child = parent.getChildAt(newWidgetIndex);
149 if (child != null) child.requestFocus();
Winson Chung4e6a9762011-05-09 11:56:34 -0700150 }
151 }
152 wasHandled = true;
153 break;
154 case KeyEvent.KEYCODE_DPAD_DOWN:
155 if (handleKeyEvent) {
156 // Select the closest icon in the previous row, otherwise do nothing
157 if (y < (cellCountY - 1)) {
158 int newWidgetIndex = Math.min(widgetCount - 1, ((y + 1) * cellCountX) + x);
Winson Chung3b0b46a2011-07-28 10:45:09 -0700159 child = parent.getChildAt(newWidgetIndex);
160 if (child != null) child.requestFocus();
Winson Chung4e6a9762011-05-09 11:56:34 -0700161 }
162 }
163 wasHandled = true;
164 break;
165 case KeyEvent.KEYCODE_ENTER:
166 case KeyEvent.KEYCODE_DPAD_CENTER:
167 if (handleKeyEvent) {
168 // Simulate a click on the widget
169 View.OnClickListener clickListener = (View.OnClickListener) container;
170 clickListener.onClick(w);
171 }
172 wasHandled = true;
173 break;
174 case KeyEvent.KEYCODE_PAGE_UP:
175 if (handleKeyEvent) {
176 // Select the first item on the previous page, or the first item on this page
177 // if there is no previous page
178 if (pageIndex > 0) {
Adam Cohenae4f1552011-10-20 00:15:42 -0700179 newParent = getAppsCustomizePage(container, pageIndex - 1);
180 if (newParent != null) {
181 child = newParent.getChildAt(0);
182 }
Winson Chung4e6a9762011-05-09 11:56:34 -0700183 } else {
Winson Chung3b0b46a2011-07-28 10:45:09 -0700184 child = parent.getChildAt(0);
Winson Chung4e6a9762011-05-09 11:56:34 -0700185 }
Winson Chung3b0b46a2011-07-28 10:45:09 -0700186 if (child != null) child.requestFocus();
Winson Chung4e6a9762011-05-09 11:56:34 -0700187 }
188 wasHandled = true;
189 break;
190 case KeyEvent.KEYCODE_PAGE_DOWN:
191 if (handleKeyEvent) {
192 // Select the first item on the next page, or the last item on this page
193 // if there is no next page
194 if (pageIndex < (pageCount - 1)) {
Adam Cohenae4f1552011-10-20 00:15:42 -0700195 newParent = getAppsCustomizePage(container, pageIndex + 1);
196 if (newParent != null) {
197 child = newParent.getChildAt(0);
198 }
Winson Chung4e6a9762011-05-09 11:56:34 -0700199 } else {
Winson Chung3b0b46a2011-07-28 10:45:09 -0700200 child = parent.getChildAt(widgetCount - 1);
Winson Chung4e6a9762011-05-09 11:56:34 -0700201 }
Winson Chung3b0b46a2011-07-28 10:45:09 -0700202 if (child != null) child.requestFocus();
Winson Chung4e6a9762011-05-09 11:56:34 -0700203 }
204 wasHandled = true;
205 break;
206 case KeyEvent.KEYCODE_MOVE_HOME:
207 if (handleKeyEvent) {
208 // Select the first item on this page
Winson Chung3b0b46a2011-07-28 10:45:09 -0700209 child = parent.getChildAt(0);
210 if (child != null) child.requestFocus();
Winson Chung4e6a9762011-05-09 11:56:34 -0700211 }
212 wasHandled = true;
213 break;
214 case KeyEvent.KEYCODE_MOVE_END:
215 if (handleKeyEvent) {
216 // Select the last item on this page
217 parent.getChildAt(widgetCount - 1).requestFocus();
218 }
219 wasHandled = true;
220 break;
221 default: break;
222 }
223 return wasHandled;
224 }
225
226 /**
Winson Chung97d85d22011-04-13 11:27:36 -0700227 * Handles key events in a PageViewCellLayout containing PagedViewIcons.
228 */
Adam Cohenae4f1552011-10-20 00:15:42 -0700229 static boolean handleAppsCustomizeKeyEvent(View v, int keyCode, KeyEvent e) {
230 ViewGroup parentLayout;
231 ViewGroup itemContainer;
232 int countX;
233 int countY;
Winson Chungc58497e2013-09-03 17:48:37 -0700234 if (v.getParent() instanceof ShortcutAndWidgetContainer) {
Adam Cohenae4f1552011-10-20 00:15:42 -0700235 itemContainer = (ViewGroup) v.getParent();
236 parentLayout = (ViewGroup) itemContainer.getParent();
Winson Chungc58497e2013-09-03 17:48:37 -0700237 countX = ((CellLayout) parentLayout).getCountX();
238 countY = ((CellLayout) parentLayout).getCountY();
Adam Cohenae4f1552011-10-20 00:15:42 -0700239 } else {
240 itemContainer = parentLayout = (ViewGroup) v.getParent();
241 countX = ((PagedViewGridLayout) parentLayout).getCellCountX();
242 countY = ((PagedViewGridLayout) parentLayout).getCellCountY();
243 }
244
Winson Chung97d85d22011-04-13 11:27:36 -0700245 // Note we have an extra parent because of the
246 // PagedViewCellLayout/PagedViewCellLayoutChildren relationship
Adam Cohenae4f1552011-10-20 00:15:42 -0700247 final PagedView container = (PagedView) parentLayout.getParent();
Adam Cohenae4f1552011-10-20 00:15:42 -0700248 final int iconIndex = itemContainer.indexOfChild(v);
249 final int itemCount = itemContainer.getChildCount();
250 final int pageIndex = ((PagedView) container).indexToPage(container.indexOfChild(parentLayout));
Winson Chung97d85d22011-04-13 11:27:36 -0700251 final int pageCount = container.getChildCount();
Adam Cohenae4f1552011-10-20 00:15:42 -0700252
253 final int x = iconIndex % countX;
254 final int y = iconIndex / countX;
Winson Chung97d85d22011-04-13 11:27:36 -0700255
Sunny Goyalb3726d92014-08-20 16:58:17 -0700256 final int action = e.getAction();
Winson Chung97d85d22011-04-13 11:27:36 -0700257 final boolean handleKeyEvent = (action != KeyEvent.ACTION_UP);
Adam Cohenae4f1552011-10-20 00:15:42 -0700258 ViewGroup newParent = null;
Winson Chung90576b52011-09-27 17:45:27 -0700259 // Side pages do not always load synchronously, so check before focusing child siblings
260 // willy-nilly
261 View child = null;
Winson Chung97d85d22011-04-13 11:27:36 -0700262 boolean wasHandled = false;
263 switch (keyCode) {
264 case KeyEvent.KEYCODE_DPAD_LEFT:
265 if (handleKeyEvent) {
266 // Select the previous icon or the last icon on the previous page
Winson Chung90576b52011-09-27 17:45:27 -0700267 if (iconIndex > 0) {
Adam Cohenae4f1552011-10-20 00:15:42 -0700268 itemContainer.getChildAt(iconIndex - 1).requestFocus();
Sunny Goyalb3726d92014-08-20 16:58:17 -0700269 v.playSoundEffect(SoundEffectConstants.NAVIGATION_LEFT);
Winson Chung97d85d22011-04-13 11:27:36 -0700270 } else {
271 if (pageIndex > 0) {
Adam Cohenae4f1552011-10-20 00:15:42 -0700272 newParent = getAppsCustomizePage(container, pageIndex - 1);
Winson Chung90576b52011-09-27 17:45:27 -0700273 if (newParent != null) {
Adam Cohenae4f1552011-10-20 00:15:42 -0700274 container.snapToPage(pageIndex - 1);
Winson Chung90576b52011-09-27 17:45:27 -0700275 child = newParent.getChildAt(newParent.getChildCount() - 1);
Sunny Goyalb3726d92014-08-20 16:58:17 -0700276 if (child != null) {
277 child.requestFocus();
278 v.playSoundEffect(SoundEffectConstants.NAVIGATION_LEFT);
279 }
Winson Chung90576b52011-09-27 17:45:27 -0700280 }
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();
Sunny Goyalb3726d92014-08-20 16:58:17 -0700291 v.playSoundEffect(SoundEffectConstants.NAVIGATION_RIGHT);
Winson Chung97d85d22011-04-13 11:27:36 -0700292 } else {
293 if (pageIndex < (pageCount - 1)) {
Adam Cohenae4f1552011-10-20 00:15:42 -0700294 newParent = getAppsCustomizePage(container, pageIndex + 1);
Winson Chung90576b52011-09-27 17:45:27 -0700295 if (newParent != null) {
Adam Cohenae4f1552011-10-20 00:15:42 -0700296 container.snapToPage(pageIndex + 1);
Winson Chung90576b52011-09-27 17:45:27 -0700297 child = newParent.getChildAt(0);
Sunny Goyalb3726d92014-08-20 16:58:17 -0700298 if (child != null) {
299 child.requestFocus();
300 v.playSoundEffect(SoundEffectConstants.NAVIGATION_RIGHT);
301 }
Winson Chung90576b52011-09-27 17:45:27 -0700302 }
Winson Chung97d85d22011-04-13 11:27:36 -0700303 }
304 }
305 }
306 wasHandled = true;
307 break;
308 case KeyEvent.KEYCODE_DPAD_UP:
309 if (handleKeyEvent) {
310 // Select the closest icon in the previous row, otherwise select the tab bar
311 if (y > 0) {
Adam Cohenae4f1552011-10-20 00:15:42 -0700312 int newiconIndex = ((y - 1) * countX) + x;
313 itemContainer.getChildAt(newiconIndex).requestFocus();
Sunny Goyalb3726d92014-08-20 16:58:17 -0700314 v.playSoundEffect(SoundEffectConstants.NAVIGATION_UP);
Winson Chung97d85d22011-04-13 11:27:36 -0700315 }
316 }
317 wasHandled = true;
318 break;
319 case KeyEvent.KEYCODE_DPAD_DOWN:
320 if (handleKeyEvent) {
Sunny Goyalb3726d92014-08-20 16:58:17 -0700321 // Select the closest icon in the next row, otherwise do nothing
Adam Cohenae4f1552011-10-20 00:15:42 -0700322 if (y < (countY - 1)) {
323 int newiconIndex = Math.min(itemCount - 1, ((y + 1) * countX) + x);
Sunny Goyalb3726d92014-08-20 16:58:17 -0700324 int newIconY = newiconIndex / countX;
325 if (newIconY != y) {
326 itemContainer.getChildAt(newiconIndex).requestFocus();
327 v.playSoundEffect(SoundEffectConstants.NAVIGATION_DOWN);
328 }
Winson Chung97d85d22011-04-13 11:27:36 -0700329 }
330 }
331 wasHandled = true;
332 break;
Winson Chung97d85d22011-04-13 11:27:36 -0700333 case KeyEvent.KEYCODE_PAGE_UP:
334 if (handleKeyEvent) {
335 // Select the first icon on the previous page, or the first icon on this page
336 // if there is no previous page
337 if (pageIndex > 0) {
Adam Cohenae4f1552011-10-20 00:15:42 -0700338 newParent = getAppsCustomizePage(container, pageIndex - 1);
Winson Chung90576b52011-09-27 17:45:27 -0700339 if (newParent != null) {
Adam Cohenae4f1552011-10-20 00:15:42 -0700340 container.snapToPage(pageIndex - 1);
Winson Chung90576b52011-09-27 17:45:27 -0700341 child = newParent.getChildAt(0);
Sunny Goyalb3726d92014-08-20 16:58:17 -0700342 if (child != null) {
343 child.requestFocus();
344 v.playSoundEffect(SoundEffectConstants.NAVIGATION_UP);
345 }
Winson Chung90576b52011-09-27 17:45:27 -0700346 }
Winson Chung97d85d22011-04-13 11:27:36 -0700347 } else {
Adam Cohenae4f1552011-10-20 00:15:42 -0700348 itemContainer.getChildAt(0).requestFocus();
Sunny Goyalb3726d92014-08-20 16:58:17 -0700349 v.playSoundEffect(SoundEffectConstants.NAVIGATION_UP);
Winson Chung97d85d22011-04-13 11:27:36 -0700350 }
351 }
352 wasHandled = true;
353 break;
354 case KeyEvent.KEYCODE_PAGE_DOWN:
355 if (handleKeyEvent) {
356 // Select the first icon on the next page, or the last icon on this page
357 // if there is no next page
358 if (pageIndex < (pageCount - 1)) {
Adam Cohenae4f1552011-10-20 00:15:42 -0700359 newParent = getAppsCustomizePage(container, pageIndex + 1);
Winson Chung90576b52011-09-27 17:45:27 -0700360 if (newParent != null) {
Adam Cohenae4f1552011-10-20 00:15:42 -0700361 container.snapToPage(pageIndex + 1);
Winson Chung90576b52011-09-27 17:45:27 -0700362 child = newParent.getChildAt(0);
Sunny Goyalb3726d92014-08-20 16:58:17 -0700363 if (child != null) {
364 child.requestFocus();
365 v.playSoundEffect(SoundEffectConstants.NAVIGATION_DOWN);
366 }
Winson Chung90576b52011-09-27 17:45:27 -0700367 }
Winson Chung97d85d22011-04-13 11:27:36 -0700368 } else {
Adam Cohenae4f1552011-10-20 00:15:42 -0700369 itemContainer.getChildAt(itemCount - 1).requestFocus();
Sunny Goyalb3726d92014-08-20 16:58:17 -0700370 v.playSoundEffect(SoundEffectConstants.NAVIGATION_DOWN);
Winson Chung97d85d22011-04-13 11:27:36 -0700371 }
372 }
373 wasHandled = true;
374 break;
375 case KeyEvent.KEYCODE_MOVE_HOME:
376 if (handleKeyEvent) {
377 // Select the first icon on this page
Adam Cohenae4f1552011-10-20 00:15:42 -0700378 itemContainer.getChildAt(0).requestFocus();
Sunny Goyalb3726d92014-08-20 16:58:17 -0700379 v.playSoundEffect(SoundEffectConstants.NAVIGATION_UP);
Winson Chung97d85d22011-04-13 11:27:36 -0700380 }
381 wasHandled = true;
382 break;
383 case KeyEvent.KEYCODE_MOVE_END:
384 if (handleKeyEvent) {
385 // Select the last icon on this page
Adam Cohenae4f1552011-10-20 00:15:42 -0700386 itemContainer.getChildAt(itemCount - 1).requestFocus();
Sunny Goyalb3726d92014-08-20 16:58:17 -0700387 v.playSoundEffect(SoundEffectConstants.NAVIGATION_DOWN);
Winson Chung97d85d22011-04-13 11:27:36 -0700388 }
389 wasHandled = true;
390 break;
391 default: break;
392 }
393 return wasHandled;
394 }
395
396 /**
397 * Handles key events in the tab widget.
398 */
399 static boolean handleTabKeyEvent(AccessibleTabView v, int keyCode, KeyEvent e) {
Daniel Sandlere4f98912013-06-25 15:13:26 -0400400 if (!LauncherAppState.getInstance().isScreenLarge()) return false;
Winson Chung97d85d22011-04-13 11:27:36 -0700401
402 final FocusOnlyTabWidget parent = (FocusOnlyTabWidget) v.getParent();
Adam Cohenc956cba2014-07-24 09:17:37 -0700403 final AppsCustomizeTabHost tabHost = findTabHostParent(parent);
404 final ViewGroup contents = tabHost.getContent();
Winson Chung97d85d22011-04-13 11:27:36 -0700405 final int tabCount = parent.getTabCount();
406 final int tabIndex = parent.getChildTabIndex(v);
407
408 final int action = e.getAction();
409 final boolean handleKeyEvent = (action != KeyEvent.ACTION_UP);
410 boolean wasHandled = false;
411 switch (keyCode) {
412 case KeyEvent.KEYCODE_DPAD_LEFT:
413 if (handleKeyEvent) {
414 // Select the previous tab
415 if (tabIndex > 0) {
416 parent.getChildTabViewAt(tabIndex - 1).requestFocus();
417 }
418 }
419 wasHandled = true;
420 break;
421 case KeyEvent.KEYCODE_DPAD_RIGHT:
422 if (handleKeyEvent) {
423 // Select the next tab, or if the last tab has a focus right id, select that
424 if (tabIndex < (tabCount - 1)) {
425 parent.getChildTabViewAt(tabIndex + 1).requestFocus();
426 } else {
427 if (v.getNextFocusRightId() != View.NO_ID) {
428 tabHost.findViewById(v.getNextFocusRightId()).requestFocus();
429 }
430 }
431 }
432 wasHandled = true;
433 break;
434 case KeyEvent.KEYCODE_DPAD_UP:
435 // Do nothing
436 wasHandled = true;
437 break;
438 case KeyEvent.KEYCODE_DPAD_DOWN:
439 if (handleKeyEvent) {
440 // Select the content view
441 contents.requestFocus();
442 }
443 wasHandled = true;
444 break;
445 default: break;
446 }
447 return wasHandled;
448 }
449
450 /**
Winson Chung3d503fb2011-07-13 17:25:49 -0700451 * Handles key events in the workspace hotseat (bottom of the screen).
Winson Chung4e6a9762011-05-09 11:56:34 -0700452 */
Winson Chung3d503fb2011-07-13 17:25:49 -0700453 static boolean handleHotseatButtonKeyEvent(View v, int keyCode, KeyEvent e, int orientation) {
Sunny Goyalb3726d92014-08-20 16:58:17 -0700454 ShortcutAndWidgetContainer parent = (ShortcutAndWidgetContainer) v.getParent();
455 final CellLayout layout = (CellLayout) parent.getParent();
Winson Chung4e6a9762011-05-09 11:56:34 -0700456
457 // NOTE: currently we don't special case for the phone UI in different
Sunny Goyalb3726d92014-08-20 16:58:17 -0700458 // orientations, even though the hotseat is on the side in landscape mode. This
Winson Chung4e6a9762011-05-09 11:56:34 -0700459 // is to ensure that accessibility consistency is maintained across rotations.
Winson Chung4e6a9762011-05-09 11:56:34 -0700460 final int action = e.getAction();
461 final boolean handleKeyEvent = (action != KeyEvent.ACTION_UP);
462 boolean wasHandled = false;
463 switch (keyCode) {
464 case KeyEvent.KEYCODE_DPAD_LEFT:
465 if (handleKeyEvent) {
Sunny Goyalb3726d92014-08-20 16:58:17 -0700466 ArrayList<View> views = getCellLayoutChildrenSortedSpatially(layout, parent);
467 int myIndex = views.indexOf(v);
468 // Select the previous button, otherwise do nothing
469 if (myIndex > 0) {
470 views.get(myIndex - 1).requestFocus();
471 v.playSoundEffect(SoundEffectConstants.NAVIGATION_LEFT);
Winson Chung4e6a9762011-05-09 11:56:34 -0700472 }
473 }
474 wasHandled = true;
475 break;
476 case KeyEvent.KEYCODE_DPAD_RIGHT:
477 if (handleKeyEvent) {
Sunny Goyalb3726d92014-08-20 16:58:17 -0700478 ArrayList<View> views = getCellLayoutChildrenSortedSpatially(layout, parent);
479 int myIndex = views.indexOf(v);
480 // Select the next button, otherwise do nothing
481 if (myIndex < views.size() - 1) {
482 views.get(myIndex + 1).requestFocus();
483 v.playSoundEffect(SoundEffectConstants.NAVIGATION_RIGHT);
Winson Chung4e6a9762011-05-09 11:56:34 -0700484 }
485 }
486 wasHandled = true;
487 break;
488 case KeyEvent.KEYCODE_DPAD_UP:
489 if (handleKeyEvent) {
Sunny Goyalb3726d92014-08-20 16:58:17 -0700490 final Workspace workspace = (Workspace)
491 v.getRootView().findViewById(R.id.workspace);
492 if (workspace != null) {
493 int pageIndex = workspace.getCurrentPage();
494 CellLayout topLayout = (CellLayout) workspace.getChildAt(pageIndex);
495 ShortcutAndWidgetContainer children = topLayout.getShortcutsAndWidgets();
496 final View newIcon = getIconInDirection(layout, children, -1, 1);
497 // Select the first bubble text view in the current page of the workspace
498 if (newIcon != null) {
499 newIcon.requestFocus();
500 v.playSoundEffect(SoundEffectConstants.NAVIGATION_UP);
501 } else {
502 workspace.requestFocus();
503 }
Winson Chung4e6a9762011-05-09 11:56:34 -0700504 }
505 }
506 wasHandled = true;
507 break;
508 case KeyEvent.KEYCODE_DPAD_DOWN:
509 // Do nothing
510 wasHandled = true;
511 break;
512 default: break;
513 }
514 return wasHandled;
515 }
516
517 /**
Winson Chung97d85d22011-04-13 11:27:36 -0700518 * Private helper method to get the CellLayoutChildren given a CellLayout index.
519 */
Michael Jurkaa52570f2012-03-20 03:18:20 -0700520 private static ShortcutAndWidgetContainer getCellLayoutChildrenForIndex(
521 ViewGroup container, int i) {
Sunny Goyalb3726d92014-08-20 16:58:17 -0700522 CellLayout parent = (CellLayout) container.getChildAt(i);
523 return parent.getShortcutsAndWidgets();
Winson Chung97d85d22011-04-13 11:27:36 -0700524 }
525
526 /**
527 * Private helper method to sort all the CellLayout children in order of their (x,y) spatially
528 * from top left to bottom right.
529 */
530 private static ArrayList<View> getCellLayoutChildrenSortedSpatially(CellLayout layout,
531 ViewGroup parent) {
532 // First we order each the CellLayout children by their x,y coordinates
533 final int cellCountX = layout.getCountX();
534 final int count = parent.getChildCount();
535 ArrayList<View> views = new ArrayList<View>();
536 for (int j = 0; j < count; ++j) {
537 views.add(parent.getChildAt(j));
538 }
539 Collections.sort(views, new Comparator<View>() {
540 @Override
541 public int compare(View lhs, View rhs) {
542 CellLayout.LayoutParams llp = (CellLayout.LayoutParams) lhs.getLayoutParams();
543 CellLayout.LayoutParams rlp = (CellLayout.LayoutParams) rhs.getLayoutParams();
544 int lvIndex = (llp.cellY * cellCountX) + llp.cellX;
545 int rvIndex = (rlp.cellY * cellCountX) + rlp.cellX;
546 return lvIndex - rvIndex;
547 }
548 });
549 return views;
550 }
551 /**
Adam Cohenac56cff2011-09-28 20:45:37 -0700552 * Private helper method to find the index of the next BubbleTextView or FolderIcon in the
553 * direction delta.
554 *
Winson Chung97d85d22011-04-13 11:27:36 -0700555 * @param delta either -1 or 1 depending on the direction we want to search
556 */
Adam Cohenac56cff2011-09-28 20:45:37 -0700557 private static View findIndexOfIcon(ArrayList<View> views, int i, int delta) {
Winson Chung97d85d22011-04-13 11:27:36 -0700558 // Then we find the next BubbleTextView offset by delta from i
559 final int count = views.size();
560 int newI = i + delta;
561 while (0 <= newI && newI < count) {
562 View newV = views.get(newI);
Adam Cohenac56cff2011-09-28 20:45:37 -0700563 if (newV instanceof BubbleTextView || newV instanceof FolderIcon) {
Winson Chung97d85d22011-04-13 11:27:36 -0700564 return newV;
565 }
566 newI += delta;
567 }
568 return null;
569 }
Adam Cohenac56cff2011-09-28 20:45:37 -0700570 private static View getIconInDirection(CellLayout layout, ViewGroup parent, int i,
Winson Chung97d85d22011-04-13 11:27:36 -0700571 int delta) {
572 final ArrayList<View> views = getCellLayoutChildrenSortedSpatially(layout, parent);
Adam Cohenac56cff2011-09-28 20:45:37 -0700573 return findIndexOfIcon(views, i, delta);
Winson Chung97d85d22011-04-13 11:27:36 -0700574 }
Adam Cohenac56cff2011-09-28 20:45:37 -0700575 private static View getIconInDirection(CellLayout layout, ViewGroup parent, View v,
Winson Chung97d85d22011-04-13 11:27:36 -0700576 int delta) {
577 final ArrayList<View> views = getCellLayoutChildrenSortedSpatially(layout, parent);
Adam Cohenac56cff2011-09-28 20:45:37 -0700578 return findIndexOfIcon(views, views.indexOf(v), delta);
Winson Chung97d85d22011-04-13 11:27:36 -0700579 }
580 /**
Adam Cohenac56cff2011-09-28 20:45:37 -0700581 * Private helper method to find the next closest BubbleTextView or FolderIcon in the direction
582 * delta on the next line.
583 *
Winson Chung97d85d22011-04-13 11:27:36 -0700584 * @param delta either -1 or 1 depending on the line and direction we want to search
585 */
Adam Cohenac56cff2011-09-28 20:45:37 -0700586 private static View getClosestIconOnLine(CellLayout layout, ViewGroup parent, View v,
Winson Chung97d85d22011-04-13 11:27:36 -0700587 int lineDelta) {
588 final ArrayList<View> views = getCellLayoutChildrenSortedSpatially(layout, parent);
589 final CellLayout.LayoutParams lp = (CellLayout.LayoutParams) v.getLayoutParams();
Winson Chung97d85d22011-04-13 11:27:36 -0700590 final int cellCountY = layout.getCountY();
591 final int row = lp.cellY;
592 final int newRow = row + lineDelta;
593 if (0 <= newRow && newRow < cellCountY) {
594 float closestDistance = Float.MAX_VALUE;
595 int closestIndex = -1;
596 int index = views.indexOf(v);
597 int endIndex = (lineDelta < 0) ? -1 : views.size();
598 while (index != endIndex) {
599 View newV = views.get(index);
600 CellLayout.LayoutParams tmpLp = (CellLayout.LayoutParams) newV.getLayoutParams();
601 boolean satisfiesRow = (lineDelta < 0) ? (tmpLp.cellY < row) : (tmpLp.cellY > row);
Adam Cohenac56cff2011-09-28 20:45:37 -0700602 if (satisfiesRow &&
603 (newV instanceof BubbleTextView || newV instanceof FolderIcon)) {
Winson Chung97d85d22011-04-13 11:27:36 -0700604 float tmpDistance = (float) Math.sqrt(Math.pow(tmpLp.cellX - lp.cellX, 2) +
605 Math.pow(tmpLp.cellY - lp.cellY, 2));
606 if (tmpDistance < closestDistance) {
607 closestIndex = index;
608 closestDistance = tmpDistance;
609 }
610 }
611 if (index <= endIndex) {
612 ++index;
613 } else {
614 --index;
615 }
616 }
617 if (closestIndex > -1) {
618 return views.get(closestIndex);
619 }
620 }
621 return null;
622 }
623
624 /**
Adam Cohenac56cff2011-09-28 20:45:37 -0700625 * Handles key events in a Workspace containing.
Winson Chung97d85d22011-04-13 11:27:36 -0700626 */
Adam Cohenac56cff2011-09-28 20:45:37 -0700627 static boolean handleIconKeyEvent(View v, int keyCode, KeyEvent e) {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700628 ShortcutAndWidgetContainer parent = (ShortcutAndWidgetContainer) v.getParent();
Winson Chung97d85d22011-04-13 11:27:36 -0700629 final CellLayout layout = (CellLayout) parent.getParent();
630 final Workspace workspace = (Workspace) layout.getParent();
631 final ViewGroup launcher = (ViewGroup) workspace.getParent();
Adam Cohen24ce0b32014-01-14 16:18:14 -0800632 final ViewGroup tabs = (ViewGroup) launcher.findViewById(R.id.search_drop_target_bar);
Winson Chung4d279d92011-07-21 11:46:32 -0700633 final ViewGroup hotseat = (ViewGroup) launcher.findViewById(R.id.hotseat);
Winson Chung97d85d22011-04-13 11:27:36 -0700634 int pageIndex = workspace.indexOfChild(layout);
635 int pageCount = workspace.getChildCount();
636
637 final int action = e.getAction();
638 final boolean handleKeyEvent = (action != KeyEvent.ACTION_UP);
639 boolean wasHandled = false;
640 switch (keyCode) {
641 case KeyEvent.KEYCODE_DPAD_LEFT:
642 if (handleKeyEvent) {
643 // Select the previous icon or the last icon on the previous 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();
Sunny Goyalb3726d92014-08-20 16:58:17 -0700647 v.playSoundEffect(SoundEffectConstants.NAVIGATION_LEFT);
Winson Chung97d85d22011-04-13 11:27:36 -0700648 } else {
649 if (pageIndex > 0) {
650 parent = getCellLayoutChildrenForIndex(workspace, pageIndex - 1);
Adam Cohenac56cff2011-09-28 20:45:37 -0700651 newIcon = getIconInDirection(layout, parent,
Winson Chung97d85d22011-04-13 11:27:36 -0700652 parent.getChildCount(), -1);
653 if (newIcon != null) {
654 newIcon.requestFocus();
655 } else {
656 // Snap to the previous page
657 workspace.snapToPage(pageIndex - 1);
658 }
Sunny Goyalb3726d92014-08-20 16:58:17 -0700659 v.playSoundEffect(SoundEffectConstants.NAVIGATION_LEFT);
Winson Chung97d85d22011-04-13 11:27:36 -0700660 }
661 }
662 }
663 wasHandled = true;
664 break;
665 case KeyEvent.KEYCODE_DPAD_RIGHT:
666 if (handleKeyEvent) {
667 // Select the next icon or the first icon on the next page if possible
Adam Cohenac56cff2011-09-28 20:45:37 -0700668 View newIcon = getIconInDirection(layout, parent, v, 1);
Winson Chung97d85d22011-04-13 11:27:36 -0700669 if (newIcon != null) {
670 newIcon.requestFocus();
Sunny Goyalb3726d92014-08-20 16:58:17 -0700671 v.playSoundEffect(SoundEffectConstants.NAVIGATION_RIGHT);
Winson Chung97d85d22011-04-13 11:27:36 -0700672 } else {
673 if (pageIndex < (pageCount - 1)) {
674 parent = getCellLayoutChildrenForIndex(workspace, pageIndex + 1);
Adam Cohenac56cff2011-09-28 20:45:37 -0700675 newIcon = getIconInDirection(layout, parent, -1, 1);
Winson Chung97d85d22011-04-13 11:27:36 -0700676 if (newIcon != null) {
677 newIcon.requestFocus();
678 } else {
679 // Snap to the next page
680 workspace.snapToPage(pageIndex + 1);
681 }
Sunny Goyalb3726d92014-08-20 16:58:17 -0700682 v.playSoundEffect(SoundEffectConstants.NAVIGATION_RIGHT);
Winson Chung97d85d22011-04-13 11:27:36 -0700683 }
684 }
685 }
686 wasHandled = true;
687 break;
688 case KeyEvent.KEYCODE_DPAD_UP:
689 if (handleKeyEvent) {
690 // Select the closest icon in the previous line, otherwise select the tab bar
Adam Cohenac56cff2011-09-28 20:45:37 -0700691 View newIcon = getClosestIconOnLine(layout, parent, v, -1);
Winson Chung97d85d22011-04-13 11:27:36 -0700692 if (newIcon != null) {
693 newIcon.requestFocus();
694 wasHandled = true;
695 } else {
696 tabs.requestFocus();
697 }
Sunny Goyalb3726d92014-08-20 16:58:17 -0700698 v.playSoundEffect(SoundEffectConstants.NAVIGATION_UP);
Winson Chung97d85d22011-04-13 11:27:36 -0700699 }
700 break;
701 case KeyEvent.KEYCODE_DPAD_DOWN:
702 if (handleKeyEvent) {
Winson Chung4d279d92011-07-21 11:46:32 -0700703 // Select the closest icon in the next line, otherwise select the button bar
Adam Cohenac56cff2011-09-28 20:45:37 -0700704 View newIcon = getClosestIconOnLine(layout, parent, v, 1);
Winson Chung97d85d22011-04-13 11:27:36 -0700705 if (newIcon != null) {
706 newIcon.requestFocus();
Sunny Goyalb3726d92014-08-20 16:58:17 -0700707 v.playSoundEffect(SoundEffectConstants.NAVIGATION_DOWN);
Winson Chung97d85d22011-04-13 11:27:36 -0700708 wasHandled = true;
Winson Chung4d279d92011-07-21 11:46:32 -0700709 } else if (hotseat != null) {
710 hotseat.requestFocus();
Sunny Goyalb3726d92014-08-20 16:58:17 -0700711 v.playSoundEffect(SoundEffectConstants.NAVIGATION_DOWN);
Winson Chung97d85d22011-04-13 11:27:36 -0700712 }
713 }
714 break;
715 case KeyEvent.KEYCODE_PAGE_UP:
716 if (handleKeyEvent) {
717 // Select the first icon on the previous page or the first icon on this page
718 // if there is no previous page
719 if (pageIndex > 0) {
720 parent = getCellLayoutChildrenForIndex(workspace, pageIndex - 1);
Adam Cohenac56cff2011-09-28 20:45:37 -0700721 View newIcon = getIconInDirection(layout, parent, -1, 1);
Winson Chung97d85d22011-04-13 11:27:36 -0700722 if (newIcon != null) {
723 newIcon.requestFocus();
724 } else {
725 // Snap to the previous page
726 workspace.snapToPage(pageIndex - 1);
727 }
Sunny Goyalb3726d92014-08-20 16:58:17 -0700728 v.playSoundEffect(SoundEffectConstants.NAVIGATION_UP);
Winson Chung97d85d22011-04-13 11:27:36 -0700729 } else {
Adam Cohenac56cff2011-09-28 20:45:37 -0700730 View newIcon = getIconInDirection(layout, parent, -1, 1);
Winson Chung97d85d22011-04-13 11:27:36 -0700731 if (newIcon != null) {
732 newIcon.requestFocus();
Sunny Goyalb3726d92014-08-20 16:58:17 -0700733 v.playSoundEffect(SoundEffectConstants.NAVIGATION_UP);
Winson Chung97d85d22011-04-13 11:27:36 -0700734 }
735 }
736 }
737 wasHandled = true;
738 break;
739 case KeyEvent.KEYCODE_PAGE_DOWN:
740 if (handleKeyEvent) {
741 // Select the first icon on the next page or the last icon on this page
742 // if there is no previous page
743 if (pageIndex < (pageCount - 1)) {
744 parent = getCellLayoutChildrenForIndex(workspace, pageIndex + 1);
Adam Cohenac56cff2011-09-28 20:45:37 -0700745 View newIcon = getIconInDirection(layout, parent, -1, 1);
Winson Chung97d85d22011-04-13 11:27:36 -0700746 if (newIcon != null) {
747 newIcon.requestFocus();
748 } else {
749 // Snap to the next page
750 workspace.snapToPage(pageIndex + 1);
751 }
Sunny Goyalb3726d92014-08-20 16:58:17 -0700752 v.playSoundEffect(SoundEffectConstants.NAVIGATION_DOWN);
Winson Chung97d85d22011-04-13 11:27:36 -0700753 } else {
Adam Cohenac56cff2011-09-28 20:45:37 -0700754 View newIcon = getIconInDirection(layout, parent,
Winson Chung97d85d22011-04-13 11:27:36 -0700755 parent.getChildCount(), -1);
756 if (newIcon != null) {
757 newIcon.requestFocus();
Sunny Goyalb3726d92014-08-20 16:58:17 -0700758 v.playSoundEffect(SoundEffectConstants.NAVIGATION_DOWN);
Winson Chung97d85d22011-04-13 11:27:36 -0700759 }
760 }
761 }
762 wasHandled = true;
763 break;
764 case KeyEvent.KEYCODE_MOVE_HOME:
765 if (handleKeyEvent) {
766 // Select the first icon on this page
Adam Cohenac56cff2011-09-28 20:45:37 -0700767 View newIcon = getIconInDirection(layout, parent, -1, 1);
Winson Chung97d85d22011-04-13 11:27:36 -0700768 if (newIcon != null) {
769 newIcon.requestFocus();
Sunny Goyalb3726d92014-08-20 16:58:17 -0700770 v.playSoundEffect(SoundEffectConstants.NAVIGATION_UP);
Winson Chung97d85d22011-04-13 11:27:36 -0700771 }
772 }
773 wasHandled = true;
774 break;
775 case KeyEvent.KEYCODE_MOVE_END:
776 if (handleKeyEvent) {
777 // Select the last icon on this page
Adam Cohenac56cff2011-09-28 20:45:37 -0700778 View newIcon = getIconInDirection(layout, parent,
779 parent.getChildCount(), -1);
780 if (newIcon != null) {
781 newIcon.requestFocus();
Sunny Goyalb3726d92014-08-20 16:58:17 -0700782 v.playSoundEffect(SoundEffectConstants.NAVIGATION_DOWN);
Adam Cohenac56cff2011-09-28 20:45:37 -0700783 }
784 }
785 wasHandled = true;
786 break;
787 default: break;
788 }
789 return wasHandled;
790 }
791
792 /**
793 * Handles key events for items in a Folder.
794 */
795 static boolean handleFolderKeyEvent(View v, int keyCode, KeyEvent e) {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700796 ShortcutAndWidgetContainer parent = (ShortcutAndWidgetContainer) v.getParent();
Adam Cohenac56cff2011-09-28 20:45:37 -0700797 final CellLayout layout = (CellLayout) parent.getParent();
Winson Chunga1f133d2013-07-25 11:14:30 -0700798 final ScrollView scrollView = (ScrollView) layout.getParent();
799 final Folder folder = (Folder) scrollView.getParent();
Adam Cohenac56cff2011-09-28 20:45:37 -0700800 View title = folder.mFolderName;
801
802 final int action = e.getAction();
803 final boolean handleKeyEvent = (action != KeyEvent.ACTION_UP);
804 boolean wasHandled = false;
805 switch (keyCode) {
806 case KeyEvent.KEYCODE_DPAD_LEFT:
807 if (handleKeyEvent) {
808 // Select the previous icon
809 View newIcon = getIconInDirection(layout, parent, v, -1);
810 if (newIcon != null) {
811 newIcon.requestFocus();
Sunny Goyalb3726d92014-08-20 16:58:17 -0700812 v.playSoundEffect(SoundEffectConstants.NAVIGATION_LEFT);
Adam Cohenac56cff2011-09-28 20:45:37 -0700813 }
814 }
815 wasHandled = true;
816 break;
817 case KeyEvent.KEYCODE_DPAD_RIGHT:
818 if (handleKeyEvent) {
819 // Select the next icon
820 View newIcon = getIconInDirection(layout, parent, v, 1);
821 if (newIcon != null) {
822 newIcon.requestFocus();
823 } else {
824 title.requestFocus();
825 }
Sunny Goyalb3726d92014-08-20 16:58:17 -0700826 v.playSoundEffect(SoundEffectConstants.NAVIGATION_RIGHT);
Adam Cohenac56cff2011-09-28 20:45:37 -0700827 }
828 wasHandled = true;
829 break;
830 case KeyEvent.KEYCODE_DPAD_UP:
831 if (handleKeyEvent) {
832 // Select the closest icon in the previous line
833 View newIcon = getClosestIconOnLine(layout, parent, v, -1);
834 if (newIcon != null) {
835 newIcon.requestFocus();
Sunny Goyalb3726d92014-08-20 16:58:17 -0700836 v.playSoundEffect(SoundEffectConstants.NAVIGATION_UP);
Adam Cohenac56cff2011-09-28 20:45:37 -0700837 }
838 }
839 wasHandled = true;
840 break;
841 case KeyEvent.KEYCODE_DPAD_DOWN:
842 if (handleKeyEvent) {
843 // Select the closest icon in the next line
844 View newIcon = getClosestIconOnLine(layout, parent, v, 1);
845 if (newIcon != null) {
846 newIcon.requestFocus();
847 } else {
848 title.requestFocus();
849 }
Sunny Goyalb3726d92014-08-20 16:58:17 -0700850 v.playSoundEffect(SoundEffectConstants.NAVIGATION_DOWN);
Adam Cohenac56cff2011-09-28 20:45:37 -0700851 }
852 wasHandled = true;
853 break;
854 case KeyEvent.KEYCODE_MOVE_HOME:
855 if (handleKeyEvent) {
856 // Select the first icon on this page
857 View newIcon = getIconInDirection(layout, parent, -1, 1);
858 if (newIcon != null) {
859 newIcon.requestFocus();
Sunny Goyalb3726d92014-08-20 16:58:17 -0700860 v.playSoundEffect(SoundEffectConstants.NAVIGATION_UP);
Adam Cohenac56cff2011-09-28 20:45:37 -0700861 }
862 }
863 wasHandled = true;
864 break;
865 case KeyEvent.KEYCODE_MOVE_END:
866 if (handleKeyEvent) {
867 // Select the last icon on this page
868 View newIcon = getIconInDirection(layout, parent,
Winson Chung97d85d22011-04-13 11:27:36 -0700869 parent.getChildCount(), -1);
870 if (newIcon != null) {
871 newIcon.requestFocus();
Sunny Goyalb3726d92014-08-20 16:58:17 -0700872 v.playSoundEffect(SoundEffectConstants.NAVIGATION_DOWN);
Winson Chung97d85d22011-04-13 11:27:36 -0700873 }
874 }
875 wasHandled = true;
876 break;
877 default: break;
878 }
879 return wasHandled;
880 }
881}