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