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