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