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.util; 18 19 import android.util.Log; 20 import android.view.KeyEvent; 21 import android.view.View; 22 import android.view.ViewGroup; 23 24 import com.android.launcher3.CellLayout; 25 import com.android.launcher3.ShortcutAndWidgetContainer; 26 27 import java.util.Arrays; 28 29 /** 30 * Calculates the next item that a {@link KeyEvent} should change the focus to. 31 *<p> 32 * Note, this utility class calculates everything regards to icon index and its (x,y) coordinates. 33 * Currently supports: 34 * <ul> 35 * <li> full matrix of cells that are 1x1 36 * <li> sparse matrix of cells that are 1x1 37 * [ 1][ ][ 2][ ] 38 * [ ][ ][ 3][ ] 39 * [ ][ 4][ ][ ] 40 * [ ][ 5][ 6][ 7] 41 * </ul> 42 * *<p> 43 * For testing, one can use a BT keyboard, or use following adb command. 44 * ex. $ adb shell input keyevent 20 // KEYCODE_DPAD_LEFT 45 */ 46 public class FocusLogic { 47 48 private static final String TAG = "FocusLogic"; 49 private static final boolean DEBUG = false; 50 51 /** Item and page index related constant used by {@link #handleKeyEvent}. */ 52 public static final int NOOP = -1; 53 54 public static final int PREVIOUS_PAGE_RIGHT_COLUMN = -2; 55 public static final int PREVIOUS_PAGE_FIRST_ITEM = -3; 56 public static final int PREVIOUS_PAGE_LAST_ITEM = -4; 57 public static final int PREVIOUS_PAGE_LEFT_COLUMN = -5; 58 59 public static final int CURRENT_PAGE_FIRST_ITEM = -6; 60 public static final int CURRENT_PAGE_LAST_ITEM = -7; 61 62 public static final int NEXT_PAGE_FIRST_ITEM = -8; 63 public static final int NEXT_PAGE_LEFT_COLUMN = -9; 64 public static final int NEXT_PAGE_RIGHT_COLUMN = -10; 65 66 public static final int ALL_APPS_COLUMN = -11; 67 68 // Matrix related constant. 69 public static final int EMPTY = -1; 70 public static final int PIVOT = 100; 71 72 /** 73 * Returns true only if this utility class handles the key code. 74 */ shouldConsume(int keyCode)75 public static boolean shouldConsume(int keyCode) { 76 return (keyCode == KeyEvent.KEYCODE_DPAD_LEFT || keyCode == KeyEvent.KEYCODE_DPAD_RIGHT || 77 keyCode == KeyEvent.KEYCODE_DPAD_UP || keyCode == KeyEvent.KEYCODE_DPAD_DOWN || 78 keyCode == KeyEvent.KEYCODE_MOVE_HOME || keyCode == KeyEvent.KEYCODE_MOVE_END || 79 keyCode == KeyEvent.KEYCODE_PAGE_UP || keyCode == KeyEvent.KEYCODE_PAGE_DOWN || 80 keyCode == KeyEvent.KEYCODE_DEL || keyCode == KeyEvent.KEYCODE_FORWARD_DEL); 81 } 82 handleKeyEvent(int keyCode, int [][] map, int iconIdx, int pageIndex, int pageCount, boolean isRtl)83 public static int handleKeyEvent(int keyCode, int [][] map, int iconIdx, int pageIndex, 84 int pageCount, boolean isRtl) { 85 86 int cntX = map == null ? -1 : map.length; 87 int cntY = map == null ? -1 : map[0].length; 88 89 if (DEBUG) { 90 Log.v(TAG, String.format( 91 "handleKeyEvent START: cntX=%d, cntY=%d, iconIdx=%d, pageIdx=%d, pageCnt=%d", 92 cntX, cntY, iconIdx, pageIndex, pageCount)); 93 } 94 95 int newIndex = NOOP; 96 switch (keyCode) { 97 case KeyEvent.KEYCODE_DPAD_LEFT: 98 newIndex = handleDpadHorizontal(iconIdx, cntX, cntY, map, -1 /*increment*/, isRtl); 99 if (!isRtl && newIndex == NOOP && pageIndex > 0) { 100 newIndex = PREVIOUS_PAGE_RIGHT_COLUMN; 101 } else if (isRtl && newIndex == NOOP && pageIndex < pageCount - 1) { 102 newIndex = NEXT_PAGE_RIGHT_COLUMN; 103 } 104 break; 105 case KeyEvent.KEYCODE_DPAD_RIGHT: 106 newIndex = handleDpadHorizontal(iconIdx, cntX, cntY, map, 1 /*increment*/, isRtl); 107 if (!isRtl && newIndex == NOOP && pageIndex < pageCount - 1) { 108 newIndex = NEXT_PAGE_LEFT_COLUMN; 109 } else if (isRtl && newIndex == NOOP && pageIndex > 0) { 110 newIndex = PREVIOUS_PAGE_LEFT_COLUMN; 111 } 112 break; 113 case KeyEvent.KEYCODE_DPAD_DOWN: 114 newIndex = handleDpadVertical(iconIdx, cntX, cntY, map, 1 /*increment*/); 115 break; 116 case KeyEvent.KEYCODE_DPAD_UP: 117 newIndex = handleDpadVertical(iconIdx, cntX, cntY, map, -1 /*increment*/); 118 break; 119 case KeyEvent.KEYCODE_MOVE_HOME: 120 newIndex = handleMoveHome(); 121 break; 122 case KeyEvent.KEYCODE_MOVE_END: 123 newIndex = handleMoveEnd(); 124 break; 125 case KeyEvent.KEYCODE_PAGE_DOWN: 126 newIndex = handlePageDown(pageIndex, pageCount); 127 break; 128 case KeyEvent.KEYCODE_PAGE_UP: 129 newIndex = handlePageUp(pageIndex); 130 break; 131 default: 132 break; 133 } 134 135 if (DEBUG) { 136 Log.v(TAG, String.format("handleKeyEvent FINISH: index [%d -> %s]", 137 iconIdx, getStringIndex(newIndex))); 138 } 139 return newIndex; 140 } 141 142 /** 143 * Returns a matrix of size (m x n) that has been initialized with {@link #EMPTY}. 144 * 145 * @param m number of columns in the matrix 146 * @param n number of rows in the matrix 147 */ 148 // TODO: get rid of dynamic matrix creation. createFullMatrix(int m, int n)149 private static int[][] createFullMatrix(int m, int n) { 150 int[][] matrix = new int [m][n]; 151 152 for (int i=0; i < m;i++) { 153 Arrays.fill(matrix[i], EMPTY); 154 } 155 return matrix; 156 } 157 158 /** 159 * Returns a matrix of size same as the {@link CellLayout} dimension that is initialized with the 160 * index of the child view. 161 */ 162 // TODO: get rid of the dynamic matrix creation createSparseMatrix(CellLayout layout)163 public static int[][] createSparseMatrix(CellLayout layout) { 164 ShortcutAndWidgetContainer parent = layout.getShortcutsAndWidgets(); 165 final int m = layout.getCountX(); 166 final int n = layout.getCountY(); 167 final boolean invert = parent.invertLayoutHorizontally(); 168 169 int[][] matrix = createFullMatrix(m, n); 170 171 // Iterate thru the children. 172 for (int i = 0; i < parent.getChildCount(); i++ ) { 173 View cell = parent.getChildAt(i); 174 if (!cell.isFocusable()) { 175 continue; 176 } 177 int cx = ((CellLayout.LayoutParams) cell.getLayoutParams()).cellX; 178 int cy = ((CellLayout.LayoutParams) cell.getLayoutParams()).cellY; 179 matrix[invert ? (m - cx - 1) : cx][cy] = i; 180 } 181 if (DEBUG) { 182 printMatrix(matrix); 183 } 184 return matrix; 185 } 186 187 /** 188 * Creates a sparse matrix that merges the icon and hotseat view group using the cell layout. 189 * The size of the returning matrix is [icon column count x (icon + hotseat row count)] 190 * in portrait orientation. In landscape, [(icon + hotseat) column count x (icon row count)] 191 */ 192 // TODO: get rid of the dynamic matrix creation createSparseMatrixWithHotseat(CellLayout iconLayout, CellLayout hotseatLayout, boolean isHotseatHorizontal, int allappsiconRank)193 public static int[][] createSparseMatrixWithHotseat(CellLayout iconLayout, 194 CellLayout hotseatLayout, boolean isHotseatHorizontal, int allappsiconRank) { 195 196 ViewGroup iconParent = iconLayout.getShortcutsAndWidgets(); 197 ViewGroup hotseatParent = hotseatLayout.getShortcutsAndWidgets(); 198 199 boolean moreIconsInHotseatThanWorkspace = isHotseatHorizontal ? 200 hotseatLayout.getCountX() > iconLayout.getCountX() : 201 hotseatLayout.getCountY() > iconLayout.getCountY(); 202 203 int m, n; 204 if (isHotseatHorizontal) { 205 m = hotseatLayout.getCountX(); 206 n = iconLayout.getCountY() + hotseatLayout.getCountY(); 207 } else { 208 m = iconLayout.getCountX() + hotseatLayout.getCountX(); 209 n = hotseatLayout.getCountY(); 210 } 211 int[][] matrix = createFullMatrix(m, n); 212 if (moreIconsInHotseatThanWorkspace) { 213 if (isHotseatHorizontal) { 214 for (int j = 0; j < n; j++) { 215 matrix[allappsiconRank][j] = ALL_APPS_COLUMN; 216 } 217 } else { 218 for (int j = 0; j < m; j++) { 219 matrix[j][allappsiconRank] = ALL_APPS_COLUMN; 220 } 221 } 222 } 223 // Iterate thru the children of the workspace. 224 for (int i = 0; i < iconParent.getChildCount(); i++) { 225 View cell = iconParent.getChildAt(i); 226 if (!cell.isFocusable()) { 227 continue; 228 } 229 int cx = ((CellLayout.LayoutParams) cell.getLayoutParams()).cellX; 230 int cy = ((CellLayout.LayoutParams) cell.getLayoutParams()).cellY; 231 if (moreIconsInHotseatThanWorkspace) { 232 if (isHotseatHorizontal && cx >= allappsiconRank) { 233 // Add 1 to account for the All Apps button. 234 cx++; 235 } 236 if (!isHotseatHorizontal && cy >= allappsiconRank) { 237 // Add 1 to account for the All Apps button. 238 cy++; 239 } 240 } 241 matrix[cx][cy] = i; 242 } 243 244 // Iterate thru the children of the hotseat. 245 for (int i = hotseatParent.getChildCount() - 1; i >= 0; i--) { 246 if (isHotseatHorizontal) { 247 int cx = ((CellLayout.LayoutParams) 248 hotseatParent.getChildAt(i).getLayoutParams()).cellX; 249 matrix[cx][iconLayout.getCountY()] = iconParent.getChildCount() + i; 250 } else { 251 int cy = ((CellLayout.LayoutParams) 252 hotseatParent.getChildAt(i).getLayoutParams()).cellY; 253 matrix[iconLayout.getCountX()][cy] = iconParent.getChildCount() + i; 254 } 255 } 256 if (DEBUG) { 257 printMatrix(matrix); 258 } 259 return matrix; 260 } 261 262 /** 263 * Creates a sparse matrix that merges the icon of previous/next page and last column of 264 * current page. When left key is triggered on the leftmost column, sparse matrix is created 265 * that combines previous page matrix and an extra column on the right. Likewise, when right 266 * key is triggered on the rightmost column, sparse matrix is created that combines this column 267 * on the 0th column and the next page matrix. 268 * 269 * @param pivotX x coordinate of the focused item in the current page 270 * @param pivotY y coordinate of the focused item in the current page 271 */ 272 // TODO: get rid of the dynamic matrix creation createSparseMatrixWithPivotColumn(CellLayout iconLayout, int pivotX, int pivotY)273 public static int[][] createSparseMatrixWithPivotColumn(CellLayout iconLayout, 274 int pivotX, int pivotY) { 275 276 ViewGroup iconParent = iconLayout.getShortcutsAndWidgets(); 277 278 int[][] matrix = createFullMatrix(iconLayout.getCountX() + 1, iconLayout.getCountY()); 279 280 // Iterate thru the children of the top parent. 281 for (int i = 0; i < iconParent.getChildCount(); i++) { 282 View cell = iconParent.getChildAt(i); 283 if (!cell.isFocusable()) { 284 continue; 285 } 286 int cx = ((CellLayout.LayoutParams) cell.getLayoutParams()).cellX; 287 int cy = ((CellLayout.LayoutParams) cell.getLayoutParams()).cellY; 288 if (pivotX < 0) { 289 matrix[cx - pivotX][cy] = i; 290 } else { 291 matrix[cx][cy] = i; 292 } 293 } 294 295 if (pivotX < 0) { 296 matrix[0][pivotY] = PIVOT; 297 } else { 298 matrix[pivotX][pivotY] = PIVOT; 299 } 300 if (DEBUG) { 301 printMatrix(matrix); 302 } 303 return matrix; 304 } 305 306 // 307 // key event handling methods. 308 // 309 310 /** 311 * Calculates icon that has is closest to the horizontal axis in reference to the cur icon. 312 * 313 * Example of the check order for KEYCODE_DPAD_RIGHT: 314 * [ ][ ][13][14][15] 315 * [ ][ 6][ 8][10][12] 316 * [ X][ 1][ 2][ 3][ 4] 317 * [ ][ 5][ 7][ 9][11] 318 */ 319 // TODO: add unit tests to verify all permutation. handleDpadHorizontal(int iconIdx, int cntX, int cntY, int[][] matrix, int increment, boolean isRtl)320 private static int handleDpadHorizontal(int iconIdx, int cntX, int cntY, 321 int[][] matrix, int increment, boolean isRtl) { 322 if(matrix == null) { 323 throw new IllegalStateException("Dpad navigation requires a matrix."); 324 } 325 int newIconIndex = NOOP; 326 327 int xPos = -1; 328 int yPos = -1; 329 // Figure out the location of the icon. 330 for (int i = 0; i < cntX; i++) { 331 for (int j = 0; j < cntY; j++) { 332 if (matrix[i][j] == iconIdx) { 333 xPos = i; 334 yPos = j; 335 } 336 } 337 } 338 if (DEBUG) { 339 Log.v(TAG, String.format("\thandleDpadHorizontal: \t[x, y]=[%d, %d] iconIndex=%d", 340 xPos, yPos, iconIdx)); 341 } 342 343 // Rule1: check first in the horizontal direction 344 for (int x = xPos + increment; 0 <= x && x < cntX; x += increment) { 345 if ((newIconIndex = inspectMatrix(x, yPos, cntX, cntY, matrix)) != NOOP 346 && newIconIndex != ALL_APPS_COLUMN) { 347 return newIconIndex; 348 } 349 } 350 351 // Rule2: check (x1-n, yPos + increment), (x1-n, yPos - increment) 352 // (x2-n, yPos + 2*increment), (x2-n, yPos - 2*increment) 353 int nextYPos1; 354 int nextYPos2; 355 boolean haveCrossedAllAppsColumn1 = false; 356 boolean haveCrossedAllAppsColumn2 = false; 357 int x = -1; 358 for (int coeff = 1; coeff < cntY; coeff++) { 359 nextYPos1 = yPos + coeff * increment; 360 nextYPos2 = yPos - coeff * increment; 361 x = xPos + increment * coeff; 362 if (inspectMatrix(x, nextYPos1, cntX, cntY, matrix) == ALL_APPS_COLUMN) { 363 haveCrossedAllAppsColumn1 = true; 364 } 365 if (inspectMatrix(x, nextYPos2, cntX, cntY, matrix) == ALL_APPS_COLUMN) { 366 haveCrossedAllAppsColumn2 = true; 367 } 368 for (; 0 <= x && x < cntX; x += increment) { 369 int offset1 = haveCrossedAllAppsColumn1 && x < cntX - 1 ? increment : 0; 370 newIconIndex = inspectMatrix(x, nextYPos1 + offset1, cntX, cntY, matrix); 371 if (newIconIndex != NOOP) { 372 return newIconIndex; 373 } 374 int offset2 = haveCrossedAllAppsColumn2 && x < cntX - 1 ? -increment : 0; 375 newIconIndex = inspectMatrix(x, nextYPos2 + offset2, cntX, cntY, matrix); 376 if (newIconIndex != NOOP) { 377 return newIconIndex; 378 } 379 } 380 } 381 382 // Rule3: if switching between pages, do a brute-force search to find an item that was 383 // missed by rules 1 and 2 (such as when going from a bottom right icon to top left) 384 if (iconIdx == PIVOT) { 385 if (isRtl) { 386 return increment < 0 ? NEXT_PAGE_FIRST_ITEM : PREVIOUS_PAGE_LAST_ITEM; 387 } 388 return increment < 0 ? PREVIOUS_PAGE_LAST_ITEM : NEXT_PAGE_FIRST_ITEM; 389 } 390 return newIconIndex; 391 } 392 393 /** 394 * Calculates icon that is closest to the vertical axis in reference to the current icon. 395 * 396 * Example of the check order for KEYCODE_DPAD_DOWN: 397 * [ ][ ][ ][ X][ ][ ][ ] 398 * [ ][ ][ 5][ 1][ 4][ ][ ] 399 * [ ][10][ 7][ 2][ 6][ 9][ ] 400 * [14][12][ 9][ 3][ 8][11][13] 401 */ 402 // TODO: add unit tests to verify all permutation. 403 private static int handleDpadVertical(int iconIndex, int cntX, int cntY, 404 int [][] matrix, int increment) { 405 int newIconIndex = NOOP; 406 if(matrix == null) { 407 throw new IllegalStateException("Dpad navigation requires a matrix."); 408 } 409 410 int xPos = -1; 411 int yPos = -1; 412 // Figure out the location of the icon. 413 for (int i = 0; i< cntX; i++) { 414 for (int j = 0; j < cntY; j++) { 415 if (matrix[i][j] == iconIndex) { 416 xPos = i; 417 yPos = j; 418 } 419 } 420 } 421 422 if (DEBUG) { 423 Log.v(TAG, String.format("\thandleDpadVertical: \t[x, y]=[%d, %d] iconIndex=%d", 424 xPos, yPos, iconIndex)); 425 } 426 427 // Rule1: check first in the dpad direction 428 for (int y = yPos + increment; 0 <= y && y <cntY && 0 <= y; y += increment) { 429 if ((newIconIndex = inspectMatrix(xPos, y, cntX, cntY, matrix)) != NOOP 430 && newIconIndex != ALL_APPS_COLUMN) { 431 return newIconIndex; 432 } 433 } 434 435 // Rule2: check (xPos + increment, y_(1-n)), (xPos - increment, y_(1-n)) 436 // (xPos + 2*increment, y_(2-n))), (xPos - 2*increment, y_(2-n)) 437 int nextXPos1; 438 int nextXPos2; 439 boolean haveCrossedAllAppsColumn1 = false; 440 boolean haveCrossedAllAppsColumn2 = false; 441 int y = -1; 442 for (int coeff = 1; coeff < cntX; coeff++) { 443 nextXPos1 = xPos + coeff * increment; 444 nextXPos2 = xPos - coeff * increment; 445 y = yPos + increment * coeff; 446 if (inspectMatrix(nextXPos1, y, cntX, cntY, matrix) == ALL_APPS_COLUMN) { 447 haveCrossedAllAppsColumn1 = true; 448 } 449 if (inspectMatrix(nextXPos2, y, cntX, cntY, matrix) == ALL_APPS_COLUMN) { 450 haveCrossedAllAppsColumn2 = true; 451 } 452 for (; 0 <= y && y < cntY; y = y + increment) { 453 int offset1 = haveCrossedAllAppsColumn1 && y < cntY - 1 ? increment : 0; 454 newIconIndex = inspectMatrix(nextXPos1 + offset1, y, cntX, cntY, matrix); 455 if (newIconIndex != NOOP) { 456 return newIconIndex; 457 } 458 int offset2 = haveCrossedAllAppsColumn2 && y < cntY - 1 ? -increment : 0; 459 newIconIndex = inspectMatrix(nextXPos2 + offset2, y, cntX, cntY, matrix); 460 if (newIconIndex != NOOP) { 461 return newIconIndex; 462 } 463 } 464 } 465 return newIconIndex; 466 } 467 468 private static int handleMoveHome() { 469 return CURRENT_PAGE_FIRST_ITEM; 470 } 471 472 private static int handleMoveEnd() { 473 return CURRENT_PAGE_LAST_ITEM; 474 } 475 476 private static int handlePageDown(int pageIndex, int pageCount) { 477 if (pageIndex < pageCount -1) { 478 return NEXT_PAGE_FIRST_ITEM; 479 } 480 return CURRENT_PAGE_LAST_ITEM; 481 } 482 483 private static int handlePageUp(int pageIndex) { 484 if (pageIndex > 0) { 485 return PREVIOUS_PAGE_FIRST_ITEM; 486 } else { 487 return CURRENT_PAGE_FIRST_ITEM; 488 } 489 } 490 491 // 492 // Helper methods. 493 // 494 495 private static boolean isValid(int xPos, int yPos, int countX, int countY) { 496 return (0 <= xPos && xPos < countX && 0 <= yPos && yPos < countY); 497 } 498 499 private static int inspectMatrix(int x, int y, int cntX, int cntY, int[][] matrix) { 500 int newIconIndex = NOOP; 501 if (isValid(x, y, cntX, cntY)) { 502 if (matrix[x][y] != -1) { 503 newIconIndex = matrix[x][y]; 504 if (DEBUG) { 505 Log.v(TAG, String.format("\t\tinspect: \t[x, y]=[%d, %d] %d", 506 x, y, matrix[x][y])); 507 } 508 return newIconIndex; 509 } 510 } 511 return newIconIndex; 512 } 513 514 /** 515 * Only used for debugging. 516 */ 517 private static String getStringIndex(int index) { 518 switch(index) { 519 case NOOP: return "NOOP"; 520 case PREVIOUS_PAGE_FIRST_ITEM: return "PREVIOUS_PAGE_FIRST"; 521 case PREVIOUS_PAGE_LAST_ITEM: return "PREVIOUS_PAGE_LAST"; 522 case PREVIOUS_PAGE_RIGHT_COLUMN:return "PREVIOUS_PAGE_RIGHT_COLUMN"; 523 case CURRENT_PAGE_FIRST_ITEM: return "CURRENT_PAGE_FIRST"; 524 case CURRENT_PAGE_LAST_ITEM: return "CURRENT_PAGE_LAST"; 525 case NEXT_PAGE_FIRST_ITEM: return "NEXT_PAGE_FIRST"; 526 case NEXT_PAGE_LEFT_COLUMN: return "NEXT_PAGE_LEFT_COLUMN"; 527 case ALL_APPS_COLUMN: return "ALL_APPS_COLUMN"; 528 default: 529 return Integer.toString(index); 530 } 531 } 532 533 /** 534 * Only used for debugging. 535 */ 536 private static void printMatrix(int[][] matrix) { 537 Log.v(TAG, "\tprintMap:"); 538 int m = matrix.length; 539 int n = matrix[0].length; 540 541 for (int j=0; j < n; j++) { 542 String colY = "\t\t"; 543 for (int i=0; i < m; i++) { 544 colY += String.format("%3d",matrix[i][j]); 545 } 546 Log.v(TAG, colY); 547 } 548 } 549 550 /** 551 * @param edgeColumn the column of the new icon. either {@link #NEXT_PAGE_LEFT_COLUMN} or 552 * {@link #NEXT_PAGE_RIGHT_COLUMN} 553 * @return the view adjacent to {@param oldView} in the {@param nextPage} of the folder. 554 */ 555 public static View getAdjacentChildInNextFolderPage( 556 ShortcutAndWidgetContainer nextPage, View oldView, int edgeColumn) { 557 final int newRow = ((CellLayout.LayoutParams) oldView.getLayoutParams()).cellY; 558 559 int column = (edgeColumn == NEXT_PAGE_LEFT_COLUMN) ^ nextPage.invertLayoutHorizontally() 560 ? 0 : (((CellLayout) nextPage.getParent()).getCountX() - 1); 561 562 for (; column >= 0; column--) { 563 for (int row = newRow; row >= 0; row--) { 564 View newView = nextPage.getChildAt(column, row); 565 if (newView != null) { 566 return newView; 567 } 568 } 569 } 570 return null; 571 } 572 } 573