1 /*
2  * Copyright (C) 2024 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 package com.android.server.pm.test.preverifieddomains
17 
18 import android.app.UiAutomation
19 import android.content.ComponentName
20 import android.content.Context
21 import android.content.pm.Flags
22 import android.content.pm.PackageInstaller
23 import android.content.pm.PackageInstaller.SessionParams.MODE_FULL_INSTALL
24 import android.content.pm.PackageManager
25 import android.os.Build
26 import android.platform.test.annotations.RequiresFlagsEnabled
27 import android.platform.test.flag.junit.CheckFlagsRule
28 import android.platform.test.flag.junit.DeviceFlagsValueProvider
29 import android.provider.DeviceConfig.NAMESPACE_PACKAGE_MANAGER_SERVICE
30 import androidx.test.ext.junit.runners.AndroidJUnit4
31 import androidx.test.platform.app.InstrumentationRegistry
32 import com.android.compatibility.common.util.DeviceConfigStateManager
33 import com.google.common.truth.Truth.assertThat
34 import org.junit.After
35 import org.junit.AfterClass
36 import org.junit.Assert.assertThrows
37 import org.junit.Assume.assumeTrue
38 import org.junit.Before
39 import org.junit.BeforeClass
40 import org.junit.Rule
41 import org.junit.Test
42 import org.junit.function.ThrowingRunnable
43 import org.junit.runner.RunWith
44 
45 /**
46  * Pre-verified domains API tests. These tests require the device's default instant app
47  * installer to be disabled temporarily and is only able to run on ENG builds.
48  */
49 @RunWith(AndroidJUnit4::class)
50 class PreVerifiedDomainsTests {
51     companion object {
52         private const val PROPERTY_PRE_VERIFIED_DOMAINS_COUNT_LIMIT =
53                 "pre_verified_domains_count_limit"
54         private const val PROPERTY_PRE_VERIFIED_DOMAIN_LENGTH_LIMIT =
55                 "pre_verified_domain_length_limit"
56         private const val TEMP_COUNT_LIMIT = 10
57         private const val TEMP_LENGTH_LIMIT = 15
58         private val testDomains = setOf("com.foo", "com.bar")
59 
60         private val uiAutomation: UiAutomation =
61                 InstrumentationRegistry.getInstrumentation().getUiAutomation()
62         private lateinit var packageManager: PackageManager
63         private var defaultInstantAppInstaller: ComponentName? = null
64         private lateinit var fakeInstantAppInstaller: ComponentName
65 
66         @JvmStatic
67         @BeforeClass
setupBeforeClassnull68         fun setupBeforeClass() {
69             val context = InstrumentationRegistry.getInstrumentation().getContext()
70             packageManager = context.packageManager
71             defaultInstantAppInstaller = packageManager.getInstantAppInstallerComponent()
72             fakeInstantAppInstaller = ComponentName(
73                     context.packageName,
74                     context.packageName + ".FakeInstantAppInstallerActivity")
75             // By disabling the original instant app installer, this test app becomes the instant
76             // app installer
77             uiAutomation.adoptShellPermissionIdentity()
78             try {
79                 // Enable the fake instant app installer before disabling the default one
80                 packageManager.setComponentEnabledSetting(
81                         fakeInstantAppInstaller,
82                         PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
83                         PackageManager.DONT_KILL_APP
84                 )
85                 if (defaultInstantAppInstaller != null) {
86                     packageManager.setComponentEnabledSetting(
87                             defaultInstantAppInstaller!!,
88                             PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
89                             0
90                     )
91                 }
92             } finally {
93                 uiAutomation.dropShellPermissionIdentity()
94             }
95             assertThat(fakeInstantAppInstaller).isEqualTo(
96                     packageManager.getInstantAppInstallerComponent())
97         }
98 
99         @JvmStatic
100         @AfterClass
restoreInstantAppInstallernull101         fun restoreInstantAppInstaller() {
102             uiAutomation.adoptShellPermissionIdentity()
103             try {
104                 // Enable the original instant app installer before disabling the temporary one, so
105                 // there won't be a time when the device doesn't have a valid instant app installer
106                 if (defaultInstantAppInstaller != null) {
107                     packageManager.setComponentEnabledSetting(
108                             defaultInstantAppInstaller!!,
109                             PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
110                             0
111                     )
112                 }
113                 // Be careful not to let this test process killed, or the test will be considered
114                 // as failed
115                 packageManager.setComponentEnabledSetting(
116                         fakeInstantAppInstaller,
117                         PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
118                         PackageManager.DONT_KILL_APP
119                 )
120             } finally {
121                 uiAutomation.dropShellPermissionIdentity()
122             }
123         }
124     }
125 
126     private lateinit var packageInstaller: PackageInstaller
127     private lateinit var context: Context
128     private lateinit var packageManager: PackageManager
129     private var mDefaultCountLimit: String? = null
130     private var mDefaultLengthLimit: String? = null
131 
132     @JvmField
133     @Rule
134     val mCheckFlagsRule: CheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule()
135 
136     @Before
setUpnull137     fun setUp() {
138         context = InstrumentationRegistry.getInstrumentation().getContext()
139         packageManager = context.packageManager
140         packageInstaller = packageManager.packageInstaller
141         mDefaultCountLimit = getLimitFromDeviceConfig(PROPERTY_PRE_VERIFIED_DOMAINS_COUNT_LIMIT)
142         mDefaultLengthLimit = getLimitFromDeviceConfig(PROPERTY_PRE_VERIFIED_DOMAIN_LENGTH_LIMIT)
143     }
144 
145     @After
cleanUpnull146     fun cleanUp() {
147         setLimitInDeviceConfig(PROPERTY_PRE_VERIFIED_DOMAINS_COUNT_LIMIT, mDefaultCountLimit)
148         setLimitInDeviceConfig(PROPERTY_PRE_VERIFIED_DOMAIN_LENGTH_LIMIT, mDefaultLengthLimit)
149     }
150 
151     @RequiresFlagsEnabled(Flags.FLAG_SET_PRE_VERIFIED_DOMAINS)
152     @Test
testSetPreVerifiedDomainsExceedsCountLimitnull153     fun testSetPreVerifiedDomainsExceedsCountLimit() {
154         // Temporarily change the count limit to a much smaller number so the test can exceed it
155         setLimitInDeviceConfig(
156                 PROPERTY_PRE_VERIFIED_DOMAINS_COUNT_LIMIT,
157                 TEMP_COUNT_LIMIT.toString()
158         )
159         val domains = mutableSetOf<String>()
160         for (i in 0 until(TEMP_COUNT_LIMIT + 1)) {
161             domains.add("domain$i")
162         }
163 
164         uiAutomation.adoptShellPermissionIdentity(android.Manifest.permission.ACCESS_INSTANT_APPS)
165         try {
166             assertThrows(
167                     IllegalArgumentException::class.java,
168                     ThrowingRunnable {
169                         createSessionWithPreVerifiedDomains(domains)
170                     }
171             )
172         } finally {
173             uiAutomation.dropShellPermissionIdentity()
174         }
175     }
176 
177     @RequiresFlagsEnabled(Flags.FLAG_SET_PRE_VERIFIED_DOMAINS)
178     @Test
testSetPreVerifiedDomainsExceedsLengthLimitnull179     fun testSetPreVerifiedDomainsExceedsLengthLimit() {
180         // Temporarily change the count limit to a much smaller number so the test can exceed it
181         setLimitInDeviceConfig(
182                 PROPERTY_PRE_VERIFIED_DOMAIN_LENGTH_LIMIT,
183                 TEMP_LENGTH_LIMIT.toString()
184         )
185         val invalidDomain = "a".repeat(TEMP_LENGTH_LIMIT + 1)
186 
187         uiAutomation.adoptShellPermissionIdentity(android.Manifest.permission.ACCESS_INSTANT_APPS)
188         try {
189             assertThrows(
190                     "Pre-verified domain: [" +
191                             invalidDomain + " ] exceeds maximum length allowed: " +
192                             TEMP_LENGTH_LIMIT,
193                     IllegalArgumentException::class.java,
194                     ThrowingRunnable {
195                         createSessionWithPreVerifiedDomains(setOf(invalidDomain))
196                     }
197             )
198         } finally {
199             uiAutomation.dropShellPermissionIdentity()
200         }
201     }
202 
203     @RequiresFlagsEnabled(Flags.FLAG_SET_PRE_VERIFIED_DOMAINS)
204     @Test
testSetAndGetPreVerifiedDomainsnull205     fun testSetAndGetPreVerifiedDomains() {
206         // Fake instant app installers can only work on ENG builds
207         assumeTrue("eng" == Build.TYPE)
208         var session: PackageInstaller.Session? = null
209         uiAutomation.adoptShellPermissionIdentity(android.Manifest.permission.ACCESS_INSTANT_APPS)
210         try {
211             val sessionId = createSessionWithPreVerifiedDomains(testDomains)
212             session = packageInstaller.openSession(sessionId)
213             assertThat(session.getPreVerifiedDomains()).isEqualTo(testDomains)
214         } finally {
215             uiAutomation.dropShellPermissionIdentity()
216             session?.abandon()
217         }
218     }
219 
createSessionWithPreVerifiedDomainsnull220     private fun createSessionWithPreVerifiedDomains(domains: Set<String>): Int {
221         val sessionParam = PackageInstaller.SessionParams(MODE_FULL_INSTALL)
222         val sessionId = packageInstaller.createSession(sessionParam)
223         val session = packageInstaller.openSession(sessionId)
224         try {
225             session.setPreVerifiedDomains(domains)
226         } catch (e: Exception) {
227             session.abandon()
228             throw e
229         }
230         return sessionId
231     }
232 
getLimitFromDeviceConfignull233     private fun getLimitFromDeviceConfig(propertyName: String): String? {
234         val stateManager = DeviceConfigStateManager(
235                 context,
236                 NAMESPACE_PACKAGE_MANAGER_SERVICE,
237                 propertyName
238         )
239         return stateManager.get()
240     }
241 
setLimitInDeviceConfignull242     private fun setLimitInDeviceConfig(propertyName: String, value: String?) {
243         val stateManager = DeviceConfigStateManager(
244                 context,
245                 NAMESPACE_PACKAGE_MANAGER_SERVICE,
246                 propertyName
247         )
248         val currentValue = stateManager.get()
249         if (currentValue != value) {
250             // Only change the value if the current value is different
251             stateManager.set(value)
252         }
253     }
254 }
255