1 /*
2  * Copyright (C) 2023 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.server.sdksandbox;
18 
19 import static com.google.common.truth.Truth.assertThat;
20 
21 import android.Manifest;
22 import android.app.sdksandbox.testutils.ProtoUtil;
23 import android.content.Context;
24 import android.os.Build;
25 import android.provider.DeviceConfig;
26 import android.util.ArrayMap;
27 
28 import androidx.test.platform.app.InstrumentationRegistry;
29 
30 import com.android.server.sdksandbox.proto.Services;
31 
32 import org.junit.After;
33 import org.junit.Before;
34 import org.junit.Test;
35 import org.mockito.Mockito;
36 
37 import java.util.List;
38 import java.util.Map;
39 
40 public class SdkSandboxSettingsListenerUnitTest {
41     private static final String PROPERTY_DISABLE_SANDBOX = "disable_sdk_sandbox";
42     private static final String PROPERTY_APPLY_SDK_SANDBOX_NEXT_RESTRICTIONS =
43             "apply_sdk_sandbox_next_restrictions";
44     private static final String PROPERTY_SERVICES_ALLOWLIST =
45             "services_allowlist_per_targetSdkVersion";
46     private SdkSandboxSettingsListener mSdkSandboxSettingsListener;
47 
48     @Before
setUp()49     public void setUp() {
50         Context context = InstrumentationRegistry.getInstrumentation().getContext();
51 
52         InstrumentationRegistry.getInstrumentation()
53                 .getUiAutomation()
54                 .adoptShellPermissionIdentity(
55                         Manifest.permission.READ_DEVICE_CONFIG,
56                         Manifest.permission.WRITE_DEVICE_CONFIG);
57 
58         mSdkSandboxSettingsListener =
59                 new SdkSandboxSettingsListener(
60                         context, Mockito.mock(SdkSandboxManagerService.class));
61     }
62 
63     @After
tearDown()64     public void tearDown() {
65         if (mSdkSandboxSettingsListener != null) {
66             mSdkSandboxSettingsListener.unregisterPropertiesListener();
67         }
68     }
69 
70     @Test
testSdkSandboxSettings_killSwitch()71     public void testSdkSandboxSettings_killSwitch() {
72         assertThat(mSdkSandboxSettingsListener.isKillSwitchEnabled()).isFalse();
73         setDeviceConfigProperty(PROPERTY_DISABLE_SANDBOX, "true");
74         assertThat(mSdkSandboxSettingsListener.isKillSwitchEnabled()).isTrue();
75         setDeviceConfigProperty(PROPERTY_DISABLE_SANDBOX, "false");
76         assertThat(mSdkSandboxSettingsListener.isKillSwitchEnabled()).isFalse();
77     }
78 
79     @Test
testOtherPropertyChangeDoesNotAffectKillSwitch()80     public void testOtherPropertyChangeDoesNotAffectKillSwitch() {
81         assertThat(mSdkSandboxSettingsListener.isKillSwitchEnabled()).isFalse();
82         setDeviceConfigProperty("other_property", "true");
83         assertThat(mSdkSandboxSettingsListener.isKillSwitchEnabled()).isFalse();
84     }
85 
86     @Test
testSdkSandboxSettings_applySdkSandboxRestrictionsNext()87     public void testSdkSandboxSettings_applySdkSandboxRestrictionsNext() {
88         assertThat(mSdkSandboxSettingsListener.applySdkSandboxRestrictionsNext()).isFalse();
89         setDeviceConfigProperty(PROPERTY_APPLY_SDK_SANDBOX_NEXT_RESTRICTIONS, "true");
90         assertThat(mSdkSandboxSettingsListener.applySdkSandboxRestrictionsNext()).isTrue();
91         setDeviceConfigProperty(PROPERTY_APPLY_SDK_SANDBOX_NEXT_RESTRICTIONS, "false");
92         assertThat(mSdkSandboxSettingsListener.applySdkSandboxRestrictionsNext()).isFalse();
93     }
94 
95     @Test
testServiceAllowlist_DeviceConfigNotAvailable()96     public void testServiceAllowlist_DeviceConfigNotAvailable() {
97         setDeviceConfigProperty(PROPERTY_SERVICES_ALLOWLIST, null);
98 
99         assertThat(
100                         mSdkSandboxSettingsListener.getServiceAllowlistForTargetSdkVersion(
101                                 /*targetSdkVersion=*/ 34))
102                 .isNull();
103     }
104 
105     @Test
testServiceAllowlist_DeviceConfigAllowlistApplied()106     public void testServiceAllowlist_DeviceConfigAllowlistApplied() {
107         /*
108          * Base64 encoded Service allowlist allowlist_per_target_sdk
109          * allowlist_per_target_sdk {
110          *   key: 33
111          *   value: {
112          *     allowed_services: {
113          *       action : "action.test.33"
114          *       packageName : "packageName.test.33"
115          *       componentClassName : "componentClassName.test.33"
116          *       componentPackageName : "componentPackageName.test.33"
117          *     }
118          *   }
119          * }
120          * allowlist_per_target_sdk {
121          *   key: 34
122          *   value: {
123          *     allowed_services: {
124          *       action : "action.test.34"
125          *       packageName : "packageName.test.34"
126          *       componentClassName : "componentClassName.test.34"
127          *       componentPackageName : "componentPackageName.test.34"
128          *     }
129          *   }
130          * }
131          */
132         ArrayMap<Integer, List<ArrayMap<String, String>>> allowedServicesMap = new ArrayMap<>();
133 
134         allowedServicesMap.put(
135                 Build.VERSION_CODES.TIRAMISU,
136                 getAllowedServicesMap(
137                         "action.test.33", "packageName.test.33",
138                         "componentClassName.test.33", "componentPackageName.test.33"));
139         allowedServicesMap.put(
140                 Build.VERSION_CODES.UPSIDE_DOWN_CAKE,
141                 getAllowedServicesMap(
142                         "action.test.34", "packageName.test.34",
143                         "componentClassName.test.34", "componentPackageName.test.34"));
144 
145         String encodedServiceAllowlist = ProtoUtil.encodeServiceAllowlist(allowedServicesMap);
146 
147         setDeviceConfigProperty(PROPERTY_SERVICES_ALLOWLIST, encodedServiceAllowlist);
148 
149         Services.AllowedServices allowedServices =
150                 mSdkSandboxSettingsListener.getServiceAllowlistForTargetSdkVersion(
151                         /*targetSdkVersion=*/ 33);
152         assertThat(allowedServices).isNotNull();
153 
154         verifyAllowlistEntryContents(
155                 allowedServices.getAllowedServices(0),
156                 /*action=*/ "action.test.33",
157                 /*packageName=*/ "packageName.test.33",
158                 /*componentClassName=*/ "componentClassName.test.33",
159                 /*componentPackageName=*/ "componentPackageName.test.33");
160 
161         allowedServices =
162                 mSdkSandboxSettingsListener.getServiceAllowlistForTargetSdkVersion(
163                         /*targetSdkVersion=*/ 34);
164         assertThat(allowedServices).isNotNull();
165 
166         verifyAllowlistEntryContents(
167                 allowedServices.getAllowedServices(0),
168                 /*action=*/ "action.test.34",
169                 /*packageName=*/ "packageName.test.34",
170                 /*componentClassName=*/ "componentClassName.test.34",
171                 /*componentPackageName=*/ "componentPackageName.test.34");
172     }
173 
verifyAllowlistEntryContents( Services.AllowedService allowedService, String action, String packageName, String componentClassName, String componentPackageName)174     private void verifyAllowlistEntryContents(
175             Services.AllowedService allowedService,
176             String action,
177             String packageName,
178             String componentClassName,
179             String componentPackageName) {
180         assertThat(allowedService.getAction()).isEqualTo(action);
181         assertThat(allowedService.getPackageName()).isEqualTo(packageName);
182         assertThat(allowedService.getComponentClassName()).isEqualTo(componentClassName);
183         assertThat(allowedService.getComponentPackageName()).isEqualTo(componentPackageName);
184     }
185 
setDeviceConfigProperty(String property, String value)186     private void setDeviceConfigProperty(String property, String value) {
187         // Explicitly calling the onPropertiesChanged method to avoid race conditions
188         if (value == null) {
189             // Map.of() does not handle null, so we need to use an ArrayMap to delete a property
190             ArrayMap<String, String> properties = new ArrayMap<>();
191             properties.put(property, null);
192             mSdkSandboxSettingsListener.onPropertiesChanged(
193                     new DeviceConfig.Properties(DeviceConfig.NAMESPACE_ADSERVICES, properties));
194         } else {
195             mSdkSandboxSettingsListener.onPropertiesChanged(
196                     new DeviceConfig.Properties(
197                             DeviceConfig.NAMESPACE_ADSERVICES, Map.of(property, value)));
198         }
199     }
200 
getAllowedServicesMap( String action, String packageName, String componentClassName, String componentPackageName)201     private List<ArrayMap<String, String>> getAllowedServicesMap(
202             String action,
203             String packageName,
204             String componentClassName,
205             String componentPackageName) {
206         ArrayMap<String, String> data = new ArrayMap<>(/* capacity= */ 4);
207         data.put("action", action);
208         data.put("packageName", packageName);
209         data.put("componentClassName", componentClassName);
210         data.put("componentPackageName", componentPackageName);
211 
212         return List.of(data);
213     }
214 }
215