1 /* 2 * Copyright (C) 2021 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.keyguard; 18 19 20 import static com.android.systemui.flags.Flags.KEYGUARD_TALKBACK_FIX; 21 import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_BATTERY; 22 import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_BIOMETRIC_MESSAGE; 23 import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_DISCLOSURE; 24 import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_OWNER_INFO; 25 26 import static org.junit.Assert.assertFalse; 27 import static org.junit.Assert.assertNull; 28 import static org.junit.Assert.assertTrue; 29 import static org.mockito.ArgumentMatchers.any; 30 import static org.mockito.ArgumentMatchers.anyLong; 31 import static org.mockito.Mockito.mock; 32 import static org.mockito.Mockito.never; 33 import static org.mockito.Mockito.reset; 34 import static org.mockito.Mockito.verify; 35 import static org.mockito.Mockito.when; 36 37 import android.content.res.ColorStateList; 38 import android.graphics.Color; 39 import android.testing.AndroidTestingRunner; 40 import android.testing.TestableLooper.RunWithLooper; 41 42 import androidx.test.filters.SmallTest; 43 44 import com.android.keyguard.logging.KeyguardLogger; 45 import com.android.systemui.SysuiTestCase; 46 import com.android.systemui.flags.FakeFeatureFlags; 47 import com.android.systemui.plugins.statusbar.StatusBarStateController; 48 import com.android.systemui.statusbar.phone.KeyguardIndicationTextView; 49 import com.android.systemui.util.concurrency.DelayableExecutor; 50 51 import org.junit.Before; 52 import org.junit.Test; 53 import org.junit.runner.RunWith; 54 import org.mockito.ArgumentCaptor; 55 import org.mockito.Captor; 56 import org.mockito.Mock; 57 import org.mockito.MockitoAnnotations; 58 59 @RunWith(AndroidTestingRunner.class) 60 @RunWithLooper 61 @SmallTest 62 public class KeyguardIndicationRotateTextViewControllerTest extends SysuiTestCase { 63 64 private static final String TEST_MESSAGE = "test message"; 65 private static final String TEST_MESSAGE_2 = "test message two"; 66 private int mMsgId = 0; 67 68 @Mock 69 private DelayableExecutor mExecutor; 70 @Mock 71 private KeyguardIndicationTextView mView; 72 @Mock 73 private StatusBarStateController mStatusBarStateController; 74 @Mock 75 private KeyguardLogger mLogger; 76 @Captor 77 private ArgumentCaptor<StatusBarStateController.StateListener> mStatusBarStateListenerCaptor; 78 79 private KeyguardIndicationRotateTextViewController mController; 80 private StatusBarStateController.StateListener mStatusBarStateListener; 81 82 @Before setUp()83 public void setUp() throws Exception { 84 MockitoAnnotations.initMocks(this); 85 when(mView.getTextColors()).thenReturn(ColorStateList.valueOf(Color.WHITE)); 86 FakeFeatureFlags flags = new FakeFeatureFlags(); 87 flags.set(KEYGUARD_TALKBACK_FIX, true); 88 mController = new KeyguardIndicationRotateTextViewController(mView, mExecutor, 89 mStatusBarStateController, mLogger, flags); 90 mController.onViewAttached(); 91 92 verify(mStatusBarStateController).addCallback(mStatusBarStateListenerCaptor.capture()); 93 mStatusBarStateListener = mStatusBarStateListenerCaptor.getValue(); 94 } 95 96 @Test onViewDetached_removesStatusBarStateListener()97 public void onViewDetached_removesStatusBarStateListener() { 98 mController.onViewDetached(); 99 verify(mStatusBarStateController).removeCallback(mStatusBarStateListener); 100 } 101 102 @Test onViewDetached_removesAllScheduledIndications()103 public void onViewDetached_removesAllScheduledIndications() { 104 // GIVEN show next indication runnable is set 105 final KeyguardIndicationRotateTextViewController.ShowNextIndication mockShowNextIndication = 106 mock(KeyguardIndicationRotateTextViewController.ShowNextIndication.class); 107 mController.mShowNextIndicationRunnable = mockShowNextIndication; 108 109 // WHEN the view is detached 110 mController.onViewDetached(); 111 112 // THEN delayed execution is cancelled & runnable set to null 113 verify(mockShowNextIndication).cancelDelayedExecution(); 114 assertNull(mController.mShowNextIndicationRunnable); 115 } 116 117 @Test destroy_removesStatusBarStateListener()118 public void destroy_removesStatusBarStateListener() { 119 mController.destroy(); 120 verify(mStatusBarStateController).removeCallback(mStatusBarStateListener); 121 } 122 123 @Test destroy_removesOnAttachStateChangeListener()124 public void destroy_removesOnAttachStateChangeListener() { 125 mController.destroy(); 126 verify(mView).removeOnAttachStateChangeListener(any()); 127 } 128 129 @Test destroy_removesAllScheduledIndications()130 public void destroy_removesAllScheduledIndications() { 131 // GIVEN show next indication runnable is set 132 final KeyguardIndicationRotateTextViewController.ShowNextIndication mockShowNextIndication = 133 mock(KeyguardIndicationRotateTextViewController.ShowNextIndication.class); 134 mController.mShowNextIndicationRunnable = mockShowNextIndication; 135 136 // WHEN the controller is destroyed 137 mController.destroy(); 138 139 // THEN delayed execution is cancelled & runnable set to null 140 verify(mockShowNextIndication).cancelDelayedExecution(); 141 assertNull(mController.mShowNextIndicationRunnable); 142 } 143 144 @Test testInitialState_noIndication()145 public void testInitialState_noIndication() { 146 assertFalse(mController.hasIndications()); 147 } 148 149 @Test testShowOneIndication()150 public void testShowOneIndication() { 151 // WHEN we add our first indication 152 final KeyguardIndication indication = createIndication(); 153 mController.updateIndication( 154 INDICATION_TYPE_DISCLOSURE, indication, false); 155 156 // THEN 157 // - we see controller has an indication 158 // - the indication shows immediately since it's the only one 159 // - no next indication is scheduled since there's only one indication 160 assertTrue(mController.hasIndications()); 161 verify(mView).switchIndication(indication); 162 verify(mExecutor, never()).executeDelayed(any(), anyLong()); 163 } 164 165 @Test testShowTwoRotatingMessages()166 public void testShowTwoRotatingMessages() { 167 // GIVEN we already have an indication message 168 mController.updateIndication( 169 INDICATION_TYPE_OWNER_INFO, createIndication(), false); 170 reset(mView); 171 172 // WHEN we have a new indication type to display 173 final KeyguardIndication indication2 = createIndication(); 174 mController.updateIndication( 175 INDICATION_TYPE_DISCLOSURE, indication2, false); 176 177 // THEN 178 // - we don't immediately see the new message until the delay 179 // - next indication is scheduled 180 verify(mView, never()).switchIndication(indication2); 181 verify(mExecutor).executeDelayed(any(), anyLong()); 182 } 183 184 @Test testUpdateCurrentMessage()185 public void testUpdateCurrentMessage() { 186 // GIVEN we already have an indication message 187 mController.updateIndication( 188 INDICATION_TYPE_DISCLOSURE, createIndication(), false); 189 reset(mView); 190 191 // WHEN we have a new message for this indication type to display 192 final KeyguardIndication indication2 = createIndication(); 193 mController.updateIndication( 194 INDICATION_TYPE_DISCLOSURE, indication2, false); 195 196 // THEN 197 // - new indication is updated immediately 198 // - we don't schedule to show anything later 199 verify(mView).switchIndication(indication2); 200 verify(mExecutor, never()).executeDelayed(any(), anyLong()); 201 } 202 203 @Test testUpdateRotatingMessageForUndisplayedIndication()204 public void testUpdateRotatingMessageForUndisplayedIndication() { 205 // GIVEN we already have two indication messages 206 mController.updateIndication( 207 INDICATION_TYPE_OWNER_INFO, createIndication(), false); 208 mController.updateIndication( 209 INDICATION_TYPE_DISCLOSURE, createIndication(), false); 210 reset(mView); 211 reset(mExecutor); 212 213 // WHEN we have a new message for an undisplayed indication type 214 final KeyguardIndication indication3 = createIndication(); 215 mController.updateIndication( 216 INDICATION_TYPE_DISCLOSURE, indication3, false); 217 218 // THEN 219 // - we don't immediately update 220 // - we don't schedule to show anything new 221 verify(mView, never()).switchIndication(indication3); 222 verify(mExecutor, never()).executeDelayed(any(), anyLong()); 223 } 224 225 @Test testUpdateImmediately()226 public void testUpdateImmediately() { 227 // GIVEN we already have three indication messages 228 mController.updateIndication( 229 INDICATION_TYPE_OWNER_INFO, createIndication(), false); 230 mController.updateIndication( 231 INDICATION_TYPE_DISCLOSURE, createIndication(), false); 232 mController.updateIndication( 233 INDICATION_TYPE_BATTERY, createIndication(), false); 234 reset(mView); 235 reset(mExecutor); 236 237 // WHEN we have a new message for a currently shown type that we want to show immediately 238 final KeyguardIndication indication4 = createIndication(); 239 mController.updateIndication( 240 INDICATION_TYPE_BATTERY, indication4, true); 241 242 // THEN 243 // - we immediately update 244 // - we schedule a new delayable to show the next message later 245 verify(mView).switchIndication(indication4); 246 verify(mExecutor).executeDelayed(any(), anyLong()); 247 248 // WHEN an already existing type is updated to show immediately 249 reset(mView); 250 reset(mExecutor); 251 final KeyguardIndication indication5 = createIndication(); 252 mController.updateIndication( 253 INDICATION_TYPE_DISCLOSURE, indication5, true); 254 255 // THEN 256 // - we immediately update 257 // - we schedule a new delayable to show the next message later 258 verify(mView).switchIndication(indication5); 259 verify(mExecutor).executeDelayed(any(), anyLong()); 260 } 261 262 @Test testSameMessage_noIndicationUpdate()263 public void testSameMessage_noIndicationUpdate() { 264 // GIVEN we are showing and indication with a test message 265 mController.updateIndication( 266 INDICATION_TYPE_OWNER_INFO, createIndication(TEST_MESSAGE), true); 267 reset(mView); 268 reset(mExecutor); 269 270 // WHEN the same type tries to show the same exact message 271 final KeyguardIndication sameIndication = createIndication(TEST_MESSAGE); 272 mController.updateIndication( 273 INDICATION_TYPE_OWNER_INFO, sameIndication, true); 274 275 // THEN 276 // - we don't update the indication b/c there's no reason the animate the same text 277 verify(mView, never()).switchIndication(sameIndication); 278 } 279 280 @Test testTransientIndication()281 public void testTransientIndication() { 282 // GIVEN we already have two indication messages 283 mController.updateIndication( 284 INDICATION_TYPE_OWNER_INFO, createIndication(), false); 285 mController.updateIndication( 286 INDICATION_TYPE_DISCLOSURE, createIndication(), false); 287 reset(mView); 288 reset(mExecutor); 289 290 // WHEN we have a transient message 291 mController.showTransient(TEST_MESSAGE_2); 292 293 // THEN 294 // - we immediately update 295 // - we schedule a new delayable to show the next message later 296 verify(mView).switchIndication(any(KeyguardIndication.class)); 297 verify(mExecutor).executeDelayed(any(), anyLong()); 298 } 299 300 @Test testHideIndicationOneMessage()301 public void testHideIndicationOneMessage() { 302 // GIVEN we have one indication message 303 KeyguardIndication indication = createIndication(); 304 mController.updateIndication( 305 INDICATION_TYPE_OWNER_INFO, indication, false); 306 verify(mView).switchIndication(indication); 307 reset(mView); 308 309 // WHEN we hide the current indication type 310 mController.hideIndication(INDICATION_TYPE_OWNER_INFO); 311 312 // THEN we immediately update the text to show no text 313 verify(mView).switchIndication(null); 314 } 315 316 @Test testHideIndicationTwoMessages()317 public void testHideIndicationTwoMessages() { 318 // GIVEN we have two indication messages 319 final KeyguardIndication indication1 = createIndication(); 320 final KeyguardIndication indication2 = createIndication(); 321 mController.updateIndication( 322 INDICATION_TYPE_OWNER_INFO, indication1, false); 323 mController.updateIndication( 324 INDICATION_TYPE_DISCLOSURE, indication2, false); 325 assertTrue(mController.isNextIndicationScheduled()); 326 327 // WHEN we hide the current indication type 328 mController.hideIndication(INDICATION_TYPE_OWNER_INFO); 329 330 // THEN we show the next indication and there's no scheduled next indication 331 verify(mView).switchIndication(indication2); 332 assertFalse(mController.isNextIndicationScheduled()); 333 } 334 335 @Test testStartDozing()336 public void testStartDozing() { 337 // GIVEN a biometric message is showing 338 mController.updateIndication(INDICATION_TYPE_BIOMETRIC_MESSAGE, 339 createIndication(), true); 340 341 // WHEN the device is dozing 342 mStatusBarStateListener.onDozingChanged(true); 343 344 // THEN switch to INDICATION_TYPE_NONE 345 verify(mView).switchIndication(null); 346 } 347 348 @Test testStoppedDozing()349 public void testStoppedDozing() { 350 // GIVEN we're dozing & we have an indication message 351 mStatusBarStateListener.onDozingChanged(true); 352 final KeyguardIndication indication = createIndication(); 353 mController.updateIndication( 354 INDICATION_TYPE_DISCLOSURE, indication, false); 355 reset(mView); 356 reset(mExecutor); 357 358 // WHEN the device is no longer dozing 359 mStatusBarStateListener.onDozingChanged(false); 360 361 // THEN show the next message 362 verify(mView).switchIndication(indication); 363 } 364 365 @Test testIsDozing()366 public void testIsDozing() { 367 // GIVEN the device is dozing 368 mStatusBarStateListener.onDozingChanged(true); 369 reset(mView); 370 371 // WHEN an indication is updated 372 final KeyguardIndication indication = createIndication(); 373 mController.updateIndication( 374 INDICATION_TYPE_DISCLOSURE, indication, false); 375 376 // THEN no message is shown since we're dozing 377 verify(mView, never()).switchIndication(any()); 378 } 379 380 /** 381 * Create an indication with a unique message. 382 */ createIndication()383 private KeyguardIndication createIndication() { 384 return createIndication(TEST_MESSAGE + " " + mMsgId++); 385 } 386 387 /** 388 * Create an indication with the given message. 389 */ createIndication(String msg)390 private KeyguardIndication createIndication(String msg) { 391 return new KeyguardIndication.Builder() 392 .setMessage(msg) 393 .setTextColor(ColorStateList.valueOf(Color.WHITE)) 394 .build(); 395 } 396 } 397