| /* |
| * Copyright (C) 2021 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.systemui.car.systembar; |
| |
| import static android.hardware.SensorPrivacyManager.Sensors.MICROPHONE; |
| import static android.hardware.SensorPrivacyManager.Sources.QS_TILE; |
| |
| import static com.google.common.truth.Truth.assertThat; |
| |
| import static org.mockito.ArgumentMatchers.any; |
| import static org.mockito.ArgumentMatchers.anyInt; |
| import static org.mockito.ArgumentMatchers.argThat; |
| import static org.mockito.ArgumentMatchers.eq; |
| import static org.mockito.Mockito.never; |
| import static org.mockito.Mockito.reset; |
| import static org.mockito.Mockito.spy; |
| import static org.mockito.Mockito.times; |
| import static org.mockito.Mockito.verify; |
| import static org.mockito.Mockito.when; |
| |
| import android.car.Car; |
| import android.car.user.CarUserManager; |
| import android.content.BroadcastReceiver; |
| import android.content.Intent; |
| import android.content.IntentFilter; |
| import android.hardware.SensorPrivacyManager; |
| import android.testing.AndroidTestingRunner; |
| import android.testing.TestableLooper; |
| import android.view.LayoutInflater; |
| import android.view.View; |
| import android.widget.FrameLayout; |
| |
| import androidx.test.filters.SmallTest; |
| |
| import com.android.systemui.R; |
| import com.android.systemui.SysuiTestCase; |
| import com.android.systemui.broadcast.BroadcastDispatcher; |
| import com.android.systemui.car.CarDeviceProvisionedController; |
| import com.android.systemui.car.CarServiceProvider; |
| import com.android.systemui.car.CarSystemUiTest; |
| import com.android.systemui.car.privacy.MicPrivacyChip; |
| import com.android.systemui.privacy.PrivacyItem; |
| import com.android.systemui.privacy.PrivacyItemController; |
| import com.android.systemui.privacy.PrivacyType; |
| |
| import org.junit.Before; |
| import org.junit.Test; |
| import org.junit.runner.RunWith; |
| import org.mockito.ArgumentCaptor; |
| import org.mockito.Captor; |
| import org.mockito.Mock; |
| import org.mockito.MockitoAnnotations; |
| |
| import java.util.Collections; |
| import java.util.concurrent.Executor; |
| |
| @CarSystemUiTest |
| @RunWith(AndroidTestingRunner.class) |
| @TestableLooper.RunWithLooper |
| @SmallTest |
| public class PrivacyChipViewControllerTest extends SysuiTestCase { |
| |
| private PrivacyChipViewController mPrivacyChipViewController; |
| private FrameLayout mFrameLayout; |
| private MicPrivacyChip mMicPrivacyChip; |
| |
| @Captor |
| private ArgumentCaptor<Runnable> mRunnableArgumentCaptor; |
| @Captor |
| private ArgumentCaptor<PrivacyItemController.Callback> mPicCallbackArgumentCaptor; |
| @Captor |
| private ArgumentCaptor<SensorPrivacyManager.OnSensorPrivacyChangedListener> |
| mOnSensorPrivacyChangedListenerArgumentCaptor; |
| @Captor |
| private ArgumentCaptor<BroadcastReceiver> mBroadcastReceiverArgumentCaptor; |
| @Captor |
| private ArgumentCaptor<CarUserManager.UserLifecycleListener> |
| mUserLifecycleListenerArgumentCaptor; |
| |
| @Mock |
| private PrivacyItemController mPrivacyItemController; |
| @Mock |
| private PrivacyItem mPrivacyItem; |
| @Mock |
| private Executor mExecutor; |
| @Mock |
| private BroadcastDispatcher mBroadcastDispatcher; |
| @Mock |
| private SensorPrivacyManager mSensorPrivacyManager; |
| @Mock |
| private CarDeviceProvisionedController mCarDeviceProvisionedController; |
| @Mock |
| private CarUserManager mCarUserManager; |
| @Mock |
| private Car mCar; |
| @Mock |
| private Runnable mQsTileNotifyUpdateRunnable; |
| |
| @Before |
| public void setUp() { |
| MockitoAnnotations.initMocks(/* testClass= */ this); |
| |
| mFrameLayout = new FrameLayout(mContext); |
| mMicPrivacyChip = spy((MicPrivacyChip) LayoutInflater.from(mContext) |
| .inflate(R.layout.mic_privacy_chip, /* root= */ null)); |
| mFrameLayout.addView(mMicPrivacyChip); |
| mContext = spy(mContext); |
| |
| when(mContext.getMainExecutor()).thenReturn(mExecutor); |
| when(mCar.isConnected()).thenReturn(true); |
| when(mCar.getCarManager(Car.CAR_USER_SERVICE)).thenReturn(mCarUserManager); |
| |
| CarServiceProvider carServiceProvider = new CarServiceProvider(mContext, mCar); |
| |
| mPrivacyChipViewController = new PrivacyChipViewController(mContext, mPrivacyItemController, |
| carServiceProvider, mBroadcastDispatcher, mSensorPrivacyManager, |
| mCarDeviceProvisionedController); |
| when(mCarDeviceProvisionedController.getCurrentUser()).thenReturn(0); |
| } |
| |
| @Test |
| public void addPrivacyChipView_privacyChipViewPresent_addCallbackCalled() { |
| mPrivacyChipViewController.addPrivacyChipView(mFrameLayout); |
| |
| verify(mPrivacyItemController).addCallback(any()); |
| } |
| |
| @Test |
| public void addPrivacyChipView_privacyChipViewPresent_micStatusSet() { |
| when(mSensorPrivacyManager.isSensorPrivacyEnabled(MICROPHONE, /* userId= */ 0)) |
| .thenReturn(false); |
| mPrivacyChipViewController.addPrivacyChipView(mFrameLayout); |
| verify(mExecutor).execute(mRunnableArgumentCaptor.capture()); |
| |
| mRunnableArgumentCaptor.getValue().run(); |
| |
| verify(mMicPrivacyChip).setMicrophoneEnabled(eq(true)); |
| } |
| |
| @Test |
| public void addPrivacyChipView_privacyChipViewNotPresent_addCallbackNotCalled() { |
| mPrivacyChipViewController.addPrivacyChipView(new View(getContext())); |
| |
| verify(mPrivacyItemController, never()).addCallback(any()); |
| } |
| |
| @Test |
| public void onPrivacyItemsChanged_micIsPartOfPrivacyItems_animateInCalled() { |
| when(mPrivacyItem.getPrivacyType()).thenReturn(PrivacyType.TYPE_MICROPHONE); |
| mPrivacyChipViewController.addPrivacyChipView(mFrameLayout); |
| verify(mPrivacyItemController).addCallback(mPicCallbackArgumentCaptor.capture()); |
| mPicCallbackArgumentCaptor.getValue().onFlagAllChanged(true); |
| mPicCallbackArgumentCaptor.getValue().onFlagMicCameraChanged(true); |
| |
| mPicCallbackArgumentCaptor.getValue() |
| .onPrivacyItemsChanged(Collections.singletonList(mPrivacyItem)); |
| verify(mExecutor, times(2)).execute(mRunnableArgumentCaptor.capture()); |
| mRunnableArgumentCaptor.getAllValues().forEach(Runnable::run); |
| |
| verify(mMicPrivacyChip).animateIn(); |
| } |
| |
| @Test |
| public void onPrivacyItemsChanged_micIsPartOfPrivacyItemsTwice_animateInCalledOnce() { |
| when(mPrivacyItem.getPrivacyType()).thenReturn(PrivacyType.TYPE_MICROPHONE); |
| mPrivacyChipViewController.addPrivacyChipView(mFrameLayout); |
| verify(mPrivacyItemController).addCallback(mPicCallbackArgumentCaptor.capture()); |
| mPicCallbackArgumentCaptor.getValue().onFlagAllChanged(true); |
| mPicCallbackArgumentCaptor.getValue().onFlagMicCameraChanged(true); |
| |
| mPicCallbackArgumentCaptor.getValue() |
| .onPrivacyItemsChanged(Collections.singletonList(mPrivacyItem)); |
| mPicCallbackArgumentCaptor.getValue() |
| .onPrivacyItemsChanged(Collections.singletonList(mPrivacyItem)); |
| verify(mExecutor, times(2)).execute(mRunnableArgumentCaptor.capture()); |
| mRunnableArgumentCaptor.getAllValues().forEach(Runnable::run); |
| |
| verify(mMicPrivacyChip).animateIn(); |
| } |
| |
| @Test |
| public void onPrivacyItemsChanged_micIsNotPartOfPrivacyItems_animateOutCalled() { |
| when(mPrivacyItem.getPrivacyType()).thenReturn(PrivacyType.TYPE_MICROPHONE); |
| mPrivacyChipViewController.addPrivacyChipView(mFrameLayout); |
| verify(mPrivacyItemController).addCallback(mPicCallbackArgumentCaptor.capture()); |
| mPicCallbackArgumentCaptor.getValue().onFlagAllChanged(true); |
| mPicCallbackArgumentCaptor.getValue().onFlagMicCameraChanged(true); |
| mPicCallbackArgumentCaptor.getValue() |
| .onPrivacyItemsChanged(Collections.singletonList(mPrivacyItem)); |
| |
| mPicCallbackArgumentCaptor.getValue().onPrivacyItemsChanged(Collections.emptyList()); |
| verify(mExecutor, times(3)) |
| .execute(mRunnableArgumentCaptor.capture()); |
| mRunnableArgumentCaptor.getAllValues().forEach(Runnable::run); |
| |
| verify(mMicPrivacyChip).animateOut(); |
| } |
| |
| @Test |
| public void onPrivacyItemsChanged_micIsNotPartOfPrivacyItemsTwice_animateOutCalledOnce() { |
| when(mPrivacyItem.getPrivacyType()).thenReturn(PrivacyType.TYPE_MICROPHONE); |
| mPrivacyChipViewController.addPrivacyChipView(mFrameLayout); |
| verify(mPrivacyItemController).addCallback(mPicCallbackArgumentCaptor.capture()); |
| mPicCallbackArgumentCaptor.getValue().onFlagAllChanged(true); |
| mPicCallbackArgumentCaptor.getValue().onFlagMicCameraChanged(true); |
| mPicCallbackArgumentCaptor.getValue() |
| .onPrivacyItemsChanged(Collections.singletonList(mPrivacyItem)); |
| |
| mPicCallbackArgumentCaptor.getValue().onPrivacyItemsChanged(Collections.emptyList()); |
| mPicCallbackArgumentCaptor.getValue().onPrivacyItemsChanged(Collections.emptyList()); |
| verify(mExecutor, times(3)) |
| .execute(mRunnableArgumentCaptor.capture()); |
| mRunnableArgumentCaptor.getAllValues().forEach(Runnable::run); |
| |
| verify(mMicPrivacyChip).animateOut(); |
| } |
| |
| @Test |
| public void onPrivacyItemsChanged_qsTileNotifyUpdateRunnableExecuted() { |
| when(mPrivacyItem.getPrivacyType()).thenReturn(PrivacyType.TYPE_MICROPHONE); |
| mPrivacyChipViewController.setNotifyUpdateRunnable(mQsTileNotifyUpdateRunnable); |
| mPrivacyChipViewController.addPrivacyChipView(mFrameLayout); |
| verify(mPrivacyItemController).addCallback(mPicCallbackArgumentCaptor.capture()); |
| mPicCallbackArgumentCaptor.getValue().onFlagAllChanged(true); |
| mPicCallbackArgumentCaptor.getValue().onFlagMicCameraChanged(true); |
| |
| mPicCallbackArgumentCaptor.getValue().onPrivacyItemsChanged(Collections.emptyList()); |
| verify(mExecutor).execute(mRunnableArgumentCaptor.capture()); |
| mRunnableArgumentCaptor.getAllValues().forEach(Runnable::run); |
| |
| verify(mQsTileNotifyUpdateRunnable).run(); |
| } |
| |
| @Test |
| public void onSensorPrivacyChanged_argTrue_setMicrophoneEnabledWithFalseCalled() { |
| mPrivacyChipViewController.addPrivacyChipView(mFrameLayout); |
| verify(mSensorPrivacyManager).addSensorPrivacyListener(eq(MICROPHONE), |
| /* userId= */ eq(0), mOnSensorPrivacyChangedListenerArgumentCaptor.capture()); |
| reset(mMicPrivacyChip); |
| reset(mExecutor); |
| mOnSensorPrivacyChangedListenerArgumentCaptor.getValue() |
| .onSensorPrivacyChanged(MICROPHONE, /* enabled= */ true); |
| verify(mExecutor).execute(mRunnableArgumentCaptor.capture()); |
| |
| mRunnableArgumentCaptor.getAllValues().forEach(Runnable::run); |
| |
| verify(mMicPrivacyChip).setMicrophoneEnabled(eq(false)); |
| } |
| |
| @Test |
| public void onSensorPrivacyChanged_argFalse_setMicrophoneEnabledWithTrueCalled() { |
| mPrivacyChipViewController.addPrivacyChipView(mFrameLayout); |
| verify(mSensorPrivacyManager).addSensorPrivacyListener(eq(MICROPHONE), |
| /* userId= */ eq(0), mOnSensorPrivacyChangedListenerArgumentCaptor.capture()); |
| reset(mMicPrivacyChip); |
| reset(mExecutor); |
| mOnSensorPrivacyChangedListenerArgumentCaptor.getValue() |
| .onSensorPrivacyChanged(MICROPHONE, /* enabled= */ false); |
| verify(mExecutor).execute(mRunnableArgumentCaptor.capture()); |
| |
| mRunnableArgumentCaptor.getAllValues().forEach(Runnable::run); |
| |
| verify(mMicPrivacyChip).setMicrophoneEnabled(eq(true)); |
| } |
| |
| @Test |
| public void onSensorPrivacyChanged_qsTileNotifyUpdateRunnableExecuted() { |
| mPrivacyChipViewController.setNotifyUpdateRunnable(mQsTileNotifyUpdateRunnable); |
| mPrivacyChipViewController.addPrivacyChipView(mFrameLayout); |
| verify(mSensorPrivacyManager).addSensorPrivacyListener(eq(MICROPHONE), |
| /* userId= */ eq(0), mOnSensorPrivacyChangedListenerArgumentCaptor.capture()); |
| reset(mMicPrivacyChip); |
| reset(mExecutor); |
| mOnSensorPrivacyChangedListenerArgumentCaptor.getValue() |
| .onSensorPrivacyChanged(MICROPHONE, /* enabled= */ true); |
| verify(mExecutor).execute(mRunnableArgumentCaptor.capture()); |
| |
| mRunnableArgumentCaptor.getAllValues().forEach(Runnable::run); |
| |
| verify(mQsTileNotifyUpdateRunnable).run(); |
| } |
| |
| @Test |
| public void onUserUpdateReceive_setMicrophoneEnabledCalled() { |
| when(mSensorPrivacyManager.isSensorPrivacyEnabled(MICROPHONE, /* userId= */ 1)) |
| .thenReturn(true); |
| mPrivacyChipViewController.addPrivacyChipView(mFrameLayout); |
| verify(mBroadcastDispatcher).registerReceiver(mBroadcastReceiverArgumentCaptor.capture(), |
| argThat((IntentFilter filter) -> filter.hasAction(Intent.ACTION_USER_INFO_CHANGED)), |
| any(), any()); |
| reset(mExecutor); |
| when(mCarDeviceProvisionedController.getCurrentUser()).thenReturn(1); |
| mBroadcastReceiverArgumentCaptor.getValue().onReceive(mContext, |
| new Intent(Intent.ACTION_USER_INFO_CHANGED)); |
| verify(mExecutor).execute(mRunnableArgumentCaptor.capture()); |
| |
| mRunnableArgumentCaptor.getValue().run(); |
| |
| verify(mMicPrivacyChip).setMicrophoneEnabled(eq(false)); |
| } |
| |
| @Test |
| public void onUserChangeReceive_setMicrophoneEnabledCalled() { |
| when(mSensorPrivacyManager.isSensorPrivacyEnabled(MICROPHONE, /* userId= */ 1)) |
| .thenReturn(false); |
| mPrivacyChipViewController.addPrivacyChipView(mFrameLayout); |
| verify(mBroadcastDispatcher).registerReceiver(mBroadcastReceiverArgumentCaptor.capture(), |
| argThat((IntentFilter filter) -> filter.hasAction(Intent.ACTION_USER_FOREGROUND)), |
| any(), any()); |
| reset(mExecutor); |
| when(mCarDeviceProvisionedController.getCurrentUser()).thenReturn(1); |
| mBroadcastReceiverArgumentCaptor.getValue().onReceive(mContext, |
| new Intent(Intent.ACTION_USER_FOREGROUND)); |
| verify(mExecutor).execute(mRunnableArgumentCaptor.capture()); |
| |
| mRunnableArgumentCaptor.getValue().run(); |
| |
| verify(mMicPrivacyChip).setMicrophoneEnabled(eq(true)); |
| } |
| |
| @Test |
| public void isMicEnabled_sensorPrivacyEnabled_returnFalse() { |
| when(mSensorPrivacyManager.isSensorPrivacyEnabled(eq(MICROPHONE), anyInt())) |
| .thenReturn(true); |
| |
| assertThat(mPrivacyChipViewController.isMicEnabled()).isFalse(); |
| } |
| |
| @Test |
| public void isMicEnabled_sensorPrivacyDisabled_returnTrue() { |
| when(mSensorPrivacyManager.isSensorPrivacyEnabled(eq(MICROPHONE), anyInt())) |
| .thenReturn(false); |
| |
| assertThat(mPrivacyChipViewController.isMicEnabled()).isTrue(); |
| } |
| |
| @Test |
| public void toggleMic_micTurnedOn_sensorPrivacyEnabled() { |
| when(mSensorPrivacyManager.isSensorPrivacyEnabled(eq(MICROPHONE), anyInt())) |
| .thenReturn(false); |
| |
| mPrivacyChipViewController.toggleMic(); |
| |
| verify(mSensorPrivacyManager) |
| .setSensorPrivacy(eq(QS_TILE), eq(MICROPHONE), eq(true), anyInt()); |
| } |
| |
| @Test |
| public void toggleMic_micTurnedOff_sensorPrivacyDisabled() { |
| when(mSensorPrivacyManager.isSensorPrivacyEnabled(eq(MICROPHONE), anyInt())) |
| .thenReturn(true); |
| |
| mPrivacyChipViewController.toggleMic(); |
| |
| verify(mSensorPrivacyManager) |
| .setSensorPrivacy(eq(QS_TILE), eq(MICROPHONE), eq(false), anyInt()); |
| } |
| } |