1 /* 2 * Copyright (C) 2008 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.widget.cts; 18 19 import static org.junit.Assert.assertEquals; 20 import static org.junit.Assert.assertFalse; 21 import static org.junit.Assert.assertNotSame; 22 import static org.junit.Assert.assertNull; 23 import static org.junit.Assert.assertSame; 24 import static org.junit.Assert.assertTrue; 25 import static org.junit.Assert.fail; 26 import static org.mockito.Mockito.mock; 27 28 import android.app.Activity; 29 import android.app.AlertDialog; 30 import android.app.Dialog; 31 import android.app.Instrumentation; 32 import android.content.res.Resources; 33 import android.content.res.Resources.NotFoundException; 34 import android.content.res.Resources.Theme; 35 import android.graphics.Color; 36 import android.graphics.drawable.Drawable; 37 import android.view.ContextThemeWrapper; 38 import android.view.Gravity; 39 import android.view.KeyEvent; 40 import android.view.ViewGroup; 41 import android.widget.ArrayAdapter; 42 import android.widget.Spinner; 43 import android.widget.cts.util.TestUtils; 44 45 import androidx.test.InstrumentationRegistry; 46 import androidx.test.annotation.UiThreadTest; 47 import androidx.test.filters.MediumTest; 48 import androidx.test.rule.ActivityTestRule; 49 import androidx.test.runner.AndroidJUnit4; 50 51 import com.android.compatibility.common.util.CtsTouchUtils; 52 import com.android.compatibility.common.util.PollingCheck; 53 import com.android.compatibility.common.util.WidgetTestUtils; 54 55 import org.junit.Before; 56 import org.junit.Rule; 57 import org.junit.Test; 58 import org.junit.runner.RunWith; 59 60 /** 61 * Test {@link Spinner}. 62 */ 63 @MediumTest 64 @RunWith(AndroidJUnit4.class) 65 public class SpinnerTest { 66 private Instrumentation mInstrumentation; 67 private Activity mActivity; 68 private Spinner mSpinnerDialogMode; 69 private Spinner mSpinnerDropdownMode; 70 private static final int SPINNER_HAS_FOCUS_DELAY_MS = 500; 71 72 @Rule 73 public ActivityTestRule<SpinnerCtsActivity> mActivityRule = 74 new ActivityTestRule<>(SpinnerCtsActivity.class); 75 76 @Before setup()77 public void setup() { 78 mInstrumentation = InstrumentationRegistry.getInstrumentation(); 79 mActivity = mActivityRule.getActivity(); 80 mSpinnerDialogMode = (Spinner) mActivity.findViewById(R.id.spinner_dialog_mode); 81 mSpinnerDropdownMode = (Spinner) mActivity.findViewById(R.id.spinner_dropdown_mode); 82 } 83 84 @Test testConstructor()85 public void testConstructor() { 86 new Spinner(mActivity); 87 88 new Spinner(mActivity, null); 89 90 new Spinner(mActivity, null, android.R.attr.spinnerStyle); 91 92 new Spinner(mActivity, Spinner.MODE_DIALOG); 93 94 new Spinner(mActivity, Spinner.MODE_DROPDOWN); 95 96 new Spinner(mActivity, null, android.R.attr.spinnerStyle, Spinner.MODE_DIALOG); 97 98 new Spinner(mActivity, null, android.R.attr.spinnerStyle, Spinner.MODE_DROPDOWN); 99 100 new Spinner(mActivity, null, 0, android.R.style.Widget_DeviceDefault_Spinner, 101 Spinner.MODE_DIALOG); 102 103 new Spinner(mActivity, null, 0, android.R.style.Widget_DeviceDefault_Spinner, 104 Spinner.MODE_DROPDOWN); 105 106 new Spinner(mActivity, null, 0, android.R.style.Widget_DeviceDefault_Light_Spinner, 107 Spinner.MODE_DIALOG); 108 109 new Spinner(mActivity, null, 0, android.R.style.Widget_DeviceDefault_Light_Spinner, 110 Spinner.MODE_DROPDOWN); 111 112 new Spinner(mActivity, null, 0, android.R.style.Widget_Material_Spinner, 113 Spinner.MODE_DIALOG); 114 115 new Spinner(mActivity, null, 0, android.R.style.Widget_Material_Spinner, 116 Spinner.MODE_DROPDOWN); 117 118 new Spinner(mActivity, null, 0, android.R.style.Widget_Material_Spinner_Underlined, 119 Spinner.MODE_DIALOG); 120 121 new Spinner(mActivity, null, 0, android.R.style.Widget_Material_Spinner_Underlined, 122 Spinner.MODE_DROPDOWN); 123 124 new Spinner(mActivity, null, 0, android.R.style.Widget_Material_Light_Spinner, 125 Spinner.MODE_DIALOG); 126 127 new Spinner(mActivity, null, 0, android.R.style.Widget_Material_Light_Spinner, 128 Spinner.MODE_DROPDOWN); 129 130 new Spinner(mActivity, null, 0, android.R.style.Widget_Material_Light_Spinner_Underlined, 131 Spinner.MODE_DIALOG); 132 133 new Spinner(mActivity, null, 0, android.R.style.Widget_Material_Light_Spinner_Underlined, 134 Spinner.MODE_DROPDOWN); 135 136 final Resources.Theme popupTheme = mActivity.getResources().newTheme(); 137 popupTheme.applyStyle(android.R.style.Theme_Material, true); 138 139 new Spinner(mActivity, null, android.R.attr.spinnerStyle, 0, Spinner.MODE_DIALOG, 140 popupTheme); 141 142 new Spinner(mActivity, null, android.R.attr.spinnerStyle, 0, Spinner.MODE_DROPDOWN, 143 popupTheme); 144 } 145 verifyGetBaseline(Spinner spinner)146 private void verifyGetBaseline(Spinner spinner) throws Throwable { 147 assertEquals(-1, spinner.getBaseline()); 148 149 ArrayAdapter<CharSequence> adapter = ArrayAdapter.createFromResource(mActivity, 150 R.array.string, android.R.layout.simple_spinner_item); 151 adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); 152 mActivityRule.runOnUiThread(() -> { 153 spinner.setAdapter(adapter); 154 assertTrue(spinner.getBaseline() > 0); 155 }); 156 } 157 158 @Test testGetBaseline()159 public void testGetBaseline() throws Throwable { 160 verifyGetBaseline(mSpinnerDialogMode); 161 verifyGetBaseline(mSpinnerDropdownMode); 162 } 163 verifySetOnItemClickListener(Spinner spinner)164 private void verifySetOnItemClickListener(Spinner spinner) { 165 try { 166 spinner.setOnItemClickListener(null); 167 fail("Should throw RuntimeException"); 168 } catch (RuntimeException e) { 169 } 170 171 try { 172 spinner.setOnItemClickListener(mock(Spinner.OnItemClickListener.class)); 173 fail("Should throw RuntimeException"); 174 } catch (RuntimeException e) { 175 } 176 } 177 178 @Test testSetOnItemClickListener()179 public void testSetOnItemClickListener() { 180 verifySetOnItemClickListener(mSpinnerDialogMode); 181 verifySetOnItemClickListener(mSpinnerDropdownMode); 182 } 183 verifyPerformClick(Spinner spinner)184 private void verifyPerformClick(Spinner spinner) throws Throwable { 185 mActivityRule.runOnUiThread(() -> assertTrue(spinner.performClick())); 186 } 187 188 @Test testPerformClick()189 public void testPerformClick() throws Throwable { 190 verifyPerformClick(mSpinnerDialogMode); 191 verifyPerformClick(mSpinnerDropdownMode); 192 } 193 verifyOnClick(Spinner spinner)194 private void verifyOnClick(Spinner spinner) { 195 // normal value 196 AlertDialog.Builder builder = new AlertDialog.Builder(mActivity); 197 AlertDialog alertDialog = builder.show(); 198 assertTrue(alertDialog.isShowing()); 199 200 spinner.onClick(alertDialog, 10); 201 assertEquals(10, spinner.getSelectedItemPosition()); 202 assertFalse(alertDialog.isShowing()); 203 204 // exceptional 205 try { 206 spinner.onClick(null, 10); 207 fail("did not throw NullPointerException"); 208 } catch (NullPointerException e) { 209 } 210 211 Dialog dialog = new Dialog(mActivity); 212 dialog.show(); 213 assertTrue(dialog.isShowing()); 214 215 spinner.onClick(dialog, -10); 216 assertEquals(-10, spinner.getSelectedItemPosition()); 217 assertFalse(dialog.isShowing()); 218 } 219 220 @UiThreadTest 221 @Test testOnClick()222 public void testOnClick() { 223 verifyOnClick(mSpinnerDialogMode); 224 verifyOnClick(mSpinnerDropdownMode); 225 } 226 verifyAccessPrompt(Spinner spinner)227 private void verifyAccessPrompt(Spinner spinner) throws Throwable { 228 final String initialPrompt = mActivity.getString(R.string.text_view_hello); 229 assertEquals(initialPrompt, spinner.getPrompt()); 230 231 final String promptText = "prompt text"; 232 233 mActivityRule.runOnUiThread(() -> spinner.setPrompt(promptText)); 234 assertEquals(promptText, spinner.getPrompt()); 235 236 spinner.setPrompt(null); 237 assertNull(spinner.getPrompt()); 238 } 239 240 @Test testAccessPrompt()241 public void testAccessPrompt() throws Throwable { 242 verifyAccessPrompt(mSpinnerDialogMode); 243 verifyAccessPrompt(mSpinnerDropdownMode); 244 } 245 verifySetPromptId(Spinner spinner)246 private void verifySetPromptId(Spinner spinner) throws Throwable { 247 mActivityRule.runOnUiThread(() -> spinner.setPromptId(R.string.hello_world)); 248 assertEquals(mActivity.getString(R.string.hello_world), spinner.getPrompt()); 249 250 try { 251 spinner.setPromptId(-1); 252 fail("Should throw NotFoundException"); 253 } catch (NotFoundException e) { 254 // issue 1695243, not clear what is supposed to happen if promptId is exceptional. 255 } 256 257 try { 258 spinner.setPromptId(Integer.MAX_VALUE); 259 fail("Should throw NotFoundException"); 260 } catch (NotFoundException e) { 261 // issue 1695243, not clear what is supposed to happen if promptId is exceptional. 262 } 263 } 264 265 @Test testSetPromptId()266 public void testSetPromptId() throws Throwable { 267 verifySetPromptId(mSpinnerDialogMode); 268 verifySetPromptId(mSpinnerDropdownMode); 269 } 270 271 @UiThreadTest 272 @Test testGetPopupContext()273 public void testGetPopupContext() { 274 Theme theme = mActivity.getResources().newTheme(); 275 Spinner themeSpinner = new Spinner(mActivity, null, 276 android.R.attr.spinnerStyle, 0, Spinner.MODE_DIALOG, theme); 277 assertNotSame(mActivity, themeSpinner.getPopupContext()); 278 assertSame(theme, themeSpinner.getPopupContext().getTheme()); 279 280 ContextThemeWrapper context = (ContextThemeWrapper)themeSpinner.getPopupContext(); 281 assertSame(mActivity, context.getBaseContext()); 282 } 283 verifyGravity(Spinner spinner)284 private void verifyGravity(Spinner spinner) throws Throwable { 285 // Note that here we're using a custom layout for the spinner's selected item 286 // that doesn't span the whole width of the parent. That way we're exercising the 287 // relevant path in spinner's layout pass that handles the currently set gravity 288 ArrayAdapter<CharSequence> adapter = ArrayAdapter.createFromResource(mActivity, 289 R.array.string, R.layout.simple_spinner_item_layout); 290 adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); 291 mActivityRule.runOnUiThread(() -> spinner.setAdapter(adapter)); 292 293 WidgetTestUtils.runOnMainAndDrawSync(mActivityRule, spinner, () -> { 294 spinner.setSelection(1); 295 spinner.getLayoutParams().width = ViewGroup.LayoutParams.MATCH_PARENT; 296 spinner.requestLayout(); 297 }); 298 299 mActivityRule.runOnUiThread(() -> spinner.setGravity(Gravity.LEFT)); 300 assertEquals(Gravity.LEFT, spinner.getGravity()); 301 302 mActivityRule.runOnUiThread(() -> spinner.setGravity(Gravity.CENTER_HORIZONTAL)); 303 assertEquals(Gravity.CENTER_HORIZONTAL, spinner.getGravity()); 304 305 mActivityRule.runOnUiThread((() -> spinner.setGravity(Gravity.RIGHT))); 306 assertEquals(Gravity.RIGHT, spinner.getGravity()); 307 308 mActivityRule.runOnUiThread(() -> spinner.setGravity(Gravity.START)); 309 assertEquals(Gravity.START, spinner.getGravity()); 310 311 mActivityRule.runOnUiThread(() -> spinner.setGravity(Gravity.END)); 312 assertEquals(Gravity.END, spinner.getGravity()); 313 } 314 315 @Test testGravity()316 public void testGravity() throws Throwable { 317 verifyGravity(mSpinnerDialogMode); 318 verifyGravity(mSpinnerDropdownMode); 319 } 320 321 @Test testDropDownMetricsDropdownMode()322 public void testDropDownMetricsDropdownMode() throws Throwable { 323 ArrayAdapter<CharSequence> adapter = ArrayAdapter.createFromResource(mActivity, 324 R.array.string, android.R.layout.simple_spinner_item); 325 adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); 326 mActivityRule.runOnUiThread(() -> mSpinnerDropdownMode.setAdapter(adapter)); 327 328 final Resources res = mActivity.getResources(); 329 final int dropDownWidth = res.getDimensionPixelSize(R.dimen.spinner_dropdown_width); 330 final int dropDownOffsetHorizontal = 331 res.getDimensionPixelSize(R.dimen.spinner_dropdown_offset_h); 332 final int dropDownOffsetVertical = 333 res.getDimensionPixelSize(R.dimen.spinner_dropdown_offset_v); 334 335 mActivityRule.runOnUiThread(() -> { 336 mSpinnerDropdownMode.setDropDownWidth(dropDownWidth); 337 mSpinnerDropdownMode.setDropDownHorizontalOffset(dropDownOffsetHorizontal); 338 mSpinnerDropdownMode.setDropDownVerticalOffset(dropDownOffsetVertical); 339 }); 340 341 // Use instrumentation to emulate a tap on the spinner to bring down its popup 342 CtsTouchUtils.emulateTapOnViewCenter(mInstrumentation, mActivityRule, mSpinnerDropdownMode); 343 // Verify that we're showing the popup 344 PollingCheck.waitFor(() -> mSpinnerDropdownMode.isPopupShowing()); 345 346 // And test its attributes 347 assertEquals(dropDownWidth, mSpinnerDropdownMode.getDropDownWidth()); 348 // TODO: restore when b/28089349 is addressed 349 // assertEquals(dropDownOffsetHorizontal, 350 // mSpinnerDropdownMode.getDropDownHorizontalOffset()); 351 assertEquals(dropDownOffsetVertical, mSpinnerDropdownMode.getDropDownVerticalOffset()); 352 } 353 354 @Test testDropDownMetricsDialogMode()355 public void testDropDownMetricsDialogMode() throws Throwable { 356 ArrayAdapter<CharSequence> adapter = ArrayAdapter.createFromResource(mActivity, 357 R.array.string, android.R.layout.simple_spinner_item); 358 adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); 359 mActivityRule.runOnUiThread(() -> mSpinnerDialogMode.setAdapter(adapter)); 360 361 final Resources res = mActivity.getResources(); 362 final int dropDownWidth = res.getDimensionPixelSize(R.dimen.spinner_dropdown_width); 363 final int dropDownOffsetHorizontal = 364 res.getDimensionPixelSize(R.dimen.spinner_dropdown_offset_h); 365 final int dropDownOffsetVertical = 366 res.getDimensionPixelSize(R.dimen.spinner_dropdown_offset_v); 367 368 mActivityRule.runOnUiThread(() -> { 369 // These are all expected to be no-ops 370 mSpinnerDialogMode.setDropDownWidth(dropDownWidth); 371 mSpinnerDialogMode.setDropDownHorizontalOffset(dropDownOffsetHorizontal); 372 mSpinnerDialogMode.setDropDownVerticalOffset(dropDownOffsetVertical); 373 }); 374 375 // Use instrumentation to emulate a tap on the spinner to bring down its popup 376 CtsTouchUtils.emulateTapOnViewCenter(mInstrumentation, null, mSpinnerDialogMode); 377 // Verify that we're showing the popup 378 PollingCheck.waitFor(() -> mSpinnerDialogMode.isPopupShowing()); 379 380 // And test its attributes. Note that we are not testing the result of getDropDownWidth 381 // for this mode 382 assertEquals(0, mSpinnerDialogMode.getDropDownHorizontalOffset()); 383 assertEquals(0, mSpinnerDialogMode.getDropDownVerticalOffset()); 384 } 385 386 @Test testDropDownBackgroundDropdownMode()387 public void testDropDownBackgroundDropdownMode() throws Throwable { 388 ArrayAdapter<CharSequence> adapter = ArrayAdapter.createFromResource(mActivity, 389 R.array.string, android.R.layout.simple_spinner_item); 390 adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); 391 mActivityRule.runOnUiThread(() -> mSpinnerDropdownMode.setAdapter(adapter)); 392 393 // Set blue background on the popup 394 mActivityRule.runOnUiThread(() -> 395 mSpinnerDropdownMode.setPopupBackgroundResource(R.drawable.blue_fill)); 396 397 // Use instrumentation to emulate a tap on the spinner to bring down its popup 398 CtsTouchUtils.emulateTapOnViewCenter(mInstrumentation, mActivityRule, mSpinnerDropdownMode); 399 // Verify that we're showing the popup 400 PollingCheck.waitFor(() -> mSpinnerDropdownMode.isPopupShowing()); 401 // And test its fill 402 Drawable dropDownBackground = mSpinnerDropdownMode.getPopupBackground(); 403 TestUtils.assertAllPixelsOfColor("Drop down should be blue", dropDownBackground, 404 dropDownBackground.getBounds().width(), dropDownBackground.getBounds().height(), 405 false, Color.BLUE, 1, true); 406 waitForHasFocusMS(SPINNER_HAS_FOCUS_DELAY_MS); 407 // Dismiss the popup with the emulated back key 408 mInstrumentation.sendKeyDownUpSync(KeyEvent.KEYCODE_BACK); 409 // Verify that we're not showing the popup 410 PollingCheck.waitFor(() -> !mSpinnerDropdownMode.isPopupShowing()); 411 412 // Set yellow background on the popup 413 mActivityRule.runOnUiThread(() -> 414 mSpinnerDropdownMode.setPopupBackgroundDrawable( 415 mActivity.getDrawable(R.drawable.yellow_fill))); 416 417 // Use instrumentation to emulate a tap on the spinner to bring down its popup 418 WidgetTestUtils.runOnMainAndDrawSync(mActivityRule, mSpinnerDropdownMode, () -> { 419 mSpinnerDropdownMode.performClick(); 420 }); 421 // Verify that we're showing the popup 422 PollingCheck.waitFor(() -> mSpinnerDropdownMode.isPopupShowing()); 423 // And test its fill 424 dropDownBackground = mSpinnerDropdownMode.getPopupBackground(); 425 TestUtils.assertAllPixelsOfColor("Drop down should be yellow", dropDownBackground, 426 dropDownBackground.getBounds().width(), dropDownBackground.getBounds().height(), 427 false, Color.YELLOW, 1, true); 428 } 429 430 @Test testDropDownBackgroundDialogMode()431 public void testDropDownBackgroundDialogMode() throws Throwable { 432 ArrayAdapter<CharSequence> adapter = ArrayAdapter.createFromResource(mActivity, 433 R.array.string, android.R.layout.simple_spinner_item); 434 adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); 435 mActivityRule.runOnUiThread(() -> mSpinnerDialogMode.setAdapter(adapter)); 436 437 // Set blue background on the popup 438 mActivityRule.runOnUiThread(() -> 439 mSpinnerDialogMode.setPopupBackgroundResource(R.drawable.blue_fill)); 440 441 // Use instrumentation to emulate a tap on the spinner to bring down its popup 442 CtsTouchUtils.emulateTapOnViewCenter(mInstrumentation, null, mSpinnerDialogMode); 443 // Verify that we're showing the popup 444 PollingCheck.waitFor(() -> mSpinnerDialogMode.isPopupShowing()); 445 // And test that getPopupBackground returns null 446 assertNull(mSpinnerDialogMode.getPopupBackground()); 447 448 // Dismiss the popup with the emulated back key 449 mInstrumentation.sendKeyDownUpSync(KeyEvent.KEYCODE_BACK); 450 // Verify that we're not showing the popup 451 PollingCheck.waitFor(() -> !mSpinnerDropdownMode.isPopupShowing()); 452 453 // Set yellow background on the popup 454 mActivityRule.runOnUiThread(() -> 455 mSpinnerDialogMode.setPopupBackgroundDrawable( 456 mActivity.getDrawable(R.drawable.yellow_fill))); 457 458 // Use instrumentation to emulate a tap on the spinner to bring down its popup 459 CtsTouchUtils.emulateTapOnViewCenter(mInstrumentation, null, mSpinnerDialogMode); 460 // Verify that we're showing the popup 461 PollingCheck.waitFor(() -> mSpinnerDialogMode.isPopupShowing()); 462 // And test that getPopupBackground returns null 463 assertNull(mSpinnerDialogMode.getPopupBackground()); 464 } 465 waitForHasFocusMS(int milliseconds)466 private void waitForHasFocusMS(int milliseconds) { 467 try { 468 Thread.sleep(milliseconds); 469 } catch (InterruptedException e) { 470 fail("unexpected InterruptedException : "+ e); 471 } 472 473 } 474 } 475