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