1 /* 2 * Copyright (C) 2009 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 android.view.cts; 18 19 import static org.junit.Assert.assertEquals; 20 import static org.junit.Assert.assertFalse; 21 import static org.junit.Assert.assertNotNull; 22 import static org.junit.Assert.assertNull; 23 import static org.junit.Assert.assertSame; 24 import static org.junit.Assert.assertTrue; 25 26 import android.app.Activity; 27 import android.app.Instrumentation; 28 import android.view.View; 29 import android.view.ViewGroup; 30 import android.widget.Button; 31 import android.widget.FrameLayout; 32 import android.widget.LinearLayout; 33 34 import androidx.test.InstrumentationRegistry; 35 import androidx.test.annotation.UiThreadTest; 36 import androidx.test.filters.MediumTest; 37 import androidx.test.rule.ActivityTestRule; 38 import androidx.test.runner.AndroidJUnit4; 39 40 import org.junit.Rule; 41 import org.junit.Test; 42 import org.junit.runner.RunWith; 43 44 @MediumTest 45 @RunWith(AndroidJUnit4.class) 46 public class View_FocusHandlingTest { 47 @Rule 48 public ActivityTestRule<FocusHandlingCtsActivity> mActivityRule = 49 new ActivityTestRule<>(FocusHandlingCtsActivity.class); 50 51 @UiThreadTest 52 @Test testFocusHandling()53 public void testFocusHandling() { 54 Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation(); 55 instrumentation.setInTouchMode(false); 56 Activity activity = mActivityRule.getActivity(); 57 58 View v1 = activity.findViewById(R.id.view1); 59 View v2 = activity.findViewById(R.id.view2); 60 View v3 = activity.findViewById(R.id.view3); 61 View v4 = activity.findViewById(R.id.view4); 62 63 assertNotNull(v1); 64 assertNotNull(v2); 65 assertNotNull(v3); 66 assertNotNull(v4); 67 68 // test isFocusable and setFocusable 69 assertFalse(v1.isFocusable()); 70 assertFalse(v2.isFocusable()); 71 assertFalse(v3.isFocusable()); 72 assertFalse(v4.isFocusable()); 73 74 v1.setFocusable(true); 75 v2.setFocusable(true); 76 v3.setFocusable(true); 77 v4.setFocusable(true); 78 79 assertTrue(v1.isFocusable()); 80 assertTrue(v2.isFocusable()); 81 assertTrue(v3.isFocusable()); 82 assertTrue(v4.isFocusable()); 83 84 v1.setNextFocusRightId(R.id.view2); 85 v1.setNextFocusDownId(R.id.view3); 86 87 v2.setNextFocusLeftId(R.id.view1); 88 v2.setNextFocusDownId(R.id.view4); 89 90 v3.setNextFocusRightId(R.id.view4); 91 v3.setNextFocusUpId(R.id.view1); 92 93 v4.setNextFocusLeftId(R.id.view3); 94 v4.setNextFocusUpId(R.id.view2); 95 96 // test getNextFocus***Id 97 assertEquals(R.id.view2, v1.getNextFocusRightId()); 98 assertEquals(R.id.view3, v1.getNextFocusDownId()); 99 100 assertEquals(R.id.view1, v2.getNextFocusLeftId()); 101 assertEquals(R.id.view4, v2.getNextFocusDownId()); 102 103 assertEquals(R.id.view1, v3.getNextFocusUpId()); 104 assertEquals(R.id.view4, v3.getNextFocusRightId()); 105 106 assertEquals(R.id.view2, v4.getNextFocusUpId()); 107 assertEquals(R.id.view3, v4.getNextFocusLeftId()); 108 109 // test focusSearch 110 assertSame(v2, v1.focusSearch(View.FOCUS_RIGHT)); 111 assertSame(v3, v1.focusSearch(View.FOCUS_DOWN)); 112 113 assertSame(v1, v2.focusSearch(View.FOCUS_LEFT)); 114 assertSame(v4, v2.focusSearch(View.FOCUS_DOWN)); 115 116 assertSame(v1, v3.focusSearch(View.FOCUS_UP)); 117 assertSame(v4, v3.focusSearch(View.FOCUS_RIGHT)); 118 119 assertSame(v2, v4.focusSearch(View.FOCUS_UP)); 120 assertSame(v3, v4.focusSearch(View.FOCUS_LEFT)); 121 122 assertTrue(v1.requestFocus()); 123 assertTrue(v1.hasFocus()); 124 assertFalse(v2.hasFocus()); 125 assertFalse(v3.hasFocus()); 126 assertFalse(v4.hasFocus()); 127 128 v1.setVisibility(View.INVISIBLE); 129 assertFalse(v1.hasFocus()); 130 assertTrue(v2.hasFocus()); 131 assertFalse(v3.hasFocus()); 132 assertFalse(v4.hasFocus()); 133 134 v2.setVisibility(View.INVISIBLE); 135 assertFalse(v1.hasFocus()); 136 assertFalse(v2.hasFocus()); 137 assertTrue(v3.hasFocus()); 138 assertFalse(v4.hasFocus()); 139 140 v3.setVisibility(View.INVISIBLE); 141 assertFalse(v1.isFocused()); 142 assertFalse(v2.isFocused()); 143 assertFalse(v3.isFocused()); 144 assertTrue(v4.isFocused()); 145 146 v4.setVisibility(View.INVISIBLE); 147 assertFalse(v1.isFocused()); 148 assertFalse(v2.isFocused()); 149 assertFalse(v3.isFocused()); 150 assertFalse(v4.isFocused()); 151 152 v1.setVisibility(View.VISIBLE); 153 v2.setVisibility(View.VISIBLE); 154 v3.setVisibility(View.VISIBLE); 155 v4.setVisibility(View.VISIBLE); 156 assertTrue(v1.isFocused()); 157 assertFalse(v2.isFocused()); 158 assertFalse(v3.isFocused()); 159 assertFalse(v4.isFocused()); 160 161 v1.requestFocus(); 162 assertTrue(v1.isFocused()); 163 164 // test scenario: a view will not actually take focus if it is not focusable 165 v2.setFocusable(false); 166 v3.setFocusable(false); 167 168 assertTrue(v1.isFocusable()); 169 assertFalse(v2.isFocusable()); 170 assertFalse(v3.isFocusable()); 171 assertTrue(v4.isFocusable()); 172 173 v1.setVisibility(View.INVISIBLE); 174 assertFalse(v1.hasFocus()); 175 assertFalse(v2.hasFocus()); 176 assertFalse(v3.hasFocus()); 177 assertTrue(v4.hasFocus()); 178 179 // compare isFocusable and hasFocusable 180 assertTrue(v1.isFocusable()); 181 assertFalse(v1.hasFocusable()); 182 183 // test requestFocus 184 v1.setVisibility(View.VISIBLE); 185 v2.setFocusable(true); 186 v3.setFocusable(true); 187 188 assertTrue(v1.hasFocusable()); 189 assertTrue(v2.hasFocusable()); 190 assertTrue(v3.hasFocusable()); 191 assertTrue(v4.hasFocusable()); 192 193 assertTrue(v2.requestFocus(View.FOCUS_UP)); 194 assertFalse(v1.hasFocus()); 195 assertTrue(v2.hasFocus()); 196 assertFalse(v3.hasFocus()); 197 assertFalse(v4.hasFocus()); 198 199 assertTrue(v1.requestFocus(View.FOCUS_LEFT, null)); 200 assertTrue(v1.hasFocus()); 201 assertFalse(v2.hasFocus()); 202 assertFalse(v3.hasFocus()); 203 assertFalse(v4.hasFocus()); 204 205 assertTrue(v3.requestFocus()); 206 assertFalse(v1.hasFocus()); 207 assertFalse(v2.hasFocus()); 208 assertTrue(v3.hasFocus()); 209 assertFalse(v4.hasFocus()); 210 211 assertTrue(v4.requestFocusFromTouch()); 212 assertFalse(v1.hasFocus()); 213 assertFalse(v2.hasFocus()); 214 assertFalse(v3.hasFocus()); 215 assertTrue(v4.hasFocus()); 216 217 // test clearFocus 218 v4.clearFocus(); 219 assertTrue(v1.hasFocus()); 220 assertFalse(v2.hasFocus()); 221 assertFalse(v3.hasFocus()); 222 assertFalse(v4.hasFocus()); 223 224 assertSame(v1, v1.findFocus()); 225 assertNull(v2.findFocus()); 226 assertNull(v3.findFocus()); 227 assertNull(v4.findFocus()); 228 229 // test visibility with a nested focusable 230 ViewGroup vg = (ViewGroup) activity.findViewById(R.id.auto_test_area); 231 vg.setVisibility(View.INVISIBLE); 232 View sub = new View(activity); 233 vg.addView(sub, 10, 10); 234 sub.setFocusable(View.FOCUSABLE); 235 for (View v : new View[]{v1, v2, v3, v4}) { 236 v.setFocusable(false); 237 } 238 assertNull(vg.getRootView().findFocus()); 239 vg.setVisibility(View.VISIBLE); 240 assertSame(sub, vg.getRootView().findFocus()); 241 } 242 243 @UiThreadTest 244 @Test testEnabledHandling()245 public void testEnabledHandling() { 246 Activity activity = mActivityRule.getActivity(); 247 248 View v1 = activity.findViewById(R.id.view1); 249 View v2 = activity.findViewById(R.id.view2); 250 View v3 = activity.findViewById(R.id.view3); 251 View v4 = activity.findViewById(R.id.view4); 252 253 for (View v : new View[]{v1, v2, v3, v4}) v.setFocusable(true); 254 255 assertTrue(v1.requestFocus()); 256 257 // disabled view should not be focusable 258 assertTrue(v1.hasFocus()); 259 v1.setEnabled(false); 260 assertFalse(v1.hasFocus()); 261 v1.requestFocus(); 262 assertFalse(v1.hasFocus()); 263 v1.setEnabled(true); 264 v1.requestFocus(); 265 assertTrue(v1.hasFocus()); 266 267 // an enabled view should not take focus if not visible OR not enabled 268 v1.setEnabled(false); 269 v1.setVisibility(View.INVISIBLE); 270 assertFalse(v1.hasFocus()); 271 v1.setEnabled(true); 272 v1.requestFocus(); 273 assertFalse(v1.hasFocus()); 274 v1.setEnabled(false); 275 v1.setVisibility(View.VISIBLE); 276 v1.requestFocus(); 277 assertFalse(v1.hasFocus()); 278 v1.setEnabled(true); 279 v1.requestFocus(); 280 assertTrue(v1.hasFocus()); 281 282 // test hasFocusable 283 ViewGroup parent = (ViewGroup) v1.getParent(); 284 assertTrue(parent.hasFocusable()); 285 for (View v : new View[]{v1, v2, v3, v4}) v.setEnabled(false); 286 assertFalse(v1.isFocused()); 287 assertFalse(v2.isFocused()); 288 assertFalse(v3.isFocused()); 289 assertFalse(v4.isFocused()); 290 assertFalse(parent.hasFocusable()); 291 292 // a view enabled while nothing has focus should get focus if not in touch mode. 293 InstrumentationRegistry.getInstrumentation().setInTouchMode(false); 294 for (View v : new View[]{v1, v2, v3, v4}) v.setEnabled(true); 295 assertEquals(true, v1.isFocused()); 296 297 // enabled state is restricted to the view only (not children) 298 v2.requestFocus(); 299 parent.setEnabled(false); 300 assertTrue(v2.isFocused()); 301 } 302 303 @Test testSizeHandling()304 public void testSizeHandling() { 305 Activity activity = mActivityRule.getActivity(); 306 Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation(); 307 308 View v5 = new Button(activity); 309 ViewGroup layout = activity.findViewById(R.id.auto_test_area); 310 311 // Test requestFocus before first layout focuses if non-0 size 312 activity.runOnUiThread(() -> { 313 layout.addView(v5, 30, 30); 314 assertTrue(isZeroSize(v5)); 315 assertTrue(v5.requestFocus()); 316 }); 317 instrumentation.waitForIdleSync(); 318 assertFalse(isZeroSize(v5)); 319 assertTrue(v5.isFocused()); 320 321 // Test resize to 0 defocuses 322 activity.runOnUiThread(() -> { 323 v5.setRight(v5.getLeft()); 324 assertEquals(0, v5.getWidth()); 325 }); 326 instrumentation.waitForIdleSync(); 327 assertTrue(isZeroSize(v5)); 328 assertFalse(v5.isFocused()); 329 330 // Test requestFocus on laid-out 0-size fails 331 activity.runOnUiThread(() -> assertFalse(v5.requestFocus())); 332 333 activity.runOnUiThread(() -> layout.removeAllViews()); 334 335 // Test requestFocus before first layout focuses a child if non-0 size 336 LinearLayout ll0 = new LinearLayout(activity); 337 ll0.setFocusable(true); 338 ll0.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS); 339 View butInScroll = new Button(activity); 340 activity.runOnUiThread(() -> { 341 ll0.addView(butInScroll, 40, 40); 342 layout.addView(ll0, 100, 100); 343 assertTrue(isZeroSize(butInScroll)); 344 assertTrue(ll0.requestFocus()); 345 }); 346 instrumentation.waitForIdleSync(); 347 assertFalse(isZeroSize(butInScroll)); 348 assertTrue(butInScroll.isFocused()); 349 350 // Test focusableViewAvailable on resize to non-0 size 351 activity.runOnUiThread(() -> { 352 butInScroll.setRight(v5.getLeft()); 353 }); 354 instrumentation.waitForIdleSync(); 355 assertTrue(isZeroSize(butInScroll)); 356 assertTrue(ll0.isFocused()); 357 358 activity.runOnUiThread(() -> layout.removeAllViews()); 359 instrumentation.waitForIdleSync(); 360 361 // Test requestFocus before first layout defocuses if still 0 size 362 LinearLayout ll = new LinearLayout(activity); 363 View zeroSizeBut = new Button(activity); 364 activity.runOnUiThread(() -> { 365 ll.addView(zeroSizeBut, 30, 0); 366 layout.addView(ll, 100, 100); 367 assertTrue(zeroSizeBut.requestFocus()); 368 }); 369 instrumentation.waitForIdleSync(); 370 assertTrue(isZeroSize(zeroSizeBut)); 371 assertFalse(zeroSizeBut.isFocused()); 372 373 activity.runOnUiThread(() -> layout.removeAllViews()); 374 instrumentation.waitForIdleSync(); 375 376 // Test requestFocus before first layout focuses parent if child is still 0 size 377 LinearLayout ll2 = new LinearLayout(activity); 378 ll2.setFocusable(true); 379 ll2.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS); 380 View zeroButInAfter = new Button(activity); 381 activity.runOnUiThread(() -> { 382 ll2.addView(zeroButInAfter, 40, 0); 383 layout.addView(ll2, 100, 100); 384 assertTrue(ll2.requestFocus()); 385 assertTrue(zeroButInAfter.isFocused()); 386 }); 387 instrumentation.waitForIdleSync(); 388 assertTrue(isZeroSize(zeroButInAfter)); 389 assertFalse(zeroButInAfter.isFocused()); 390 assertTrue(ll2.isFocused()); 391 392 activity.runOnUiThread(() -> layout.removeAllViews()); 393 instrumentation.waitForIdleSync(); 394 395 // Test that we don't focus anything above/outside of where we requested focus 396 LinearLayout ll3 = new LinearLayout(activity); 397 Button outside = new Button(activity); 398 LinearLayout sub = new LinearLayout(activity); 399 Button inside = new Button(activity); 400 activity.runOnUiThread(() -> { 401 ll3.addView(outside, 40, 40); 402 sub.addView(inside, 30, 0); 403 ll3.addView(sub, 40, 40); 404 layout.addView(ll3, 100, 100); 405 assertTrue(sub.requestFocus()); 406 assertTrue(inside.isFocused()); 407 }); 408 instrumentation.waitForIdleSync(); 409 assertTrue(isZeroSize(inside)); 410 assertTrue(outside.isFocusable() && !isZeroSize(outside)); 411 assertNull(layout.getRootView().findFocus()); 412 } 413 isZeroSize(View view)414 private boolean isZeroSize(View view) { 415 return view.getWidth() <= 0 || view.getHeight() <= 0; 416 } 417 418 @UiThreadTest 419 @Test testFocusAuto()420 public void testFocusAuto() { 421 Activity activity = mActivityRule.getActivity(); 422 423 activity.getLayoutInflater().inflate(R.layout.focus_handling_auto_layout, 424 (ViewGroup) activity.findViewById(R.id.auto_test_area)); 425 426 View def = activity.findViewById(R.id.focusabledefault); 427 View auto = activity.findViewById(R.id.focusableauto); 428 View click = activity.findViewById(R.id.onlyclickable); 429 View clickNotFocus = activity.findViewById(R.id.clickablenotfocusable); 430 431 assertEquals(View.FOCUSABLE_AUTO, auto.getFocusable()); 432 assertFalse(auto.isFocusable()); 433 assertFalse(def.isFocusable()); 434 assertTrue(click.isFocusable()); 435 assertFalse(clickNotFocus.isFocusable()); 436 437 View test = new View(activity); 438 assertFalse(test.isFocusable()); 439 test.setClickable(true); 440 assertTrue(test.isFocusable()); 441 test.setFocusable(View.NOT_FOCUSABLE); 442 assertFalse(test.isFocusable()); 443 test.setFocusable(View.FOCUSABLE_AUTO); 444 assertTrue(test.isFocusable()); 445 test.setClickable(false); 446 assertFalse(test.isFocusable()); 447 448 // Make sure setFocusable(boolean) unsets FOCUSABLE_AUTO. 449 auto.setFocusable(true); 450 assertSame(View.FOCUSABLE, auto.getFocusable()); 451 auto.setFocusable(View.FOCUSABLE_AUTO); 452 assertSame(View.FOCUSABLE_AUTO, auto.getFocusable()); 453 auto.setFocusable(false); 454 assertSame(View.NOT_FOCUSABLE, auto.getFocusable()); 455 } 456 457 @UiThreadTest 458 @Test testFocusableInTouchMode()459 public void testFocusableInTouchMode() { 460 final Activity activity = mActivityRule.getActivity(); 461 View singleView = new View(activity); 462 assertFalse("Must not be focusable by default", singleView.isFocusable()); 463 singleView.setFocusableInTouchMode(true); 464 assertSame("setFocusableInTouchMode(true) must imply explicit focusable", 465 View.FOCUSABLE, singleView.getFocusable()); 466 467 activity.getLayoutInflater().inflate(R.layout.focus_handling_auto_layout, 468 (ViewGroup) activity.findViewById(R.id.auto_test_area)); 469 View focusTouchModeView = activity.findViewById(R.id.focusabletouchmode); 470 assertSame("focusableInTouchMode=\"true\" must imply explicit focusable", 471 View.FOCUSABLE, focusTouchModeView.getFocusable()); 472 } 473 474 @UiThreadTest 475 @Test testHasFocusable()476 public void testHasFocusable() { 477 final Activity activity = mActivityRule.getActivity(); 478 final ViewGroup group = (ViewGroup) activity.findViewById(R.id.auto_test_area); 479 480 View singleView = new View(activity); 481 group.addView(singleView); 482 483 testHasFocusable(singleView); 484 485 group.removeView(singleView); 486 487 View groupView = new FrameLayout(activity); 488 group.addView(groupView); 489 490 testHasFocusable(groupView); 491 } 492 testHasFocusable(View view)493 private void testHasFocusable(View view) { 494 assertEquals("single view was not auto-focusable", View.FOCUSABLE_AUTO, 495 view.getFocusable()); 496 assertFalse("single view unexpectedly hasFocusable", view.hasFocusable()); 497 assertFalse("single view unexpectedly hasExplicitFocusable", view.hasExplicitFocusable()); 498 499 view.setClickable(true); 500 assertTrue("single view doesn't hasFocusable", view.hasFocusable()); 501 assertFalse("single view unexpectedly hasExplicitFocusable", view.hasExplicitFocusable()); 502 503 view.setClickable(false); 504 assertFalse("single view unexpectedly hasFocusable", view.hasFocusable()); 505 assertFalse("single view unexpectedly hasExplicitFocusable", view.hasExplicitFocusable()); 506 507 view.setFocusable(View.NOT_FOCUSABLE); 508 assertFalse("single view unexpectedly hasFocusable", view.hasFocusable()); 509 assertFalse("single view unexpectedly hasExplicitFocusable", view.hasExplicitFocusable()); 510 511 view.setFocusable(View.FOCUSABLE); 512 assertTrue("single view doesn't hasFocusable", view.hasFocusable()); 513 assertTrue("single view doesn't hasExplicitFocusable", view.hasExplicitFocusable()); 514 515 view.setFocusable(View.FOCUSABLE_AUTO); 516 view.setFocusableInTouchMode(true); 517 assertTrue("single view doesn't hasFocusable", view.hasFocusable()); 518 assertTrue("single view doesn't hasExplicitFocusable", view.hasExplicitFocusable()); 519 } 520 521 @UiThreadTest 522 @Test testFocusAfterDescendantsTransfer()523 public void testFocusAfterDescendantsTransfer() throws Throwable { 524 final Activity activity = mActivityRule.getActivity(); 525 final ViewGroup group = (ViewGroup) activity.findViewById(R.id.auto_test_area); 526 ViewGroup root = (ViewGroup) activity.findViewById(R.id.main_view); 527 group.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS); 528 group.setFocusableInTouchMode(true); 529 group.setKeyboardNavigationCluster(true); 530 group.requestFocus(); 531 assertTrue(group.isFocused()); 532 533 LinearLayout mid = new LinearLayout(activity); 534 Button but = new Button(activity); 535 but.setRight(but.getLeft() + 10); 536 but.setBottom(but.getTop() + 10); 537 but.setFocusableInTouchMode(true); 538 but.setVisibility(View.INVISIBLE); 539 mid.addView(but, ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); 540 541 group.addView(mid, ViewGroup.LayoutParams.WRAP_CONTENT, 542 ViewGroup.LayoutParams.WRAP_CONTENT); 543 but.setVisibility(View.VISIBLE); 544 assertSame(root.findFocus(), but); 545 assertFalse(group.isFocused()); 546 547 assertFalse(root.restoreFocusNotInCluster()); 548 assertSame(root.findFocus(), but); 549 } 550 } 551