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 com.android.car.garagemode; 18 19 import static com.google.common.truth.Truth.assertThat; 20 import static com.google.common.truth.Truth.assertWithMessage; 21 22 import static org.mockito.ArgumentMatchers.any; 23 import static org.mockito.ArgumentMatchers.anyInt; 24 import static org.mockito.Mockito.doAnswer; 25 import static org.mockito.Mockito.mock; 26 import static org.mockito.Mockito.timeout; 27 import static org.mockito.Mockito.verify; 28 import static org.mockito.Mockito.when; 29 30 import android.app.job.JobScheduler; 31 import android.car.user.CarUserManager; 32 import android.car.user.CarUserManager.UserLifecycleEvent; 33 import android.car.user.CarUserManager.UserLifecycleListener; 34 import android.content.Context; 35 import android.os.Handler; 36 import android.os.HandlerThread; 37 import android.os.Looper; 38 import android.util.Log; 39 40 import androidx.test.ext.junit.runners.AndroidJUnit4; 41 import androidx.test.filters.SmallTest; 42 43 import com.android.car.CarLocalServices; 44 import com.android.car.power.CarPowerManagementService; 45 import com.android.car.systeminterface.SystemInterface; 46 import com.android.car.user.CarUserService; 47 48 import org.junit.After; 49 import org.junit.Before; 50 import org.junit.Rule; 51 import org.junit.Test; 52 import org.junit.runner.RunWith; 53 import org.mockito.ArgumentCaptor; 54 import org.mockito.Mock; 55 import org.mockito.junit.MockitoJUnit; 56 import org.mockito.junit.MockitoRule; 57 58 import java.io.File; 59 import java.io.IOException; 60 import java.nio.file.Files; 61 import java.util.ArrayList; 62 import java.util.Arrays; 63 import java.util.concurrent.CountDownLatch; 64 import java.util.concurrent.TimeUnit; 65 66 @RunWith(AndroidJUnit4.class) 67 @SmallTest 68 public final class GarageModeTest { 69 70 private static final int DEFAULT_TIMEOUT_MS = 1000; 71 private static final String TAG = "GarageModeTest"; 72 73 @Rule 74 public final MockitoRule rule = MockitoJUnit.rule(); 75 private GarageMode mGarageMode; 76 @Mock 77 private Context mContext; 78 @Mock 79 private GarageModeController mController; 80 @Mock 81 private JobScheduler mJobScheduler; 82 @Mock 83 private CarUserService mCarUserService; 84 private final Handler mHandler = new Handler(Looper.getMainLooper()); 85 private final HandlerThread mBgHandlerThread = new HandlerThread("GarageModeTest"); 86 private Handler mBgHandler; 87 88 @Mock 89 private SystemInterface mSystemInterface; 90 private File mTempTestDir; 91 92 @Before setUp()93 public void setUp() throws IOException { 94 mBgHandlerThread.start(); 95 mBgHandler = new Handler(mBgHandlerThread.getLooper()); 96 97 when(mController.getHandler()).thenReturn(mHandler); 98 when(mContext.getSystemService(JobScheduler.class)).thenReturn(mJobScheduler); 99 100 CarLocalServices.removeServiceForTest(CarUserService.class); 101 CarLocalServices.addService(CarUserService.class, mCarUserService); 102 103 CarLocalServices.removeServiceForTest(SystemInterface.class); 104 CarLocalServices.addService(SystemInterface.class, mSystemInterface); 105 106 mTempTestDir = Files.createTempDirectory("garagemode_test").toFile(); 107 when(mSystemInterface.getSystemCarDir()).thenReturn(mTempTestDir); 108 Log.v(TAG, "Using temp dir: %s " + mTempTestDir.getAbsolutePath()); 109 110 mGarageMode = new GarageMode(mContext, mController); 111 112 mGarageMode.init(); 113 } 114 115 @After teardown()116 public void teardown() throws Exception { 117 mBgHandlerThread.quitSafely(); 118 mBgHandlerThread.join(); 119 120 CarLocalServices.removeServiceForTest(CarUserService.class); 121 } 122 123 @Test test_releaseRemoveListener()124 public void test_releaseRemoveListener() { 125 mGarageMode.release(); 126 127 verify(mCarUserService).removeUserLifecycleListener(any()); 128 } 129 130 @Test test_backgroundUsersStopedOnGarageModeCancel()131 public void test_backgroundUsersStopedOnGarageModeCancel() throws Exception { 132 ArrayList<Integer> userToStartInBackground = new ArrayList<>(Arrays.asList(101, 102, 103)); 133 when(mCarUserService.startAllBackgroundUsersInGarageMode()) 134 .thenReturn(userToStartInBackground); 135 mockCarUserServiceStopUserCall(getEventListener()); 136 137 mHandler.post(() -> { 138 mGarageMode.enterGarageMode(/* completor= */ null); 139 }); 140 141 verify(mCarUserService, timeout(DEFAULT_TIMEOUT_MS)).startAllBackgroundUsersInGarageMode(); 142 143 CountDownLatch latch = new CountDownLatch(1); 144 mHandler.post(() -> { 145 mGarageMode.cancel(() -> latch.countDown()); 146 }); 147 148 waitForHandlerThreadToFinish(latch); 149 verify(mCarUserService).startAllBackgroundUsersInGarageMode(); 150 assertThat(mGarageMode.getStartedBackgroundUsers()).isEmpty(); 151 } 152 153 @Test test_backgroundUsersStopedOnGarageModeCancel_beforeStartingBgUsers()154 public void test_backgroundUsersStopedOnGarageModeCancel_beforeStartingBgUsers() 155 throws Exception { 156 ArrayList<Integer> userToStartInBackground = new ArrayList<>(Arrays.asList(101, 102, 103)); 157 when(mCarUserService.startAllBackgroundUsersInGarageMode()) 158 .thenReturn(userToStartInBackground); 159 mockCarUserServiceStopUserCall(getEventListener()); 160 161 mHandler.post(() -> { 162 mGarageMode.enterGarageMode(/* completor= */ null); 163 }); 164 165 CountDownLatch latch = new CountDownLatch(1); 166 // It is possible that cancel is called before the background users are started, in this 167 // case the completor must still be called. 168 mHandler.post(() -> { 169 mGarageMode.cancel(() -> latch.countDown()); 170 }); 171 172 waitForHandlerThreadToFinish(latch); 173 assertThat(mGarageMode.getStartedBackgroundUsers()).isEmpty(); 174 } 175 176 177 @Test test_backgroundUsersStoppedOnGarageModeFinish()178 public void test_backgroundUsersStoppedOnGarageModeFinish() throws Exception { 179 ArrayList<Integer> userToStartInBackground = new ArrayList<>(Arrays.asList(101, 102, 103)); 180 when(mCarUserService.startAllBackgroundUsersInGarageMode()) 181 .thenReturn(userToStartInBackground); 182 mockCarUserServiceStopUserCall(getEventListener()); 183 184 CountDownLatch latch = new CountDownLatch(1); 185 mHandler.post(() -> { 186 mGarageMode.enterGarageMode(() -> latch.countDown()); 187 }); 188 189 verify(mCarUserService, timeout(DEFAULT_TIMEOUT_MS)).startAllBackgroundUsersInGarageMode(); 190 191 mHandler.post(() -> { 192 mGarageMode.finish(); 193 }); 194 195 waitForHandlerThreadToFinish(latch); 196 assertThat(mGarageMode.getStartedBackgroundUsers()).isEmpty(); 197 } 198 199 @Test test_restartingGarageModeStorePreviouslyStartedUsers()200 public void test_restartingGarageModeStorePreviouslyStartedUsers() throws Exception { 201 ArrayList<Integer> userToStartInBackground = new ArrayList<>(Arrays.asList(101, 102, 103)); 202 CountDownLatch latch = mockCarUserServiceStartUsersCall(userToStartInBackground); 203 mGarageMode.enterGarageMode(/* completor= */ null); 204 205 waitForHandlerThreadToFinish(latch); 206 assertThat(mGarageMode.getStartedBackgroundUsers()).containsExactly(101, 102, 103); 207 208 userToStartInBackground = new ArrayList<>(Arrays.asList(103, 104, 105)); 209 latch = mockCarUserServiceStartUsersCall(userToStartInBackground); 210 mGarageMode.enterGarageMode(/* completor= */ null); 211 212 waitForHandlerThreadToFinish(latch); 213 assertThat(mGarageMode.getStartedBackgroundUsers()) 214 .containsExactly(101, 102, 103, 104, 105); 215 } 216 217 @Test test_garageModeTestExitImmediately()218 public void test_garageModeTestExitImmediately() throws Exception { 219 CarPowerManagementService mockCarPowerManagementService = 220 mock(CarPowerManagementService.class); 221 222 // Mock CPMS to force Garage Mode early exit 223 CarLocalServices.removeServiceForTest(CarPowerManagementService.class); 224 CarLocalServices.addService(CarPowerManagementService.class, mockCarPowerManagementService); 225 when(mockCarPowerManagementService.garageModeShouldExitImmediately()).thenReturn(true); 226 227 // Check exit immediately without completor 228 GarageMode garageMode = new GarageMode(mContext, mController); 229 garageMode.init(); 230 garageMode.enterGarageMode(/* completor= */ null); 231 assertThat(garageMode.isGarageModeActive()).isFalse(); 232 233 // Create new instance of GarageMode 234 garageMode = new GarageMode(mContext, mController); 235 garageMode.init(); 236 // Check exit immediately with completor 237 CompletorImpl completor = new CompletorImpl(); 238 garageMode.enterGarageMode(completor); 239 assertThat(garageMode.isGarageModeActive()).isFalse(); 240 assertThat(completor.isFinished()).isTrue(); 241 242 CarLocalServices.removeServiceForTest(CarPowerManagementService.class); 243 } 244 waitForHandlerThreadToFinish(CountDownLatch latch)245 private void waitForHandlerThreadToFinish(CountDownLatch latch) throws Exception { 246 assertWithMessage("Latch has timed out.") 247 .that(latch.await(DEFAULT_TIMEOUT_MS, TimeUnit.MILLISECONDS)).isTrue(); 248 mHandler.runWithScissors(() -> {}, DEFAULT_TIMEOUT_MS); 249 } 250 mockCarUserServiceStartUsersCall( ArrayList<Integer> userToStartInBackground)251 private CountDownLatch mockCarUserServiceStartUsersCall( 252 ArrayList<Integer> userToStartInBackground) { 253 CountDownLatch latch = new CountDownLatch(1); 254 doAnswer(inv -> { 255 latch.countDown(); 256 return userToStartInBackground; 257 }).when(mCarUserService).startAllBackgroundUsersInGarageMode(); 258 259 return latch; 260 } 261 getEventListener()262 private UserLifecycleListener getEventListener() { 263 ArgumentCaptor<UserLifecycleListener> listenerCaptor = 264 ArgumentCaptor.forClass(UserLifecycleListener.class); 265 verify(mCarUserService).addUserLifecycleListener(any(), listenerCaptor.capture()); 266 UserLifecycleListener listener = listenerCaptor.getValue(); 267 return listener; 268 } 269 mockCarUserServiceStopUserCall(UserLifecycleListener listener)270 private void mockCarUserServiceStopUserCall(UserLifecycleListener listener) { 271 doAnswer(inv -> { 272 int userId = (int) inv.getArguments()[0]; 273 mBgHandler.post(() -> listener.onEvent(new UserLifecycleEvent( 274 CarUserManager.USER_LIFECYCLE_EVENT_TYPE_STOPPED, userId))); 275 return true; 276 }).when(mCarUserService).stopBackgroundUserInGagageMode(anyInt()); 277 } 278 279 private static final class CompletorImpl implements Runnable { 280 private boolean mFinished; 281 282 @Override run()283 public void run() { 284 mFinished = true; 285 } 286 isFinished()287 public boolean isFinished() { 288 return mFinished; 289 } 290 } 291 } 292 293