1 /* 2 * Copyright (C) 2022 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 package com.android.systemui.complication; 17 18 import static com.google.common.truth.Truth.assertThat; 19 20 import static org.mockito.ArgumentMatchers.eq; 21 import static org.mockito.Mockito.never; 22 import static org.mockito.Mockito.verify; 23 import static org.mockito.Mockito.when; 24 25 import android.testing.AndroidTestingRunner; 26 import android.view.View; 27 28 import androidx.constraintlayout.widget.ConstraintLayout; 29 import androidx.test.filters.SmallTest; 30 31 import com.android.systemui.R; 32 import com.android.systemui.SysuiTestCase; 33 import com.android.systemui.complication.ComplicationLayoutEngine.Margins; 34 import com.android.systemui.touch.TouchInsetManager; 35 36 import org.junit.Before; 37 import org.junit.Test; 38 import org.junit.runner.RunWith; 39 import org.mockito.ArgumentCaptor; 40 import org.mockito.Mock; 41 import org.mockito.Mockito; 42 import org.mockito.MockitoAnnotations; 43 44 import java.util.Arrays; 45 import java.util.List; 46 import java.util.Random; 47 import java.util.function.Consumer; 48 import java.util.stream.Collectors; 49 50 @SmallTest 51 @RunWith(AndroidTestingRunner.class) 52 public class ComplicationLayoutEngineTest extends SysuiTestCase { 53 @Mock 54 ConstraintLayout mLayout; 55 56 @Mock 57 TouchInsetManager.TouchInsetSession mTouchSession; 58 createComplicationLayoutEngine()59 ComplicationLayoutEngine createComplicationLayoutEngine() { 60 return createComplicationLayoutEngine(0); 61 } 62 createComplicationLayoutEngine(int spacing)63 ComplicationLayoutEngine createComplicationLayoutEngine(int spacing) { 64 return new ComplicationLayoutEngine(mLayout, spacing, 0, 0, 0, 0, mTouchSession, 0, 0); 65 } 66 67 @Before setup()68 public void setup() { 69 MockitoAnnotations.initMocks(this); 70 } 71 72 private static class ViewInfo { 73 private static int sNextId = 1; 74 public final ComplicationId id; 75 public final View view; 76 public final ComplicationLayoutParams lp; 77 78 @Complication.Category 79 public final int category; 80 81 private static ComplicationId.Factory sFactory = new ComplicationId.Factory(); 82 ViewInfo(ComplicationLayoutParams params, @Complication.Category int category, ConstraintLayout layout)83 ViewInfo(ComplicationLayoutParams params, @Complication.Category int category, 84 ConstraintLayout layout) { 85 this.lp = params; 86 this.category = category; 87 this.view = Mockito.mock(View.class); 88 this.id = sFactory.getNextId(); 89 when(view.getId()).thenReturn(sNextId++); 90 when(view.getParent()).thenReturn(layout); 91 } 92 clearInvocations()93 void clearInvocations() { 94 Mockito.clearInvocations(view); 95 } 96 } 97 verifyChange(ViewInfo viewInfo, boolean verifyAdd, Consumer<ConstraintLayout.LayoutParams> paramConsumer)98 private void verifyChange(ViewInfo viewInfo, 99 boolean verifyAdd, 100 Consumer<ConstraintLayout.LayoutParams> paramConsumer) { 101 ArgumentCaptor<ConstraintLayout.LayoutParams> lpCaptor = 102 ArgumentCaptor.forClass(ConstraintLayout.LayoutParams.class); 103 verify(viewInfo.view).setLayoutParams(lpCaptor.capture()); 104 105 if (verifyAdd) { 106 verify(mLayout).addView(eq(viewInfo.view)); 107 } 108 109 ConstraintLayout.LayoutParams capturedParams = lpCaptor.getValue(); 110 paramConsumer.accept(capturedParams); 111 } 112 addComplication(ComplicationLayoutEngine engine, ViewInfo info)113 private void addComplication(ComplicationLayoutEngine engine, ViewInfo info) { 114 engine.addComplication(info.id, info.view, info.lp, info.category); 115 } 116 117 @Test testCombineMargins()118 public void testCombineMargins() { 119 final Random rand = new Random(); 120 final Margins margins1 = new Margins(rand.nextInt(), rand.nextInt(), rand.nextInt(), 121 rand.nextInt()); 122 final Margins margins2 = new Margins(rand.nextInt(), rand.nextInt(), rand.nextInt(), 123 rand.nextInt()); 124 final Margins combined = Margins.combine(margins1, margins2); 125 assertThat(margins1.start + margins2.start).isEqualTo(combined.start); 126 assertThat(margins1.top + margins2.top).isEqualTo(combined.top); 127 assertThat(margins1.end + margins2.end).isEqualTo(combined.end); 128 assertThat(margins1.bottom + margins2.bottom).isEqualTo(combined.bottom); 129 } 130 131 @Test testComplicationMarginPosition()132 public void testComplicationMarginPosition() { 133 final Random rand = new Random(); 134 final int startMargin = rand.nextInt(); 135 final int topMargin = rand.nextInt(); 136 final int endMargin = rand.nextInt(); 137 final int bottomMargin = rand.nextInt(); 138 final int spacing = rand.nextInt(); 139 140 final ComplicationLayoutEngine engine = new ComplicationLayoutEngine(mLayout, spacing, 141 startMargin, topMargin, endMargin, bottomMargin, mTouchSession, 0, 0); 142 143 final ViewInfo firstViewInfo = new ViewInfo( 144 new ComplicationLayoutParams( 145 100, 146 100, 147 ComplicationLayoutParams.POSITION_TOP 148 | ComplicationLayoutParams.POSITION_END, 149 ComplicationLayoutParams.DIRECTION_DOWN, 150 0), 151 Complication.CATEGORY_SYSTEM, 152 mLayout); 153 154 addComplication(engine, firstViewInfo); 155 firstViewInfo.clearInvocations(); 156 157 final ViewInfo secondViewInfo = new ViewInfo( 158 new ComplicationLayoutParams( 159 100, 160 100, 161 ComplicationLayoutParams.POSITION_TOP 162 | ComplicationLayoutParams.POSITION_END, 163 ComplicationLayoutParams.DIRECTION_DOWN, 164 0), 165 Complication.CATEGORY_STANDARD, 166 mLayout); 167 168 addComplication(engine, secondViewInfo); 169 170 171 // The first added view should have margins from both directions from the corner position. 172 verifyChange(firstViewInfo, false, lp -> { 173 assertThat(lp.topMargin).isEqualTo(topMargin); 174 assertThat(lp.getMarginEnd()).isEqualTo(endMargin); 175 }); 176 177 // The second view should be spaced below the first view and have the side end margin. 178 verifyChange(secondViewInfo, false, lp -> { 179 assertThat(lp.topMargin).isEqualTo(spacing); 180 assertThat(lp.getMarginEnd()).isEqualTo(endMargin); 181 }); 182 } 183 184 /** 185 * Makes sure the engine properly places a view within the {@link ConstraintLayout}. 186 */ 187 @Test testSingleLayout()188 public void testSingleLayout() { 189 final ViewInfo firstViewInfo = new ViewInfo( 190 new ComplicationLayoutParams( 191 100, 192 100, 193 ComplicationLayoutParams.POSITION_TOP 194 | ComplicationLayoutParams.POSITION_END, 195 ComplicationLayoutParams.DIRECTION_DOWN, 196 0), 197 Complication.CATEGORY_STANDARD, 198 mLayout); 199 200 final ComplicationLayoutEngine engine = createComplicationLayoutEngine(); 201 addComplication(engine, firstViewInfo); 202 203 // Ensure the view is added to the top end corner 204 verifyChange(firstViewInfo, true, lp -> { 205 assertThat(lp.topToTop == ConstraintLayout.LayoutParams.PARENT_ID).isTrue(); 206 assertThat(lp.endToEnd == ConstraintLayout.LayoutParams.PARENT_ID).isTrue(); 207 }); 208 } 209 210 /** 211 * Makes sure the engine properly places a view within the {@link ConstraintLayout}. 212 */ 213 @Test testSnapToGuide()214 public void testSnapToGuide() { 215 final ViewInfo firstViewInfo = new ViewInfo( 216 new ComplicationLayoutParams( 217 100, 218 100, 219 ComplicationLayoutParams.POSITION_TOP 220 | ComplicationLayoutParams.POSITION_END, 221 ComplicationLayoutParams.DIRECTION_DOWN, 222 0, 223 true), 224 Complication.CATEGORY_STANDARD, 225 mLayout); 226 227 final ComplicationLayoutEngine engine = createComplicationLayoutEngine(); 228 addComplication(engine, firstViewInfo); 229 230 // Ensure the view is added to the top end corner 231 verifyChange(firstViewInfo, true, lp -> { 232 assertThat(lp.topToTop == ConstraintLayout.LayoutParams.PARENT_ID).isTrue(); 233 assertThat(lp.endToEnd == ConstraintLayout.LayoutParams.PARENT_ID).isTrue(); 234 assertThat(lp.startToEnd == R.id.complication_end_guide).isTrue(); 235 }); 236 } 237 238 /** 239 * Ensures layout in a particular direction updates. 240 */ 241 @Test testDirectionLayout()242 public void testDirectionLayout() { 243 final ComplicationLayoutEngine engine = createComplicationLayoutEngine(); 244 245 final ViewInfo firstViewInfo = new ViewInfo( 246 new ComplicationLayoutParams( 247 100, 248 100, 249 ComplicationLayoutParams.POSITION_TOP 250 | ComplicationLayoutParams.POSITION_END, 251 ComplicationLayoutParams.DIRECTION_DOWN, 252 0), 253 Complication.CATEGORY_STANDARD, 254 mLayout); 255 256 addComplication(engine, firstViewInfo); 257 258 firstViewInfo.clearInvocations(); 259 260 final ViewInfo secondViewInfo = new ViewInfo( 261 new ComplicationLayoutParams( 262 100, 263 100, 264 ComplicationLayoutParams.POSITION_TOP 265 | ComplicationLayoutParams.POSITION_END, 266 ComplicationLayoutParams.DIRECTION_DOWN, 267 0), 268 Complication.CATEGORY_SYSTEM, 269 mLayout); 270 271 addComplication(engine, secondViewInfo); 272 273 // The first added view should now be underneath the second view. 274 verifyChange(firstViewInfo, false, lp -> { 275 assertThat(lp.topToBottom == secondViewInfo.view.getId()).isTrue(); 276 assertThat(lp.endToEnd == ConstraintLayout.LayoutParams.PARENT_ID).isTrue(); 277 }); 278 279 // The second view should be in the top position. 280 verifyChange(secondViewInfo, true, lp -> { 281 assertThat(lp.topToTop == ConstraintLayout.LayoutParams.PARENT_ID).isTrue(); 282 assertThat(lp.endToEnd == ConstraintLayout.LayoutParams.PARENT_ID).isTrue(); 283 }); 284 } 285 286 /** 287 * Ensures layout in a particular position updates. 288 */ 289 @Test testPositionLayout()290 public void testPositionLayout() { 291 final ComplicationLayoutEngine engine = createComplicationLayoutEngine(); 292 293 final ViewInfo firstViewInfo = new ViewInfo( 294 new ComplicationLayoutParams( 295 100, 296 100, 297 ComplicationLayoutParams.POSITION_TOP 298 | ComplicationLayoutParams.POSITION_END, 299 ComplicationLayoutParams.DIRECTION_DOWN, 300 0), 301 Complication.CATEGORY_STANDARD, 302 mLayout); 303 304 addComplication(engine, firstViewInfo); 305 306 final ViewInfo secondViewInfo = new ViewInfo( 307 new ComplicationLayoutParams( 308 100, 309 100, 310 ComplicationLayoutParams.POSITION_TOP 311 | ComplicationLayoutParams.POSITION_END, 312 ComplicationLayoutParams.DIRECTION_DOWN, 313 0), 314 Complication.CATEGORY_SYSTEM, 315 mLayout); 316 317 addComplication(engine, secondViewInfo); 318 319 firstViewInfo.clearInvocations(); 320 secondViewInfo.clearInvocations(); 321 322 final ViewInfo thirdViewInfo = new ViewInfo( 323 new ComplicationLayoutParams( 324 100, 325 100, 326 ComplicationLayoutParams.POSITION_TOP 327 | ComplicationLayoutParams.POSITION_END, 328 ComplicationLayoutParams.DIRECTION_START, 329 1), 330 Complication.CATEGORY_SYSTEM, 331 mLayout); 332 333 addComplication(engine, thirdViewInfo); 334 335 // The first added view should now be underneath the second view. 336 verifyChange(firstViewInfo, false, lp -> { 337 assertThat(lp.topToBottom == secondViewInfo.view.getId()).isTrue(); 338 assertThat(lp.endToEnd == ConstraintLayout.LayoutParams.PARENT_ID).isTrue(); 339 }); 340 341 // The second view should be in underneath the third view. 342 verifyChange(secondViewInfo, false, lp -> { 343 assertThat(lp.topToBottom == thirdViewInfo.view.getId()).isTrue(); 344 assertThat(lp.endToEnd == ConstraintLayout.LayoutParams.PARENT_ID).isTrue(); 345 }); 346 347 // The third view should be in at the top. 348 verifyChange(thirdViewInfo, true, lp -> { 349 assertThat(lp.topToTop == ConstraintLayout.LayoutParams.PARENT_ID).isTrue(); 350 assertThat(lp.endToEnd == ConstraintLayout.LayoutParams.PARENT_ID).isTrue(); 351 }); 352 353 final ViewInfo fourthViewInfo = new ViewInfo( 354 new ComplicationLayoutParams( 355 100, 356 100, 357 ComplicationLayoutParams.POSITION_TOP 358 | ComplicationLayoutParams.POSITION_END, 359 ComplicationLayoutParams.DIRECTION_START, 360 1), 361 Complication.CATEGORY_STANDARD, 362 mLayout); 363 364 addComplication(engine, fourthViewInfo); 365 366 verifyChange(fourthViewInfo, true, lp -> { 367 assertThat(lp.topToTop == ConstraintLayout.LayoutParams.PARENT_ID).isTrue(); 368 assertThat(lp.endToStart == thirdViewInfo.view.getId()).isTrue(); 369 }); 370 } 371 372 /** 373 * Ensures default margin is applied 374 */ 375 @Test testDefaultMargin()376 public void testDefaultMargin() { 377 final int margin = 5; 378 final ComplicationLayoutEngine engine = createComplicationLayoutEngine(margin); 379 380 final ViewInfo firstViewInfo = new ViewInfo( 381 new ComplicationLayoutParams( 382 100, 383 100, 384 ComplicationLayoutParams.POSITION_TOP 385 | ComplicationLayoutParams.POSITION_END, 386 ComplicationLayoutParams.DIRECTION_DOWN, 387 0), 388 Complication.CATEGORY_STANDARD, 389 mLayout); 390 391 addComplication(engine, firstViewInfo); 392 393 final ViewInfo secondViewInfo = new ViewInfo( 394 new ComplicationLayoutParams( 395 100, 396 100, 397 ComplicationLayoutParams.POSITION_TOP 398 | ComplicationLayoutParams.POSITION_END, 399 ComplicationLayoutParams.DIRECTION_START, 400 0), 401 Complication.CATEGORY_SYSTEM, 402 mLayout); 403 404 addComplication(engine, secondViewInfo); 405 406 firstViewInfo.clearInvocations(); 407 secondViewInfo.clearInvocations(); 408 409 final ViewInfo thirdViewInfo = new ViewInfo( 410 new ComplicationLayoutParams( 411 100, 412 100, 413 ComplicationLayoutParams.POSITION_TOP 414 | ComplicationLayoutParams.POSITION_END, 415 ComplicationLayoutParams.DIRECTION_START, 416 1), 417 Complication.CATEGORY_SYSTEM, 418 mLayout); 419 420 addComplication(engine, thirdViewInfo); 421 422 // The first added view should now be underneath the third view. 423 verifyChange(firstViewInfo, false, lp -> { 424 assertThat(lp.topToBottom == thirdViewInfo.view.getId()).isTrue(); 425 assertThat(lp.endToEnd == ConstraintLayout.LayoutParams.PARENT_ID).isTrue(); 426 assertThat(lp.topMargin).isEqualTo(margin); 427 }); 428 429 // The second view should be to the start of the third view. 430 verifyChange(secondViewInfo, false, lp -> { 431 assertThat(lp.endToStart == thirdViewInfo.view.getId()).isTrue(); 432 assertThat(lp.topToTop == ConstraintLayout.LayoutParams.PARENT_ID).isTrue(); 433 assertThat(lp.getMarginEnd()).isEqualTo(margin); 434 }); 435 436 // The third view should be at the top end corner. No margin should be applied. 437 verifyChange(thirdViewInfo, true, lp -> { 438 assertThat(lp.topToTop == ConstraintLayout.LayoutParams.PARENT_ID).isTrue(); 439 assertThat(lp.endToEnd == ConstraintLayout.LayoutParams.PARENT_ID).isTrue(); 440 assertThat(lp.getMarginStart()).isEqualTo(0); 441 assertThat(lp.getMarginEnd()).isEqualTo(0); 442 assertThat(lp.topMargin).isEqualTo(0); 443 assertThat(lp.bottomMargin).isEqualTo(0); 444 }); 445 } 446 447 /** 448 * Ensures complication margin is applied 449 */ 450 @Test testComplicationMargin()451 public void testComplicationMargin() { 452 final int defaultMargin = 5; 453 final int complicationMargin = 10; 454 final ComplicationLayoutEngine engine = createComplicationLayoutEngine(defaultMargin); 455 456 final ViewInfo firstViewInfo = new ViewInfo( 457 new ComplicationLayoutParams( 458 100, 459 100, 460 ComplicationLayoutParams.POSITION_TOP 461 | ComplicationLayoutParams.POSITION_END, 462 ComplicationLayoutParams.DIRECTION_DOWN, 463 0, 464 complicationMargin), 465 Complication.CATEGORY_STANDARD, 466 mLayout); 467 468 addComplication(engine, firstViewInfo); 469 470 final ViewInfo secondViewInfo = new ViewInfo( 471 new ComplicationLayoutParams( 472 100, 473 100, 474 ComplicationLayoutParams.POSITION_TOP 475 | ComplicationLayoutParams.POSITION_END, 476 ComplicationLayoutParams.DIRECTION_START, 477 0), 478 Complication.CATEGORY_SYSTEM, 479 mLayout); 480 481 addComplication(engine, secondViewInfo); 482 483 firstViewInfo.clearInvocations(); 484 secondViewInfo.clearInvocations(); 485 486 final ViewInfo thirdViewInfo = new ViewInfo( 487 new ComplicationLayoutParams( 488 100, 489 100, 490 ComplicationLayoutParams.POSITION_TOP 491 | ComplicationLayoutParams.POSITION_END, 492 ComplicationLayoutParams.DIRECTION_START, 493 1), 494 Complication.CATEGORY_SYSTEM, 495 mLayout); 496 497 addComplication(engine, thirdViewInfo); 498 499 // The first added view should now be underneath the third view. 500 verifyChange(firstViewInfo, false, lp -> { 501 assertThat(lp.topToBottom == thirdViewInfo.view.getId()).isTrue(); 502 assertThat(lp.endToEnd == ConstraintLayout.LayoutParams.PARENT_ID).isTrue(); 503 assertThat(lp.topMargin).isEqualTo(complicationMargin); 504 }); 505 506 // The second view should be to the start of the third view. 507 verifyChange(secondViewInfo, false, lp -> { 508 assertThat(lp.endToStart == thirdViewInfo.view.getId()).isTrue(); 509 assertThat(lp.topToTop == ConstraintLayout.LayoutParams.PARENT_ID).isTrue(); 510 assertThat(lp.getMarginEnd()).isEqualTo(defaultMargin); 511 }); 512 } 513 514 /** 515 * Ensures layout sets correct max width constraint. 516 */ 517 @Test testWidthConstraint()518 public void testWidthConstraint() { 519 final int maxWidth = 20; 520 final ComplicationLayoutEngine engine = createComplicationLayoutEngine(); 521 522 final ViewInfo viewStartDirection = new ViewInfo( 523 new ComplicationLayoutParams( 524 100, 525 100, 526 ComplicationLayoutParams.POSITION_TOP 527 | ComplicationLayoutParams.POSITION_END, 528 ComplicationLayoutParams.DIRECTION_START, 529 0, 530 5, 531 maxWidth), 532 Complication.CATEGORY_STANDARD, 533 mLayout); 534 final ViewInfo viewEndDirection = new ViewInfo( 535 new ComplicationLayoutParams( 536 100, 537 100, 538 ComplicationLayoutParams.POSITION_TOP 539 | ComplicationLayoutParams.POSITION_START, 540 ComplicationLayoutParams.DIRECTION_END, 541 0, 542 5, 543 maxWidth), 544 Complication.CATEGORY_STANDARD, 545 mLayout); 546 547 addComplication(engine, viewStartDirection); 548 addComplication(engine, viewEndDirection); 549 550 // Verify both horizontal direction views have max width set correctly, and max height is 551 // not set. 552 verifyChange(viewStartDirection, false, lp -> { 553 assertThat(lp.matchConstraintMaxWidth).isEqualTo(maxWidth); 554 assertThat(lp.matchConstraintMaxHeight).isEqualTo(0); 555 }); 556 verifyChange(viewEndDirection, false, lp -> { 557 assertThat(lp.matchConstraintMaxWidth).isEqualTo(maxWidth); 558 assertThat(lp.matchConstraintMaxHeight).isEqualTo(0); 559 }); 560 } 561 562 /** 563 * Ensures layout sets correct max height constraint. 564 */ 565 @Test testHeightConstraint()566 public void testHeightConstraint() { 567 final int maxHeight = 20; 568 final ComplicationLayoutEngine engine = createComplicationLayoutEngine(); 569 570 final ViewInfo viewUpDirection = new ViewInfo( 571 new ComplicationLayoutParams( 572 100, 573 100, 574 ComplicationLayoutParams.POSITION_BOTTOM 575 | ComplicationLayoutParams.POSITION_END, 576 ComplicationLayoutParams.DIRECTION_UP, 577 0, 578 5, 579 maxHeight), 580 Complication.CATEGORY_STANDARD, 581 mLayout); 582 final ViewInfo viewDownDirection = new ViewInfo( 583 new ComplicationLayoutParams( 584 100, 585 100, 586 ComplicationLayoutParams.POSITION_TOP 587 | ComplicationLayoutParams.POSITION_END, 588 ComplicationLayoutParams.DIRECTION_DOWN, 589 0, 590 5, 591 maxHeight), 592 Complication.CATEGORY_STANDARD, 593 mLayout); 594 595 addComplication(engine, viewUpDirection); 596 addComplication(engine, viewDownDirection); 597 598 // Verify both vertical direction views have max height set correctly, and max width is 599 // not set. 600 verifyChange(viewUpDirection, false, lp -> { 601 assertThat(lp.matchConstraintMaxHeight).isEqualTo(maxHeight); 602 assertThat(lp.matchConstraintMaxWidth).isEqualTo(0); 603 }); 604 verifyChange(viewDownDirection, false, lp -> { 605 assertThat(lp.matchConstraintMaxHeight).isEqualTo(maxHeight); 606 assertThat(lp.matchConstraintMaxWidth).isEqualTo(0); 607 }); 608 } 609 610 /** 611 * Ensures layout does not set any constraint if not specified. 612 */ 613 @Test testConstraintNotSetWhenNotSpecified()614 public void testConstraintNotSetWhenNotSpecified() { 615 final ComplicationLayoutEngine engine = createComplicationLayoutEngine(); 616 617 final ViewInfo view = new ViewInfo( 618 new ComplicationLayoutParams( 619 100, 620 100, 621 ComplicationLayoutParams.POSITION_TOP 622 | ComplicationLayoutParams.POSITION_END, 623 ComplicationLayoutParams.DIRECTION_DOWN, 624 0, 625 5), 626 Complication.CATEGORY_STANDARD, 627 mLayout); 628 629 addComplication(engine, view); 630 631 // Verify neither max height nor max width set. 632 verifyChange(view, false, lp -> { 633 assertThat(lp.matchConstraintMaxHeight).isEqualTo(0); 634 assertThat(lp.matchConstraintMaxWidth).isEqualTo(0); 635 }); 636 } 637 638 /** 639 * Ensures layout in a particular position updates. 640 */ 641 @Test testRemoval()642 public void testRemoval() { 643 final ComplicationLayoutEngine engine = createComplicationLayoutEngine(); 644 645 final ViewInfo firstViewInfo = new ViewInfo( 646 new ComplicationLayoutParams( 647 100, 648 100, 649 ComplicationLayoutParams.POSITION_TOP 650 | ComplicationLayoutParams.POSITION_END, 651 ComplicationLayoutParams.DIRECTION_DOWN, 652 0), 653 Complication.CATEGORY_STANDARD, 654 mLayout); 655 656 engine.addComplication(firstViewInfo.id, firstViewInfo.view, firstViewInfo.lp, 657 firstViewInfo.category); 658 659 final ViewInfo secondViewInfo = new ViewInfo( 660 new ComplicationLayoutParams( 661 100, 662 100, 663 ComplicationLayoutParams.POSITION_TOP 664 | ComplicationLayoutParams.POSITION_END, 665 ComplicationLayoutParams.DIRECTION_DOWN, 666 0), 667 Complication.CATEGORY_SYSTEM, 668 mLayout); 669 670 engine.addComplication(secondViewInfo.id, secondViewInfo.view, secondViewInfo.lp, 671 secondViewInfo.category); 672 673 firstViewInfo.clearInvocations(); 674 675 engine.removeComplication(secondViewInfo.id); 676 verify(mLayout).removeView(eq(secondViewInfo.view)); 677 678 verifyChange(firstViewInfo, true, lp -> { 679 assertThat(lp.topToTop == ConstraintLayout.LayoutParams.PARENT_ID).isTrue(); 680 assertThat(lp.endToEnd == ConstraintLayout.LayoutParams.PARENT_ID).isTrue(); 681 }); 682 } 683 684 /** 685 * Ensures a second removal of a complication is a no-op. 686 */ 687 @Test testDoubleRemoval()688 public void testDoubleRemoval() { 689 final ComplicationLayoutEngine engine = createComplicationLayoutEngine(); 690 691 final ViewInfo firstViewInfo = new ViewInfo( 692 new ComplicationLayoutParams( 693 100, 694 100, 695 ComplicationLayoutParams.POSITION_TOP 696 | ComplicationLayoutParams.POSITION_END, 697 ComplicationLayoutParams.DIRECTION_DOWN, 698 0), 699 Complication.CATEGORY_STANDARD, 700 mLayout); 701 702 engine.addComplication(firstViewInfo.id, firstViewInfo.view, firstViewInfo.lp, 703 firstViewInfo.category); 704 verify(mLayout).addView(firstViewInfo.view); 705 706 assertThat(engine.removeComplication(firstViewInfo.id)).isTrue(); 707 verify(firstViewInfo.view).getParent(); 708 verify(mLayout).removeView(firstViewInfo.view); 709 710 Mockito.clearInvocations(mLayout, firstViewInfo.view); 711 assertThat(engine.removeComplication(firstViewInfo.id)).isFalse(); 712 verify(firstViewInfo.view, never()).getParent(); 713 verify(mLayout, never()).removeView(firstViewInfo.view); 714 } 715 716 @Test testGetViews()717 public void testGetViews() { 718 final ComplicationLayoutEngine engine = createComplicationLayoutEngine(); 719 720 final ViewInfo topEndView = new ViewInfo( 721 new ComplicationLayoutParams( 722 100, 723 100, 724 ComplicationLayoutParams.POSITION_TOP 725 | ComplicationLayoutParams.POSITION_END, 726 ComplicationLayoutParams.DIRECTION_DOWN, 727 0), 728 Complication.CATEGORY_STANDARD, 729 mLayout); 730 731 addComplication(engine, topEndView); 732 733 final ViewInfo topStartView = new ViewInfo( 734 new ComplicationLayoutParams( 735 100, 736 100, 737 ComplicationLayoutParams.POSITION_TOP 738 | ComplicationLayoutParams.POSITION_START, 739 ComplicationLayoutParams.DIRECTION_DOWN, 740 0), 741 Complication.CATEGORY_SYSTEM, 742 mLayout); 743 744 addComplication(engine, topStartView); 745 746 final ViewInfo bottomEndView = new ViewInfo( 747 new ComplicationLayoutParams( 748 100, 749 100, 750 ComplicationLayoutParams.POSITION_BOTTOM 751 | ComplicationLayoutParams.POSITION_END, 752 ComplicationLayoutParams.DIRECTION_START, 753 1), 754 Complication.CATEGORY_SYSTEM, 755 mLayout); 756 757 addComplication(engine, bottomEndView); 758 759 verifyViewsAtPosition(engine, ComplicationLayoutParams.POSITION_TOP, topStartView, 760 topEndView); 761 verifyViewsAtPosition(engine, 762 ComplicationLayoutParams.POSITION_TOP | ComplicationLayoutParams.POSITION_START, 763 topStartView); 764 verifyViewsAtPosition(engine, 765 ComplicationLayoutParams.POSITION_BOTTOM, 766 bottomEndView); 767 } 768 verifyViewsAtPosition(ComplicationLayoutEngine engine, int position, ViewInfo... views)769 private void verifyViewsAtPosition(ComplicationLayoutEngine engine, int position, 770 ViewInfo... views) { 771 final List<Integer> idList = engine.getViewsAtPosition(position).stream() 772 .map(View::getId) 773 .collect(Collectors.toList()); 774 775 assertThat(idList).containsExactlyElementsIn( 776 Arrays.stream(views) 777 .map(viewInfo -> viewInfo.view.getId()) 778 .collect(Collectors.toList())); 779 } 780 } 781