1 /*
2  * Copyright (C) 2019 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 
17 package android.car;
18 
19 import static com.google.common.truth.Truth.assertThat;
20 
21 import static org.mockito.ArgumentMatchers.anyBoolean;
22 import static org.mockito.ArgumentMatchers.eq;
23 import static org.mockito.Mockito.verify;
24 import static org.robolectric.Shadows.shadowOf;
25 
26 import android.app.Application;
27 import android.car.CarAppFocusManager.OnAppFocusChangedListener;
28 import android.car.CarAppFocusManager.OnAppFocusOwnershipCallback;
29 import android.car.testapi.CarAppFocusController;
30 import android.car.testapi.FakeCar;
31 import android.os.Looper;
32 
33 import androidx.test.core.app.ApplicationProvider;
34 
35 import org.junit.Before;
36 import org.junit.Rule;
37 import org.junit.Test;
38 import org.junit.runner.RunWith;
39 import org.mockito.Mock;
40 import org.mockito.junit.MockitoJUnit;
41 import org.mockito.junit.MockitoRule;
42 import org.robolectric.RobolectricTestRunner;
43 import org.robolectric.annotation.internal.DoNotInstrument;
44 import org.robolectric.shadows.ShadowBinder;
45 
46 @RunWith(RobolectricTestRunner.class)
47 @DoNotInstrument
48 public class CarAppFocusManagerTest {
49     @Rule
50     public MockitoRule rule = MockitoJUnit.rule();
51 
52     private Application mContext;
53 
54     private FakeCar mFakeCar;
55     private CarAppFocusManager mCarAppFocusManager;
56     private CarAppFocusController mCarAppFocusController;
57     private Looper mAppFocusServiceLooper;
58 
59     private static final int APP1_UID = 1041;
60     private static final int APP1_PID = 1043;
61     private static final int APP2_UID = 1072;
62     private static final int APP2_PID = 1074;
63     private static final int APP3_UID = 1111;
64     private static final int APP3_PID = 2222;
65 
66     @Mock OnAppFocusOwnershipCallback mApp1Callback;
67     @Mock OnAppFocusChangedListener mApp1Listener;
68     @Mock OnAppFocusOwnershipCallback mApp2Callback;
69     @Mock OnAppFocusChangedListener mApp2Listener;
70     @Mock OnAppFocusOwnershipCallback mApp3Callback;
71     @Mock OnAppFocusChangedListener mApp3Listener;
72 
73     @Before
setUp()74     public void setUp() {
75         ShadowBinder.reset();
76         mContext = ApplicationProvider.getApplicationContext();
77         mFakeCar = FakeCar.createFakeCar(mContext);
78         mCarAppFocusManager =
79                 (CarAppFocusManager) mFakeCar.getCar().getCarManager(Car.APP_FOCUS_SERVICE);
80         mCarAppFocusController = mFakeCar.getAppFocusController();
81         mAppFocusServiceLooper = mCarAppFocusController.getLooper();
82     }
83 
84     @Test
defaultState_noFocusesHeld()85     public void defaultState_noFocusesHeld() {
86         assertThat(mCarAppFocusManager.getActiveAppTypes()).isEmpty();
87     }
88 
89     @Test
requestNavFocus_noCurrentFocus_requestShouldSucceed()90     public void requestNavFocus_noCurrentFocus_requestShouldSucceed() {
91         int result = mCarAppFocusManager.requestAppFocus(
92                 CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION, mApp1Callback);
93         assertThat(result).isEqualTo(CarAppFocusManager.APP_FOCUS_REQUEST_SUCCEEDED);
94     }
95 
96     @Test
requestNavFocus_noCurrentFocus_callbackIsRun()97     public void requestNavFocus_noCurrentFocus_callbackIsRun() {
98         mCarAppFocusManager.requestAppFocus(CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION,
99                 mApp1Callback);
100         shadowOf(mAppFocusServiceLooper).runToEndOfTasks();
101 
102         verify(mApp1Callback)
103                 .onAppFocusOwnershipGranted(CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION);
104     }
105 
106     @Test
requestNavFocus_noCurrentFocus_holdsOwnership()107     public void requestNavFocus_noCurrentFocus_holdsOwnership() {
108         mCarAppFocusManager.requestAppFocus(CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION,
109                 mApp1Callback);
110 
111         assertThat(
112                 mCarAppFocusManager
113                         .isOwningFocus(mApp1Callback, CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION))
114                 .isTrue();
115     }
116 
117     @Test
requestNavFocus_noCurrentFocus_onlyNavActive()118     public void requestNavFocus_noCurrentFocus_onlyNavActive() {
119         mCarAppFocusManager.requestAppFocus(CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION,
120                 mApp1Callback);
121 
122         assertThat(mCarAppFocusManager.getActiveAppTypes())
123                 .isEqualTo(new int[] {CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION});
124     }
125 
setCallingApp(int uid, int pid)126     private void setCallingApp(int uid, int pid) {
127         ShadowBinder.setCallingUid(uid);
128         ShadowBinder.setCallingPid(pid);
129     }
130 
app2GainsFocus_app1BroughtToForeground()131     private void app2GainsFocus_app1BroughtToForeground() {
132         setCallingApp(APP2_UID, APP2_PID);
133         mCarAppFocusManager.requestAppFocus(CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION,
134                 mApp2Callback);
135         mCarAppFocusController.setForegroundUid(APP1_UID);
136         mCarAppFocusController.setForegroundPid(APP1_PID);
137         setCallingApp(APP2_UID, APP1_PID);
138     }
139 
140     @Test
requestNavFocus_currentOwnerInBackground_requestShouldSucceed()141     public void requestNavFocus_currentOwnerInBackground_requestShouldSucceed() {
142         app2GainsFocus_app1BroughtToForeground();
143 
144         assertThat(
145                 mCarAppFocusManager
146                         .requestAppFocus(
147                                 CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION, mApp1Callback))
148                 .isEqualTo(CarAppFocusManager.APP_FOCUS_REQUEST_SUCCEEDED);
149     }
150 
151     @Test
requestNavFocus_currentOwnerInBackground_callbackIsRun()152     public void requestNavFocus_currentOwnerInBackground_callbackIsRun() {
153         app2GainsFocus_app1BroughtToForeground();
154         mCarAppFocusManager
155                 .requestAppFocus(CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION, mApp1Callback);
156         shadowOf(mAppFocusServiceLooper).runToEndOfTasks();
157 
158         verify(mApp1Callback)
159                 .onAppFocusOwnershipGranted(CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION);
160     }
161 
162     @Test
requestNavFocus_currentOwnerInBackground_holdsOwnership()163     public void requestNavFocus_currentOwnerInBackground_holdsOwnership() {
164         app2GainsFocus_app1BroughtToForeground();
165         mCarAppFocusManager
166                 .requestAppFocus(CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION, mApp1Callback);
167 
168         assertThat(
169                 mCarAppFocusManager
170                         .isOwningFocus(mApp1Callback, CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION))
171                 .isTrue();
172     }
173 
174     @Test
requestNavFocus_currentOwnerInForeground_requestFails()175     public void requestNavFocus_currentOwnerInForeground_requestFails() {
176         setCallingApp(APP2_UID, APP2_PID);
177         mCarAppFocusController.setForegroundUid(APP2_UID);
178         mCarAppFocusController.setForegroundPid(APP2_PID);
179         mCarAppFocusManager.requestAppFocus(CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION,
180                 mApp2Callback);
181         setCallingApp(APP1_UID, APP1_PID);
182 
183         assertThat(
184                 mCarAppFocusManager
185                         .requestAppFocus(
186                                 CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION, mApp1Callback))
187                 .isEqualTo(CarAppFocusManager.APP_FOCUS_REQUEST_FAILED);
188     }
189 
190     @Test
requestAppFocus_callingAppNotified()191     public void requestAppFocus_callingAppNotified() {
192         setCallingApp(APP1_UID, APP1_PID);
193         mCarAppFocusManager
194                 .addFocusListener(mApp1Listener, CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION);
195         mCarAppFocusManager.requestAppFocus(CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION,
196                 mApp1Callback);
197         shadowOf(mAppFocusServiceLooper).runToEndOfTasks();
198 
199         verify(mApp1Listener)
200                 .onAppFocusChanged(eq(CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION), anyBoolean());
201     }
202 
203     @Test
requestAppFocus_otherAppNotified()204     public void requestAppFocus_otherAppNotified() {
205         setCallingApp(APP2_UID, APP2_PID);
206         mCarAppFocusManager
207                 .addFocusListener(mApp2Listener, CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION);
208         setCallingApp(APP1_UID, APP1_PID);
209         mCarAppFocusManager.requestAppFocus(CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION,
210                 mApp1Callback);
211         shadowOf(mAppFocusServiceLooper).runToEndOfTasks();
212 
213         verify(mApp2Listener)
214                 .onAppFocusChanged(eq(CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION), eq(true));
215     }
216 
217     @Test
requestAppFocus_focusLost_otherAppRequest_callbackRun()218     public void requestAppFocus_focusLost_otherAppRequest_callbackRun() {
219         setCallingApp(APP2_UID, APP2_PID);
220         mCarAppFocusManager.requestAppFocus(CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION,
221                 mApp2Callback);
222         setCallingApp(APP1_UID, APP1_PID);
223         mCarAppFocusManager.requestAppFocus(CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION,
224                 mApp1Callback);
225         shadowOf(mAppFocusServiceLooper).runToEndOfTasks();
226 
227         verify(mApp2Callback)
228                 .onAppFocusOwnershipLost(eq(CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION));
229     }
230 
231     @Test
abandonAppFocus_callingAppNotified()232     public void abandonAppFocus_callingAppNotified() {
233         setCallingApp(APP1_UID, APP1_PID);
234         mCarAppFocusManager
235                 .addFocusListener(mApp1Listener, CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION);
236         mCarAppFocusManager.requestAppFocus(CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION,
237                 mApp1Callback);
238         mCarAppFocusManager
239                 .abandonAppFocus(mApp1Callback, CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION);
240         shadowOf(mAppFocusServiceLooper).runToEndOfTasks();
241 
242         verify(mApp1Listener)
243                 .onAppFocusChanged(eq(CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION), eq(false));
244     }
245 
246     @Test
abandonAppFocus_otherAppNotified()247     public void abandonAppFocus_otherAppNotified() {
248         setCallingApp(APP2_UID, APP2_PID);
249         mCarAppFocusManager
250                 .addFocusListener(mApp2Listener, CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION);
251         setCallingApp(APP1_UID, APP1_PID);
252         mCarAppFocusManager.requestAppFocus(CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION,
253                 mApp1Callback);
254         mCarAppFocusManager
255                 .abandonAppFocus(mApp1Callback, CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION);
256         shadowOf(mAppFocusServiceLooper).runToEndOfTasks();
257 
258         verify(mApp2Listener)
259                 .onAppFocusChanged(eq(CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION), eq(false));
260     }
261 
262     @Test
gainAppFocus_multipleListenersRegistered_bothUnownedTrigger()263     public void gainAppFocus_multipleListenersRegistered_bothUnownedTrigger() {
264         setCallingApp(APP1_UID, APP1_PID);
265         mCarAppFocusManager
266                 .addFocusListener(mApp1Listener, CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION);
267         setCallingApp(APP2_UID, APP2_PID);
268         mCarAppFocusManager
269                 .addFocusListener(mApp2Listener, CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION);
270         setCallingApp(APP3_UID, APP3_PID);
271         mCarAppFocusManager
272                 .addFocusListener(mApp3Listener, CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION);
273         mCarAppFocusManager
274                 .requestAppFocus(CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION, mApp3Callback);
275         shadowOf(mAppFocusServiceLooper).runToEndOfTasks();
276 
277         verify(mApp1Listener)
278                 .onAppFocusChanged(eq(CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION), eq(true));
279         verify(mApp2Listener)
280                 .onAppFocusChanged(eq(CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION), eq(true));
281         verify(mApp3Listener)
282                 .onAppFocusChanged(eq(CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION), eq(true));
283     }
284 }
285