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