/* * Copyright (C) 2023 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.ondevicepersonalization.services; import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_ERROR_REPORTED__ERROR_CODE__ON_DEVICE_PERSONALIZATION_ERROR; import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_ERROR_REPORTED__PPAPI_NAME__ODP; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertThrows; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.isA; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.adservices.ondevicepersonalization.Constants; import android.adservices.ondevicepersonalization.aidl.IOnDevicePersonalizationConfigServiceCallback; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; import android.os.Build; import android.os.IBinder; import androidx.test.core.app.ApplicationProvider; import androidx.test.filters.SdkSuppress; import androidx.test.rule.ServiceTestRule; import com.android.modules.utils.testing.ExtendedMockitoRule; import com.android.modules.utils.testing.ExtendedMockitoRule.MockStatic; import com.android.ondevicepersonalization.services.data.user.RawUserData; import com.android.ondevicepersonalization.services.data.user.UserDataCollector; import com.android.ondevicepersonalization.services.data.user.UserPrivacyStatus; import com.android.ondevicepersonalization.services.statsd.errorlogging.ClientErrorLogger; import org.junit.After; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; import org.mockito.Mock; import org.mockito.quality.Strictness; import java.util.TimeZone; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; @RunWith(JUnit4.class) @MockStatic(ClientErrorLogger.class) public class OnDevicePersonalizationConfigServiceTest { @Rule public final ExtendedMockitoRule extendedMockitoRule = new ExtendedMockitoRule.Builder(this).setStrictness(Strictness.LENIENT).build(); @Rule public final ServiceTestRule serviceRule = new ServiceTestRule(); private Context mContext = spy(ApplicationProvider.getApplicationContext()); private OnDevicePersonalizationConfigServiceDelegate mBinder; private UserPrivacyStatus mUserPrivacyStatus; private RawUserData mUserData; private UserDataCollector mUserDataCollector; @Mock private ClientErrorLogger mMockClientErrorLogger; @Before public void setup() throws Exception { PhFlagsTestUtil.setUpDeviceConfigPermissions(); PhFlagsTestUtil.disableGlobalKillSwitch(); PhFlagsTestUtil.disablePersonalizationStatusOverride(); when(mContext.checkCallingPermission(anyString())) .thenReturn(PackageManager.PERMISSION_GRANTED); mBinder = new OnDevicePersonalizationConfigServiceDelegate(mContext); mUserPrivacyStatus = UserPrivacyStatus.getInstanceForTest(); mUserPrivacyStatus.setPersonalizationStatusEnabled(false); mUserData = RawUserData.getInstance(); TimeZone pstTime = TimeZone.getTimeZone("GMT-08:00"); TimeZone.setDefault(pstTime); mUserDataCollector = UserDataCollector.getInstanceForTest(mContext); when(ClientErrorLogger.getInstance()).thenReturn(mMockClientErrorLogger); } @Test public void testThrowIfGlobalKillSwitchEnabled() throws Exception { PhFlagsTestUtil.enableGlobalKillSwitch(); try { assertThrows( IllegalStateException.class, () -> mBinder.setPersonalizationStatus(true, null) ); } finally { PhFlagsTestUtil.disableGlobalKillSwitch(); } } @Test public void testSetPersonalizationStatusNoCallingPermission() throws Exception { when(mContext.checkCallingPermission(anyString())) .thenReturn(PackageManager.PERMISSION_DENIED); assertThrows(SecurityException.class, () -> { mBinder.setPersonalizationStatus(true, null); }); } @Test public void testSetPersonalizationStatusChanged() throws Exception { assertFalse(mUserPrivacyStatus.isPersonalizationStatusEnabled()); populateUserData(); assertNotEquals(0, mUserData.utcOffset); assertTrue(mUserDataCollector.isInitialized()); CountDownLatch latch = new CountDownLatch(1); mBinder.setPersonalizationStatus(true, new IOnDevicePersonalizationConfigServiceCallback.Stub() { @Override public void onSuccess() { latch.countDown(); } @Override public void onFailure(int errorCode) { latch.countDown(); } }); latch.await(); assertTrue(mUserPrivacyStatus.isPersonalizationStatusEnabled()); assertEquals(0, mUserData.utcOffset); assertFalse(mUserDataCollector.isInitialized()); } @Test public void testSetPersonalizationStatusIfCallbackMissing() throws Exception { assertThrows(NullPointerException.class, () -> { mBinder.setPersonalizationStatus(true, null); }); } @Test @SdkSuppress(minSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE) public void testSetPersonalizationStatusThrowsRuntimeException() throws Exception { when(mContext.getSystemService(any(Class.class))).thenThrow(RuntimeException.class); CountDownLatch latch = new CountDownLatch(1); TestCallback callback = new TestCallback(latch); mBinder.setPersonalizationStatus(true, callback); assertTrue(latch.await(10000, TimeUnit.MILLISECONDS)); assertEquals(Constants.STATUS_INTERNAL_ERROR, callback.getErrCode()); verify(mMockClientErrorLogger) .logErrorWithExceptionInfo( isA(RuntimeException.class), eq(AD_SERVICES_ERROR_REPORTED__ERROR_CODE__ON_DEVICE_PERSONALIZATION_ERROR), eq(AD_SERVICES_ERROR_REPORTED__PPAPI_NAME__ODP)); } @Test public void testSetPersonalizationStatusNoOps() throws Exception { mUserPrivacyStatus.setPersonalizationStatusEnabled(true); populateUserData(); assertNotEquals(0, mUserData.utcOffset); int utcOffset = mUserData.utcOffset; assertTrue(mUserDataCollector.isInitialized()); CountDownLatch latch = new CountDownLatch(1); mBinder.setPersonalizationStatus(true, new IOnDevicePersonalizationConfigServiceCallback.Stub() { @Override public void onSuccess() { latch.countDown(); } @Override public void onFailure(int errorCode) { latch.countDown(); } }); latch.await(); assertTrue(mUserPrivacyStatus.isPersonalizationStatusEnabled()); // Adult data should not be roll-back'ed assertEquals(utcOffset, mUserData.utcOffset); assertTrue(mUserDataCollector.isInitialized()); } @Test public void testWithBoundService() throws TimeoutException { Intent serviceIntent = new Intent(mContext, OnDevicePersonalizationConfigServiceImpl.class); IBinder binder = serviceRule.bindService(serviceIntent); assertTrue(binder instanceof OnDevicePersonalizationConfigServiceDelegate); } @After public void tearDown() throws Exception { mUserDataCollector.clearUserData(mUserData); mUserDataCollector.clearMetadata(); } private void populateUserData() { mUserDataCollector.updateUserData(mUserData); } class TestCallback extends IOnDevicePersonalizationConfigServiceCallback.Stub { int mErrCode; CountDownLatch mLatch; TestCallback(CountDownLatch latch) { this.mLatch = latch; } @Override public void onSuccess() { } @Override public void onFailure(int errorCode) { mErrCode = errorCode; mLatch.countDown(); } public int getErrCode() { return mErrCode; } } }