1 /* 2 * Copyright (C) 2015 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 17 package com.android.launcher3; 18 19 import android.util.Log; 20 import android.view.KeyEvent; 21 import android.view.SoundEffectConstants; 22 import android.view.View; 23 import android.view.ViewGroup; 24 25 import com.android.launcher3.config.FeatureFlags; 26 import com.android.launcher3.folder.Folder; 27 import com.android.launcher3.folder.FolderPagedView; 28 import com.android.launcher3.util.FocusLogic; 29 import com.android.launcher3.util.Thunk; 30 31 /** 32 * A keyboard listener we set on all the workspace icons. 33 */ 34 class IconKeyEventListener implements View.OnKeyListener { 35 @Override 36 public boolean onKey(View v, int keyCode, KeyEvent event) { 37 return FocusHelper.handleIconKeyEvent(v, keyCode, event); 38 } 39 } 40 41 /** 42 * A keyboard listener we set on all the hotseat buttons. 43 */ 44 class HotseatIconKeyEventListener implements View.OnKeyListener { 45 @Override 46 public boolean onKey(View v, int keyCode, KeyEvent event) { 47 return FocusHelper.handleHotseatButtonKeyEvent(v, keyCode, event); 48 } 49 } 50 51 /** 52 * A keyboard listener we set on full screen pages (e.g. custom content). 53 */ 54 class FullscreenKeyEventListener implements View.OnKeyListener { 55 @Override 56 public boolean onKey(View v, int keyCode, KeyEvent event) { 57 if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT || keyCode == KeyEvent.KEYCODE_DPAD_RIGHT 58 || keyCode == KeyEvent.KEYCODE_PAGE_DOWN || keyCode == KeyEvent.KEYCODE_PAGE_UP) { 59 // Handle the key event just like a workspace icon would in these cases. In this case, 60 // it will basically act as if there is a single icon in the top left (so you could 61 // think of the fullscreen page as a focusable fullscreen widget). 62 return FocusHelper.handleIconKeyEvent(v, keyCode, event); 63 } 64 return false; 65 } 66 } 67 68 public class FocusHelper { 69 70 private static final String TAG = "FocusHelper"; 71 private static final boolean DEBUG = false; 72 73 /** 74 * Handles key events in paged folder. 75 */ 76 public static class PagedFolderKeyEventListener implements View.OnKeyListener { 77 78 private final Folder mFolder; 79 80 public PagedFolderKeyEventListener(Folder folder) { 81 mFolder = folder; 82 } 83 84 @Override 85 public boolean onKey(View v, int keyCode, KeyEvent e) { 86 boolean consume = FocusLogic.shouldConsume(keyCode); 87 if (e.getAction() == KeyEvent.ACTION_UP) { 88 return consume; 89 } 90 if (DEBUG) { 91 Log.v(TAG, String.format("Handle ALL Folders keyevent=[%s].", 92 KeyEvent.keyCodeToString(keyCode))); 93 } 94 95 if (!(v.getParent() instanceof ShortcutAndWidgetContainer)) { 96 if (FeatureFlags.IS_DOGFOOD_BUILD) { 97 throw new IllegalStateException("Parent of the focused item is not supported."); 98 } else { 99 return false; 100 } 101 } 102 103 // Initialize variables. 104 final ShortcutAndWidgetContainer itemContainer = (ShortcutAndWidgetContainer) v.getParent(); 105 final CellLayout cellLayout = (CellLayout) itemContainer.getParent(); 106 107 final int iconIndex = itemContainer.indexOfChild(v); 108 final FolderPagedView pagedView = (FolderPagedView) cellLayout.getParent(); 109 110 final int pageIndex = pagedView.indexOfChild(cellLayout); 111 final int pageCount = pagedView.getPageCount(); 112 final boolean isLayoutRtl = Utilities.isRtl(v.getResources()); 113 114 int[][] matrix = FocusLogic.createSparseMatrix(cellLayout); 115 // Process focus. 116 int newIconIndex = FocusLogic.handleKeyEvent(keyCode, matrix, iconIndex, pageIndex, 117 pageCount, isLayoutRtl); 118 if (newIconIndex == FocusLogic.NOOP) { 119 handleNoopKey(keyCode, v); 120 return consume; 121 } 122 ShortcutAndWidgetContainer newParent = null; 123 View child = null; 124 125 switch (newIconIndex) { 126 case FocusLogic.PREVIOUS_PAGE_RIGHT_COLUMN: 127 case FocusLogic.PREVIOUS_PAGE_LEFT_COLUMN: 128 newParent = getCellLayoutChildrenForIndex(pagedView, pageIndex - 1); 129 if (newParent != null) { 130 int row = ((CellLayout.LayoutParams) v.getLayoutParams()).cellY; 131 pagedView.snapToPage(pageIndex - 1); 132 child = newParent.getChildAt( 133 ((newIconIndex == FocusLogic.PREVIOUS_PAGE_LEFT_COLUMN) 134 ^ newParent.invertLayoutHorizontally()) ? 0 : matrix.length - 1, 135 row); 136 } 137 break; 138 case FocusLogic.PREVIOUS_PAGE_FIRST_ITEM: 139 newParent = getCellLayoutChildrenForIndex(pagedView, pageIndex - 1); 140 if (newParent != null) { 141 pagedView.snapToPage(pageIndex - 1); 142 child = newParent.getChildAt(0, 0); 143 } 144 break; 145 case FocusLogic.PREVIOUS_PAGE_LAST_ITEM: 146 newParent = getCellLayoutChildrenForIndex(pagedView, pageIndex - 1); 147 if (newParent != null) { 148 pagedView.snapToPage(pageIndex - 1); 149 child = newParent.getChildAt(matrix.length - 1, matrix[0].length - 1); 150 } 151 break; 152 case FocusLogic.NEXT_PAGE_FIRST_ITEM: 153 newParent = getCellLayoutChildrenForIndex(pagedView, pageIndex + 1); 154 if (newParent != null) { 155 pagedView.snapToPage(pageIndex + 1); 156 child = newParent.getChildAt(0, 0); 157 } 158 break; 159 case FocusLogic.NEXT_PAGE_LEFT_COLUMN: 160 case FocusLogic.NEXT_PAGE_RIGHT_COLUMN: 161 newParent = getCellLayoutChildrenForIndex(pagedView, pageIndex + 1); 162 if (newParent != null) { 163 pagedView.snapToPage(pageIndex + 1); 164 child = FocusLogic.getAdjacentChildInNextFolderPage( 165 newParent, v, newIconIndex); 166 } 167 break; 168 case FocusLogic.CURRENT_PAGE_FIRST_ITEM: 169 child = cellLayout.getChildAt(0, 0); 170 break; 171 case FocusLogic.CURRENT_PAGE_LAST_ITEM: 172 child = pagedView.getLastItem(); 173 break; 174 default: // Go to some item on the current page. 175 child = itemContainer.getChildAt(newIconIndex); 176 break; 177 } 178 if (child != null) { 179 child.requestFocus(); 180 playSoundEffect(keyCode, v); 181 } else { 182 handleNoopKey(keyCode, v); 183 } 184 return consume; 185 } 186 187 public void handleNoopKey(int keyCode, View v) { 188 if (keyCode == KeyEvent.KEYCODE_DPAD_DOWN) { 189 mFolder.mFolderName.requestFocus(); 190 playSoundEffect(keyCode, v); 191 } 192 } 193 } 194 195 /** 196 * Handles key events in the workspace hotseat (bottom of the screen). 197 * <p>Currently we don't special case for the phone UI in different orientations, even though 198 * the hotseat is on the side in landscape mode. This is to ensure that accessibility 199 * consistency is maintained across rotations. 200 */ 201 static boolean handleHotseatButtonKeyEvent(View v, int keyCode, KeyEvent e) { 202 boolean consume = FocusLogic.shouldConsume(keyCode); 203 if (e.getAction() == KeyEvent.ACTION_UP || !consume) { 204 return consume; 205 } 206 207 final Launcher launcher = Launcher.getLauncher(v.getContext()); 208 final DeviceProfile profile = launcher.getDeviceProfile(); 209 210 if (DEBUG) { 211 Log.v(TAG, String.format( 212 "Handle HOTSEAT BUTTONS keyevent=[%s] on hotseat buttons, isVertical=%s", 213 KeyEvent.keyCodeToString(keyCode), profile.isVerticalBarLayout())); 214 } 215 216 // Initialize the variables. 217 final Workspace workspace = (Workspace) v.getRootView().findViewById(R.id.workspace); 218 final ShortcutAndWidgetContainer hotseatParent = (ShortcutAndWidgetContainer) v.getParent(); 219 final CellLayout hotseatLayout = (CellLayout) hotseatParent.getParent(); 220 221 final ItemInfo itemInfo = (ItemInfo) v.getTag(); 222 int pageIndex = workspace.getNextPage(); 223 int pageCount = workspace.getChildCount(); 224 int iconIndex = hotseatParent.indexOfChild(v); 225 int iconRank = ((CellLayout.LayoutParams) hotseatLayout.getShortcutsAndWidgets() 226 .getChildAt(iconIndex).getLayoutParams()).cellX; 227 228 final CellLayout iconLayout = (CellLayout) workspace.getChildAt(pageIndex); 229 if (iconLayout == null) { 230 // This check is to guard against cases where key strokes rushes in when workspace 231 // child creation/deletion is still in flux. (e.g., during drop or fling 232 // animation.) 233 return consume; 234 } 235 final ViewGroup iconParent = iconLayout.getShortcutsAndWidgets(); 236 237 ViewGroup parent = null; 238 int[][] matrix = null; 239 240 if (keyCode == KeyEvent.KEYCODE_DPAD_UP && 241 !profile.isVerticalBarLayout()) { 242 matrix = FocusLogic.createSparseMatrixWithHotseat(iconLayout, hotseatLayout, profile); 243 iconIndex += iconParent.getChildCount(); 244 parent = iconParent; 245 } else if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT && 246 profile.isVerticalBarLayout()) { 247 matrix = FocusLogic.createSparseMatrixWithHotseat(iconLayout, hotseatLayout, profile); 248 iconIndex += iconParent.getChildCount(); 249 parent = iconParent; 250 } else if (keyCode == KeyEvent.KEYCODE_DPAD_RIGHT && 251 profile.isVerticalBarLayout()) { 252 keyCode = KeyEvent.KEYCODE_PAGE_DOWN; 253 } else { 254 // For other KEYCODE_DPAD_LEFT and KEYCODE_DPAD_RIGHT navigation, do not use the 255 // matrix extended with hotseat. 256 matrix = FocusLogic.createSparseMatrix(hotseatLayout); 257 parent = hotseatParent; 258 } 259 260 // Process the focus. 261 int newIconIndex = FocusLogic.handleKeyEvent(keyCode, matrix, iconIndex, pageIndex, 262 pageCount, Utilities.isRtl(v.getResources())); 263 264 View newIcon = null; 265 switch (newIconIndex) { 266 case FocusLogic.NEXT_PAGE_FIRST_ITEM: 267 parent = getCellLayoutChildrenForIndex(workspace, pageIndex + 1); 268 newIcon = parent.getChildAt(0); 269 // TODO(hyunyoungs): handle cases where the child is not an icon but 270 // a folder or a widget. 271 workspace.snapToPage(pageIndex + 1); 272 break; 273 case FocusLogic.PREVIOUS_PAGE_FIRST_ITEM: 274 parent = getCellLayoutChildrenForIndex(workspace, pageIndex - 1); 275 newIcon = parent.getChildAt(0); 276 // TODO(hyunyoungs): handle cases where the child is not an icon but 277 // a folder or a widget. 278 workspace.snapToPage(pageIndex - 1); 279 break; 280 case FocusLogic.PREVIOUS_PAGE_LAST_ITEM: 281 parent = getCellLayoutChildrenForIndex(workspace, pageIndex - 1); 282 newIcon = parent.getChildAt(parent.getChildCount() - 1); 283 // TODO(hyunyoungs): handle cases where the child is not an icon but 284 // a folder or a widget. 285 workspace.snapToPage(pageIndex - 1); 286 break; 287 case FocusLogic.PREVIOUS_PAGE_LEFT_COLUMN: 288 case FocusLogic.PREVIOUS_PAGE_RIGHT_COLUMN: 289 // Go to the previous page but keep the focus on the same hotseat icon. 290 workspace.snapToPage(pageIndex - 1); 291 // If the page we are going to is fullscreen, have it take the focus from hotseat. 292 CellLayout prevPage = (CellLayout) workspace.getPageAt(pageIndex - 1); 293 boolean isPrevPageFullscreen = ((CellLayout.LayoutParams) prevPage 294 .getShortcutsAndWidgets().getChildAt(0).getLayoutParams()).isFullscreen; 295 if (isPrevPageFullscreen) { 296 workspace.getPageAt(pageIndex - 1).requestFocus(); 297 } 298 break; 299 case FocusLogic.NEXT_PAGE_LEFT_COLUMN: 300 case FocusLogic.NEXT_PAGE_RIGHT_COLUMN: 301 // Go to the next page but keep the focus on the same hotseat icon. 302 workspace.snapToPage(pageIndex + 1); 303 // If the page we are going to is fullscreen, have it take the focus from hotseat. 304 CellLayout nextPage = (CellLayout) workspace.getPageAt(pageIndex + 1); 305 boolean isNextPageFullscreen = ((CellLayout.LayoutParams) nextPage 306 .getShortcutsAndWidgets().getChildAt(0).getLayoutParams()).isFullscreen; 307 if (isNextPageFullscreen) { 308 workspace.getPageAt(pageIndex + 1).requestFocus(); 309 } 310 break; 311 } 312 if (parent == iconParent && newIconIndex >= iconParent.getChildCount()) { 313 newIconIndex -= iconParent.getChildCount(); 314 } 315 if (parent != null) { 316 if (newIcon == null && newIconIndex >= 0) { 317 newIcon = parent.getChildAt(newIconIndex); 318 } 319 if (newIcon != null) { 320 newIcon.requestFocus(); 321 playSoundEffect(keyCode, v); 322 } 323 } 324 return consume; 325 } 326 327 /** 328 * Handles key events in a workspace containing icons. 329 */ 330 static boolean handleIconKeyEvent(View v, int keyCode, KeyEvent e) { 331 boolean consume = FocusLogic.shouldConsume(keyCode); 332 if (e.getAction() == KeyEvent.ACTION_UP || !consume) { 333 return consume; 334 } 335 336 Launcher launcher = Launcher.getLauncher(v.getContext()); 337 DeviceProfile profile = launcher.getDeviceProfile(); 338 339 if (DEBUG) { 340 Log.v(TAG, String.format("Handle WORKSPACE ICONS keyevent=[%s] isVerticalBar=%s", 341 KeyEvent.keyCodeToString(keyCode), profile.isVerticalBarLayout())); 342 } 343 344 // Initialize the variables. 345 ShortcutAndWidgetContainer parent = (ShortcutAndWidgetContainer) v.getParent(); 346 CellLayout iconLayout = (CellLayout) parent.getParent(); 347 final Workspace workspace = (Workspace) iconLayout.getParent(); 348 final ViewGroup dragLayer = (ViewGroup) workspace.getParent(); 349 final ViewGroup tabs = (ViewGroup) dragLayer.findViewById(R.id.drop_target_bar); 350 final Hotseat hotseat = (Hotseat) dragLayer.findViewById(R.id.hotseat); 351 352 final ItemInfo itemInfo = (ItemInfo) v.getTag(); 353 final int iconIndex = parent.indexOfChild(v); 354 final int pageIndex = workspace.indexOfChild(iconLayout); 355 final int pageCount = workspace.getChildCount(); 356 357 CellLayout hotseatLayout = (CellLayout) hotseat.getChildAt(0); 358 ShortcutAndWidgetContainer hotseatParent = hotseatLayout.getShortcutsAndWidgets(); 359 int[][] matrix; 360 361 // KEYCODE_DPAD_DOWN in portrait (KEYCODE_DPAD_RIGHT in landscape) is the only key allowed 362 // to take a user to the hotseat. For other dpad navigation, do not use the matrix extended 363 // with the hotseat. 364 if (keyCode == KeyEvent.KEYCODE_DPAD_DOWN && !profile.isVerticalBarLayout()) { 365 matrix = FocusLogic.createSparseMatrixWithHotseat(iconLayout, hotseatLayout, profile); 366 } else if (keyCode == KeyEvent.KEYCODE_DPAD_RIGHT && 367 profile.isVerticalBarLayout()) { 368 matrix = FocusLogic.createSparseMatrixWithHotseat(iconLayout, hotseatLayout, profile); 369 } else { 370 matrix = FocusLogic.createSparseMatrix(iconLayout); 371 } 372 373 // Process the focus. 374 int newIconIndex = FocusLogic.handleKeyEvent(keyCode, matrix, iconIndex, pageIndex, 375 pageCount, Utilities.isRtl(v.getResources())); 376 boolean isRtl = Utilities.isRtl(v.getResources()); 377 View newIcon = null; 378 CellLayout workspaceLayout = (CellLayout) workspace.getChildAt(pageIndex); 379 switch (newIconIndex) { 380 case FocusLogic.NOOP: 381 if (keyCode == KeyEvent.KEYCODE_DPAD_UP) { 382 newIcon = tabs; 383 } 384 break; 385 case FocusLogic.PREVIOUS_PAGE_RIGHT_COLUMN: 386 case FocusLogic.NEXT_PAGE_RIGHT_COLUMN: 387 int newPageIndex = pageIndex - 1; 388 if (newIconIndex == FocusLogic.NEXT_PAGE_RIGHT_COLUMN) { 389 newPageIndex = pageIndex + 1; 390 } 391 int row = ((CellLayout.LayoutParams) v.getLayoutParams()).cellY; 392 parent = getCellLayoutChildrenForIndex(workspace, newPageIndex); 393 if (parent != null) { 394 iconLayout = (CellLayout) parent.getParent(); 395 matrix = FocusLogic.createSparseMatrixWithPivotColumn(iconLayout, 396 iconLayout.getCountX(), row); 397 newIconIndex = FocusLogic.handleKeyEvent(keyCode, matrix, FocusLogic.PIVOT, 398 newPageIndex, pageCount, Utilities.isRtl(v.getResources())); 399 if (newIconIndex == FocusLogic.NEXT_PAGE_FIRST_ITEM) { 400 newIcon = handleNextPageFirstItem(workspace, hotseatLayout, pageIndex, 401 isRtl); 402 } else if (newIconIndex == FocusLogic.PREVIOUS_PAGE_LAST_ITEM) { 403 newIcon = handlePreviousPageLastItem(workspace, hotseatLayout, pageIndex, 404 isRtl); 405 } else { 406 newIcon = parent.getChildAt(newIconIndex); 407 } 408 } 409 break; 410 case FocusLogic.PREVIOUS_PAGE_FIRST_ITEM: 411 workspaceLayout = (CellLayout) workspace.getChildAt(pageIndex - 1); 412 newIcon = getFirstFocusableIconInReadingOrder(workspaceLayout, isRtl); 413 if (newIcon == null) { 414 // Check the hotseat if no focusable item was found on the workspace. 415 newIcon = getFirstFocusableIconInReadingOrder(hotseatLayout, isRtl); 416 workspace.snapToPage(pageIndex - 1); 417 } 418 break; 419 case FocusLogic.PREVIOUS_PAGE_LAST_ITEM: 420 newIcon = handlePreviousPageLastItem(workspace, hotseatLayout, pageIndex, isRtl); 421 break; 422 case FocusLogic.NEXT_PAGE_FIRST_ITEM: 423 newIcon = handleNextPageFirstItem(workspace, hotseatLayout, pageIndex, isRtl); 424 break; 425 case FocusLogic.NEXT_PAGE_LEFT_COLUMN: 426 case FocusLogic.PREVIOUS_PAGE_LEFT_COLUMN: 427 newPageIndex = pageIndex + 1; 428 if (newIconIndex == FocusLogic.PREVIOUS_PAGE_LEFT_COLUMN) { 429 newPageIndex = pageIndex - 1; 430 } 431 row = ((CellLayout.LayoutParams) v.getLayoutParams()).cellY; 432 parent = getCellLayoutChildrenForIndex(workspace, newPageIndex); 433 if (parent != null) { 434 iconLayout = (CellLayout) parent.getParent(); 435 matrix = FocusLogic.createSparseMatrixWithPivotColumn(iconLayout, -1, row); 436 newIconIndex = FocusLogic.handleKeyEvent(keyCode, matrix, FocusLogic.PIVOT, 437 newPageIndex, pageCount, Utilities.isRtl(v.getResources())); 438 if (newIconIndex == FocusLogic.NEXT_PAGE_FIRST_ITEM) { 439 newIcon = handleNextPageFirstItem(workspace, hotseatLayout, pageIndex, 440 isRtl); 441 } else if (newIconIndex == FocusLogic.PREVIOUS_PAGE_LAST_ITEM) { 442 newIcon = handlePreviousPageLastItem(workspace, hotseatLayout, pageIndex, 443 isRtl); 444 } else { 445 newIcon = parent.getChildAt(newIconIndex); 446 } 447 } 448 break; 449 case FocusLogic.CURRENT_PAGE_FIRST_ITEM: 450 newIcon = getFirstFocusableIconInReadingOrder(workspaceLayout, isRtl); 451 if (newIcon == null) { 452 // Check the hotseat if no focusable item was found on the workspace. 453 newIcon = getFirstFocusableIconInReadingOrder(hotseatLayout, isRtl); 454 } 455 break; 456 case FocusLogic.CURRENT_PAGE_LAST_ITEM: 457 newIcon = getFirstFocusableIconInReverseReadingOrder(workspaceLayout, isRtl); 458 if (newIcon == null) { 459 // Check the hotseat if no focusable item was found on the workspace. 460 newIcon = getFirstFocusableIconInReverseReadingOrder(hotseatLayout, isRtl); 461 } 462 break; 463 default: 464 // current page, some item. 465 if (0 <= newIconIndex && newIconIndex < parent.getChildCount()) { 466 newIcon = parent.getChildAt(newIconIndex); 467 } else if (parent.getChildCount() <= newIconIndex && 468 newIconIndex < parent.getChildCount() + hotseatParent.getChildCount()) { 469 newIcon = hotseatParent.getChildAt(newIconIndex - parent.getChildCount()); 470 } 471 break; 472 } 473 if (newIcon != null) { 474 newIcon.requestFocus(); 475 playSoundEffect(keyCode, v); 476 } 477 return consume; 478 } 479 480 // 481 // Helper methods. 482 // 483 484 /** 485 * Private helper method to get the CellLayoutChildren given a CellLayout index. 486 */ 487 @Thunk static ShortcutAndWidgetContainer getCellLayoutChildrenForIndex( 488 ViewGroup container, int i) { 489 CellLayout parent = (CellLayout) container.getChildAt(i); 490 return parent.getShortcutsAndWidgets(); 491 } 492 493 /** 494 * Helper method to be used for playing sound effects. 495 */ 496 @Thunk static void playSoundEffect(int keyCode, View v) { 497 switch (keyCode) { 498 case KeyEvent.KEYCODE_DPAD_LEFT: 499 v.playSoundEffect(SoundEffectConstants.NAVIGATION_LEFT); 500 break; 501 case KeyEvent.KEYCODE_DPAD_RIGHT: 502 v.playSoundEffect(SoundEffectConstants.NAVIGATION_RIGHT); 503 break; 504 case KeyEvent.KEYCODE_DPAD_DOWN: 505 case KeyEvent.KEYCODE_PAGE_DOWN: 506 case KeyEvent.KEYCODE_MOVE_END: 507 v.playSoundEffect(SoundEffectConstants.NAVIGATION_DOWN); 508 break; 509 case KeyEvent.KEYCODE_DPAD_UP: 510 case KeyEvent.KEYCODE_PAGE_UP: 511 case KeyEvent.KEYCODE_MOVE_HOME: 512 v.playSoundEffect(SoundEffectConstants.NAVIGATION_UP); 513 break; 514 default: 515 break; 516 } 517 } 518 519 private static View handlePreviousPageLastItem(Workspace workspace, CellLayout hotseatLayout, 520 int pageIndex, boolean isRtl) { 521 if (pageIndex - 1 < 0) { 522 return null; 523 } 524 CellLayout workspaceLayout = (CellLayout) workspace.getChildAt(pageIndex - 1); 525 View newIcon = getFirstFocusableIconInReverseReadingOrder(workspaceLayout, isRtl); 526 if (newIcon == null) { 527 // Check the hotseat if no focusable item was found on the workspace. 528 newIcon = getFirstFocusableIconInReverseReadingOrder(hotseatLayout,isRtl); 529 workspace.snapToPage(pageIndex - 1); 530 } 531 return newIcon; 532 } 533 534 private static View handleNextPageFirstItem(Workspace workspace, CellLayout hotseatLayout, 535 int pageIndex, boolean isRtl) { 536 if (pageIndex + 1 >= workspace.getPageCount()) { 537 return null; 538 } 539 CellLayout workspaceLayout = (CellLayout) workspace.getChildAt(pageIndex + 1); 540 View newIcon = getFirstFocusableIconInReadingOrder(workspaceLayout, isRtl); 541 if (newIcon == null) { 542 // Check the hotseat if no focusable item was found on the workspace. 543 newIcon = getFirstFocusableIconInReadingOrder(hotseatLayout, isRtl); 544 workspace.snapToPage(pageIndex + 1); 545 } 546 return newIcon; 547 } 548 549 private static View getFirstFocusableIconInReadingOrder(CellLayout cellLayout, boolean isRtl) { 550 View icon; 551 int countX = cellLayout.getCountX(); 552 for (int y = 0; y < cellLayout.getCountY(); y++) { 553 int increment = isRtl ? -1 : 1; 554 for (int x = isRtl ? countX - 1 : 0; 0 <= x && x < countX; x += increment) { 555 if ((icon = cellLayout.getChildAt(x, y)) != null && icon.isFocusable()) { 556 return icon; 557 } 558 } 559 } 560 return null; 561 } 562 563 private static View getFirstFocusableIconInReverseReadingOrder(CellLayout cellLayout, 564 boolean isRtl) { 565 View icon; 566 int countX = cellLayout.getCountX(); 567 for (int y = cellLayout.getCountY() - 1; y >= 0; y--) { 568 int increment = isRtl ? 1 : -1; 569 for (int x = isRtl ? 0 : countX - 1; 0 <= x && x < countX; x += increment) { 570 if ((icon = cellLayout.getChildAt(x, y)) != null && icon.isFocusable()) { 571 return icon; 572 } 573 } 574 } 575 return null; 576 } 577 } 578