/* * Copyright (C) 2022 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.server.logcat; import static android.os.Process.INVALID_UID; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.app.ActivityManager; import android.app.ActivityManagerInternal; import android.content.Context; import android.content.pm.PackageManager; import android.os.ILogd; import android.os.Looper; import android.os.UserHandle; import android.os.test.TestLooper; import com.android.server.LocalServices; import com.android.server.logcat.LogcatManagerService.Injector; import com.android.server.testutils.OffsettableClock; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import java.util.function.Supplier; /** * Tests for {@link com.android.server.logcat.LogcatManagerService}. * * Build/Install/Run: * atest FrameworksServicesTests:LogcatManagerServiceTest */ @SuppressWarnings("GuardedBy") public class LogcatManagerServiceTest { private static final String APP1_PACKAGE_NAME = "app1"; private static final int APP1_UID = 10001; private static final int APP1_GID = 10001; private static final int APP1_PID = 10001; private static final String APP2_PACKAGE_NAME = "app2"; private static final int APP2_UID = 10002; private static final int APP2_GID = 10002; private static final int APP2_PID = 10002; private static final int FD1 = 10; private static final int FD2 = 11; @Mock private Context mContextSpy; @Mock private ActivityManagerInternal mActivityManagerInternalMock; @Mock private PackageManager mPackageManagerMock; @Mock private ILogd mLogdMock; private LogcatManagerService mService; private LogcatManagerService.LogAccessDialogCallback mDialogCallback; private OffsettableClock mClock; private TestLooper mTestLooper; @Before public void setUp() throws Exception { MockitoAnnotations.initMocks(this); addLocalServiceMock(ActivityManagerInternal.class, mActivityManagerInternalMock); when(mActivityManagerInternalMock.getInstrumentationSourceUid(anyInt())) .thenReturn(INVALID_UID); mClock = new OffsettableClock.Stopped(); mTestLooper = new TestLooper(mClock::now); when(mContextSpy.getPackageManager()).thenReturn(mPackageManagerMock); when(mPackageManagerMock.getPackagesForUid(APP1_UID)).thenReturn( new String[]{APP1_PACKAGE_NAME}); when(mPackageManagerMock.getPackagesForUid(APP2_UID)).thenReturn( new String[]{APP2_PACKAGE_NAME}); when(mActivityManagerInternalMock.getPackageNameByPid(APP1_PID)).thenReturn( APP1_PACKAGE_NAME); when(mActivityManagerInternalMock.getPackageNameByPid(APP2_PID)).thenReturn( APP2_PACKAGE_NAME); mService = new LogcatManagerService(mContextSpy, new Injector() { @Override protected Supplier createClock() { return mClock::now; } @Override protected Looper getLooper() { return mTestLooper.getLooper(); } @Override protected ILogd getLogdService() { return mLogdMock; } }); mDialogCallback = mService.getDialogCallback(); mService.onStart(); } @After public void tearDown() throws Exception { LocalServices.removeServiceForTest(ActivityManagerInternal.class); } /** * Creates a mock and registers it to {@link LocalServices}. */ private static void addLocalServiceMock(Class clazz, T mock) { LocalServices.removeServiceForTest(clazz); LocalServices.addService(clazz, mock); } @Test public void test_RequestFromBackground_DeclinedWithoutPrompt() throws Exception { when(mActivityManagerInternalMock.getUidProcessState(APP1_UID)).thenReturn( ActivityManager.PROCESS_STATE_RECEIVER); mService.getBinderService().startThread(APP1_UID, APP1_GID, APP1_PID, FD1); mTestLooper.dispatchAll(); verify(mLogdMock).decline(APP1_UID, APP1_GID, APP1_PID, FD1); verify(mLogdMock, never()).approve(APP1_UID, APP1_GID, APP1_PID, FD1); verify(mContextSpy, never()).startActivityAsUser(any(), any()); } @Test public void test_RequestFromBackground_ApprovedIfInstrumented() throws Exception { when(mActivityManagerInternalMock.getInstrumentationSourceUid(APP1_UID)) .thenReturn(APP1_UID); when(mActivityManagerInternalMock.getUidProcessState(APP1_UID)).thenReturn( ActivityManager.PROCESS_STATE_RECEIVER); mService.getBinderService().startThread(APP1_UID, APP1_GID, APP1_PID, FD1); mTestLooper.dispatchAll(); verify(mLogdMock).approve(APP1_UID, APP1_GID, APP1_PID, FD1); verify(mLogdMock, never()).decline(APP1_UID, APP1_GID, APP1_PID, FD1); verify(mContextSpy, never()).startActivityAsUser(any(), any()); } @Test public void test_RequestFromForegroundService_DeclinedWithoutPrompt() throws Exception { when(mActivityManagerInternalMock.getUidProcessState(APP1_UID)).thenReturn( ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE); mService.getBinderService().startThread(APP1_UID, APP1_GID, APP1_PID, FD1); mTestLooper.dispatchAll(); verify(mLogdMock).decline(APP1_UID, APP1_GID, APP1_PID, FD1); verify(mLogdMock, never()).approve(APP1_UID, APP1_GID, APP1_PID, FD1); verify(mContextSpy, never()).startActivityAsUser(any(), any()); } @Test public void test_RequestFromTop_ShowsPrompt() throws Exception { when(mActivityManagerInternalMock.getUidProcessState(APP1_UID)).thenReturn( ActivityManager.PROCESS_STATE_TOP); mService.getBinderService().startThread(APP1_UID, APP1_GID, APP1_PID, FD1); mTestLooper.dispatchAll(); verify(mLogdMock, never()).approve(APP1_UID, APP1_GID, APP1_PID, FD1); verify(mLogdMock, never()).decline(APP1_UID, APP1_GID, APP1_PID, FD1); verify(mContextSpy, times(1)).startActivityAsUser(any(), eq(UserHandle.SYSTEM)); } @Test public void test_RequestFromTop_NoInteractionWithPrompt_DeclinesAfterTimeout() throws Exception { when(mActivityManagerInternalMock.getUidProcessState(APP1_UID)).thenReturn( ActivityManager.PROCESS_STATE_TOP); mService.getBinderService().startThread(APP1_UID, APP1_GID, APP1_PID, FD1); mTestLooper.dispatchAll(); advanceTime(LogcatManagerService.PENDING_CONFIRMATION_TIMEOUT_MILLIS); verify(mLogdMock, never()).approve(APP1_UID, APP1_GID, APP1_PID, FD1); verify(mLogdMock).decline(APP1_UID, APP1_GID, APP1_PID, FD1); } @Test public void test_RequestFromTop_Approved() throws Exception { when(mActivityManagerInternalMock.getUidProcessState(APP1_UID)).thenReturn( ActivityManager.PROCESS_STATE_TOP); mService.getBinderService().startThread(APP1_UID, APP1_GID, APP1_PID, FD1); mTestLooper.dispatchAll(); verify(mContextSpy, times(1)).startActivityAsUser(any(), eq(UserHandle.SYSTEM)); mDialogCallback.approveAccessForClient(APP1_UID, APP1_PACKAGE_NAME); mTestLooper.dispatchAll(); verify(mLogdMock, times(1)).approve(APP1_UID, APP1_GID, APP1_PID, FD1); verify(mLogdMock, never()).decline(APP1_UID, APP1_GID, APP1_PID, FD1); } @Test public void test_RequestFromTop_Declined() throws Exception { when(mActivityManagerInternalMock.getUidProcessState(APP1_UID)).thenReturn( ActivityManager.PROCESS_STATE_TOP); mService.getBinderService().startThread(APP1_UID, APP1_GID, APP1_PID, FD1); mTestLooper.dispatchAll(); verify(mContextSpy, times(1)).startActivityAsUser(any(), eq(UserHandle.SYSTEM)); mDialogCallback.declineAccessForClient(APP1_UID, APP1_PACKAGE_NAME); mTestLooper.dispatchAll(); verify(mLogdMock, never()).approve(APP1_UID, APP1_GID, APP1_PID, FD1); verify(mLogdMock, times(1)).decline(APP1_UID, APP1_GID, APP1_PID, FD1); } @Test public void test_RequestFromTop_MultipleRequestsApprovedTogether() throws Exception { when(mActivityManagerInternalMock.getUidProcessState(APP1_UID)).thenReturn( ActivityManager.PROCESS_STATE_TOP); mService.getBinderService().startThread(APP1_UID, APP1_GID, APP1_PID, FD1); mService.getBinderService().startThread(APP1_UID, APP1_GID, APP1_PID, FD2); mTestLooper.dispatchAll(); verify(mContextSpy, times(1)).startActivityAsUser(any(), eq(UserHandle.SYSTEM)); verify(mLogdMock, never()).approve(eq(APP1_UID), eq(APP1_GID), eq(APP1_PID), anyInt()); verify(mLogdMock, never()).decline(eq(APP1_UID), eq(APP1_GID), eq(APP1_PID), anyInt()); mDialogCallback.approveAccessForClient(APP1_UID, APP1_PACKAGE_NAME); mTestLooper.dispatchAll(); verify(mLogdMock, times(1)).approve(APP1_UID, APP1_GID, APP1_PID, FD1); verify(mLogdMock, times(1)).approve(APP1_UID, APP1_GID, APP1_PID, FD2); verify(mLogdMock, never()).decline(APP1_UID, APP1_GID, APP1_PID, FD1); verify(mLogdMock, never()).decline(APP1_UID, APP1_GID, APP1_PID, FD2); } @Test public void test_RequestFromTop_MultipleRequestsDeclinedTogether() throws Exception { when(mActivityManagerInternalMock.getUidProcessState(APP1_UID)).thenReturn( ActivityManager.PROCESS_STATE_TOP); mService.getBinderService().startThread(APP1_UID, APP1_GID, APP1_PID, FD1); mService.getBinderService().startThread(APP1_UID, APP1_GID, APP1_PID, FD2); mTestLooper.dispatchAll(); verify(mContextSpy, times(1)).startActivityAsUser(any(), eq(UserHandle.SYSTEM)); verify(mLogdMock, never()).approve(eq(APP1_UID), eq(APP1_GID), eq(APP1_PID), anyInt()); verify(mLogdMock, never()).decline(eq(APP1_UID), eq(APP1_GID), eq(APP1_PID), anyInt()); mDialogCallback.declineAccessForClient(APP1_UID, APP1_PACKAGE_NAME); mTestLooper.dispatchAll(); verify(mLogdMock, times(1)).decline(APP1_UID, APP1_GID, APP1_PID, FD1); verify(mLogdMock, times(1)).decline(APP1_UID, APP1_GID, APP1_PID, FD2); verify(mLogdMock, never()).approve(APP1_UID, APP1_GID, APP1_PID, FD1); verify(mLogdMock, never()).approve(APP1_UID, APP1_GID, APP1_PID, FD2); } @Test public void test_RequestFromTop_Approved_DoesNotShowPromptAgain() throws Exception { when(mActivityManagerInternalMock.getUidProcessState(APP1_UID)).thenReturn( ActivityManager.PROCESS_STATE_TOP); mService.getBinderService().startThread(APP1_UID, APP1_GID, APP1_PID, FD1); mTestLooper.dispatchAll(); mDialogCallback.approveAccessForClient(APP1_UID, APP1_PACKAGE_NAME); mTestLooper.dispatchAll(); mService.getBinderService().startThread(APP1_UID, APP1_GID, APP1_PID, FD2); mTestLooper.dispatchAll(); verify(mContextSpy, times(1)).startActivityAsUser(any(), eq(UserHandle.SYSTEM)); verify(mLogdMock, times(1)).approve(APP1_UID, APP1_GID, APP1_PID, FD1); verify(mLogdMock, times(1)).approve(APP1_UID, APP1_GID, APP1_PID, FD2); verify(mLogdMock, never()).decline(APP1_UID, APP1_GID, APP1_PID, FD2); } @Test public void test_RequestFromTop_Declined_DoesNotShowPromptAgain() throws Exception { when(mActivityManagerInternalMock.getUidProcessState(APP1_UID)).thenReturn( ActivityManager.PROCESS_STATE_TOP); mService.getBinderService().startThread(APP1_UID, APP1_GID, APP1_PID, FD1); mTestLooper.dispatchAll(); mDialogCallback.declineAccessForClient(APP1_UID, APP1_PACKAGE_NAME); mTestLooper.dispatchAll(); mService.getBinderService().startThread(APP1_UID, APP1_GID, APP1_PID, FD2); mTestLooper.dispatchAll(); verify(mContextSpy, times(1)).startActivityAsUser(any(), eq(UserHandle.SYSTEM)); verify(mLogdMock, times(1)).decline(APP1_UID, APP1_GID, APP1_PID, FD1); verify(mLogdMock, times(1)).decline(APP1_UID, APP1_GID, APP1_PID, FD2); verify(mLogdMock, never()).approve(APP1_UID, APP1_GID, APP1_PID, FD2); } @Test public void test_RequestFromTop_Approved_ShowsPromptForDifferentClient() throws Exception { when(mActivityManagerInternalMock.getUidProcessState(APP1_UID)).thenReturn( ActivityManager.PROCESS_STATE_TOP); when(mActivityManagerInternalMock.getUidProcessState(APP2_UID)).thenReturn( ActivityManager.PROCESS_STATE_TOP); mService.getBinderService().startThread(APP1_UID, APP1_GID, APP1_PID, FD1); mTestLooper.dispatchAll(); mDialogCallback.approveAccessForClient(APP1_UID, APP1_PACKAGE_NAME); mTestLooper.dispatchAll(); mService.getBinderService().startThread(APP2_UID, APP2_GID, APP2_PID, FD2); mTestLooper.dispatchAll(); verify(mContextSpy, times(2)).startActivityAsUser(any(), eq(UserHandle.SYSTEM)); verify(mLogdMock, never()).decline(APP2_UID, APP2_GID, APP2_PID, FD2); verify(mLogdMock, never()).approve(APP2_UID, APP2_GID, APP2_PID, FD2); } @Test public void test_RequestFromTop_Approved_ShowPromptAgainAfterTimeout() throws Exception { when(mActivityManagerInternalMock.getUidProcessState(APP1_UID)).thenReturn( ActivityManager.PROCESS_STATE_TOP); mService.getBinderService().startThread(APP1_UID, APP1_GID, APP1_PID, FD1); mTestLooper.dispatchAll(); mDialogCallback.declineAccessForClient(APP1_UID, APP1_PACKAGE_NAME); mTestLooper.dispatchAll(); advanceTime(LogcatManagerService.STATUS_EXPIRATION_TIMEOUT_MILLIS); mService.getBinderService().startThread(APP1_UID, APP1_GID, APP1_PID, FD1); mTestLooper.dispatchAll(); verify(mContextSpy, times(2)).startActivityAsUser(any(), eq(UserHandle.SYSTEM)); } private void advanceTime(long timeMs) { mClock.fastForward(timeMs); mTestLooper.dispatchAll(); } }