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.junit.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.util.Log; 31 32 import androidx.test.filters.FlakyTest; 33 import androidx.test.filters.MediumTest; 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 final 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 // Unregistered 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 createManager()265 private CarAppFocusManager createManager() throws InterruptedException { 266 return createManager(getContext(), mEventThread); 267 } 268 createManager(Context context, LooperThread eventThread)269 private static CarAppFocusManager createManager(Context context, 270 LooperThread eventThread) throws InterruptedException { 271 Car car = createCar(context, eventThread); 272 CarAppFocusManager manager = (CarAppFocusManager) car.getCarManager(Car.APP_FOCUS_SERVICE); 273 assertThat(manager).isNotNull(); 274 return manager; 275 } 276 createCar(Context context, LooperThread eventThread)277 private static Car createCar(Context context, LooperThread eventThread) 278 throws InterruptedException { 279 DefaultServiceConnectionListener connectionListener = 280 new DefaultServiceConnectionListener(); 281 Car car = Car.createCar(context, connectionListener, eventThread.mHandler); 282 assertThat(car).isNotNull(); 283 car.connect(); 284 connectionListener.waitForConnection(DEFAULT_WAIT_TIMEOUT_MS); 285 return car; 286 } 287 288 @RequiresDevice 289 @Test testMultipleChangeListenersPerManager()290 public void testMultipleChangeListenersPerManager() throws Exception { 291 CarAppFocusManager manager = createManager(); 292 FocusChangedListener listener = new FocusChangedListener(); 293 FocusChangedListener listener2 = new FocusChangedListener(); 294 FocusOwnershipCallback owner = new FocusOwnershipCallback(); 295 manager.addFocusListener(listener, APP_FOCUS_TYPE_NAVIGATION); 296 manager.addFocusListener(listener2, APP_FOCUS_TYPE_NAVIGATION); 297 298 assertThat(manager.requestAppFocus(APP_FOCUS_TYPE_NAVIGATION, owner)) 299 .isEqualTo(APP_FOCUS_REQUEST_SUCCEEDED); 300 assertThat(owner.waitForOwnershipGrantAndAssert( 301 DEFAULT_WAIT_TIMEOUT_MS, APP_FOCUS_TYPE_NAVIGATION)).isTrue(); 302 303 assertThat(listener.waitForFocusChangeAndAssert(DEFAULT_WAIT_TIMEOUT_MS, 304 APP_FOCUS_TYPE_NAVIGATION, true)).isTrue(); 305 assertThat(listener2.waitForFocusChangeAndAssert(DEFAULT_WAIT_TIMEOUT_MS, 306 APP_FOCUS_TYPE_NAVIGATION, true)).isTrue(); 307 308 listener.reset(); 309 listener2.reset(); 310 manager.abandonAppFocus(owner, APP_FOCUS_TYPE_NAVIGATION); 311 assertThat(listener.waitForFocusChangeAndAssert(DEFAULT_WAIT_TIMEOUT_MS, 312 APP_FOCUS_TYPE_NAVIGATION, false)).isTrue(); 313 assertThat(listener2.waitForFocusChangeAndAssert(DEFAULT_WAIT_TIMEOUT_MS, 314 APP_FOCUS_TYPE_NAVIGATION, false)).isTrue(); 315 } 316 317 private final class FocusChangedListener 318 implements CarAppFocusManager.OnAppFocusChangedListener { 319 private volatile int mLastChangeAppType; 320 private volatile boolean mLastChangeAppActive; 321 private volatile Semaphore mChangeWait = new Semaphore(0); 322 waitForFocusChangeAndAssert(long timeoutMs, int expectedAppType, boolean expectedAppActive)323 boolean waitForFocusChangeAndAssert(long timeoutMs, int expectedAppType, 324 boolean expectedAppActive) throws Exception { 325 if (!mChangeWait.tryAcquire(timeoutMs, TimeUnit.MILLISECONDS)) { 326 return false; 327 } 328 assertThat(mLastChangeAppType).isEqualTo(expectedAppType); 329 assertThat(mLastChangeAppActive).isEqualTo(expectedAppActive); 330 return true; 331 } 332 reset()333 void reset() { 334 mLastChangeAppType = 0; 335 mLastChangeAppActive = false; 336 mChangeWait.drainPermits(); 337 } 338 339 @Override onAppFocusChanged(int appType, boolean active)340 public void onAppFocusChanged(int appType, boolean active) { 341 assertEventThread(); 342 mLastChangeAppType = appType; 343 mLastChangeAppActive = active; 344 mChangeWait.release(); 345 } 346 } 347 348 private final class FocusOwnershipCallback 349 implements CarAppFocusManager.OnAppFocusOwnershipCallback { 350 private int mLastLossEvent; 351 private final Semaphore mLossEventWait = new Semaphore(0); 352 private int mLastGrantEvent; 353 private final Semaphore mGrantEventWait = new Semaphore(0); 354 private final boolean mAssertEventThread; 355 FocusOwnershipCallback(boolean assertEventThread)356 private FocusOwnershipCallback(boolean assertEventThread) { 357 mAssertEventThread = assertEventThread; 358 } 359 FocusOwnershipCallback()360 private FocusOwnershipCallback() { 361 this(true); 362 } 363 waitForOwnershipLossAndAssert(long timeoutMs, int expectedAppType)364 boolean waitForOwnershipLossAndAssert(long timeoutMs, int expectedAppType) 365 throws Exception { 366 if (!mLossEventWait.tryAcquire(timeoutMs, TimeUnit.MILLISECONDS)) { 367 return false; 368 } 369 assertThat(mLastLossEvent).isEqualTo(expectedAppType); 370 return true; 371 } 372 waitForOwnershipGrantAndAssert(long timeoutMs, int expectedAppType)373 boolean waitForOwnershipGrantAndAssert(long timeoutMs, int expectedAppType) 374 throws Exception { 375 if (!mGrantEventWait.tryAcquire(timeoutMs, TimeUnit.MILLISECONDS)) { 376 return false; 377 } 378 assertThat(mLastGrantEvent).isEqualTo(expectedAppType); 379 return true; 380 } 381 382 @Override onAppFocusOwnershipLost(int appType)383 public void onAppFocusOwnershipLost(int appType) { 384 Log.i(TAG, "onAppFocusOwnershipLost " + appType); 385 if (mAssertEventThread) { 386 assertEventThread(); 387 } 388 mLastLossEvent = appType; 389 mLossEventWait.release(); 390 } 391 392 @Override onAppFocusOwnershipGranted(int appType)393 public void onAppFocusOwnershipGranted(int appType) { 394 Log.i(TAG, "onAppFocusOwnershipGranted " + appType); 395 mLastGrantEvent = appType; 396 mGrantEventWait.release(); 397 } 398 } 399 assertEventThread()400 private void assertEventThread() { 401 assertThat(Thread.currentThread()).isSameInstanceAs(mEventThread); 402 } 403 404 private static final class LooperThread extends Thread { 405 406 private final Object mReadySync = new Object(); 407 408 volatile Handler mHandler; 409 410 @Override run()411 public void run() { 412 Looper.prepare(); 413 mHandler = new Handler(); 414 415 synchronized (mReadySync) { 416 mReadySync.notifyAll(); 417 } 418 419 Looper.loop(); 420 } 421 waitForReadyState()422 void waitForReadyState() throws InterruptedException { 423 synchronized (mReadySync) { 424 mReadySync.wait(DEFAULT_WAIT_TIMEOUT_MS); 425 } 426 } 427 } 428 } 429