1 /*
2  * Copyright (C) 2021 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.cluster;
18 
19 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
20 
21 import static com.google.common.truth.Truth.assertThat;
22 
23 import static org.mockito.ArgumentMatchers.any;
24 import static org.mockito.ArgumentMatchers.anyInt;
25 import static org.mockito.ArgumentMatchers.anyLong;
26 import static org.mockito.ArgumentMatchers.anyString;
27 import static org.mockito.ArgumentMatchers.eq;
28 import static org.mockito.Mockito.doAnswer;
29 import static org.mockito.Mockito.doNothing;
30 import static org.mockito.Mockito.doThrow;
31 import static org.mockito.Mockito.times;
32 import static org.mockito.Mockito.verify;
33 import static org.mockito.Mockito.verifyNoMoreInteractions;
34 import static org.mockito.Mockito.when;
35 
36 import android.app.Activity;
37 import android.content.Intent;
38 import android.os.Bundle;
39 import android.os.IBinder;
40 import android.os.RemoteException;
41 import android.platform.test.annotations.DisabledOnRavenwood;
42 import android.platform.test.ravenwood.RavenwoodRule;
43 import android.view.SurfaceControl;
44 import android.view.View;
45 import android.view.ViewRootImpl;
46 import android.view.ViewTreeObserver;
47 import android.view.Window;
48 
49 import com.android.car.internal.ICarBase;
50 
51 import org.junit.Before;
52 import org.junit.Rule;
53 import org.junit.Test;
54 import org.junit.runner.RunWith;
55 import org.mockito.ArgumentCaptor;
56 import org.mockito.Mock;
57 import org.mockito.junit.MockitoJUnitRunner;
58 
59 import java.util.concurrent.Executor;
60 
61 /**
62  * Unit tests for {@link ClusterHomeManager}
63  */
64 @RunWith(MockitoJUnitRunner.class)
65 @DisabledOnRavenwood(
66         reason = "SurfaceControl cannot be mocked because it uses dalvik.system.CloseGuard")
67 public final class ClusterHomeManagerUnitTest {
68 
69     @Rule
70     public final RavenwoodRule mRavenwood = new RavenwoodRule.Builder().build();
71 
72     @Mock
73     private ICarBase mCar;
74 
75     @Mock
76     private IBinder mBinder;
77 
78     @Mock
79     private IClusterHomeService.Stub mService;
80 
81     @Mock
82     private ClusterHomeManager.ClusterStateListener mClusterStateListener;
83 
84     @Mock
85     private ClusterHomeManager.ClusterNavigationStateListener mClusterNavigationStateListener;
86 
87     @Mock
88     private Activity mActivity;
89     @Mock
90     private Window mWindow;
91     @Mock
92     private View mDecorView;
93     @Mock
94     private ViewTreeObserver mViewTreeObserver;
95     @Mock
96     private ViewRootImpl mViewRoot;
97     @Mock
98     private SurfaceControl mSurfaceControl;
99 
100     private ClusterHomeManager mClusterHomeManager;
101     private final Executor mCurrentThreadExecutor = new Executor() {
102         @Override
103         public void execute(Runnable command) {
104             command.run();
105         }
106     };
107 
108     @Before
setup()109     public void setup() {
110         when(mBinder.queryLocalInterface(anyString())).thenReturn(mService);
111         mClusterHomeManager = new ClusterHomeManager(mCar, mBinder);
112     }
113 
114     @Test
getClusterState_serviceFailure()115     public void getClusterState_serviceFailure() throws Exception {
116         RemoteException thrownException = new RemoteException();
117         when(mService.getClusterState()).thenThrow(thrownException);
118 
119         ClusterState clusterState = mClusterHomeManager.getClusterState();
120 
121         verify(mCar).handleRemoteExceptionFromCarService(thrownException);
122         assertThat(clusterState).isNull();
123     }
124 
125     @Test
registerClusterStateListener_serviceFailure()126     public void registerClusterStateListener_serviceFailure() throws Exception {
127         RemoteException thrownException = new RemoteException();
128         doThrow(thrownException)
129                 .when(mService).registerClusterStateListener(any());
130 
131         mClusterHomeManager.registerClusterStateListener(mCurrentThreadExecutor,
132                 mClusterStateListener);
133 
134         verify(mCar).handleRemoteExceptionFromCarService(thrownException);
135     }
136 
137     @Test
registerClusterNavigationStateListener_serviceFailure()138     public void registerClusterNavigationStateListener_serviceFailure() throws Exception {
139         RemoteException thrownException = new RemoteException();
140         doThrow(thrownException)
141                 .when(mService).registerClusterNavigationStateListener(any());
142 
143         mClusterHomeManager.registerClusterNavigationStateListener(mCurrentThreadExecutor,
144                 mClusterNavigationStateListener);
145 
146         verify(mCar).handleRemoteExceptionFromCarService(thrownException);
147     }
148 
149     @Test
registerClusterStateListener_callbackAlreadyRegistered_doNothing()150     public void registerClusterStateListener_callbackAlreadyRegistered_doNothing()
151             throws Exception {
152         doNothing().when(mService).registerClusterStateListener(any());
153         mClusterHomeManager.registerClusterStateListener(mCurrentThreadExecutor,
154                 mClusterStateListener);
155 
156         mClusterHomeManager.registerClusterStateListener(mCurrentThreadExecutor,
157                 mClusterStateListener);
158 
159         verify(mService, times(1)).registerClusterStateListener(any());
160         verifyNoMoreInteractions(mService);
161     }
162 
163     @Test
registerClusterNavigationStateListener_callbackAlreadyRegistered_doNothing()164     public void registerClusterNavigationStateListener_callbackAlreadyRegistered_doNothing()
165             throws Exception {
166         doNothing().when(mService).registerClusterNavigationStateListener(any());
167         mClusterHomeManager.registerClusterNavigationStateListener(mCurrentThreadExecutor,
168                 mClusterNavigationStateListener);
169 
170         mClusterHomeManager.registerClusterNavigationStateListener(mCurrentThreadExecutor,
171                 mClusterNavigationStateListener);
172 
173         verify(mService, times(1)).registerClusterNavigationStateListener(any());
174         verifyNoMoreInteractions(mService);
175     }
176 
177     @Test
onNavigationStateChanged_callsCallbacks()178     public void onNavigationStateChanged_callsCallbacks() throws Exception {
179         byte[] newNavigationState = new byte[]{1};
180         doAnswer(invocation -> {
181             IClusterNavigationStateListener.Stub clusterHomeManagerNavigationStateListener =
182                     (IClusterNavigationStateListener.Stub) invocation.getArgument(0);
183             clusterHomeManagerNavigationStateListener.onNavigationStateChanged(newNavigationState);
184             return null;
185         }).when(mService).registerClusterNavigationStateListener(any());
186 
187         mClusterHomeManager.registerClusterNavigationStateListener(mCurrentThreadExecutor,
188                 mClusterNavigationStateListener);
189 
190         verify(mClusterNavigationStateListener).onNavigationStateChanged(eq(newNavigationState));
191     }
192 
193     @Test
reportState_serviceFailure()194     public void reportState_serviceFailure() throws Exception {
195         RemoteException thrownException = new RemoteException();
196         doThrow(thrownException)
197                 .when(mService).reportState(anyInt(), anyInt(), any(byte[].class));
198 
199         mClusterHomeManager.reportState(1, 1, new byte[]{1});
200 
201         verify(mCar).handleRemoteExceptionFromCarService(thrownException);
202     }
203 
204     @Test
requestDisplay_serviceFailure()205     public void requestDisplay_serviceFailure() throws Exception {
206         RemoteException thrownException = new RemoteException();
207         doThrow(thrownException).when(mService).requestDisplay(anyInt());
208 
209         mClusterHomeManager.requestDisplay(1);
210         verify(mCar).handleRemoteExceptionFromCarService(thrownException);
211     }
212 
213     @Test
startFixedActivityMode_serviceFailure()214     public void startFixedActivityMode_serviceFailure() throws Exception {
215         RemoteException thrownException = new RemoteException();
216         doThrow(thrownException)
217                 .when(mService).startFixedActivityModeAsUser(any(), any(), anyInt());
218 
219         boolean launchedAsFixedActivity =
220                 mClusterHomeManager.startFixedActivityModeAsUser(new Intent(), new Bundle(), 1);
221         verify(mCar).handleRemoteExceptionFromCarService(thrownException);
222         assertThat(launchedAsFixedActivity).isFalse();
223     }
224 
225     @Test
stopFixedActivityMode_serviceFailure()226     public void stopFixedActivityMode_serviceFailure() throws Exception {
227         RemoteException thrownException = new RemoteException();
228         doThrow(thrownException).when(mService).stopFixedActivityMode();
229 
230         mClusterHomeManager.stopFixedActivityMode();
231 
232         verify(mCar).handleRemoteExceptionFromCarService(thrownException);
233     }
234 
235     @Test
unregisterClusterStateListener_serviceFailure()236     public void unregisterClusterStateListener_serviceFailure() throws Exception {
237         RemoteException thrownException = new RemoteException();
238         doNothing().when(mService).registerClusterStateListener(any());
239         doThrow(thrownException).when(mService).unregisterClusterStateListener(any());
240         mClusterHomeManager.registerClusterStateListener(mCurrentThreadExecutor,
241                 mClusterStateListener);
242 
243         mClusterHomeManager.unregisterClusterStateListener(mClusterStateListener);
244 
245         verifyNoMoreInteractions(mCar);
246     }
247 
248     @Test
unregisterClusterStateListener_callbackNotPresent_doNothing()249     public void unregisterClusterStateListener_callbackNotPresent_doNothing() throws Exception {
250         RemoteException thrownException = new RemoteException();
251         doNothing().when(mService).registerClusterStateListener(any());
252         doThrow(thrownException).when(mService).unregisterClusterStateListener(any());
253 
254         mClusterHomeManager.unregisterClusterStateListener(mClusterStateListener);
255 
256         verifyNoMoreInteractions(mCar);
257         verifyNoMoreInteractions(mService);
258     }
259 
260     @Test
unregisterClusterNavigationStateListener_serviceFailure()261     public void unregisterClusterNavigationStateListener_serviceFailure() throws Exception {
262         RemoteException thrownException = new RemoteException();
263         doNothing().when(mService).registerClusterNavigationStateListener(any());
264         doThrow(thrownException).when(mService).unregisterClusterNavigationStateListener(any());
265         mClusterHomeManager.registerClusterNavigationStateListener(mCurrentThreadExecutor,
266                 mClusterNavigationStateListener);
267 
268         mClusterHomeManager
269                 .unregisterClusterNavigationStateListener(mClusterNavigationStateListener);
270 
271         verifyNoMoreInteractions(mCar);
272     }
273 
274     @Test
unregisterClusterNavigationStateListener_callbackNotPresent_doNothing()275     public void unregisterClusterNavigationStateListener_callbackNotPresent_doNothing()
276             throws Exception {
277         RemoteException thrownException = new RemoteException();
278         doNothing().when(mService).registerClusterNavigationStateListener(any());
279         doThrow(thrownException).when(mService).unregisterClusterNavigationStateListener(any());
280 
281         mClusterHomeManager
282                 .unregisterClusterNavigationStateListener(mClusterNavigationStateListener);
283         verifyNoMoreInteractions(mCar);
284         verifyNoMoreInteractions(mService);
285     }
286 
287     @Test
sendHeartbeat_serviceFailure()288     public void sendHeartbeat_serviceFailure() throws Exception {
289         RemoteException thrownException = new RemoteException();
290         doThrow(thrownException).when(mService).sendHeartbeat(anyLong(), any());
291 
292         mClusterHomeManager.sendHeartbeat(System.nanoTime(), /* appMetadata= */ null);
293 
294         verify(mCar).handleRemoteExceptionFromCarService(thrownException);
295     }
296 
297     @Test
startVisibilityMonitoring_startsOrStopsMonitoring_perSurfaceReadiness()298     public void startVisibilityMonitoring_startsOrStopsMonitoring_perSurfaceReadiness()
299             throws Exception {
300         setUpActivity();
301 
302         mClusterHomeManager.startVisibilityMonitoring(mActivity);
303 
304         // Checks whether the necessary callbacks are registered.
305         ArgumentCaptor<ViewTreeObserver.OnPreDrawListener> onPreDrawListenerCaptor =
306                 ArgumentCaptor.forClass(ViewTreeObserver.OnPreDrawListener.class);
307         verify(mViewTreeObserver).addOnPreDrawListener(onPreDrawListenerCaptor.capture());
308         assertThat(onPreDrawListenerCaptor).isNotNull();
309         ArgumentCaptor<ViewTreeObserver.OnWindowAttachListener> onWindowAttachListenerCaptor =
310                 ArgumentCaptor.forClass(ViewTreeObserver.OnWindowAttachListener.class);
311         verify(mViewTreeObserver).addOnWindowAttachListener(onWindowAttachListenerCaptor.capture());
312         assertThat(onWindowAttachListenerCaptor).isNotNull();
313         verifyNoMoreInteractions(mService);
314 
315         // Starts monitoring when the Surface is ready.
316         onPreDrawListenerCaptor.getValue().onPreDraw();
317         verify(mService).startVisibilityMonitoring(mSurfaceControl);
318 
319         // Stops monitoring when the Surface is removed.
320         onWindowAttachListenerCaptor.getValue().onWindowDetached();
321         verify(mService).stopVisibilityMonitoring();
322     }
323 
setUpActivity()324     private void setUpActivity() {
325         when(mCar.getContext()).thenReturn(mActivity);
326         when(mActivity.checkCallingOrSelfPermission(anyString())).thenReturn(PERMISSION_GRANTED);
327         when(mActivity.getWindow()).thenReturn(mWindow);
328         when(mWindow.getDecorView()).thenReturn(mDecorView);
329         when(mDecorView.getViewTreeObserver()).thenReturn(mViewTreeObserver);
330         when(mDecorView.getViewRootImpl()).thenReturn(mViewRoot);
331         when(mViewRoot.getSurfaceControl()).thenReturn(
332                 null,  // Supposed to be called in onCreate
333                 mSurfaceControl);  // Supposed to be called in onAttachedToWindow
334     }
335 }
336