1 /* 2 * Copyright (C) 2023 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.ui; 18 19 import static androidx.test.core.app.ApplicationProvider.getApplicationContext; 20 21 import static com.android.launcher3.BubbleTextView.DISPLAY_ALL_APPS; 22 import static com.android.launcher3.BubbleTextView.DISPLAY_PREDICTION_ROW; 23 import static com.android.launcher3.BubbleTextView.DISPLAY_SEARCH_RESULT; 24 import static com.android.launcher3.BubbleTextView.DISPLAY_SEARCH_RESULT_SMALL; 25 import static com.android.launcher3.LauncherPrefs.ENABLE_TWOLINE_ALLAPPS_TOGGLE; 26 27 import static com.google.common.truth.Truth.assertThat; 28 29 import static org.junit.Assert.assertEquals; 30 import static org.junit.Assert.assertNotEquals; 31 32 import android.content.ComponentName; 33 import android.content.Context; 34 import android.content.Intent; 35 import android.graphics.Typeface; 36 import android.os.UserHandle; 37 import android.platform.test.flag.junit.SetFlagsRule; 38 import android.view.ViewGroup; 39 40 import androidx.test.ext.junit.runners.AndroidJUnit4; 41 import androidx.test.filters.SmallTest; 42 43 import com.android.launcher3.BubbleTextView; 44 import com.android.launcher3.Flags; 45 import com.android.launcher3.LauncherPrefs; 46 import com.android.launcher3.Utilities; 47 import com.android.launcher3.model.data.AppInfo; 48 import com.android.launcher3.model.data.ItemInfoWithIcon; 49 import com.android.launcher3.search.StringMatcherUtility; 50 import com.android.launcher3.util.ActivityContextWrapper; 51 import com.android.launcher3.util.FlagOp; 52 import com.android.launcher3.util.IntArray; 53 import com.android.launcher3.views.BaseDragLayer; 54 55 import org.junit.Before; 56 import org.junit.Rule; 57 import org.junit.Test; 58 import org.junit.runner.RunWith; 59 import org.mockito.MockitoAnnotations; 60 61 /** 62 * Unit tests for testing modifyTitleToSupportMultiLine() in BubbleTextView.java 63 * This class tests a couple of strings and uses the getMaxLines() to determine if the test passes. 64 * Verifying with getMaxLines() is sufficient since BubbleTextView can only be in one line or 65 * two lines, and this is enough to ensure whether the string should be specifically wrapped onto 66 * the second line and to ensure truncation. 67 */ 68 @SmallTest 69 @RunWith(AndroidJUnit4.class) 70 public class BubbleTextViewTest { 71 72 @Rule public final SetFlagsRule mSetFlagsRule = 73 new SetFlagsRule(SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT); 74 private static final StringMatcherUtility.StringMatcher 75 MATCHER = StringMatcherUtility.StringMatcher.getInstance(); 76 private static final UserHandle WORK_HANDLE = new UserHandle(13); 77 private static final int WORK_FLAG = 1; 78 private static final int ONE_LINE = 1; 79 private static final int TWO_LINE = 2; 80 private static final String TEST_STRING_WITH_SPACE_LONGER_THAN_CHAR_LIMIT = "Battery Stats"; 81 private static final String TEST_STRING_WITH_SPACE_LONGER_THAN_CHAR_LIMIT_RESULT = 82 "Battery\nStats"; 83 private static final String TEST_LONG_STRING_NO_SPACE_LONGER_THAN_CHAR_LIMIT = 84 "flutterappflorafy"; 85 private static final String TEST_LONG_STRING_WITH_SPACE_LONGER_THAN_CHAR_LIMIT = 86 "System UWB Field Test"; 87 private static final String TEST_LONG_STRING_WITH_SPACE_LONGER_THAN_CHAR_LIMIT_RESULT = 88 "System\nUWB Field Test"; 89 private static final String TEST_LONG_STRING_SYMBOL_LONGER_THAN_CHAR_LIMIT = 90 "LEGO®Builder"; 91 private static final String TEST_LONG_STRING_SYMBOL_LONGER_THAN_CHAR_LIMIT_RESULT = 92 "LEGO®\nBuilder"; 93 private static final String EMPTY_STRING = ""; 94 private static final int CHAR_CNT = 7; 95 private static final int MAX_HEIGHT = Integer.MAX_VALUE; 96 private static final int LIMITED_HEIGHT = 357; /* allowedHeight in Pixel6 */ 97 private static final float SPACE_MULTIPLIER = 1; 98 private static final float SPACE_EXTRA = 0; 99 100 private BubbleTextView mBubbleTextView; 101 private ItemInfoWithIcon mItemInfoWithIcon; 102 private Context mContext; 103 private int mLimitedWidth; 104 private AppInfo mGmailAppInfo; 105 private LauncherPrefs mLauncherPrefs; 106 107 @Before setUp()108 public void setUp() throws Exception { 109 MockitoAnnotations.initMocks(this); 110 Utilities.enableRunningInTestHarnessForTests(); 111 mContext = new ActivityContextWrapper(getApplicationContext()); 112 mLauncherPrefs = LauncherPrefs.get(mContext); 113 mBubbleTextView = new BubbleTextView(mContext); 114 mBubbleTextView.reset(); 115 116 BubbleTextView testView = new BubbleTextView(mContext); 117 testView.setTypeface(Typeface.MONOSPACE); 118 testView.setText("B"); 119 // calculate the maxWidth of the textView by calculating the width of one monospace 120 // character * CHAR_CNT 121 mLimitedWidth = 122 (int) (testView.getPaint().measureText(testView.getText().toString()) * CHAR_CNT); 123 // needed otherwise there is a NPE during setText() on checkForRelayout() 124 mBubbleTextView.setLayoutParams( 125 new ViewGroup.LayoutParams(mLimitedWidth, 126 BaseDragLayer.LayoutParams.WRAP_CONTENT)); 127 mItemInfoWithIcon = new ItemInfoWithIcon() { 128 @Override 129 public ItemInfoWithIcon clone() { 130 return null; 131 } 132 }; 133 ComponentName componentName = new ComponentName(mContext, 134 "com.android.launcher3.tests.Activity" + "Gmail"); 135 mGmailAppInfo = new AppInfo(componentName, "Gmail", WORK_HANDLE, new Intent()); 136 } 137 138 @Test testEmptyString_flagOn()139 public void testEmptyString_flagOn() { 140 mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_TWOLINE_TOGGLE); 141 mLauncherPrefs.put(ENABLE_TWOLINE_ALLAPPS_TOGGLE, true); 142 mItemInfoWithIcon.title = EMPTY_STRING; 143 mBubbleTextView.setDisplay(DISPLAY_ALL_APPS); 144 mBubbleTextView.applyLabel(mItemInfoWithIcon); 145 mBubbleTextView.setTypeface(Typeface.MONOSPACE); 146 mBubbleTextView.measure(mLimitedWidth, LIMITED_HEIGHT); 147 148 mBubbleTextView.onPreDraw(); 149 150 assertNotEquals(TWO_LINE, mBubbleTextView.getMaxLines()); 151 } 152 153 @Test testEmptyString_flagOff()154 public void testEmptyString_flagOff() { 155 mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_TWOLINE_TOGGLE); 156 mItemInfoWithIcon.title = EMPTY_STRING; 157 mBubbleTextView.setDisplay(DISPLAY_ALL_APPS); 158 mBubbleTextView.applyLabel(mItemInfoWithIcon); 159 mBubbleTextView.setTypeface(Typeface.MONOSPACE); 160 mBubbleTextView.measure(mLimitedWidth, LIMITED_HEIGHT); 161 162 mBubbleTextView.onPreDraw(); 163 164 assertEquals(ONE_LINE, mBubbleTextView.getLineCount()); 165 } 166 167 @Test testStringWithSpaceLongerThanCharLimit_flagOn()168 public void testStringWithSpaceLongerThanCharLimit_flagOn() { 169 mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_TWOLINE_TOGGLE); 170 mLauncherPrefs.put(ENABLE_TWOLINE_ALLAPPS_TOGGLE, true); 171 // test string: "Battery Stats" 172 mItemInfoWithIcon.title = TEST_STRING_WITH_SPACE_LONGER_THAN_CHAR_LIMIT; 173 mBubbleTextView.applyLabel(mItemInfoWithIcon); 174 mBubbleTextView.setDisplay(DISPLAY_ALL_APPS); 175 mBubbleTextView.setTypeface(Typeface.MONOSPACE); 176 mBubbleTextView.measure(mLimitedWidth, MAX_HEIGHT); 177 178 mBubbleTextView.onPreDraw(); 179 180 assertEquals(TWO_LINE, mBubbleTextView.getLineCount()); 181 } 182 183 @Test testStringWithSpaceLongerThanCharLimit_flagOff()184 public void testStringWithSpaceLongerThanCharLimit_flagOff() { 185 mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_TWOLINE_TOGGLE); 186 // test string: "Battery Stats" 187 mItemInfoWithIcon.title = TEST_STRING_WITH_SPACE_LONGER_THAN_CHAR_LIMIT; 188 mBubbleTextView.applyLabel(mItemInfoWithIcon); 189 mBubbleTextView.setDisplay(DISPLAY_ALL_APPS); 190 mBubbleTextView.setTypeface(Typeface.MONOSPACE); 191 mBubbleTextView.measure(mLimitedWidth, LIMITED_HEIGHT); 192 193 mBubbleTextView.onPreDraw(); 194 195 assertEquals(ONE_LINE, mBubbleTextView.getLineCount()); 196 } 197 198 @Test testLongStringNoSpaceLongerThanCharLimit_flagOn()199 public void testLongStringNoSpaceLongerThanCharLimit_flagOn() { 200 mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_TWOLINE_TOGGLE); 201 mLauncherPrefs.put(ENABLE_TWOLINE_ALLAPPS_TOGGLE, true); 202 // test string: "flutterappflorafy" 203 mItemInfoWithIcon.title = TEST_LONG_STRING_NO_SPACE_LONGER_THAN_CHAR_LIMIT; 204 mBubbleTextView.applyLabel(mItemInfoWithIcon); 205 mBubbleTextView.setDisplay(DISPLAY_ALL_APPS); 206 mBubbleTextView.setTypeface(Typeface.MONOSPACE); 207 mBubbleTextView.measure(mLimitedWidth, LIMITED_HEIGHT); 208 209 mBubbleTextView.onPreDraw(); 210 211 assertEquals(ONE_LINE, mBubbleTextView.getLineCount()); 212 } 213 214 @Test testLongStringNoSpaceLongerThanCharLimit_flagOff()215 public void testLongStringNoSpaceLongerThanCharLimit_flagOff() { 216 mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_TWOLINE_TOGGLE); 217 // test string: "flutterappflorafy" 218 mItemInfoWithIcon.title = TEST_LONG_STRING_NO_SPACE_LONGER_THAN_CHAR_LIMIT; 219 mBubbleTextView.applyLabel(mItemInfoWithIcon); 220 mBubbleTextView.setDisplay(DISPLAY_ALL_APPS); 221 mBubbleTextView.setTypeface(Typeface.MONOSPACE); 222 mBubbleTextView.measure(mLimitedWidth, LIMITED_HEIGHT); 223 224 mBubbleTextView.onPreDraw(); 225 226 assertEquals(ONE_LINE, mBubbleTextView.getLineCount()); 227 } 228 229 @Test testLongStringWithSpaceLongerThanCharLimit_flagOn()230 public void testLongStringWithSpaceLongerThanCharLimit_flagOn() { 231 mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_TWOLINE_TOGGLE); 232 mLauncherPrefs.put(ENABLE_TWOLINE_ALLAPPS_TOGGLE, true); 233 // test string: "System UWB Field Test" 234 mItemInfoWithIcon.title = TEST_LONG_STRING_WITH_SPACE_LONGER_THAN_CHAR_LIMIT; 235 mBubbleTextView.applyLabel(mItemInfoWithIcon); 236 mBubbleTextView.setDisplay(DISPLAY_ALL_APPS); 237 mBubbleTextView.setTypeface(Typeface.MONOSPACE); 238 mBubbleTextView.measure(mLimitedWidth, MAX_HEIGHT); 239 240 mBubbleTextView.onPreDraw(); 241 242 assertEquals(TWO_LINE, mBubbleTextView.getLineCount()); 243 } 244 245 @Test testLongStringWithSpaceLongerThanCharLimit_flagOff()246 public void testLongStringWithSpaceLongerThanCharLimit_flagOff() { 247 mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_TWOLINE_TOGGLE); 248 // test string: "System UWB Field Test" 249 mItemInfoWithIcon.title = TEST_LONG_STRING_WITH_SPACE_LONGER_THAN_CHAR_LIMIT; 250 mBubbleTextView.applyLabel(mItemInfoWithIcon); 251 mBubbleTextView.setDisplay(DISPLAY_ALL_APPS); 252 mBubbleTextView.setTypeface(Typeface.MONOSPACE); 253 mBubbleTextView.measure(mLimitedWidth, LIMITED_HEIGHT); 254 255 mBubbleTextView.onPreDraw(); 256 257 assertEquals(ONE_LINE, mBubbleTextView.getLineCount()); 258 } 259 260 @Test testLongStringSymbolLongerThanCharLimit_flagOn()261 public void testLongStringSymbolLongerThanCharLimit_flagOn() { 262 mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_TWOLINE_TOGGLE); 263 mLauncherPrefs.put(ENABLE_TWOLINE_ALLAPPS_TOGGLE, true); 264 // test string: "LEGO®Builder" 265 mItemInfoWithIcon.title = TEST_LONG_STRING_SYMBOL_LONGER_THAN_CHAR_LIMIT; 266 mBubbleTextView.applyLabel(mItemInfoWithIcon); 267 mBubbleTextView.setDisplay(DISPLAY_ALL_APPS); 268 mBubbleTextView.setTypeface(Typeface.MONOSPACE); 269 mBubbleTextView.measure(mLimitedWidth, MAX_HEIGHT); 270 271 mBubbleTextView.onPreDraw(); 272 273 assertEquals(TWO_LINE, mBubbleTextView.getLineCount()); 274 } 275 276 @Test testLongStringSymbolLongerThanCharLimit_flagOff()277 public void testLongStringSymbolLongerThanCharLimit_flagOff() { 278 mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_TWOLINE_TOGGLE); 279 // test string: "LEGO®Builder" 280 mItemInfoWithIcon.title = TEST_LONG_STRING_SYMBOL_LONGER_THAN_CHAR_LIMIT; 281 mBubbleTextView.applyLabel(mItemInfoWithIcon); 282 mBubbleTextView.setDisplay(DISPLAY_ALL_APPS); 283 mBubbleTextView.setTypeface(Typeface.MONOSPACE); 284 mBubbleTextView.measure(mLimitedWidth, LIMITED_HEIGHT); 285 286 mBubbleTextView.onPreDraw(); 287 288 assertEquals(ONE_LINE, mBubbleTextView.getLineCount()); 289 } 290 291 @Test modifyTitleToSupportMultiLine_TEST_STRING_WITH_SPACE_LONGER_THAN_CHAR_LIMIT()292 public void modifyTitleToSupportMultiLine_TEST_STRING_WITH_SPACE_LONGER_THAN_CHAR_LIMIT() { 293 // test string: "Battery Stats" 294 IntArray breakPoints = StringMatcherUtility.getListOfBreakpoints( 295 TEST_STRING_WITH_SPACE_LONGER_THAN_CHAR_LIMIT, MATCHER); 296 CharSequence newString = BubbleTextView.modifyTitleToSupportMultiLine(mLimitedWidth, 297 MAX_HEIGHT, 298 TEST_STRING_WITH_SPACE_LONGER_THAN_CHAR_LIMIT, mBubbleTextView.getPaint(), 299 breakPoints, 300 SPACE_MULTIPLIER, 301 SPACE_EXTRA); 302 assertEquals(TEST_STRING_WITH_SPACE_LONGER_THAN_CHAR_LIMIT_RESULT, newString); 303 } 304 305 @Test modifyTitleToSupportMultiLine_TEST_LONG_STRING_NO_SPACE_LONGER_THAN_CHAR_LIMIT()306 public void modifyTitleToSupportMultiLine_TEST_LONG_STRING_NO_SPACE_LONGER_THAN_CHAR_LIMIT() { 307 // test string: "flutterappflorafy" 308 IntArray breakPoints = StringMatcherUtility.getListOfBreakpoints( 309 TEST_LONG_STRING_NO_SPACE_LONGER_THAN_CHAR_LIMIT, MATCHER); 310 CharSequence newString = BubbleTextView.modifyTitleToSupportMultiLine(mLimitedWidth, 0, 311 TEST_LONG_STRING_NO_SPACE_LONGER_THAN_CHAR_LIMIT, mBubbleTextView.getPaint(), 312 breakPoints, 313 SPACE_MULTIPLIER, 314 SPACE_EXTRA); 315 assertEquals(TEST_LONG_STRING_NO_SPACE_LONGER_THAN_CHAR_LIMIT, newString); 316 } 317 318 @Test modifyTitleToSupportMultiLine_TEST_LONG_STRING_WITH_SPACE_LONGER_THAN_CHAR_LIMIT()319 public void modifyTitleToSupportMultiLine_TEST_LONG_STRING_WITH_SPACE_LONGER_THAN_CHAR_LIMIT() { 320 // test string: "System UWB Field Test" 321 IntArray breakPoints = StringMatcherUtility.getListOfBreakpoints( 322 TEST_LONG_STRING_WITH_SPACE_LONGER_THAN_CHAR_LIMIT, MATCHER); 323 CharSequence newString = BubbleTextView.modifyTitleToSupportMultiLine(mLimitedWidth, 324 MAX_HEIGHT, 325 TEST_LONG_STRING_WITH_SPACE_LONGER_THAN_CHAR_LIMIT, mBubbleTextView.getPaint(), 326 breakPoints, 327 SPACE_MULTIPLIER, 328 SPACE_EXTRA); 329 assertEquals(TEST_LONG_STRING_WITH_SPACE_LONGER_THAN_CHAR_LIMIT_RESULT, newString); 330 } 331 332 @Test modifyTitleToSupportMultiLine_TEST_LONG_STRING_SYMBOL_LONGER_THAN_CHAR_LIMIT()333 public void modifyTitleToSupportMultiLine_TEST_LONG_STRING_SYMBOL_LONGER_THAN_CHAR_LIMIT() { 334 // test string: "LEGO®Builder" 335 IntArray breakPoints = StringMatcherUtility.getListOfBreakpoints( 336 TEST_LONG_STRING_SYMBOL_LONGER_THAN_CHAR_LIMIT, MATCHER); 337 CharSequence newString = BubbleTextView.modifyTitleToSupportMultiLine( 338 mLimitedWidth, 339 MAX_HEIGHT, 340 TEST_LONG_STRING_SYMBOL_LONGER_THAN_CHAR_LIMIT, mBubbleTextView.getPaint(), 341 breakPoints, 342 SPACE_MULTIPLIER, 343 SPACE_EXTRA); 344 assertEquals(TEST_LONG_STRING_SYMBOL_LONGER_THAN_CHAR_LIMIT_RESULT, newString); 345 } 346 347 @Test testEnsurePredictionRowIsTwoLine()348 public void testEnsurePredictionRowIsTwoLine() { 349 mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_TWOLINE_TOGGLE); 350 mLauncherPrefs.put(ENABLE_TWOLINE_ALLAPPS_TOGGLE, true); 351 // test string: "Battery Stats" 352 mItemInfoWithIcon.title = TEST_STRING_WITH_SPACE_LONGER_THAN_CHAR_LIMIT; 353 mBubbleTextView.setDisplay(DISPLAY_PREDICTION_ROW); 354 mBubbleTextView.applyLabel(mItemInfoWithIcon); 355 mBubbleTextView.setTypeface(Typeface.MONOSPACE); 356 mBubbleTextView.measure(mLimitedWidth, MAX_HEIGHT); 357 358 mBubbleTextView.onPreDraw(); 359 360 assertEquals(TWO_LINE, mBubbleTextView.getLineCount()); 361 } 362 363 @Test modifyTitleToSupportMultiLine_whenLimitedHeight_shouldBeOneLine()364 public void modifyTitleToSupportMultiLine_whenLimitedHeight_shouldBeOneLine() { 365 mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_TWOLINE_TOGGLE); 366 mLauncherPrefs.put(ENABLE_TWOLINE_ALLAPPS_TOGGLE, true); 367 // test string: "LEGO®Builder" 368 mItemInfoWithIcon.title = TEST_LONG_STRING_SYMBOL_LONGER_THAN_CHAR_LIMIT; 369 mBubbleTextView.applyLabel(mItemInfoWithIcon); 370 mBubbleTextView.setTypeface(Typeface.MONOSPACE); 371 mBubbleTextView.measure(mLimitedWidth, LIMITED_HEIGHT); 372 373 mBubbleTextView.onPreDraw(); 374 375 assertEquals(ONE_LINE, mBubbleTextView.getLineCount()); 376 } 377 378 @Test modifyTitleToSupportMultiLine_whenUnlimitedHeight_shouldBeTwoLine()379 public void modifyTitleToSupportMultiLine_whenUnlimitedHeight_shouldBeTwoLine() { 380 mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_TWOLINE_TOGGLE); 381 mLauncherPrefs.put(ENABLE_TWOLINE_ALLAPPS_TOGGLE, true); 382 // test string: "LEGO®Builder" 383 mItemInfoWithIcon.title = TEST_LONG_STRING_SYMBOL_LONGER_THAN_CHAR_LIMIT; 384 mBubbleTextView.setDisplay(DISPLAY_ALL_APPS); 385 mBubbleTextView.applyLabel(mItemInfoWithIcon); 386 mBubbleTextView.setTypeface(Typeface.MONOSPACE); 387 mBubbleTextView.measure(mLimitedWidth, MAX_HEIGHT); 388 389 mBubbleTextView.onPreDraw(); 390 391 assertEquals(TWO_LINE, mBubbleTextView.getLineCount()); 392 } 393 394 @Test applyIconAndLabel_whenDisplay_DISPLAY_SEARCH_RESULT_SMALL_noBadge()395 public void applyIconAndLabel_whenDisplay_DISPLAY_SEARCH_RESULT_SMALL_noBadge() { 396 FlagOp op = FlagOp.NO_OP; 397 // apply the WORK bitmap flag to show work badge 398 mGmailAppInfo.bitmap.flags = op.apply(WORK_FLAG); 399 mBubbleTextView.setDisplay(DISPLAY_SEARCH_RESULT_SMALL); 400 401 mBubbleTextView.applyIconAndLabel(mGmailAppInfo); 402 403 assertThat(mBubbleTextView.getIcon().hasBadge()).isEqualTo(false); 404 } 405 406 @Test applyIconAndLabel_whenDisplay_DISPLAY_SEARCH_RESULT_hasBadge()407 public void applyIconAndLabel_whenDisplay_DISPLAY_SEARCH_RESULT_hasBadge() { 408 FlagOp op = FlagOp.NO_OP; 409 // apply the WORK bitmap flag to show work badge 410 mGmailAppInfo.bitmap.flags = op.apply(WORK_FLAG); 411 mBubbleTextView.setDisplay(DISPLAY_SEARCH_RESULT); 412 413 mBubbleTextView.applyIconAndLabel(mGmailAppInfo); 414 415 assertThat(mBubbleTextView.getIcon().hasBadge()).isEqualTo(true); 416 } 417 } 418