FocusHelper.java revision ac56cff1860b71d3f164aedd268703936e08fdc0
1/* 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 17package com.android.launcher2; 18 19import android.content.res.Configuration; 20import android.view.KeyEvent; 21import android.view.View; 22import android.view.ViewGroup; 23import android.view.ViewParent; 24import android.widget.TabHost; 25import android.widget.TabWidget; 26import android.widget.TextView; 27 28import com.android.launcher.R; 29 30import java.util.ArrayList; 31import java.util.Collections; 32import java.util.Comparator; 33 34/** 35 * A keyboard listener we set on all the workspace icons. 36 */ 37class IconKeyEventListener implements View.OnKeyListener { 38 public boolean onKey(View v, int keyCode, KeyEvent event) { 39 return FocusHelper.handleIconKeyEvent(v, keyCode, event); 40 } 41} 42 43/** 44 * A keyboard listener we set on all the workspace icons. 45 */ 46class FolderKeyEventListener implements View.OnKeyListener { 47 public boolean onKey(View v, int keyCode, KeyEvent event) { 48 return FocusHelper.handleFolderKeyEvent(v, keyCode, event); 49 } 50} 51 52/** 53 * A keyboard listener we set on all the hotseat buttons. 54 */ 55class HotseatIconKeyEventListener implements View.OnKeyListener { 56 public boolean onKey(View v, int keyCode, KeyEvent event) { 57 final Configuration configuration = v.getResources().getConfiguration(); 58 return FocusHelper.handleHotseatButtonKeyEvent(v, keyCode, event, configuration.orientation); 59 } 60} 61 62/** 63 * A keyboard listener we set on the last tab button in AppsCustomize to jump to then 64 * market icon and vice versa. 65 */ 66class AppsCustomizeTabKeyEventListener implements View.OnKeyListener { 67 public boolean onKey(View v, int keyCode, KeyEvent event) { 68 return FocusHelper.handleAppsCustomizeTabKeyEvent(v, keyCode, event); 69 } 70} 71 72public class FocusHelper { 73 /** 74 * Private helper to get the parent TabHost in the view hiearchy. 75 */ 76 private static TabHost findTabHostParent(View v) { 77 ViewParent p = v.getParent(); 78 while (p != null && !(p instanceof TabHost)) { 79 p = p.getParent(); 80 } 81 return (TabHost) p; 82 } 83 84 /** 85 * Handles key events in a AppsCustomize tab between the last tab view and the shop button. 86 */ 87 static boolean handleAppsCustomizeTabKeyEvent(View v, int keyCode, KeyEvent e) { 88 final TabHost tabHost = findTabHostParent(v); 89 final ViewGroup contents = (ViewGroup) 90 tabHost.findViewById(com.android.internal.R.id.tabcontent); 91 final View shop = tabHost.findViewById(R.id.market_button); 92 93 final int action = e.getAction(); 94 final boolean handleKeyEvent = (action != KeyEvent.ACTION_UP); 95 boolean wasHandled = false; 96 switch (keyCode) { 97 case KeyEvent.KEYCODE_DPAD_RIGHT: 98 if (handleKeyEvent) { 99 // Select the shop button if we aren't on it 100 if (v != shop) { 101 shop.requestFocus(); 102 } 103 } 104 wasHandled = true; 105 break; 106 case KeyEvent.KEYCODE_DPAD_DOWN: 107 if (handleKeyEvent) { 108 // Select the content view (down is handled by the tab key handler otherwise) 109 if (v == shop) { 110 contents.requestFocus(); 111 wasHandled = true; 112 } 113 } 114 break; 115 default: break; 116 } 117 return wasHandled; 118 } 119 120 /** 121 * Private helper to determine whether a view is visible. 122 */ 123 private static boolean isVisible(View v) { 124 return v.getVisibility() == View.VISIBLE; 125 } 126 127 /** 128 * Handles key events in a PageViewExtendedLayout containing PagedViewWidgets. 129 */ 130 static boolean handlePagedViewGridLayoutWidgetKeyEvent(PagedViewWidget w, int keyCode, 131 KeyEvent e) { 132 133 final PagedViewGridLayout parent = (PagedViewGridLayout) w.getParent(); 134 final ViewGroup container = (ViewGroup) parent.getParent(); 135 final TabHost tabHost = findTabHostParent(container); 136 final TabWidget tabs = (TabWidget) tabHost.findViewById(com.android.internal.R.id.tabs); 137 final int widgetIndex = parent.indexOfChild(w); 138 final int widgetCount = parent.getChildCount(); 139 final int pageIndex = container.indexOfChild(parent); 140 final int pageCount = container.getChildCount(); 141 final int cellCountX = parent.getCellCountX(); 142 final int cellCountY = parent.getCellCountY(); 143 final int x = widgetIndex % cellCountX; 144 final int y = widgetIndex / cellCountX; 145 146 final int action = e.getAction(); 147 final boolean handleKeyEvent = (action != KeyEvent.ACTION_UP); 148 PagedViewGridLayout newParent = null; 149 // Now that we load items in the bg asynchronously, we can't just focus 150 // child siblings willy-nilly 151 View child = null; 152 boolean wasHandled = false; 153 switch (keyCode) { 154 case KeyEvent.KEYCODE_DPAD_LEFT: 155 if (handleKeyEvent) { 156 // Select the previous widget or the last widget on the previous page 157 if (widgetIndex > 0) { 158 parent.getChildAt(widgetIndex - 1).requestFocus(); 159 } else { 160 if (pageIndex > 0) { 161 newParent = (PagedViewGridLayout) 162 container.getChildAt(pageIndex - 1); 163 child = newParent.getChildAt(newParent.getChildCount() - 1); 164 if (child != null) child.requestFocus(); 165 } 166 } 167 } 168 wasHandled = true; 169 break; 170 case KeyEvent.KEYCODE_DPAD_RIGHT: 171 if (handleKeyEvent) { 172 // Select the next widget or the first widget on the next page 173 if (widgetIndex < (widgetCount - 1)) { 174 parent.getChildAt(widgetIndex + 1).requestFocus(); 175 } else { 176 if (pageIndex < (pageCount - 1)) { 177 newParent = (PagedViewGridLayout) 178 container.getChildAt(pageIndex + 1); 179 child = newParent.getChildAt(0); 180 if (child != null) child.requestFocus(); 181 } 182 } 183 } 184 wasHandled = true; 185 break; 186 case KeyEvent.KEYCODE_DPAD_UP: 187 if (handleKeyEvent) { 188 // Select the closest icon in the previous row, otherwise select the tab bar 189 if (y > 0) { 190 int newWidgetIndex = ((y - 1) * cellCountX) + x; 191 child = parent.getChildAt(newWidgetIndex); 192 if (child != null) child.requestFocus(); 193 } else { 194 tabs.requestFocus(); 195 } 196 } 197 wasHandled = true; 198 break; 199 case KeyEvent.KEYCODE_DPAD_DOWN: 200 if (handleKeyEvent) { 201 // Select the closest icon in the previous row, otherwise do nothing 202 if (y < (cellCountY - 1)) { 203 int newWidgetIndex = Math.min(widgetCount - 1, ((y + 1) * cellCountX) + x); 204 child = parent.getChildAt(newWidgetIndex); 205 if (child != null) child.requestFocus(); 206 } 207 } 208 wasHandled = true; 209 break; 210 case KeyEvent.KEYCODE_ENTER: 211 case KeyEvent.KEYCODE_DPAD_CENTER: 212 if (handleKeyEvent) { 213 // Simulate a click on the widget 214 View.OnClickListener clickListener = (View.OnClickListener) container; 215 clickListener.onClick(w); 216 } 217 wasHandled = true; 218 break; 219 case KeyEvent.KEYCODE_PAGE_UP: 220 if (handleKeyEvent) { 221 // Select the first item on the previous page, or the first item on this page 222 // if there is no previous page 223 if (pageIndex > 0) { 224 newParent = (PagedViewGridLayout) container.getChildAt(pageIndex - 1); 225 child = newParent.getChildAt(0); 226 } else { 227 child = parent.getChildAt(0); 228 } 229 if (child != null) child.requestFocus(); 230 } 231 wasHandled = true; 232 break; 233 case KeyEvent.KEYCODE_PAGE_DOWN: 234 if (handleKeyEvent) { 235 // Select the first item on the next page, or the last item on this page 236 // if there is no next page 237 if (pageIndex < (pageCount - 1)) { 238 newParent = (PagedViewGridLayout) container.getChildAt(pageIndex + 1); 239 child = newParent.getChildAt(0); 240 } else { 241 child = parent.getChildAt(widgetCount - 1); 242 } 243 if (child != null) child.requestFocus(); 244 } 245 wasHandled = true; 246 break; 247 case KeyEvent.KEYCODE_MOVE_HOME: 248 if (handleKeyEvent) { 249 // Select the first item on this page 250 child = parent.getChildAt(0); 251 if (child != null) child.requestFocus(); 252 } 253 wasHandled = true; 254 break; 255 case KeyEvent.KEYCODE_MOVE_END: 256 if (handleKeyEvent) { 257 // Select the last item on this page 258 parent.getChildAt(widgetCount - 1).requestFocus(); 259 } 260 wasHandled = true; 261 break; 262 default: break; 263 } 264 return wasHandled; 265 } 266 267 /** 268 * Private helper method to get the PagedViewCellLayoutChildren given a PagedViewCellLayout 269 * index. 270 */ 271 private static PagedViewCellLayoutChildren getPagedViewCellLayoutChildrenForIndex( 272 ViewGroup container, int i) { 273 ViewGroup parent = (ViewGroup) container.getChildAt(i); 274 return (PagedViewCellLayoutChildren) parent.getChildAt(0); 275 } 276 277 /** 278 * Handles key events in a PageViewCellLayout containing PagedViewIcons. 279 */ 280 static boolean handlePagedViewIconKeyEvent(PagedViewIcon v, int keyCode, KeyEvent e) { 281 final PagedViewCellLayoutChildren parent = (PagedViewCellLayoutChildren) v.getParent(); 282 final PagedViewCellLayout parentLayout = (PagedViewCellLayout) parent.getParent(); 283 // Note we have an extra parent because of the 284 // PagedViewCellLayout/PagedViewCellLayoutChildren relationship 285 final ViewGroup container = (ViewGroup) parentLayout.getParent(); 286 final TabHost tabHost = findTabHostParent(container); 287 final TabWidget tabs = (TabWidget) tabHost.findViewById(com.android.internal.R.id.tabs); 288 final int iconIndex = parent.indexOfChild(v); 289 final int widgetCount = parent.getChildCount(); 290 final int pageIndex = container.indexOfChild(parentLayout); 291 final int pageCount = container.getChildCount(); 292 final int cellCountX = parentLayout.getCellCountX(); 293 final int cellCountY = parentLayout.getCellCountY(); 294 final int x = iconIndex % cellCountX; 295 final int y = iconIndex / cellCountX; 296 297 final int action = e.getAction(); 298 final boolean handleKeyEvent = (action != KeyEvent.ACTION_UP); 299 PagedViewCellLayoutChildren newParent = null; 300 // Side pages do not always load synchronously, so check before focusing child siblings 301 // willy-nilly 302 View child = null; 303 boolean wasHandled = false; 304 switch (keyCode) { 305 case KeyEvent.KEYCODE_DPAD_LEFT: 306 if (handleKeyEvent) { 307 // Select the previous icon or the last icon on the previous page 308 if (iconIndex > 0) { 309 parent.getChildAt(iconIndex - 1).requestFocus(); 310 } else { 311 if (pageIndex > 0) { 312 newParent = getPagedViewCellLayoutChildrenForIndex(container, 313 pageIndex - 1); 314 if (newParent != null) { 315 child = newParent.getChildAt(newParent.getChildCount() - 1); 316 if (child != null) child.requestFocus(); 317 } 318 } 319 } 320 } 321 wasHandled = true; 322 break; 323 case KeyEvent.KEYCODE_DPAD_RIGHT: 324 if (handleKeyEvent) { 325 // Select the next icon or the first icon on the next page 326 if (iconIndex < (widgetCount - 1)) { 327 parent.getChildAt(iconIndex + 1).requestFocus(); 328 } else { 329 if (pageIndex < (pageCount - 1)) { 330 newParent = getPagedViewCellLayoutChildrenForIndex(container, 331 pageIndex + 1); 332 if (newParent != null) { 333 child = newParent.getChildAt(0); 334 if (child != null) child.requestFocus(); 335 } 336 } 337 } 338 } 339 wasHandled = true; 340 break; 341 case KeyEvent.KEYCODE_DPAD_UP: 342 if (handleKeyEvent) { 343 // Select the closest icon in the previous row, otherwise select the tab bar 344 if (y > 0) { 345 int newiconIndex = ((y - 1) * cellCountX) + x; 346 parent.getChildAt(newiconIndex).requestFocus(); 347 } else { 348 tabs.requestFocus(); 349 } 350 } 351 wasHandled = true; 352 break; 353 case KeyEvent.KEYCODE_DPAD_DOWN: 354 if (handleKeyEvent) { 355 // Select the closest icon in the previous row, otherwise do nothing 356 if (y < (cellCountY - 1)) { 357 int newiconIndex = Math.min(widgetCount - 1, ((y + 1) * cellCountX) + x); 358 parent.getChildAt(newiconIndex).requestFocus(); 359 } 360 } 361 wasHandled = true; 362 break; 363 case KeyEvent.KEYCODE_ENTER: 364 case KeyEvent.KEYCODE_DPAD_CENTER: 365 if (handleKeyEvent) { 366 // Simulate a click on the icon 367 View.OnClickListener clickListener = (View.OnClickListener) container; 368 clickListener.onClick(v); 369 } 370 wasHandled = true; 371 break; 372 case KeyEvent.KEYCODE_PAGE_UP: 373 if (handleKeyEvent) { 374 // Select the first icon on the previous page, or the first icon on this page 375 // if there is no previous page 376 if (pageIndex > 0) { 377 newParent = getPagedViewCellLayoutChildrenForIndex(container, 378 pageIndex - 1); 379 if (newParent != null) { 380 child = newParent.getChildAt(0); 381 if (child != null) child.requestFocus(); 382 } 383 } else { 384 parent.getChildAt(0).requestFocus(); 385 } 386 } 387 wasHandled = true; 388 break; 389 case KeyEvent.KEYCODE_PAGE_DOWN: 390 if (handleKeyEvent) { 391 // Select the first icon on the next page, or the last icon on this page 392 // if there is no next page 393 if (pageIndex < (pageCount - 1)) { 394 newParent = getPagedViewCellLayoutChildrenForIndex(container, 395 pageIndex + 1); 396 if (newParent != null) { 397 child = newParent.getChildAt(0); 398 if (child != null) child.requestFocus(); 399 } 400 } else { 401 parent.getChildAt(widgetCount - 1).requestFocus(); 402 } 403 } 404 wasHandled = true; 405 break; 406 case KeyEvent.KEYCODE_MOVE_HOME: 407 if (handleKeyEvent) { 408 // Select the first icon on this page 409 parent.getChildAt(0).requestFocus(); 410 } 411 wasHandled = true; 412 break; 413 case KeyEvent.KEYCODE_MOVE_END: 414 if (handleKeyEvent) { 415 // Select the last icon on this page 416 parent.getChildAt(widgetCount - 1).requestFocus(); 417 } 418 wasHandled = true; 419 break; 420 default: break; 421 } 422 return wasHandled; 423 } 424 425 /** 426 * Handles key events in the tab widget. 427 */ 428 static boolean handleTabKeyEvent(AccessibleTabView v, int keyCode, KeyEvent e) { 429 if (!LauncherApplication.isScreenLarge()) return false; 430 431 final FocusOnlyTabWidget parent = (FocusOnlyTabWidget) v.getParent(); 432 final TabHost tabHost = findTabHostParent(parent); 433 final ViewGroup contents = (ViewGroup) 434 tabHost.findViewById(com.android.internal.R.id.tabcontent); 435 final int tabCount = parent.getTabCount(); 436 final int tabIndex = parent.getChildTabIndex(v); 437 438 final int action = e.getAction(); 439 final boolean handleKeyEvent = (action != KeyEvent.ACTION_UP); 440 boolean wasHandled = false; 441 switch (keyCode) { 442 case KeyEvent.KEYCODE_DPAD_LEFT: 443 if (handleKeyEvent) { 444 // Select the previous tab 445 if (tabIndex > 0) { 446 parent.getChildTabViewAt(tabIndex - 1).requestFocus(); 447 } 448 } 449 wasHandled = true; 450 break; 451 case KeyEvent.KEYCODE_DPAD_RIGHT: 452 if (handleKeyEvent) { 453 // Select the next tab, or if the last tab has a focus right id, select that 454 if (tabIndex < (tabCount - 1)) { 455 parent.getChildTabViewAt(tabIndex + 1).requestFocus(); 456 } else { 457 if (v.getNextFocusRightId() != View.NO_ID) { 458 tabHost.findViewById(v.getNextFocusRightId()).requestFocus(); 459 } 460 } 461 } 462 wasHandled = true; 463 break; 464 case KeyEvent.KEYCODE_DPAD_UP: 465 // Do nothing 466 wasHandled = true; 467 break; 468 case KeyEvent.KEYCODE_DPAD_DOWN: 469 if (handleKeyEvent) { 470 // Select the content view 471 contents.requestFocus(); 472 } 473 wasHandled = true; 474 break; 475 default: break; 476 } 477 return wasHandled; 478 } 479 480 /** 481 * Handles key events in the workspace hotseat (bottom of the screen). 482 */ 483 static boolean handleHotseatButtonKeyEvent(View v, int keyCode, KeyEvent e, int orientation) { 484 final ViewGroup parent = (ViewGroup) v.getParent(); 485 final ViewGroup launcher = (ViewGroup) parent.getParent(); 486 final Workspace workspace = (Workspace) launcher.findViewById(R.id.workspace); 487 final int buttonIndex = parent.indexOfChild(v); 488 final int buttonCount = parent.getChildCount(); 489 final int pageIndex = workspace.getCurrentPage(); 490 491 // NOTE: currently we don't special case for the phone UI in different 492 // orientations, even though the hotseat is on the side in landscape mode. This 493 // is to ensure that accessibility consistency is maintained across rotations. 494 495 final int action = e.getAction(); 496 final boolean handleKeyEvent = (action != KeyEvent.ACTION_UP); 497 boolean wasHandled = false; 498 switch (keyCode) { 499 case KeyEvent.KEYCODE_DPAD_LEFT: 500 if (handleKeyEvent) { 501 // Select the previous button, otherwise snap to the previous page 502 if (buttonIndex > 0) { 503 parent.getChildAt(buttonIndex - 1).requestFocus(); 504 } else { 505 workspace.snapToPage(pageIndex - 1); 506 } 507 } 508 wasHandled = true; 509 break; 510 case KeyEvent.KEYCODE_DPAD_RIGHT: 511 if (handleKeyEvent) { 512 // Select the next button, otherwise snap to the next page 513 if (buttonIndex < (buttonCount - 1)) { 514 parent.getChildAt(buttonIndex + 1).requestFocus(); 515 } else { 516 workspace.snapToPage(pageIndex + 1); 517 } 518 } 519 wasHandled = true; 520 break; 521 case KeyEvent.KEYCODE_DPAD_UP: 522 if (handleKeyEvent) { 523 // Select the first bubble text view in the current page of the workspace 524 final CellLayout layout = (CellLayout) workspace.getChildAt(pageIndex); 525 final CellLayoutChildren children = layout.getChildrenLayout(); 526 final View newIcon = getIconInDirection(layout, children, -1, 1); 527 if (newIcon != null) { 528 newIcon.requestFocus(); 529 } else { 530 workspace.requestFocus(); 531 } 532 } 533 wasHandled = true; 534 break; 535 case KeyEvent.KEYCODE_DPAD_DOWN: 536 // Do nothing 537 wasHandled = true; 538 break; 539 default: break; 540 } 541 return wasHandled; 542 } 543 544 /** 545 * Private helper method to get the CellLayoutChildren given a CellLayout index. 546 */ 547 private static CellLayoutChildren getCellLayoutChildrenForIndex(ViewGroup container, int i) { 548 ViewGroup parent = (ViewGroup) container.getChildAt(i); 549 return (CellLayoutChildren) parent.getChildAt(0); 550 } 551 552 /** 553 * Private helper method to sort all the CellLayout children in order of their (x,y) spatially 554 * from top left to bottom right. 555 */ 556 private static ArrayList<View> getCellLayoutChildrenSortedSpatially(CellLayout layout, 557 ViewGroup parent) { 558 // First we order each the CellLayout children by their x,y coordinates 559 final int cellCountX = layout.getCountX(); 560 final int count = parent.getChildCount(); 561 ArrayList<View> views = new ArrayList<View>(); 562 for (int j = 0; j < count; ++j) { 563 views.add(parent.getChildAt(j)); 564 } 565 Collections.sort(views, new Comparator<View>() { 566 @Override 567 public int compare(View lhs, View rhs) { 568 CellLayout.LayoutParams llp = (CellLayout.LayoutParams) lhs.getLayoutParams(); 569 CellLayout.LayoutParams rlp = (CellLayout.LayoutParams) rhs.getLayoutParams(); 570 int lvIndex = (llp.cellY * cellCountX) + llp.cellX; 571 int rvIndex = (rlp.cellY * cellCountX) + rlp.cellX; 572 return lvIndex - rvIndex; 573 } 574 }); 575 return views; 576 } 577 /** 578 * Private helper method to find the index of the next BubbleTextView or FolderIcon in the 579 * direction delta. 580 * 581 * @param delta either -1 or 1 depending on the direction we want to search 582 */ 583 private static View findIndexOfIcon(ArrayList<View> views, int i, int delta) { 584 // Then we find the next BubbleTextView offset by delta from i 585 final int count = views.size(); 586 int newI = i + delta; 587 while (0 <= newI && newI < count) { 588 View newV = views.get(newI); 589 if (newV instanceof BubbleTextView || newV instanceof FolderIcon) { 590 return newV; 591 } 592 newI += delta; 593 } 594 return null; 595 } 596 private static View getIconInDirection(CellLayout layout, ViewGroup parent, int i, 597 int delta) { 598 final ArrayList<View> views = getCellLayoutChildrenSortedSpatially(layout, parent); 599 return findIndexOfIcon(views, i, delta); 600 } 601 private static View getIconInDirection(CellLayout layout, ViewGroup parent, View v, 602 int delta) { 603 final ArrayList<View> views = getCellLayoutChildrenSortedSpatially(layout, parent); 604 return findIndexOfIcon(views, views.indexOf(v), delta); 605 } 606 /** 607 * Private helper method to find the next closest BubbleTextView or FolderIcon in the direction 608 * delta on the next line. 609 * 610 * @param delta either -1 or 1 depending on the line and direction we want to search 611 */ 612 private static View getClosestIconOnLine(CellLayout layout, ViewGroup parent, View v, 613 int lineDelta) { 614 final ArrayList<View> views = getCellLayoutChildrenSortedSpatially(layout, parent); 615 final CellLayout.LayoutParams lp = (CellLayout.LayoutParams) v.getLayoutParams(); 616 final int cellCountX = layout.getCountX(); 617 final int cellCountY = layout.getCountY(); 618 final int row = lp.cellY; 619 final int newRow = row + lineDelta; 620 if (0 <= newRow && newRow < cellCountY) { 621 float closestDistance = Float.MAX_VALUE; 622 int closestIndex = -1; 623 int index = views.indexOf(v); 624 int endIndex = (lineDelta < 0) ? -1 : views.size(); 625 while (index != endIndex) { 626 View newV = views.get(index); 627 CellLayout.LayoutParams tmpLp = (CellLayout.LayoutParams) newV.getLayoutParams(); 628 boolean satisfiesRow = (lineDelta < 0) ? (tmpLp.cellY < row) : (tmpLp.cellY > row); 629 if (satisfiesRow && 630 (newV instanceof BubbleTextView || newV instanceof FolderIcon)) { 631 float tmpDistance = (float) Math.sqrt(Math.pow(tmpLp.cellX - lp.cellX, 2) + 632 Math.pow(tmpLp.cellY - lp.cellY, 2)); 633 if (tmpDistance < closestDistance) { 634 closestIndex = index; 635 closestDistance = tmpDistance; 636 } 637 } 638 if (index <= endIndex) { 639 ++index; 640 } else { 641 --index; 642 } 643 } 644 if (closestIndex > -1) { 645 return views.get(closestIndex); 646 } 647 } 648 return null; 649 } 650 651 /** 652 * Handles key events in a Workspace containing. 653 */ 654 static boolean handleIconKeyEvent(View v, int keyCode, KeyEvent e) { 655 CellLayoutChildren parent = (CellLayoutChildren) v.getParent(); 656 final CellLayout layout = (CellLayout) parent.getParent(); 657 final Workspace workspace = (Workspace) layout.getParent(); 658 final ViewGroup launcher = (ViewGroup) workspace.getParent(); 659 final ViewGroup tabs = (ViewGroup) launcher.findViewById(R.id.qsb_bar); 660 final ViewGroup hotseat = (ViewGroup) launcher.findViewById(R.id.hotseat); 661 int pageIndex = workspace.indexOfChild(layout); 662 int pageCount = workspace.getChildCount(); 663 664 final int action = e.getAction(); 665 final boolean handleKeyEvent = (action != KeyEvent.ACTION_UP); 666 boolean wasHandled = false; 667 switch (keyCode) { 668 case KeyEvent.KEYCODE_DPAD_LEFT: 669 if (handleKeyEvent) { 670 // Select the previous icon or the last icon on the previous page if possible 671 View newIcon = getIconInDirection(layout, parent, v, -1); 672 if (newIcon != null) { 673 newIcon.requestFocus(); 674 } else { 675 if (pageIndex > 0) { 676 parent = getCellLayoutChildrenForIndex(workspace, pageIndex - 1); 677 newIcon = getIconInDirection(layout, parent, 678 parent.getChildCount(), -1); 679 if (newIcon != null) { 680 newIcon.requestFocus(); 681 } else { 682 // Snap to the previous page 683 workspace.snapToPage(pageIndex - 1); 684 } 685 } 686 } 687 } 688 wasHandled = true; 689 break; 690 case KeyEvent.KEYCODE_DPAD_RIGHT: 691 if (handleKeyEvent) { 692 // Select the next icon or the first icon on the next page if possible 693 View newIcon = getIconInDirection(layout, parent, v, 1); 694 if (newIcon != null) { 695 newIcon.requestFocus(); 696 } else { 697 if (pageIndex < (pageCount - 1)) { 698 parent = getCellLayoutChildrenForIndex(workspace, pageIndex + 1); 699 newIcon = getIconInDirection(layout, parent, -1, 1); 700 if (newIcon != null) { 701 newIcon.requestFocus(); 702 } else { 703 // Snap to the next page 704 workspace.snapToPage(pageIndex + 1); 705 } 706 } 707 } 708 } 709 wasHandled = true; 710 break; 711 case KeyEvent.KEYCODE_DPAD_UP: 712 if (handleKeyEvent) { 713 // Select the closest icon in the previous line, otherwise select the tab bar 714 View newIcon = getClosestIconOnLine(layout, parent, v, -1); 715 if (newIcon != null) { 716 newIcon.requestFocus(); 717 wasHandled = true; 718 } else { 719 tabs.requestFocus(); 720 } 721 } 722 break; 723 case KeyEvent.KEYCODE_DPAD_DOWN: 724 if (handleKeyEvent) { 725 // Select the closest icon in the next line, otherwise select the button bar 726 View newIcon = getClosestIconOnLine(layout, parent, v, 1); 727 if (newIcon != null) { 728 newIcon.requestFocus(); 729 wasHandled = true; 730 } else if (hotseat != null) { 731 hotseat.requestFocus(); 732 } 733 } 734 break; 735 case KeyEvent.KEYCODE_PAGE_UP: 736 if (handleKeyEvent) { 737 // Select the first icon on the previous page or the first icon on this page 738 // if there is no previous page 739 if (pageIndex > 0) { 740 parent = getCellLayoutChildrenForIndex(workspace, pageIndex - 1); 741 View newIcon = getIconInDirection(layout, parent, -1, 1); 742 if (newIcon != null) { 743 newIcon.requestFocus(); 744 } else { 745 // Snap to the previous page 746 workspace.snapToPage(pageIndex - 1); 747 } 748 } else { 749 View newIcon = getIconInDirection(layout, parent, -1, 1); 750 if (newIcon != null) { 751 newIcon.requestFocus(); 752 } 753 } 754 } 755 wasHandled = true; 756 break; 757 case KeyEvent.KEYCODE_PAGE_DOWN: 758 if (handleKeyEvent) { 759 // Select the first icon on the next page or the last icon on this page 760 // if there is no previous page 761 if (pageIndex < (pageCount - 1)) { 762 parent = getCellLayoutChildrenForIndex(workspace, pageIndex + 1); 763 View newIcon = getIconInDirection(layout, parent, -1, 1); 764 if (newIcon != null) { 765 newIcon.requestFocus(); 766 } else { 767 // Snap to the next page 768 workspace.snapToPage(pageIndex + 1); 769 } 770 } else { 771 View newIcon = getIconInDirection(layout, parent, 772 parent.getChildCount(), -1); 773 if (newIcon != null) { 774 newIcon.requestFocus(); 775 } 776 } 777 } 778 wasHandled = true; 779 break; 780 case KeyEvent.KEYCODE_MOVE_HOME: 781 if (handleKeyEvent) { 782 // Select the first icon on this page 783 View newIcon = getIconInDirection(layout, parent, -1, 1); 784 if (newIcon != null) { 785 newIcon.requestFocus(); 786 } 787 } 788 wasHandled = true; 789 break; 790 case KeyEvent.KEYCODE_MOVE_END: 791 if (handleKeyEvent) { 792 // Select the last icon on this page 793 View newIcon = getIconInDirection(layout, parent, 794 parent.getChildCount(), -1); 795 if (newIcon != null) { 796 newIcon.requestFocus(); 797 } 798 } 799 wasHandled = true; 800 break; 801 default: break; 802 } 803 return wasHandled; 804 } 805 806 /** 807 * Handles key events for items in a Folder. 808 */ 809 static boolean handleFolderKeyEvent(View v, int keyCode, KeyEvent e) { 810 CellLayoutChildren parent = (CellLayoutChildren) v.getParent(); 811 final CellLayout layout = (CellLayout) parent.getParent(); 812 final Folder folder = (Folder) layout.getParent(); 813 View title = folder.mFolderName; 814 815 final int action = e.getAction(); 816 final boolean handleKeyEvent = (action != KeyEvent.ACTION_UP); 817 boolean wasHandled = false; 818 switch (keyCode) { 819 case KeyEvent.KEYCODE_DPAD_LEFT: 820 if (handleKeyEvent) { 821 // Select the previous icon 822 View newIcon = getIconInDirection(layout, parent, v, -1); 823 if (newIcon != null) { 824 newIcon.requestFocus(); 825 } 826 } 827 wasHandled = true; 828 break; 829 case KeyEvent.KEYCODE_DPAD_RIGHT: 830 if (handleKeyEvent) { 831 // Select the next icon 832 View newIcon = getIconInDirection(layout, parent, v, 1); 833 if (newIcon != null) { 834 newIcon.requestFocus(); 835 } else { 836 title.requestFocus(); 837 } 838 } 839 wasHandled = true; 840 break; 841 case KeyEvent.KEYCODE_DPAD_UP: 842 if (handleKeyEvent) { 843 // Select the closest icon in the previous line 844 View newIcon = getClosestIconOnLine(layout, parent, v, -1); 845 if (newIcon != null) { 846 newIcon.requestFocus(); 847 } 848 } 849 wasHandled = true; 850 break; 851 case KeyEvent.KEYCODE_DPAD_DOWN: 852 if (handleKeyEvent) { 853 // Select the closest icon in the next line 854 View newIcon = getClosestIconOnLine(layout, parent, v, 1); 855 if (newIcon != null) { 856 newIcon.requestFocus(); 857 } else { 858 title.requestFocus(); 859 } 860 } 861 wasHandled = true; 862 break; 863 case KeyEvent.KEYCODE_MOVE_HOME: 864 if (handleKeyEvent) { 865 // Select the first icon on this page 866 View newIcon = getIconInDirection(layout, parent, -1, 1); 867 if (newIcon != null) { 868 newIcon.requestFocus(); 869 } 870 } 871 wasHandled = true; 872 break; 873 case KeyEvent.KEYCODE_MOVE_END: 874 if (handleKeyEvent) { 875 // Select the last icon on this page 876 View newIcon = getIconInDirection(layout, parent, 877 parent.getChildCount(), -1); 878 if (newIcon != null) { 879 newIcon.requestFocus(); 880 } 881 } 882 wasHandled = true; 883 break; 884 default: break; 885 } 886 return wasHandled; 887 } 888} 889