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