1 /* 2 * Copyright (C) 2018 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.systemui.volume; 18 19 import static android.media.AudioManager.RINGER_MODE_NORMAL; 20 import static android.media.AudioManager.RINGER_MODE_SILENT; 21 import static android.media.AudioManager.RINGER_MODE_VIBRATE; 22 23 import static com.android.systemui.Flags.FLAG_HAPTIC_VOLUME_SLIDER; 24 import static com.android.systemui.volume.Events.DISMISS_REASON_UNKNOWN; 25 import static com.android.systemui.volume.Events.SHOW_REASON_UNKNOWN; 26 import static com.android.systemui.volume.VolumeDialogControllerImpl.STREAMS; 27 28 import static junit.framework.Assert.assertEquals; 29 import static junit.framework.Assert.assertFalse; 30 import static junit.framework.Assert.assertNotNull; 31 import static junit.framework.Assert.assertNotSame; 32 import static junit.framework.Assert.assertTrue; 33 34 import static org.junit.Assume.assumeNotNull; 35 import static org.mockito.ArgumentMatchers.any; 36 import static org.mockito.ArgumentMatchers.anyInt; 37 import static org.mockito.ArgumentMatchers.eq; 38 import static org.mockito.Mockito.never; 39 import static org.mockito.Mockito.reset; 40 import static org.mockito.Mockito.times; 41 import static org.mockito.Mockito.verify; 42 import static org.mockito.Mockito.when; 43 44 import android.app.KeyguardManager; 45 import android.content.res.Configuration; 46 import android.graphics.Bitmap; 47 import android.graphics.Canvas; 48 import android.graphics.drawable.Drawable; 49 import android.media.AudioManager; 50 import android.media.AudioSystem; 51 import android.os.SystemClock; 52 import android.platform.test.annotations.DisableFlags; 53 import android.platform.test.annotations.EnableFlags; 54 import android.provider.Settings; 55 import android.testing.TestableLooper; 56 import android.util.Log; 57 import android.view.Gravity; 58 import android.view.InputDevice; 59 import android.view.MotionEvent; 60 import android.view.View; 61 import android.view.ViewGroup; 62 import android.view.accessibility.AccessibilityManager; 63 import android.widget.ImageButton; 64 import android.widget.SeekBar; 65 66 import androidx.test.core.view.MotionEventBuilder; 67 import androidx.test.ext.junit.runners.AndroidJUnit4; 68 import androidx.test.filters.SmallTest; 69 70 import com.android.internal.jank.InteractionJankMonitor; 71 import com.android.internal.logging.testing.UiEventLoggerFake; 72 import com.android.systemui.Prefs; 73 import com.android.systemui.SysuiTestCase; 74 import com.android.systemui.animation.AnimatorTestRule; 75 import com.android.systemui.dump.DumpManager; 76 import com.android.systemui.media.dialog.MediaOutputDialogManager; 77 import com.android.systemui.plugins.VolumeDialogController; 78 import com.android.systemui.plugins.VolumeDialogController.State; 79 import com.android.systemui.res.R; 80 import com.android.systemui.statusbar.VibratorHelper; 81 import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper; 82 import com.android.systemui.statusbar.policy.ConfigurationController; 83 import com.android.systemui.statusbar.policy.DevicePostureController; 84 import com.android.systemui.statusbar.policy.DeviceProvisionedController; 85 import com.android.systemui.statusbar.policy.FakeConfigurationController; 86 import com.android.systemui.util.settings.FakeSettings; 87 import com.android.systemui.util.settings.SecureSettings; 88 import com.android.systemui.util.time.FakeSystemClock; 89 import com.android.systemui.volume.domain.interactor.VolumeDialogInteractor; 90 import com.android.systemui.volume.domain.interactor.VolumePanelNavigationInteractor; 91 import com.android.systemui.volume.panel.shared.flag.VolumePanelFlag; 92 import com.android.systemui.volume.ui.navigation.VolumeNavigator; 93 94 import dagger.Lazy; 95 96 import junit.framework.Assert; 97 98 import org.junit.After; 99 import org.junit.Before; 100 import org.junit.Rule; 101 import org.junit.Test; 102 import org.junit.runner.RunWith; 103 import org.mockito.ArgumentCaptor; 104 import org.mockito.Mock; 105 import org.mockito.Mockito; 106 import org.mockito.MockitoAnnotations; 107 108 import java.util.Arrays; 109 import java.util.function.Predicate; 110 111 @SmallTest 112 @RunWith(AndroidJUnit4.class) 113 @TestableLooper.RunWithLooper(setAsMainLooper = true) 114 public class VolumeDialogImplTest extends SysuiTestCase { 115 VolumeDialogImpl mDialog; 116 View mActiveRinger; 117 View mDrawerContainer; 118 View mDrawerVibrate; 119 View mDrawerMute; 120 View mDrawerNormal; 121 ViewGroup mDialogRowsView; 122 CaptionsToggleImageButton mODICaptionsIcon; 123 private TestableLooper mTestableLooper; 124 private ConfigurationController mConfigurationController; 125 private int mOriginalOrientation; 126 127 private static final String TAG = "VolumeDialogImplTest"; 128 129 @Mock 130 VolumeDialogController mVolumeDialogController; 131 @Mock 132 KeyguardManager mKeyguard; 133 @Mock 134 AccessibilityManagerWrapper mAccessibilityMgr; 135 @Mock 136 DeviceProvisionedController mDeviceProvisionedController; 137 @Mock 138 MediaOutputDialogManager mMediaOutputDialogManager; 139 @Mock 140 InteractionJankMonitor mInteractionJankMonitor; 141 @Mock 142 private DumpManager mDumpManager; 143 @Mock CsdWarningDialog mCsdWarningDialog; 144 @Mock 145 DevicePostureController mPostureController; 146 @Mock 147 private Lazy<SecureSettings> mLazySecureSettings; 148 @Mock 149 private VolumePanelNavigationInteractor mVolumePanelNavigationInteractor; 150 @Mock 151 private VolumeNavigator mVolumeNavigator; 152 @Mock 153 private VolumePanelFlag mVolumePanelFlag; 154 @Mock 155 private VolumeDialogInteractor mVolumeDialogInteractor; 156 157 private final CsdWarningDialog.Factory mCsdWarningDialogFactory = 158 new CsdWarningDialog.Factory() { 159 @Override 160 public CsdWarningDialog create(int warningType, Runnable onCleanup) { 161 return mCsdWarningDialog; 162 } 163 }; 164 @Mock 165 private VibratorHelper mVibratorHelper; 166 167 private int mLongestHideShowAnimationDuration = 250; 168 private FakeSettings mSecureSettings; 169 170 @Rule 171 public final AnimatorTestRule mAnimatorTestRule = new AnimatorTestRule(this); 172 173 @Before setup()174 public void setup() throws Exception { 175 MockitoAnnotations.initMocks(this); 176 177 getContext().addMockSystemService(KeyguardManager.class, mKeyguard); 178 179 mTestableLooper = TestableLooper.get(this); 180 allowTestableLooperAsMainThread(); 181 182 when(mPostureController.getDevicePosture()) 183 .thenReturn(DevicePostureController.DEVICE_POSTURE_CLOSED); 184 185 int hideDialogDuration = mContext.getResources() 186 .getInteger(R.integer.config_dialogHideAnimationDurationMs); 187 int showDialogDuration = mContext.getResources() 188 .getInteger(R.integer.config_dialogShowAnimationDurationMs); 189 190 mLongestHideShowAnimationDuration = Math.max(hideDialogDuration, showDialogDuration); 191 192 mOriginalOrientation = mContext.getResources().getConfiguration().orientation; 193 194 mConfigurationController = new FakeConfigurationController(); 195 196 mSecureSettings = new FakeSettings(); 197 198 when(mLazySecureSettings.get()).thenReturn(mSecureSettings); 199 200 when(mVibratorHelper.getPrimitiveDurations(anyInt())).thenReturn(new int[]{0}); 201 202 mDialog = new VolumeDialogImpl( 203 getContext(), 204 mVolumeDialogController, 205 mAccessibilityMgr, 206 mDeviceProvisionedController, 207 mConfigurationController, 208 mMediaOutputDialogManager, 209 mInteractionJankMonitor, 210 mVolumePanelNavigationInteractor, 211 mVolumeNavigator, 212 false, 213 mCsdWarningDialogFactory, 214 mPostureController, 215 mTestableLooper.getLooper(), 216 mVolumePanelFlag, 217 mDumpManager, 218 mLazySecureSettings, 219 mVibratorHelper, 220 new FakeSystemClock(), 221 mVolumeDialogInteractor); 222 mDialog.init(0, null); 223 State state = createShellState(); 224 mDialog.onStateChangedH(state); 225 226 mActiveRinger = mDialog.getDialogView().findViewById( 227 R.id.volume_new_ringer_active_icon_container); 228 mDrawerContainer = mDialog.getDialogView().findViewById(R.id.volume_drawer_container); 229 230 // Drawer is not always available, e.g. on TVs 231 if (mDrawerContainer != null) { 232 mDrawerVibrate = mDrawerContainer.findViewById(R.id.volume_drawer_vibrate); 233 mDrawerMute = mDrawerContainer.findViewById(R.id.volume_drawer_mute); 234 mDrawerNormal = mDrawerContainer.findViewById(R.id.volume_drawer_normal); 235 } 236 mODICaptionsIcon = mDialog.getDialogView().findViewById(R.id.odi_captions_icon); 237 238 mDialogRowsView = mDialog.getDialogView().findViewById(R.id.volume_dialog_rows); 239 240 Prefs.putInt(mContext, 241 Prefs.Key.SEEN_RINGER_GUIDANCE_COUNT, 242 VolumePrefs.SHOW_RINGER_TOAST_COUNT + 1); 243 244 Prefs.putBoolean(mContext, Prefs.Key.HAS_SEEN_ODI_CAPTIONS_TOOLTIP, false); 245 } 246 assumeHasDrawer()247 private void assumeHasDrawer() { 248 assumeNotNull("Layout does not contain drawer", mDrawerContainer); 249 } 250 createShellState()251 private State createShellState() { 252 State state = new VolumeDialogController.State(); 253 for (int i = AudioManager.STREAM_VOICE_CALL; i <= AudioManager.STREAM_ACCESSIBILITY; i++) { 254 VolumeDialogController.StreamState ss = new VolumeDialogController.StreamState(); 255 ss.name = STREAMS.get(i); 256 ss.level = 1; 257 ss.levelMin = 0; 258 ss.levelMax = 25; 259 state.states.append(i, ss); 260 } 261 return state; 262 } 263 navigateViews(View view, Predicate<View> condition)264 private void navigateViews(View view, Predicate<View> condition) { 265 if (view instanceof ViewGroup) { 266 ViewGroup viewGroup = (ViewGroup) view; 267 for (int i = 0; i < viewGroup.getChildCount(); i++) { 268 navigateViews(viewGroup.getChildAt(i), condition); 269 } 270 } else { 271 String resourceName = null; 272 try { 273 resourceName = getContext().getResources().getResourceName(view.getId()); 274 } catch (Exception e) {} 275 assertTrue("View " + resourceName != null ? resourceName : view.getId() 276 + " failed test", condition.test(view)); 277 } 278 } 279 280 @Test 281 @DisableFlags(FLAG_HAPTIC_VOLUME_SLIDER) addSliderHaptics_withHapticsDisabled_doesNotDeliverOnProgressChangedHaptics()282 public void addSliderHaptics_withHapticsDisabled_doesNotDeliverOnProgressChangedHaptics() { 283 // GIVEN that the slider haptics flag is disabled and we try to add haptics to volume rows 284 mDialog.addSliderHapticsToRows(); 285 286 // WHEN haptics try to be delivered to a volume stream 287 boolean canDeliverHaptics = 288 mDialog.canDeliverProgressHapticsToStream(AudioSystem.STREAM_MUSIC, true, 50); 289 290 // THEN the result is that haptics are not successfully delivered 291 assertFalse(canDeliverHaptics); 292 } 293 294 @Test 295 @EnableFlags(FLAG_HAPTIC_VOLUME_SLIDER) addSliderHaptics_withHapticsEnabled_canDeliverOnProgressChangedHaptics()296 public void addSliderHaptics_withHapticsEnabled_canDeliverOnProgressChangedHaptics() { 297 // GIVEN that the slider haptics flag is enabled and we try to add haptics to volume rows 298 mDialog.addSliderHapticsToRows(); 299 300 // WHEN haptics try to be delivered to a volume stream 301 boolean canDeliverHaptics = 302 mDialog.canDeliverProgressHapticsToStream(AudioSystem.STREAM_MUSIC, true, 50); 303 304 // THEN the result is that haptics are successfully delivered 305 assertTrue(canDeliverHaptics); 306 } 307 308 @Test testComputeTimeout()309 public void testComputeTimeout() { 310 Mockito.reset(mAccessibilityMgr); 311 mDialog.rescheduleTimeoutH(); 312 verify(mAccessibilityMgr).getRecommendedTimeoutMillis( 313 VolumeDialogImpl.DIALOG_TIMEOUT_MILLIS, 314 AccessibilityManager.FLAG_CONTENT_CONTROLS); 315 } 316 317 @Test testSetTimeoutValue_ComputeTimeout()318 public void testSetTimeoutValue_ComputeTimeout() { 319 mSecureSettings.putInt(Settings.Secure.VOLUME_DIALOG_DISMISS_TIMEOUT, 7000); 320 Mockito.reset(mAccessibilityMgr); 321 mDialog.init(0, null); 322 mDialog.rescheduleTimeoutH(); 323 verify(mAccessibilityMgr).getRecommendedTimeoutMillis( 324 7000, 325 AccessibilityManager.FLAG_CONTENT_CONTROLS); 326 } 327 328 @Test testComputeTimeout_tooltip()329 public void testComputeTimeout_tooltip() { 330 Mockito.reset(mAccessibilityMgr); 331 mDialog.showCaptionsTooltip(); 332 verify(mAccessibilityMgr).getRecommendedTimeoutMillis( 333 VolumeDialogImpl.DIALOG_ODI_CAPTIONS_TOOLTIP_TIMEOUT_MILLIS, 334 AccessibilityManager.FLAG_CONTENT_CONTROLS 335 | AccessibilityManager.FLAG_CONTENT_TEXT); 336 } 337 338 @Test testComputeTimeout_withHovering()339 public void testComputeTimeout_withHovering() { 340 Mockito.reset(mAccessibilityMgr); 341 View dialog = mDialog.getDialogView(); 342 long uptimeMillis = SystemClock.uptimeMillis(); 343 MotionEvent event = MotionEvent.obtain(uptimeMillis, uptimeMillis, 344 MotionEvent.ACTION_HOVER_ENTER, 0, 0, 0); 345 event.setSource(InputDevice.SOURCE_TOUCHSCREEN); 346 dialog.dispatchGenericMotionEvent(event); 347 event.recycle(); 348 verify(mAccessibilityMgr).getRecommendedTimeoutMillis( 349 VolumeDialogImpl.DIALOG_HOVERING_TIMEOUT_MILLIS, 350 AccessibilityManager.FLAG_CONTENT_CONTROLS); 351 } 352 353 @Test testComputeTimeout_withSafetyWarningOn()354 public void testComputeTimeout_withSafetyWarningOn() { 355 Mockito.reset(mAccessibilityMgr); 356 ArgumentCaptor<VolumeDialogController.Callbacks> controllerCallbackCapture = 357 ArgumentCaptor.forClass(VolumeDialogController.Callbacks.class); 358 verify(mVolumeDialogController).addCallback(controllerCallbackCapture.capture(), any()); 359 VolumeDialogController.Callbacks callbacks = controllerCallbackCapture.getValue(); 360 361 callbacks.onShowSafetyWarning(AudioManager.FLAG_SHOW_UI); 362 verify(mAccessibilityMgr).getRecommendedTimeoutMillis( 363 VolumeDialogImpl.DIALOG_SAFETYWARNING_TIMEOUT_MILLIS, 364 AccessibilityManager.FLAG_CONTENT_TEXT 365 | AccessibilityManager.FLAG_CONTENT_CONTROLS); 366 } 367 368 @Test testComputeTimeout_standard()369 public void testComputeTimeout_standard() { 370 Mockito.reset(mAccessibilityMgr); 371 mDialog.tryToRemoveCaptionsTooltip(); 372 mDialog.rescheduleTimeoutH(); 373 verify(mAccessibilityMgr).getRecommendedTimeoutMillis( 374 VolumeDialogImpl.DIALOG_TIMEOUT_MILLIS, 375 AccessibilityManager.FLAG_CONTENT_CONTROLS); 376 } 377 378 @Test testVibrateOnRingerChangedToVibrate()379 public void testVibrateOnRingerChangedToVibrate() { 380 final State initialSilentState = new State(); 381 initialSilentState.ringerModeInternal = AudioManager.RINGER_MODE_SILENT; 382 383 final State vibrateState = new State(); 384 vibrateState.ringerModeInternal = AudioManager.RINGER_MODE_VIBRATE; 385 386 // change ringer to silent 387 mDialog.onStateChangedH(initialSilentState); 388 389 // expected: shouldn't call vibrate yet 390 verify(mVolumeDialogController, never()).vibrate(any()); 391 392 // changed ringer to vibrate 393 mDialog.onStateChangedH(vibrateState); 394 395 // expected: vibrate device 396 verify(mVolumeDialogController).vibrate(any()); 397 } 398 399 @Test testNoVibrateOnRingerInitialization()400 public void testNoVibrateOnRingerInitialization() { 401 final State initialUnsetState = new State(); 402 initialUnsetState.ringerModeInternal = -1; 403 404 // ringer not initialized yet: 405 mDialog.onStateChangedH(initialUnsetState); 406 407 final State vibrateState = new State(); 408 vibrateState.ringerModeInternal = AudioManager.RINGER_MODE_VIBRATE; 409 410 // changed ringer to vibrate 411 mDialog.onStateChangedH(vibrateState); 412 413 // shouldn't call vibrate 414 verify(mVolumeDialogController, never()).vibrate(any()); 415 } 416 417 @Test testSelectVibrateFromDrawer()418 public void testSelectVibrateFromDrawer() { 419 assumeHasDrawer(); 420 421 final State initialUnsetState = new State(); 422 initialUnsetState.ringerModeInternal = AudioManager.RINGER_MODE_NORMAL; 423 mDialog.onStateChangedH(initialUnsetState); 424 425 mActiveRinger.performClick(); 426 mDrawerVibrate.performClick(); 427 428 // Make sure we've actually changed the ringer mode. 429 verify(mVolumeDialogController, times(1)).setRingerMode( 430 AudioManager.RINGER_MODE_VIBRATE, false); 431 } 432 433 @Test testSelectMuteFromDrawer()434 public void testSelectMuteFromDrawer() { 435 assumeHasDrawer(); 436 437 final State initialUnsetState = new State(); 438 initialUnsetState.ringerModeInternal = AudioManager.RINGER_MODE_NORMAL; 439 mDialog.onStateChangedH(initialUnsetState); 440 441 mActiveRinger.performClick(); 442 mDrawerMute.performClick(); 443 444 // Make sure we've actually changed the ringer mode. 445 verify(mVolumeDialogController, times(1)).setRingerMode( 446 AudioManager.RINGER_MODE_SILENT, false); 447 } 448 449 @Test testSelectNormalFromDrawer()450 public void testSelectNormalFromDrawer() { 451 assumeHasDrawer(); 452 453 final State initialUnsetState = new State(); 454 initialUnsetState.ringerModeInternal = AudioManager.RINGER_MODE_VIBRATE; 455 mDialog.onStateChangedH(initialUnsetState); 456 457 mActiveRinger.performClick(); 458 mDrawerNormal.performClick(); 459 460 // Make sure we've actually changed the ringer mode. 461 verify(mVolumeDialogController, times(1)).setRingerMode( 462 AudioManager.RINGER_MODE_NORMAL, false); 463 } 464 465 /** 466 * Ideally we would look at the ringer ImageView and check its assigned drawable id, but that 467 * API does not exist. So we do the next best thing; we check the cached icon id. 468 */ 469 @Test notificationVolumeSeparated_theRingerIconChangesToSpeakerIcon()470 public void notificationVolumeSeparated_theRingerIconChangesToSpeakerIcon() { 471 // already separated. assert icon is new based on res id 472 assertEquals(mDialog.mVolumeRingerIconDrawableId, 473 R.drawable.ic_speaker_on); 474 assertEquals(mDialog.mVolumeRingerMuteIconDrawableId, 475 R.drawable.ic_speaker_mute); 476 } 477 478 @Test testDialogDismissAnimation_notifyVisibleIsNotCalledBeforeAnimation()479 public void testDialogDismissAnimation_notifyVisibleIsNotCalledBeforeAnimation() { 480 mDialog.dismissH(DISMISS_REASON_UNKNOWN); 481 // notifyVisible(false) should not be called immediately but only after the dismiss 482 // animation has ended. 483 verify(mVolumeDialogController, times(0)).notifyVisible(false); 484 mDialog.getDialogView().animate().cancel(); 485 } 486 487 @Test showCsdWarning_dialogShown()488 public void showCsdWarning_dialogShown() { 489 mDialog.showCsdWarningH(AudioManager.CSD_WARNING_DOSE_REACHED_1X, 490 CsdWarningDialog.NO_ACTION_TIMEOUT_MS); 491 492 verify(mCsdWarningDialog).show(); 493 } 494 495 @Test ifPortraitHalfOpen_drawVerticallyTop()496 public void ifPortraitHalfOpen_drawVerticallyTop() { 497 mDialog.onPostureChanged(DevicePostureController.DEVICE_POSTURE_HALF_OPENED); 498 mTestableLooper.processAllMessages(); // let dismiss() finish 499 500 setOrientation(Configuration.ORIENTATION_PORTRAIT); 501 502 // Call show() to trigger layout updates before verifying position 503 mDialog.show(SHOW_REASON_UNKNOWN); 504 mTestableLooper.processAllMessages(); // let show() finish before assessing its side-effect 505 506 int gravity = mDialog.getWindowGravity(); 507 assertEquals(Gravity.TOP, gravity & Gravity.VERTICAL_GRAVITY_MASK); 508 } 509 510 @Test ifPortraitAndOpen_drawCenterVertically()511 public void ifPortraitAndOpen_drawCenterVertically() { 512 mDialog.onPostureChanged(DevicePostureController.DEVICE_POSTURE_OPENED); 513 mTestableLooper.processAllMessages(); // let dismiss() finish 514 515 setOrientation(Configuration.ORIENTATION_PORTRAIT); 516 517 mDialog.show(SHOW_REASON_UNKNOWN); 518 mTestableLooper.processAllMessages(); // let show() finish before assessing its side-effect 519 520 int gravity = mDialog.getWindowGravity(); 521 assertEquals(Gravity.CENTER_VERTICAL, gravity & Gravity.VERTICAL_GRAVITY_MASK); 522 } 523 524 @Test ifLandscapeAndHalfOpen_drawCenterVertically()525 public void ifLandscapeAndHalfOpen_drawCenterVertically() { 526 mDialog.onPostureChanged(DevicePostureController.DEVICE_POSTURE_HALF_OPENED); 527 mTestableLooper.processAllMessages(); // let dismiss() finish 528 529 setOrientation(Configuration.ORIENTATION_LANDSCAPE); 530 531 mDialog.show(SHOW_REASON_UNKNOWN); 532 mTestableLooper.processAllMessages(); // let show() finish before assessing its side-effect 533 534 int gravity = mDialog.getWindowGravity(); 535 assertEquals(Gravity.CENTER_VERTICAL, gravity & Gravity.VERTICAL_GRAVITY_MASK); 536 } 537 538 @Test dialogInit_addsPostureControllerCallback()539 public void dialogInit_addsPostureControllerCallback() { 540 // init is already called in setup 541 verify(mPostureController).addCallback(any()); 542 } 543 544 @Test dialogDestroy_removesPostureControllerCallback()545 public void dialogDestroy_removesPostureControllerCallback() { 546 verify(mPostureController, never()).removeCallback(any()); 547 mDialog.destroy(); 548 verify(mPostureController).removeCallback(any()); 549 } 550 setOrientation(int orientation)551 private void setOrientation(int orientation) { 552 Configuration config = new Configuration(); 553 config.orientation = orientation; 554 if (mConfigurationController != null) { 555 mConfigurationController.onConfigurationChanged(config); 556 } 557 } 558 559 private enum RingerDrawerState {INIT, OPEN, CLOSE} 560 561 @Test ringerModeNormal_ringerContainerDescribesItsState()562 public void ringerModeNormal_ringerContainerDescribesItsState() { 563 assertRingerContainerDescribesItsState(RINGER_MODE_NORMAL, RingerDrawerState.INIT); 564 } 565 566 @Test ringerModeSilent_ringerContainerDescribesItsState()567 public void ringerModeSilent_ringerContainerDescribesItsState() { 568 assertRingerContainerDescribesItsState(RINGER_MODE_SILENT, RingerDrawerState.INIT); 569 } 570 571 @Test ringerModeVibrate_ringerContainerDescribesItsState()572 public void ringerModeVibrate_ringerContainerDescribesItsState() { 573 assertRingerContainerDescribesItsState(RINGER_MODE_VIBRATE, RingerDrawerState.INIT); 574 } 575 576 @Test ringerModeNormal_openDrawer_ringerContainerDescribesItsState()577 public void ringerModeNormal_openDrawer_ringerContainerDescribesItsState() { 578 assertRingerContainerDescribesItsState(RINGER_MODE_NORMAL, RingerDrawerState.OPEN); 579 } 580 581 @Test ringerModeSilent_openDrawer_ringerContainerDescribesItsState()582 public void ringerModeSilent_openDrawer_ringerContainerDescribesItsState() { 583 assertRingerContainerDescribesItsState(RINGER_MODE_SILENT, RingerDrawerState.OPEN); 584 } 585 586 @Test ringerModeVibrate_openDrawer_ringerContainerDescribesItsState()587 public void ringerModeVibrate_openDrawer_ringerContainerDescribesItsState() { 588 assertRingerContainerDescribesItsState(RINGER_MODE_VIBRATE, RingerDrawerState.OPEN); 589 } 590 591 @Test ringerModeNormal_closeDrawer_ringerContainerDescribesItsState()592 public void ringerModeNormal_closeDrawer_ringerContainerDescribesItsState() { 593 assertRingerContainerDescribesItsState(RINGER_MODE_NORMAL, RingerDrawerState.CLOSE); 594 } 595 596 @Test ringerModeSilent_closeDrawer_ringerContainerDescribesItsState()597 public void ringerModeSilent_closeDrawer_ringerContainerDescribesItsState() { 598 assertRingerContainerDescribesItsState(RINGER_MODE_SILENT, RingerDrawerState.CLOSE); 599 } 600 601 @Test ringerModeVibrate_closeDrawer_ringerContainerDescribesItsState()602 public void ringerModeVibrate_closeDrawer_ringerContainerDescribesItsState() { 603 assertRingerContainerDescribesItsState(RINGER_MODE_VIBRATE, RingerDrawerState.CLOSE); 604 } 605 606 @Test testOnCaptionEnabledStateChanged_checkBeforeSwitchTrue_setCaptionsEnabledState()607 public void testOnCaptionEnabledStateChanged_checkBeforeSwitchTrue_setCaptionsEnabledState() { 608 ArgumentCaptor<VolumeDialogController.Callbacks> controllerCallbackCapture = 609 ArgumentCaptor.forClass(VolumeDialogController.Callbacks.class); 610 verify(mVolumeDialogController).addCallback(controllerCallbackCapture.capture(), any()); 611 VolumeDialogController.Callbacks callbacks = controllerCallbackCapture.getValue(); 612 613 callbacks.onCaptionEnabledStateChanged(true, true); 614 verify(mVolumeDialogController).setCaptionsEnabledState(eq(false)); 615 } 616 617 @Test testOnCaptionEnabledStateChanged_checkBeforeSwitchFalse_getCaptionsEnabledTrue()618 public void testOnCaptionEnabledStateChanged_checkBeforeSwitchFalse_getCaptionsEnabledTrue() { 619 ArgumentCaptor<VolumeDialogController.Callbacks> controllerCallbackCapture = 620 ArgumentCaptor.forClass(VolumeDialogController.Callbacks.class); 621 verify(mVolumeDialogController).addCallback(controllerCallbackCapture.capture(), any()); 622 VolumeDialogController.Callbacks callbacks = controllerCallbackCapture.getValue(); 623 624 callbacks.onCaptionEnabledStateChanged(true, false); 625 assertTrue(mODICaptionsIcon.getCaptionsEnabled()); 626 } 627 628 /** 629 * The content description should include ringer state, and the correct one. 630 */ assertRingerContainerDescribesItsState(int ringerMode, RingerDrawerState drawerState)631 private void assertRingerContainerDescribesItsState(int ringerMode, 632 RingerDrawerState drawerState) { 633 assumeHasDrawer(); 634 635 State state = createShellState(); 636 state.ringerModeInternal = ringerMode; 637 mDialog.onStateChangedH(state); 638 639 mDialog.show(SHOW_REASON_UNKNOWN); 640 641 if (drawerState != RingerDrawerState.INIT) { 642 // in both cases we first open the drawer 643 mDialog.toggleRingerDrawer(true); 644 645 if (drawerState == RingerDrawerState.CLOSE) { 646 mDialog.toggleRingerDrawer(false); 647 } 648 } 649 650 String ringerContainerDescription = mDialog.getSelectedRingerContainerDescription(); 651 assumeNotNull(ringerContainerDescription); 652 653 String ringerDescription = mContext.getString( 654 mDialog.getStringDescriptionResourceForRingerMode(ringerMode)); 655 656 if (drawerState == RingerDrawerState.OPEN) { 657 assertEquals(ringerDescription, ringerContainerDescription); 658 } else { 659 assertNotSame(ringerDescription, ringerContainerDescription); 660 assertTrue(ringerContainerDescription.startsWith(ringerDescription)); 661 } 662 } 663 664 /** 665 * The click should be a single tap, thus we inject a down and an up event. 666 */ 667 @Test clickCaptionsButton_logsUiEvent()668 public void clickCaptionsButton_logsUiEvent() { 669 UiEventLoggerFake logger = new UiEventLoggerFake(); 670 Events.sUiEventLogger = logger; 671 MotionEvent down = MotionEventBuilder.newBuilder() 672 .setAction(MotionEvent.ACTION_DOWN).build(); 673 MotionEvent up = MotionEventBuilder.newBuilder() 674 .setAction(MotionEvent.ACTION_UP).build(); 675 676 mODICaptionsIcon.onTouchEvent(down); 677 mODICaptionsIcon.onTouchEvent(up); 678 mTestableLooper.moveTimeForward(300); // to confirm it was only a single tap 679 mTestableLooper.processAllMessages(); 680 681 boolean foundCaptionLog = false; 682 for (UiEventLoggerFake.FakeUiEvent event : logger.getLogs()) { 683 if (event.eventId 684 == Events.VolumeDialogEvent.VOLUME_DIALOG_ODI_CAPTIONS_CLICKED.getId()) { 685 foundCaptionLog = true; 686 break; 687 } 688 } 689 Assert.assertTrue("Did not log the captions button click.", foundCaptionLog); 690 } 691 692 /** 693 * Pressing the small x button at top right dismisses the captions tooltip. 694 */ 695 @Test dismissCaptionsTooltip_logsUiEvent()696 public void dismissCaptionsTooltip_logsUiEvent() { 697 UiEventLoggerFake logger = new UiEventLoggerFake(); 698 Events.sUiEventLogger = logger; 699 mDialog.showCaptionsTooltip(); 700 assumeNotNull(mDialog.mODICaptionsTooltipView); 701 View dismissButton = mDialog.mODICaptionsTooltipView.findViewById(R.id.dismiss); 702 703 dismissButton.performClick(); 704 705 boolean foundCaptionLog = false; 706 for (UiEventLoggerFake.FakeUiEvent event : logger.getLogs()) { 707 if (event.eventId 708 == Events.VolumeDialogEvent.VOLUME_DIALOG_ODI_CAPTIONS_TOOLTIP_CLICKED.getId() 709 ) { 710 foundCaptionLog = true; 711 break; 712 } 713 } 714 Assert.assertTrue("Did not log the captions tooltip dismiss button click.", 715 foundCaptionLog); 716 } 717 718 @Test volumeSliderTracksTouch_logsStartAndStopTrackingUiEvents()719 public void volumeSliderTracksTouch_logsStartAndStopTrackingUiEvents() { 720 UiEventLoggerFake logger = new UiEventLoggerFake(); 721 Events.sUiEventLogger = logger; 722 723 mDialog.show(SHOW_REASON_UNKNOWN); 724 mTestableLooper.processAllMessages(); 725 726 MotionEvent down = MotionEventBuilder.newBuilder() 727 .setAction(MotionEvent.ACTION_DOWN).build(); 728 MotionEvent up = MotionEventBuilder.newBuilder().setAction(MotionEvent.ACTION_UP).build(); 729 730 SeekBar slider = 731 mDialogRowsView.getChildAt(0).findViewById(R.id.volume_row_slider); 732 slider.onTouchEvent(down); 733 slider.onTouchEvent(up); 734 mTestableLooper.moveTimeForward(300); 735 mTestableLooper.processAllMessages(); 736 737 boolean foundStartTrackingTouch = false; 738 boolean foundStopTrackingTouch = false; 739 for (UiEventLoggerFake.FakeUiEvent event : logger.getLogs()) { 740 if (event.eventId 741 == Events.VolumeDialogEvent.VOLUME_DIALOG_SLIDER_STARTED_TRACKING_TOUCH.getId() 742 ) { 743 foundStartTrackingTouch = true; 744 } 745 if (event.eventId 746 == Events.VolumeDialogEvent.VOLUME_DIALOG_SLIDER_STOPPED_TRACKING_TOUCH.getId() 747 ) { 748 foundStopTrackingTouch = true; 749 } 750 } 751 Assert.assertTrue("Did not log the event of start tracking touch.", 752 foundStartTrackingTouch); 753 Assert.assertTrue("Did not log the event of stop tracking touch.", 754 foundStopTrackingTouch); 755 } 756 757 @Test turnOnDnD_volumeSliderIconChangesToDnd()758 public void turnOnDnD_volumeSliderIconChangesToDnd() { 759 State state = createShellState(); 760 state.zenMode = Settings.Global.ZEN_MODE_NO_INTERRUPTIONS; 761 762 mDialog.onStateChangedH(state); 763 mTestableLooper.processAllMessages(); 764 765 boolean foundDnDIcon = findDndIconAmongVolumeRows(); 766 assertTrue(foundDnDIcon); 767 } 768 769 @Test turnOffDnD_volumeSliderIconIsNotDnd()770 public void turnOffDnD_volumeSliderIconIsNotDnd() { 771 State state = createShellState(); 772 state.zenMode = Settings.Global.ZEN_MODE_OFF; 773 774 mDialog.onStateChangedH(state); 775 mTestableLooper.processAllMessages(); 776 777 boolean foundDnDIcon = findDndIconAmongVolumeRows(); 778 assertFalse(foundDnDIcon); 779 } 780 781 @Test testInteractor_onShow()782 public void testInteractor_onShow() { 783 mDialog.show(SHOW_REASON_UNKNOWN); 784 mTestableLooper.processAllMessages(); 785 786 verify(mVolumeDialogInteractor).onDialogShown(); 787 verify(mVolumeDialogInteractor).onDialogDismissed(); // dismiss by timeout 788 } 789 790 /** 791 * @return true if at least one volume row has the DND icon 792 */ findDndIconAmongVolumeRows()793 private boolean findDndIconAmongVolumeRows() { 794 ViewGroup volumeDialogRows = mDialog.getDialogView().findViewById(R.id.volume_dialog_rows); 795 assumeNotNull(volumeDialogRows); 796 Drawable expected = getContext().getDrawable(com.android.internal.R.drawable.ic_qs_dnd); 797 boolean foundDnDIcon = false; 798 final int rowCount = volumeDialogRows.getChildCount(); 799 // we don't make assumptions about the position of the dnd row 800 for (int i = 0; i < rowCount && !foundDnDIcon; i++) { 801 View volumeRow = volumeDialogRows.getChildAt(i); 802 ImageButton rowIcon = volumeRow.findViewById(R.id.volume_row_icon); 803 assertNotNull(rowIcon); 804 805 // VolumeDialogImpl changes tint and alpha in a private method, so we clear those here. 806 rowIcon.setImageTintList(null); 807 rowIcon.setAlpha(0xFF); 808 809 Drawable actual = rowIcon.getDrawable(); 810 foundDnDIcon |= areDrawablesEqual(expected, actual); 811 } 812 return foundDnDIcon; 813 } 814 areDrawablesEqual(Drawable drawable1, Drawable drawable2)815 private boolean areDrawablesEqual(Drawable drawable1, Drawable drawable2) { 816 int size = 100; 817 Bitmap bm1 = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888); 818 Bitmap bm2 = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888); 819 820 Canvas canvas1 = new Canvas(bm1); 821 Canvas canvas2 = new Canvas(bm2); 822 823 drawable1.setBounds(0, 0, size, size); 824 drawable2.setBounds(0, 0, size, size); 825 826 drawable1.draw(canvas1); 827 drawable2.draw(canvas2); 828 829 boolean areBitmapsEqual = areBitmapsEqual(bm1, bm2); 830 bm1.recycle(); 831 bm2.recycle(); 832 return areBitmapsEqual; 833 } 834 areBitmapsEqual(Bitmap a, Bitmap b)835 private boolean areBitmapsEqual(Bitmap a, Bitmap b) { 836 if (a.getWidth() != b.getWidth() || a.getHeight() != b.getHeight()) return false; 837 int w = a.getWidth(); 838 int h = a.getHeight(); 839 int[] aPix = new int[w * h]; 840 int[] bPix = new int[w * h]; 841 a.getPixels(aPix, 0, w, 0, 0, w, h); 842 b.getPixels(bPix, 0, w, 0, 0, w, h); 843 return Arrays.equals(aPix, bPix); 844 } 845 846 @After teardown()847 public void teardown() { 848 // Detailed logs to track down timeout issues in b/299491332 849 Log.d(TAG, "teardown: entered"); 850 setOrientation(mOriginalOrientation); 851 Log.d(TAG, "teardown: after setOrientation"); 852 // Unclear why we used to do this, and it seems to be a source of flakes 853 // mAnimatorTestRule.advanceTimeBy(mLongestHideShowAnimationDuration); 854 Log.d(TAG, "teardown: skipped advanceTimeBy"); 855 mTestableLooper.moveTimeForward(mLongestHideShowAnimationDuration); 856 Log.d(TAG, "teardown: after moveTimeForward"); 857 mTestableLooper.processAllMessages(); 858 Log.d(TAG, "teardown: after processAllMessages"); 859 reset(mPostureController); 860 Log.d(TAG, "teardown: after reset"); 861 cleanUp(mDialog); 862 Log.d(TAG, "teardown: after cleanUp"); 863 } 864 cleanUp(VolumeDialogImpl dialog)865 private void cleanUp(VolumeDialogImpl dialog) { 866 if (dialog != null) { 867 dialog.clearInternalHandlerAfterTest(); 868 } 869 } 870 871 /* 872 @Test 873 public void testContentDescriptions() { 874 mDialog.show(SHOW_REASON_UNKNOWN); 875 ViewGroup dialog = mDialog.getDialogView(); 876 877 navigateViews(dialog, view -> { 878 if (view instanceof ImageView) { 879 return !TextUtils.isEmpty(view.getContentDescription()); 880 } else { 881 return true; 882 } 883 }); 884 885 mDialog.dismiss(DISMISS_REASON_UNKNOWN); 886 } 887 888 @Test 889 public void testNoDuplicationOfParentState() { 890 mDialog.show(SHOW_REASON_UNKNOWN); 891 ViewGroup dialog = mDialog.getDialogView(); 892 893 navigateViews(dialog, view -> !view.isDuplicateParentStateEnabled()); 894 895 mDialog.dismiss(DISMISS_REASON_UNKNOWN); 896 } 897 898 @Test 899 public void testNoClickableViewGroups() { 900 mDialog.show(SHOW_REASON_UNKNOWN); 901 ViewGroup dialog = mDialog.getDialogView(); 902 903 navigateViews(dialog, view -> { 904 if (view instanceof ViewGroup) { 905 return !view.isClickable(); 906 } else { 907 return true; 908 } 909 }); 910 911 mDialog.dismiss(DISMISS_REASON_UNKNOWN); 912 } 913 914 @Test 915 public void testTristateToggle_withVibrator() { 916 when(mController.hasVibrator()).thenReturn(true); 917 918 State state = createShellState(); 919 state.ringerModeInternal = RINGER_MODE_NORMAL; 920 mDialog.onStateChangedH(state); 921 922 mDialog.show(SHOW_REASON_UNKNOWN); 923 ViewGroup dialog = mDialog.getDialogView(); 924 925 // click once, verify updates to vibrate 926 dialog.findViewById(R.id.ringer_icon).performClick(); 927 verify(mController, times(1)).setRingerMode(RINGER_MODE_VIBRATE, false); 928 929 // fake the update back to the dialog with the new ringer mode 930 state = createShellState(); 931 state.ringerModeInternal = RINGER_MODE_VIBRATE; 932 mDialog.onStateChangedH(state); 933 934 // click once, verify updates to silent 935 dialog.findViewById(R.id.ringer_icon).performClick(); 936 verify(mController, times(1)).setRingerMode(RINGER_MODE_SILENT, false); 937 verify(mController, times(1)).setStreamVolume(STREAM_RING, 0); 938 939 // fake the update back to the dialog with the new ringer mode 940 state = createShellState(); 941 state.states.get(STREAM_RING).level = 0; 942 state.ringerModeInternal = RINGER_MODE_SILENT; 943 mDialog.onStateChangedH(state); 944 945 // click once, verify updates to normal 946 dialog.findViewById(R.id.ringer_icon).performClick(); 947 verify(mController, times(1)).setRingerMode(RINGER_MODE_NORMAL, false); 948 verify(mController, times(1)).setStreamVolume(STREAM_RING, 0); 949 } 950 951 @Test 952 public void testTristateToggle_withoutVibrator() { 953 when(mController.hasVibrator()).thenReturn(false); 954 955 State state = createShellState(); 956 state.ringerModeInternal = RINGER_MODE_NORMAL; 957 mDialog.onStateChangedH(state); 958 959 mDialog.show(SHOW_REASON_UNKNOWN); 960 ViewGroup dialog = mDialog.getDialogView(); 961 962 // click once, verify updates to silent 963 dialog.findViewById(R.id.ringer_icon).performClick(); 964 verify(mController, times(1)).setRingerMode(RINGER_MODE_SILENT, false); 965 verify(mController, times(1)).setStreamVolume(STREAM_RING, 0); 966 967 // fake the update back to the dialog with the new ringer mode 968 state = createShellState(); 969 state.states.get(STREAM_RING).level = 0; 970 state.ringerModeInternal = RINGER_MODE_SILENT; 971 mDialog.onStateChangedH(state); 972 973 // click once, verify updates to normal 974 dialog.findViewById(R.id.ringer_icon).performClick(); 975 verify(mController, times(1)).setRingerMode(RINGER_MODE_NORMAL, false); 976 verify(mController, times(1)).setStreamVolume(STREAM_RING, 0); 977 } 978 */ 979 } 980