blob: e6070471884ccd8b5a28bb14a5c4175b5891772b [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;
Sunny Goyalb3726d92014-08-20 16:58:17 -070021import android.view.SoundEffectConstants;
Winson Chung97d85d22011-04-13 11:27:36 -070022import android.view.View;
23import android.view.ViewGroup;
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 {
Winson Chung97d85d22011-04-13 11:27:36 -070059
60 /**
Adam Cohenae4f1552011-10-20 00:15:42 -070061 * Returns the Viewgroup containing page contents for the page at the index specified.
62 */
63 private static ViewGroup getAppsCustomizePage(ViewGroup container, int index) {
64 ViewGroup page = (ViewGroup) ((PagedView) container).getPageAt(index);
Winson Chungc58497e2013-09-03 17:48:37 -070065 if (page instanceof CellLayout) {
Adam Cohenae4f1552011-10-20 00:15:42 -070066 // There are two layers, a PagedViewCellLayout and PagedViewCellLayoutChildren
Winson Chungc58497e2013-09-03 17:48:37 -070067 page = ((CellLayout) page).getShortcutsAndWidgets();
Adam Cohenae4f1552011-10-20 00:15:42 -070068 }
69 return page;
70 }
71
72 /**
Winson Chung97d85d22011-04-13 11:27:36 -070073 * Handles key events in a PageViewCellLayout containing PagedViewIcons.
74 */
Adam Cohenae4f1552011-10-20 00:15:42 -070075 static boolean handleAppsCustomizeKeyEvent(View v, int keyCode, KeyEvent e) {
76 ViewGroup parentLayout;
77 ViewGroup itemContainer;
78 int countX;
79 int countY;
Winson Chungc58497e2013-09-03 17:48:37 -070080 if (v.getParent() instanceof ShortcutAndWidgetContainer) {
Adam Cohenae4f1552011-10-20 00:15:42 -070081 itemContainer = (ViewGroup) v.getParent();
82 parentLayout = (ViewGroup) itemContainer.getParent();
Winson Chungc58497e2013-09-03 17:48:37 -070083 countX = ((CellLayout) parentLayout).getCountX();
84 countY = ((CellLayout) parentLayout).getCountY();
Adam Cohenae4f1552011-10-20 00:15:42 -070085 } else {
86 itemContainer = parentLayout = (ViewGroup) v.getParent();
87 countX = ((PagedViewGridLayout) parentLayout).getCellCountX();
88 countY = ((PagedViewGridLayout) parentLayout).getCellCountY();
89 }
90
Winson Chung97d85d22011-04-13 11:27:36 -070091 // Note we have an extra parent because of the
92 // PagedViewCellLayout/PagedViewCellLayoutChildren relationship
Adam Cohenae4f1552011-10-20 00:15:42 -070093 final PagedView container = (PagedView) parentLayout.getParent();
Adam Cohenae4f1552011-10-20 00:15:42 -070094 final int iconIndex = itemContainer.indexOfChild(v);
95 final int itemCount = itemContainer.getChildCount();
96 final int pageIndex = ((PagedView) container).indexToPage(container.indexOfChild(parentLayout));
Winson Chung97d85d22011-04-13 11:27:36 -070097 final int pageCount = container.getChildCount();
Adam Cohenae4f1552011-10-20 00:15:42 -070098
99 final int x = iconIndex % countX;
100 final int y = iconIndex / countX;
Winson Chung97d85d22011-04-13 11:27:36 -0700101
Sunny Goyalb3726d92014-08-20 16:58:17 -0700102 final int action = e.getAction();
Winson Chung97d85d22011-04-13 11:27:36 -0700103 final boolean handleKeyEvent = (action != KeyEvent.ACTION_UP);
Adam Cohenae4f1552011-10-20 00:15:42 -0700104 ViewGroup newParent = null;
Winson Chung90576b52011-09-27 17:45:27 -0700105 // Side pages do not always load synchronously, so check before focusing child siblings
106 // willy-nilly
107 View child = null;
Winson Chung97d85d22011-04-13 11:27:36 -0700108 boolean wasHandled = false;
109 switch (keyCode) {
110 case KeyEvent.KEYCODE_DPAD_LEFT:
111 if (handleKeyEvent) {
112 // Select the previous icon or the last icon on the previous page
Winson Chung90576b52011-09-27 17:45:27 -0700113 if (iconIndex > 0) {
Adam Cohenae4f1552011-10-20 00:15:42 -0700114 itemContainer.getChildAt(iconIndex - 1).requestFocus();
Sunny Goyalb3726d92014-08-20 16:58:17 -0700115 v.playSoundEffect(SoundEffectConstants.NAVIGATION_LEFT);
Winson Chung97d85d22011-04-13 11:27:36 -0700116 } else {
117 if (pageIndex > 0) {
Adam Cohenae4f1552011-10-20 00:15:42 -0700118 newParent = getAppsCustomizePage(container, pageIndex - 1);
Winson Chung90576b52011-09-27 17:45:27 -0700119 if (newParent != null) {
Adam Cohenae4f1552011-10-20 00:15:42 -0700120 container.snapToPage(pageIndex - 1);
Winson Chung90576b52011-09-27 17:45:27 -0700121 child = newParent.getChildAt(newParent.getChildCount() - 1);
Sunny Goyalb3726d92014-08-20 16:58:17 -0700122 if (child != null) {
123 child.requestFocus();
124 v.playSoundEffect(SoundEffectConstants.NAVIGATION_LEFT);
125 }
Winson Chung90576b52011-09-27 17:45:27 -0700126 }
Winson Chung97d85d22011-04-13 11:27:36 -0700127 }
128 }
129 }
130 wasHandled = true;
131 break;
132 case KeyEvent.KEYCODE_DPAD_RIGHT:
133 if (handleKeyEvent) {
134 // Select the next icon or the first icon on the next page
Adam Cohenae4f1552011-10-20 00:15:42 -0700135 if (iconIndex < (itemCount - 1)) {
136 itemContainer.getChildAt(iconIndex + 1).requestFocus();
Sunny Goyalb3726d92014-08-20 16:58:17 -0700137 v.playSoundEffect(SoundEffectConstants.NAVIGATION_RIGHT);
Winson Chung97d85d22011-04-13 11:27:36 -0700138 } else {
139 if (pageIndex < (pageCount - 1)) {
Adam Cohenae4f1552011-10-20 00:15:42 -0700140 newParent = getAppsCustomizePage(container, pageIndex + 1);
Winson Chung90576b52011-09-27 17:45:27 -0700141 if (newParent != null) {
Adam Cohenae4f1552011-10-20 00:15:42 -0700142 container.snapToPage(pageIndex + 1);
Winson Chung90576b52011-09-27 17:45:27 -0700143 child = newParent.getChildAt(0);
Sunny Goyalb3726d92014-08-20 16:58:17 -0700144 if (child != null) {
145 child.requestFocus();
146 v.playSoundEffect(SoundEffectConstants.NAVIGATION_RIGHT);
147 }
Winson Chung90576b52011-09-27 17:45:27 -0700148 }
Winson Chung97d85d22011-04-13 11:27:36 -0700149 }
150 }
151 }
152 wasHandled = true;
153 break;
154 case KeyEvent.KEYCODE_DPAD_UP:
155 if (handleKeyEvent) {
156 // Select the closest icon in the previous row, otherwise select the tab bar
157 if (y > 0) {
Adam Cohenae4f1552011-10-20 00:15:42 -0700158 int newiconIndex = ((y - 1) * countX) + x;
159 itemContainer.getChildAt(newiconIndex).requestFocus();
Sunny Goyalb3726d92014-08-20 16:58:17 -0700160 v.playSoundEffect(SoundEffectConstants.NAVIGATION_UP);
Winson Chung97d85d22011-04-13 11:27:36 -0700161 }
162 }
163 wasHandled = true;
164 break;
165 case KeyEvent.KEYCODE_DPAD_DOWN:
166 if (handleKeyEvent) {
Sunny Goyalb3726d92014-08-20 16:58:17 -0700167 // Select the closest icon in the next row, otherwise do nothing
Adam Cohenae4f1552011-10-20 00:15:42 -0700168 if (y < (countY - 1)) {
169 int newiconIndex = Math.min(itemCount - 1, ((y + 1) * countX) + x);
Sunny Goyalb3726d92014-08-20 16:58:17 -0700170 int newIconY = newiconIndex / countX;
171 if (newIconY != y) {
172 itemContainer.getChildAt(newiconIndex).requestFocus();
173 v.playSoundEffect(SoundEffectConstants.NAVIGATION_DOWN);
174 }
Winson Chung97d85d22011-04-13 11:27:36 -0700175 }
176 }
177 wasHandled = true;
178 break;
Winson Chung97d85d22011-04-13 11:27:36 -0700179 case KeyEvent.KEYCODE_PAGE_UP:
180 if (handleKeyEvent) {
181 // Select the first icon on the previous page, or the first icon on this page
182 // if there is no previous page
183 if (pageIndex > 0) {
Adam Cohenae4f1552011-10-20 00:15:42 -0700184 newParent = getAppsCustomizePage(container, pageIndex - 1);
Winson Chung90576b52011-09-27 17:45:27 -0700185 if (newParent != null) {
Adam Cohenae4f1552011-10-20 00:15:42 -0700186 container.snapToPage(pageIndex - 1);
Winson Chung90576b52011-09-27 17:45:27 -0700187 child = newParent.getChildAt(0);
Sunny Goyalb3726d92014-08-20 16:58:17 -0700188 if (child != null) {
189 child.requestFocus();
190 v.playSoundEffect(SoundEffectConstants.NAVIGATION_UP);
191 }
Winson Chung90576b52011-09-27 17:45:27 -0700192 }
Winson Chung97d85d22011-04-13 11:27:36 -0700193 } else {
Adam Cohenae4f1552011-10-20 00:15:42 -0700194 itemContainer.getChildAt(0).requestFocus();
Sunny Goyalb3726d92014-08-20 16:58:17 -0700195 v.playSoundEffect(SoundEffectConstants.NAVIGATION_UP);
Winson Chung97d85d22011-04-13 11:27:36 -0700196 }
197 }
198 wasHandled = true;
199 break;
200 case KeyEvent.KEYCODE_PAGE_DOWN:
201 if (handleKeyEvent) {
202 // Select the first icon on the next page, or the last icon on this page
203 // if there is no next page
204 if (pageIndex < (pageCount - 1)) {
Adam Cohenae4f1552011-10-20 00:15:42 -0700205 newParent = getAppsCustomizePage(container, pageIndex + 1);
Winson Chung90576b52011-09-27 17:45:27 -0700206 if (newParent != null) {
Adam Cohenae4f1552011-10-20 00:15:42 -0700207 container.snapToPage(pageIndex + 1);
Winson Chung90576b52011-09-27 17:45:27 -0700208 child = newParent.getChildAt(0);
Sunny Goyalb3726d92014-08-20 16:58:17 -0700209 if (child != null) {
210 child.requestFocus();
211 v.playSoundEffect(SoundEffectConstants.NAVIGATION_DOWN);
212 }
Winson Chung90576b52011-09-27 17:45:27 -0700213 }
Winson Chung97d85d22011-04-13 11:27:36 -0700214 } else {
Adam Cohenae4f1552011-10-20 00:15:42 -0700215 itemContainer.getChildAt(itemCount - 1).requestFocus();
Sunny Goyalb3726d92014-08-20 16:58:17 -0700216 v.playSoundEffect(SoundEffectConstants.NAVIGATION_DOWN);
Winson Chung97d85d22011-04-13 11:27:36 -0700217 }
218 }
219 wasHandled = true;
220 break;
221 case KeyEvent.KEYCODE_MOVE_HOME:
222 if (handleKeyEvent) {
223 // Select the first icon on this page
Adam Cohenae4f1552011-10-20 00:15:42 -0700224 itemContainer.getChildAt(0).requestFocus();
Sunny Goyalb3726d92014-08-20 16:58:17 -0700225 v.playSoundEffect(SoundEffectConstants.NAVIGATION_UP);
Winson Chung97d85d22011-04-13 11:27:36 -0700226 }
227 wasHandled = true;
228 break;
229 case KeyEvent.KEYCODE_MOVE_END:
230 if (handleKeyEvent) {
231 // Select the last icon on this page
Adam Cohenae4f1552011-10-20 00:15:42 -0700232 itemContainer.getChildAt(itemCount - 1).requestFocus();
Sunny Goyalb3726d92014-08-20 16:58:17 -0700233 v.playSoundEffect(SoundEffectConstants.NAVIGATION_DOWN);
Winson Chung97d85d22011-04-13 11:27:36 -0700234 }
235 wasHandled = true;
236 break;
237 default: break;
238 }
239 return wasHandled;
240 }
241
242 /**
Winson Chung3d503fb2011-07-13 17:25:49 -0700243 * Handles key events in the workspace hotseat (bottom of the screen).
Winson Chung4e6a9762011-05-09 11:56:34 -0700244 */
Winson Chung3d503fb2011-07-13 17:25:49 -0700245 static boolean handleHotseatButtonKeyEvent(View v, int keyCode, KeyEvent e, int orientation) {
Sunny Goyalb3726d92014-08-20 16:58:17 -0700246 ShortcutAndWidgetContainer parent = (ShortcutAndWidgetContainer) v.getParent();
247 final CellLayout layout = (CellLayout) parent.getParent();
Winson Chung4e6a9762011-05-09 11:56:34 -0700248
249 // NOTE: currently we don't special case for the phone UI in different
Sunny Goyalb3726d92014-08-20 16:58:17 -0700250 // orientations, even though the hotseat is on the side in landscape mode. This
Winson Chung4e6a9762011-05-09 11:56:34 -0700251 // is to ensure that accessibility consistency is maintained across rotations.
Winson Chung4e6a9762011-05-09 11:56:34 -0700252 final int action = e.getAction();
253 final boolean handleKeyEvent = (action != KeyEvent.ACTION_UP);
254 boolean wasHandled = false;
255 switch (keyCode) {
256 case KeyEvent.KEYCODE_DPAD_LEFT:
257 if (handleKeyEvent) {
Sunny Goyalb3726d92014-08-20 16:58:17 -0700258 ArrayList<View> views = getCellLayoutChildrenSortedSpatially(layout, parent);
259 int myIndex = views.indexOf(v);
260 // Select the previous button, otherwise do nothing
261 if (myIndex > 0) {
262 views.get(myIndex - 1).requestFocus();
263 v.playSoundEffect(SoundEffectConstants.NAVIGATION_LEFT);
Winson Chung4e6a9762011-05-09 11:56:34 -0700264 }
265 }
266 wasHandled = true;
267 break;
268 case KeyEvent.KEYCODE_DPAD_RIGHT:
269 if (handleKeyEvent) {
Sunny Goyalb3726d92014-08-20 16:58:17 -0700270 ArrayList<View> views = getCellLayoutChildrenSortedSpatially(layout, parent);
271 int myIndex = views.indexOf(v);
272 // Select the next button, otherwise do nothing
273 if (myIndex < views.size() - 1) {
274 views.get(myIndex + 1).requestFocus();
275 v.playSoundEffect(SoundEffectConstants.NAVIGATION_RIGHT);
Winson Chung4e6a9762011-05-09 11:56:34 -0700276 }
277 }
278 wasHandled = true;
279 break;
280 case KeyEvent.KEYCODE_DPAD_UP:
281 if (handleKeyEvent) {
Sunny Goyalb3726d92014-08-20 16:58:17 -0700282 final Workspace workspace = (Workspace)
283 v.getRootView().findViewById(R.id.workspace);
284 if (workspace != null) {
285 int pageIndex = workspace.getCurrentPage();
286 CellLayout topLayout = (CellLayout) workspace.getChildAt(pageIndex);
287 ShortcutAndWidgetContainer children = topLayout.getShortcutsAndWidgets();
288 final View newIcon = getIconInDirection(layout, children, -1, 1);
289 // Select the first bubble text view in the current page of the workspace
290 if (newIcon != null) {
291 newIcon.requestFocus();
292 v.playSoundEffect(SoundEffectConstants.NAVIGATION_UP);
293 } else {
294 workspace.requestFocus();
295 }
Winson Chung4e6a9762011-05-09 11:56:34 -0700296 }
297 }
298 wasHandled = true;
299 break;
300 case KeyEvent.KEYCODE_DPAD_DOWN:
301 // Do nothing
302 wasHandled = true;
303 break;
304 default: break;
305 }
306 return wasHandled;
307 }
308
309 /**
Winson Chung97d85d22011-04-13 11:27:36 -0700310 * Private helper method to get the CellLayoutChildren given a CellLayout index.
311 */
Michael Jurkaa52570f2012-03-20 03:18:20 -0700312 private static ShortcutAndWidgetContainer getCellLayoutChildrenForIndex(
313 ViewGroup container, int i) {
Sunny Goyalb3726d92014-08-20 16:58:17 -0700314 CellLayout parent = (CellLayout) container.getChildAt(i);
315 return parent.getShortcutsAndWidgets();
Winson Chung97d85d22011-04-13 11:27:36 -0700316 }
317
318 /**
319 * Private helper method to sort all the CellLayout children in order of their (x,y) spatially
320 * from top left to bottom right.
321 */
322 private static ArrayList<View> getCellLayoutChildrenSortedSpatially(CellLayout layout,
323 ViewGroup parent) {
324 // First we order each the CellLayout children by their x,y coordinates
325 final int cellCountX = layout.getCountX();
326 final int count = parent.getChildCount();
327 ArrayList<View> views = new ArrayList<View>();
328 for (int j = 0; j < count; ++j) {
329 views.add(parent.getChildAt(j));
330 }
331 Collections.sort(views, new Comparator<View>() {
332 @Override
333 public int compare(View lhs, View rhs) {
334 CellLayout.LayoutParams llp = (CellLayout.LayoutParams) lhs.getLayoutParams();
335 CellLayout.LayoutParams rlp = (CellLayout.LayoutParams) rhs.getLayoutParams();
336 int lvIndex = (llp.cellY * cellCountX) + llp.cellX;
337 int rvIndex = (rlp.cellY * cellCountX) + rlp.cellX;
338 return lvIndex - rvIndex;
339 }
340 });
341 return views;
342 }
343 /**
Adam Cohenac56cff2011-09-28 20:45:37 -0700344 * Private helper method to find the index of the next BubbleTextView or FolderIcon in the
345 * direction delta.
346 *
Winson Chung97d85d22011-04-13 11:27:36 -0700347 * @param delta either -1 or 1 depending on the direction we want to search
348 */
Adam Cohenac56cff2011-09-28 20:45:37 -0700349 private static View findIndexOfIcon(ArrayList<View> views, int i, int delta) {
Winson Chung97d85d22011-04-13 11:27:36 -0700350 // Then we find the next BubbleTextView offset by delta from i
351 final int count = views.size();
352 int newI = i + delta;
353 while (0 <= newI && newI < count) {
354 View newV = views.get(newI);
Adam Cohenac56cff2011-09-28 20:45:37 -0700355 if (newV instanceof BubbleTextView || newV instanceof FolderIcon) {
Winson Chung97d85d22011-04-13 11:27:36 -0700356 return newV;
357 }
358 newI += delta;
359 }
360 return null;
361 }
Adam Cohenac56cff2011-09-28 20:45:37 -0700362 private static View getIconInDirection(CellLayout layout, ViewGroup parent, int i,
Winson Chung97d85d22011-04-13 11:27:36 -0700363 int delta) {
364 final ArrayList<View> views = getCellLayoutChildrenSortedSpatially(layout, parent);
Adam Cohenac56cff2011-09-28 20:45:37 -0700365 return findIndexOfIcon(views, i, delta);
Winson Chung97d85d22011-04-13 11:27:36 -0700366 }
Adam Cohenac56cff2011-09-28 20:45:37 -0700367 private static View getIconInDirection(CellLayout layout, ViewGroup parent, View v,
Winson Chung97d85d22011-04-13 11:27:36 -0700368 int delta) {
369 final ArrayList<View> views = getCellLayoutChildrenSortedSpatially(layout, parent);
Adam Cohenac56cff2011-09-28 20:45:37 -0700370 return findIndexOfIcon(views, views.indexOf(v), delta);
Winson Chung97d85d22011-04-13 11:27:36 -0700371 }
372 /**
Adam Cohenac56cff2011-09-28 20:45:37 -0700373 * Private helper method to find the next closest BubbleTextView or FolderIcon in the direction
374 * delta on the next line.
375 *
Winson Chung97d85d22011-04-13 11:27:36 -0700376 * @param delta either -1 or 1 depending on the line and direction we want to search
377 */
Adam Cohenac56cff2011-09-28 20:45:37 -0700378 private static View getClosestIconOnLine(CellLayout layout, ViewGroup parent, View v,
Winson Chung97d85d22011-04-13 11:27:36 -0700379 int lineDelta) {
380 final ArrayList<View> views = getCellLayoutChildrenSortedSpatially(layout, parent);
381 final CellLayout.LayoutParams lp = (CellLayout.LayoutParams) v.getLayoutParams();
Winson Chung97d85d22011-04-13 11:27:36 -0700382 final int cellCountY = layout.getCountY();
383 final int row = lp.cellY;
384 final int newRow = row + lineDelta;
385 if (0 <= newRow && newRow < cellCountY) {
386 float closestDistance = Float.MAX_VALUE;
387 int closestIndex = -1;
388 int index = views.indexOf(v);
389 int endIndex = (lineDelta < 0) ? -1 : views.size();
390 while (index != endIndex) {
391 View newV = views.get(index);
392 CellLayout.LayoutParams tmpLp = (CellLayout.LayoutParams) newV.getLayoutParams();
393 boolean satisfiesRow = (lineDelta < 0) ? (tmpLp.cellY < row) : (tmpLp.cellY > row);
Adam Cohenac56cff2011-09-28 20:45:37 -0700394 if (satisfiesRow &&
395 (newV instanceof BubbleTextView || newV instanceof FolderIcon)) {
Winson Chung97d85d22011-04-13 11:27:36 -0700396 float tmpDistance = (float) Math.sqrt(Math.pow(tmpLp.cellX - lp.cellX, 2) +
397 Math.pow(tmpLp.cellY - lp.cellY, 2));
398 if (tmpDistance < closestDistance) {
399 closestIndex = index;
400 closestDistance = tmpDistance;
401 }
402 }
403 if (index <= endIndex) {
404 ++index;
405 } else {
406 --index;
407 }
408 }
409 if (closestIndex > -1) {
410 return views.get(closestIndex);
411 }
412 }
413 return null;
414 }
415
416 /**
Adam Cohenac56cff2011-09-28 20:45:37 -0700417 * Handles key events in a Workspace containing.
Winson Chung97d85d22011-04-13 11:27:36 -0700418 */
Adam Cohenac56cff2011-09-28 20:45:37 -0700419 static boolean handleIconKeyEvent(View v, int keyCode, KeyEvent e) {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700420 ShortcutAndWidgetContainer parent = (ShortcutAndWidgetContainer) v.getParent();
Winson Chung97d85d22011-04-13 11:27:36 -0700421 final CellLayout layout = (CellLayout) parent.getParent();
422 final Workspace workspace = (Workspace) layout.getParent();
423 final ViewGroup launcher = (ViewGroup) workspace.getParent();
Adam Cohen24ce0b32014-01-14 16:18:14 -0800424 final ViewGroup tabs = (ViewGroup) launcher.findViewById(R.id.search_drop_target_bar);
Winson Chung4d279d92011-07-21 11:46:32 -0700425 final ViewGroup hotseat = (ViewGroup) launcher.findViewById(R.id.hotseat);
Winson Chung97d85d22011-04-13 11:27:36 -0700426 int pageIndex = workspace.indexOfChild(layout);
427 int pageCount = workspace.getChildCount();
428
429 final int action = e.getAction();
430 final boolean handleKeyEvent = (action != KeyEvent.ACTION_UP);
431 boolean wasHandled = false;
432 switch (keyCode) {
433 case KeyEvent.KEYCODE_DPAD_LEFT:
434 if (handleKeyEvent) {
435 // Select the previous icon or the last icon on the previous page if possible
Adam Cohenac56cff2011-09-28 20:45:37 -0700436 View newIcon = getIconInDirection(layout, parent, v, -1);
Winson Chung97d85d22011-04-13 11:27:36 -0700437 if (newIcon != null) {
438 newIcon.requestFocus();
Sunny Goyalb3726d92014-08-20 16:58:17 -0700439 v.playSoundEffect(SoundEffectConstants.NAVIGATION_LEFT);
Winson Chung97d85d22011-04-13 11:27:36 -0700440 } else {
441 if (pageIndex > 0) {
442 parent = getCellLayoutChildrenForIndex(workspace, pageIndex - 1);
Adam Cohenac56cff2011-09-28 20:45:37 -0700443 newIcon = getIconInDirection(layout, parent,
Winson Chung97d85d22011-04-13 11:27:36 -0700444 parent.getChildCount(), -1);
445 if (newIcon != null) {
446 newIcon.requestFocus();
447 } else {
448 // Snap to the previous page
449 workspace.snapToPage(pageIndex - 1);
450 }
Sunny Goyalb3726d92014-08-20 16:58:17 -0700451 v.playSoundEffect(SoundEffectConstants.NAVIGATION_LEFT);
Winson Chung97d85d22011-04-13 11:27:36 -0700452 }
453 }
454 }
455 wasHandled = true;
456 break;
457 case KeyEvent.KEYCODE_DPAD_RIGHT:
458 if (handleKeyEvent) {
459 // Select the next icon or the first icon on the next page if possible
Adam Cohenac56cff2011-09-28 20:45:37 -0700460 View newIcon = getIconInDirection(layout, parent, v, 1);
Winson Chung97d85d22011-04-13 11:27:36 -0700461 if (newIcon != null) {
462 newIcon.requestFocus();
Sunny Goyalb3726d92014-08-20 16:58:17 -0700463 v.playSoundEffect(SoundEffectConstants.NAVIGATION_RIGHT);
Winson Chung97d85d22011-04-13 11:27:36 -0700464 } else {
465 if (pageIndex < (pageCount - 1)) {
466 parent = getCellLayoutChildrenForIndex(workspace, pageIndex + 1);
Adam Cohenac56cff2011-09-28 20:45:37 -0700467 newIcon = getIconInDirection(layout, parent, -1, 1);
Winson Chung97d85d22011-04-13 11:27:36 -0700468 if (newIcon != null) {
469 newIcon.requestFocus();
470 } else {
471 // Snap to the next page
472 workspace.snapToPage(pageIndex + 1);
473 }
Sunny Goyalb3726d92014-08-20 16:58:17 -0700474 v.playSoundEffect(SoundEffectConstants.NAVIGATION_RIGHT);
Winson Chung97d85d22011-04-13 11:27:36 -0700475 }
476 }
477 }
478 wasHandled = true;
479 break;
480 case KeyEvent.KEYCODE_DPAD_UP:
481 if (handleKeyEvent) {
482 // Select the closest icon in the previous line, otherwise select the tab bar
Adam Cohenac56cff2011-09-28 20:45:37 -0700483 View newIcon = getClosestIconOnLine(layout, parent, v, -1);
Winson Chung97d85d22011-04-13 11:27:36 -0700484 if (newIcon != null) {
485 newIcon.requestFocus();
486 wasHandled = true;
487 } else {
488 tabs.requestFocus();
489 }
Sunny Goyalb3726d92014-08-20 16:58:17 -0700490 v.playSoundEffect(SoundEffectConstants.NAVIGATION_UP);
Winson Chung97d85d22011-04-13 11:27:36 -0700491 }
492 break;
493 case KeyEvent.KEYCODE_DPAD_DOWN:
494 if (handleKeyEvent) {
Winson Chung4d279d92011-07-21 11:46:32 -0700495 // Select the closest icon in the next line, otherwise select the button bar
Adam Cohenac56cff2011-09-28 20:45:37 -0700496 View newIcon = getClosestIconOnLine(layout, parent, v, 1);
Winson Chung97d85d22011-04-13 11:27:36 -0700497 if (newIcon != null) {
498 newIcon.requestFocus();
Sunny Goyalb3726d92014-08-20 16:58:17 -0700499 v.playSoundEffect(SoundEffectConstants.NAVIGATION_DOWN);
Winson Chung97d85d22011-04-13 11:27:36 -0700500 wasHandled = true;
Winson Chung4d279d92011-07-21 11:46:32 -0700501 } else if (hotseat != null) {
502 hotseat.requestFocus();
Sunny Goyalb3726d92014-08-20 16:58:17 -0700503 v.playSoundEffect(SoundEffectConstants.NAVIGATION_DOWN);
Winson Chung97d85d22011-04-13 11:27:36 -0700504 }
505 }
506 break;
507 case KeyEvent.KEYCODE_PAGE_UP:
508 if (handleKeyEvent) {
509 // Select the first icon on the previous page or the first icon on this page
510 // if there is no previous page
511 if (pageIndex > 0) {
512 parent = getCellLayoutChildrenForIndex(workspace, pageIndex - 1);
Adam Cohenac56cff2011-09-28 20:45:37 -0700513 View newIcon = getIconInDirection(layout, parent, -1, 1);
Winson Chung97d85d22011-04-13 11:27:36 -0700514 if (newIcon != null) {
515 newIcon.requestFocus();
516 } else {
517 // Snap to the previous page
518 workspace.snapToPage(pageIndex - 1);
519 }
Sunny Goyalb3726d92014-08-20 16:58:17 -0700520 v.playSoundEffect(SoundEffectConstants.NAVIGATION_UP);
Winson Chung97d85d22011-04-13 11:27:36 -0700521 } else {
Adam Cohenac56cff2011-09-28 20:45:37 -0700522 View newIcon = getIconInDirection(layout, parent, -1, 1);
Winson Chung97d85d22011-04-13 11:27:36 -0700523 if (newIcon != null) {
524 newIcon.requestFocus();
Sunny Goyalb3726d92014-08-20 16:58:17 -0700525 v.playSoundEffect(SoundEffectConstants.NAVIGATION_UP);
Winson Chung97d85d22011-04-13 11:27:36 -0700526 }
527 }
528 }
529 wasHandled = true;
530 break;
531 case KeyEvent.KEYCODE_PAGE_DOWN:
532 if (handleKeyEvent) {
533 // Select the first icon on the next page or the last icon on this page
534 // if there is no previous page
535 if (pageIndex < (pageCount - 1)) {
536 parent = getCellLayoutChildrenForIndex(workspace, pageIndex + 1);
Adam Cohenac56cff2011-09-28 20:45:37 -0700537 View newIcon = getIconInDirection(layout, parent, -1, 1);
Winson Chung97d85d22011-04-13 11:27:36 -0700538 if (newIcon != null) {
539 newIcon.requestFocus();
540 } else {
541 // Snap to the next page
542 workspace.snapToPage(pageIndex + 1);
543 }
Sunny Goyalb3726d92014-08-20 16:58:17 -0700544 v.playSoundEffect(SoundEffectConstants.NAVIGATION_DOWN);
Winson Chung97d85d22011-04-13 11:27:36 -0700545 } else {
Adam Cohenac56cff2011-09-28 20:45:37 -0700546 View newIcon = getIconInDirection(layout, parent,
Winson Chung97d85d22011-04-13 11:27:36 -0700547 parent.getChildCount(), -1);
548 if (newIcon != null) {
549 newIcon.requestFocus();
Sunny Goyalb3726d92014-08-20 16:58:17 -0700550 v.playSoundEffect(SoundEffectConstants.NAVIGATION_DOWN);
Winson Chung97d85d22011-04-13 11:27:36 -0700551 }
552 }
553 }
554 wasHandled = true;
555 break;
556 case KeyEvent.KEYCODE_MOVE_HOME:
557 if (handleKeyEvent) {
558 // Select the first icon on this page
Adam Cohenac56cff2011-09-28 20:45:37 -0700559 View newIcon = getIconInDirection(layout, parent, -1, 1);
Winson Chung97d85d22011-04-13 11:27:36 -0700560 if (newIcon != null) {
561 newIcon.requestFocus();
Sunny Goyalb3726d92014-08-20 16:58:17 -0700562 v.playSoundEffect(SoundEffectConstants.NAVIGATION_UP);
Winson Chung97d85d22011-04-13 11:27:36 -0700563 }
564 }
565 wasHandled = true;
566 break;
567 case KeyEvent.KEYCODE_MOVE_END:
568 if (handleKeyEvent) {
569 // Select the last icon on this page
Adam Cohenac56cff2011-09-28 20:45:37 -0700570 View newIcon = getIconInDirection(layout, parent,
571 parent.getChildCount(), -1);
572 if (newIcon != null) {
573 newIcon.requestFocus();
Sunny Goyalb3726d92014-08-20 16:58:17 -0700574 v.playSoundEffect(SoundEffectConstants.NAVIGATION_DOWN);
Adam Cohenac56cff2011-09-28 20:45:37 -0700575 }
576 }
577 wasHandled = true;
578 break;
579 default: break;
580 }
581 return wasHandled;
582 }
583
584 /**
585 * Handles key events for items in a Folder.
586 */
587 static boolean handleFolderKeyEvent(View v, int keyCode, KeyEvent e) {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700588 ShortcutAndWidgetContainer parent = (ShortcutAndWidgetContainer) v.getParent();
Adam Cohenac56cff2011-09-28 20:45:37 -0700589 final CellLayout layout = (CellLayout) parent.getParent();
Winson Chunga1f133d2013-07-25 11:14:30 -0700590 final ScrollView scrollView = (ScrollView) layout.getParent();
591 final Folder folder = (Folder) scrollView.getParent();
Adam Cohenac56cff2011-09-28 20:45:37 -0700592 View title = folder.mFolderName;
593
594 final int action = e.getAction();
595 final boolean handleKeyEvent = (action != KeyEvent.ACTION_UP);
596 boolean wasHandled = false;
597 switch (keyCode) {
598 case KeyEvent.KEYCODE_DPAD_LEFT:
599 if (handleKeyEvent) {
600 // Select the previous icon
601 View newIcon = getIconInDirection(layout, parent, v, -1);
602 if (newIcon != null) {
603 newIcon.requestFocus();
Sunny Goyalb3726d92014-08-20 16:58:17 -0700604 v.playSoundEffect(SoundEffectConstants.NAVIGATION_LEFT);
Adam Cohenac56cff2011-09-28 20:45:37 -0700605 }
606 }
607 wasHandled = true;
608 break;
609 case KeyEvent.KEYCODE_DPAD_RIGHT:
610 if (handleKeyEvent) {
611 // Select the next icon
612 View newIcon = getIconInDirection(layout, parent, v, 1);
613 if (newIcon != null) {
614 newIcon.requestFocus();
615 } else {
616 title.requestFocus();
617 }
Sunny Goyalb3726d92014-08-20 16:58:17 -0700618 v.playSoundEffect(SoundEffectConstants.NAVIGATION_RIGHT);
Adam Cohenac56cff2011-09-28 20:45:37 -0700619 }
620 wasHandled = true;
621 break;
622 case KeyEvent.KEYCODE_DPAD_UP:
623 if (handleKeyEvent) {
624 // Select the closest icon in the previous line
625 View newIcon = getClosestIconOnLine(layout, parent, v, -1);
626 if (newIcon != null) {
627 newIcon.requestFocus();
Sunny Goyalb3726d92014-08-20 16:58:17 -0700628 v.playSoundEffect(SoundEffectConstants.NAVIGATION_UP);
Adam Cohenac56cff2011-09-28 20:45:37 -0700629 }
630 }
631 wasHandled = true;
632 break;
633 case KeyEvent.KEYCODE_DPAD_DOWN:
634 if (handleKeyEvent) {
635 // Select the closest icon in the next line
636 View newIcon = getClosestIconOnLine(layout, parent, v, 1);
637 if (newIcon != null) {
638 newIcon.requestFocus();
639 } else {
640 title.requestFocus();
641 }
Sunny Goyalb3726d92014-08-20 16:58:17 -0700642 v.playSoundEffect(SoundEffectConstants.NAVIGATION_DOWN);
Adam Cohenac56cff2011-09-28 20:45:37 -0700643 }
644 wasHandled = true;
645 break;
646 case KeyEvent.KEYCODE_MOVE_HOME:
647 if (handleKeyEvent) {
648 // Select the first icon on this page
649 View newIcon = getIconInDirection(layout, parent, -1, 1);
650 if (newIcon != null) {
651 newIcon.requestFocus();
Sunny Goyalb3726d92014-08-20 16:58:17 -0700652 v.playSoundEffect(SoundEffectConstants.NAVIGATION_UP);
Adam Cohenac56cff2011-09-28 20:45:37 -0700653 }
654 }
655 wasHandled = true;
656 break;
657 case KeyEvent.KEYCODE_MOVE_END:
658 if (handleKeyEvent) {
659 // Select the last icon on this page
660 View newIcon = getIconInDirection(layout, parent,
Winson Chung97d85d22011-04-13 11:27:36 -0700661 parent.getChildCount(), -1);
662 if (newIcon != null) {
663 newIcon.requestFocus();
Sunny Goyalb3726d92014-08-20 16:58:17 -0700664 v.playSoundEffect(SoundEffectConstants.NAVIGATION_DOWN);
Winson Chung97d85d22011-04-13 11:27:36 -0700665 }
666 }
667 wasHandled = true;
668 break;
669 default: break;
670 }
671 return wasHandled;
672 }
673}