1 /* 2 * Copyright (C) 2015 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 package android.car.apitest; 17 18 import static android.car.CarAppFocusManager.APP_FOCUS_REQUEST_SUCCEEDED; 19 import static android.car.CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION; 20 21 import static com.google.common.truth.Truth.assertThat; 22 23 import static org.testng.Assert.assertThrows; 24 25 import android.car.Car; 26 import android.car.CarAppFocusManager; 27 import android.content.Context; 28 import android.os.Handler; 29 import android.os.Looper; 30 import android.test.suitebuilder.annotation.MediumTest; 31 import android.util.Log; 32 33 import androidx.test.filters.FlakyTest; 34 import androidx.test.filters.RequiresDevice; 35 36 import org.junit.After; 37 import org.junit.Before; 38 import org.junit.Test; 39 40 import java.util.concurrent.Semaphore; 41 import java.util.concurrent.TimeUnit; 42 43 @MediumTest 44 public class CarAppFocusManagerTest extends CarApiTestBase { 45 private static final String TAG = CarAppFocusManagerTest.class.getSimpleName(); 46 47 private static final long NEGATIVE_CASE_WAIT_TIMEOUT_MS = 100L; 48 49 private CarAppFocusManager mManager; 50 51 private final LooperThread mEventThread = new LooperThread(); 52 53 @Before setUp()54 public void setUp() throws Exception { 55 mManager = (CarAppFocusManager) getCar().getCarManager(Car.APP_FOCUS_SERVICE); 56 assertThat(mManager).isNotNull(); 57 abandonAllAppFocuses(); 58 59 mEventThread.start(); 60 mEventThread.waitForReadyState(); 61 } 62 63 @After tearDown()64 public void tearDown() throws Exception { 65 abandonAllAppFocuses(); 66 } 67 abandonAllAppFocuses()68 private void abandonAllAppFocuses() throws Exception { 69 // Request all application focuses and abandon them to ensure no active context is present 70 // when test starts and ends. 71 int[] activeTypes = mManager.getActiveAppTypes(); 72 FocusOwnershipCallback owner = new FocusOwnershipCallback(/* assertEventThread= */ false); 73 for (int i = 0; i < activeTypes.length; i++) { 74 mManager.requestAppFocus(activeTypes[i], owner); 75 owner.waitForOwnershipGrantAndAssert(NEGATIVE_CASE_WAIT_TIMEOUT_MS, activeTypes[i]); 76 mManager.abandonAppFocus(owner, activeTypes[i]); 77 owner.waitForOwnershipLossAndAssert( 78 NEGATIVE_CASE_WAIT_TIMEOUT_MS, activeTypes[i]); 79 } 80 } 81 82 @Test testSetActiveNullListener()83 public void testSetActiveNullListener() throws Exception { 84 assertThrows(IllegalArgumentException.class, 85 () -> mManager.requestAppFocus(APP_FOCUS_TYPE_NAVIGATION, null)); 86 } 87 88 @Test testRegisterNull()89 public void testRegisterNull() throws Exception { 90 assertThrows(IllegalArgumentException.class, () -> mManager.addFocusListener(null, 0)); 91 } 92 93 @Test testRegisterUnregister()94 public void testRegisterUnregister() throws Exception { 95 FocusChangedListener listener = new FocusChangedListener(); 96 FocusChangedListener listener2 = new FocusChangedListener(); 97 mManager.addFocusListener(listener, 1); 98 mManager.addFocusListener(listener2, 1); 99 mManager.removeFocusListener(listener); 100 mManager.removeFocusListener(listener2); 101 mManager.removeFocusListener(listener2); // Double-unregister is OK 102 } 103 104 @Test testRegisterUnregisterSpecificApp()105 public void testRegisterUnregisterSpecificApp() throws Exception { 106 FocusChangedListener listener1 = new FocusChangedListener(); 107 FocusChangedListener listener2 = new FocusChangedListener(); 108 109 CarAppFocusManager manager = createManager(); 110 manager.addFocusListener(listener1, APP_FOCUS_TYPE_NAVIGATION); 111 manager.addFocusListener(listener2, APP_FOCUS_TYPE_NAVIGATION); 112 113 manager.removeFocusListener(listener1, APP_FOCUS_TYPE_NAVIGATION); 114 115 assertThat(manager.requestAppFocus(APP_FOCUS_TYPE_NAVIGATION, new FocusOwnershipCallback())) 116 .isEqualTo(CarAppFocusManager.APP_FOCUS_REQUEST_SUCCEEDED); 117 118 // Unregistred from nav app, no events expected. 119 assertThat(listener1.waitForFocusChangeAndAssert( 120 NEGATIVE_CASE_WAIT_TIMEOUT_MS, APP_FOCUS_TYPE_NAVIGATION, 121 true)).isFalse(); 122 assertThat(listener2.waitForFocusChangeAndAssert( 123 DEFAULT_WAIT_TIMEOUT_MS, APP_FOCUS_TYPE_NAVIGATION, true)).isTrue(); 124 125 manager.removeFocusListener(listener2, APP_FOCUS_TYPE_NAVIGATION); 126 // Used a new FocusOwnershipCallback to generate a new focus change event. 127 assertThat(manager.requestAppFocus(APP_FOCUS_TYPE_NAVIGATION, new FocusOwnershipCallback())) 128 .isEqualTo(CarAppFocusManager.APP_FOCUS_REQUEST_SUCCEEDED); 129 assertThat(listener2.waitForFocusChangeAndAssert( 130 NEGATIVE_CASE_WAIT_TIMEOUT_MS, APP_FOCUS_TYPE_NAVIGATION, 131 true)).isFalse(); 132 133 manager.removeFocusListener(listener2, 2); 134 manager.removeFocusListener(listener2, 2); // Double-unregister is OK 135 } 136 137 @Test 138 @FlakyTest testFocusChange()139 public void testFocusChange() throws Exception { 140 CarAppFocusManager manager1 = createManager(); 141 CarAppFocusManager manager2 = createManager(); 142 assertThat(manager2).isNotNull(); 143 144 assertThat(manager1.getActiveAppTypes()).asList().isEmpty(); 145 FocusChangedListener change1 = new FocusChangedListener(); 146 FocusChangedListener change2 = new FocusChangedListener(); 147 FocusOwnershipCallback owner1 = new FocusOwnershipCallback(); 148 FocusOwnershipCallback owner2 = new FocusOwnershipCallback(); 149 manager1.addFocusListener(change1, APP_FOCUS_TYPE_NAVIGATION); 150 manager2.addFocusListener(change2, APP_FOCUS_TYPE_NAVIGATION); 151 152 153 assertThat(manager1.requestAppFocus(APP_FOCUS_TYPE_NAVIGATION, owner1)) 154 .isEqualTo(CarAppFocusManager.APP_FOCUS_REQUEST_SUCCEEDED); 155 assertThat(owner1.waitForOwnershipGrantAndAssert( 156 DEFAULT_WAIT_TIMEOUT_MS, APP_FOCUS_TYPE_NAVIGATION)).isTrue(); 157 int expectedFocus = APP_FOCUS_TYPE_NAVIGATION; 158 assertThat(manager1.getActiveAppTypes()).asList().containsExactly(expectedFocus).inOrder(); 159 assertThat(manager2.getActiveAppTypes()).asList().containsExactly(expectedFocus).inOrder(); 160 assertThat(manager1.isOwningFocus(owner1, APP_FOCUS_TYPE_NAVIGATION)).isTrue(); 161 assertThat(manager2.isOwningFocus(owner2, APP_FOCUS_TYPE_NAVIGATION)).isFalse(); 162 assertThat(change2.waitForFocusChangeAndAssert(DEFAULT_WAIT_TIMEOUT_MS, 163 APP_FOCUS_TYPE_NAVIGATION, true)).isTrue(); 164 assertThat(change1.waitForFocusChangeAndAssert(DEFAULT_WAIT_TIMEOUT_MS, 165 APP_FOCUS_TYPE_NAVIGATION, true)).isTrue(); 166 167 expectedFocus = APP_FOCUS_TYPE_NAVIGATION; 168 assertThat(manager1.isOwningFocus(owner1, APP_FOCUS_TYPE_NAVIGATION)).isTrue(); 169 assertThat(manager2.isOwningFocus(owner2, APP_FOCUS_TYPE_NAVIGATION)).isFalse(); 170 assertThat(manager1.getActiveAppTypes()).asList().containsExactly(expectedFocus).inOrder(); 171 assertThat(manager2.getActiveAppTypes()).asList().containsExactly(expectedFocus).inOrder(); 172 173 // this should be no-op 174 change1.reset(); 175 change2.reset(); 176 assertThat(manager1.requestAppFocus(APP_FOCUS_TYPE_NAVIGATION, owner1)) 177 .isEqualTo(CarAppFocusManager.APP_FOCUS_REQUEST_SUCCEEDED); 178 assertThat(owner1.waitForOwnershipGrantAndAssert( 179 DEFAULT_WAIT_TIMEOUT_MS, APP_FOCUS_TYPE_NAVIGATION)).isTrue(); 180 181 assertThat(manager1.getActiveAppTypes()).asList().containsExactly(expectedFocus).inOrder(); 182 assertThat(manager2.getActiveAppTypes()).asList().containsExactly(expectedFocus).inOrder(); 183 assertThat(change2.waitForFocusChangeAndAssert(DEFAULT_WAIT_TIMEOUT_MS, 184 APP_FOCUS_TYPE_NAVIGATION, true)).isTrue(); 185 assertThat(change1.waitForFocusChangeAndAssert(DEFAULT_WAIT_TIMEOUT_MS, 186 APP_FOCUS_TYPE_NAVIGATION, true)).isTrue(); 187 188 assertThat(manager2.requestAppFocus(APP_FOCUS_TYPE_NAVIGATION, owner2)) 189 .isEqualTo(CarAppFocusManager.APP_FOCUS_REQUEST_SUCCEEDED); 190 assertThat(owner2.waitForOwnershipGrantAndAssert( 191 DEFAULT_WAIT_TIMEOUT_MS, APP_FOCUS_TYPE_NAVIGATION)).isTrue(); 192 193 assertThat(manager1.isOwningFocus(owner1, APP_FOCUS_TYPE_NAVIGATION)).isFalse(); 194 assertThat(manager2.isOwningFocus(owner2, APP_FOCUS_TYPE_NAVIGATION)).isTrue(); 195 assertThat(manager1.getActiveAppTypes()).asList().containsExactly(expectedFocus).inOrder(); 196 assertThat(manager2.getActiveAppTypes()).asList().containsExactly(expectedFocus).inOrder(); 197 assertThat(owner1.waitForOwnershipLossAndAssert(DEFAULT_WAIT_TIMEOUT_MS, 198 APP_FOCUS_TYPE_NAVIGATION)).isTrue(); 199 200 // no-op as it is not owning it 201 change1.reset(); 202 change2.reset(); 203 manager1.abandonAppFocus(owner1, APP_FOCUS_TYPE_NAVIGATION); 204 assertThat(manager1.isOwningFocus(owner1, APP_FOCUS_TYPE_NAVIGATION)).isFalse(); 205 assertThat(manager2.isOwningFocus(owner2, APP_FOCUS_TYPE_NAVIGATION)).isTrue(); 206 assertThat(manager1.getActiveAppTypes()).asList().containsExactly(expectedFocus).inOrder(); 207 assertThat(manager2.getActiveAppTypes()).asList().containsExactly(expectedFocus).inOrder(); 208 209 change1.reset(); 210 change2.reset(); 211 assertThat(manager1.isOwningFocus(owner1, APP_FOCUS_TYPE_NAVIGATION)).isFalse(); 212 assertThat(manager2.isOwningFocus(owner2, APP_FOCUS_TYPE_NAVIGATION)).isTrue(); 213 expectedFocus = APP_FOCUS_TYPE_NAVIGATION; 214 assertThat(manager1.getActiveAppTypes()).asList().containsExactly(expectedFocus).inOrder(); 215 assertThat(manager2.getActiveAppTypes()).asList().containsExactly(expectedFocus).inOrder(); 216 217 change1.reset(); 218 change2.reset(); 219 manager2.abandonAppFocus(owner2, APP_FOCUS_TYPE_NAVIGATION); 220 assertThat(manager1.isOwningFocus(owner1, APP_FOCUS_TYPE_NAVIGATION)).isFalse(); 221 assertThat(manager2.isOwningFocus(owner2, APP_FOCUS_TYPE_NAVIGATION)).isFalse(); 222 assertThat(manager1.getActiveAppTypes()).asList().isEmpty(); 223 assertThat(manager2.getActiveAppTypes()).asList().isEmpty(); 224 assertThat(change1.waitForFocusChangeAndAssert(DEFAULT_WAIT_TIMEOUT_MS, 225 APP_FOCUS_TYPE_NAVIGATION, false)).isTrue(); 226 227 manager1.removeFocusListener(change1); 228 manager2.removeFocusListener(change2); 229 } 230 231 @RequiresDevice 232 @Test testFilter()233 public void testFilter() throws Exception { 234 CarAppFocusManager manager1 = createManager(getContext(), mEventThread); 235 CarAppFocusManager manager2 = createManager(getContext(), mEventThread); 236 237 assertThat(manager1.getActiveAppTypes()).asList().isEmpty(); 238 assertThat(manager2.getActiveAppTypes()).asList().isEmpty(); 239 240 FocusChangedListener listener1 = new FocusChangedListener(); 241 FocusChangedListener listener2 = new FocusChangedListener(); 242 FocusOwnershipCallback owner = new FocusOwnershipCallback(); 243 manager1.addFocusListener(listener1, APP_FOCUS_TYPE_NAVIGATION); 244 manager2.addFocusListener(listener2, APP_FOCUS_TYPE_NAVIGATION); 245 246 assertThat(manager1.requestAppFocus(APP_FOCUS_TYPE_NAVIGATION, owner)) 247 .isEqualTo(APP_FOCUS_REQUEST_SUCCEEDED); 248 assertThat(owner.waitForOwnershipGrantAndAssert( 249 DEFAULT_WAIT_TIMEOUT_MS, APP_FOCUS_TYPE_NAVIGATION)).isTrue(); 250 251 assertThat(listener1.waitForFocusChangeAndAssert(DEFAULT_WAIT_TIMEOUT_MS, 252 APP_FOCUS_TYPE_NAVIGATION, true)).isTrue(); 253 assertThat(listener2.waitForFocusChangeAndAssert(DEFAULT_WAIT_TIMEOUT_MS, 254 APP_FOCUS_TYPE_NAVIGATION, true)).isTrue(); 255 256 listener1.reset(); 257 listener2.reset(); 258 manager1.abandonAppFocus(owner, APP_FOCUS_TYPE_NAVIGATION); 259 assertThat(listener1.waitForFocusChangeAndAssert(DEFAULT_WAIT_TIMEOUT_MS, 260 APP_FOCUS_TYPE_NAVIGATION, false)).isTrue(); 261 assertThat(listener2.waitForFocusChangeAndAssert(DEFAULT_WAIT_TIMEOUT_MS, 262 APP_FOCUS_TYPE_NAVIGATION, false)).isTrue(); 263 } 264 265 @Test testGetAppTypeOwner()266 public void testGetAppTypeOwner() throws Exception { 267 CarAppFocusManager manager = createManager(getContext(), mEventThread); 268 269 assertThat(manager.getAppTypeOwner(APP_FOCUS_TYPE_NAVIGATION)).isNull(); 270 271 FocusOwnershipCallback owner = new FocusOwnershipCallback(); 272 assertThat(manager.requestAppFocus(APP_FOCUS_TYPE_NAVIGATION, owner)) 273 .isEqualTo(APP_FOCUS_REQUEST_SUCCEEDED); 274 275 assertThat(manager.getAppTypeOwner(APP_FOCUS_TYPE_NAVIGATION)) 276 .containsExactly("android.car.apitest"); 277 278 manager.abandonAppFocus(owner, APP_FOCUS_TYPE_NAVIGATION); 279 280 assertThat(manager.getAppTypeOwner(APP_FOCUS_TYPE_NAVIGATION)).isNull(); 281 } 282 createManager()283 private CarAppFocusManager createManager() throws InterruptedException { 284 return createManager(getContext(), mEventThread); 285 } 286 createManager(Context context, LooperThread eventThread)287 private static CarAppFocusManager createManager(Context context, 288 LooperThread eventThread) throws InterruptedException { 289 Car car = createCar(context, eventThread); 290 CarAppFocusManager manager = (CarAppFocusManager) car.getCarManager(Car.APP_FOCUS_SERVICE); 291 assertThat(manager).isNotNull(); 292 return manager; 293 } 294 createCar(Context context, LooperThread eventThread)295 private static Car createCar(Context context, LooperThread eventThread) 296 throws InterruptedException { 297 DefaultServiceConnectionListener connectionListener = 298 new DefaultServiceConnectionListener(); 299 Car car = Car.createCar(context, connectionListener, eventThread.mHandler); 300 assertThat(car).isNotNull(); 301 car.connect(); 302 connectionListener.waitForConnection(DEFAULT_WAIT_TIMEOUT_MS); 303 return car; 304 } 305 306 @RequiresDevice 307 @Test testMultipleChangeListenersPerManager()308 public void testMultipleChangeListenersPerManager() throws Exception { 309 CarAppFocusManager manager = createManager(); 310 FocusChangedListener listener = new FocusChangedListener(); 311 FocusChangedListener listener2 = new FocusChangedListener(); 312 FocusOwnershipCallback owner = new FocusOwnershipCallback(); 313 manager.addFocusListener(listener, APP_FOCUS_TYPE_NAVIGATION); 314 manager.addFocusListener(listener2, APP_FOCUS_TYPE_NAVIGATION); 315 316 assertThat(manager.requestAppFocus(APP_FOCUS_TYPE_NAVIGATION, owner)) 317 .isEqualTo(APP_FOCUS_REQUEST_SUCCEEDED); 318 assertThat(owner.waitForOwnershipGrantAndAssert( 319 DEFAULT_WAIT_TIMEOUT_MS, APP_FOCUS_TYPE_NAVIGATION)).isTrue(); 320 321 assertThat(listener.waitForFocusChangeAndAssert(DEFAULT_WAIT_TIMEOUT_MS, 322 APP_FOCUS_TYPE_NAVIGATION, true)).isTrue(); 323 assertThat(listener2.waitForFocusChangeAndAssert(DEFAULT_WAIT_TIMEOUT_MS, 324 APP_FOCUS_TYPE_NAVIGATION, true)).isTrue(); 325 326 listener.reset(); 327 listener2.reset(); 328 manager.abandonAppFocus(owner, APP_FOCUS_TYPE_NAVIGATION); 329 assertThat(listener.waitForFocusChangeAndAssert(DEFAULT_WAIT_TIMEOUT_MS, 330 APP_FOCUS_TYPE_NAVIGATION, false)).isTrue(); 331 assertThat(listener2.waitForFocusChangeAndAssert(DEFAULT_WAIT_TIMEOUT_MS, 332 APP_FOCUS_TYPE_NAVIGATION, false)).isTrue(); 333 } 334 335 private final class FocusChangedListener 336 implements CarAppFocusManager.OnAppFocusChangedListener { 337 private volatile int mLastChangeAppType; 338 private volatile boolean mLastChangeAppActive; 339 private volatile Semaphore mChangeWait = new Semaphore(0); 340 waitForFocusChangeAndAssert(long timeoutMs, int expectedAppType, boolean expectedAppActive)341 boolean waitForFocusChangeAndAssert(long timeoutMs, int expectedAppType, 342 boolean expectedAppActive) throws Exception { 343 if (!mChangeWait.tryAcquire(timeoutMs, TimeUnit.MILLISECONDS)) { 344 return false; 345 } 346 assertThat(mLastChangeAppType).isEqualTo(expectedAppType); 347 assertThat(mLastChangeAppActive).isEqualTo(expectedAppActive); 348 return true; 349 } 350 reset()351 void reset() { 352 mLastChangeAppType = 0; 353 mLastChangeAppActive = false; 354 mChangeWait.drainPermits(); 355 } 356 357 @Override onAppFocusChanged(int appType, boolean active)358 public void onAppFocusChanged(int appType, boolean active) { 359 assertEventThread(); 360 mLastChangeAppType = appType; 361 mLastChangeAppActive = active; 362 mChangeWait.release(); 363 } 364 } 365 366 private final class FocusOwnershipCallback 367 implements CarAppFocusManager.OnAppFocusOwnershipCallback { 368 private int mLastLossEvent; 369 private final Semaphore mLossEventWait = new Semaphore(0); 370 private int mLastGrantEvent; 371 private final Semaphore mGrantEventWait = new Semaphore(0); 372 private final boolean mAssertEventThread; 373 FocusOwnershipCallback(boolean assertEventThread)374 private FocusOwnershipCallback(boolean assertEventThread) { 375 mAssertEventThread = assertEventThread; 376 } 377 FocusOwnershipCallback()378 private FocusOwnershipCallback() { 379 this(true); 380 } 381 waitForOwnershipLossAndAssert(long timeoutMs, int expectedAppType)382 boolean waitForOwnershipLossAndAssert(long timeoutMs, int expectedAppType) 383 throws Exception { 384 if (!mLossEventWait.tryAcquire(timeoutMs, TimeUnit.MILLISECONDS)) { 385 return false; 386 } 387 assertThat(mLastLossEvent).isEqualTo(expectedAppType); 388 return true; 389 } 390 waitForOwnershipGrantAndAssert(long timeoutMs, int expectedAppType)391 boolean waitForOwnershipGrantAndAssert(long timeoutMs, int expectedAppType) 392 throws Exception { 393 if (!mGrantEventWait.tryAcquire(timeoutMs, TimeUnit.MILLISECONDS)) { 394 return false; 395 } 396 assertThat(mLastGrantEvent).isEqualTo(expectedAppType); 397 return true; 398 } 399 400 @Override onAppFocusOwnershipLost(int appType)401 public void onAppFocusOwnershipLost(int appType) { 402 Log.i(TAG, "onAppFocusOwnershipLost " + appType); 403 if (mAssertEventThread) { 404 assertEventThread(); 405 } 406 mLastLossEvent = appType; 407 mLossEventWait.release(); 408 } 409 410 @Override onAppFocusOwnershipGranted(int appType)411 public void onAppFocusOwnershipGranted(int appType) { 412 Log.i(TAG, "onAppFocusOwnershipGranted " + appType); 413 mLastGrantEvent = appType; 414 mGrantEventWait.release(); 415 } 416 } 417 assertEventThread()418 private void assertEventThread() { 419 assertThat(Thread.currentThread()).isSameInstanceAs(mEventThread); 420 } 421 422 private static final class LooperThread extends Thread { 423 424 private final Object mReadySync = new Object(); 425 426 volatile Handler mHandler; 427 428 @Override run()429 public void run() { 430 Looper.prepare(); 431 mHandler = new Handler(); 432 433 synchronized (mReadySync) { 434 mReadySync.notifyAll(); 435 } 436 437 Looper.loop(); 438 } 439 waitForReadyState()440 void waitForReadyState() throws InterruptedException { 441 synchronized (mReadySync) { 442 mReadySync.wait(DEFAULT_WAIT_TIMEOUT_MS); 443 } 444 } 445 } 446 } 447