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