• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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