| /* |
| * Copyright (C) 2019 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.car.vms; |
| |
| import static org.junit.Assert.assertEquals; |
| import static org.junit.Assert.assertTrue; |
| import static org.mockito.ArgumentMatchers.any; |
| import static org.mockito.ArgumentMatchers.anyInt; |
| import static org.mockito.ArgumentMatchers.eq; |
| import static org.mockito.ArgumentMatchers.isNull; |
| import static org.mockito.Mockito.atLeast; |
| import static org.mockito.Mockito.reset; |
| import static org.mockito.Mockito.times; |
| import static org.mockito.Mockito.verify; |
| import static org.mockito.Mockito.verifyNoMoreInteractions; |
| import static org.mockito.Mockito.verifyZeroInteractions; |
| import static org.mockito.Mockito.when; |
| |
| import android.car.userlib.CarUserManagerHelper; |
| import android.content.ComponentName; |
| import android.content.Context; |
| import android.content.Intent; |
| import android.content.IntentFilter; |
| import android.content.ServiceConnection; |
| import android.content.pm.PackageManager; |
| import android.content.pm.UserInfo; |
| import android.content.res.Resources; |
| import android.os.Binder; |
| import android.os.Handler; |
| import android.os.UserHandle; |
| |
| import androidx.test.filters.SmallTest; |
| import androidx.test.runner.AndroidJUnit4; |
| |
| import org.junit.After; |
| import org.junit.Before; |
| import org.junit.Rule; |
| import org.junit.Test; |
| import org.junit.runner.RunWith; |
| import org.mockito.ArgumentCaptor; |
| import org.mockito.Captor; |
| import org.mockito.Mock; |
| import org.mockito.junit.MockitoJUnit; |
| import org.mockito.junit.MockitoRule; |
| |
| @RunWith(AndroidJUnit4.class) |
| @SmallTest |
| public class VmsClientManagerTest { |
| @Rule |
| public MockitoRule mMockitoRule = MockitoJUnit.rule(); |
| @Mock |
| private Context mContext; |
| @Mock |
| private PackageManager mPackageManager; |
| @Mock |
| private Resources mResources; |
| @Mock |
| private CarUserManagerHelper mUserManager; |
| private UserInfo mUserInfo; |
| |
| @Mock |
| private VmsClientManager.ConnectionListener mConnectionListener; |
| private VmsClientManager mClientManager; |
| |
| @Captor |
| private ArgumentCaptor<ServiceConnection> mConnectionCaptor; |
| |
| @Before |
| public void setUp() { |
| resetContext(); |
| when(mPackageManager.isPackageAvailable(any())).thenReturn(true); |
| |
| when(mResources.getInteger( |
| com.android.car.R.integer.millisecondsBeforeRebindToVmsPublisher)).thenReturn( |
| 5); |
| when(mResources.getStringArray( |
| com.android.car.R.array.vmsPublisherSystemClients)).thenReturn( |
| new String[]{ |
| "com.google.android.apps.vms.test/.VmsSystemClient" |
| }); |
| when(mResources.getStringArray( |
| com.android.car.R.array.vmsPublisherUserClients)).thenReturn( |
| new String[]{ |
| "com.google.android.apps.vms.test/.VmsUserClient" |
| }); |
| mUserInfo = new UserInfo(10, "Driver", 0); |
| when(mUserManager.getCurrentForegroundUserInfo()).thenReturn(mUserInfo); |
| |
| mClientManager = new VmsClientManager(mContext, mUserManager); |
| mClientManager.registerConnectionListener(mConnectionListener); |
| } |
| |
| @After |
| public void tearDown() throws Exception { |
| Thread.sleep(10); // Time to allow for delayed rebinds to settle |
| verify(mContext, atLeast(0)).getResources(); |
| verify(mContext, atLeast(0)).getPackageManager(); |
| verifyNoMoreInteractions(mContext); |
| } |
| |
| @Test |
| public void testInit() { |
| mClientManager.init(); |
| |
| // Verify registration of boot completed receiver |
| ArgumentCaptor<IntentFilter> bootFilterCaptor = ArgumentCaptor.forClass(IntentFilter.class); |
| verify(mContext).registerReceiver(eq(mClientManager.mBootCompletedReceiver), |
| bootFilterCaptor.capture()); |
| IntentFilter bootFilter = bootFilterCaptor.getValue(); |
| assertEquals(1, bootFilter.countActions()); |
| assertTrue(bootFilter.hasAction(Intent.ACTION_LOCKED_BOOT_COMPLETED)); |
| |
| // Verify registration of user switch receiver |
| ArgumentCaptor<IntentFilter> userFilterCaptor = ArgumentCaptor.forClass(IntentFilter.class); |
| verify(mContext).registerReceiverAsUser(eq(mClientManager.mUserSwitchReceiver), |
| eq(UserHandle.ALL), userFilterCaptor.capture(), isNull(), isNull()); |
| IntentFilter userEventFilter = userFilterCaptor.getValue(); |
| assertEquals(2, userEventFilter.countActions()); |
| assertTrue(userEventFilter.hasAction(Intent.ACTION_USER_SWITCHED)); |
| assertTrue(userEventFilter.hasAction(Intent.ACTION_USER_UNLOCKED)); |
| } |
| |
| @Test |
| public void testRelease() { |
| mClientManager.release(); |
| |
| // Verify both receivers are unregistered |
| verify(mContext).unregisterReceiver(mClientManager.mBootCompletedReceiver); |
| verify(mContext).unregisterReceiver(mClientManager.mUserSwitchReceiver); |
| } |
| |
| @Test |
| public void testLockedBootCompleted() { |
| notifyLockedBootCompleted(); |
| notifyLockedBootCompleted(); |
| |
| // Multiple events should only trigger a single bind, when successful |
| verifySystemBind(1); |
| } |
| |
| @Test |
| public void testLockedBootCompleted_BindFailed() { |
| when(mContext.bindServiceAsUser(any(), any(), anyInt(), any(), any())).thenReturn(false); |
| notifyLockedBootCompleted(); |
| notifyLockedBootCompleted(); |
| |
| // Failure state will trigger another attempt on event |
| verifySystemBind(2); |
| } |
| |
| @Test |
| public void testLockedBootCompleted_BindException() { |
| when(mContext.bindServiceAsUser(any(), any(), anyInt(), any(), any())).thenThrow( |
| new SecurityException()); |
| notifyLockedBootCompleted(); |
| notifyLockedBootCompleted(); |
| |
| // Failure state will trigger another attempt on event |
| verifySystemBind(2); |
| } |
| |
| @Test |
| public void testUserSwitched() { |
| notifyUserSwitched(); |
| notifyUserSwitched(); |
| |
| // Multiple events should only trigger a single bind, when successful |
| verifyUserBind(1); |
| } |
| |
| @Test |
| public void testUserSwitched_BindFailed() { |
| when(mContext.bindServiceAsUser(any(), any(), anyInt(), any(), any())).thenReturn(false); |
| notifyUserSwitched(); |
| notifyUserSwitched(); |
| |
| // Failure state will trigger another attempt on event |
| verifyUserBind(2); |
| } |
| |
| @Test |
| public void testUserSwitched_BindException() { |
| when(mContext.bindServiceAsUser(any(), any(), anyInt(), any(), any())).thenThrow( |
| new SecurityException()); |
| notifyUserSwitched(); |
| notifyUserSwitched(); |
| |
| // Failure state will trigger another attempt on event |
| verifyUserBind(2); |
| } |
| |
| @Test |
| public void testUserUnlocked() { |
| notifyUserUnlocked(); |
| notifyUserUnlocked(); |
| |
| // Multiple events should only trigger a single bind, when successful |
| verifyUserBind(1); |
| } |
| |
| @Test |
| public void testUserUnlocked_BindFailed() { |
| when(mContext.bindServiceAsUser(any(), any(), anyInt(), any(), any())).thenReturn(false); |
| notifyUserUnlocked(); |
| notifyUserUnlocked(); |
| |
| // Failure state will trigger another attempt on event |
| verifyUserBind(2); |
| } |
| |
| @Test |
| public void testUserUnlocked_BindException() { |
| when(mContext.bindServiceAsUser(any(), any(), anyInt(), any(), any())).thenThrow( |
| new SecurityException()); |
| notifyUserUnlocked(); |
| notifyUserUnlocked(); |
| |
| // Failure state will trigger another attempt on event |
| verifyUserBind(2); |
| } |
| |
| @Test |
| public void testUserSwitchedAndUnlocked() { |
| notifyUserSwitched(); |
| notifyUserUnlocked(); |
| |
| // Multiple events should only trigger a single bind, when successful |
| verifyUserBind(1); |
| } |
| |
| @Test |
| public void testUserSwitchedToSystemUser() { |
| mUserInfo = new UserInfo(UserHandle.USER_SYSTEM, "Owner", 0); |
| when(mUserManager.getCurrentForegroundUserInfo()).thenReturn(mUserInfo); |
| notifyUserSwitched(); |
| |
| // System user should not trigger any binding |
| verifyUserBind(0); |
| } |
| |
| @Test |
| public void testUnregisterConnectionListener() { |
| mClientManager.unregisterConnectionListener(mConnectionListener); |
| notifyLockedBootCompleted(); |
| verifySystemBind(1); |
| |
| ServiceConnection connection = mConnectionCaptor.getValue(); |
| connection.onServiceConnected(null, new Binder()); |
| verifyZeroInteractions(mConnectionListener); |
| } |
| |
| @Test |
| public void testOnSystemServiceConnected() { |
| notifyLockedBootCompleted(); |
| verifySystemBind(1); |
| resetContext(); |
| |
| Binder binder = new Binder(); |
| ServiceConnection connection = mConnectionCaptor.getValue(); |
| connection.onServiceConnected(null, binder); |
| |
| verify(mConnectionListener).onClientConnected( |
| eq("com.google.android.apps.vms.test/com.google.android.apps.vms.test" |
| + ".VmsSystemClient U=0"), |
| eq(binder)); |
| } |
| |
| @Test |
| public void testOnUserServiceConnected() { |
| notifyUserSwitched(); |
| verifyUserBind(1); |
| resetContext(); |
| |
| Binder binder = new Binder(); |
| ServiceConnection connection = mConnectionCaptor.getValue(); |
| connection.onServiceConnected(null, binder); |
| |
| verify(mConnectionListener).onClientConnected( |
| eq("com.google.android.apps.vms.test/com.google.android.apps.vms.test" |
| + ".VmsUserClient U=10"), |
| eq(binder)); |
| } |
| |
| @Test |
| public void testOnSystemServiceDisconnected() throws Exception { |
| notifyLockedBootCompleted(); |
| verifySystemBind(1); |
| resetContext(); |
| |
| ServiceConnection connection = mConnectionCaptor.getValue(); |
| connection.onServiceConnected(null, new Binder()); |
| connection.onServiceDisconnected(null); |
| |
| verify(mContext).unbindService(connection); |
| verify(mConnectionListener).onClientDisconnected( |
| eq("com.google.android.apps.vms.test/com.google.android.apps.vms.test" |
| + ".VmsSystemClient U=0")); |
| |
| Thread.sleep(10); |
| verifySystemBind(1); |
| } |
| |
| @Test |
| public void testOnSystemServiceDisconnected_ServiceNotConnected() throws Exception { |
| notifyLockedBootCompleted(); |
| verifySystemBind(1); |
| resetContext(); |
| |
| ServiceConnection connection = mConnectionCaptor.getValue(); |
| connection.onServiceDisconnected(null); |
| |
| verify(mContext).unbindService(connection); |
| verifyZeroInteractions(mConnectionListener); |
| |
| Thread.sleep(10); |
| verifySystemBind(1); |
| } |
| |
| @Test |
| public void testOnUserServiceDisconnected() throws Exception { |
| notifyUserSwitched(); |
| verifyUserBind(1); |
| resetContext(); |
| |
| ServiceConnection connection = mConnectionCaptor.getValue(); |
| connection.onServiceConnected(null, new Binder()); |
| connection.onServiceDisconnected(null); |
| |
| verify(mContext).unbindService(connection); |
| verify(mConnectionListener).onClientDisconnected( |
| eq("com.google.android.apps.vms.test/com.google.android.apps.vms.test" |
| + ".VmsUserClient U=10")); |
| |
| Thread.sleep(10); |
| verifyUserBind(1); |
| } |
| |
| @Test |
| public void testOnUserServiceDisconnected_ServiceNotConnected() throws Exception { |
| notifyUserSwitched(); |
| verifyUserBind(1); |
| resetContext(); |
| |
| ServiceConnection connection = mConnectionCaptor.getValue(); |
| connection.onServiceDisconnected(null); |
| |
| verify(mContext).unbindService(connection); |
| verifyZeroInteractions(mConnectionListener); |
| |
| Thread.sleep(10); |
| verifyUserBind(1); |
| } |
| |
| @Test |
| public void testOnSystemServiceBindingDied() throws Exception { |
| notifyLockedBootCompleted(); |
| verifySystemBind(1); |
| resetContext(); |
| |
| ServiceConnection connection = mConnectionCaptor.getValue(); |
| connection.onServiceConnected(null, new Binder()); |
| connection.onBindingDied(null); |
| |
| verify(mContext).unbindService(connection); |
| verify(mConnectionListener).onClientDisconnected( |
| eq("com.google.android.apps.vms.test/com.google.android.apps.vms.test" |
| + ".VmsSystemClient U=0")); |
| |
| Thread.sleep(10); |
| verifySystemBind(1); |
| } |
| |
| @Test |
| public void testOnSystemServiceBindingDied_ServiceNotConnected() throws Exception { |
| notifyLockedBootCompleted(); |
| verifySystemBind(1); |
| resetContext(); |
| |
| ServiceConnection connection = mConnectionCaptor.getValue(); |
| connection.onBindingDied(null); |
| |
| verify(mContext).unbindService(connection); |
| verifyZeroInteractions(mConnectionListener); |
| |
| Thread.sleep(10); |
| verifySystemBind(1); |
| } |
| |
| @Test |
| public void testOnUserServiceBindingDied() throws Exception { |
| notifyUserSwitched(); |
| verifyUserBind(1); |
| resetContext(); |
| |
| ServiceConnection connection = mConnectionCaptor.getValue(); |
| connection.onServiceConnected(null, new Binder()); |
| connection.onBindingDied(null); |
| |
| verify(mContext).unbindService(connection); |
| verify(mConnectionListener).onClientDisconnected( |
| eq("com.google.android.apps.vms.test/com.google.android.apps.vms.test" |
| + ".VmsUserClient U=10")); |
| |
| Thread.sleep(10); |
| verifyUserBind(1); |
| } |
| |
| @Test |
| public void testOnUserServiceBindingDied_ServiceNotConnected() throws Exception { |
| notifyUserSwitched(); |
| verifyUserBind(1); |
| resetContext(); |
| |
| ServiceConnection connection = mConnectionCaptor.getValue(); |
| connection.onBindingDied(null); |
| |
| verify(mContext).unbindService(connection); |
| verifyZeroInteractions(mConnectionListener); |
| |
| Thread.sleep(10); |
| verifyUserBind(1); |
| } |
| |
| @Test |
| public void testOnSystemServiceNullBinding() throws Exception { |
| notifyLockedBootCompleted(); |
| verifySystemBind(1); |
| resetContext(); |
| |
| ServiceConnection connection = mConnectionCaptor.getValue(); |
| connection.onServiceConnected(null, new Binder()); |
| connection.onNullBinding(null); |
| |
| verify(mContext).unbindService(connection); |
| verify(mConnectionListener).onClientDisconnected( |
| eq("com.google.android.apps.vms.test/com.google.android.apps.vms.test" |
| + ".VmsSystemClient U=0")); |
| } |
| |
| @Test |
| public void testOnSystemServiceNullBinding_ServiceNotConnected() throws Exception { |
| notifyLockedBootCompleted(); |
| verifySystemBind(1); |
| resetContext(); |
| |
| ServiceConnection connection = mConnectionCaptor.getValue(); |
| connection.onNullBinding(null); |
| |
| verify(mContext).unbindService(connection); |
| verifyZeroInteractions(mConnectionListener); |
| } |
| |
| @Test |
| public void testOnUserServiceNullBinding() throws Exception { |
| notifyUserSwitched(); |
| verifyUserBind(1); |
| resetContext(); |
| |
| ServiceConnection connection = mConnectionCaptor.getValue(); |
| connection.onServiceConnected(null, new Binder()); |
| connection.onNullBinding(null); |
| |
| verify(mContext).unbindService(connection); |
| verify(mConnectionListener).onClientDisconnected( |
| eq("com.google.android.apps.vms.test/com.google.android.apps.vms.test" |
| + ".VmsUserClient U=10")); |
| } |
| |
| @Test |
| public void testOnUserServiceNullBinding_ServiceNotConnected() throws Exception { |
| notifyUserSwitched(); |
| verifyUserBind(1); |
| resetContext(); |
| |
| ServiceConnection connection = mConnectionCaptor.getValue(); |
| connection.onNullBinding(null); |
| |
| verify(mContext).unbindService(connection); |
| verifyZeroInteractions(mConnectionListener); |
| } |
| |
| @Test |
| public void testOnUserSwitched_UserChange() { |
| notifyUserSwitched(); |
| verifyUserBind(1); |
| ServiceConnection connection = mConnectionCaptor.getValue(); |
| connection.onServiceConnected(null, new Binder()); |
| resetContext(); |
| reset(mConnectionListener); |
| |
| mUserInfo = new UserInfo(11, "Driver", 0); |
| when(mUserManager.getCurrentForegroundUserInfo()).thenReturn(mUserInfo); |
| notifyUserSwitched(); |
| |
| verify(mContext).unbindService(connection); |
| verify(mConnectionListener).onClientDisconnected( |
| eq("com.google.android.apps.vms.test/com.google.android.apps.vms.test" |
| + ".VmsUserClient U=10")); |
| verifyBind(1, "com.google.android.apps.vms.test/.VmsUserClient", |
| mUserInfo.getUserHandle()); |
| } |
| |
| @Test |
| public void testOnUserSwitched_UserChange_ToSystemUser() { |
| notifyUserSwitched(); |
| verifyUserBind(1); |
| ServiceConnection connection = mConnectionCaptor.getValue(); |
| connection.onServiceConnected(null, new Binder()); |
| resetContext(); |
| reset(mConnectionListener); |
| |
| mUserInfo = new UserInfo(UserHandle.USER_SYSTEM, "Owner", 0); |
| when(mUserManager.getCurrentForegroundUserInfo()).thenReturn(mUserInfo); |
| notifyUserSwitched(); |
| |
| verify(mContext).unbindService(connection); |
| verify(mConnectionListener).onClientDisconnected( |
| eq("com.google.android.apps.vms.test/com.google.android.apps.vms.test" |
| + ".VmsUserClient U=10")); |
| // User processes will not be bound for system user |
| verifyBind(0, "com.google.android.apps.vms.test/.VmsUserClient", |
| mUserInfo.getUserHandle()); |
| } |
| |
| @Test |
| public void testOnUserSwitched_UserChange_ServiceNotConnected() { |
| notifyUserSwitched(); |
| verifyUserBind(1); |
| ServiceConnection connection = mConnectionCaptor.getValue(); |
| resetContext(); |
| |
| mUserInfo = new UserInfo(11, "Driver", 0); |
| when(mUserManager.getCurrentForegroundUserInfo()).thenReturn(mUserInfo); |
| notifyUserSwitched(); |
| |
| verify(mContext).unbindService(connection); |
| verifyBind(1, "com.google.android.apps.vms.test/.VmsUserClient", |
| mUserInfo.getUserHandle()); |
| } |
| |
| @Test |
| public void testOnUserUnlocked_UserChange() { |
| notifyUserUnlocked(); |
| verifyUserBind(1); |
| ServiceConnection connection = mConnectionCaptor.getValue(); |
| connection.onServiceConnected(null, new Binder()); |
| resetContext(); |
| reset(mConnectionListener); |
| |
| mUserInfo = new UserInfo(11, "Driver", 0); |
| when(mUserManager.getCurrentForegroundUserInfo()).thenReturn(mUserInfo); |
| notifyUserUnlocked(); |
| |
| verify(mContext).unbindService(connection); |
| verify(mConnectionListener).onClientDisconnected( |
| eq("com.google.android.apps.vms.test/com.google.android.apps.vms.test" |
| + ".VmsUserClient U=10")); |
| verifyBind(1, "com.google.android.apps.vms.test/.VmsUserClient", |
| mUserInfo.getUserHandle()); |
| } |
| |
| @Test |
| public void testOnUserLocked_UserChange_ToSystemUser() { |
| notifyUserUnlocked(); |
| verifyUserBind(1); |
| ServiceConnection connection = mConnectionCaptor.getValue(); |
| connection.onServiceConnected(null, new Binder()); |
| resetContext(); |
| reset(mConnectionListener); |
| |
| mUserInfo = new UserInfo(UserHandle.USER_SYSTEM, "Owner", 0); |
| when(mUserManager.getCurrentForegroundUserInfo()).thenReturn(mUserInfo); |
| notifyUserUnlocked(); |
| |
| verify(mContext).unbindService(connection); |
| verify(mConnectionListener).onClientDisconnected( |
| eq("com.google.android.apps.vms.test/com.google.android.apps.vms.test" |
| + ".VmsUserClient U=10")); |
| // User processes will not be bound for system user |
| verifyBind(0, "com.google.android.apps.vms.test/.VmsUserClient", |
| mUserInfo.getUserHandle()); |
| } |
| |
| @Test |
| public void testOnUserUnlocked_UserChange_ServiceNotConnected() { |
| notifyUserUnlocked(); |
| verifyUserBind(1); |
| ServiceConnection connection = mConnectionCaptor.getValue(); |
| resetContext(); |
| |
| mUserInfo = new UserInfo(11, "Driver", 0); |
| when(mUserManager.getCurrentForegroundUserInfo()).thenReturn(mUserInfo); |
| notifyUserUnlocked(); |
| |
| verify(mContext).unbindService(connection); |
| verifyBind(1, "com.google.android.apps.vms.test/.VmsUserClient", |
| mUserInfo.getUserHandle()); |
| } |
| |
| private void resetContext() { |
| reset(mContext); |
| when(mContext.getPackageManager()).thenReturn(mPackageManager); |
| when(mContext.bindServiceAsUser(any(), any(), anyInt(), any(), any())).thenReturn(true); |
| when(mContext.getResources()).thenReturn(mResources); |
| } |
| |
| private void notifyLockedBootCompleted() { |
| mClientManager.mBootCompletedReceiver.onReceive(mContext, |
| new Intent(Intent.ACTION_LOCKED_BOOT_COMPLETED)); |
| } |
| |
| private void notifyUserSwitched() { |
| mClientManager.mUserSwitchReceiver.onReceive(mContext, |
| new Intent(Intent.ACTION_USER_SWITCHED)); |
| } |
| |
| private void notifyUserUnlocked() { |
| mClientManager.mUserSwitchReceiver.onReceive(mContext, |
| new Intent(Intent.ACTION_USER_UNLOCKED)); |
| } |
| |
| private void verifySystemBind(int times) { |
| verifyBind(times, "com.google.android.apps.vms.test/.VmsSystemClient", |
| UserHandle.SYSTEM); |
| } |
| |
| private void verifyUserBind(int times) { |
| verifyBind(times, "com.google.android.apps.vms.test/.VmsUserClient", |
| mUserInfo.getUserHandle()); |
| } |
| |
| private void verifyBind(int times, String componentName, |
| UserHandle user) { |
| ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class); |
| verify(mContext, times(times)).bindServiceAsUser( |
| intentCaptor.capture(), |
| mConnectionCaptor.capture(), |
| eq(Context.BIND_AUTO_CREATE), any(Handler.class), eq(user)); |
| if (times > 0) { |
| assertEquals( |
| ComponentName.unflattenFromString(componentName), |
| intentCaptor.getValue().getComponent()); |
| } |
| } |
| } |