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